Here is the component—it's pretty ugly, I think—but this used to be in the Svelte file within the route of a dashboard page. Figured it was a good idea to extract it out as a component. Now that it's working, I wanted to write about it to make sure I completely understand the process and nail down all the Svelte concepts that are still a little unclear to me.
```ts
<script lang="ts">
import { onMount } from "svelte";
import * as d3 from "d3";
import { pointer } from "d3-selection";
export let width = 1040;
export let height = 400;
export let marginTop = 20;
export let marginRight = 20;
export let marginBottom = 30;
export let marginLeft = 40;
type ObservationPlotData = {
datetime: string;
value: number;
};
let svg;
let gx;
let gy;
let gp;
let gvertline;
export let observations: ObservationPlotData[];
$: xScale = d3
.scaleTime()
.domain(d3.extent(observations, (d) => new Date(d.datetime)))
.range([marginLeft, width - marginRight]);
$: yScale = d3
.scaleLinear()
.domain(d3.extent(observations, (d) => parseFloat(d.value)))
.range([height - marginBottom, marginTop]);
$: lineGenerator = d3
.line()
.x((d) => xScale(new Date(d.datetime)))
.y((d) => yScale(parseFloat(d.value)));
$: path = lineGenerator(observations);
onMount(() => {
const svg_element = d3.select("svg");
// set up the xaxis
gx = svg_element
.append("g")
.attr("transform", `translate(0, ${height - marginBottom})`)
.call(d3.axisBottom(xScale));
// set up the yaxis
gy = svg_element
.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(yScale));
gp = svg_element
.append("g")
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5);
gp.append("path").attr("d", path);
gvertline = svg_element.append("g").attr("pointer-events", "none");
gvertline
.append("path")
.attr("stroke", "#999")
.attr("stroke-width", 1)
.attr("stroke-dasharray", "5,5");
svg_element.on("mousemove", handle_mouse_event).on("mouseleave", handle_mouse_leave);
});
function handle_mouse_event(event) {
const [mouse_position] = pointer(event);
console.log("handling the mouse event, mouse it at:", mouse_position);
const nearest_date_index = d3
.bisector((d) => new Date(d.datetime))
.left(observations, xScale.invert(mouse_position));
const nearest_obs = observations[nearest_date_index];
if (nearest_obs) {
gvertline
.attr("transform", `translate(${xScale(new Date(nearest_obs.datetime))},0)`)
.style("display", null);
gvertline.select("path").attr("d", `M0,${height - marginBottom}V${marginTop}`);
}
}
function handle_mouse_leave() {
gvertline.style("display", "none");
}
</script>
<svg bind:this={svg} {width} {height}></svg>
```