Zoomable radial chart with color scales and nesting. Based on Zoomable Sunburst.
forked from sathomas's block: Zoomable radial chart with color scales
| license: mit |
Zoomable radial chart with color scales and nesting. Based on Zoomable Sunburst.
forked from sathomas's block: Zoomable radial chart with color scales
| <!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> | |
| <script> | |
| d3.csv("https://gist.githubusercontent.com/sephcoster/c64c316b597895f0a644e4cbc5ed941c/raw/0be456471e8cbc17da626f134ac6536d9be8d00d/r1-2017-dod.json", function(data) { | |
| buildChart(data); | |
| }); | |
| function buildChart(dataset){ | |
| // 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: "Account Title", | |
| values: d3.nest() | |
| .key(function(d) { return d.organization; }) | |
| .key(function(d) { return d["Budget Activity Title"]; }) | |
| .key(function(d) { return d["Program Element / Budget Line Item (BLI) Title"]; }) | |
| .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> |
| http://jsdatav.is/img/thumbnails/radial.png |