Skip to content

Instantly share code, notes, and snippets.

@estk
Last active December 24, 2015 20:19
Show Gist options
  • Save estk/6856958 to your computer and use it in GitHub Desktop.
Save estk/6856958 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<title>Radar chart</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<style>
body {
font: 10px sans-serif;
}
.axis + .axis g text {
display: none;
}
.chart {
margin: auto;
text-align:center;
}
</style>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="RadarChart.js" charset="utf-8"></script>
</head>
<body>
<div class="chart"></div>
<script src="script.js"></script>
</body>
</html>
//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: 10,
w: 600,
h: 600,
rotate: 0,
factor: 1,
factorLegend: 0.85,
levels: 3,
maxValue: 0,
radians: 2 * Math.PI,
opacityArea: 0.5,
opacityFill: 0.7,
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();
function getXCoord(i) {
return cfg.w/2*(1-cfg.factor*Math.sin((i*cfg.radians)/total));
}
function getYCoord(i) {
return cfg.h/2*(1-cfg.factor*Math.cos((i*cfg.radians)/total));
}
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 + ")"+" rotate("+cfg.rotate +','+(cfg.w/2)+','+cfg.h/2+')');
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) + ")");
}
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 getXCoord(i);})
.attr("y2", function(d, i){return getYCoord(i);})
.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){
var x = getXCoord(i)-60*Math.sin(i*cfg.radians/total);
var y = getYCoord(i)-20*Math.cos(i*cfg.radians/total);
return "translate(0, -10) rotate(" + -cfg.rotate + ',' + x +','+ y +')';
})
.attr("x", function(d, i){return getXCoord(i)-60*Math.sin(i*cfg.radians/total);})
.attr("y", function(d, i){return getYCoord(i)-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", cfg.opacityFill);
})
.on('mouseout', function(){
g.selectAll("polygon")
.transition(400)
.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", cfg.opacityFill)
.on('mouseover', function (d, i){
newX = parseFloat(d3.select(this).attr('cx'))-30*Math.sin(i*cfg.radians/total);
newY = parseFloat(d3.select(this).attr('cy'))-30*Math.cos(i*cfg.radians/total);
tooltip
.attr('x', newX)
.attr('y', newY)
.attr("text-anchor", "middle")
.attr("transform", function(d, i){
return "rotate(" + -cfg.rotate + ',' + newX +','+ 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", cfg.opacityFill);
})
.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');
}
};
d3.select(self.frameElement).style("height", "900px");
var colorscale = d3.scale.category10();
// Data
d = [
[{ axis: "Novelty", value: .75},
{ axis:"Originiality", value: .45},
{ axis:"Multidimentionality", value: .3},
{ axis:"Density", value: .2},
{ axis:"Functionality", value: .8},
{ axis:"Abstraction", value: .9},
{ axis:"Redundancy", value: .25},
{ axis:"Familiarity", value: .65},
{ axis:"Unidimensionality", value: .7},
{ axis:"Lightness", value: .8},
{ axis:"Decoration", value: .2},
{ axis:"Figuration", value: .1}]
];
//Options for the Radar chart, other than default
var config = {
w: 600,
h: 600,
rotate: 75,
maxValue: 1,
levels: 0,
factor: .925,
opacityArea: .2,
opacityFill: .2,
ExtraWidthX: 200
}
RadarChart.draw(".chart", d, config);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment