Question: How can I get the x/y data values from an Obervable Plot for the point the mouse is hovering over? (Or touched on mobile.)
Background: I am trying to make an interactive timeline like on https://merrysky.net. So I would like to convert the mouse's X position to the time/temperature value from the data that has been plotted.
Current progress:
I have examined the Observable Plot documentation. There seems to be API's for updating certain marks when the pointer is near them, but I could not see anything for extracting data from the plot based on mouse position. The Crosshair mark seems to be the closest to what I am looking for.
Perhaps the underlying D3 API must be used?
Wow, Observable Plot is extremely cool. Looks like you can completely replicate your target (and what I did 8 years ago) as easy as:
const plot = Plot.plot({
marks: [
// draw the line graph
Plot.line(data, { x: 'x', y: 'y' }),
// add vertical interaction line
Plot.ruleX(data, Plot.pointerX({ x: 'x', py: 'y', stroke: 'red' })),
// add interaction dot
Plot.dot(data, Plot.pointerX({ x: 'x', y: 'y', stroke: 'red' })),
// add interaction text
Plot.text(
data,
Plot.pointerX({
x: 'x',
y: 'y',
dx: 20,
text: 'y',
})
),
],
});
Running example:
<!DOCTYPE html>
<div id="myplot"></div>
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<script src="https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6"></script>
<script type="module">
const data = [];
for (let i = 0; i < 20; i++) {
data.push({
x: i,
y: Math.random() * 100,
});
}
const plot = Plot.plot({
marks: [
Plot.line(data, { x: 'x', y: 'y' }),
Plot.ruleX(data, Plot.pointerX({ x: 'x', py: 'y', stroke: 'red' })),
Plot.dot(data, Plot.pointerX({ x: 'x', y: 'y', stroke: 'red' })),
Plot.text(
data,
Plot.pointerX({
x: 'x',
y: 'y',
dx: 20,
text: 'y',
})
),
],
});
const div = document.querySelector('#myplot');
div.append(plot);
</script>
-- Edits based on comment --
How about something like this. It provides an entry point to the plot so you can only tear down and recreate what you need:
<!DOCTYPE html>
<div id="myplot"></div>
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<script src="https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6"></script>
<script type="module">
const div = document.querySelector('#myplot');
const data1 = [];
for (let i = 0; i < 20; i++) {
data1.push({
x: i,
y: Math.random() * 100,
});
}
const plot1 = Plot.ruleX([0], {
render: (i, s, v, d, c, next) => {
const g = next(i, s, v, d, c);
c.ownerSVGElement.updateRuleX = (value) => {
const pg = d3.select('g');
pg.select('.custom-rule').remove();
const ig = pg.append('g')
.attr('class','custom-rule');
ig.append('line')
.attr('x1', s.x(value))
.attr('x2', s.x(value))
.attr('y1', s.y.range()[0])
.attr('y2', s.y.range()[1])
.attr('stroke', 'red');
ig.append('circle')
.attr('r', 5)
.attr('stroke', 'red')
.attr('cx', s.x(value))
.attr('cy', s.y(data1[value].y));
};
},
}).plot({
marks: [
Plot.line(data1, {x: 'x', y: 'y'}),
Plot.ruleX(data1, Plot.pointerX({ x: 'x', py: 'y', stroke: 'red' })),
Plot.dot(data1, Plot.pointerX({ x: 'x', y: 'y', stroke: 'red' })),
Plot.text(
data1,
Plot.pointerX({
x: 'x',
y: 'y',
dx: 20,
text: 'y',
})
),
],
});
div.append(plot1);
const updateRuleExternal = () => {
const v = Math.floor(Math.random() * (20 - 1 + 1) + 1);
plot1.updateRuleX(v)
};
setInterval(updateRuleExternal, 500);
</script>