D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
TLecailtel
Full window
Github gist
Stacked-to-grouped bar chart of stocks
Built with
blockbuilder.org
<!DOCTYPE html> <style> body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } form { position: absolute; left: 200px; top: 10px; } label { display: block; } .xAxis .domain { display: none; } </style> <form> <label><input type="radio" name="mode" value="stacked" checked> Stacked</label> <label><input type="radio" name="mode" value="grouped"> Grouped</label> </form> <body> <script src="https://d3js.org/d3.v4.min.js"></script> <script> // Sources: // https://bl.ocks.org/mbostock/3886208 // https://bl.ocks.org/mbostock/3887051 // https://bl.ocks.org/mbostock/3943967 var parseDate = d3.timeParse("%b %Y"); var formatTime = d3.timeFormat("%Y"); var margin = {top: 30, right: 30, bottom: 30, left: 40}, width = 960 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var svg = d3.select("body").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom); var g = svg.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var x = d3.scaleBand() .range([0, width]) .padding(0.4); var x1 = d3.scaleBand() .padding(0.1); var yBar = d3.scaleLinear() .range([height, 0]); var yStack = d3.scaleLinear() .range([height, 0]); var color = d3.scaleOrdinal() .range(["#81b900", "#ff9900", "#1f70c1", "#a7a9ac"]); d3.csv("stocks.csv", function(data) { var nest_by_year = d3.nest() .key(function(d) {return formatTime(parseDate(d.date)); }) .entries(data); // nest_by_year = [{key: "2000", values: {symbol: "MSFT", date: "Jan 2000", price: "39.81"},{symbol: ...}},{key: ...}] var transitional_data = []; nest_by_year.forEach(function(KeyYear) { var nest_by_symbol = d3.nest() .key(function(d) {return d.symbol; }) .rollup(function(v) {return d3.mean(v,function(d) {return d.price; })}) .object(KeyYear.values); // nest_by_symbol = {MSFT: 29.67, AMZN: 43.93, ...} transitional_data.push({ key: +KeyYear.key, values: nest_by_symbol }); }); // transitional_data = [{key: 2000, values: {MSFT: 29.67, AMZN: 43.93, ...}}, {key: ...}] var final_data = transitional_data.map(function(d) { var merged = {year: d.key}; // Copy each key-val pair of d.values into merged Object.keys(d.values).forEach(function(key) { merged[key] = d.values[key]; }); return merged; }); // final_data = [{year: 2000, MSFT: 29.67, AMZN: 43.93, ...}, {year: ...}] var keys = d3.keys(final_data[0]).slice(1); // keys = ["MSFT", "AMZN", "IBM", "AAPL"] var totals = []; final_data.forEach(function(d){ totals.push(d3.sum(keys, function(key) {return d[key]; })) }); x.domain(final_data.map(function(d) {return d.year; })); x1.domain(keys).range([0, x.bandwidth()]); yBar.domain([0, d3.max(final_data, function(d) {return d3.max(keys, function(key) {return d[key]; }); })]); yStack.domain([0, d3.max(totals)]); var group = g .selectAll("g") .data(d3.stack().keys(keys)(final_data)) .enter().append("g") .attr("fill", function(d) {return color(d.key); }); var rect = group.selectAll("rect") .data(function(d) {return d; }) .enter().append("rect") .attr("x", function(d) {return x(d.data.year); }) .attr("y", function(d) {return yStack(d[1]); }) .attr("height", function(d) {return yStack(d[0]) - yStack(d[1]); }) .attr("width", x.bandwidth()); g.append("g") .attr("class", "xAxis") .attr("transform", "translate(0," + height + ")") .call(d3.axisBottom(x).tickSize(0)); var yAxis = g.append("g") .call(d3.axisLeft(yStack)); yAxis.append("text") .attr("x", -35) .attr("y", -15) .attr("dy", "0.32em") .attr("fill", "#000") .attr("font-weight", "bold") .attr("text-anchor", "start") .text("Price (USD)"); d3.selectAll("input") .on("change", changed); function changed() { if (this.value === "grouped") toGrouped(); else toStacked(); }; function toGrouped() { rect .transition().duration(750) .attr("width", x1.bandwidth()) .transition().duration(1200) .attr("x", function(d) {return x(d.data.year) + x1(this.parentNode.__data__.key)}) .transition().duration(1200) .attr("y", function(d) {return yBar(d[1] - d[0]); }) .attr("height", function(d) {return yBar(0) - yBar(d[1]-d[0]); }); yAxis .transition() .call(d3.axisLeft(yBar)); }; function toStacked() { rect .transition().duration(1200) .attr("y", function(d) {return yStack(d[1] - d[0]); }) .attr("height", function(d) {return yStack(d[0]) - yStack(d[1]); }) .transition().duration(1200) .attr("y", function(d) {return yStack(d[1]); }) .transition().duration(750) .attr("x", function(d) {return x(d.data.year); }) .attr("width", x.bandwidth()); yAxis .transition() .call(d3.axisLeft(yStack)); }; var legend = svg.selectAll(".legend") .data(color.domain().reverse()).enter().append("g") .attr("class", "legend") .attr("transform", function(d, i) {return "translate(0," + i * 20 + ")"; }); legend.append("path") .style("fill", function(d) {return color(d); }) .attr("d", d3.symbol().type(d3.symbolSquare).size(100) ) .attr("transform", "translate(" + width/2.5 + "," + 20 +")"); legend.append("text") .attr("x", width/2.5 + 15) .attr("y", 20) .attr("dy", ".35em") .style("text-anchor", "start") .text(function(d) {return d; }); }); </script>
https://d3js.org/d3.v4.min.js