Skip to content

Instantly share code, notes, and snippets.

@tomshanley
Last active August 10, 2017 00:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomshanley/dd93d10cc2959f72b2bc3858bf486075 to your computer and use it in GitHub Desktop.
Save tomshanley/dd93d10cc2959f72b2bc3858bf486075 to your computer and use it in GitHub Desktop.
Horizon bar chart v3 (mirror v offset)
license: mit
series value
A 9.5433
A 4.5433
A 4.5433
A 6.0817
A 8.7743
A 10.6971
A 22.2356
A 34.5433
A 37.2356
A 37.6202
A 37.2356
A 35.6971
A 34.5433
A 38.3894
A 43.3894
A 47.6202
A 52.2356
A 55.3125
A 59.1587
A 59.9279
A 58.3894
A 56.851
A 56.0817
A 59.5433
A 61.851
A 64.9279
A 67.6202
A 69.9279
A 69.9279
A 68.004
A 67.2356
A 65.3125
A 62.2356
A 60.3125
A 58.3894
A 57.2356
A 47.6202
A 39.9279
A 1.0817
A 2.6202
A -3.0048
A -6.6202
A -16.4663
A -19.0817
A -39.9279
A -57.2356
A -65.3125
A -82.6202
A -91.0817
A -88.774
A -77.6202
A -66.0817
A -64.5433
A -54.5433
A -45.3125
A -46.4663
A -49.9279
A -40.6971
A -37.6202
A -36.0817
A -35.3125
A -39.5433
A -38.774
A -27.6202
A -20.3125
A -16.851
A 7.6202
A 8.774
A 13.774
A 18.774
A 28.0048
A 36.4663
A 40.3125
B -9.5433
B -4.5433
B -4.5433
B -6.0817
B -8.7743
B -10.6971
B -22.2356
B -34.5433
B -37.2356
B -37.6202
B -37.2356
B -35.6971
B -34.5433
B -38.3894
B -43.3894
B -47.6202
B -52.2356
B -55.3125
B -59.1587
B -59.9279
B -58.3894
B -56.851
B -56.0817
B -59.5433
B -61.851
B -64.9279
B -67.6202
B -69.9279
B -69.9279
B -68.004
B -67.2356
B -65.3125
B -62.2356
B -60.3125
B -58.3894
B -57.2356
B -47.6202
B -39.9279
B -1.0817
B -2.6202
B 3.0048
B 6.6202
B 16.4663
B 19.0817
B 39.9279
B 57.2356
B 65.3125
B 82.6202
B 91.0817
B 88.774
B 77.6202
B 66.0817
B 64.5433
B 54.5433
B 45.3125
B 46.4663
B 49.9279
B 40.6971
B 37.6202
B 36.0817
B 35.3125
B 39.5433
B 38.774
B 27.6202
B 20.3125
B 16.851
B -7.6202
B -8.774
B -13.774
B -18.774
B -28.0048
B -36.4663
B -40.3125
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<style>
body {
font-family: sans-serif;
margin: 0;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
rect,
line {
shape-rendering: crispEdges
}
</style>
</head>
<body>
<h2>Horizon bar chart</h2>
<div id="horizon-controls">
<p>Choose number of bands:
<select id="bands-select">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="10">10</option>
</select>
</p>
<input name="mode" type="radio" value="mirror" id="horizon-mode-mirror" checked><label for="horizon-mode-mirror"> Mirror</label>
<input name="mode" type="radio" value="offset" id="horizon-mode-offset"><label for="horizon-mode-offset"> Offset</label>
</div>
<div id="horizon"></div>
<script>
const maxY = 100;
const minY = -(maxY);
//defaults
var numberOfBands = 4;
var bandWidth = maxY/numberOfBands;
let mode = "mirror"; //or offset
//let mode = "offset";
const height = 80;
const width = 800;
const margin = { "top": 10, "bottom": 10, "left": 50, "right": 10, };
const xScale = d3.scaleLinear()
.range([0, width]);
let colour = d3.scaleSequential(d3.interpolateRdYlGn)
.domain([minY,maxY])
var bandsSelect = d3.select("#bands-select");
bandsSelect.property("value", numberOfBands);
var modeSelect = d3.selectAll("#horizon-controls input[name=mode]");
d3.csv("data.csv", convertTextToNumbers, function(error, data){
if (error) { throw error; };
modeSelect.on("change", function() {
mode = this.value;
drawHorizon(data);
drawLegend();
});
bandsSelect.on("change", function(d){
var selectedBand = d3.select("select").property("value");
numberOfBands = +selectedBand;
bandWidth = maxY/numberOfBands;
drawHorizon(data);
drawLegend();
});
drawHorizon(data);
drawLegend();
});
function convertTextToNumbers(d) {
d.value = +d.value
return d;
};
function drawHorizon(data) {
d3.selectAll("svg").remove();
let yScale = d3.scaleLinear()
.domain([0, bandWidth])
.range([height, 0]);
var nestedBySeries = d3.nest()
.key(function(d){ return d.series })
.entries(data);
nestedBySeries.forEach(function(series){
let seriesData = series.values;
let barWidth = width / seriesData.length - 1;
xScale.domain([0, seriesData.length]);
let svg = d3.select("#horizon").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
let g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
let bars = g.selectAll(".bars")
.data(seriesData)
.enter()
.append("g")
.attr("transform", function (d, i) {
return "translate(" + xScale(i) + ",0)";
});
let backgroundBars = bars.append("rect")
.attr("width", barWidth)
.attr("height", height)
.style("fill", function (d) {
return Math.abs(d.value) < bandWidth
? "white"
: colour(band(d.value, bandWidth));
});
let foregroundBars = bars.append("rect")
.attr("y", function (d) {
if (mode == "offset" && d.value < 0) {
return yScale(bandWidth)
} else {
let thisHeight = barHeight(d.value, bandWidth);
return yScale(thisHeight);
};
})
.attr("width", barWidth)
.attr("height", function (d) {
let thisHeight = barHeight(d.value, bandWidth);
return height - yScale(thisHeight);
})
.style("fill", function (d) {
let thisBand = d.value > 0
? band(d.value, bandWidth) + bandWidth
: band(d.value, bandWidth) - bandWidth
return colour(thisBand);
});
});
};
function drawLegend() {
let legendWidth = 25;
let numberOfLegendItems = numberOfBands * 2;
let legendHeight = legendWidth * numberOfLegendItems;
const legendMargin = {"top": 10, "bottom": 10, "left": 25, "right": 150 };
let legend = d3.select("body").append("svg")
.attr("width", legendWidth + legendMargin.left + legendMargin.right)
.attr("height", legendMargin.top + legendHeight + legendMargin.bottom)
.append("g")
.attr("transform", "translate(" + legendMargin.left + "," + legendMargin.top + ")");
let legendData = [];
let i = 0;
for (i; i < numberOfLegendItems; i++) {
let datum = minY + (i * bandWidth) + bandWidth;
legendData.push(datum)
};
let legendItems = legend.selectAll("g")
.data(legendData)
.enter()
.append("g")
.attr("transform", function(d, j) {
return "translate(0," + (j * legendWidth) + ")"
});
legendItems.append("rect")
.attr("width", legendWidth)
.attr("height", legendWidth)
.style("fill", function(d) {
return d <= 0
? colour(d - bandWidth)
: colour(d);
})
.style("stroke", "white");
legendItems.append("text")
.text(function(d){
return round(d - bandWidth) + " to " + round(d);
})
.attr("x", legendWidth + 5)
.attr("y", legendWidth/2 + 5)
};
function band(n, bandWidth) {
let band = n > 0
? Math.floor(n / bandWidth) * bandWidth
: Math.ceil(n / bandWidth) * bandWidth;
return band;
};
function barHeight(n, bandWidth) {
let absoluteN = Math.abs(n)
return absoluteN - band(absoluteN, bandWidth);
};
function round(n){
return Math.round(n * 10)/10;
};
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment