@fundar/data-chart-telling 0.0.43 → 0.0.44

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.
@@ -32,20 +32,53 @@
32
32
  const xValues = $derived(data.map(getX));
33
33
  const yValues = $derived(data.map(getY));
34
34
 
35
- /** Caps tick count to the axis's distinct value count, never above what fits the pixel span. Skipped once the caller sets tick density directly. */
35
+ function naturalTickCount(span: number, spacing: number, min: number): number {
36
+ return Math.max(min, Math.round(span / spacing));
37
+ }
38
+
39
+ /** `[min, max]` of a pinned `scale.domain` (if numeric), else the extent of `values`. */
40
+ function axisExtent(scale: AxisBasedScalesConfig<TData>['x'], values: AxisValue[]): [number, number] | undefined {
41
+ const domainNumbers = scale?.domain?.filter((d): d is number => typeof d === 'number');
42
+ const source = domainNumbers && domainNumbers.length >= 2 ? domainNumbers : values.filter((v): v is number => typeof v === 'number');
43
+ if (source.length < 2) return undefined;
44
+ const lo = Math.min(...source);
45
+ const hi = Math.max(...source);
46
+ return hi > lo ? [lo, hi] : undefined;
47
+ }
48
+
49
+ /** Caps tick count to the axis's distinct value count, never above what fits the pixel span. Skipped once the caller sets tick density directly, or pins the domain. */
36
50
  function autoTickCount(scale: AxisBasedScalesConfig<TData>['x'], values: AxisValue[], span: number, spacing: number, min: number): number | undefined {
37
- if (scale?.ticks != null || scale?.tickSpacing != null || scale?.interval != null) return undefined;
51
+ if (scale?.ticks != null || scale?.tickSpacing != null || scale?.interval != null || scale?.domain != null) return undefined;
38
52
  const uniqueCount = new Set(values.map(String)).size;
39
- const natural = Math.max(min, Math.round(span / spacing));
53
+ const natural = naturalTickCount(span, spacing, min);
40
54
  return uniqueCount >= 2 && uniqueCount < natural ? uniqueCount : undefined;
41
55
  }
42
56
 
43
- const xTickCount = $derived(
44
- autoTickCount(scales?.x, xValues, Math.max(0, width - numericMargins.left - numericMargins.right), 80, 3),
45
- );
46
- const yTickCount = $derived(
47
- autoTickCount(scales?.y, yValues, Math.max(0, height - numericMargins.top - numericMargins.bottom), 50, 2),
48
- );
57
+ /** Forces a whole-unit tick interval on an integer domain that spans fewer integers than the pixel span would naturally want ticks for. */
58
+ function integerTickInterval(
59
+ scale: AxisBasedScalesConfig<TData>['x'],
60
+ values: AxisValue[],
61
+ span: number,
62
+ spacing: number,
63
+ min: number
64
+ ): number | undefined {
65
+ if (scale?.ticks != null || scale?.tickSpacing != null || scale?.interval != null) return undefined;
66
+ if (scale?.type != null && scale.type !== 'linear' && scale.type !== 'auto') return undefined;
67
+ const extent = axisExtent(scale, values);
68
+ if (!extent) return undefined;
69
+ const [lo, hi] = extent;
70
+ if (!Number.isInteger(lo) || !Number.isInteger(hi)) return undefined;
71
+ return hi - lo < naturalTickCount(span, spacing, min) ? 1 : undefined;
72
+ }
73
+
74
+ const xSpan = $derived(Math.max(0, width - numericMargins.left - numericMargins.right));
75
+ const ySpan = $derived(Math.max(0, height - numericMargins.top - numericMargins.bottom));
76
+
77
+ const xTickCount = $derived(autoTickCount(scales?.x, xValues, xSpan, 80, 3));
78
+ const yTickCount = $derived(autoTickCount(scales?.y, yValues, ySpan, 50, 2));
79
+
80
+ const xTickInterval = $derived(integerTickInterval(scales?.x, xValues, xSpan, 80, 3));
81
+ const yTickInterval = $derived(integerTickInterval(scales?.y, yValues, ySpan, 50, 2));
49
82
 
50
83
  /** Vertical Y-axis title placement, when `scales.y.labelOrientation === 'vertical'`. */
51
84
  const verticalYLabel = $derived.by(() => {
@@ -61,6 +94,7 @@
61
94
 
62
95
  <AxisX
63
96
  tickCount={xTickCount}
97
+ interval={xTickInterval}
64
98
  tickSize={scales?.x?.tickSize ?? cfg.axis.xTickSize}
65
99
  tickPadding={scales?.x?.tickPadding ?? cfg.axis.xTickPadding}
66
100
  tickFontSize={cfg.axis.fontSize}
@@ -68,6 +102,7 @@
68
102
  />
69
103
  <AxisY
70
104
  tickCount={yTickCount}
105
+ interval={yTickInterval}
71
106
  tickSize={scales?.y?.tickSize ?? cfg.axis.yTickSize}
72
107
  tickPadding={scales?.y?.tickPadding ?? cfg.axis.yTickPadding}
73
108
  tickFontSize={cfg.axis.fontSize}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fundar/data-chart-telling",
3
- "version": "0.0.43",
3
+ "version": "0.0.44",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"