Created
April 12, 2017 16:05
-
-
Save mjmdavis/a0abaa06f53bd65899d2190562cacd3b to your computer and use it in GitHub Desktop.
noisy radar
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
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/ > | |
<title>Smoothed D3.js Radar Chart</title> | |
<!-- Google fonts --> | |
<link href='http://fonts.googleapis.com/css?family=Open+Sans:400,300' rel='stylesheet' type='text/css'> | |
<link href='https://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'> | |
<!-- D3.js --> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script> | |
<script src="https://d3js.org/d3-random.v1.min.js"></script> | |
<script src="https://d3js.org/d3-array.v1.min.js"></script> | |
<style> | |
body { | |
font-family: 'Open Sans', sans-serif; | |
font-size: 11px; | |
font-weight: 300; | |
fill: #242424; | |
text-align: center; | |
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff; | |
cursor: default; | |
} | |
.legend { | |
font-family: 'Raleway', sans-serif; | |
fill: #333333; | |
} | |
.tooltip { | |
fill: #333333; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="radarChart"></div> | |
<script src="radarChart.js"></script> | |
<script> | |
////////////////////////////////////////////////////////////// | |
//////////////////////// Set-Up ////////////////////////////// | |
////////////////////////////////////////////////////////////// | |
var margin = {top: 100, right: 100, bottom: 100, left: 100}, | |
width = Math.min(700, window.innerWidth - 10) - margin.left - margin.right, | |
height = Math.min(width, window.innerHeight - margin.top - margin.bottom - 20); | |
////////////////////////////////////////////////////////////// | |
////////////////////////// Data ////////////////////////////// | |
////////////////////////////////////////////////////////////// | |
var companies = ["Adobe", | |
"Amazon", | |
"Apple", | |
"Cisco", | |
"eBay", | |
"Facebook", | |
"Google", | |
"HP", | |
"IBM", | |
"Intel", | |
"LinkedIn", | |
"Microsoft", | |
"Oracle", | |
"Qualcomm", | |
"Salesforce", | |
"Samsung", | |
"SpaceX", | |
"Tesla"]; | |
var attributes = ["Early Career Median Pay", | |
"Mid-career Median Pay", | |
"Median Age", | |
"Median Years Experience", | |
"Median years w/Company", | |
"% Female", | |
"% High Job Satisfaction", | |
"% High Job Meaning", | |
"% High Job Stress"]; | |
var data = []; | |
const rand = d3.randomUniform(0,18); | |
for(var i=0; i < companies.length; i++) { | |
var new_obj = [ | |
{axis:"Early Career Median Pay", value:rand()}, | |
{axis:"Mid-career Median Pay", value:rand()}, | |
{axis:"Median Age", value:rand()}, | |
{axis:"Median Years Experience", value:rand()}, | |
{axis:"Median years w/Company", value:rand()}, | |
{axis:"% Female", value:rand()}, | |
{axis:"% High Job Satisfaction", value:rand()}, | |
{axis:"% High Job Meaning", value:rand()}, | |
{axis:"% High Job Stress", value:rand()} | |
]; | |
data[i] = new_obj; | |
} | |
for(var a_i=0; a_i<attributes.length; a_i++) { | |
var possible_values = d3.range(1, 1 + companies.length); | |
d3.shuffle(possible_values); | |
for(var c_i=0; c_i< companies.length; c_i++) { | |
data[c_i][a_i]['value'] = possible_values.pop(); | |
} | |
} | |
console.table(data); | |
////////////////////////////////////////////////////////////// | |
//////////////////// Draw the Chart ////////////////////////// | |
////////////////////////////////////////////////////////////// | |
var color = d3.scale.category20(); | |
var radarChartOptions = { | |
w: width, | |
h: height, | |
margin: margin, | |
maxValue: 10, | |
levels: 10, | |
roundStrokes: true, | |
color: color, | |
labels: companies | |
}; | |
//Call function to draw the Radar chart | |
RadarChart(".radarChart", data, radarChartOptions); | |
</script> | |
</body> | |
</html> |
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
// Radar chard modified by mjmdavis | |
// attributions below | |
///////////////////////////////////////////////////////// | |
/////////////// The Radar Chart Function //////////////// | |
/////////////// Written by Nadieh Bremer //////////////// | |
////////////////// VisualCinnamon.com /////////////////// | |
/////////// Inspired by the code of alangrafu /////////// | |
///////////////////////////////////////////////////////// | |
// d3.legend.js | |
// (C) 2012 ziggy.jonsson.nyc@gmail.com | |
// MIT licence | |
function RadarChart(id, data, options) { | |
var cfg = { | |
w: 600, //Width of the circle | |
h: 600, //Height of the circle | |
margin: {top: 20, right: 20, bottom: 20, left: 20}, //The margins of the SVG | |
levels: 3, //How many levels or inner circles should there be drawn | |
maxValue: 0, //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.0, //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: false, //If true the area and stroke will follow a round path (cardinal-closed) | |
color: d3.scale.category10(), //Color function | |
opacityAreaSelected: 0.8, | |
opacityLine: 0.8, | |
opacityLineFade: 0.3, | |
labels: null | |
}; | |
//Put all of the options into a variable called cfg | |
if('undefined' !== typeof options){ | |
for(var i in options){ | |
if('undefined' !== typeof options[i]){ cfg[i] = options[i]; } | |
}//for i | |
}//if | |
const highlight_area = function(parent_i) { | |
d3.selectAll(".radarArea") | |
.filter(function(d,i){ | |
const area_id = parseInt(this.parentNode.getAttribute('id')); | |
return area_id == parent_i;}) | |
.transition().duration(200) | |
.style("fill-opacity", cfg.opacityAreaSelected); | |
d3.selectAll(".radarStroke") | |
.filter(function(d,i){ | |
const area_id = parseInt(this.parentNode.getAttribute('id')); | |
return area_id != parent_i;}) | |
.transition().duration(200) | |
.style("stroke-opacity", cfg.opacityLineFade); | |
d3.selectAll(".legend-items > text") | |
.filter(function(d,i){ | |
const area_id = parseInt(this.getAttribute('id')); | |
return area_id != parent_i}) | |
.attr("opacity", "0.2") | |
}; | |
const un_highlight_area = function(){ | |
d3.selectAll(".radarArea") | |
.transition().duration(200) | |
.style("fill-opacity", cfg.opacityArea); | |
d3.selectAll(".radarStroke") | |
.transition().duration(200) | |
.style("stroke-opacity", cfg.opacityLine); | |
d3.selectAll(".legend-items > text") | |
.attr("opacity", "1.0") | |
}; | |
d3.legend = function(g) { | |
g.each(function() { | |
var g= d3.select(this), | |
items = {}, | |
svg = d3.select(g.property("nearestViewportElement")), | |
legendPadding = g.attr("data-style-padding") || 5, | |
lb = g.selectAll(".legend-box").data([true]), | |
li = g.selectAll(".legend-items").data([true]); | |
lb.enter().append("rect").classed("legend-box",true).attr("fill", "white"); | |
li.enter().append("g").classed("legend-items",true); | |
svg.selectAll("[data-legend]").each(function() { | |
var self = d3.select(this); | |
items[self.attr("data-legend")] = { | |
pos : self.attr("data-legend-pos") || this.getBBox().y, | |
color : self.attr("data-legend-color") != undefined ? self.attr("data-legend-color") : self.style("fill") != 'none' ? self.style("fill") : self.style("stroke") | |
} | |
}); | |
items = d3.entries(items).sort(function(a,b) { return a.value.pos-b.value.pos}); | |
li.selectAll("text") | |
.data(items,function(d) { return d.key}) | |
.call(function(d) { d.enter().append("text")}) | |
.call(function(d) { d.exit().remove()}) | |
.attr("y",function(d,i) { return i+"em"}) | |
.attr("x","1em") | |
.attr('id', function(d,i){return i}) | |
.text(function(d) { ;return d.key}) | |
.on("mouseover", function(d, i){ | |
var parent_i = parseInt(this.getAttribute('id')); | |
highlight_area(parent_i); | |
}) | |
.on("mouseout", function(d, i){ | |
un_highlight_area(); | |
}); | |
li.selectAll("circle") | |
.data(items,function(d) { return d.key}) | |
.call(function(d) { d.enter().append("circle")}) | |
.call(function(d) { d.exit().remove()}) | |
.attr("cy",function(d,i) { return i-0.25+"em"}) | |
.attr("cx",0) | |
.attr("r","0.4em") | |
.style("fill",function(d) { console.log(d.value.color);return d.value.color}) | |
// Reposition and resize the box | |
var lbbox = li[0][0].getBBox() | |
lb.attr("x",(lbbox.x-legendPadding)) | |
.attr("y",(lbbox.y-legendPadding)) | |
.attr("height",(lbbox.height+2*legendPadding)) | |
.attr("width",(lbbox.width+2*legendPadding)) | |
}) | |
return g; | |
} | |
//If the supplied maxValue is smaller than the actual one, replace by the max in the data | |
var maxValue = Math.max(cfg.maxValue, d3.max(data, function(i){return d3.max(i.map(function(o){return o.value;}))})); | |
var allAxis = (data[0].map(function(i, j){return i.axis})), //Names of each axis | |
total = allAxis.length, //The number of different axes | |
radius = Math.min(cfg.w/2, cfg.h/2), //Radius of the outermost circle | |
Format = d3.format('%'), //Percentage formatting | |
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, maxValue]); | |
///////////////////////////////////////////////////////// | |
//////////// Create the container SVG and g ///////////// | |
///////////////////////////////////////////////////////// | |
//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(id).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"+id); | |
//Append a g element | |
var g = svg.append("g") | |
.attr("transform", "translate(" + (cfg.w/2 + cfg.margin.left) + "," + (cfg.h/2 + cfg.margin.top) + ")"); | |
///////////////////////////////////////////////////////// | |
////////// Glow filter for some extra pizzazz /////////// | |
///////////////////////////////////////////////////////// | |
//Filter for the outside glow | |
var filter = g.append('defs').append('filter').attr('id','glow'), | |
feGaussianBlur = filter.append('feGaussianBlur').attr('stdDeviation','2.5').attr('result','coloredBlur'), | |
feMerge = filter.append('feMerge'), | |
feMergeNode_1 = feMerge.append('feMergeNode').attr('in','coloredBlur'), | |
feMergeNode_2 = feMerge.append('feMergeNode').attr('in','SourceGraphic'); | |
///////////////////////////////////////////////////////// | |
/////////////// Draw the Circular grid ////////////////// | |
///////////////////////////////////////////////////////// | |
//Wrapper for the grid & axes | |
var axisGrid = g.append("g").attr("class", "axisWrapper"); | |
// Draw the legend | |
var legend = g.append("g").attr("class", "legend"); | |
legend.data(cfg.labels) | |
.enter() | |
.append("g") | |
.attr("class", "legendEntry") | |
///////////////////////////////////////////////////////// | |
//////////////////// Draw the axes ////////////////////// | |
///////////////////////////////////////////////////////// | |
//Create the straight lines radiating outward from the center | |
var axis = axisGrid.selectAll(".axis") | |
.data(allAxis) | |
.enter() | |
.append("g") | |
.attr("class", "axis"); | |
//Append the lines | |
axis.append("line") | |
.attr("x1", 0) | |
.attr("y1", 0) | |
.attr("x2", function(d, i){ return rScale(maxValue*1.1) * Math.cos(angleSlice*i - Math.PI/2); }) | |
.attr("y2", function(d, i){ return rScale(maxValue*1.1) * Math.sin(angleSlice*i - Math.PI/2); }) | |
.attr("class", "line") | |
.style("stroke", "gray") | |
.style("stroke-opacity", 0.5) | |
.style("stroke-width", "2px"); | |
//Append the labels at each axis | |
axis.append("text") | |
.attr("class", "legend") | |
.style("font-size", "11px") | |
.attr("text-anchor", "middle") | |
.attr("dy", "0.35em") | |
.attr("x", function(d, i){ return rScale(maxValue * cfg.labelFactor) * Math.cos(angleSlice*i - Math.PI/2); }) | |
.attr("y", function(d, i){ return rScale(maxValue * cfg.labelFactor) * Math.sin(angleSlice*i - Math.PI/2); }) | |
.text(function(d){return d}) | |
.call(wrap, cfg.wrapWidth); | |
///////////////////////////////////////////////////////// | |
///////////// Draw the radar chart blobs //////////////// | |
///////////////////////////////////////////////////////// | |
//The radial line function | |
var radarLine = d3.svg.line.radial() | |
.interpolate("linear-closed") | |
.radius(function(d) { return rScale(d.value); }) | |
.angle(function(d,i) { return i*angleSlice; }); | |
if(cfg.roundStrokes) { | |
radarLine.interpolate("cardinal-closed"); | |
} | |
//Create a wrapper for the blobs | |
var blobWrapper = g.selectAll(".radarWrapper") | |
.data(data) | |
.enter().append("g") | |
.attr("class", "radarWrapper") | |
.attr("id", function(d, i){return i}); | |
//Append the backgrounds | |
blobWrapper | |
.append("path") | |
.attr("class", "radarArea") | |
.attr("d", function(d,i) { return radarLine(d); }) | |
.attr("id", function(d,i){return i}) | |
.style("fill", function(d,i) { return cfg.color(i); }) | |
.style("fill-opacity", cfg.opacityArea); | |
//Create the outlines | |
blobWrapper.append("path") | |
.attr("class", "radarStroke") | |
.attr("d", function(d,i) { return radarLine(d); }) | |
.style("stroke-width", cfg.strokeWidth + "px") | |
.style("stroke", function(d,i) { return cfg.color(i); }) | |
.style("fill", "none") | |
.style("filter" , "url(#glow)") | |
.attr("data-legend", function(d, i){return cfg.labels[i];}) | |
.attr("data-legend-pos", function(d,i){return i}) | |
; | |
//Append the circles | |
blobWrapper.selectAll(".radarCircle") | |
.data(function(d,i) { return d; }) | |
.enter().append("circle") | |
.attr("class", "radarCircle") | |
.attr("r", cfg.dotRadius) | |
.attr("cx", function(d,i){ return rScale(d.value) * Math.cos(angleSlice*i - Math.PI/2); }) | |
.attr("cy", function(d,i){ return rScale(d.value) * Math.sin(angleSlice*i - Math.PI/2); }) | |
.style("fill", function(d,i,j) { return cfg.color(j); }) | |
.style("fill-opacity", 0.8); | |
///////////////////////////////////////////////////////// | |
//////// Append invisible circles for tooltip /////////// | |
///////////////////////////////////////////////////////// | |
//Wrapper for the invisible circles on top | |
var blobCircleWrapper = g.selectAll(".radarCircleWrapper") | |
.data(data) | |
.enter().append("g") | |
.attr("class", "radarCircleWrapper") | |
.attr("id", function(d,i){return i;}); | |
//Append a set of invisible circles on top for the mouseover pop-up | |
blobCircleWrapper.selectAll(".radarInvisibleCircle") | |
.data(function(d,i) { return d; }) | |
.enter().append("circle") | |
.attr("class", "radarInvisibleCircle") | |
.attr("r", cfg.dotRadius*2.0) | |
// .attr("id", function(d,i){return i}) | |
.attr("cx", function(d,i){ return rScale(d.value) * Math.cos(angleSlice*i - Math.PI/2); }) | |
.attr("cy", function(d,i){ return rScale(d.value) * Math.sin(angleSlice*i - Math.PI/2); }) | |
.style("fill", "none") | |
.style("pointer-events", "all") | |
.on("mouseover", function(d,i) { | |
newX = parseFloat(d3.select(this).attr('cx')) - 10; | |
newY = parseFloat(d3.select(this).attr('cy')) - 10; | |
tooltip | |
.attr('x', newX) | |
.attr('y', newY) | |
.text(Format(d.value)) | |
.transition().duration(200) | |
.style('opacity', 1); | |
var parent_i = parseInt(this.parentNode.getAttribute('id')); | |
highlight_area(parent_i); | |
// d3.selectAll(".radarStroke") | |
// .transition().duration(200) | |
// .style("stroke-opacity", cfg.opacityLineFade); | |
//Bring back the hovered over blob | |
// d3.select(this) | |
// .transition().duration(200) | |
// .style("fill-opacity", 0.7); | |
}) | |
.on("mouseout", function(){ | |
tooltip.transition().duration(200) | |
.style("opacity", 0); | |
un_highlight_area(); | |
}); | |
//Set up the small tooltip for when you hover over a circle | |
var tooltip = g.append("text") | |
.attr("class", "tooltip") | |
.style("opacity", 0); | |
legend = svg.append("g") | |
.attr("class","legend") | |
.attr("transform","translate(20,30)") | |
.style("font-size","12px") | |
.style("fill-color", "white") | |
.call(d3.legend); | |
///////////////////////////////////////////////////////// | |
/////////////////// Helper Function ///////////////////// | |
///////////////////////////////////////////////////////// | |
//Taken from http://bl.ocks.org/mbostock/7555321 | |
//Wraps SVG text | |
function wrap(text, width) { | |
text.each(function() { | |
var text = d3.select(this), | |
words = text.text().split(/\s+/).reverse(), | |
word, | |
line = [], | |
lineNumber = 0, | |
lineHeight = 1.4, // ems | |
y = text.attr("y"), | |
x = text.attr("x"), | |
dy = parseFloat(text.attr("dy")), | |
tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em"); | |
while (word = words.pop()) { | |
line.push(word); | |
tspan.text(line.join(" ")); | |
if (tspan.node().getComputedTextLength() > width) { | |
line.pop(); | |
tspan.text(line.join(" ")); | |
line = [word]; | |
tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word); | |
} | |
} | |
}); | |
}//wrap | |
}//RadarChart | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment