@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} {x} {y}>
169
- <AxisX tickSize={scales?.x?.tickSize ?? cfg.axis.xTickSize} tickPadding={scales?.x?.tickPadding ?? cfg.axis.xTickPadding} tickFontSize={cfg.axis.fontSize} titleFontSize={scales?.x?.titleFontSize ?? cfg.axis.titleFontSize} />
170
- <AxisY tickSize={scales?.y?.tickSize ?? cfg.axis.yTickSize} tickPadding={scales?.y?.tickPadding ?? cfg.axis.yTickPadding} tickFontSize={cfg.axis.fontSize} titleFontSize={scales?.y?.titleFontSize ?? cfg.axis.titleFontSize} />
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?: ((a: unknown, b: unknown) => number) | {
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[]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fundar/data-chart-telling",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"