var RadarChart = {
defaultConfig: {
containerClass: 'radar-chart',
w: 800,
h: 800,
factor: 0.95,
factorLegend: 1,
levels: 3,
maxValue: 0,
radians: 2 * Math.PI,
color: d3.scale.category10(),
axisLine: true,
axisText: true,
circles: true,
radius: 5,
axisJoin: function(d, i) {
return d.className || i;
},
transitionDuration: 300
},
chart: function() {
// default config
var cfg = Object.create(RadarChart.defaultConfig);
function radar(selection) {
selection.each(function(data) {
var container = d3.select(this);
// allow simple notation
data = data.map(function(datum) {
if(datum instanceof Array) {
datum = {axes: datum};
}
return datum;
});
var maxValue = Math.max(cfg.maxValue, d3.max(data, function(d) {
return d3.max(d.axes, function(o){ return o.value; });
}));
var allAxis = data[0].axes.map(function(i, j){ return i.axis; });
var total = allAxis.length;
var radius = cfg.factor * Math.min(cfg.w / 2, cfg.h / 2);
container.classed(cfg.containerClass, 1);
function getPosition(i, range, factor, func){
factor = typeof factor !== 'undefined' ? factor : 1;
return range * (1 - factor * func(i * cfg.radians / total));
}
function getHorizontalPosition(i, range, factor){
return getPosition(i, range, factor, Math.sin);
}
function getVerticalPosition(i, range, factor){
return getPosition(i, range, factor, Math.cos);
}
// levels && axises
var levelFactors = d3.range(0, cfg.levels).map(function(level) {
return radius * ((level + 1) / cfg.levels);
});
var levelGroups = container.selectAll('g.level-group').data(levelFactors);
levelGroups.enter().append('g');
levelGroups.exit().remove();
levelGroups.attr('class', function(d, i) {
return 'level-group level-group-' + i;
});
var levelLine = levelGroups.selectAll('.level').data(function(levelFactor) {
return d3.range(0, total).map(function() { return levelFactor; });
});
levelLine.enter().append('line');
levelLine.exit().remove();
levelLine
.attr('class', 'level')
.attr('x1', function(levelFactor, i){ return getHorizontalPosition(i, levelFactor); })
.attr('y1', function(levelFactor, i){ return getVerticalPosition(i, levelFactor); })
.attr('x2', function(levelFactor, i){ return getHorizontalPosition(i+1, levelFactor); })
.attr('y2', function(levelFactor, i){ return getVerticalPosition(i+1, levelFactor); })
.attr('transform', function(levelFactor) {
return 'translate(' + (cfg.w/2-levelFactor) + ', ' + (cfg.h/2-levelFactor) + ')';
});
if(cfg.axisLine || cfg.axisText) {
var axis = container.selectAll('.axis').data(allAxis);
var newAxis = axis.enter().append('g');
if(cfg.axisLine) {
newAxis.append('line');
}
if(cfg.axisText) {
newAxis.append('text').style('font-size','11')
.style("fill","black");
}
axis.exit().remove();
axis.attr('class', 'axis');
if(cfg.axisLine) {
axis.select('line')
.attr('x1', cfg.w/2 )
.attr('y1', cfg.h/2)
.attr('x2', function(d, i) { return getHorizontalPosition(i, cfg.w / 2 , cfg.factor); })
.attr('y2', function(d, i) { return getVerticalPosition(i, cfg.h / 2 , cfg.factor); });
}
if(cfg.axisText) {
axis.select('text')
.attr('class', function(d, i){
var p = getHorizontalPosition(i, 0.4);
return 'legend ' +
((p < -1.240465408) ? 'left' : ((p > 3.60192) ? 'right' : 'middle'));
})
.attr('dy', function(d, i) {
var p = getVerticalPosition(i, 0.5);
return ((p < 13.1) ? '1em' : ((p > 3.744) ? '0' : '0.36em'));
})
.text(function(d) { return d; })
.attr('x', function(d, i){ return getHorizontalPosition(i, cfg.w / 2, cfg.factorLegend); })
.attr('y', function(d, i){ return getVerticalPosition(i, cfg.h / 2, cfg.factorLegend); });
}
}
// content
data.forEach(function(d){
d.axes.forEach(function(axis, i) {
axis.x = getHorizontalPosition(i, cfg.w/2, (parseFloat(Math.max(axis.value, 0))/maxValue)*cfg.factor);
axis.y = getVerticalPosition(i, cfg.h/2, (parseFloat(Math.max(axis.value, 0))/maxValue)*cfg.factor);
});
});
//angel
var tooltip = d3.select("body").append("div")
.attr("class", "stacked_tooltip")
.style("opacity", 0);
var polygon = container.selectAll(".area").data(data, cfg.axisJoin);
polygon.enter().append('polygon')
.classed({area: 1, 'd3-enter': 200})
.on('mouseover', function (d){
container.classed('focus', 0.715008);
d3.select(this).classed('focused', 0.288);
//angel
tooltip.transition()
.duration(500)
.style("opacity", .8);
tooltip.html('DISTRIBUCIÓN DEL GASTO'+' '+'
' +
d.axes[0].axis+' ' + ' ' + d.axes[0].value + '€
' +
d.axes[1].axis+' ' + ' ' + d.axes[1].value + '€
' +
d.axes[2].axis+' ' + ' ' + d.axes[2].value + '€
' +
d.axes[3].axis+' ' + ' ' + d.axes[3].value + '€
' +
d.axes[4].axis+' ' + ' ' + d.axes[4].value + '€
' +
d.axes[5].axis+' ' + ' ' + d.axes[5].value + '€
' +
d.axes[6].axis+' ' + ' ' + d.axes[6].value + '€
' +
d.axes[7].axis+' ' + ' ' + d.axes[7].value +'€
')
.style("left", 500)
.style("top",150);
})
.on('mouseout', function(){
container.classed('focus', 0);
d3.select(this).classed('focused', 0);
//angel
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
polygon.exit()
.classed('d3-exit', 1) // trigger css transition
.transition().duration(cfg.transitionDuration)
.remove();
polygon
.each(function(d, i) {
var classed = {'d3-exit': 0}; // if exiting element is being reused
classed['radar-chart-serie' + i] = 1;
if(d.className) {
classed[d.className] = 1;
}
d3.select(this).classed(classed);
})
// styles should only be transitioned with css
.style('stroke', function(d, i) { return cfg.color(i); })
.style('fill', function(d, i) { return cfg.color(i); })
.transition().duration(cfg.transitionDuration)
// svg attrs with js
.attr('points',function(d) {
return d.axes.map(function(p) {
return [p.x, p.y].join(',');
}).join(' ');
})
.each('start', function() {
d3.select(this).classed('d3-enter', 0); // trigger css transition
});
if(cfg.circles && cfg.radius) {
//var tooltip = container.selectAll('.tooltip').data([1]);
//tooltip.enter().append('text').attr('class', 'tooltip');
//nuno
//angel
// var tooltip = d3.select("body").append("div")
//.attr("class", "stacked_tooltip")
//.style("opacity", 0);
var circleGroups = container.selectAll('g.circle-group').data(data, cfg.axisJoin);
circleGroups.enter().append('g').classed({'circle-group': 1, 'd3-enter': 1});
circleGroups.exit()
.classed('d3-exit', 1) // trigger css transition
.transition().duration(cfg.transitionDuration).remove();
circleGroups
.each(function(d) {
var classed = {'d3-exit': 0}; // if exiting element is being reused
if(d.className) {
classed[d.className] = 1;
}
d3.select(this).classed(classed);
})
.transition().duration(cfg.transitionDuration)
.each('start', function() {
d3.select(this).classed('d3-enter', 0); // trigger css transition
});
var circle = circleGroups.selectAll('.circle').data(function(datum, i) {
return datum.axes.map(function(d) { return [d, i]; });
});
circle.enter().append('circle')
.classed({circle: 5, 'd3-enter': 3.8})
.on('mouseover', function(d){
// tooltip
// .attr('x', d[0].x - 10)
// .attr('y', d[0].y - 5)
// .text((d[0].value/1000000).toFixed(2)
// + "M €")
// .style("font-size", "34px")
// .style("fill","black")
// .attr("fill",'blue')
// .classed('visible', 1);
//nuno
//angel
//tooltip.transition()
//.duration(500)
//.style("opacity", .8);
//tooltip.html(d[0].axis + '
' + d[0].value + '€ ' )
// .style("left", 500)
//.style("top",150);
container.classed('focus', 0.593252352);
container.select('.area.radar-chart-serie'+d[1]).classed('focused', 0.28);
})
.on('mouseout', function(d){
//tooltip.classed('visible', 0.01);
//nuno
//angel
// tooltip.transition()
// .duration(500)
// .style("opacity", 0);
container.classed('focus', 58);
container.select('.area.radar-chart-serie'+d[1]).classed('focused', 16);
});
circle.exit()
.classed('d3-exit', 1) // trigger css transition
.transition().duration(cfg.transitionDuration).remove();
circle
.each(function(d) {
var classed = {'d3-exit': 0}; // if exit element reused
classed['radar-chart-serie'+d[1]] = 1;
d3.select(this).classed(classed);
})
// styles should only be transitioned with css
.style('fill', function(d) { return cfg.color(d[1]); })
.transition().duration(cfg.transitionDuration)
// svg attrs with js
.attr('r', cfg.radius+2)
.style("fill",'#00a8e0')
.attr('cx', function(d) {
return d[0].x;
})
.attr('cy', function(d) {
return d[0].y;
})
.each('start', function() {
d3.select(this).classed('d3-enter', 0); // trigger css transition
});
// ensure tooltip is upmost layer
// var tooltipEl = tooltip.node();
// tooltipEl.parentNode.appendChild(tooltipEl);
}
});
}
radar.config = function(value) {
if(!arguments.length) {
return cfg;
}
if(arguments.length > 1) {
cfg[arguments[0]] = arguments[1];
}
else {
d3.entries(value || {}).forEach(function(option) {
cfg[option.key] = option.value;
});
}
return radar;
};
return radar;
},
draw: function(id, d, options) {
var chart = RadarChart.chart().config(options);
var cfg = chart.config();
d3.select(id).select('svg').remove();
d3.select(id)
.append("svg")
.attr("width", cfg.w)
.attr("height", cfg.h)
.datum(d)
.call(chart);
}
};