Published
Edited
May 24, 2019
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
html`
<div id="chart">
<div id="_dom"/>
<canvas id="_canvas" width="${width}" height="${height}"></canvas>
<svg id="_svg" width="${width}" height="${height}" />
</div>`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
x = d3.scaleBand().range([ margin.left, width - margin.right ]).domain(data.map(d => d.letter)).padding(0.25)
Insert cell
y = d3.scaleLinear().range([ height - margin.bottom, margin.top ]).domain([ 0, d3.max(data, d => d.count) ]).nice()
Insert cell
Insert cell
Insert cell
renderSvg = svg => {
svg.call(xAxis);
svg.call(yAxis);
}
Insert cell
Insert cell
renderHtml = html => {
let yTitle = html.select(".yTitle");
if (yTitle.size() === 0) {
yTitle = html
.append("div")
.attr("class", "yTitle")
.text("Count");
}
yTitle
.style("text-align", "center")
.style("width", `${height}px`)
.style("transform", `translate(-{height/2}px, 200px)rotate(-90deg)`);
let xTitle = html.select(".xTitle");
if (xTitle.size() === 0) {
xTitle = html
.append("div")
.attr("class", "xTitle")
.text("Letters");
}
xTitle
.style("text-align", "center")
.style("width", `${width}px`)
.style("transform", `translate(0px, ${height - margin.bottom + 20}px)`);
}
Insert cell
Insert cell
renderCircle = (context, node) => {
const selection = d3.select(node);
const cx = selection.attr("cx");
const cy = selection.attr("cy");
const r = selection.attr("r");
const fill = selection.style("fill");
const stroke = selection.style("stroke");
const strokeWidth = selection.style("stroke-width") || 1;

context.beginPath();
context.arc(cx, cy, r, 0, 2 * Math.PI);

if (fill) {
context.fillStyle = fill;
context.fill();
}

if (stroke) {
context.strokeStyle = stroke;
context.lineWidth = strokeWidth;
context.stroke();
}
};
Insert cell
renderRect = (context, node) => {
const selection = d3.select(node);
const x = selection.attr("x");
const y = selection.attr("y");
const width = selection.attr("width");
const height = selection.attr("height");
const fill = selection.style("fill");
const stroke = selection.style("stroke");
const strokeWidth = selection.style("stroke-width") || 1;

if (fill) {
context.fillStyle = fill;
context.fillRect(x, y, width, height);
}

if (stroke && stroke !== "none") {
context.strokeStyle = stroke;
context.lineWidth = strokeWidth;
context.strokeRect(x, y, width, height);
}
}
Insert cell
md`To make things even easier, we can provide a more generic render function. We'll call this and it can itself determine the best canvas rendering function to call based on the type of element (we'll come to these shortly).`
Insert cell
renderNode = (context, node) => {
switch(node.nodeName) {
case "CIRCLE": renderCircle(context, node);
case "RECT": renderRect(context, node);
}
}
Insert cell
Insert cell
document.createElement("custom");
Insert cell
{
render(data)
}
Insert cell
render = () => {
const dom = d3.select("#_dom");
const svg = d3.select("#_svg");
const canvas = d3.select("#_canvas").node().getContext("2d");
const join = d3.select("custom")
.selectAll(".bar")
.data(data, d => d.letter);
const exit = join
.exit()
.transition()
.duration(duration)
.attr("y", d => y(0))
.attr("height", 0)
.remove();
const enter = join.enter()
.append("rect")
.attr("class", "bar")
.attr("y", d => y(0))
.attr("x", d => x(d.letter))
.attr("height", 0)
.attr("width", x.bandwidth())
.style("fill", "steelblue");
// Elements to update
const update = enter
.merge(join)
.transition()
.delay(duration)
.duration(duration)
.attr("x", d => x(d.letter))
.attr("width", x.bandwidth())
.transition()
.delay((d, i) => i * duration / 4)
.ease(d3.easeBounce)
.attr("y", d => y(d.count))
.attr("height", d => y(0) - y(d.count))
renderSvg(svg);
renderHtml(dom);
renderCanvas(canvas, enter, exit, enter.merge(update));
}
Insert cell
Insert cell
renderCanvas = (context, enter, exit, update) => {
d3.timer(() => {
context.clearRect(0, 0, width, height);
enter.each((d, i, elements) => { renderNode(context, elements[i]); });
update.each((d, i, elements) => { renderNode(context, elements[i]); });
exit.each((d, i, elements) => { renderNode(context, elements[i]); });
}, 0, duration);
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more