-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathAxes.tsx
More file actions
117 lines (101 loc) · 3.56 KB
/
Axes.tsx
File metadata and controls
117 lines (101 loc) · 3.56 KB
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// Code generated by AI and checked/modified for correctness
import { For, createEffect } from "solid-js";
import { useChartContext } from "./ChartContainer";
type AxisProps = {
type?: "linear" | "log";
domain?: () => [number, number]; // TODO: is this needed for reactivity?
label?: string;
tickValues?: number[];
};
export const AxisBottom = (props: AxisProps) => {
const [chart, updateChart] = useChartContext();
// Update scale props when domain or type changes
createEffect(() => {
props.domain && updateChart("originalDomainX", props.domain());
props.domain && updateChart("scalePropsX", { domain: props.domain() });
props.type && updateChart("scalePropsX", { type: props.type });
});
const ticks = () => props.tickValues || generateTicks(chart.scaleX.domain());
return (
<g transform={`translate(0,${chart.innerHeight - 0.5})`}>
<line x1="0" x2={chart.innerWidth} y1="0" y2="0" stroke="currentColor" />
<For each={ticks()}>
{(tick) => (
<g transform={`translate(${chart.scaleX(tick)}, 0)`}>
<line y2="6" stroke="currentColor" />
<text y="9" dy="0.71em" text-anchor="middle">
{chart.formatX(tick)}
</text>
</g>
)}
</For>
<text x={chart.innerWidth} y="9" dy="2em" text-anchor="end">
{props.label}
</text>
</g>
);
};
export const AxisLeft = (props: AxisProps) => {
const [chart, updateChart] = useChartContext();
// Update scale props when domain or type changes
createEffect(() => {
props.domain && updateChart("originalDomainY", props.domain());
props.domain && updateChart("scalePropsY", { domain: props.domain() });
props.type && updateChart("scalePropsY", { type: props.type });
});
const ticks = () => props.tickValues || generateTicks(chart.scaleY.domain());
return (
<g transform="translate(-0.5,0)">
<line
x1={0}
x2={0}
y1={chart.scaleY.range()[0]}
y2={chart.scaleY.range()[1]}
stroke="currentColor"
/>
<For each={ticks()}>
{(tick) => (
<g transform={`translate(0, ${chart.scaleY(tick)})`}>
<line x2="-6" stroke="currentColor" />
<text x="-9" dy="0.32em" text-anchor="end">
{chart.formatY(tick)}
</text>
</g>
)}
</For>
<text y="0" text-anchor="end" transform="translate(-45, 0) rotate(-90)">
{props.label}
</text>
</g>
);
};
/**
* Calculate a "nice" step size by rounding up to the nearest power of 10
* Snap the min and max to the nearest multiple of step
*/
export function getNiceAxisLimits(
data: number[],
extraMargin = 0,
roundTo?: number, // Optional rounding step, e.g. 600 for 10 minutes
): [number, number] {
const finiteData = data.filter(Number.isFinite);
if (!finiteData.length) {
// Fallback limits if no data yet
return [0, 1];
}
const max = Math.max(...finiteData);
const min = Math.min(...finiteData);
const range = max - min;
if (range === 0)
// Avoid NaNs on axis for constant values
return [min - 1, max + 1];
const step = roundTo ?? 10 ** Math.floor(Math.log10(range));
const niceMin = Math.floor(min / step) * step - extraMargin * step;
const niceMax = Math.ceil(max / step) * step + extraMargin * step;
return [niceMin, niceMax];
}
/** Generate evenly space tick values for a linear scale */
const generateTicks = (domain = [0, 1], tickCount = 5) => {
const step = (domain[1] - domain[0]) / (tickCount - 1);
return [...Array(10).keys()].map((i) => domain[0] + i * step);
};