@motionscript/charts 0.1.0 → 0.1.1-alpha.1

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 (215) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +98 -28
  3. package/dist/bar-chart/bar-chart.d.ts +242 -119
  4. package/dist/bar-chart/bar-chart.d.ts.map +1 -1
  5. package/dist/bar-chart/bar-chart.js +231 -260
  6. package/dist/bar-chart/bar-chart.js.map +1 -1
  7. package/dist/bar-chart/category-axis.d.ts +16 -38
  8. package/dist/bar-chart/category-axis.d.ts.map +1 -1
  9. package/dist/bar-chart/category-axis.js +59 -68
  10. package/dist/bar-chart/category-axis.js.map +1 -1
  11. package/dist/bar-chart/chart-bars.d.ts +60 -91
  12. package/dist/bar-chart/chart-bars.d.ts.map +1 -1
  13. package/dist/bar-chart/chart-bars.js +88 -125
  14. package/dist/bar-chart/chart-bars.js.map +1 -1
  15. package/dist/bar-chart/index.d.ts +5 -13
  16. package/dist/bar-chart/index.d.ts.map +1 -1
  17. package/dist/bar-chart/index.js +2 -9
  18. package/dist/bar-chart/index.js.map +1 -1
  19. package/dist/bar-chart/shared.d.ts +40 -132
  20. package/dist/bar-chart/shared.d.ts.map +1 -1
  21. package/dist/bar-chart/shared.js +70 -158
  22. package/dist/bar-chart/shared.js.map +1 -1
  23. package/dist/browser/chunks/chunk-R3Q5EE6E.js +2 -0
  24. package/dist/browser/chunks/chunk-R3Q5EE6E.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 +8 -17
  30. package/dist/engine.d.ts.map +1 -1
  31. package/dist/engine.js +8 -17
  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 +49 -35
  38. package/dist/kit/cartesian.d.ts.map +1 -1
  39. package/dist/kit/cartesian.js +80 -42
  40. package/dist/kit/cartesian.js.map +1 -1
  41. package/dist/kit/chart-canvas.d.ts +37 -40
  42. package/dist/kit/chart-canvas.d.ts.map +1 -1
  43. package/dist/kit/chart-canvas.js +53 -47
  44. package/dist/kit/chart-canvas.js.map +1 -1
  45. package/dist/kit/chart-legend.d.ts +16 -59
  46. package/dist/kit/chart-legend.d.ts.map +1 -1
  47. package/dist/kit/chart-legend.js +38 -76
  48. package/dist/kit/chart-legend.js.map +1 -1
  49. package/dist/kit/entries.d.ts +2 -0
  50. package/dist/kit/entries.d.ts.map +1 -1
  51. package/dist/kit/entries.js +2 -0
  52. package/dist/kit/entries.js.map +1 -1
  53. package/dist/kit/horizontal-axis.d.ts +77 -0
  54. package/dist/kit/horizontal-axis.d.ts.map +1 -0
  55. package/dist/kit/horizontal-axis.js +99 -0
  56. package/dist/kit/horizontal-axis.js.map +1 -0
  57. package/dist/kit/motion.d.ts +16 -3
  58. package/dist/kit/motion.d.ts.map +1 -1
  59. package/dist/kit/motion.js +15 -1
  60. package/dist/kit/motion.js.map +1 -1
  61. package/dist/{pie-chart → kit}/pie-legend.d.ts +14 -29
  62. package/dist/kit/pie-legend.d.ts.map +1 -0
  63. package/dist/kit/pie-legend.js +93 -0
  64. package/dist/kit/pie-legend.js.map +1 -0
  65. package/dist/kit/polar.d.ts +31 -0
  66. package/dist/kit/polar.d.ts.map +1 -0
  67. package/dist/kit/polar.js +33 -0
  68. package/dist/kit/polar.js.map +1 -0
  69. package/dist/kit/shared.d.ts +60 -214
  70. package/dist/kit/shared.d.ts.map +1 -1
  71. package/dist/kit/shared.js +91 -291
  72. package/dist/kit/shared.js.map +1 -1
  73. package/dist/kit/style.d.ts +254 -0
  74. package/dist/kit/style.d.ts.map +1 -0
  75. package/dist/kit/style.js +197 -0
  76. package/dist/kit/style.js.map +1 -0
  77. package/dist/kit/vertical-axis.d.ts +36 -45
  78. package/dist/kit/vertical-axis.d.ts.map +1 -1
  79. package/dist/kit/vertical-axis.js +69 -81
  80. package/dist/kit/vertical-axis.js.map +1 -1
  81. package/dist/line-chart/chart-line.d.ts +107 -40
  82. package/dist/line-chart/chart-line.d.ts.map +1 -1
  83. package/dist/line-chart/chart-line.js +172 -89
  84. package/dist/line-chart/chart-line.js.map +1 -1
  85. package/dist/line-chart/chart-region.d.ts +14 -27
  86. package/dist/line-chart/chart-region.d.ts.map +1 -1
  87. package/dist/line-chart/chart-region.js +13 -28
  88. package/dist/line-chart/chart-region.js.map +1 -1
  89. package/dist/line-chart/index.d.ts +5 -18
  90. package/dist/line-chart/index.d.ts.map +1 -1
  91. package/dist/line-chart/index.js +3 -16
  92. package/dist/line-chart/index.js.map +1 -1
  93. package/dist/line-chart/line-chart.d.ts +272 -119
  94. package/dist/line-chart/line-chart.d.ts.map +1 -1
  95. package/dist/line-chart/line-chart.js +309 -257
  96. package/dist/line-chart/line-chart.js.map +1 -1
  97. package/dist/line-chart/shared.d.ts +73 -130
  98. package/dist/line-chart/shared.d.ts.map +1 -1
  99. package/dist/line-chart/shared.js +195 -156
  100. package/dist/line-chart/shared.js.map +1 -1
  101. package/dist/line-chart/x-scale.d.ts +16 -55
  102. package/dist/line-chart/x-scale.d.ts.map +1 -1
  103. package/dist/line-chart/x-scale.js +44 -111
  104. package/dist/line-chart/x-scale.js.map +1 -1
  105. package/dist/nodes.d.ts +3 -13
  106. package/dist/nodes.d.ts.map +1 -1
  107. package/dist/nodes.js +9 -13
  108. package/dist/nodes.js.map +1 -1
  109. package/dist/pie-chart/index.d.ts +3 -15
  110. package/dist/pie-chart/index.d.ts.map +1 -1
  111. package/dist/pie-chart/index.js +1 -11
  112. package/dist/pie-chart/index.js.map +1 -1
  113. package/dist/pie-chart/pie-chart.d.ts +147 -85
  114. package/dist/pie-chart/pie-chart.d.ts.map +1 -1
  115. package/dist/pie-chart/pie-chart.js +65 -86
  116. package/dist/pie-chart/pie-chart.js.map +1 -1
  117. package/dist/pie-chart/pie-ring.d.ts +65 -196
  118. package/dist/pie-chart/pie-ring.d.ts.map +1 -1
  119. package/dist/pie-chart/pie-ring.js +154 -292
  120. package/dist/pie-chart/pie-ring.js.map +1 -1
  121. package/dist/pie-chart/shared.d.ts +78 -172
  122. package/dist/pie-chart/shared.d.ts.map +1 -1
  123. package/dist/pie-chart/shared.js +54 -177
  124. package/dist/pie-chart/shared.js.map +1 -1
  125. package/dist/radar-chart/index.d.ts +5 -0
  126. package/dist/radar-chart/index.d.ts.map +1 -0
  127. package/dist/radar-chart/index.js +5 -0
  128. package/dist/radar-chart/index.js.map +1 -0
  129. package/dist/radar-chart/radar-chart.d.ts +218 -0
  130. package/dist/radar-chart/radar-chart.d.ts.map +1 -0
  131. package/dist/radar-chart/radar-chart.js +224 -0
  132. package/dist/radar-chart/radar-chart.js.map +1 -0
  133. package/dist/radar-chart/radar-grid.d.ts +43 -0
  134. package/dist/radar-chart/radar-grid.d.ts.map +1 -0
  135. package/dist/radar-chart/radar-grid.js +61 -0
  136. package/dist/radar-chart/radar-grid.js.map +1 -0
  137. package/dist/radar-chart/radar-series.d.ts +59 -0
  138. package/dist/radar-chart/radar-series.d.ts.map +1 -0
  139. package/dist/radar-chart/radar-series.js +71 -0
  140. package/dist/radar-chart/radar-series.js.map +1 -0
  141. package/dist/radar-chart/shared.d.ts +73 -0
  142. package/dist/radar-chart/shared.d.ts.map +1 -0
  143. package/dist/radar-chart/shared.js +123 -0
  144. package/dist/radar-chart/shared.js.map +1 -0
  145. package/dist/radial-chart/index.d.ts +6 -0
  146. package/dist/radial-chart/index.d.ts.map +1 -0
  147. package/dist/radial-chart/index.js +4 -0
  148. package/dist/radial-chart/index.js.map +1 -0
  149. package/dist/radial-chart/radial-chart.d.ts +216 -0
  150. package/dist/radial-chart/radial-chart.d.ts.map +1 -0
  151. package/dist/radial-chart/radial-chart.js +174 -0
  152. package/dist/radial-chart/radial-chart.js.map +1 -0
  153. package/dist/radial-chart/radial-rings.d.ts +121 -0
  154. package/dist/radial-chart/radial-rings.d.ts.map +1 -0
  155. package/dist/radial-chart/radial-rings.js +273 -0
  156. package/dist/radial-chart/radial-rings.js.map +1 -0
  157. package/dist/radial-chart/shared.d.ts +73 -0
  158. package/dist/radial-chart/shared.d.ts.map +1 -0
  159. package/dist/radial-chart/shared.js +79 -0
  160. package/dist/radial-chart/shared.js.map +1 -0
  161. package/package.json +4 -4
  162. package/registry.json +21 -6
  163. package/src/bar-chart/bar-chart.ts +274 -315
  164. package/src/bar-chart/category-axis.ts +53 -88
  165. package/src/bar-chart/chart-bars.ts +108 -205
  166. package/src/bar-chart/index.ts +8 -19
  167. package/src/bar-chart/shared.ts +81 -309
  168. package/src/engine.ts +8 -17
  169. package/src/index.ts +2 -0
  170. package/src/kit/cartesian.ts +118 -55
  171. package/src/kit/chart-canvas.ts +50 -80
  172. package/src/kit/chart-legend.ts +36 -97
  173. package/src/kit/entries.ts +4 -1
  174. package/src/kit/horizontal-axis.ts +114 -0
  175. package/src/kit/motion.ts +18 -2
  176. package/src/{pie-chart → kit}/pie-legend.ts +25 -65
  177. package/src/kit/polar.ts +54 -0
  178. package/src/kit/shared.ts +104 -487
  179. package/src/kit/style.ts +257 -0
  180. package/src/kit/vertical-axis.ts +81 -115
  181. package/src/line-chart/chart-line.ts +185 -91
  182. package/src/line-chart/chart-region.ts +14 -31
  183. package/src/line-chart/index.ts +14 -22
  184. package/src/line-chart/line-chart.ts +377 -315
  185. package/src/line-chart/shared.ts +229 -304
  186. package/src/line-chart/x-scale.ts +44 -121
  187. package/src/nodes.ts +9 -13
  188. package/src/pie-chart/index.ts +3 -35
  189. package/src/pie-chart/pie-chart.ts +83 -134
  190. package/src/pie-chart/pie-ring.ts +162 -368
  191. package/src/pie-chart/shared.ts +92 -379
  192. package/src/radar-chart/index.ts +4 -0
  193. package/src/radar-chart/radar-chart.ts +287 -0
  194. package/src/radar-chart/radar-grid.ts +78 -0
  195. package/src/radar-chart/radar-series.ts +88 -0
  196. package/src/radar-chart/shared.ts +146 -0
  197. package/src/radial-chart/index.ts +5 -0
  198. package/src/radial-chart/radial-chart.ts +210 -0
  199. package/src/radial-chart/radial-rings.ts +328 -0
  200. package/src/radial-chart/shared.ts +114 -0
  201. package/dist/browser/chunks/chunk-TJNHLQGV.js +0 -2
  202. package/dist/browser/chunks/chunk-TJNHLQGV.js.map +0 -7
  203. package/dist/kit/chart-border.d.ts +0 -27
  204. package/dist/kit/chart-border.d.ts.map +0 -1
  205. package/dist/kit/chart-border.js +0 -28
  206. package/dist/kit/chart-border.js.map +0 -1
  207. package/dist/line-chart/horizontal-axis.d.ts +0 -79
  208. package/dist/line-chart/horizontal-axis.d.ts.map +0 -1
  209. package/dist/line-chart/horizontal-axis.js +0 -129
  210. package/dist/line-chart/horizontal-axis.js.map +0 -1
  211. package/dist/pie-chart/pie-legend.d.ts.map +0 -1
  212. package/dist/pie-chart/pie-legend.js +0 -117
  213. package/dist/pie-chart/pie-legend.js.map +0 -1
  214. package/src/kit/chart-border.ts +0 -50
  215. package/src/line-chart/horizontal-axis.ts +0 -151
@@ -1,29 +1,11 @@
1
1
  /**
2
- * What a line chart's x column *means* — the one question that has to be
3
- * answered before a row can be given a position along the bottom of the plot.
2
+ * What a line chart's x column means: a number (plotted as is), an instant
3
+ * (`Jan`, `2026-W12`; plotted by time, so a gap in the data reads as a gap), or
4
+ * anything else (the row's position, evenly spaced).
4
5
  *
5
- * A y column is always a magnitude. An x column is one of three things, and
6
- * which one is a property of the data rather than something the author states:
7
- *
8
- * - **numeric** — `0, 5, 10` — plot the number.
9
- * - **time** — `Jan`, `2026-03`, `2026-W12`, `2026-03-14` — plot the instant,
10
- * so samples a month apart sit a month apart and a gap in the data reads as a
11
- * gap.
12
- * - **ordinal** — anything else — plot the row's position, evenly spaced.
13
- *
14
- * Before this, every x column went through `num()`, which reads a non-numeric
15
- * cell as `0`. A `Month` column of `Jan…Dec` therefore stacked all twelve
16
- * points on one x and drew a vertical line — no error, no empty chart, just a
17
- * wrong picture. That is the failure this module exists to remove: every column
18
- * now plots as *something*, and the something is the reading a person would
19
- * give it.
20
- *
21
- * The captions come back off the rows rather than out of a formatter. A tick on
22
- * a time or ordinal scale is chosen *from the data* (see {@link XScale.ticks}),
23
- * so the label it wants is the cell the author wrote — `Jan`, not `1970-01-01`,
24
- * and `2026-W12`, not the Monday it resolves to. Only a tick that is not a
25
- * sample — an author-set range, or a `zoomTo` mid-flight — needs formatting,
26
- * and that is the one case {@link formatInstant} covers.
6
+ * A tick on a time or ordinal scale is a sample, so its caption is the cell the
7
+ * author wrote; only a tick that is not (an author-set range, a `zoomTo`
8
+ * mid-flight) is formatted, by {@link formatInstant}.
27
9
  */
28
10
 
29
11
  import type { DataRecord } from "@motionscript/sdk"
@@ -40,26 +22,18 @@ import { formatNumber } from "@motionscript/sdk/component"
40
22
  /** How an x column's cells become positions along the axis. */
41
23
  export type XScaleKind = "numeric" | "time" | "ordinal"
42
24
 
43
- /**
44
- * An x column resolved into positions, captions and ticks.
45
- *
46
- * The three kinds differ in all three answers, so they are three closures built
47
- * once per (data, column) rather than a kind tag every caller switches on.
48
- */
25
+ /** An x column resolved into positions, captions and ticks: three closures built once per (data, column), not a tag callers switch on. */
49
26
  export interface XScale {
50
27
  kind: XScaleKind
51
28
  /** Position of row `index` along the axis, in this scale's own units. */
52
29
  at(index: number): number
53
- /** `[min, max]` across the rows — the scale when no range is set. */
30
+ /** `[min, max]` across the rows: the scale when no range is set. */
54
31
  extent: Range
55
- /** The caption for a tick at `value`. */
56
32
  label(value: number): string
57
33
  /** `divisions + 1` tick values spanning `range`, ends included. */
58
34
  ticks(range: Range, divisions: number): number[]
59
35
  }
60
36
 
61
- // ---- Time parsing --------------------------------------------------------
62
-
63
37
  const DAY = 86_400_000
64
38
 
65
39
  const MONTHS = [
@@ -77,29 +51,15 @@ const MONTHS = [
77
51
  "december",
78
52
  ]
79
53
 
80
- /**
81
- * The year a cell that names no year is dated into.
82
- *
83
- * Any year would place `Jan…Dec` in the right order at the right spacing; a
84
- * leap year is the one that also spaces February correctly against a column
85
- * that *does* carry years, so the two read alike.
86
- */
54
+ /** The year a cell naming none is dated into; a leap year, so February spaces the same as in a column that carries years. */
87
55
  const NOMINAL_YEAR = 2024
88
56
 
89
- /**
90
- * Month index for any unambiguous abbreviation of a month name — `jan`, `sept`,
91
- * `january` — or -1.
92
- *
93
- * Three letters minimum, and then a prefix match, which is what lets `sept`
94
- * through without letting `janitor` through: a longer word has to still *be*
95
- * the month.
96
- */
57
+ /** Month index for `jan`, `sept` or `january`, else -1: three letters minimum, then a prefix match, so `sept` passes and `janitor` does not. */
97
58
  function monthIndex(word: string): number {
98
59
  if (word.length < 3) return -1
99
60
  return MONTHS.findIndex((month) => month.startsWith(word))
100
61
  }
101
62
 
102
- /** The Monday of ISO week `week` in `year`, as an epoch timestamp. */
103
63
  function isoWeekStart(year: number, week: number): number {
104
64
  // ISO week 1 is the week containing 4 January, and weeks start on Monday.
105
65
  const jan4 = Date.UTC(year, 0, 4)
@@ -108,14 +68,10 @@ function isoWeekStart(year: number, week: number): number {
108
68
  }
109
69
 
110
70
  /**
111
- * One cell read as an instant, or `null` when it isn't one.
112
- *
113
- * Deliberately **not** `Date.parse`. That accepts `"5"`, `"Q3"` and a good deal
114
- * else besides, differs between engines on anything non-ISO, and reads a bare
115
- * `2026-03-14` as UTC but `2026/03/14` as local — three ways for a column to be
116
- * silently misread as time. Every form below is matched explicitly, and an
117
- * ambiguous all-numeric date (`03/04/2026` — March or April, depending on where
118
- * the author lives) is deliberately not one of them.
71
+ * One cell read as an instant, or `null`. Not `Date.parse`, which accepts `"5"`
72
+ * and `"Q3"`, differs between engines off ISO, and reads `2026-03-14` as UTC but
73
+ * `2026/03/14` as local. Every form is matched explicitly, and an ambiguous
74
+ * all-numeric date (`03/04/2026`) is not one of them.
119
75
  */
120
76
  export function parseInstant(raw: unknown): number | null {
121
77
  if (typeof raw !== "string") return null
@@ -169,14 +125,7 @@ export function parseInstant(raw: unknown): number | null {
169
125
  return null
170
126
  }
171
127
 
172
- /**
173
- * An instant captioned at the coarsest granularity the axis's own span can tell
174
- * apart — a decade of samples reads as years, a year of them as months.
175
- *
176
- * Only reached for a tick that is *not* one of the rows (an author-set range, a
177
- * `zoomTo` part-way through). A tick that is a sample is captioned with the
178
- * cell the author wrote; see the note at the top of this file.
179
- */
128
+ /** An instant captioned at the coarsest granularity the span tells apart: a decade reads as years, a year as months. */
180
129
  export function formatInstant(value: number, span: number): string {
181
130
  const date = new Date(value)
182
131
  const year = date.getUTCFullYear()
@@ -187,16 +136,7 @@ export function formatInstant(value: number, span: number): string {
187
136
  return `${label} ${date.getUTCDate()}`
188
137
  }
189
138
 
190
- // ---- Column reading ------------------------------------------------------
191
-
192
- /**
193
- * Whether every non-empty cell of `field` holds a number.
194
- *
195
- * The same rule `numericFields` applies, asked of one column: a single text
196
- * cell in an otherwise numeric column takes the whole column off the numeric
197
- * scale, because plotting that row at 0 is the failure this module is here to
198
- * stop.
199
- */
139
+ /** `numericFields`' rule for one column: a single text cell takes it off the numeric scale rather than plot that row at 0. */
200
140
  function isNumericColumn(data: DataRecord[], field: string): boolean {
201
141
  let sawValue = false
202
142
  for (const row of data) {
@@ -209,13 +149,9 @@ function isNumericColumn(data: DataRecord[], field: string): boolean {
209
149
  }
210
150
 
211
151
  /**
212
- * Every non-empty cell of `field` parsed as an instant, or `null` if any one of
213
- * them isn't one — in which case the column isn't a time column at all.
214
- *
215
- * A blank cell keeps its slot, so the returned list stays index-aligned with
216
- * the rows, and takes the nearest known instant: a hole in an x column is a row
217
- * whose date the file didn't say, and stacking it on the epoch would drag the
218
- * whole scale back fifty years to make room for it.
152
+ * Every non-empty cell as an instant, or `null` if any is not one. A blank cell
153
+ * keeps its slot and takes the nearest known instant: stacking it on the epoch
154
+ * would drag the whole scale back fifty years.
219
155
  */
220
156
  function instantColumn(data: DataRecord[], field: string): number[] | null {
221
157
  const parsed: (number | null)[] = []
@@ -249,24 +185,16 @@ function extentOf(values: number[]): Range {
249
185
  return lo <= hi ? [lo, hi] : [0, 1]
250
186
  }
251
187
 
188
+ /** Up to this many samples in view, every one is a tick. */
189
+ export const EVERY_SAMPLE = 12
190
+
252
191
  /**
253
- * Ticks spanning `range`, each one **a sample** — a row the author can point at
254
- * in their file — placed as near as it can be to where an evenly spaced tick
255
- * would have gone.
256
- *
257
- * Two things have to hold at once, and picking either alone gets it wrong.
258
- * Ticks must be *samples*, because a caption between two of them has nothing to
259
- * say (two-thirds of the way through March is not a month, and on a category
260
- * column it is not anything at all). And they must be *spread*, because the
261
- * labels sit side by side in a row: taking every nth sample instead puts five
262
- * dates in the two weeks a burst of rows happens to cover and prints them on
263
- * top of each other, which is what a column with a gap in it does.
264
- *
265
- * Walking the targets rather than the samples is what satisfies both. A run of
266
- * rows too close together to caption collapses to one tick — consecutive
267
- * targets pick the same sample and the duplicate is dropped — so a chart asked
268
- * for five ticks across a gappy column draws four honest ones instead of five
269
- * overlapping.
192
+ * Ticks spanning `range`, each a sample, nearest to where an evenly spaced tick
193
+ * would go. They must be samples (a caption between two has nothing to say) and
194
+ * spread (every nth sample would print five dates on top of each other across a
195
+ * burst of rows), so this walks the evenly spaced targets, not the samples.
196
+ * Targets that pick the same sample collapse to one tick, so a gappy column
197
+ * draws fewer honest ticks rather than overlapping ones.
270
198
  */
271
199
  function sampledTicks(
272
200
  positions: number[],
@@ -276,6 +204,8 @@ function sampledTicks(
276
204
  const inside = positions.filter((v) => v >= min && v <= max)
277
205
  const unique = [...new Set(inside)].sort((a, b) => a - b)
278
206
  if (unique.length === 0) return markerValues([min, max], divisions)
207
+ // Few and even enough to caption every one: a month left out reads as missing data.
208
+ if (unique.length <= EVERY_SAMPLE && evenlySpaced(unique)) return unique
279
209
 
280
210
  const nearest = (target: number) =>
281
211
  unique.reduce((best, v) =>
@@ -290,10 +220,15 @@ function sampledTicks(
290
220
  return out
291
221
  }
292
222
 
293
- /**
294
- * The caption a tick carries on a sample-based scale: the cell of the row it
295
- * came from, or `fallback` when the tick isn't a sample after all.
296
- */
223
+ /** Whether no two neighbouring samples sit much closer than the average gap: months do, a burst of days between weeks of nothing does not. */
224
+ function evenlySpaced(sorted: readonly number[]): boolean {
225
+ if (sorted.length < 3) return true
226
+ const mean = (sorted[sorted.length - 1] - sorted[0]) / (sorted.length - 1)
227
+ for (let i = 1; i < sorted.length; i++) if (sorted[i] - sorted[i - 1] < mean * 0.75) return false
228
+ return true
229
+ }
230
+
231
+ /** The cell of the row a tick came from, or `fallback` when it is not a sample. */
297
232
  function labelBySample(
298
233
  positions: number[],
299
234
  captions: string[],
@@ -304,16 +239,10 @@ function labelBySample(
304
239
  return i >= 0 ? captions[i] : fallback(value)
305
240
  }
306
241
 
307
- /** Scales already resolved, by data array, then by field and pattern. */
242
+ /** Resolved scales by data array, then by field and pattern. */
308
243
  const scales = new WeakMap<DataRecord[], Map<string, XScale>>()
309
244
 
310
- /**
311
- * How `field` positions the rows: the number it holds, the instant it names, or
312
- * failing both, the row's own place in the file.
313
- *
314
- * Kept per `data` array: a chart's bound props ask a dozen times a frame, and
315
- * parsing every row's instant each time was most of a frame.
316
- */
245
+ /** Kept per `data` array: bound props ask a dozen times a frame, and parsing every row's instant each time was most of a frame. */
317
246
  export function resolveXScale(
318
247
  data: DataRecord[],
319
248
  field: string,
@@ -330,11 +259,7 @@ export function resolveXScale(
330
259
  function xScaleOf(
331
260
  data: DataRecord[],
332
261
  field: string,
333
- /**
334
- * The number-format pattern for a *numeric* column. Ignored by the other two
335
- * kinds, and correctly so: a tick on those is captioned with the cell the
336
- * author wrote, and `Jan` has no decimal places.
337
- */
262
+ /** Number-format pattern for a numeric column only; ticks on the other kinds are the author's cells. */
338
263
  pattern?: string
339
264
  ): XScale {
340
265
  if (field !== "" && isNumericColumn(data, field)) {
@@ -345,8 +270,7 @@ function xScaleOf(
345
270
  extent: extentOf(positions),
346
271
  label: (value) =>
347
272
  pattern === undefined ? formatMarker(value) : formatNumber(value, pattern),
348
- // Round numbers, evenly spaced — a continuous axis is read by its scale
349
- // rather than by which samples happen to be on it.
273
+ // A continuous axis is read by its scale, not by which samples are on it.
350
274
  ticks: markerValues,
351
275
  }
352
276
  }
@@ -373,8 +297,7 @@ function xScaleOf(
373
297
  return {
374
298
  kind: "ordinal",
375
299
  at: (i) => i,
376
- // `[0, n-1]` rather than the tidy `[0, n]`: the first and last rows sit on
377
- // the plot's edges, which is where a reader looks for them.
300
+ // `[0, n-1]`, not `[0, n]`: the first and last rows sit on the plot's edges.
378
301
  extent: positions.length > 0 ? [0, positions.length - 1] : [0, 1],
379
302
  label: (value) => captions[Math.round(value)] ?? "",
380
303
  ticks: (range, divisions) => sampledTicks(positions, range, divisions),
package/src/nodes.ts CHANGED
@@ -1,18 +1,14 @@
1
1
  import { LineChart } from "./line-chart";
2
2
  import { BarChart } from "./bar-chart";
3
3
  import { PieChart } from "./pie-chart";
4
+ import { RadarChart } from "./radar-chart";
5
+ import { RadialChart } from "./radial-chart";
4
6
 
5
- /**
6
- * Every node type this package publishes.
7
- *
8
- * A host registers these by handing the array to an engine —
9
- * `new Engine(platform, { nodes: NODES })` — which reads each component's key.
10
- * Defining a component only *declares* that key: nothing is registered by
11
- * importing a module, so a registry holds exactly what its owner asked for.
12
- *
13
- * Listing the component **values** is also what survives bundling: this package
14
- * declares `sideEffects: false`, and a document names a node type by string, so
15
- * an import kept only for its side effect is exactly what gets shaken out.
16
- */
17
7
  // Typed by name, so the declaration names each component rather than spelling out its type through core.
18
- export const NODES: (typeof LineChart | typeof BarChart | typeof PieChart)[] = [LineChart, BarChart, PieChart];
8
+ export const NODES: (typeof LineChart | typeof BarChart | typeof PieChart | typeof RadarChart | typeof RadialChart)[] = [
9
+ LineChart,
10
+ BarChart,
11
+ PieChart,
12
+ RadarChart,
13
+ RadialChart,
14
+ ];
@@ -1,37 +1,5 @@
1
- /**
2
- * The pie chart: the one in the family that draws a mark per **row** rather
3
- * than per column.
4
- *
5
- * So it shares the legend and the theme machinery with its siblings and none of
6
- * the cartesian geometry. Set the theme's `ring.holeRadius` to 0 for a solid
7
- * pie; it defaults to a donut, because the hole is the only place a chart has
8
- * to put its conclusion.
9
- */
10
1
  export { PieChart, isSliceSelected } from "./pie-chart"
11
- export type { PieChartProps, PieChartValues } from "./pie-chart"
2
+ export type { PieChartValues } from "./pie-chart"
12
3
  export { PieRing } from "./pie-ring"
13
- export type { PieRingProps } from "./pie-ring"
14
- export { PieLegend } from "./pie-legend"
15
- export type { PieLegendProps } from "./pie-legend"
16
- export {
17
- DEFAULT_PIE_CHART_THEME,
18
- lerpPieChartTheme,
19
- pieArcs,
20
- relaxLabels,
21
- resolvePieChartTheme,
22
- resolvePieSlices,
23
- ringPoint,
24
- sliceTotal,
25
- sliceValue,
26
- } from "./shared"
27
- export type {
28
- CenterConfig,
29
- LabelConfig,
30
- PieArc,
31
- PieChartConfig,
32
- PieChartTheme,
33
- PieLabelMode,
34
- PieLabelPlacement,
35
- PieSlice,
36
- RingConfig,
37
- } from "./shared"
4
+ export { PIE_STYLE_GROUPS, pieArcs, relaxLabels, resolvePieSlices, sliceTotal, sliceValue } from "./shared"
5
+ export type { LabelConfig, PieArc, PieLabelMode, PieLabelPlacement, PieStyle, RingConfig } from "./shared"