Published
Edited
Dec 10, 2019
1 fork
25 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
delaunay = d3.Delaunay.from(data, d => x(d.date), d => y(d.price_gb))
Insert cell
Insert cell
rect = g =>
g
.append('rect')
.attr('fill', 'transparent')
.attr('width', chartWidth)
.attr('height', height)
.on('mousemove', mousemoved)
.on('mouseleave', mouseleft)
Insert cell
Insert cell
tooltip = {
const div = d3
.create('div')
.style('width', '200px')
.style('position', 'absolute')
.style('padding', '8px')
.style('border', '1px solid #ccc')
.style('pointer-events', 'none')
.style('background', 'white')
.style('display', 'none');

return div.node();
}
Insert cell
Insert cell
function mousemoved() {
const [mx, my] = d3.mouse(d3.select(this).node());

// the mutable keyword is an Observable thing, don't reference it on a normal browser
// https://observablehq.com/@observablehq/introduction-to-mutable-state
mutable hover = find(mx, my);

if (!hover) return mouseleft();

// quick trick for avoiding the edges in the tooltip
const xRatio = mx / chartWidth;

d3
.select(tooltip)
.style('display', 'block')
.style(
'left',
`${xRatio > 0.75 ? mx - 200 : xRatio < 0.15 ? mx + 50 : mx - 50}px`
)
.style('top', `${my + 30}px`).html(`<div>
<strong>${hover['Manufacturer ']} ${hover['Series ']}</strong>
</div>
<div class="flex">
<div>Size</div><div>${hover['Size ']}</div>
</div>
<div class="flex">
<div>Price</div><div>${ft(hover.price)}</div>
</div>
<div class="flex">
<div>Price per 128GB</div><div>${ft(hover.price_gb)}</div>
</div>`);
}
Insert cell
Insert cell
Insert cell
Insert cell
find = (mx, my) => {
const idx = delaunay.find(mx, my);

if (idx !== null) {
const datum = data[idx];
const d = distance(x(datum.date), y(datum.price_gb), mx, my);

return d < radius ? datum : null;
}

return null;
}
Insert cell
Insert cell
distance = (px, py, mx, my) => {
const a = px - mx;
const b = py - my;

return Math.sqrt(a * a + b * b);
}
Insert cell
Insert cell
mouseleft = () => {
d3.select(tooltip).style('display', 'none');
}
Insert cell
Insert cell
html`<style>
.flex {
display: flex;
justify-content: space-between;
}
.flex:not(:last-child) {
border-bottom: 1px solid #eee;
margin-bottom: 2px;
}
</style>`
Insert cell
Insert cell
chart = {
const svg = d3
.create('svg')
.attr('width', chartWidth + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom);

const g = svg
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);

g.append('g')
.attr('class', 'x axis')
.attr('transform', `translate(0,${height})`)
.call(xAxis)
.call(g => g.select(".domain").remove());

g.append('g')
.attr('class', 'y axis')
.call(yAxis)
.call(g => g.select(".domain").remove())
.selectAll('.tick line')
.attr('stroke', '#eee');

g.append('g')
.attr('class', 'points')
.selectAll('circle')
.data(data)
.join('circle')
.attr('fill', 'steelblue')
.attr('r', 4)
.attr('cx', d => x(d.date))
.attr('cy', d => y(d.price_gb));

g.append('circle')
.attr('opacity', hover ? 1 : 0)
.attr('r', 4)
.attr('stroke-width', 2)
.attr('fill', 'none')
.attr('stroke', 'black')
.attr('cx', hover ? x(hover.date) : null)
.attr('cy', hover ? y(hover.price_gb) : null);

g.append('g').call(rect);

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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