@motionscript/charts 0.1.1-alpha.0 → 0.1.1-alpha.2

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.
Files changed (207) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +109 -28
  3. package/dist/bar-chart/bar-chart.d.ts +265 -39
  4. package/dist/bar-chart/bar-chart.d.ts.map +1 -1
  5. package/dist/bar-chart/bar-chart.js +219 -171
  6. package/dist/bar-chart/bar-chart.js.map +1 -1
  7. package/dist/bar-chart/category-axis.d.ts +12 -7
  8. package/dist/bar-chart/category-axis.d.ts.map +1 -1
  9. package/dist/bar-chart/category-axis.js +56 -42
  10. package/dist/bar-chart/category-axis.js.map +1 -1
  11. package/dist/bar-chart/chart-bars.d.ts +76 -52
  12. package/dist/bar-chart/chart-bars.d.ts.map +1 -1
  13. package/dist/bar-chart/chart-bars.js +105 -89
  14. package/dist/bar-chart/chart-bars.js.map +1 -1
  15. package/dist/bar-chart/index.d.ts +4 -4
  16. package/dist/bar-chart/index.d.ts.map +1 -1
  17. package/dist/bar-chart/index.js +2 -2
  18. package/dist/bar-chart/index.js.map +1 -1
  19. package/dist/bar-chart/shared.d.ts +41 -65
  20. package/dist/bar-chart/shared.d.ts.map +1 -1
  21. package/dist/bar-chart/shared.js +73 -117
  22. package/dist/bar-chart/shared.js.map +1 -1
  23. package/dist/browser/chunks/chunk-7XCRX6GE.js +2 -0
  24. package/dist/browser/chunks/chunk-7XCRX6GE.js.map +7 -0
  25. package/dist/browser/index.js +1 -1
  26. package/dist/browser/index.js.map +4 -4
  27. package/dist/browser/kit.js +1 -1
  28. package/dist/browser/manifest.json +1 -1
  29. package/dist/engine.d.ts +5 -2
  30. package/dist/engine.d.ts.map +1 -1
  31. package/dist/engine.js +5 -2
  32. package/dist/engine.js.map +1 -1
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +2 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/kit/cartesian.d.ts +51 -15
  38. package/dist/kit/cartesian.d.ts.map +1 -1
  39. package/dist/kit/cartesian.js +81 -29
  40. package/dist/kit/cartesian.js.map +1 -1
  41. package/dist/kit/chart-canvas.d.ts +32 -24
  42. package/dist/kit/chart-canvas.d.ts.map +1 -1
  43. package/dist/kit/chart-canvas.js +52 -46
  44. package/dist/kit/chart-canvas.js.map +1 -1
  45. package/dist/kit/chart-legend.d.ts +8 -4
  46. package/dist/kit/chart-legend.d.ts.map +1 -1
  47. package/dist/kit/chart-legend.js +30 -20
  48. package/dist/kit/chart-legend.js.map +1 -1
  49. package/dist/{line-chart → kit}/horizontal-axis.d.ts +31 -6
  50. package/dist/kit/horizontal-axis.d.ts.map +1 -0
  51. package/dist/{line-chart → kit}/horizontal-axis.js +23 -24
  52. package/dist/kit/horizontal-axis.js.map +1 -0
  53. package/dist/kit/motion.d.ts +5 -0
  54. package/dist/kit/motion.d.ts.map +1 -1
  55. package/dist/kit/motion.js +8 -0
  56. package/dist/kit/motion.js.map +1 -1
  57. package/dist/{pie-chart → kit}/pie-legend.d.ts +3 -3
  58. package/dist/kit/pie-legend.d.ts.map +1 -0
  59. package/dist/{pie-chart → kit}/pie-legend.js +11 -10
  60. package/dist/kit/pie-legend.js.map +1 -0
  61. package/dist/kit/polar.d.ts +31 -0
  62. package/dist/kit/polar.d.ts.map +1 -0
  63. package/dist/kit/polar.js +33 -0
  64. package/dist/kit/polar.js.map +1 -0
  65. package/dist/kit/shared.d.ts +11 -50
  66. package/dist/kit/shared.d.ts.map +1 -1
  67. package/dist/kit/shared.js +58 -141
  68. package/dist/kit/shared.js.map +1 -1
  69. package/dist/kit/style.d.ts +517 -0
  70. package/dist/kit/style.d.ts.map +1 -0
  71. package/dist/kit/style.js +292 -0
  72. package/dist/kit/style.js.map +1 -0
  73. package/dist/kit/vertical-axis.d.ts +22 -2
  74. package/dist/kit/vertical-axis.d.ts.map +1 -1
  75. package/dist/kit/vertical-axis.js +56 -43
  76. package/dist/kit/vertical-axis.js.map +1 -1
  77. package/dist/line-chart/chart-line.d.ts +95 -3
  78. package/dist/line-chart/chart-line.d.ts.map +1 -1
  79. package/dist/line-chart/chart-line.js +165 -53
  80. package/dist/line-chart/chart-line.js.map +1 -1
  81. package/dist/line-chart/chart-region.d.ts +1 -1
  82. package/dist/line-chart/chart-region.d.ts.map +1 -1
  83. package/dist/line-chart/chart-region.js +7 -8
  84. package/dist/line-chart/chart-region.js.map +1 -1
  85. package/dist/line-chart/index.d.ts +5 -6
  86. package/dist/line-chart/index.d.ts.map +1 -1
  87. package/dist/line-chart/index.js +3 -4
  88. package/dist/line-chart/index.js.map +1 -1
  89. package/dist/line-chart/line-chart.d.ts +281 -18
  90. package/dist/line-chart/line-chart.d.ts.map +1 -1
  91. package/dist/line-chart/line-chart.js +284 -134
  92. package/dist/line-chart/line-chart.js.map +1 -1
  93. package/dist/line-chart/shared.d.ts +51 -34
  94. package/dist/line-chart/shared.d.ts.map +1 -1
  95. package/dist/line-chart/shared.js +175 -83
  96. package/dist/line-chart/shared.js.map +1 -1
  97. package/dist/line-chart/x-scale.d.ts +2 -0
  98. package/dist/line-chart/x-scale.d.ts.map +1 -1
  99. package/dist/line-chart/x-scale.js +15 -0
  100. package/dist/line-chart/x-scale.js.map +1 -1
  101. package/dist/nodes.d.ts +3 -1
  102. package/dist/nodes.d.ts.map +1 -1
  103. package/dist/nodes.js +9 -1
  104. package/dist/nodes.js.map +1 -1
  105. package/dist/pie-chart/index.d.ts +2 -3
  106. package/dist/pie-chart/index.d.ts.map +1 -1
  107. package/dist/pie-chart/index.js +1 -2
  108. package/dist/pie-chart/index.js.map +1 -1
  109. package/dist/pie-chart/pie-chart.d.ts +114 -18
  110. package/dist/pie-chart/pie-chart.d.ts.map +1 -1
  111. package/dist/pie-chart/pie-chart.js +38 -24
  112. package/dist/pie-chart/pie-chart.js.map +1 -1
  113. package/dist/pie-chart/pie-ring.d.ts +16 -5
  114. package/dist/pie-chart/pie-ring.d.ts.map +1 -1
  115. package/dist/pie-chart/pie-ring.js +89 -55
  116. package/dist/pie-chart/pie-ring.js.map +1 -1
  117. package/dist/pie-chart/shared.d.ts +61 -62
  118. package/dist/pie-chart/shared.d.ts.map +1 -1
  119. package/dist/pie-chart/shared.js +44 -123
  120. package/dist/pie-chart/shared.js.map +1 -1
  121. package/dist/radar-chart/index.d.ts +5 -0
  122. package/dist/radar-chart/index.d.ts.map +1 -0
  123. package/dist/radar-chart/index.js +5 -0
  124. package/dist/radar-chart/index.js.map +1 -0
  125. package/dist/radar-chart/radar-chart.d.ts +212 -0
  126. package/dist/radar-chart/radar-chart.d.ts.map +1 -0
  127. package/dist/radar-chart/radar-chart.js +224 -0
  128. package/dist/radar-chart/radar-chart.js.map +1 -0
  129. package/dist/radar-chart/radar-grid.d.ts +49 -0
  130. package/dist/radar-chart/radar-grid.d.ts.map +1 -0
  131. package/dist/radar-chart/radar-grid.js +63 -0
  132. package/dist/radar-chart/radar-grid.js.map +1 -0
  133. package/dist/radar-chart/radar-series.d.ts +59 -0
  134. package/dist/radar-chart/radar-series.d.ts.map +1 -0
  135. package/dist/radar-chart/radar-series.js +71 -0
  136. package/dist/radar-chart/radar-series.js.map +1 -0
  137. package/dist/radar-chart/shared.d.ts +78 -0
  138. package/dist/radar-chart/shared.d.ts.map +1 -0
  139. package/dist/radar-chart/shared.js +129 -0
  140. package/dist/radar-chart/shared.js.map +1 -0
  141. package/dist/radial-chart/index.d.ts +6 -0
  142. package/dist/radial-chart/index.d.ts.map +1 -0
  143. package/dist/radial-chart/index.js +4 -0
  144. package/dist/radial-chart/index.js.map +1 -0
  145. package/dist/radial-chart/radial-chart.d.ts +210 -0
  146. package/dist/radial-chart/radial-chart.d.ts.map +1 -0
  147. package/dist/radial-chart/radial-chart.js +175 -0
  148. package/dist/radial-chart/radial-chart.js.map +1 -0
  149. package/dist/radial-chart/radial-rings.d.ts +121 -0
  150. package/dist/radial-chart/radial-rings.d.ts.map +1 -0
  151. package/dist/radial-chart/radial-rings.js +273 -0
  152. package/dist/radial-chart/radial-rings.js.map +1 -0
  153. package/dist/radial-chart/shared.d.ts +77 -0
  154. package/dist/radial-chart/shared.d.ts.map +1 -0
  155. package/dist/radial-chart/shared.js +78 -0
  156. package/dist/radial-chart/shared.js.map +1 -0
  157. package/package.json +4 -4
  158. package/registry.json +21 -6
  159. package/src/bar-chart/bar-chart.ts +263 -179
  160. package/src/bar-chart/category-axis.ts +46 -40
  161. package/src/bar-chart/chart-bars.ts +136 -117
  162. package/src/bar-chart/index.ts +7 -10
  163. package/src/bar-chart/shared.ts +88 -233
  164. package/src/engine.ts +5 -2
  165. package/src/index.ts +2 -0
  166. package/src/kit/cartesian.ts +121 -40
  167. package/src/kit/chart-canvas.ts +49 -54
  168. package/src/kit/chart-legend.ts +28 -20
  169. package/src/kit/horizontal-axis.ts +114 -0
  170. package/src/kit/motion.ts +7 -0
  171. package/src/{pie-chart → kit}/pie-legend.ts +12 -11
  172. package/src/kit/polar.ts +54 -0
  173. package/src/kit/shared.ts +55 -277
  174. package/src/kit/style.ts +353 -0
  175. package/src/kit/vertical-axis.ts +67 -48
  176. package/src/line-chart/chart-line.ts +176 -54
  177. package/src/line-chart/chart-region.ts +6 -10
  178. package/src/line-chart/index.ts +14 -10
  179. package/src/line-chart/line-chart.ts +353 -151
  180. package/src/line-chart/shared.ts +193 -174
  181. package/src/line-chart/x-scale.ts +13 -0
  182. package/src/nodes.ts +9 -1
  183. package/src/pie-chart/index.ts +2 -23
  184. package/src/pie-chart/pie-chart.ts +54 -35
  185. package/src/pie-chart/pie-ring.ts +92 -71
  186. package/src/pie-chart/shared.ts +80 -270
  187. package/src/radar-chart/index.ts +4 -0
  188. package/src/radar-chart/radar-chart.ts +290 -0
  189. package/src/radar-chart/radar-grid.ts +83 -0
  190. package/src/radar-chart/radar-series.ts +88 -0
  191. package/src/radar-chart/shared.ts +153 -0
  192. package/src/radial-chart/index.ts +5 -0
  193. package/src/radial-chart/radial-chart.ts +212 -0
  194. package/src/radial-chart/radial-rings.ts +328 -0
  195. package/src/radial-chart/shared.ts +126 -0
  196. package/dist/browser/chunks/chunk-4SMRCU24.js +0 -2
  197. package/dist/browser/chunks/chunk-4SMRCU24.js.map +0 -7
  198. package/dist/kit/chart-border.d.ts +0 -18
  199. package/dist/kit/chart-border.d.ts.map +0 -1
  200. package/dist/kit/chart-border.js +0 -23
  201. package/dist/kit/chart-border.js.map +0 -1
  202. package/dist/line-chart/horizontal-axis.d.ts.map +0 -1
  203. package/dist/line-chart/horizontal-axis.js.map +0 -1
  204. package/dist/pie-chart/pie-legend.d.ts.map +0 -1
  205. package/dist/pie-chart/pie-legend.js.map +0 -1
  206. package/src/kit/chart-border.ts +0 -36
  207. package/src/line-chart/horizontal-axis.ts +0 -122
package/src/kit/shared.ts CHANGED
@@ -11,29 +11,22 @@
11
11
  */
12
12
 
13
13
  import {
14
- fillOps,
15
14
  lerpNumber,
16
- shadowOps,
17
- strokeOps,
18
- insetsOps,
19
- textOps,
20
15
  type DataRecord,
21
16
  type Fill,
22
- type FillResolved,
23
17
  type Insets,
24
- type InsetsResolved,
25
18
  type Shadow,
26
- type ShadowResolved,
27
19
  type Stroke,
28
- type StrokeResolved,
29
20
  type TextStyle,
30
21
  } from "@motionscript/sdk"
22
+ import { niceStep } from "@motionscript/sdk/component"
31
23
 
32
24
  /** One series of a chart: a column of the table, captioned and painted. */
33
25
  export interface ChartSeries {
34
26
  /** Key into each data row for this series' value. */
35
27
  field: string
36
- label: string
28
+ /** The legend caption; empty or absent shows `field`. */
29
+ label?: string
37
30
  /** Overrides the theme palette's entry for this series' index, for its mark and legend swatch. */
38
31
  fill?: Fill
39
32
  /** Defaults to `true`. */
@@ -166,7 +159,7 @@ export function settleAxisDomain(domain: AxisDomainConfig, drawn: (domain: AxisD
166
159
  }
167
160
 
168
161
  /** The colour tokens the default themes are built from: a dark plot panel with warm-grey axis furniture. */
169
- export const CHART_PALETTE = {
162
+ const CHART_PALETTE = {
170
163
  plotFill: "#15130e",
171
164
  borderColor: "#4a4036",
172
165
  gridColor: "#2c2820",
@@ -176,15 +169,6 @@ export const CHART_PALETTE = {
176
169
  regionColor: "#d99a3a",
177
170
  } as const
178
171
 
179
- /** The out-of-the-box series palette, cycled by series index. */
180
- export const DEFAULT_SERIES_COLORS = [
181
- "#d9603b",
182
- "#8194ad",
183
- "#e0a93b",
184
- "#5e9d6b",
185
- "#9ec85a",
186
- ] as const
187
-
188
172
  /** Opacity an un-spotlighted series fades to, mark and legend entry alike. */
189
173
  export const SPOTLIGHT_DIM = 0.2
190
174
 
@@ -269,13 +253,6 @@ export interface LegendConfig {
269
253
  markerStyle: LegendMarkerConfig
270
254
  }
271
255
 
272
- /** The plot panel a cartesian chart draws its marks over. */
273
- export interface PlotAreaConfig {
274
- stroke: Stroke
275
- fill: Fill
276
- shadow: Shadow
277
- }
278
-
279
256
  const AXIS_LABEL_STYLE: TextStyle = {
280
257
  fontSize: 24,
281
258
  fontWeight: 700,
@@ -306,13 +283,6 @@ export const DEFAULT_Y_AXIS: AxisConfig = {
306
283
  titlePadding: 24,
307
284
  }
308
285
 
309
- /** Unpainted but framed: a chart reads against the stage rather than punching a panel into it. */
310
- export const DEFAULT_PLOT_AREA: PlotAreaConfig = {
311
- fill: [],
312
- stroke: { weight: 6, fill: CHART_PALETTE.borderColor },
313
- shadow: [],
314
- }
315
-
316
286
  export const DEFAULT_LEGEND: LegendConfig = {
317
287
  padding: 0,
318
288
  textStyle: {
@@ -331,249 +301,6 @@ export const DEFAULT_LEGEND: LegendConfig = {
331
301
  },
332
302
  }
333
303
 
334
- /** {@link DEFAULT_SERIES_COLORS} as 4px strokes. */
335
- export const DEFAULT_SERIES_STROKES: Stroke[] = DEFAULT_SERIES_COLORS.map(
336
- (fill) => ({ weight: 4, fill })
337
- )
338
-
339
- /** {@link DEFAULT_SERIES_COLORS} as solid fills. */
340
- export const DEFAULT_SERIES_FILLS: Fill[] = DEFAULT_SERIES_COLORS.map(
341
- (fill) => fill
342
- )
343
-
344
- export function resolveAxisConfig(
345
- axis: Partial<AxisConfig> | undefined,
346
- previous: AxisConfig
347
- ): AxisConfig {
348
- return {
349
- labelStyle: textOps.resolveStyle(axis?.labelStyle, previous.labelStyle),
350
- titleStyle: textOps.resolveStyle(axis?.titleStyle, previous.titleStyle),
351
- lineStyle: strokeOps.resolve(axis?.lineStyle ?? previous.lineStyle),
352
- tickPadding: insetsOps.resolve(
353
- axis?.tickPadding ?? previous.tickPadding,
354
- previous.tickPadding as InsetsResolved
355
- ),
356
- titlePadding: insetsOps.resolve(
357
- axis?.titlePadding ?? previous.titlePadding,
358
- previous.titlePadding as InsetsResolved
359
- ),
360
- }
361
- }
362
-
363
- export function lerpAxisConfig(
364
- from: AxisConfig,
365
- to: AxisConfig,
366
- t: number
367
- ): AxisConfig {
368
- return {
369
- labelStyle: textOps.lerpStyle(from.labelStyle, to.labelStyle, t),
370
- titleStyle: textOps.lerpStyle(from.titleStyle, to.titleStyle, t),
371
- lineStyle: strokeOps.lerp(
372
- from.lineStyle as StrokeResolved[],
373
- to.lineStyle as StrokeResolved[],
374
- t
375
- ),
376
- tickPadding: insetsOps.lerp(
377
- from.tickPadding as InsetsResolved,
378
- to.tickPadding as InsetsResolved,
379
- t
380
- ),
381
- titlePadding: insetsOps.lerp(
382
- from.titlePadding as InsetsResolved,
383
- to.titlePadding as InsetsResolved,
384
- t
385
- ),
386
- }
387
- }
388
-
389
- export function resolvePlotArea(
390
- plotArea: Partial<PlotAreaConfig> | undefined,
391
- previous: PlotAreaConfig
392
- ): PlotAreaConfig {
393
- return {
394
- fill: fillOps.resolve(plotArea?.fill ?? previous.fill),
395
- stroke: strokeOps.resolve(plotArea?.stroke ?? previous.stroke),
396
- shadow: shadowOps.resolve(plotArea?.shadow ?? previous.shadow),
397
- }
398
- }
399
-
400
- export function lerpPlotArea(
401
- from: PlotAreaConfig,
402
- to: PlotAreaConfig,
403
- t: number
404
- ): PlotAreaConfig {
405
- return {
406
- fill: fillOps.lerp(
407
- from.fill as FillResolved[],
408
- to.fill as FillResolved[],
409
- t
410
- ),
411
- stroke: strokeOps.lerp(
412
- from.stroke as StrokeResolved[],
413
- to.stroke as StrokeResolved[],
414
- t
415
- ),
416
- shadow: shadowOps.lerp(
417
- from.shadow as ShadowResolved[],
418
- to.shadow as ShadowResolved[],
419
- t
420
- ),
421
- }
422
- }
423
-
424
- function resolveLegendMarkerConfig(
425
- marker: Partial<LegendMarkerConfig> | undefined,
426
- previous: LegendMarkerConfig
427
- ): LegendMarkerConfig {
428
- return {
429
- width: marker?.width ?? previous.width,
430
- height: marker?.height ?? previous.height,
431
- borderRadius: marker?.borderRadius ?? previous.borderRadius,
432
- stroke: strokeOps.resolve(marker?.stroke ?? previous.stroke),
433
- shadow: shadowOps.resolve(marker?.shadow ?? previous.shadow),
434
- }
435
- }
436
-
437
- function lerpLegendMarkerConfig(
438
- from: LegendMarkerConfig,
439
- to: LegendMarkerConfig,
440
- t: number
441
- ): LegendMarkerConfig {
442
- return {
443
- width: lerpNumber(from.width, to.width, t),
444
- height: lerpNumber(from.height, to.height, t),
445
- borderRadius: lerpNumber(from.borderRadius, to.borderRadius, t),
446
- stroke: strokeOps.lerp(
447
- from.stroke as StrokeResolved[],
448
- to.stroke as StrokeResolved[],
449
- t
450
- ),
451
- shadow: shadowOps.lerp(
452
- from.shadow as ShadowResolved[],
453
- to.shadow as ShadowResolved[],
454
- t
455
- ),
456
- }
457
- }
458
-
459
- /** A legend theme as the author may state it: every branch optional. */
460
- export type LegendTheme = Partial<Omit<LegendConfig, "markerStyle">> & {
461
- markerStyle?: Partial<LegendMarkerConfig>
462
- }
463
-
464
- export function resolveLegendConfig(
465
- legend: LegendTheme | undefined,
466
- previous: LegendConfig
467
- ): LegendConfig {
468
- return {
469
- padding: insetsOps.resolve(
470
- legend?.padding ?? previous.padding,
471
- previous.padding as InsetsResolved
472
- ),
473
- textStyle: textOps.resolveStyle(legend?.textStyle, previous.textStyle),
474
- itemGap: legend?.itemGap ?? previous.itemGap,
475
- markerStyle: resolveLegendMarkerConfig(
476
- legend?.markerStyle,
477
- previous.markerStyle
478
- ),
479
- }
480
- }
481
-
482
- export function lerpLegendConfig(
483
- from: LegendConfig,
484
- to: LegendConfig,
485
- t: number
486
- ): LegendConfig {
487
- return {
488
- padding: insetsOps.lerp(
489
- from.padding as InsetsResolved,
490
- to.padding as InsetsResolved,
491
- t
492
- ),
493
- textStyle: textOps.lerpStyle(from.textStyle, to.textStyle, t),
494
- itemGap: lerpNumber(from.itemGap, to.itemGap, t),
495
- markerStyle: lerpLegendMarkerConfig(from.markerStyle, to.markerStyle, t),
496
- }
497
- }
498
-
499
- /** A palette override replaces the previous palette outright. */
500
- export function resolveSeriesStrokes(
501
- styles: Stroke[] | undefined,
502
- previous: Stroke[]
503
- ): Stroke[] {
504
- return (styles ?? previous).map((s) => strokeOps.resolve(s))
505
- }
506
-
507
- /** Pairwise by index; a palette that grows/shrinks holds the longer side's tail. */
508
- export function lerpSeriesStrokes(
509
- from: Stroke[],
510
- to: Stroke[],
511
- t: number
512
- ): Stroke[] {
513
- const n = Math.max(from.length, to.length)
514
- const out: Stroke[] = []
515
- for (let i = 0; i < n; i++) {
516
- const a = (from[i] ?? to[i]) as StrokeResolved[]
517
- const b = (to[i] ?? from[i]) as StrokeResolved[]
518
- out.push(strokeOps.lerp(a, b, t))
519
- }
520
- return out
521
- }
522
-
523
- /** The fill-palette twin of {@link resolveSeriesStrokes}, for filled marks. */
524
- export function resolveSeriesFills(
525
- fills: Fill[] | undefined,
526
- previous: Fill[]
527
- ): Fill[] {
528
- return (fills ?? previous).map((f) => fillOps.resolve(f))
529
- }
530
-
531
- /** The fill-palette twin of {@link lerpSeriesStrokes}. */
532
- export function lerpSeriesFills(from: Fill[], to: Fill[], t: number): Fill[] {
533
- const n = Math.max(from.length, to.length)
534
- const out: Fill[] = []
535
- for (let i = 0; i < n; i++) {
536
- const a = (from[i] ?? to[i]) as FillResolved[]
537
- const b = (to[i] ?? from[i]) as FillResolved[]
538
- out.push(fillOps.lerp(a, b, t))
539
- }
540
- return out
541
- }
542
-
543
- /**
544
- * The stroke for series `index`: the palette's entry at `index % palette.length`,
545
- * with the series' own `fill`, if set, replacing just the topmost layer's colour.
546
- */
547
- export function resolveSeriesStroke(
548
- palette: Stroke[],
549
- series: ChartSeries,
550
- index: number
551
- ): Stroke {
552
- const entry: Stroke =
553
- palette.length > 0
554
- ? palette[index % palette.length]
555
- : { weight: 4, fill: "white" }
556
- if (series.fill == null) return entry
557
- const layers = strokeOps.resolve(entry)
558
- if (layers.length === 0) return { weight: 4, fill: series.fill }
559
- const next = [...layers]
560
- next[next.length - 1] = {
561
- ...next[next.length - 1],
562
- fill: fillOps.resolve(series.fill),
563
- }
564
- return next
565
- }
566
-
567
- /** The colour {@link resolveSeriesStroke} draws series `index` in, for a legend swatch. */
568
- export function resolveSeriesFill(
569
- palette: Stroke[],
570
- series: ChartSeries,
571
- index: number
572
- ): Fill {
573
- const layers = strokeOps.resolve(resolveSeriesStroke(palette, series, index))
574
- return layers[layers.length - 1]?.fill ?? "white"
575
- }
576
-
577
304
  /**
578
305
  * Format a marker value: integers plain, otherwise one decimal, or as many as
579
306
  * the tick `step` needs to tell neighbours apart (a unit axis ticked every 0.05
@@ -619,3 +346,54 @@ export function markerValues([min, max]: Range, markerCount: number): number[] {
619
346
  const n = Math.max(2, Math.round(markerCount))
620
347
  return Array.from({ length: n + 1 }, (_, i) => min + ((max - min) / n) * i)
621
348
  }
349
+
350
+ /** `[lo, hi]` nudged out to bounds a whole number of tidy steps apart, about `divisions` of them. */
351
+ export function niceRange([lo, hi]: Range, divisions: number): Range {
352
+ const segments = Math.max(1, Math.round(divisions))
353
+ if (hi === lo) return constantScale(lo, segments)
354
+ const step = niceStep((hi - lo) / segments)
355
+ return [Math.floor(lo / step) * step, Math.ceil(hi / step) * step]
356
+ }
357
+
358
+ /**
359
+ * The scale for constant data. A zero-width scale would put every tick on one
360
+ * value and the line on the floor, so it widens by a step sized to the
361
+ * magnitude (`5` gets `[3, 7]`), or `[0, 1]` for zero.
362
+ */
363
+ function constantScale(value: number, segments: number): Range {
364
+ if (value === 0) return [0, 1]
365
+ const step = niceStep(Math.abs(value) / segments)
366
+ return [value - step, value + step]
367
+ }
368
+
369
+ /**
370
+ * Ticks for a range worked out from the data: every multiple of the tidiest
371
+ * step (1, 2 or 5 times a power of ten) that divides it into about `divisions`
372
+ * parts, so zero is a tick whenever the range spans it. A range no such step
373
+ * divides (an author's) is split evenly instead.
374
+ */
375
+ export function niceTicks([lo, hi]: Range, divisions: number): number[] {
376
+ const span = hi - lo
377
+ const target = Math.max(1, Math.round(divisions))
378
+ if (!(span > 0)) return markerValues([lo, hi], target)
379
+ const whole = (v: number) => Math.abs(v - Math.round(v)) < 1e-6
380
+ const base = niceStep(span / target)
381
+ let best: number | undefined
382
+ for (const step of ladder(base)) {
383
+ const parts = span / step
384
+ if (!whole(parts) || !whole(lo / step) || parts < 2 || parts > 8) continue
385
+ if (best === undefined || Math.abs(parts - target) < Math.abs(span / best - target)) best = step
386
+ }
387
+ if (best === undefined) return markerValues([lo, hi], target)
388
+ const first = Math.round(lo / best)
389
+ return Array.from({ length: Math.round(span / best) + 1 }, (_, k) => Number(((first + k) * best).toPrecision(12)))
390
+ }
391
+
392
+ /** `step` and its neighbours on the 1-2-5 ladder, two either side. */
393
+ function ladder(step: number): number[] {
394
+ const mag = 10 ** Math.floor(Math.log10(step))
395
+ const rungs: number[] = []
396
+ for (const m of [mag / 10, mag, mag * 10]) for (const k of [1, 2, 5]) rungs.push(k * m)
397
+ const at = rungs.findIndex((r) => Math.abs(r - step) < step * 1e-9)
398
+ return rungs.slice(Math.max(0, at - 2), at + 3)
399
+ }