@fundar/data-chart-telling 0.0.13 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { hasHoverMarker, findHoverMarker } from '../tooltip/utils';
|
|
8
8
|
import HoverMarker from '../../plots/markers/HoverMarker.svelte';
|
|
9
9
|
import Tooltip from '../tooltip/Tooltip.svelte';
|
|
10
|
-
import type { AxisValue, Series, ScalesConfig, MarginConfig } from '../../types/plots/common';
|
|
10
|
+
import type { AxisValue, AxisScale, Series, ScalesConfig, MarginConfig } from '../../types/plots/common';
|
|
11
11
|
import type { Marker } from '../../types/plots/markers';
|
|
12
12
|
import type { HoverStrategy, HoverDisplayPoint, HoverPoint, TooltipRuntime } from '../../types/layout/tooltip';
|
|
13
13
|
|
|
@@ -98,6 +98,51 @@
|
|
|
98
98
|
};
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
// ── Axis ticks / domain ──────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
const xValues = $derived(data.map(getX));
|
|
104
|
+
const yValues = $derived(data.map(getY));
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Caps the requested tick count to the axis's own number of distinct
|
|
108
|
+
* values, but never above what the pixel width would naturally request —
|
|
109
|
+
* so a narrow domain (e.g. 5 whole years) never gets subdivided into
|
|
110
|
+
* fractional ticks (2020, 2020.5, 2021, …) just to fill the space. Skipped
|
|
111
|
+
* whenever the caller already controls tick density directly.
|
|
112
|
+
*/
|
|
113
|
+
function autoTickCount(scale: AxisScale | undefined, values: AxisValue[], span: number, spacing: number, min: number): number | undefined {
|
|
114
|
+
if (scale?.ticks != null || scale?.tickSpacing != null || scale?.interval != null) return undefined;
|
|
115
|
+
const uniqueCount = new Set(values.map(String)).size;
|
|
116
|
+
const natural = Math.max(min, Math.round(span / spacing));
|
|
117
|
+
return uniqueCount >= 2 && uniqueCount < natural ? uniqueCount : undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const xTickCount = $derived(
|
|
121
|
+
autoTickCount(scales?.x, xValues, Math.max(0, width - computedMargins.left - computedMargins.right), 80, 3),
|
|
122
|
+
);
|
|
123
|
+
const yTickCount = $derived(
|
|
124
|
+
autoTickCount(scales?.y, yValues, Math.max(0, height - computedMargins.top - computedMargins.bottom), 50, 2),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* svelteplot's own `sort` scale option only toggles a fixed ascending sort
|
|
129
|
+
* (lexicographic for strings) — it can't take a custom comparator. When the
|
|
130
|
+
* caller supplies one via `scales.x.sort`/`scales.y.sort` for a string-valued
|
|
131
|
+
* axis (e.g. dates formatted as text), compute the ordered domain ourselves
|
|
132
|
+
* and hand it to the scale as an explicit `domain`, which svelteplot does
|
|
133
|
+
* respect as-is for ordinal scales.
|
|
134
|
+
*/
|
|
135
|
+
function sortedScale<T>(scale: T, scaleConfig: AxisScale | undefined, values: AxisValue[]): T {
|
|
136
|
+
const sort = scaleConfig?.sort;
|
|
137
|
+
if (!sort || scaleConfig?.domain || values.length === 0) return scale;
|
|
138
|
+
if (!values.every((v) => typeof v === 'string')) return scale;
|
|
139
|
+
const domain = [...new Set(values)].sort(sort);
|
|
140
|
+
return { ...(scale as object), domain } as T;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const resolvedX = $derived(sortedScale(x, scales?.x, xValues));
|
|
144
|
+
const resolvedY = $derived(sortedScale(y, scales?.y, yValues));
|
|
145
|
+
|
|
101
146
|
// ── Hover / tooltip ───────────────────────────────────────────────────────
|
|
102
147
|
const hoverActive = $derived(tooltip != null || hasHoverMarker(markers));
|
|
103
148
|
|
|
@@ -165,9 +210,21 @@
|
|
|
165
210
|
onpointermove={hoverActive ? ctrl.onMove : undefined}
|
|
166
211
|
onpointerleave={hoverActive ? ctrl.onLeave : undefined}
|
|
167
212
|
>
|
|
168
|
-
<Plot {width} {height} margin={computedMargins} {
|
|
169
|
-
<AxisX
|
|
170
|
-
|
|
213
|
+
<Plot {width} {height} margin={computedMargins} x={resolvedX} y={resolvedY}>
|
|
214
|
+
<AxisX
|
|
215
|
+
tickCount={xTickCount}
|
|
216
|
+
tickSize={scales?.x?.tickSize ?? cfg.axis.xTickSize}
|
|
217
|
+
tickPadding={scales?.x?.tickPadding ?? cfg.axis.xTickPadding}
|
|
218
|
+
tickFontSize={cfg.axis.fontSize}
|
|
219
|
+
titleFontSize={scales?.x?.titleFontSize ?? cfg.axis.titleFontSize}
|
|
220
|
+
/>
|
|
221
|
+
<AxisY
|
|
222
|
+
tickCount={yTickCount}
|
|
223
|
+
tickSize={scales?.y?.tickSize ?? cfg.axis.yTickSize}
|
|
224
|
+
tickPadding={scales?.y?.tickPadding ?? cfg.axis.yTickPadding}
|
|
225
|
+
tickFontSize={cfg.axis.fontSize}
|
|
226
|
+
titleFontSize={scales?.y?.titleFontSize ?? cfg.axis.titleFontSize}
|
|
227
|
+
/>
|
|
171
228
|
{@render children({
|
|
172
229
|
matchedPoints: ctrl.points,
|
|
173
230
|
ruleX: ctrl.ruleX,
|
|
@@ -42,10 +42,7 @@ export type AxisScale = {
|
|
|
42
42
|
titleFontSize?: number;
|
|
43
43
|
base?: number;
|
|
44
44
|
constant?: number;
|
|
45
|
-
sort?: (
|
|
46
|
-
channel: string;
|
|
47
|
-
order: 'ascending' | 'descending';
|
|
48
|
-
};
|
|
45
|
+
sort?: (a: AxisValue, b: AxisValue) => number;
|
|
49
46
|
grid?: boolean;
|
|
50
47
|
tickRotate?: number;
|
|
51
48
|
tickFormat?: false | Intl.NumberFormatOptions | Intl.DateTimeFormatOptions | ((d: unknown, index: number, ticks: unknown[]) => string | string[]);
|