chart = {
const marginTop = 30;
const marginRight = 10;
const marginBottom = 0;
const marginLeft = 10;
const step = 29;
const height = data.length * (step + 1) + marginTop + marginBottom;
const x = d3.scaleTime().range([0, width]);
const y = d3.scaleLinear().rangeRound([0, -bands * step]);
const color = i => scheme[Math.max(3, bands)][i + Math.max(0, 3 - bands)];
const div = d3.create("div").style("position", "relative");
const canvas = div.selectAll("canvas")
.data(data)
.enter().append("canvas")
.attr("width", width)
.attr("height", step)
.style("position", "absolute")
.style("image-rendering", "pixelated")
.style("top", (d, i) => `${i * (step + 1) + marginTop}px`)
.property("context", function() { return this.getContext("2d"); })
.each(horizon);
const svg = div.append("svg")
.attr("width", width)
.attr("height", height)
.style("position", "relative")
.style("font", "10px sans-serif");
const gX = svg.append("g")
.attr("transform", `translate(0,${marginTop})`);
svg.append("g")
.selectAll("text")
.data(data)
.join("text")
.attr("x", 4)
.attr("y", (d, i) => (i + 0.5) * (step + 1) + marginTop)
.attr("dy", "0.35em")
.text((d, i) => i);
const rule = svg.append("line")
.attr("stroke", "#000")
.attr("y1", marginTop - 6)
.attr("y2", height - marginBottom - 1)
.attr("x1", 0.5)
.attr("x2", 0.5);
svg.on("mousemove touchmove", (event) => {
const x = d3.pointer(event, svg.node())[0] + 0.5;
rule.attr("x1", x).attr("x2", x);
});
function horizon(d) {
const {context} = this;
const {length: k} = d;
if (k < width) context.drawImage(this, k, 0, width - k, step, 0, 0, width - k, step);
context.fillStyle = "#fff";
context.fillRect(width - k, 0, k, step);
for (let i = 0; i < bands; ++i) {
context.save();
context.translate(width - k, (i + 1) * step);
context.fillStyle = color(i);
for (let j = 0; j < k; ++j) {
context.fillRect(j, y(d[j]), 1, -y(d[j]));
}
context.restore();
}
}
return Object.assign(div.node(), {
update: (data, {then, period}) => {
canvas.data(data).each(horizon);
x.domain([then - period * width, then]);
gX
.call(d3.axisTop(x).ticks(width / 80).tickSizeOuter(0))
.call(g => g.selectAll(".tick").filter(d => x(d) < marginLeft || x(d) >= width - marginRight).remove())
.call(g => g.select(".domain").remove());
}
});
}