Last active
November 13, 2018 18:20
-
-
Save LemoNode/cae7896d9a34d2d13b28a7f78381e9ac to your computer and use it in GitHub Desktop.
Diverging horizontal bars
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
license: gpl-3.0 |
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
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v5.min.js"></script> | |
<style> | |
body { | |
padding-top: 45px; | |
margin: auto; | |
width: 700px; | |
font: 11px arial; | |
} | |
.hide path , | |
.hide line { | |
display: none; | |
} | |
.bar--pos { | |
transition: 0.5s; | |
transition-property: fill; | |
fill: steelblue; | |
} | |
.bar--neg { | |
transition: 0.5s; | |
transition-property: fill; | |
fill: crimson; | |
} | |
.text--pos { | |
text-anchor:end; | |
} | |
.text--neg { | |
text-anchor:start; | |
} | |
</style> | |
</head> | |
<body> | |
<svg id="both" width="325" height="220"></svg> | |
<svg id="combined" width="325" height="220"></svg> | |
<select id="options"></select> | |
<script> | |
var server = [ | |
{ | |
month: "Q1", new_all: 130, old_all: 100, | |
new_1: 54, new_2: 32, new_3: 44, | |
old_1: 34, old_2: 44, old_3: 22 | |
}, | |
{ | |
month: "Q2", new_all: 138, old_all: 98, | |
new_1: 62, new_2: 62, new_3: 14, | |
old_1: 4, old_2: 42, old_3: 52 | |
}, | |
{ | |
month: "Q3", new_all: 94, old_all: 74, | |
new_1: 32, new_2: 52, new_3: 10, | |
old_1: 10, old_2: 22, old_3: 42 | |
}, | |
{ | |
month: "Q4", new_all: 85, old_all: 65, | |
new_1: 30, new_2: 46, new_3: 9, | |
old_1: 9 , old_2: 20, old_3: 36 | |
} | |
]; | |
load(server); | |
function load(data) { | |
var options = d3.select("#options").selectAll("option") | |
.data(["_1", "_2", "_3", "_all"]) | |
.enter().append("option") | |
.attr("value", d => d) | |
.text(d => "Type: " + d) | |
both(data); | |
combined(data); | |
}; | |
function both(data) { | |
var svg = d3.select("#both"), | |
margin = {top: 15, right: 10, bottom: 15, left: 10, axis: 15}, | |
width = +svg.attr("width") - margin.left - margin.right, | |
height = +svg.attr("height") - margin.top - margin.bottom; | |
var xN = d3.scaleLinear() | |
.rangeRound([(width / 2) + margin.axis, width - margin.left]) | |
var xO = d3.scaleLinear() | |
.rangeRound([(width / 2) - margin.axis, margin.right]) | |
var y = d3.scaleBand() | |
.domain(data.map(d => d.month)) | |
.rangeRound([height - margin.bottom, margin.top]) | |
.padding(0.1); | |
var xAxisN = svg.append("g") | |
.attr("class", "x-axis") | |
.attr("transform", `translate(0,${(height - margin.bottom)})`); | |
var xAxisO = xAxisN.clone(); | |
var yAxis = svg.append("g") | |
.attr("class", "hide") | |
.style("text-anchor", "middle") | |
.attr("transform", | |
`translate(${((margin.left + width + margin.right) / 2)}, 0)`) | |
.call(d3.axisLeft(y).ticks(null, "s")) | |
update("_all", 0); | |
function update(input, speed) { | |
var max = d3.max(data, d => Math.max(d["new"+input], d["old"+input])) | |
xN.domain([0, max]).nice(); | |
xO.domain(xN.domain()); | |
xAxisN.transition().duration(speed) | |
.call(d3.axisBottom(xO).ticks(5)); | |
xAxisO.transition().duration(speed) | |
.call(d3.axisBottom(xN).ticks(5)); | |
var newInput = svg.selectAll(".newInput") | |
.data(data, d => d.month); | |
newInput.exit().remove() | |
newInput.enter().append("rect") | |
.attr("class", "newInput bar--pos") | |
.attr("x", xN(0)) | |
.attr("y", d => y(d.month)) | |
.attr("height", y.bandwidth()) | |
.merge(newInput) | |
.transition().duration(speed) | |
.attr("width", d => xN(d["new" + input]) - xN(0)); | |
var oldInput = svg.selectAll(".oldInput") | |
.data(data, d => d.month); | |
oldInput.exit().remove() | |
oldInput.enter().append("rect") | |
.attr("class", "oldInput bar--neg") | |
.attr("y", d => y(d.month)) | |
.attr("height", y.bandwidth()) | |
.merge(oldInput) | |
.transition().duration(speed) | |
.attr("x", d => xO(d["old" + input])) | |
.attr("width", d => Math.abs(xO(d["old" + input]) - xO(0))); | |
} | |
both.update = update; | |
} | |
function combined(data) { | |
var svg = d3.select("#combined"), | |
margin = {top: 15, right: 10, bottom: 15, left: 10}, | |
width = +svg.attr("width") - margin.left - margin.right, | |
height = +svg.attr("height") - margin.top - margin.bottom; | |
var x = d3.scaleLinear() | |
.range([margin.left, width - margin.right]); | |
var y = d3.scaleBand() | |
.domain(data.map(d => d.month)) | |
.rangeRound([height - margin.bottom, margin.top]) | |
.padding(.1); | |
var xAxis = d3.axisBottom(x), | |
yAxis = d3.axisLeft(y).tickSizeOuter(0); | |
svg.append("g") | |
.attr("class", "x-axis") | |
.attr("transform", `translate(0,${(height - margin.bottom)})`) | |
svg.append("g") | |
.attr("class", "y-axis") | |
.attr("transform", "translate(" + (width/2) + ",0)") | |
update("_all", 0); | |
function update(input, speed) { | |
data.forEach(function(d) { | |
d.abs = +([d["new" + input] - d["old" + input]]); | |
return d; | |
}) | |
x.domain([ | |
d3.min(data, d => -Math.abs(d.abs)), | |
d3.max(data, d => Math.abs(d.abs)) | |
]).nice(); | |
svg.selectAll(".x-axis").transition().duration(speed) | |
.call(xAxis); | |
var bar = svg.selectAll(".bar") | |
.data(data, d => d.month); | |
bar.exit().remove() | |
bar.enter().insert("g", ".y-axis").append("rect") | |
.attr("class", d => "bar bar--" + (d.abs < 0 ? "neg" : "pos")) | |
.attr("y", d => y(d.month)) | |
.attr("height", y.bandwidth()) | |
.merge(bar) | |
.transition().duration(speed) | |
.attr("class", d => "bar bar--" + (d.abs < 0 ? "neg" : "pos")) | |
.attr("x", d => x(Math.min(0, d.abs))) | |
.attr("width", d => Math.abs(x(d.abs) - x(0))); | |
var text = svg.selectAll(".text") | |
.data(data, d => d.month); | |
text.exit().remove(); | |
text.enter().append("text") | |
.attr("fill", "#fff") | |
.attr("y", d => y(d.month) + (y.bandwidth() / 2) + 3) | |
.merge(text) | |
.transition().duration(speed) | |
.attr("class", d => "text text--" + (d.abs < 0 ? "neg" : "pos")) | |
.attr("width", d => Math.abs(x(d.abs) - x(0))) | |
.attr("x", d => (d.abs <= 0 | |
? x(Math.min(0, d.abs)) + 5 | |
: x(Math.max(0, d.abs)) - 5)) | |
.text(d => { | |
if (x(d.abs) < ((width / 2) + 15) && | |
x(d.abs) > ((width / 2) - 15)) return null; | |
return d.abs; | |
}) | |
var ticks = svg.selectAll(".y-axis").transition().duration(speed) | |
.call(yAxis); | |
ticks.selectAll("text") | |
.attr("text-anchor", (_, i) => data[i].abs >= 0 ? "end" : "start") | |
.attr("x", (_, i) => data[i].abs >= 0 ? -10 : 10); | |
ticks.selectAll("line") | |
.attr("x2", (_, i) => data[i].abs >= 0 ? -5 : 5); | |
} | |
combined.update = update; | |
} | |
var select = d3.select("#options") | |
.on("change", function() { | |
both.update(this.value, 750) | |
combined.update(this.value, 750) | |
}) | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment