Skip to content

Instantly share code, notes, and snippets.

@tomshanley
Last active August 10, 2017 00:52
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/61b8c01eb29130a0061b549931315c42 to your computer and use it in GitHub Desktop.
Save tomshanley/61b8c01eb29130a0061b549931315c42 to your computer and use it in GitHub Desktop.
Horizon bar chart v3 (mirror v offset)
license: mit
<!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);
var numberOfBands = 4;
var bandWidth = maxY/numberOfBands;
const height = 80;
const width = 800;
const margin = { "top": 10, "bottom": 10, "left": 50, "right": 10, };
//used Draw My Data http://www.robertgrantstats.co.uk/drawmydata.html
let data = [9.5433, 4.5433, 4.5433, 6.0817, 8.7743, 10.6971, 22.2356, 34.5433, 37.2356, 37.6202, 37.2356, 35.6971, 34.5433, 38.3894, 43.3894, 47.6202, 52.2356, 55.3125, 59.1587, 59.9279, 58.3894, 56.851, 56.0817, 59.5433, 61.851, 64.9279, 67.6202, 69.9279, 69.9279, 68.004, 67.2356, 65.3125, 62.2356, 60.3125, 58.3894, 57.2356, 47.6202, 39.9279, 1.0817, 2.6202, -3.0048, -6.6202, -16.4663, -19.0817, -39.9279, -57.2356, -65.3125, -82.6202, -91.0817, -88.774, -77.6202, -66.0817, -64.5433, -54.5433, -45.3125, -46.4663, -49.9279, -40.6971, -37.6202, -36.0817, -35.3125, -39.5433, -38.774, -27.6202, -20.3125, -16.851, 7.6202, 8.774, 13.774, 18.774, 28.0048, 36.4663, 40.3125];
//If I want just positive values;
/*for(var i = 0; i < data.length; i++) {
data[i] = Math.abs(data[i])
}*/
const barWidth = width / data.length - 1;
const xScale = d3.scaleLinear()
.domain([0, data.length])
.range([0, width]);
let mode = "mirror"; //or offset
//let mode = "offset";
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]");
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 drawHorizon(data) {
d3.selectAll("svg").remove();
let yScale = d3.scaleLinear()
.domain([0, bandWidth])
.range([height, 0]);
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(data)
.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) < bandWidth
? "white"
: colour(band(d, bandWidth));
});
let foregroundBars = bars.append("rect")
.attr("y", function (d) {
if (mode == "offset" && d < 0) {
return yScale(bandWidth)
} else {
let thisHeight = barHeight(d, bandWidth);
return yScale(thisHeight);
};
})
.attr("width", barWidth)
.attr("height", function (d) {
let thisHeight = barHeight(d, bandWidth);
return height - yScale(thisHeight);
})
.style("fill", function (d) {
let thisBand = d > 0
? band(d, bandWidth) + bandWidth
: band(d, 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