|
<!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> |