/
ScatterPlot.svelte
92 lines (80 loc) · 2.15 KB
/
ScatterPlot.svelte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<script lang="ts">
import { extent } from "d3-array";
import { axisBottom, axisLeft } from "d3-axis";
import { quadtree } from "d3-quadtree";
import { scalePoint, scaleUtc } from "d3-scale";
import { day } from "../format";
import Axis from "./Axis.svelte";
import { scatterplotScale } from "./helpers";
import type { ScatterPlotDatum } from "./scatterplot";
import type { TooltipFindNode } from "./tooltip";
import { domHelpers, positionedTooltip } from "./tooltip";
export let data: ScatterPlotDatum[];
export let width: number;
const today = new Date();
const margin = {
top: 10,
right: 10,
bottom: 30,
left: 70,
};
const height = 250;
$: innerWidth = width - margin.left - margin.right;
$: innerHeight = height - margin.top - margin.bottom;
// Scales
$: dateExtent = extent(data, (d) => d.date);
$: x = scaleUtc()
.domain(dateExtent[0] ? dateExtent : [0, 1])
.range([0, innerWidth]);
$: y = scalePoint()
.padding(1)
.domain(data.map((d) => d.type))
.range([innerHeight, 0]);
// Axes
$: xAxis = axisBottom(x).tickSizeOuter(0);
$: yAxis = axisLeft(y)
.tickPadding(6)
.tickSize(-innerWidth)
.tickFormat((d) => d);
/** Quadtree for hover. */
$: quad = quadtree(
data,
(d) => x(d.date),
(d) => y(d.type) ?? 0
);
function tooltipText(d: ScatterPlotDatum) {
return [domHelpers.t(d.description), domHelpers.em(day(d.date))];
}
const tooltipFindNode: TooltipFindNode = (xPos, yPos) => {
const d = quad.find(xPos, yPos);
return d && [x(d.date), y(d.type) ?? 0, tooltipText(d)];
};
</script>
<svg {width} {height}>
<g
use:positionedTooltip={tooltipFindNode}
transform={`translate(${margin.left},${margin.top})`}
>
<Axis x axis={xAxis} {innerHeight} />
<Axis y axis={yAxis} />
<g>
{#each data as dot}
<circle
r="5"
fill={scatterplotScale(dot.type)}
cx={x(dot.date)}
cy={y(dot.type)}
class:desaturate={dot.date > today}
/>
{/each}
</g>
</g>
</svg>
<style>
svg > g {
pointer-events: all;
}
.desaturate {
filter: saturate(50%);
}
</style>