Skip to content

Instantly share code, notes, and snippets.

@jorgesancha
Last active December 14, 2020 21:20
Show Gist options
  • Save jorgesancha/3a606cfcc250d26974311b2bab2d7c59 to your computer and use it in GitHub Desktop.
Save jorgesancha/3a606cfcc250d26974311b2bab2d7c59 to your computer and use it in GitHub Desktop.
An animated radar chart in D3.js inspired by the Westworld series

Part of the talk Math, radars and D3.js - slides, video

<!DOCTYPE html>
<html>
<head></head>
<link rel="stylesheet" href="style.css">
<script src="https://d3js.org/d3.v4.min.js"></script>
<body>
<div id="chart"></div>
<script>
var chart = d3.select('#chart');
var width = 960, height = 500;
var centerCoords = [width/2, height/2];
var RADIAN_OFFSET = Math.PI/2;
var padding = 60;
var radius = d3.min([width - padding, height - padding])/2;
var svg = chart.selectAll("svg")
.data([{}]).enter()
.append("svg")
.attr("height", height)
.attr("width", width);
var data = [
{attribute: 'Bulk Apperception', value: 14},
{attribute: 'Candor', value: 19},
{attribute: 'Vivacity', value: 17},
{attribute: 'Coordination', value: 10},
{attribute: 'Meekness', value: 2},
{attribute: 'Humility', value: 3},
{attribute: 'Cruelty', value: 1},
{attribute: 'Self-Preservation', value: 10},
{attribute: 'Patience', value: 3},
{attribute: 'Decisiveness', value: 14},
{attribute: 'Imagination', value: 13},
{attribute: 'Curiosity', value: 8},
{attribute: 'Aggression', value: 5},
{attribute: 'Loyalty', value: 16},
{attribute: 'Empathy', value: 9},
{attribute: 'Tenacity', value: 17},
{attribute: 'Courage', value: 15},
{attribute: 'Sensuality', value: 18},
{attribute: 'Charm', value: 18},
{attribute: 'Humor', value: 9}
]
var domain = [0,20]
var angle = d3.scaleLinear()
.domain([0, data.length])
.range([0 - RADIAN_OFFSET, 2 * Math.PI - RADIAN_OFFSET]);
var measureScale = d3.scaleLinear()
.domain(domain)
.range([30, radius])
var X = function(i, r, offset) {
if (offset === undefined) offset = 0;
if (r === undefined) r = radius;
return Math.cos(angle(i)) * (r + offset);
}
var Y = function(i, r, offset) {
if (offset === undefined) offset = 0;
if (r === undefined) r = radius;
return Math.sin(angle(i)) * (r + offset);
}
var labelAnchor = function(d, i) {
var topAngle = 0 - Math.PI/2;
var bottomAngle = 0 + Math.PI/2;
var the_angle = angle(i);
var anchor = "";
if (the_angle == topAngle || the_angle == bottomAngle)
anchor = "middle"
else if(the_angle > topAngle && the_angle < bottomAngle)
anchor = "start"
else
anchor = "end"
return anchor;
}
var radar = svg.append('g')
.attr('transform',"translate(" + centerCoords[0] + "," + centerCoords[1] + ")");
radar.selectAll('circle.ring')
.data(d3.range(domain[0],domain[1])).enter()
.append('circle')
.classed('ring',true)
.attr('cx',0)
.attr('cy',0)
.attr('r', function(d,i) {
return measureScale(i + 1);
})
radar.selectAll('line.axis')
.data(data).enter()
.append('line')
.classed('axis',true)
.attr('x0',0)
.attr('y0',0)
.attr('x1',function(d,i) { return X(i)})
.attr('y1',function(d,i) { return Y(i)})
radar.selectAll('circle.axis_circle')
.data(data).enter()
.append('circle')
.classed('axis_circle',true)
.attr('cx',function(d,i) { return X(i)})
.attr('cy',function(d,i) { return Y(i)})
.attr('r',7)
var areas = radar.selectAll('polygon.area')
.data([data]).enter()
areas.append('polygon')
.classed('area',true)
.attr('points', function(d) {
return d.map(function() {
return [0,0]
}).join(' ')
})
.merge(areas)
.transition()
.duration(2500)
.attr('points', function(d) {
return d.map(function(d,i) {
var rad = measureScale(d.value);
return [X(i, rad), Y(i, rad)];
}).join(' ');
})
var points = radar.selectAll('circle.point')
.data(data).enter()
points.append('circle')
.classed('point',true)
.attr('r', 7)
.attr('cx',0)
.attr('cy',0)
.merge(points)
.transition()
.duration(2500)
.attr('cx',function(d,i) { return X(i,measureScale(d.value))})
.attr('cy',function(d,i) { return Y(i,measureScale(d.value))})
radar.append('circle')
.classed('center',true)
.attr('cx',0)
.attr('cy',0)
.attr('r',30)
radar.selectAll('text.label')
.data(data).enter()
.append('text')
.classed('label',true)
.attr('x', function(d,i) { return X(i, radius, 20)})
.attr('y', function(d,i) { return Y(i, radius, 20)})
.style("text-anchor", labelAnchor)
.text(function(d) {
return d.attribute.toUpperCase() + " [" + d.value +"]";
})
</script>
</body>
body {
background-color: #222;
}
circle, line, polygon {
stroke: #E3E3E3;
stroke-width: 3px;
fill:none;
}
text {
font-family: 'Helvetica';
font-size:12px;
font-weight: bold;
stroke: none;
fill:#BFEFFF;
alignment-baseline: middle;
}
circle.ring,
polygon.ring {
stroke-width: 1px;
opacity: .5;
fill:none;
}
circle.axis_circle,
circle.point {
stroke-width: 1px;
stroke: #BFEFFF;
fill:none;
}
circle.point {
fill:#5890A4;
}
circle.center {
stroke-width: 2px;
stroke: #BFEFFF;
fill: #145B60;
}
polygon.area {
stroke: #BFEFFF;
stroke-width: 2px;
fill:none;
fill:#4493AA;
fill-opacity:.4;
}
line.axis {
stroke-width: 1px;
opacity: .5;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment