Zoomable radial chart with color scales and nesting. Based on Zoomable Sunburst.
Last active
August 29, 2015 14:11
-
-
Save sathomas/4a3b74228d9cb11eb486 to your computer and use it in GitHub Desktop.
Zoomable radial chart with color scales
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 charset='utf-8'> | |
<title>Zoomable radial chart with color scales</title> | |
<link href='http://fonts.googleapis.com/css?family=Varela' rel='stylesheet' | |
type='text/css'> | |
<style> | |
body { font-family: Varela,sans-serif; } | |
</style> | |
</head> | |
<body> | |
<script src='http://d3js.org/d3.v3.min.js'></script> | |
<!-- | |
Because of cross-origin restrictions, we have to use | |
a hack to load our map data and tornado dataset. | |
--> | |
<script src='http://jsDataV.is/data/tornadoes.js'></script> | |
<script> | |
// Define the dimensions of the visualization. | |
var margin = {top: 30, right: 10, bottom: 20, left: 10}, | |
width = 636 - margin.left - margin.right, | |
height = 476 - margin.top - margin.bottom, | |
radius = Math.min(width, height) / 2; | |
// Create the SVG container for the visualization and | |
// define its dimensions. Within that container, add a | |
// group element (`<g>`) that can be transformed via | |
// a translation to account for the margins and to | |
// center the visualization in the container. | |
var svg = d3.select("body").append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", "translate(" + | |
(margin.left + width / 2) + "," + | |
(margin.top + height / 2) + ")"); | |
// Define the scales that will translate data values | |
// into visualization properties. The "x" scale | |
// will represent angular position within the | |
// visualization, so it ranges lnearly from 0 to | |
// 2π. The "y" scale will reprent area, so it | |
// ranges from 0 to the full radius of the | |
// visualization. Since area varies as the square | |
// of the radius, this scale takes the square | |
// root of the input domain before mapping to | |
// the output range. | |
var x = d3.scale.linear() | |
.range([0, 2 * Math.PI]); | |
var y = d3.scale.sqrt() | |
.range([0, radius]); | |
// Define the function that creates a partition | |
// layout from the dataset. Because we're using | |
// `d3.nest` to construct the input dataset, the | |
// children array will be stored in the `values` | |
// property unless the node is a leaf node. In | |
// that case the `values` property will hold | |
// the data value itself. | |
var partition = d3.layout.partition() | |
.children(function(d) { | |
return Array.isArray(d.values) ? | |
d.values : null; | |
}) | |
.value(function(d) { | |
return d.values; | |
}); | |
// Define a function that returns the color | |
// for a data point. The input parameter | |
// should be a data point as defined/created | |
// by the partition layout. | |
var color = function(d) { | |
// This function builds the total | |
// color palette incrementally so | |
// we don't have to iterate through | |
// the entire data structure. | |
// We're going to need a color scale. | |
// Normally we'll distribute the colors | |
// in the scale to child nodes. | |
var colors; | |
// The root node is special since | |
// we have to seed it with our | |
// desired palette. | |
if (!d.parent) { | |
// Create a categorical color | |
// scale to use both for the | |
// root node's immediate | |
// children. We're using the | |
// 10-color predefined scale, | |
// so set the domain to be | |
// [0, ... 9] to ensure that | |
// we can predictably generate | |
// correct individual colors. | |
colors = d3.scale.category10() | |
.domain(d3.range(0,10)); | |
// White for the root node | |
// itself. | |
d.color = "#fff"; | |
} else if (d.children) { | |
// Since this isn't the root node, | |
// we construct the scale from the | |
// node's assigned color. Our scale | |
// will range from darker than the | |
// node's color to brigher than the | |
// node's color. | |
var startColor = d3.hcl(d.color) | |
.darker(), | |
endColor = d3.hcl(d.color) | |
.brighter(); | |
// Create the scale | |
colors = d3.scale.linear() | |
.interpolate(d3.interpolateHcl) | |
.range([ | |
startColor.toString(), | |
endColor.toString() | |
]) | |
.domain([0,d.children.length+1]); | |
} | |
if (d.children) { | |
// Now distribute those colors to | |
// the child nodes. We want to do | |
// it in sorted order, so we'll | |
// have to calculate that. Because | |
// JavaScript sorts arrays in place, | |
// we use a mapped version. | |
d.children.map(function(child, i) { | |
return {value: child.value, idx: i}; | |
}).sort(function(a,b) { | |
return b.value - a.value | |
}).forEach(function(child, i) { | |
d.children[child.idx].color = colors(i); | |
}); | |
} | |
return d.color; | |
}; | |
// Define the function that constructs the | |
// path for an arc corresponding to a data | |
// value. | |
var arc = d3.svg.arc() | |
.startAngle(function(d) { | |
return Math.max(0, | |
Math.min(2 * Math.PI, x(d.x))); | |
}) | |
.endAngle(function(d) { | |
return Math.max(0, | |
Math.min(2 * Math.PI, x(d.x + d.dx))); | |
}) | |
.innerRadius(function(d) { | |
return Math.max(0, y(d.y)); | |
}) | |
.outerRadius(function(d) { | |
return Math.max(0, y(d.y + d.dy)); | |
}); | |
// Extract the hierachy from the raw data | |
// Using `d3.nest` operations. The data's | |
// hierarchy is region -> state -> county. | |
// At the county level, we're only interested | |
// in a count of the data points. | |
var hierarchy = { | |
key: "United States", | |
values: d3.nest() | |
.key(function(d) { return d.region; }) | |
.key(function(d) { return d.state; }) | |
.key(function(d) { return d.county; }) | |
.rollup(function(leaves) { | |
return leaves.length; | |
}) | |
.entries(dataset) | |
}; | |
// Construct the visualization. | |
var path = svg.selectAll("path") | |
.data(partition.nodes(hierarchy)) | |
.enter().append("path") | |
.attr("d", arc) | |
.attr("stroke", "#fff") | |
.attr("fill-rule", "evenodd") | |
.attr("fill", color) | |
.on("click", click) | |
.on("mouseover", mouseover) | |
.on("mouseout", mouseout); | |
// Add a container for the tooltip. | |
var tooltip = svg.append("text") | |
.attr("font-size", 12) | |
.attr("fill", "#000") | |
.attr("fill-opacity", 0) | |
.attr("text-anchor", "middle") | |
.attr("transform", "translate(" + 0 + "," + (12 + height/2) +")") | |
.style("pointer-events", "none"); | |
// Add the title. | |
svg.append("text") | |
.attr("font-size", 16) | |
.attr("fill", "#000") | |
.attr("text-anchor", "middle") | |
.attr("transform", "translate(" + 0 + "," + (-10 -height/2) +")") | |
.text("Tornado Sightings in 2013 (www.noaa.gov)"); | |
// Handle clicks on data points. All | |
// we need to do is start the transition | |
// that updates the paths of the arcs. | |
function click(d) { | |
path.transition() | |
.duration(750) | |
.attrTween("d", arcTween(d)); | |
// Hide the tooltip since the | |
// path "underneath" the cursor | |
// will likely have changed. | |
mouseout(); | |
}; | |
// Handle mouse moving over a data point | |
// by enabling the tooltip. | |
function mouseover(d) { | |
tooltip.text(d.key + ": " + | |
d.value + " sighting" + | |
(d.value > 1 ? "s" : "")) | |
.transition() | |
.attr("fill-opacity", 1); | |
}; | |
// Handle mouse leaving a data point | |
// by disabling the tooltip. | |
function mouseout() { | |
tooltip.transition() | |
.attr("fill-opacity", 0); | |
}; | |
// Function to interpolate values for | |
// the visualization elements during | |
// a transition. | |
function arcTween(d) { | |
var xd = d3.interpolate(x.domain(), | |
[d.x, d.x + d.dx]), | |
yd = d3.interpolate(y.domain(), | |
[d.y, 1]), | |
yr = d3.interpolate(y.range(), | |
[d.y ? 20 : 0, radius]); | |
return function(d, i) { | |
return i ? | |
function(t) { | |
return arc(d); | |
} : | |
function(t) { | |
x.domain(xd(t)); | |
y.domain(yd(t)).range(yr(t)); | |
return arc(d); | |
}; | |
}; | |
} | |
</script> |
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
http://jsdatav.is/img/thumbnails/radial.png |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment