Copied and tweaked from Scott Murray's examples. This gives introduction to:
- linking together two graphs built from same dataset
- adding 3 different sorting buttons based on ascending/descending of different variables
Copied and tweaked from Scott Murray's examples. This gives introduction to:
| <!DOCTYPE html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
| <title>D3: Linked charts</title> | |
| <style type="text/css"> | |
| body { | |
| background-color: gray; | |
| } | |
| #buttonContainer { | |
| margin-bottom: 10px; | |
| } | |
| button { | |
| padding: 15px; | |
| } | |
| svg { | |
| display: block; | |
| margin-bottom: 10px; | |
| background-color: white; | |
| } | |
| svg text { | |
| font-family: sans-serif; | |
| font-size: 16px; | |
| fill: black; | |
| font-style: bold; | |
| } | |
| g.bar { | |
| fill: #000775; | |
| } | |
| g.bar text { | |
| font-family: sans-serif; | |
| font-size: 14px; | |
| fill: black; | |
| font-style: bold; | |
| text-anchor: middle; | |
| opacity: 0; | |
| } | |
| g.bar.highlight text { | |
| opacity: 1; | |
| } | |
| g.bar.highlight rect { | |
| fill: #8ec1ff; | |
| } | |
| .axis path, | |
| .axis line { | |
| fill: none; | |
| stroke: black; | |
| shape-rendering: crispEdges; | |
| } | |
| .axis text { | |
| font-family: sans-serif; | |
| font-size: 11px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="buttonContainer"> | |
| <button id="sort">Sort Giving</button> | |
| <button id="sortDescending">Sort Receiving</button> | |
| <button id="sortRank">Sort Rank</button> | |
| </div> | |
| <script type="text/javascript"> | |
| //Width, height, padding | |
| var w = 600; | |
| var h = 350; | |
| var padding = 50; | |
| //Sample data | |
| var dataset = [ | |
| {"name":"A","sales":1520,"bonus":20}, | |
| {"name":"B","sales":656,"bonus":340}, | |
| {"name":"C","sales":182,"bonus":87}, | |
| {"name":"D","sales":187,"bonus":242}, | |
| {"name":"E","sales":35,"bonus":47}, | |
| {"name":"F","sales":193,"bonus":321}, | |
| {"name":"G","sales":56,"bonus":320}, | |
| {"name":"H","sales":61,"bonus":447}, | |
| {"name":"I","sales":21,"bonus":210}, | |
| {"name":"J","sales":26,"bonus":420}, | |
| {"name":"K","sales":28,"bonus":511}] ; | |
| //Configure x and y scale functions | |
| var xScale = d3.scale.ordinal() | |
| .domain(d3.range(dataset.length)) | |
| .rangeRoundBands([ padding, w - padding ], 0.1); | |
| //Now using two different y scales for two different charts | |
| var salesScale = d3.scale.linear() | |
| .domain([ 0, d3.max(dataset, function(d) { | |
| return d.sales; | |
| }) ]) | |
| .rangeRound([ h - padding, padding ]); | |
| var bonusScale = d3.scale.linear() | |
| .domain([ 0, d3.max(dataset, function(d) { | |
| return d.bonus; | |
| }) ]) | |
| .rangeRound([ h - padding, padding ]); | |
| //Now using two different y axes | |
| var salesAxis = d3.svg.axis() | |
| .scale(salesScale) | |
| .orient("left") | |
| .ticks(5) | |
| .outerTickSize(0); // remove outer tick mark on axis | |
| var bonusAxis = d3.svg.axis() | |
| .scale(bonusScale) | |
| .orient("left") | |
| .ticks(5) | |
| .outerTickSize(0); | |
| // | |
| // Make the first chart (sales data) | |
| // | |
| //Create SVG element | |
| var svg = d3.select("body") | |
| .append("svg") | |
| .attr("id", "salesChart") | |
| .attr("width", w) | |
| .attr("height", h); | |
| //Create groups | |
| var groups = svg.selectAll("g") | |
| .data(dataset) | |
| .enter() | |
| .append("g") | |
| .attr("class", "bar") | |
| .attr("transform", function(d, i) { | |
| return "translate(" + xScale(i) + ",0)"; | |
| }); | |
| //Add bar to each group | |
| var rects = groups.append("rect") | |
| .attr("x", 0) | |
| .attr("y", function(d) { | |
| return h - padding; | |
| }) | |
| .attr("width", xScale.rangeBand()) | |
| .attr("height", 0) | |
| // .attr("fill", "SteelBlue") | |
| ; | |
| //Add label to each group | |
| groups.append("text") | |
| .attr("x", xScale.rangeBand() / 2) | |
| .attr("y", function(d) { | |
| return salesScale(d.sales) - 12; | |
| }) | |
| .text(function(d) { | |
| return d.name + ": " + d.sales; | |
| }) | |
| //Add second piece of text - remove css opacity directly | |
| // height is related to height-padding of svg | |
| groups.append("text") | |
| .attr("x", xScale.rangeBand() / 2) | |
| .attr("y", [h - padding/1.6]) | |
| .text(function(d) { | |
| return d.name; | |
| }) | |
| .style("opacity", 1) | |
| //Transition rects into place | |
| rects.transition() | |
| .delay(function(d, i) { | |
| return i * 100; | |
| }) | |
| .duration(1500) | |
| .attr("y", function(d) { | |
| return salesScale(d.sales); | |
| }) | |
| .attr("height", function(d) { | |
| return h - padding - salesScale(d.sales); | |
| }); | |
| //Create y axis | |
| svg.append("g") | |
| .attr("class", "axis") | |
| .attr("transform", "translate(" + padding + ",0)") | |
| .attr("opacity", 0) | |
| .call(salesAxis) | |
| .transition() | |
| .delay(2000) | |
| .duration(1500) | |
| .attr("opacity", 1.0); | |
| // Title for chart 1 | |
| svg.append("text") | |
| .attr("x", padding/2) | |
| .attr("y", padding/2.5) | |
| .text("Giving") | |
| .style("text-anchor", "start"); | |
| // | |
| // Make the second chart (bonus data) | |
| // | |
| //Create SVG element | |
| svg = d3.select("body") | |
| .append("svg") | |
| .attr("id", "bonusChart") | |
| .attr("width", w) | |
| .attr("height", h); | |
| //Create groups | |
| groups = svg.selectAll("g") | |
| .data(dataset) | |
| .enter() | |
| .append("g") | |
| .attr("class", "bar") | |
| .attr("transform", function(d, i) { | |
| return "translate(" + xScale(i) + ",0)"; | |
| }); | |
| //Add bar to each group | |
| rects = groups.append("rect") | |
| .attr("x", 0) | |
| .attr("y", function(d) { | |
| return h - padding; | |
| }) | |
| .attr("width", xScale.rangeBand()) | |
| .attr("height", 0) | |
| // .attr("fill", "SteelBlue") | |
| ; | |
| //Add label to each group | |
| groups.append("text") | |
| .attr("x", xScale.rangeBand() / 2) | |
| .attr("y", function(d) { | |
| return bonusScale(d.bonus) - 10; | |
| }) | |
| .text(function(d) { | |
| return d.name + ": " + d.bonus; | |
| }) | |
| //Add second piece of text - remove css opacity directly | |
| // height is related to height-padding of svg | |
| groups.append("text") | |
| .attr("x", xScale.rangeBand() / 2) | |
| .attr("y", [h - padding/1.6]) | |
| .text(function(d) { | |
| return d.name; | |
| }) | |
| .style("opacity", 1) | |
| //Transition rects into place | |
| rects.transition() | |
| .delay(function(d, i) { | |
| return i * 100; | |
| }) | |
| .duration(1500) | |
| .attr("y", function(d) { | |
| return bonusScale(d.bonus); | |
| }) | |
| .attr("height", function(d) { | |
| return h - padding - bonusScale(d.bonus); | |
| }); | |
| //Create y axis | |
| svg.append("g") | |
| .attr("class", "axis") | |
| .attr("transform", "translate(" + padding + ",0)") | |
| .attr("opacity", 0) | |
| .call(bonusAxis) | |
| .transition() | |
| .delay(2000) | |
| .duration(1500) | |
| .attr("opacity", 1.0); | |
| //New functionality for interaction for ALL groups | |
| //in BOTH charts | |
| d3.selectAll("g.bar") | |
| .on("mouseover", function(d) { | |
| //Instead of d3.select(this), now we want to | |
| //select ALL groups that match the same | |
| //criteria as this one, i.e. this group | |
| //and the corresponding one in the other chart. | |
| // | |
| //We begin by selecting all groups, then | |
| //filtering to exclude those that don't match. | |
| // | |
| //We'll use each person's "name" as a unique | |
| //identifier. (With a real-world data set, you'd | |
| //probably use an ID number here.) | |
| var thisName = d.name; | |
| d3.selectAll("g.bar") | |
| .filter(function(d) { | |
| //If the name from the original group on | |
| //which mouseover was triggered matches the | |
| //name on the group we're evaluating right now… | |
| if (thisName == d.name) { | |
| return true; //…then it's a match | |
| } | |
| }) | |
| .classed("highlight", true); | |
| }) | |
| .on("mouseout", function() { | |
| //We could be selective, using the same filtering | |
| //criteria above, or, for simplicity, just | |
| //de-highlight all the groups. | |
| d3.selectAll("g.bar") | |
| .classed("highlight", false); | |
| }) | |
| // Title for chart 2 | |
| svg.append("text") | |
| .attr("x", padding/2) | |
| .attr("y", padding/2.5) | |
| .text("Receiving") | |
| .style("text-anchor", "start"); | |
| //Sort Ascending button - keep both graphs but link. | |
| d3.select("#sort") | |
| .on("click", function() { | |
| //Need to reselect all groups in each chart | |
| d3.selectAll("#salesChart g.bar").sort(function(a, b) { | |
| return d3.descending(a.sales, b.sales); | |
| }) | |
| .transition() | |
| .delay(function(d, i) { | |
| return i * 50; | |
| }) | |
| .duration(1000) | |
| .attr("transform", function(d, i) { | |
| return "translate(" + xScale(i) + ",0)"; | |
| }); | |
| d3.selectAll("#bonusChart g.bar").sort(function(a, b) { | |
| return d3.descending(a.sales, b.sales); | |
| }) | |
| .transition() | |
| .delay(function(d, i) { | |
| return i * 50; | |
| }) | |
| .duration(1000) | |
| .attr("transform", function(d, i) { | |
| return "translate(" + xScale(i) + ",0)"; | |
| }); | |
| }) | |
| ; | |
| /// Descending Button - sort both graphs but keep linked | |
| d3.select("#sortDescending") | |
| .on("click", function() { | |
| //Need to reselect all groups in each chart | |
| d3.selectAll("#salesChart g.bar").sort(function(a, b) { | |
| return d3.descending(a.bonus, b.bonus); | |
| }) | |
| .transition() | |
| .delay(function(d, i) { | |
| return i * 50; | |
| }) | |
| .duration(1000) | |
| .attr("transform", function(d, i) { | |
| return "translate(" + xScale(i) + ",0)"; | |
| }); | |
| d3.selectAll("#bonusChart g.bar").sort(function(a, b) { | |
| return d3.descending(a.bonus, b.bonus); | |
| }) | |
| .transition() | |
| .delay(function(d, i) { | |
| return i * 50; | |
| }) | |
| .duration(1000) | |
| .attr("transform", function(d, i) { | |
| return "translate(" + xScale(i) + ",0)"; | |
| }); | |
| }); | |
| /// Descending Button - sort both graphs but keep linked | |
| d3.select("#sortRank") | |
| .on("click", function() { | |
| //Need to reselect all groups in each chart | |
| d3.selectAll("#salesChart g.bar").sort(function(a, b) { | |
| return d3.ascending(a.name, b.name); | |
| }) | |
| .transition() | |
| .delay(function(d, i) { | |
| return i * 50; | |
| }) | |
| .duration(1000) | |
| .attr("transform", function(d, i) { | |
| return "translate(" + xScale(i) + ",0)"; | |
| }); | |
| d3.selectAll("#bonusChart g.bar").sort(function(a, b) { | |
| return d3.ascending(a.name, b.name); | |
| }) | |
| .transition() | |
| .delay(function(d, i) { | |
| return i * 50; | |
| }) | |
| .duration(1000) | |
| .attr("transform", function(d, i) { | |
| return "translate(" + xScale(i) + ",0)"; | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |