Published
Edited
Aug 21, 2021
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
html`<svg viewBox="0 0 ${width} ${height}">
<path d="${line(data)}" fill="none" stroke="steelblue" stroke-width="1.5" stroke-miterlimit="1"></path>
<g fill="none" pointer-events="all">
${d3.pairs(data, (d, b) => svg`<rect x="${x(d.date)}" height="${height}" width="${x(b.date) - x(d.date)}">
<title>${formatDate(d.date)}
${formatClose(d.close)}</title>
</rect>`)}
</g>
${d3.select(svg`<g>`).call(xAxis).node()}
${d3.select(svg`<g>`).call(yAxis).node()}
</svg>`
Insert cell
Insert cell
Insert cell
voronoi = d3.Delaunay
.from(data, d => x(d.date), d => y(d.close))
.voronoi([0, 0, width, height])
Insert cell
html`<svg viewBox="0 0 ${width} ${height}">
<path d="${line(data)}" fill="none" stroke="steelblue" stroke-width="1.5" stroke-miterlimit="1"></path>
<g fill="none" pointer-events="all" stroke="red" stroke-width="0.5">
${data.map((d, i) => svg`<path d="${voronoi.renderCell(i)}">
<title>${formatDate(d.date)}
${formatClose(d.close)}</title>
</path>`)}
</g>
${d3.select(svg`<g>`).call(xAxis).node()}
${d3.select(svg`<g>`).call(yAxis).node()}
</svg>`
Insert cell
Insert cell
{
const tooltip = new Tooltip();
return html`<svg viewBox="0 0 ${width} ${height}">
<path d="${line(data)}" fill="none" stroke="steelblue" stroke-width="1.5" stroke-miterlimit="1"></path>
${d3.select(svg`<g>`).call(xAxis).node()}
${d3.select(svg`<g>`).call(yAxis).node()}
<g fill="none" pointer-events="all">
${d3.pairs(data, (a, b) => Object.assign(svg`<rect x="${x(a.date)}" height="${height}" width="${x(b.date) - x(a.date)}"></rect>`, {
onmouseover: () => tooltip.show(a),
onmouseout: () => tooltip.hide()
}))}
</g>
${tooltip.node}
</svg>`;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
class Tooltip {
constructor() {
this._date = svg`<text y="-22"></text>`;
this._close = svg`<text y="-12"></text>`;
this.node = svg`<g pointer-events="none" display="none" font-family="sans-serif" font-size="10" text-anchor="middle">
<rect x="-27" width="54" y="-30" height="20" fill="white"></rect>
${this._date}
${this._close}
<circle r="2.5"></circle>
</g>`;
}
show(d) {
this.node.removeAttribute("display");
this.node.setAttribute("transform", `translate(${x(d.date)},${y(d.close)})`);
this._date.textContent = formatDate(d.date);
this._close.textContent = formatClose(d.close);
}
hide() {
this.node.setAttribute("display", "none");
}
}
Insert cell
Insert cell
{
const tooltip = new Tooltip();

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

svg.append("path")
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("stroke-miterlimit", 1)
.attr("d", line(data));

svg.append("g")
.call(xAxis);

svg.append("g")
.call(yAxis);

svg.append("g")
.attr("fill", "none")
.attr("pointer-events", "all")
.selectAll("rect")
.data(d3.pairs(data))
.join("rect")
.attr("x", ([a, b]) => x(a.date))
.attr("height", height)
.attr("width", ([a, b]) => x(b.date) - x(a.date))
.on("mouseover", (event, [a]) => tooltip.show(a))
.on("mouseout", () => tooltip.hide());

svg.append(() => tooltip.node);

return svg.node();
}
Insert cell
Insert cell
{
const tooltip = new Tooltip();
return Object.assign(html`<svg viewBox="0 0 ${width} ${height}">
<path d="${line(data)}" fill="none" stroke="steelblue" stroke-width="1.5" stroke-miterlimit="1"></path>
${d3.select(svg`<g>`).call(xAxis).node()}
${d3.select(svg`<g>`).call(yAxis).node()}
${tooltip.node}
</svg>`, {
onmousemove: event => tooltip.show(bisect(data, x.invert(event.offsetX))),
onmouseleave: () => tooltip.hide()
});
}
Insert cell
Insert cell
bisect = {
const bisectDate = d3.bisector(d => d.date).left;
return (data, date) => {
const i = bisectDate(data, date, 1);
const a = data[i - 1], b = data[i];
return date - a.date > b.date - date ? b : a;
};
}
Insert cell
Insert cell
Insert cell
viewof number = html`<input type="range" min="0" max="100" step="1">`
Insert cell
number
Insert cell
Insert cell
viewof name = html`<input type="text" placeholder="Type a name!">`
Insert cell
name
Insert cell
viewof fruit = html`<select>
<option value="apple">Apple</option>
<option value="orange" selected>Orange</option>
<option value="banana">Banana</option>
</select>`
Insert cell
fruit
Insert cell
Insert cell
Insert cell
Insert cell
viewof hover = {
return Object.assign(html`<svg viewBox="0 0 ${width} ${height}">
<path d="${line(data)}" fill="none" stroke="steelblue" stroke-width="1.5" stroke-miterlimit="1"></path>
${d3.select(svg`<g>`).call(xAxis).node()}
${d3.select(svg`<g>`).call(yAxis).node()}
</svg>`, {
value: null,
onmousemove: event => {
event.currentTarget.value = bisect(data, x.invert(event.offsetX));
event.currentTarget.dispatchEvent(new CustomEvent("input"));
},
onmouseleave: event => {
event.currentTarget.value = null;
event.currentTarget.dispatchEvent(new CustomEvent("input"));
}
});
}
Insert cell
hover
Insert cell
Insert cell
viewof i = Scrubber(d3.range(100), {
autoplay: false,
loop: false
})
Insert cell
i
Insert cell
Insert cell
Insert cell
Insert cell
data = d3.csvParse(await FileAttachment("aapl-bollinger.csv").text(), d3.autoType)
Insert cell
line = d3.line().x(d => x(d.date)).y(d => y(d.close))
Insert cell
x = d3.scaleUtc()
.domain(d3.extent(data, d => d.date))
.range([margin.left, width - margin.right])
Insert cell
y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.upper)])
.range([height - margin.bottom, margin.top])
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0))
Insert cell
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y).ticks(height / 40))
.call(g => g.select(".domain").remove())
Insert cell
formatDate = d3.utcFormat("%b %-d, ’%y")
Insert cell
formatClose = d3.format("$.2f")
Insert cell
height = 240
Insert cell
margin = ({top: 20, right: 30, bottom: 30, left: 40})
Insert cell
d3 = require("d3@6")
Insert cell
import {Scrubber} from "@mbostock/scrubber"
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