Instantly share code, notes, and snippets.

@nbremer /.block
Last active Oct 11, 2018

What would you like to do?
D3.js - Radar Chart or Spider Chart - Adjusted from radar-chart-d3
height: 650
license: mit
<!DOCTYPE html>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Radar chart</title>
<script src=""></script>
<script src="RadarChart.js"></script>
body {
overflow: hidden;
margin: 0;
font-size: 14px;
font-family: "Helvetica Neue", Helvetica;
#chart {
position: absolute;
top: 50px;
left: 100px;
<div id="body">
<div id="chart"></div>
<script type="text/javascript" src="script.js"></script>
//Practically all this code comes from
//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:
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({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('%');"svg").remove();
var g =
.attr("width", cfg.w+cfg.ExtraWidthX)
.attr("height", cfg.h+cfg.ExtraWidthY)
.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);
.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);
.data([1]) //dummy data
.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")
series = 0;
var axis = g.selectAll(".axis")
.attr("class", "axis");
.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");
.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 = [];
.data(y, function(j, i){
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))
.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.""class");
.style("fill-opacity", 0.1);
.style("fill-opacity", .7);
.on('mouseout', function(){
.style("fill-opacity", cfg.opacityArea);
d.forEach(function(y, x){
.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){
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('cx')) - 10;
newY = parseFloat('cy')) - 5;
.attr('x', newX)
.attr('y', newY)
.style('opacity', 1);
z = "polygon.""class");
.style("fill-opacity", 0.1);
.style("fill-opacity", .7);
.on('mouseout', function(){
.style('opacity', 0);
.style("fill-opacity", cfg.opacityArea);
.text(function(j){return Math.max(j.value, 0)});
tooltip = g.append('text')
.style('opacity', 0)
.style('font-family', 'sans-serif')
.style('font-size', '13px');
var w = 500,
h = 500;
var colorscale = d3.scale.category10();
//Legend titles
var LegendOptions = ['Smartphone','Tablet'];
var d = [
{axis:"Social Networks",value:0.56},
{axis:"Internet Banking",value:0.42},
{axis:"News Sportsites",value:0.34},
{axis:"Search Engine",value:0.48},
{axis:"View Shopping sites",value:0.14},
{axis:"Paying Online",value:0.11},
{axis:"Buy Online",value:0.05},
{axis:"Stream Music",value:0.07},
{axis:"Online Gaming",value:0.12},
{axis:"App connected to TV program",value:0.03},
{axis:"Offline Gaming",value:0.12},
{axis:"Photo Video",value:0.4},
{axis:"Listen Music",value:0.22},
{axis:"Watch TV",value:0.03},
{axis:"TV Movies Streaming",value:0.03},
{axis:"Listen Radio",value:0.07},
{axis:"Sending Money",value:0.18},
{axis:"Use less Once week",value:0.08}
{axis:"Social Networks",value:0.41},
{axis:"Internet Banking",value:0.27},
{axis:"News Sportsites",value:0.28},
{axis:"Search Engine",value:0.46},
{axis:"View Shopping sites",value:0.29},
{axis:"Paying Online",value:0.11},
{axis:"Buy Online",value:0.14},
{axis:"Stream Music",value:0.05},
{axis:"Online Gaming",value:0.19},
{axis:"App connected to TV program",value:0.06},
{axis:"Offline Gaming",value:0.24},
{axis:"Photo Video",value:0.17},
{axis:"Listen Music",value:0.12},
{axis:"Watch TV",value:0.1},
{axis:"TV Movies Streaming",value:0.14},
{axis:"Listen Radio",value:0.06},
{axis:"Sending Money",value:0.16},
{axis:"Use less Once week",value:0.17}
//Options for the Radar chart, other than default
var mycfg = {
w: w,
h: h,
maxValue: 0.6,
levels: 6,
ExtraWidthX: 300
//Call function to draw the Radar chart
//Will expect that data is in %'s
RadarChart.draw("#chart", d, mycfg);
/////////// Initiate legend ////////////////
var svg ='#body')
.attr("width", w+300)
.attr("height", h)
//Create the title for the legend
var text = svg.append("text")
.attr("class", "title")
.attr('transform', 'translate(90,0)')
.attr("x", w - 70)
.attr("y", 10)
.attr("font-size", "12px")
.attr("fill", "#404040")
.text("What % of owners use a specific service in a week");
//Initiate Legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 200)
.attr('transform', 'translate(90,20)')
//Create colour squares
.attr("x", w - 65)
.attr("y", function(d, i){ return i * 20;})
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d, i){ return colorscale(i);})
//Create text next to squares
.attr("x", w - 52)
.attr("y", function(d, i){ return i * 20 + 9;})
.attr("font-size", "11px")
.attr("fill", "#737373")
.text(function(d) { return d; })

This comment has been minimized.

MaximeMaxime commented May 12, 2015

Great work on this one! Nice looking radar chart are hard to generate. Quick question (please note I am new to D3...) : is there an easy way to rotate the entire radar graph by some degree? I am trying to generate a radar graph that will not have a 'vertex' at the top, but rather have an 'edge'... Any idea? Thanks a lot :)


This comment has been minimized.

sytpp commented Jun 25, 2015

Thanks @nbremer for the work that went into making a radar chart look nicer - your post on it was very helpful! Just following up that one: Like you said, it would be a nice idea to highlight the polygons on hover over the legend-key/text; did you get to implement that? It would be a great addtion!


This comment has been minimized.

MartenHoogeveen commented Sep 23, 2016

What is the type of licence?


This comment has been minimized.

betocollin commented Sep 1, 2017

Im pretty new to D3 and just had a look at your chart extension. Looks pretty good!
I was trying to add custom labels (e.g. Title - line break - subtitle) but could not find an easy way.
Do you know a way to extend your code for allowing that?
Thanks again.


This comment has been minimized.

nareshgcs commented Sep 7, 2017

Nice work.
I am trying to do with negative values. But the dots are getting wrong positions (screenshot is attached: 20% point shows on 50% axis).
Can you help?



This comment has been minimized.

johanmos commented Dec 21, 2017

Good job,
I like all this, but I dont know how to do to save the chart as image
I'm trying use this tutorial but dont work :(
Can you help me?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment