Built with blockbuilder.org
This is an example of a radar chart used to visualise organic food purchases in Sweden. You can visit the full visualisation here.
Built with blockbuilder.org
This is an example of a radar chart used to visualise organic food purchases in Sweden. You can visit the full visualisation here.
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
<link href='https://fonts.googleapis.com/css?family=Lato:400,700' rel='stylesheet' type='text/css'> | |
<style> | |
body { | |
margin: 10px 0 0 0; | |
position:fixed; | |
top:0; | |
right:0; | |
bottom:0; | |
left:0; | |
background-color: #149EBA; | |
color: #fff; | |
font-family: 'Lato'; | |
font-weight: 400; | |
} | |
#vkvArea { | |
position: relative; | |
width: 700px; | |
margin-left: auto; | |
margin-right: auto; | |
} | |
#vkvGraph { | |
text-transform: uppercase; | |
} | |
#vkvMainTitle { | |
font-weight: 700; | |
} | |
#sweVkv { | |
color: #dbd37e; | |
font-size: 20px; | |
margin: 0; | |
} | |
#prodResult { | |
position: absolute; | |
top: 10px; | |
right: 235px; | |
color: #6a213e; | |
font-weight: 700; | |
font-size: 24px; | |
} | |
#prodLegend { | |
position: absolute; | |
top: 0; | |
right: 50px; | |
color: rgba(255,255,255,0.6); | |
width: 220px; | |
text-align: right; | |
line-height: 24px; | |
} | |
#prodLegend span { | |
color: #6a213e; | |
} | |
text:hover { | |
cursor: pointer; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="vkvArea"> | |
<div id="vkvGraph"> | |
<p id="vkvMainTitle">Vad köper vi?</p> | |
<p id="sweVkv">Sverige</p> | |
<div id="prodResult"></div> | |
<p id="prodLegend">Procent <span>ekologiskt</span> inköp per produktslag</p> | |
</div> | |
<div id="svgArea"></div> | |
</div> | |
<script> | |
d3.csv('vkv.csv', function(error, data) { | |
if (error) throw error; | |
function radarChart(data) { | |
var cfg = { | |
w: 280, //Width of the circle | |
h: 280, //Height of the circle | |
margin: {top: 50, right: 150, bottom: 100, left: 150 }, //The margins of the SVG | |
levels: 4, //How many levels or inner circles should there be drawn | |
maxValue: 1, //What is the value that the biggest circle will represent | |
labelFactor: 1.25, //How much farther than the radius of the outer circle should the labels be placed | |
wrapWidth: 60, //The number of pixels after which a label needs to be given a new line | |
opacityArea: 0.35, //The opacity of the area of the blob | |
dotRadius: 4, //The size of the colored circles of each blog | |
opacityCircles: 0.1, //The opacity of the circles of each blob | |
strokeWidth: 2, //The width of the stroke around each blob | |
roundStrokes: true //If true the area and stroke will follow a round path (cardinal-closed) | |
// color: '#6a213e' //Color | |
}; | |
var allAxis = (data.map(function(i, j){ return i.Produktgrupp})); //Names of each axis | |
var total = allAxis.length; //The number of different axes | |
var radius = Math.min(cfg.w/2, cfg.h/2); //Radius of the outermost circle | |
var format = d3.format('%'); //Percentage formatting | |
var angleSlice = Math.PI * 2 / total; //The width in radians of each "slice" | |
//Scale for the radius | |
var rScale = d3.scale.linear() | |
.range([0, radius]) | |
.domain([0, 1]); | |
// //Remove whatever chart with the same id/class was present before | |
// d3.select(id).select("svg").remove(); | |
//Initiate the radar chart SVG | |
var svg = d3.select('#svgArea').append("svg") | |
.attr("width", cfg.w + cfg.margin.left + cfg.margin.right) | |
.attr("height", cfg.h + cfg.margin.top + cfg.margin.bottom) | |
.attr("class", "radar"); | |
//Append a g element | |
var g = svg.append("g") | |
.attr('class', 'mainG') | |
.attr("transform", "translate(" + 300 + "," + 180 + ")"); | |
//Wrapper for the grid & axes | |
var axisGrid = g.append("g").attr("class", "axisWrapper"); | |
//Draw the background circles | |
axisGrid.selectAll('.levels') | |
.data(d3.range(1,(cfg.levels+1)).reverse()) | |
.enter() | |
.append('circle') | |
.attr({ | |
class: 'gridCirlcle', | |
r: function(d, i) { | |
return radius / cfg.levels * d; | |
} | |
}) | |
.style({ | |
fill: '#71C6D6', | |
stroke: 'rgba(255,255,255,0.4)', | |
'stroke-width': function(d, i) { | |
if (i > 0) { | |
return '1px'; | |
} else { | |
return '0px'; | |
} | |
}, | |
'stroke-dasharray': function(d, i) { | |
if (i > 0) { | |
return '5,5'; | |
} | |
} | |
}); | |
//The radial line function | |
var radarLine = d3.svg.line.radial() | |
.interpolate("cardinal-closed") | |
.radius(function(d) { return rScale(d.Ekovarde); }) | |
.angle(function(d,i) {return i * angleSlice; }); | |
//Create a wrapper for the blobs | |
var blobWrapper = g.append("g") | |
.attr("class", "radarWrapper"); | |
//Append the backgrounds | |
var blob = blobWrapper | |
.append('path') | |
.attr({ | |
class:'radarArea', | |
d: radarLine(data) | |
}) | |
.style({ | |
fill: '#6a213e', | |
opacity: 0.6 | |
}); | |
//Create the outlines | |
blobWrapper.append('path') | |
.attr({ | |
class: 'radarStroke', | |
d: function(d, i) { | |
return radarLine(data); | |
} | |
}) | |
.style({ | |
stroke: '#6a213e', | |
'stroke-width': '3px', | |
fill: 'none' | |
}); | |
//Text indicating at what % each level is | |
var percentAxis = g.append("g").attr("class", "percentAxis"); | |
percentAxis.selectAll('.axisPerc') | |
.data(d3.range(1,(cfg.levels+1)).reverse()) | |
.enter().append('text') | |
.attr({ | |
class: 'percentPerc', | |
x: 4, | |
y: function(d) { | |
return -d * radius / cfg.levels; | |
}, | |
dy: '-0.5em', | |
dx: '-1.5em', | |
transform: 'rotate(25)' | |
}) | |
.style({ | |
'font-size': '10px', | |
fill: '#fff', | |
opacity: 1, | |
'font-family': 'Futura Heavy' | |
}) | |
.text(function(d,i) { | |
if (d < 4) { | |
return format(1 * d/cfg.levels); | |
} | |
}); | |
//Create the straight lines radiating outward from the center | |
var axis = axisGrid.selectAll(".axis") | |
.data(data) | |
.enter() | |
.append("g") | |
.attr("class", "axis"); | |
function titleAlign(d, i) { | |
if (i == 0 || i == 8) { | |
return 'middle'; | |
} else if (i > 0 && i < 8) { | |
return 'start'; | |
} else if (i > 8) { | |
return 'end'; | |
} | |
} | |
//Append the lines | |
axis.append('line') | |
.attr({ | |
x1: 0, | |
y1: 0, | |
x2: function(d, i) { | |
return rScale(1.1) * Math.cos(angleSlice * i - Math.PI / 2); | |
}, | |
y2: function(d, i) { | |
return rScale(1.1) * Math.sin(angleSlice * i- Math.PI / 2); | |
}, | |
class: 'radialLine' | |
}) | |
.style({ | |
stroke: 'rgba(255,255,255,0.4)', | |
'stroke-width': '1px' | |
}); | |
//Append the labels at each axis | |
axis.append('text') | |
.attr({ | |
class: 'radialLegend', | |
'text-anchor': function(d,i) { | |
return titleAlign(d,i); | |
}, | |
dy: '0.5em', | |
x: function(d, i) { | |
return rScale(1.2) * Math.cos(angleSlice * i - Math.PI / 2); | |
}, | |
y: function(d, i) { | |
return rScale(1.2) * Math.sin(angleSlice * i - Math.PI / 2); | |
} | |
}) | |
.style({ | |
'font-size': '12px', | |
'opacity': 0.6, | |
'fill': '#fff' | |
}) | |
.text(function(d) { | |
return d.Produktgrupp; | |
}) | |
.on('mouseenter', function(d, i) { | |
d3.selectAll('path.sweden').style('opacity', 0); | |
d3.select(this).style({opacity: 1, 'font-weight': 700}); | |
// display result for each project group | |
var ekovarde = d.Ekovarde; | |
d3.select('#prodResult').text(format(d.Ekovarde)); | |
}) | |
.on('mouseleave', function(d, i) { | |
d3.select(this).style({opacity: 0.6, 'font-weight': 400}); | |
d3.select('#prodResult').text(''); //clear result text | |
}); | |
} | |
radarChart(data); | |
}) //end load csv | |
</script> | |
</body> |
Produktgrupp | Ekovarde | |
---|---|---|
Baljväxter & frö | 0.567 | |
Bröd | 0.063 | |
Cerealier | 0.423 | |
Drycker (alkoholfria) | 0.087 | |
Fisk & skaldjur | 0.47 | |
Frukt & bär | 0.384 | |
Färdigmat (hel & halvfabrikat) | 0.105 | |
Grönsaker och potatis | 0.242 | |
Kaffe & te | 0.735 | |
Kryddor | 0.089 | |
Kött &chark | 0.136 | |
Dressing, sås & vinäger | 0.200 | |
Matfett smörgås | 0.306 | |
Mejeriprodukter | 0.489 | |
Ägg | 0.485 | |
Övrigt | 0.133 |