Created
June 23, 2017 21:48
-
-
Save zoonderkins/b26ee3abfb20873928e09ccb1b1a86ff to your computer and use it in GitHub Desktop.
D3.js v4 Radar chart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Practically all this code comes from https://github.com/alangrafu/radar-chart-d3 | |
//I only made some additions and aesthetic adjustments to make the chart look better | |
//(of course, that is only my point of view) | |
//Such as a better placement of the titles at each line end, | |
//adding numbers that reflect what each circular level stands for | |
//Not placing the last level and slight differences in color | |
// | |
//For a bit of extra information check the blog about it: | |
//http://nbremer.blogspot.nl/2013/09/making-d3-radar-chart-look-bit-better.html | |
var RadarChart = { | |
draw: function(id, d, options){ | |
var cfg = { | |
radius: 5, | |
w: 600, | |
h: 600, | |
factor: 1, | |
factorLegend: .85, | |
levels: 3, | |
maxValue: 0, | |
radians: 2 * Math.PI, | |
opacityArea: 0.5, | |
ToRight: 5, | |
TranslateX: 80, | |
TranslateY: 30, | |
ExtraWidthX: 100, | |
ExtraWidthY: 100, | |
color: d3.scale.category10() | |
}; | |
if('undefined' !== typeof options){ | |
for(var i in options){ | |
if('undefined' !== typeof options[i]){ | |
cfg[i] = options[i]; | |
} | |
} | |
} | |
cfg.maxValue = Math.max(cfg.maxValue, d3.max(d, function(i){return d3.max(i.map(function(o){return o.value;}))})); | |
var allAxis = (d[0].map(function(i, j){return i.axis})); | |
var total = allAxis.length; | |
var radius = cfg.factor*Math.min(cfg.w/2, cfg.h/2); | |
var Format = d3.format('%'); | |
d3.select(id).select("svg").remove(); | |
var g = d3.select(id) | |
.append("svg") | |
.attr("width", cfg.w+cfg.ExtraWidthX) | |
.attr("height", cfg.h+cfg.ExtraWidthY) | |
.append("g") | |
.attr("transform", "translate(" + cfg.TranslateX + "," + cfg.TranslateY + ")"); | |
; | |
var tooltip; | |
//Circular segments | |
for(var j=0; j<cfg.levels-1; j++){ | |
var levelFactor = cfg.factor*radius*((j+1)/cfg.levels); | |
g.selectAll(".levels") | |
.data(allAxis) | |
.enter() | |
.append("svg:line") | |
.attr("x1", function(d, i){return levelFactor*(1-cfg.factor*Math.sin(i*cfg.radians/total));}) | |
.attr("y1", function(d, i){return levelFactor*(1-cfg.factor*Math.cos(i*cfg.radians/total));}) | |
.attr("x2", function(d, i){return levelFactor*(1-cfg.factor*Math.sin((i+1)*cfg.radians/total));}) | |
.attr("y2", function(d, i){return levelFactor*(1-cfg.factor*Math.cos((i+1)*cfg.radians/total));}) | |
.attr("class", "line") | |
.style("stroke", "grey") | |
.style("stroke-opacity", "0.75") | |
.style("stroke-width", "0.3px") | |
.attr("transform", "translate(" + (cfg.w/2-levelFactor) + ", " + (cfg.h/2-levelFactor) + ")"); | |
} | |
//Text indicating at what % each level is | |
for(var j=0; j<cfg.levels; j++){ | |
var levelFactor = cfg.factor*radius*((j+1)/cfg.levels); | |
g.selectAll(".levels") | |
.data([1]) //dummy data | |
.enter() | |
.append("svg:text") | |
.attr("x", function(d){return levelFactor*(1-cfg.factor*Math.sin(0));}) | |
.attr("y", function(d){return levelFactor*(1-cfg.factor*Math.cos(0));}) | |
.attr("class", "legend") | |
.style("font-family", "sans-serif") | |
.style("font-size", "10px") | |
.attr("transform", "translate(" + (cfg.w/2-levelFactor + cfg.ToRight) + ", " + (cfg.h/2-levelFactor) + ")") | |
.attr("fill", "#737373") | |
.text(Format((j+1)*cfg.maxValue/cfg.levels)); | |
} | |
series = 0; | |
var axis = g.selectAll(".axis") | |
.data(allAxis) | |
.enter() | |
.append("g") | |
.attr("class", "axis"); | |
axis.append("line") | |
.attr("x1", cfg.w/2) | |
.attr("y1", cfg.h/2) | |
.attr("x2", function(d, i){return cfg.w/2*(1-cfg.factor*Math.sin(i*cfg.radians/total));}) | |
.attr("y2", function(d, i){return cfg.h/2*(1-cfg.factor*Math.cos(i*cfg.radians/total));}) | |
.attr("class", "line") | |
.style("stroke", "grey") | |
.style("stroke-width", "1px"); | |
axis.append("text") | |
.attr("class", "legend") | |
.text(function(d){return d}) | |
.style("font-family", "sans-serif") | |
.style("font-size", "11px") | |
.attr("text-anchor", "middle") | |
.attr("dy", "1.5em") | |
.attr("transform", function(d, i){return "translate(0, -10)"}) | |
.attr("x", function(d, i){return cfg.w/2*(1-cfg.factorLegend*Math.sin(i*cfg.radians/total))-60*Math.sin(i*cfg.radians/total);}) | |
.attr("y", function(d, i){return cfg.h/2*(1-Math.cos(i*cfg.radians/total))-20*Math.cos(i*cfg.radians/total);}); | |
d.forEach(function(y, x){ | |
dataValues = []; | |
g.selectAll(".nodes") | |
.data(y, function(j, i){ | |
dataValues.push([ | |
cfg.w/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)), | |
cfg.h/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total)) | |
]); | |
}); | |
dataValues.push(dataValues[0]); | |
g.selectAll(".area") | |
.data([dataValues]) | |
.enter() | |
.append("polygon") | |
.attr("class", "radar-chart-serie"+series) | |
.style("stroke-width", "2px") | |
.style("stroke", cfg.color(series)) | |
.attr("points",function(d) { | |
var str=""; | |
for(var pti=0;pti<d.length;pti++){ | |
str=str+d[pti][0]+","+d[pti][1]+" "; | |
} | |
return str; | |
}) | |
.style("fill", function(j, i){return cfg.color(series)}) | |
.style("fill-opacity", cfg.opacityArea) | |
.on('mouseover', function (d){ | |
z = "polygon."+d3.select(this).attr("class"); | |
g.selectAll("polygon") | |
.transition(200) | |
.style("fill-opacity", 0.1); | |
g.selectAll(z) | |
.transition(200) | |
.style("fill-opacity", .7); | |
}) | |
.on('mouseout', function(){ | |
g.selectAll("polygon") | |
.transition(200) | |
.style("fill-opacity", cfg.opacityArea); | |
}); | |
series++; | |
}); | |
series=0; | |
d.forEach(function(y, x){ | |
g.selectAll(".nodes") | |
.data(y).enter() | |
.append("svg:circle") | |
.attr("class", "radar-chart-serie"+series) | |
.attr('r', cfg.radius) | |
.attr("alt", function(j){return Math.max(j.value, 0)}) | |
.attr("cx", function(j, i){ | |
dataValues.push([ | |
cfg.w/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)), | |
cfg.h/2*(1-(parseFloat(Math.max(j.value, 0))/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total)) | |
]); | |
return cfg.w/2*(1-(Math.max(j.value, 0)/cfg.maxValue)*cfg.factor*Math.sin(i*cfg.radians/total)); | |
}) | |
.attr("cy", function(j, i){ | |
return cfg.h/2*(1-(Math.max(j.value, 0)/cfg.maxValue)*cfg.factor*Math.cos(i*cfg.radians/total)); | |
}) | |
.attr("data-id", function(j){return j.axis}) | |
.style("fill", cfg.color(series)).style("fill-opacity", .9) | |
.on('mouseover', function (d){ | |
newX = parseFloat(d3.select(this).attr('cx')) - 10; | |
newY = parseFloat(d3.select(this).attr('cy')) - 5; | |
tooltip | |
.attr('x', newX) | |
.attr('y', newY) | |
.text(Format(d.value)) | |
.transition(200) | |
.style('opacity', 1); | |
z = "polygon."+d3.select(this).attr("class"); | |
g.selectAll("polygon") | |
.transition(200) | |
.style("fill-opacity", 0.1); | |
g.selectAll(z) | |
.transition(200) | |
.style("fill-opacity", .7); | |
}) | |
.on('mouseout', function(){ | |
tooltip | |
.transition(200) | |
.style('opacity', 0); | |
g.selectAll("polygon") | |
.transition(200) | |
.style("fill-opacity", cfg.opacityArea); | |
}) | |
.append("svg:title") | |
.text(function(j){return Math.max(j.value, 0)}); | |
series++; | |
}); | |
//Tooltip | |
tooltip = g.append('text') | |
.style('opacity', 0) | |
.style('font-family', 'sans-serif') | |
.style('font-size', '13px'); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment