@motionscript/charts 0.1.0-alpha.3 → 0.1.0-alpha.5

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 (93) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/LICENSE +201 -201
  3. package/NOTICE +48 -0
  4. package/README.md +85 -0
  5. package/dist/bar-chart/bar-chart.d.ts +28 -13
  6. package/dist/bar-chart/bar-chart.d.ts.map +1 -1
  7. package/dist/bar-chart/bar-chart.js +60 -56
  8. package/dist/bar-chart/bar-chart.js.map +1 -1
  9. package/dist/bar-chart/category-axis.js.map +1 -1
  10. package/dist/bar-chart/chart-bars.js.map +1 -1
  11. package/dist/bar-chart/index.js.map +1 -1
  12. package/dist/bar-chart/shared.d.ts +3 -3
  13. package/dist/bar-chart/shared.js +3 -3
  14. package/dist/bar-chart/shared.js.map +1 -1
  15. package/dist/browser/chunks/chunk-5ZPBV6BU.js +2 -0
  16. package/dist/browser/chunks/chunk-5ZPBV6BU.js.map +7 -0
  17. package/dist/browser/index.js +1 -1
  18. package/dist/browser/index.js.map +3 -3
  19. package/dist/browser/kit.js +1 -1
  20. package/dist/browser/manifest.json +3 -2
  21. package/dist/engine.js.map +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/kit/chart-border.js.map +1 -1
  24. package/dist/kit/chart-canvas.js.map +1 -1
  25. package/dist/kit/chart-legend.d.ts +4 -25
  26. package/dist/kit/chart-legend.d.ts.map +1 -1
  27. package/dist/kit/chart-legend.js +31 -55
  28. package/dist/kit/chart-legend.js.map +1 -1
  29. package/dist/kit/shared.d.ts +29 -6
  30. package/dist/kit/shared.d.ts.map +1 -1
  31. package/dist/kit/shared.js +64 -8
  32. package/dist/kit/shared.js.map +1 -1
  33. package/dist/kit/vertical-axis.js.map +1 -1
  34. package/dist/line-chart/chart-line.d.ts +2 -2
  35. package/dist/line-chart/chart-line.js.map +1 -1
  36. package/dist/line-chart/chart-region.js.map +1 -1
  37. package/dist/line-chart/horizontal-axis.js.map +1 -1
  38. package/dist/line-chart/index.js.map +1 -1
  39. package/dist/line-chart/line-chart.d.ts +9 -1
  40. package/dist/line-chart/line-chart.d.ts.map +1 -1
  41. package/dist/line-chart/line-chart.js +32 -8
  42. package/dist/line-chart/line-chart.js.map +1 -1
  43. package/dist/line-chart/shared.d.ts +3 -3
  44. package/dist/line-chart/shared.d.ts.map +1 -1
  45. package/dist/line-chart/shared.js +19 -4
  46. package/dist/line-chart/shared.js.map +1 -1
  47. package/dist/line-chart/x-scale.d.ts +1 -1
  48. package/dist/line-chart/x-scale.js +1 -1
  49. package/dist/line-chart/x-scale.js.map +1 -1
  50. package/dist/nodes.js.map +1 -1
  51. package/dist/pie-chart/index.js.map +1 -1
  52. package/dist/pie-chart/pie-chart.d.ts +2 -2
  53. package/dist/pie-chart/pie-chart.d.ts.map +1 -1
  54. package/dist/pie-chart/pie-chart.js +9 -7
  55. package/dist/pie-chart/pie-chart.js.map +1 -1
  56. package/dist/pie-chart/pie-legend.js +4 -4
  57. package/dist/pie-chart/pie-legend.js.map +1 -1
  58. package/dist/pie-chart/pie-ring.d.ts +13 -4
  59. package/dist/pie-chart/pie-ring.d.ts.map +1 -1
  60. package/dist/pie-chart/pie-ring.js +44 -14
  61. package/dist/pie-chart/pie-ring.js.map +1 -1
  62. package/dist/pie-chart/shared.d.ts +3 -3
  63. package/dist/pie-chart/shared.js +3 -3
  64. package/dist/pie-chart/shared.js.map +1 -1
  65. package/package.json +6 -5
  66. package/registry.json +23 -23
  67. package/src/bar-chart/bar-chart.ts +820 -814
  68. package/src/bar-chart/category-axis.ts +177 -177
  69. package/src/bar-chart/chart-bars.ts +310 -310
  70. package/src/bar-chart/index.ts +31 -31
  71. package/src/bar-chart/shared.ts +354 -354
  72. package/src/engine.ts +26 -26
  73. package/src/index.ts +5 -5
  74. package/src/kit/chart-border.ts +61 -61
  75. package/src/kit/chart-canvas.ts +157 -157
  76. package/src/kit/chart-legend.ts +245 -250
  77. package/src/kit/shared.ts +733 -675
  78. package/src/kit/vertical-axis.ts +224 -224
  79. package/src/line-chart/chart-line.ts +208 -208
  80. package/src/line-chart/chart-region.ts +135 -135
  81. package/src/line-chart/horizontal-axis.ts +202 -202
  82. package/src/line-chart/index.ts +39 -39
  83. package/src/line-chart/line-chart.ts +961 -929
  84. package/src/line-chart/shared.ts +473 -459
  85. package/src/line-chart/x-scale.ts +367 -367
  86. package/src/nodes.ts +20 -20
  87. package/src/pie-chart/index.ts +37 -37
  88. package/src/pie-chart/pie-chart.ts +383 -381
  89. package/src/pie-chart/pie-legend.ts +159 -159
  90. package/src/pie-chart/pie-ring.ts +935 -903
  91. package/src/pie-chart/shared.ts +490 -490
  92. package/dist/browser/chunks/chunk-RMHQ6GYU.js +0 -2
  93. package/dist/browser/chunks/chunk-RMHQ6GYU.js.map +0 -7
@@ -1 +1 @@
1
- {"version":3,"file":"x-scale.js","sourceRoot":"","sources":["../../src/line-chart/x-scale.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a line chart's x column *means* — the one question that has to be\n * answered before a row can be given a position along the bottom of the plot.\n *\n * A y column is always a magnitude. An x column is one of three things, and\n * which one is a property of the data rather than something the author states:\n *\n * - **numeric** — `0, 5, 10` — plot the number.\n * - **time** — `Jan`, `2026-03`, `2026-W12`, `2026-03-14` — plot the instant,\n * so samples a month apart sit a month apart and a gap in the data reads as a\n * gap.\n * - **ordinal** — anything else — plot the row's position, evenly spaced.\n *\n * Before this, every x column went through `num()`, which reads a non-numeric\n * cell as `0`. A `Month` column of `Jan…Dec` therefore stacked all twelve\n * points on one x and drew a vertical line — no error, no empty chart, just a\n * wrong picture. That is the failure this module exists to remove: every column\n * now plots as *something*, and the something is the reading a person would\n * give it.\n *\n * The captions come back off the rows rather than out of a formatter. A tick on\n * a time or ordinal scale is chosen *from the data* (see {@link XScale.ticks}),\n * so the label it wants is the cell the author wrote — `Jan`, not `1970-01-01`,\n * and `2026-W12`, not the Monday it resolves to. Only a tick that is not a\n * sample — an author-set range, or a `zoomTo` mid-flight — needs formatting,\n * and that is the one case {@link formatInstant} covers.\n */\n\nimport type { DataRecord } from \"@motionscript/sdk\"\n\nimport {\n formatMarker,\n markerValues,\n num,\n text,\n type Range,\n} from \"../kit/shared\"\nimport { formatNumber } from \"@motionscript/sdk/component\"\n\n/** How an x column's cells become positions along the axis. */\nexport type XScaleKind = \"numeric\" | \"time\" | \"ordinal\"\n\n/**\n * An x column resolved into positions, captions and ticks.\n *\n * The three kinds differ in all three answers, so they are three closures built\n * once per (data, column) rather than a kind tag every caller switches on.\n */\nexport interface XScale {\n kind: XScaleKind\n /** Position of row `index` along the axis, in this scale's own units. */\n at(index: number): number\n /** `[min, max]` across the rows — the scale when no range is set. */\n extent: Range\n /** The caption for a tick at `value`. */\n label(value: number): string\n /** `divisions + 1` tick values spanning `range`, ends included. */\n ticks(range: Range, divisions: number): number[]\n}\n\n// ---- Time parsing --------------------------------------------------------\n\nconst DAY = 86_400_000\n\nconst MONTHS = [\n \"january\",\n \"february\",\n \"march\",\n \"april\",\n \"may\",\n \"june\",\n \"july\",\n \"august\",\n \"september\",\n \"october\",\n \"november\",\n \"december\",\n]\n\n/**\n * The year a cell that names no year is dated into.\n *\n * Any year would place `Jan…Dec` in the right order at the right spacing; a\n * leap year is the one that also spaces February correctly against a column\n * that *does* carry years, so the two read alike.\n */\nconst NOMINAL_YEAR = 2024\n\n/**\n * Month index for any unambiguous abbreviation of a month name — `jan`, `sept`,\n * `january` — or -1.\n *\n * Three letters minimum, and then a prefix match, which is what lets `sept`\n * through without letting `janitor` through: a longer word has to still *be*\n * the month.\n */\nfunction monthIndex(word: string): number {\n if (word.length < 3) return -1\n return MONTHS.findIndex((month) => month.startsWith(word))\n}\n\n/** The Monday of ISO week `week` in `year`, as an epoch timestamp. */\nfunction isoWeekStart(year: number, week: number): number {\n // ISO week 1 is the week containing 4 January, and weeks start on Monday.\n const jan4 = Date.UTC(year, 0, 4)\n const weekday = (new Date(jan4).getUTCDay() + 6) % 7 // Monday = 0\n return jan4 - weekday * DAY + (week - 1) * 7 * DAY\n}\n\n/**\n * One cell read as an instant, or `null` when it isn't one.\n *\n * Deliberately **not** `Date.parse`. That accepts `\"5\"`, `\"Q3\"` and a good deal\n * else besides, differs between engines on anything non-ISO, and reads a bare\n * `2026-03-14` as UTC but `2026/03/14` as local — three ways for a column to be\n * silently misread as time. Every form below is matched explicitly, and an\n * ambiguous all-numeric date (`03/04/2026` — March or April, depending on where\n * the author lives) is deliberately not one of them.\n */\nexport function parseInstant(raw: unknown): number | null {\n if (typeof raw !== \"string\") return null\n const s = raw.trim().toLowerCase()\n if (s === \"\") return null\n\n // 2026-03-14 · 2026/03/14 · 2026-03 · 2026/03\n const ymd = /^(\\d{4})[-/](\\d{1,2})(?:[-/](\\d{1,2}))?$/.exec(s)\n if (ymd) {\n const month = Number(ymd[2])\n const day = ymd[3] === undefined ? 1 : Number(ymd[3])\n if (month < 1 || month > 12 || day < 1 || day > 31) return null\n return Date.UTC(Number(ymd[1]), month - 1, day)\n }\n\n // 2026-W12 · 2026W12 · w12 · week 12\n const week = /^(?:(\\d{4})[-\\s]?)?w(?:ee)?k?[-\\s]?(\\d{1,2})$/.exec(s)\n if (week) {\n const n = Number(week[2])\n if (n < 1 || n > 53) return null\n return isoWeekStart(\n week[1] === undefined ? NOMINAL_YEAR : Number(week[1]),\n n\n )\n }\n\n // jan · january · jan 2026 · january-2026 · 2026 jan\n const named = /^(?:(\\d{4})[-\\s]+)?([a-z]+)(?:[-\\s]+(\\d{4}))?$/.exec(s)\n if (named) {\n const month = monthIndex(named[2])\n if (month >= 0) {\n const year = named[1] ?? named[3]\n const y = year === undefined ? NOMINAL_YEAR : Number(year)\n return Date.UTC(y, month, 1)\n }\n }\n\n // 14 mar 2026 · mar 14 2026 · mar 14, 2026\n const dayNamed =\n /^(?:(\\d{1,2})[-\\s]+([a-z]+)|([a-z]+)[-\\s]+(\\d{1,2}),?)[-\\s]+(\\d{4})$/.exec(\n s\n )\n if (dayNamed) {\n const month = monthIndex(dayNamed[2] ?? dayNamed[3])\n const day = Number(dayNamed[1] ?? dayNamed[4])\n if (month >= 0 && day >= 1 && day <= 31) {\n return Date.UTC(Number(dayNamed[5]), month, day)\n }\n }\n\n return null\n}\n\n/**\n * An instant captioned at the coarsest granularity the axis's own span can tell\n * apart — a decade of samples reads as years, a year of them as months.\n *\n * Only reached for a tick that is *not* one of the rows (an author-set range, a\n * `zoomTo` part-way through). A tick that is a sample is captioned with the\n * cell the author wrote; see the note at the top of this file.\n */\nexport function formatInstant(value: number, span: number): string {\n const date = new Date(value)\n const year = date.getUTCFullYear()\n const month = MONTHS[date.getUTCMonth()].slice(0, 3)\n const label = month.charAt(0).toUpperCase() + month.slice(1)\n if (span > 4 * 365 * DAY) return String(year)\n if (span > 60 * DAY) return `${label} ${year}`\n return `${label} ${date.getUTCDate()}`\n}\n\n// ---- Column reading ------------------------------------------------------\n\n/**\n * Whether every non-empty cell of `field` holds a number.\n *\n * The same rule `numericFields` applies, asked of one column: a single text\n * cell in an otherwise numeric column takes the whole column off the numeric\n * scale, because plotting that row at 0 is the failure this module is here to\n * stop.\n */\nfunction isNumericColumn(data: DataRecord[], field: string): boolean {\n let sawValue = false\n for (const row of data) {\n const v = row[field]\n if (v === undefined || v === null || v === \"\") continue\n if (typeof v !== \"number\") return false\n sawValue = true\n }\n return sawValue\n}\n\n/**\n * Every non-empty cell of `field` parsed as an instant, or `null` if any one of\n * them isn't one — in which case the column isn't a time column at all.\n *\n * A blank cell keeps its slot, so the returned list stays index-aligned with\n * the rows, and takes the nearest known instant: a hole in an x column is a row\n * whose date the file didn't say, and stacking it on the epoch would drag the\n * whole scale back fifty years to make room for it.\n */\nfunction instantColumn(data: DataRecord[], field: string): number[] | null {\n const parsed: (number | null)[] = []\n let sawValue = false\n for (const row of data) {\n const raw = row[field]\n if (raw === undefined || raw === null || raw === \"\") {\n parsed.push(null)\n continue\n }\n const t = parseInstant(raw)\n if (t === null) return null\n parsed.push(t)\n sawValue = true\n }\n if (!sawValue) return null\n\n const first = parsed.find((t): t is number => t !== null) as number\n let last = first\n return parsed.map((t) => (t === null ? last : (last = t)))\n}\n\n/** `[min, max]` of `values`, or `[0, 1]` when there is nothing to span. */\nfunction extentOf(values: number[]): Range {\n let lo = Infinity\n let hi = -Infinity\n for (const v of values) {\n if (v < lo) lo = v\n if (v > hi) hi = v\n }\n return lo <= hi ? [lo, hi] : [0, 1]\n}\n\n/**\n * Ticks spanning `range`, each one **a sample** — a row the author can point at\n * in their file — placed as near as it can be to where an evenly spaced tick\n * would have gone.\n *\n * Two things have to hold at once, and picking either alone gets it wrong.\n * Ticks must be *samples*, because a caption between two of them has nothing to\n * say (two-thirds of the way through March is not a month, and on a category\n * column it is not anything at all). And they must be *spread*, because the\n * labels sit side by side in a row: taking every nth sample instead puts five\n * dates in the two weeks a burst of rows happens to cover and prints them on\n * top of each other, which is what a column with a gap in it does.\n *\n * Walking the targets rather than the samples is what satisfies both. A run of\n * rows too close together to caption collapses to one tick — consecutive\n * targets pick the same sample and the duplicate is dropped — so a chart asked\n * for five ticks across a gappy column draws four honest ones instead of five\n * overlapping.\n */\nfunction sampledTicks(\n positions: number[],\n [min, max]: Range,\n divisions: number\n): number[] {\n const inside = positions.filter((v) => v >= min && v <= max)\n const unique = [...new Set(inside)].sort((a, b) => a - b)\n if (unique.length === 0) return markerValues([min, max], divisions)\n\n const nearest = (target: number) =>\n unique.reduce((best, v) =>\n Math.abs(v - target) < Math.abs(best - target) ? v : best\n )\n\n const out: number[] = []\n for (const target of markerValues([min, max], divisions)) {\n const tick = nearest(target)\n if (out[out.length - 1] !== tick) out.push(tick)\n }\n return out\n}\n\n/**\n * The caption a tick carries on a sample-based scale: the cell of the row it\n * came from, or `fallback` when the tick isn't a sample after all.\n */\nfunction labelBySample(\n positions: number[],\n captions: string[],\n value: number,\n fallback: (value: number) => string\n): string {\n const i = positions.indexOf(value)\n return i >= 0 ? captions[i] : fallback(value)\n}\n\n/**\n * How `field` positions the rows: the number it holds, the instant it names, or\n * failing both, the row's own place in the file.\n *\n * Resolved from the data on every read rather than cached on the node: it is\n * cheap (one pass to classify, one to position), and a cache keyed on a `data`\n * array that `buildScene` rebuilds on every edit would miss every time anyway.\n */\nexport function resolveXScale(\n data: DataRecord[],\n field: string,\n /**\n * The number-format pattern for a *numeric* column. Ignored by the other two\n * kinds, and correctly so: a tick on those is captioned with the cell the\n * author wrote, and `Jan` has no decimal places.\n */\n pattern?: string\n): XScale {\n if (field !== \"\" && isNumericColumn(data, field)) {\n const positions = data.map((row) => num(row, field))\n return {\n kind: \"numeric\",\n at: (i) => positions[i] ?? 0,\n extent: extentOf(positions),\n label: (value) =>\n pattern === undefined ? formatMarker(value) : formatNumber(value, pattern),\n // Round numbers, evenly spaced — a continuous axis is read by its scale\n // rather than by which samples happen to be on it.\n ticks: markerValues,\n }\n }\n\n const captions = data.map((row) => text(row, field))\n const instants = field === \"\" ? null : instantColumn(data, field)\n\n if (instants) {\n const extent = extentOf(instants)\n const span = extent[1] - extent[0]\n return {\n kind: \"time\",\n at: (i) => instants[i] ?? 0,\n extent,\n label: (value) =>\n labelBySample(instants, captions, value, (v) =>\n formatInstant(v, span)\n ),\n ticks: (range, divisions) => sampledTicks(instants, range, divisions),\n }\n }\n\n const positions = data.map((_, i) => i)\n return {\n kind: \"ordinal\",\n at: (i) => i,\n // `[0, n-1]` rather than the tidy `[0, n]`: the first and last rows sit on\n // the plot's edges, which is where a reader looks for them.\n extent: positions.length > 0 ? [0, positions.length - 1] : [0, 1],\n label: (value) => captions[Math.round(value)] ?? \"\",\n ticks: (range, divisions) => sampledTicks(positions, range, divisions),\n }\n}\n"]}
1
+ {"version":3,"file":"x-scale.js","sourceRoot":"","sources":["../../src/line-chart/x-scale.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * What a line chart's x column *means* — the one question that has to be\r\n * answered before a row can be given a position along the bottom of the plot.\r\n *\r\n * A y column is always a magnitude. An x column is one of three things, and\r\n * which one is a property of the data rather than something the author states:\r\n *\r\n * - **numeric** — `0, 5, 10` — plot the number.\r\n * - **time** — `Jan`, `2026-03`, `2026-W12`, `2026-03-14` — plot the instant,\r\n * so samples a month apart sit a month apart and a gap in the data reads as a\r\n * gap.\r\n * - **ordinal** — anything else — plot the row's position, evenly spaced.\r\n *\r\n * Before this, every x column went through `num()`, which reads a non-numeric\r\n * cell as `0`. A `Month` column of `Jan…Dec` therefore stacked all twelve\r\n * points on one x and drew a vertical line — no error, no empty chart, just a\r\n * wrong picture. That is the failure this module exists to remove: every column\r\n * now plots as *something*, and the something is the reading a person would\r\n * give it.\r\n *\r\n * The captions come back off the rows rather than out of a formatter. A tick on\r\n * a time or ordinal scale is chosen *from the data* (see {@link XScale.ticks}),\r\n * so the label it wants is the cell the author wrote — `Jan`, not `1970-01-01`,\r\n * and `2026-W12`, not the Monday it resolves to. Only a tick that is not a\r\n * sample — an author-set range, or a `zoomTo` mid-flight — needs formatting,\r\n * and that is the one case {@link formatInstant} covers.\r\n */\r\n\r\nimport type { DataRecord } from \"@motionscript/sdk\"\r\n\r\nimport {\r\n formatMarker,\r\n markerValues,\r\n num,\r\n text,\r\n type Range,\r\n} from \"../kit/shared\"\r\nimport { formatNumber } from \"@motionscript/sdk/component\"\r\n\r\n/** How an x column's cells become positions along the axis. */\r\nexport type XScaleKind = \"numeric\" | \"time\" | \"ordinal\"\r\n\r\n/**\r\n * An x column resolved into positions, captions and ticks.\r\n *\r\n * The three kinds differ in all three answers, so they are three closures built\r\n * once per (data, column) rather than a kind tag every caller switches on.\r\n */\r\nexport interface XScale {\r\n kind: XScaleKind\r\n /** Position of row `index` along the axis, in this scale's own units. */\r\n at(index: number): number\r\n /** `[min, max]` across the rows — the scale when no range is set. */\r\n extent: Range\r\n /** The caption for a tick at `value`. */\r\n label(value: number): string\r\n /** `divisions + 1` tick values spanning `range`, ends included. */\r\n ticks(range: Range, divisions: number): number[]\r\n}\r\n\r\n// ---- Time parsing --------------------------------------------------------\r\n\r\nconst DAY = 86_400_000\r\n\r\nconst MONTHS = [\r\n \"january\",\r\n \"february\",\r\n \"march\",\r\n \"april\",\r\n \"may\",\r\n \"june\",\r\n \"july\",\r\n \"august\",\r\n \"september\",\r\n \"october\",\r\n \"november\",\r\n \"december\",\r\n]\r\n\r\n/**\r\n * The year a cell that names no year is dated into.\r\n *\r\n * Any year would place `Jan…Dec` in the right order at the right spacing; a\r\n * leap year is the one that also spaces February correctly against a column\r\n * that *does* carry years, so the two read alike.\r\n */\r\nconst NOMINAL_YEAR = 2024\r\n\r\n/**\r\n * Month index for any unambiguous abbreviation of a month name — `jan`, `sept`,\r\n * `january` — or -1.\r\n *\r\n * Three letters minimum, and then a prefix match, which is what lets `sept`\r\n * through without letting `janitor` through: a longer word has to still *be*\r\n * the month.\r\n */\r\nfunction monthIndex(word: string): number {\r\n if (word.length < 3) return -1\r\n return MONTHS.findIndex((month) => month.startsWith(word))\r\n}\r\n\r\n/** The Monday of ISO week `week` in `year`, as an epoch timestamp. */\r\nfunction isoWeekStart(year: number, week: number): number {\r\n // ISO week 1 is the week containing 4 January, and weeks start on Monday.\r\n const jan4 = Date.UTC(year, 0, 4)\r\n const weekday = (new Date(jan4).getUTCDay() + 6) % 7 // Monday = 0\r\n return jan4 - weekday * DAY + (week - 1) * 7 * DAY\r\n}\r\n\r\n/**\r\n * One cell read as an instant, or `null` when it isn't one.\r\n *\r\n * Deliberately **not** `Date.parse`. That accepts `\"5\"`, `\"Q3\"` and a good deal\r\n * else besides, differs between engines on anything non-ISO, and reads a bare\r\n * `2026-03-14` as UTC but `2026/03/14` as local — three ways for a column to be\r\n * silently misread as time. Every form below is matched explicitly, and an\r\n * ambiguous all-numeric date (`03/04/2026` — March or April, depending on where\r\n * the author lives) is deliberately not one of them.\r\n */\r\nexport function parseInstant(raw: unknown): number | null {\r\n if (typeof raw !== \"string\") return null\r\n const s = raw.trim().toLowerCase()\r\n if (s === \"\") return null\r\n\r\n // 2026-03-14 · 2026/03/14 · 2026-03 · 2026/03\r\n const ymd = /^(\\d{4})[-/](\\d{1,2})(?:[-/](\\d{1,2}))?$/.exec(s)\r\n if (ymd) {\r\n const month = Number(ymd[2])\r\n const day = ymd[3] === undefined ? 1 : Number(ymd[3])\r\n if (month < 1 || month > 12 || day < 1 || day > 31) return null\r\n return Date.UTC(Number(ymd[1]), month - 1, day)\r\n }\r\n\r\n // 2026-W12 · 2026W12 · w12 · week 12\r\n const week = /^(?:(\\d{4})[-\\s]?)?w(?:ee)?k?[-\\s]?(\\d{1,2})$/.exec(s)\r\n if (week) {\r\n const n = Number(week[2])\r\n if (n < 1 || n > 53) return null\r\n return isoWeekStart(\r\n week[1] === undefined ? NOMINAL_YEAR : Number(week[1]),\r\n n\r\n )\r\n }\r\n\r\n // jan · january · jan 2026 · january-2026 · 2026 jan\r\n const named = /^(?:(\\d{4})[-\\s]+)?([a-z]+)(?:[-\\s]+(\\d{4}))?$/.exec(s)\r\n if (named) {\r\n const month = monthIndex(named[2])\r\n if (month >= 0) {\r\n const year = named[1] ?? named[3]\r\n const y = year === undefined ? NOMINAL_YEAR : Number(year)\r\n return Date.UTC(y, month, 1)\r\n }\r\n }\r\n\r\n // 14 mar 2026 · mar 14 2026 · mar 14, 2026\r\n const dayNamed =\r\n /^(?:(\\d{1,2})[-\\s]+([a-z]+)|([a-z]+)[-\\s]+(\\d{1,2}),?)[-\\s]+(\\d{4})$/.exec(\r\n s\r\n )\r\n if (dayNamed) {\r\n const month = monthIndex(dayNamed[2] ?? dayNamed[3])\r\n const day = Number(dayNamed[1] ?? dayNamed[4])\r\n if (month >= 0 && day >= 1 && day <= 31) {\r\n return Date.UTC(Number(dayNamed[5]), month, day)\r\n }\r\n }\r\n\r\n return null\r\n}\r\n\r\n/**\r\n * An instant captioned at the coarsest granularity the axis's own span can tell\r\n * apart — a decade of samples reads as years, a year of them as months.\r\n *\r\n * Only reached for a tick that is *not* one of the rows (an author-set range, a\r\n * `zoomTo` part-way through). A tick that is a sample is captioned with the\r\n * cell the author wrote; see the note at the top of this file.\r\n */\r\nexport function formatInstant(value: number, span: number): string {\r\n const date = new Date(value)\r\n const year = date.getUTCFullYear()\r\n const month = MONTHS[date.getUTCMonth()].slice(0, 3)\r\n const label = month.charAt(0).toUpperCase() + month.slice(1)\r\n if (span > 4 * 365 * DAY) return String(year)\r\n if (span > 60 * DAY) return `${label} ${year}`\r\n return `${label} ${date.getUTCDate()}`\r\n}\r\n\r\n// ---- Column reading ------------------------------------------------------\r\n\r\n/**\r\n * Whether every non-empty cell of `field` holds a number.\r\n *\r\n * The same rule `numericFields` applies, asked of one column: a single text\r\n * cell in an otherwise numeric column takes the whole column off the numeric\r\n * scale, because plotting that row at 0 is the failure this module is here to\r\n * stop.\r\n */\r\nfunction isNumericColumn(data: DataRecord[], field: string): boolean {\r\n let sawValue = false\r\n for (const row of data) {\r\n const v = row[field]\r\n if (v === undefined || v === null || v === \"\") continue\r\n if (typeof v !== \"number\") return false\r\n sawValue = true\r\n }\r\n return sawValue\r\n}\r\n\r\n/**\r\n * Every non-empty cell of `field` parsed as an instant, or `null` if any one of\r\n * them isn't one — in which case the column isn't a time column at all.\r\n *\r\n * A blank cell keeps its slot, so the returned list stays index-aligned with\r\n * the rows, and takes the nearest known instant: a hole in an x column is a row\r\n * whose date the file didn't say, and stacking it on the epoch would drag the\r\n * whole scale back fifty years to make room for it.\r\n */\r\nfunction instantColumn(data: DataRecord[], field: string): number[] | null {\r\n const parsed: (number | null)[] = []\r\n let sawValue = false\r\n for (const row of data) {\r\n const raw = row[field]\r\n if (raw === undefined || raw === null || raw === \"\") {\r\n parsed.push(null)\r\n continue\r\n }\r\n const t = parseInstant(raw)\r\n if (t === null) return null\r\n parsed.push(t)\r\n sawValue = true\r\n }\r\n if (!sawValue) return null\r\n\r\n const first = parsed.find((t): t is number => t !== null) as number\r\n let last = first\r\n return parsed.map((t) => (t === null ? last : (last = t)))\r\n}\r\n\r\n/** `[min, max]` of `values`, or `[0, 1]` when there is nothing to span. */\r\nfunction extentOf(values: number[]): Range {\r\n let lo = Infinity\r\n let hi = -Infinity\r\n for (const v of values) {\r\n if (v < lo) lo = v\r\n if (v > hi) hi = v\r\n }\r\n return lo <= hi ? [lo, hi] : [0, 1]\r\n}\r\n\r\n/**\r\n * Ticks spanning `range`, each one **a sample** — a row the author can point at\r\n * in their file — placed as near as it can be to where an evenly spaced tick\r\n * would have gone.\r\n *\r\n * Two things have to hold at once, and picking either alone gets it wrong.\r\n * Ticks must be *samples*, because a caption between two of them has nothing to\r\n * say (two-thirds of the way through March is not a month, and on a category\r\n * column it is not anything at all). And they must be *spread*, because the\r\n * labels sit side by side in a row: taking every nth sample instead puts five\r\n * dates in the two weeks a burst of rows happens to cover and prints them on\r\n * top of each other, which is what a column with a gap in it does.\r\n *\r\n * Walking the targets rather than the samples is what satisfies both. A run of\r\n * rows too close together to caption collapses to one tick — consecutive\r\n * targets pick the same sample and the duplicate is dropped — so a chart asked\r\n * for five ticks across a gappy column draws four honest ones instead of five\r\n * overlapping.\r\n */\r\nfunction sampledTicks(\r\n positions: number[],\r\n [min, max]: Range,\r\n divisions: number\r\n): number[] {\r\n const inside = positions.filter((v) => v >= min && v <= max)\r\n const unique = [...new Set(inside)].sort((a, b) => a - b)\r\n if (unique.length === 0) return markerValues([min, max], divisions)\r\n\r\n const nearest = (target: number) =>\r\n unique.reduce((best, v) =>\r\n Math.abs(v - target) < Math.abs(best - target) ? v : best\r\n )\r\n\r\n const out: number[] = []\r\n for (const target of markerValues([min, max], divisions)) {\r\n const tick = nearest(target)\r\n if (out[out.length - 1] !== tick) out.push(tick)\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * The caption a tick carries on a sample-based scale: the cell of the row it\r\n * came from, or `fallback` when the tick isn't a sample after all.\r\n */\r\nfunction labelBySample(\r\n positions: number[],\r\n captions: string[],\r\n value: number,\r\n fallback: (value: number) => string\r\n): string {\r\n const i = positions.indexOf(value)\r\n return i >= 0 ? captions[i] : fallback(value)\r\n}\r\n\r\n/**\r\n * How `field` positions the rows: the number it holds, the instant it names, or\r\n * failing both, the row's own place in the file.\r\n *\r\n * Resolved from the data on every read rather than cached on the node: it is\r\n * cheap (one pass to classify, one to position), and a cache keyed on a `data`\r\n * array that a rebuild replaces on every edit would miss every time anyway.\r\n */\r\nexport function resolveXScale(\r\n data: DataRecord[],\r\n field: string,\r\n /**\r\n * The number-format pattern for a *numeric* column. Ignored by the other two\r\n * kinds, and correctly so: a tick on those is captioned with the cell the\r\n * author wrote, and `Jan` has no decimal places.\r\n */\r\n pattern?: string\r\n): XScale {\r\n if (field !== \"\" && isNumericColumn(data, field)) {\r\n const positions = data.map((row) => num(row, field))\r\n return {\r\n kind: \"numeric\",\r\n at: (i) => positions[i] ?? 0,\r\n extent: extentOf(positions),\r\n label: (value) =>\r\n pattern === undefined ? formatMarker(value) : formatNumber(value, pattern),\r\n // Round numbers, evenly spaced — a continuous axis is read by its scale\r\n // rather than by which samples happen to be on it.\r\n ticks: markerValues,\r\n }\r\n }\r\n\r\n const captions = data.map((row) => text(row, field))\r\n const instants = field === \"\" ? null : instantColumn(data, field)\r\n\r\n if (instants) {\r\n const extent = extentOf(instants)\r\n const span = extent[1] - extent[0]\r\n return {\r\n kind: \"time\",\r\n at: (i) => instants[i] ?? 0,\r\n extent,\r\n label: (value) =>\r\n labelBySample(instants, captions, value, (v) =>\r\n formatInstant(v, span)\r\n ),\r\n ticks: (range, divisions) => sampledTicks(instants, range, divisions),\r\n }\r\n }\r\n\r\n const positions = data.map((_, i) => i)\r\n return {\r\n kind: \"ordinal\",\r\n at: (i) => i,\r\n // `[0, n-1]` rather than the tidy `[0, n]`: the first and last rows sit on\r\n // the plot's edges, which is where a reader looks for them.\r\n extent: positions.length > 0 ? [0, positions.length - 1] : [0, 1],\r\n label: (value) => captions[Math.round(value)] ?? \"\",\r\n ticks: (range, divisions) => sampledTicks(positions, range, divisions),\r\n }\r\n}\r\n"]}
package/dist/nodes.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"nodes.js","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC","sourcesContent":["import { LineChart } from \"./line-chart\";\nimport { BarChart } from \"./bar-chart\";\nimport { PieChart } from \"./pie-chart\";\n\n/**\n * Every node type this package publishes.\n *\n * A host registers these by handing the array to an engine —\n * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`\n * key. The decorator only *declares* that key: nothing is registered by the mere\n * act of importing a module, so a registry holds exactly what its owner asked\n * for and two engines in one process can differ about it.\n *\n * Listing class **values** is also what survives bundling. This package declares\n * `sideEffects: false`, which licenses a bundler to drop a module nothing\n * imports a value from, and a document names a node type by string — so\n * `import \"./line-chart\"` for its side effect is exactly what gets shaken out, where an\n * array of bindings is a data dependency that cannot be.\n */\nexport const NODES = [LineChart, BarChart, PieChart];\n"]}
1
+ {"version":3,"file":"nodes.js","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC","sourcesContent":["import { LineChart } from \"./line-chart\";\r\nimport { BarChart } from \"./bar-chart\";\r\nimport { PieChart } from \"./pie-chart\";\r\n\r\n/**\r\n * Every node type this package publishes.\r\n *\r\n * A host registers these by handing the array to an engine —\r\n * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`\r\n * key. The decorator only *declares* that key: nothing is registered by the mere\r\n * act of importing a module, so a registry holds exactly what its owner asked\r\n * for and two engines in one process can differ about it.\r\n *\r\n * Listing class **values** is also what survives bundling. This package declares\r\n * `sideEffects: false`, which licenses a bundler to drop a module nothing\r\n * imports a value from, and a document names a node type by string — so\r\n * `import \"./line-chart\"` for its side effect is exactly what gets shaken out, where an\r\n * array of bindings is a data dependency that cannot be.\r\n */\r\nexport const NODES = [LineChart, BarChart, PieChart];\r\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pie-chart/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEtC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAExC,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,OAAO,EACP,WAAW,EACX,oBAAoB,EACpB,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,UAAU,GACX,MAAM,UAAU,CAAA","sourcesContent":["/**\n * The pie chart: the one in the family that draws a mark per **row** rather\n * than per column.\n *\n * So it shares the legend and the theme machinery with its siblings and none of\n * the cartesian geometry. Set the theme's `ring.holeRadius` to 0 for a solid\n * pie; it defaults to a donut, because the hole is the only place a chart has\n * to put its conclusion.\n */\nexport { PieChart } from \"./pie-chart\"\nexport type { PieChartProps } from \"./pie-chart\"\nexport { PieRing } from \"./pie-ring\"\nexport type { PieRingProps } from \"./pie-ring\"\nexport { PieLegend } from \"./pie-legend\"\nexport type { PieLegendProps } from \"./pie-legend\"\nexport {\n DEFAULT_PIE_CHART_THEME,\n lerpPieChartTheme,\n pieArcs,\n relaxLabels,\n resolvePieChartTheme,\n resolvePieSlices,\n ringPoint,\n sliceTotal,\n sliceValue,\n} from \"./shared\"\nexport type {\n CenterConfig,\n LabelConfig,\n PieArc,\n PieChartConfig,\n PieChartTheme,\n PieLabelMode,\n PieLabelPlacement,\n PieSlice,\n RingConfig,\n} from \"./shared\"\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pie-chart/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEtC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAExC,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,OAAO,EACP,WAAW,EACX,oBAAoB,EACpB,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,UAAU,GACX,MAAM,UAAU,CAAA","sourcesContent":["/**\r\n * The pie chart: the one in the family that draws a mark per **row** rather\r\n * than per column.\r\n *\r\n * So it shares the legend and the theme machinery with its siblings and none of\r\n * the cartesian geometry. Set the theme's `ring.holeRadius` to 0 for a solid\r\n * pie; it defaults to a donut, because the hole is the only place a chart has\r\n * to put its conclusion.\r\n */\r\nexport { PieChart } from \"./pie-chart\"\r\nexport type { PieChartProps } from \"./pie-chart\"\r\nexport { PieRing } from \"./pie-ring\"\r\nexport type { PieRingProps } from \"./pie-ring\"\r\nexport { PieLegend } from \"./pie-legend\"\r\nexport type { PieLegendProps } from \"./pie-legend\"\r\nexport {\r\n DEFAULT_PIE_CHART_THEME,\r\n lerpPieChartTheme,\r\n pieArcs,\r\n relaxLabels,\r\n resolvePieChartTheme,\r\n resolvePieSlices,\r\n ringPoint,\r\n sliceTotal,\r\n sliceValue,\r\n} from \"./shared\"\r\nexport type {\r\n CenterConfig,\r\n LabelConfig,\r\n PieArc,\r\n PieChartConfig,\r\n PieChartTheme,\r\n PieLabelMode,\r\n PieLabelPlacement,\r\n PieSlice,\r\n RingConfig,\r\n} from \"./shared\"\r\n"]}
@@ -59,7 +59,7 @@ type SlicePops = {
59
59
  * edge staying put, and everything else dims behind it.
60
60
  *
61
61
  * **Resting state is the finished chart** — the same bargain the other two make:
62
- * the studio paints a node's Initial state on a paused canvas, so a chart that
62
+ * an editor paints a node's Initial state on a paused canvas, so a chart that
63
63
  * only appeared once a command had run would be edited blind. {@link enter} arms
64
64
  * every part back to its pre-entrance state before animating. The one exception
65
65
  * is the hole's summary, which a scene can hold back with `showCenter: false`
@@ -161,7 +161,7 @@ export declare class PieChart extends Node2D<PieChartProps> {
161
161
  /**
162
162
  * Ease every slice's pop from where it is now to what `pop` asks for.
163
163
  *
164
- * Consumes its full duration even when nothing moves: in the studio a
164
+ * Consumes its full duration even when nothing moves: in an editor a
165
165
  * command's duration is what the timeline lays out and what its parallel
166
166
  * siblings are timed against, so a no-op that took no frames would pull
167
167
  * everything after it forward.
@@ -1 +1 @@
1
- {"version":3,"file":"pie-chart.d.ts","sourceRoot":"","sources":["../../src/pie-chart/pie-chart.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pie-chart.d.ts","sourceRoot":"","sources":["../../src/pie-chart/pie-chart.ts"],"names":[],"mappings":"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"}
@@ -41,7 +41,7 @@ const LABEL_SHARE = 0.35;
41
41
  * edge staying put, and everything else dims behind it.
42
42
  *
43
43
  * **Resting state is the finished chart** — the same bargain the other two make:
44
- * the studio paints a node's Initial state on a paused canvas, so a chart that
44
+ * an editor paints a node's Initial state on a paused canvas, so a chart that
45
45
  * only appeared once a command had run would be edited blind. {@link enter} arms
46
46
  * every part back to its pre-entrance state before animating. The one exception
47
47
  * is the hole's summary, which a scene can hold back with `showCenter: false`
@@ -83,16 +83,18 @@ let PieChart = class PieChart extends Node2D {
83
83
  super(props);
84
84
  // The whole chart is composed from plain props/`data` — no context needed,
85
85
  // so the parts are built here (super() applied the props above). The slice
86
- // *structure* (one wedge and one legend row each) is read once, like every
87
- // other chart's series list; everything the parts draw with is passed as a
88
- // closure, so `chart.to({ theme })` genuinely re-paints the ring.
86
+ // *structure* (one label and one legend row per row of `data`) is read once,
87
+ // like every other chart's series list; everything the parts draw with,
88
+ // including the slices themselves and the paint they take from the palette,
89
+ // is passed as a closure, so `chart.to({ theme })` genuinely re-paints the
90
+ // ring and the swatches and a `set` of `data` re-cuts the wedges.
89
91
  const slices = this.slices;
90
92
  const showLegend = props?.showLegend !== false;
91
93
  this.pops = slices.map(() => 0);
92
94
  this.ring = new PieRing({
93
95
  width: "fill",
94
96
  height: "fill",
95
- slices,
97
+ slices: () => this.slices,
96
98
  ring: () => this.resolvedTheme.ring,
97
99
  label: () => this.resolvedTheme.label,
98
100
  centerStyle: () => this.resolvedTheme.center,
@@ -107,7 +109,7 @@ let PieChart = class PieChart extends Node2D {
107
109
  const children = [this.ring];
108
110
  if (showLegend) {
109
111
  this.legend = new PieLegend({
110
- slices,
112
+ slices: () => this.slices,
111
113
  legend: () => this.resolvedTheme.legend,
112
114
  dims: () => this.sliceDims(),
113
115
  });
@@ -198,7 +200,7 @@ let PieChart = class PieChart extends Node2D {
198
200
  /**
199
201
  * Ease every slice's pop from where it is now to what `pop` asks for.
200
202
  *
201
- * Consumes its full duration even when nothing moves: in the studio a
203
+ * Consumes its full duration even when nothing moves: in an editor a
202
204
  * command's duration is what the timeline lays out and what its parallel
203
205
  * siblings are timed against, so a no-op that took no frames would pull
204
206
  * everything after it forward.
@@ -1 +1 @@
1
- {"version":3,"file":"pie-chart.js","sourceRoot":"","sources":["../../src/pie-chart/pie-chart.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Rect,\n command,\n node,\n Node2D,\n easeOut,\n property,\n type Command,\n type CommandArgs,\n type DataRecord,\n type EasingFunction,\n type NodeConfig,\n type Node2DProps,\n} from \"@motionscript/sdk\"\n\nimport { clampUnit } from \"../kit/shared\"\nimport { PieLegend } from \"./pie-legend\"\nimport { PieRing } from \"./pie-ring\"\nimport {\n DEFAULT_PIE_CHART_THEME,\n lerpPieChartTheme,\n resolvePieChartTheme,\n resolvePieSlices,\n type PieChartConfig,\n type PieChartTheme,\n type PieLabelMode,\n type PieLabelPlacement,\n type PieSlice,\n} from \"./shared\"\nimport {\n asCommand,\n hold,\n sequence as sequenceOf,\n together,\n} from \"@motionscript/sdk/component\"\n\nexport interface PieChartProps extends Node2DProps {\n /** One row per slice — a plain record of field name to value. */\n data: DataRecord[]\n /** Key into each row for its slice caption, used on the labels and the legend. */\n categoryField: string\n /** Key into each row for its slice magnitude — the wedge's share of the ring. */\n valueField: string\n /**\n * The chart's visual theme — slice palette, ring geometry, label and centre\n * type, legend. Every branch is optional and deep-merged onto\n * {@link DEFAULT_PIE_CHART_THEME}.\n */\n theme?: PieChartTheme\n /** What the ring's labels say. */\n labelMode?: PieLabelMode\n /** Where they sit: inside their wedge, outside on a leader, or nowhere. */\n labelPlacement?: PieLabelPlacement\n /** Whether the legend column beside the ring is drawn at all. */\n showLegend?: boolean\n /** Gap between the ring and the legend, in px. */\n gap?: number\n /** Text set in the ring's hole — see {@link revealCenter}. */\n centerLabel?: string\n /** Smaller second line under {@link centerLabel}. */\n centerCaption?: string\n /**\n * Whether the hole's summary is drawn at rest. `true` (the default) is the\n * plain answer; a scene that wants the conclusion to land on a later beat sets\n * this `false` and calls {@link revealCenter} when it gets there.\n */\n showCenter?: boolean\n}\n\n/**\n * How {@link PieChart.enter} divides its duration: the wedges sweeping in, then\n * the labels and the legend arriving together. Shares of the whole, so they sum\n * to 1 — the entrance takes exactly the time it was given, for the reason spelt\n * out on `LineChart.enter`.\n */\nconst SWEEP_SHARE = 0.65\nconst LABEL_SHARE = 0.35\n\n/** What the selection commands move: the chart's own {@link PieChart.pops}. */\ntype SlicePops = { pops: number[] }\n\n/**\n * A pie or donut chart, composed from two independent parts rather than one\n * monolithic draw — the third of the chart family, built the same way as the\n * line and bar charts beside it:\n *\n * - {@link PieRing} — the wedges, each sized by its share of the total, with its\n * label set inside its own band or pinned outside on a leader line, and the\n * summary in the hole. The only part of the family that draws a path.\n * - {@link PieLegend} — a column of swatch-and-caption rows naming each slice.\n *\n * The two are laid out by a plain horizontal-flow {@link Rect}: the ring fills whatever the\n * hugging legend leaves, both centred, so the chart re-flows if its box changes.\n *\n * **One mark per row, not per column** — which is what makes this the odd one of\n * the three. A line or a bar chart plots *series*, so its colours belong to\n * columns and hiding one is a sensible thing to ask for. A pie plots rows, so its\n * palette is indexed by row and there is nothing to hide: dropping a slice would\n * silently re-cut every other wedge's share, which is a different chart rather\n * than the same chart with a series switched off. {@link selectSlice} and its\n * siblings are what a pie has instead — a slice swells out of the ring, its hole\n * edge staying put, and everything else dims behind it.\n *\n * **Resting state is the finished chart** — the same bargain the other two make:\n * the studio paints a node's Initial state on a paused canvas, so a chart that\n * only appeared once a command had run would be edited blind. {@link enter} arms\n * every part back to its pre-entrance state before animating. The one exception\n * is the hole's summary, which a scene can hold back with `showCenter: false`\n * because it is usually the chart's *conclusion* rather than part of its setup.\n */\n@node({\n key: \"pieChart\",\n parentKey: \"node\",\n forkable: true,\n layout: {\n children: \"freeform\",\n defaultWidthMode: \"fixed\",\n defaultHeightMode: \"fixed\",\n acceptsChildren: true,\n },\n seed: {\n width: 680,\n height: 480,\n },\n})\nexport class PieChart extends Node2D<PieChartProps> {\n @property({ default: () => [] as DataRecord[] }) declare data: DataRecord[]\n @property({ default: \"\" }) declare categoryField: string\n @property({ default: \"\" }) declare valueField: string\n @property({\n default: DEFAULT_PIE_CHART_THEME,\n mapper: resolvePieChartTheme,\n tween: lerpPieChartTheme,\n })\n declare theme: PieChartTheme\n @property({ default: \"percent\" }) declare labelMode: PieLabelMode\n @property({ default: \"leader\" }) declare labelPlacement: PieLabelPlacement\n @property({ default: 40 }) declare gap: number\n @property({ default: \"\" }) declare centerLabel: string\n @property({ default: \"\" }) declare centerCaption: string\n\n /**\n * How far each slice is currently pushed out of the ring, `0`–`1` per index.\n *\n * Held here rather than on the ring because two parts read it: the wedges grow\n * out by it, and the legend entries dim by it. One signal, one tween, and the\n * two can't drift.\n */\n @property({ default: () => [] as number[] }) declare pops: number[]\n\n // ---- Part handles (built in the constructor) --------------------------\n private ring: PieRing\n private legend: PieLegend | null = null\n\n /** `this.theme` read back through its *resolved* shape — see `LineChart.theme`. */\n private get resolvedTheme(): PieChartConfig {\n return this.theme as unknown as PieChartConfig\n }\n\n /** The slices the ring and the legend draw, resolved from the rows and the palette. */\n private get slices(): PieSlice[] {\n return resolvePieSlices(\n this.data,\n this.categoryField,\n this.valueField,\n this.resolvedTheme.sliceFills\n )\n }\n\n /**\n * How far each slice is dimmed by the current selection, one entry per slice —\n * 1 while nothing is selected, and for the selected slice itself; falling\n * toward the theme's `dimOpacity` for the rest in proportion to how far the\n * selection has grown.\n */\n private sliceDims(): number[] {\n let focus = 0\n for (let i = 0; i < this.data.length; i++) {\n focus = Math.max(focus, this.popAt(i))\n }\n const dim = this.resolvedTheme.spotlight.dimOpacity\n if (focus <= 0) return this.data.map(() => 1)\n return this.data.map(\n (_, i) => 1 - (1 - clampUnit(dim)) * focus * (1 - this.popAt(i))\n )\n }\n\n /** How far slice `index` is currently pushed out, in `[0, 1]`. */\n private popAt(index: number): number {\n return clampUnit(this.pops[index] ?? 0)\n }\n\n constructor(props?: NodeConfig<PieChart, PieChartProps>) {\n super(props)\n\n // The whole chart is composed from plain props/`data` — no context needed,\n // so the parts are built here (super() applied the props above). The slice\n // *structure* (one wedge and one legend row each) is read once, like every\n // other chart's series list; everything the parts draw with is passed as a\n // closure, so `chart.to({ theme })` genuinely re-paints the ring.\n const slices = this.slices\n const showLegend = props?.showLegend !== false\n this.pops = slices.map(() => 0)\n\n this.ring = new PieRing({\n width: \"fill\",\n height: \"fill\",\n slices,\n ring: () => this.resolvedTheme.ring,\n label: () => this.resolvedTheme.label,\n centerStyle: () => this.resolvedTheme.center,\n labelMode: this.labelMode,\n labelPlacement: this.labelPlacement,\n pops: () => this.pops,\n dimOpacity: () => this.resolvedTheme.spotlight.dimOpacity,\n centerLabel: this.centerLabel,\n centerCaption: this.centerCaption,\n centerReveal: props?.showCenter === false ? 0 : 1,\n })\n\n const children: Node2D[] = [this.ring]\n if (showLegend) {\n this.legend = new PieLegend({\n slices,\n legend: () => this.resolvedTheme.legend,\n dims: () => this.sliceDims(),\n })\n children.push(this.legend)\n }\n\n this.add(\n new Rect({ flow: \"horizontal\",\n width: \"fill\",\n height: \"fill\",\n gap: this.gap,\n align: \"center\",\n children,\n })\n )\n }\n\n // ---- Orchestrated entrance --------------------------------------------\n\n /**\n * Prime every part at its pre-entrance state — no wedges swept, no labels, the\n * legend shifted and transparent.\n *\n * Called by {@link enter} before its first frame so nothing flashes ahead of\n * its beat. Idempotent, and public so a scene can hide a chart ahead of time\n * without starting the entrance. The hole's summary is deliberately untouched:\n * it has its own signal, so a scene that set it up to land later keeps that.\n */\n arm(): void {\n this.ring.set({ growth: 0, labelReveal: 0 })\n this.legend?.arm()\n }\n\n /**\n * Animate the whole chart in: the wedges sweep in clockwise from the theme's\n * `startAngle`, then the labels fade up and the legend staggers in together.\n *\n * `duration` is the **whole** entrance; the phases are shares of it.\n */\n @command()\n enter(\n args: CommandArgs<Record<string, never>> & { duration: number }\n ): Command<Record<string, never>> {\n const duration = args.duration\n const easing = args.easing ?? easeOut()\n this.arm()\n\n return asCommand(\n sequenceOf(\n this.ring.sweep({ duration: duration * SWEEP_SHARE, easing }),\n together(\n this.ring.revealLabels({ duration: duration * LABEL_SHARE, easing }),\n this.legend\n ? this.legend.enter({ duration: duration * LABEL_SHARE, easing })\n : hold(duration * LABEL_SHARE)\n )\n ),\n duration\n )\n }\n\n /**\n * Fade the hole's summary up.\n *\n * Held out of {@link enter} on purpose: what sits in a donut's hole is usually\n * the chart's conclusion, so the scene lands it on the beat it belongs to\n * rather than giving it away with the first frame. Pair it with\n * `showCenter: false`, which is what leaves the hole clear to begin with.\n */\n @command()\n revealCenter(\n args: CommandArgs<Record<string, never>> & { duration?: number } = {}\n ): Command<{ centerReveal: number }> {\n return this.ring.revealCenter({ duration: args.duration ?? 0.6, easing: args.easing ?? easeOut() })\n }\n\n // ---- Selection ---------------------------------------------------------\n\n /** True when slice `index` is currently grown out past the ring's rim. */\n isSelected(index: number): boolean {\n return this.popAt(index) > 0.5\n }\n\n /**\n * Grow slice `index` out past the ring's rim and dim the rest of the chart\n * behind it, leaving any other selected slice where it is.\n */\n @command({ args: [{ key: \"sliceIndex\", kind: \"number\", default: 0, min: 0, step: 1 }] })\n selectSlice(\n args: CommandArgs<{ sliceIndex: number }> & { duration?: number }\n ): Command<SlicePops> {\n const { sliceIndex: index } = args.data as { sliceIndex: number }\n const duration = args.duration ?? 0.45\n const easing = args.easing ?? easeOut()\n return this.tweenPops((i) => (i === index ? 1 : this.popAt(i)), duration, easing)\n }\n\n /** Settle slice `index` back flush with the ring. */\n @command({ args: [{ key: \"sliceIndex\", kind: \"number\", default: 0, min: 0, step: 1 }] })\n deselectSlice(\n args: CommandArgs<{ sliceIndex: number }> & { duration?: number }\n ): Command<SlicePops> {\n const { sliceIndex: index } = args.data as { sliceIndex: number }\n const duration = args.duration ?? 0.45\n const easing = args.easing ?? easeOut()\n return this.tweenPops((i) => (i === index ? 0 : this.popAt(i)), duration, easing)\n }\n\n /** Move the focus to slice `index` alone, settling whatever else was out. */\n @command({ args: [{ key: \"sliceIndex\", kind: \"number\", default: 0, min: 0, step: 1 }] })\n selectOnlySlice(\n args: CommandArgs<{ sliceIndex: number }> & { duration?: number }\n ): Command<SlicePops> {\n const { sliceIndex: index } = args.data as { sliceIndex: number }\n const duration = args.duration ?? 0.45\n const easing = args.easing ?? easeOut()\n return this.tweenPops((i) => (i === index ? 1 : 0), duration, easing)\n }\n\n /** Settle every slice flush and bring the whole chart back to full. */\n @command()\n clearSelection(\n args: CommandArgs<Record<string, never>> & { duration?: number } = {}\n ): Command<SlicePops> {\n const duration = args.duration ?? 0.45\n const easing = args.easing ?? easeOut()\n return this.tweenPops(() => 0, duration, easing)\n }\n\n /**\n * Ease every slice's pop from where it is now to what `pop` asks for.\n *\n * Consumes its full duration even when nothing moves: in the studio a\n * command's duration is what the timeline lays out and what its parallel\n * siblings are timed against, so a no-op that took no frames would pull\n * everything after it forward.\n *\n * `from` and the targets are read when the command is *built*, which for a\n * driven scene is with the node in the state its own start time implies — so a\n * select that follows another starts from where that one left the ring.\n */\n private tweenPops(\n pop: (index: number) => number,\n duration: number,\n easing: EasingFunction\n ): Command<SlicePops> {\n const from = this.data.map((_, i) => this.popAt(i))\n const targets = this.data.map((_, i) => clampUnit(pop(i)))\n if (from.every((value, i) => value === targets[i])) return this.command<SlicePops>(() => ({}), duration)\n\n return this.command<SlicePops>(\n (t) => ({ pops: from.map((value, i) => value + (targets[i] - value) * t) }),\n duration,\n easing\n )\n }\n}\n"]}
1
+ {"version":3,"file":"pie-chart.js","sourceRoot":"","sources":["../../src/pie-chart/pie-chart.ts"],"names":[],"mappings":"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","sourcesContent":["import {\r\n Rect,\r\n command,\r\n node,\r\n Node2D,\r\n easeOut,\r\n property,\r\n type Command,\r\n type CommandArgs,\r\n type DataRecord,\r\n type EasingFunction,\r\n type NodeConfig,\r\n type Node2DProps,\r\n} from \"@motionscript/sdk\"\r\n\r\nimport { clampUnit } from \"../kit/shared\"\r\nimport { PieLegend } from \"./pie-legend\"\r\nimport { PieRing } from \"./pie-ring\"\r\nimport {\r\n DEFAULT_PIE_CHART_THEME,\r\n lerpPieChartTheme,\r\n resolvePieChartTheme,\r\n resolvePieSlices,\r\n type PieChartConfig,\r\n type PieChartTheme,\r\n type PieLabelMode,\r\n type PieLabelPlacement,\r\n type PieSlice,\r\n} from \"./shared\"\r\nimport {\r\n asCommand,\r\n hold,\r\n sequence as sequenceOf,\r\n together,\r\n} from \"@motionscript/sdk/component\"\r\n\r\nexport interface PieChartProps extends Node2DProps {\r\n /** One row per slice — a plain record of field name to value. */\r\n data: DataRecord[]\r\n /** Key into each row for its slice caption, used on the labels and the legend. */\r\n categoryField: string\r\n /** Key into each row for its slice magnitude — the wedge's share of the ring. */\r\n valueField: string\r\n /**\r\n * The chart's visual theme — slice palette, ring geometry, label and centre\r\n * type, legend. Every branch is optional and deep-merged onto\r\n * {@link DEFAULT_PIE_CHART_THEME}.\r\n */\r\n theme?: PieChartTheme\r\n /** What the ring's labels say. */\r\n labelMode?: PieLabelMode\r\n /** Where they sit: inside their wedge, outside on a leader, or nowhere. */\r\n labelPlacement?: PieLabelPlacement\r\n /** Whether the legend column beside the ring is drawn at all. */\r\n showLegend?: boolean\r\n /** Gap between the ring and the legend, in px. */\r\n gap?: number\r\n /** Text set in the ring's hole — see {@link revealCenter}. */\r\n centerLabel?: string\r\n /** Smaller second line under {@link centerLabel}. */\r\n centerCaption?: string\r\n /**\r\n * Whether the hole's summary is drawn at rest. `true` (the default) is the\r\n * plain answer; a scene that wants the conclusion to land on a later beat sets\r\n * this `false` and calls {@link revealCenter} when it gets there.\r\n */\r\n showCenter?: boolean\r\n}\r\n\r\n/**\r\n * How {@link PieChart.enter} divides its duration: the wedges sweeping in, then\r\n * the labels and the legend arriving together. Shares of the whole, so they sum\r\n * to 1 — the entrance takes exactly the time it was given, for the reason spelt\r\n * out on `LineChart.enter`.\r\n */\r\nconst SWEEP_SHARE = 0.65\r\nconst LABEL_SHARE = 0.35\r\n\r\n/** What the selection commands move: the chart's own {@link PieChart.pops}. */\r\ntype SlicePops = { pops: number[] }\r\n\r\n/**\r\n * A pie or donut chart, composed from two independent parts rather than one\r\n * monolithic draw — the third of the chart family, built the same way as the\r\n * line and bar charts beside it:\r\n *\r\n * - {@link PieRing} — the wedges, each sized by its share of the total, with its\r\n * label set inside its own band or pinned outside on a leader line, and the\r\n * summary in the hole. The only part of the family that draws a path.\r\n * - {@link PieLegend} — a column of swatch-and-caption rows naming each slice.\r\n *\r\n * The two are laid out by a plain horizontal-flow {@link Rect}: the ring fills whatever the\r\n * hugging legend leaves, both centred, so the chart re-flows if its box changes.\r\n *\r\n * **One mark per row, not per column** — which is what makes this the odd one of\r\n * the three. A line or a bar chart plots *series*, so its colours belong to\r\n * columns and hiding one is a sensible thing to ask for. A pie plots rows, so its\r\n * palette is indexed by row and there is nothing to hide: dropping a slice would\r\n * silently re-cut every other wedge's share, which is a different chart rather\r\n * than the same chart with a series switched off. {@link selectSlice} and its\r\n * siblings are what a pie has instead — a slice swells out of the ring, its hole\r\n * edge staying put, and everything else dims behind it.\r\n *\r\n * **Resting state is the finished chart** — the same bargain the other two make:\r\n * an editor paints a node's Initial state on a paused canvas, so a chart that\r\n * only appeared once a command had run would be edited blind. {@link enter} arms\r\n * every part back to its pre-entrance state before animating. The one exception\r\n * is the hole's summary, which a scene can hold back with `showCenter: false`\r\n * because it is usually the chart's *conclusion* rather than part of its setup.\r\n */\r\n@node({\r\n key: \"pieChart\",\r\n parentKey: \"node\",\r\n forkable: true,\r\n layout: {\r\n children: \"freeform\",\r\n defaultWidthMode: \"fixed\",\r\n defaultHeightMode: \"fixed\",\r\n acceptsChildren: true,\r\n },\r\n seed: {\r\n width: 680,\r\n height: 480,\r\n },\r\n})\r\nexport class PieChart extends Node2D<PieChartProps> {\r\n @property({ default: () => [] as DataRecord[] }) declare data: DataRecord[]\r\n @property({ default: \"\" }) declare categoryField: string\r\n @property({ default: \"\" }) declare valueField: string\r\n @property({\r\n default: DEFAULT_PIE_CHART_THEME,\r\n mapper: resolvePieChartTheme,\r\n tween: lerpPieChartTheme,\r\n })\r\n declare theme: PieChartTheme\r\n @property({ default: \"percent\" }) declare labelMode: PieLabelMode\r\n @property({ default: \"leader\" }) declare labelPlacement: PieLabelPlacement\r\n @property({ default: 40 }) declare gap: number\r\n @property({ default: \"\" }) declare centerLabel: string\r\n @property({ default: \"\" }) declare centerCaption: string\r\n\r\n /**\r\n * How far each slice is currently pushed out of the ring, `0`–`1` per index.\r\n *\r\n * Held here rather than on the ring because two parts read it: the wedges grow\r\n * out by it, and the legend entries dim by it. One signal, one tween, and the\r\n * two can't drift.\r\n */\r\n @property({ default: () => [] as number[] }) declare pops: number[]\r\n\r\n // ---- Part handles (built in the constructor) --------------------------\r\n private ring: PieRing\r\n private legend: PieLegend | null = null\r\n\r\n /** `this.theme` read back through its *resolved* shape — see `LineChart.theme`. */\r\n private get resolvedTheme(): PieChartConfig {\r\n return this.theme as unknown as PieChartConfig\r\n }\r\n\r\n /** The slices the ring and the legend draw, resolved from the rows and the palette. */\r\n private get slices(): PieSlice[] {\r\n return resolvePieSlices(\r\n this.data,\r\n this.categoryField,\r\n this.valueField,\r\n this.resolvedTheme.sliceFills\r\n )\r\n }\r\n\r\n /**\r\n * How far each slice is dimmed by the current selection, one entry per slice —\r\n * 1 while nothing is selected, and for the selected slice itself; falling\r\n * toward the theme's `dimOpacity` for the rest in proportion to how far the\r\n * selection has grown.\r\n */\r\n private sliceDims(): number[] {\r\n let focus = 0\r\n for (let i = 0; i < this.data.length; i++) {\r\n focus = Math.max(focus, this.popAt(i))\r\n }\r\n const dim = this.resolvedTheme.spotlight.dimOpacity\r\n if (focus <= 0) return this.data.map(() => 1)\r\n return this.data.map(\r\n (_, i) => 1 - (1 - clampUnit(dim)) * focus * (1 - this.popAt(i))\r\n )\r\n }\r\n\r\n /** How far slice `index` is currently pushed out, in `[0, 1]`. */\r\n private popAt(index: number): number {\r\n return clampUnit(this.pops[index] ?? 0)\r\n }\r\n\r\n constructor(props?: NodeConfig<PieChart, PieChartProps>) {\r\n super(props)\r\n\r\n // The whole chart is composed from plain props/`data` — no context needed,\r\n // so the parts are built here (super() applied the props above). The slice\r\n // *structure* (one label and one legend row per row of `data`) is read once,\r\n // like every other chart's series list; everything the parts draw with,\r\n // including the slices themselves and the paint they take from the palette,\r\n // is passed as a closure, so `chart.to({ theme })` genuinely re-paints the\r\n // ring and the swatches and a `set` of `data` re-cuts the wedges.\r\n const slices = this.slices\r\n const showLegend = props?.showLegend !== false\r\n this.pops = slices.map(() => 0)\r\n\r\n this.ring = new PieRing({\r\n width: \"fill\",\r\n height: \"fill\",\r\n slices: () => this.slices,\r\n ring: () => this.resolvedTheme.ring,\r\n label: () => this.resolvedTheme.label,\r\n centerStyle: () => this.resolvedTheme.center,\r\n labelMode: this.labelMode,\r\n labelPlacement: this.labelPlacement,\r\n pops: () => this.pops,\r\n dimOpacity: () => this.resolvedTheme.spotlight.dimOpacity,\r\n centerLabel: this.centerLabel,\r\n centerCaption: this.centerCaption,\r\n centerReveal: props?.showCenter === false ? 0 : 1,\r\n })\r\n\r\n const children: Node2D[] = [this.ring]\r\n if (showLegend) {\r\n this.legend = new PieLegend({\r\n slices: () => this.slices,\r\n legend: () => this.resolvedTheme.legend,\r\n dims: () => this.sliceDims(),\r\n })\r\n children.push(this.legend)\r\n }\r\n\r\n this.add(\r\n new Rect({ flow: \"horizontal\",\r\n width: \"fill\",\r\n height: \"fill\",\r\n gap: this.gap,\r\n align: \"center\",\r\n children,\r\n })\r\n )\r\n }\r\n\r\n // ---- Orchestrated entrance --------------------------------------------\r\n\r\n /**\r\n * Prime every part at its pre-entrance state — no wedges swept, no labels, the\r\n * legend shifted and transparent.\r\n *\r\n * Called by {@link enter} before its first frame so nothing flashes ahead of\r\n * its beat. Idempotent, and public so a scene can hide a chart ahead of time\r\n * without starting the entrance. The hole's summary is deliberately untouched:\r\n * it has its own signal, so a scene that set it up to land later keeps that.\r\n */\r\n arm(): void {\r\n this.ring.set({ growth: 0, labelReveal: 0 })\r\n this.legend?.arm()\r\n }\r\n\r\n /**\r\n * Animate the whole chart in: the wedges sweep in clockwise from the theme's\r\n * `startAngle`, then the labels fade up and the legend staggers in together.\r\n *\r\n * `duration` is the **whole** entrance; the phases are shares of it.\r\n */\r\n @command()\r\n enter(\r\n args: CommandArgs<Record<string, never>> & { duration: number }\r\n ): Command<Record<string, never>> {\r\n const duration = args.duration\r\n const easing = args.easing ?? easeOut()\r\n this.arm()\r\n\r\n return asCommand(\r\n sequenceOf(\r\n this.ring.sweep({ duration: duration * SWEEP_SHARE, easing }),\r\n together(\r\n this.ring.revealLabels({ duration: duration * LABEL_SHARE, easing }),\r\n this.legend\r\n ? this.legend.enter({ duration: duration * LABEL_SHARE, easing })\r\n : hold(duration * LABEL_SHARE)\r\n )\r\n ),\r\n duration\r\n )\r\n }\r\n\r\n /**\r\n * Fade the hole's summary up.\r\n *\r\n * Held out of {@link enter} on purpose: what sits in a donut's hole is usually\r\n * the chart's conclusion, so the scene lands it on the beat it belongs to\r\n * rather than giving it away with the first frame. Pair it with\r\n * `showCenter: false`, which is what leaves the hole clear to begin with.\r\n */\r\n @command()\r\n revealCenter(\r\n args: CommandArgs<Record<string, never>> & { duration?: number } = {}\r\n ): Command<{ centerReveal: number }> {\r\n return this.ring.revealCenter({ duration: args.duration ?? 0.6, easing: args.easing ?? easeOut() })\r\n }\r\n\r\n // ---- Selection ---------------------------------------------------------\r\n\r\n /** True when slice `index` is currently grown out past the ring's rim. */\r\n isSelected(index: number): boolean {\r\n return this.popAt(index) > 0.5\r\n }\r\n\r\n /**\r\n * Grow slice `index` out past the ring's rim and dim the rest of the chart\r\n * behind it, leaving any other selected slice where it is.\r\n */\r\n @command({ args: [{ key: \"sliceIndex\", kind: \"number\", default: 0, min: 0, step: 1 }] })\r\n selectSlice(\r\n args: CommandArgs<{ sliceIndex: number }> & { duration?: number }\r\n ): Command<SlicePops> {\r\n const { sliceIndex: index } = args.data as { sliceIndex: number }\r\n const duration = args.duration ?? 0.45\r\n const easing = args.easing ?? easeOut()\r\n return this.tweenPops((i) => (i === index ? 1 : this.popAt(i)), duration, easing)\r\n }\r\n\r\n /** Settle slice `index` back flush with the ring. */\r\n @command({ args: [{ key: \"sliceIndex\", kind: \"number\", default: 0, min: 0, step: 1 }] })\r\n deselectSlice(\r\n args: CommandArgs<{ sliceIndex: number }> & { duration?: number }\r\n ): Command<SlicePops> {\r\n const { sliceIndex: index } = args.data as { sliceIndex: number }\r\n const duration = args.duration ?? 0.45\r\n const easing = args.easing ?? easeOut()\r\n return this.tweenPops((i) => (i === index ? 0 : this.popAt(i)), duration, easing)\r\n }\r\n\r\n /** Move the focus to slice `index` alone, settling whatever else was out. */\r\n @command({ args: [{ key: \"sliceIndex\", kind: \"number\", default: 0, min: 0, step: 1 }] })\r\n selectOnlySlice(\r\n args: CommandArgs<{ sliceIndex: number }> & { duration?: number }\r\n ): Command<SlicePops> {\r\n const { sliceIndex: index } = args.data as { sliceIndex: number }\r\n const duration = args.duration ?? 0.45\r\n const easing = args.easing ?? easeOut()\r\n return this.tweenPops((i) => (i === index ? 1 : 0), duration, easing)\r\n }\r\n\r\n /** Settle every slice flush and bring the whole chart back to full. */\r\n @command()\r\n clearSelection(\r\n args: CommandArgs<Record<string, never>> & { duration?: number } = {}\r\n ): Command<SlicePops> {\r\n const duration = args.duration ?? 0.45\r\n const easing = args.easing ?? easeOut()\r\n return this.tweenPops(() => 0, duration, easing)\r\n }\r\n\r\n /**\r\n * Ease every slice's pop from where it is now to what `pop` asks for.\r\n *\r\n * Consumes its full duration even when nothing moves: in an editor a\r\n * command's duration is what the timeline lays out and what its parallel\r\n * siblings are timed against, so a no-op that took no frames would pull\r\n * everything after it forward.\r\n *\r\n * `from` and the targets are read when the command is *built*, which for a\r\n * driven scene is with the node in the state its own start time implies — so a\r\n * select that follows another starts from where that one left the ring.\r\n */\r\n private tweenPops(\r\n pop: (index: number) => number,\r\n duration: number,\r\n easing: EasingFunction\r\n ): Command<SlicePops> {\r\n const from = this.data.map((_, i) => this.popAt(i))\r\n const targets = this.data.map((_, i) => clampUnit(pop(i)))\r\n if (from.every((value, i) => value === targets[i])) return this.command<SlicePops>(() => ({}), duration)\r\n\r\n return this.command<SlicePops>(\r\n (t) => ({ pops: from.map((value, i) => value + (targets[i] - value) * t) }),\r\n duration,\r\n easing\r\n )\r\n }\r\n}\r\n"]}
@@ -43,7 +43,7 @@ export class PieLegend extends Rect {
43
43
  // Fixed column geometry last, so it wins over what super() applied.
44
44
  this.set({ gap: this.legend.itemGap / 3, padding: this.legend.padding });
45
45
  for (let i = 0; i < this.slices.length; i++) {
46
- const item = this.buildItem(this.slices[i]);
46
+ const item = this.buildItem(i);
47
47
  this.items.push(item);
48
48
  // The selection dim rides a wrapper rather than the entry itself, so the
49
49
  // entrance can animate the entry's own opacity without the two fighting
@@ -59,7 +59,7 @@ export class PieLegend extends Rect {
59
59
  }
60
60
  }
61
61
  /** One entry: the slice's colour swatch beside its caption. */
62
- buildItem(slice) {
62
+ buildItem(index) {
63
63
  const marker = this.legend.markerStyle;
64
64
  return new Rect({ flow: "horizontal",
65
65
  gap: 12,
@@ -71,13 +71,13 @@ export class PieLegend extends Rect {
71
71
  width: marker.height,
72
72
  height: marker.height,
73
73
  cornerRadius: marker.borderRadius,
74
- fill: slice.fill,
74
+ fill: () => this.slices[index]?.fill ?? "white",
75
75
  stroke: marker.stroke,
76
76
  shadow: marker.shadow,
77
77
  }),
78
78
  new Text({
79
79
  ...textOps.styleProps(this.legend.textStyle),
80
- text: slice.category,
80
+ text: () => this.slices[index]?.category ?? "",
81
81
  }),
82
82
  ],
83
83
  });
@@ -1 +1 @@
1
- {"version":3,"file":"pie-legend.js","sourceRoot":"","sources":["../../src/pie-chart/pie-legend.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n command,\n Rect,\n Text,\n easeOut,\n property,\n textOps,\n type CommandArgs,\n type NodeConfig,\n type RectProps,\n} from \"@motionscript/sdk\"\n\nimport {\n DEFAULT_LEGEND,\n clampUnit,\n type LegendConfig,\n} from \"../kit/shared\"\nimport { at, compose, hold, type Seekable } from \"@motionscript/sdk/component\"\nimport type { PieSlice } from \"./shared\"\n\nexport interface PieLegendProps extends RectProps {\n /** One entry per slice, in ring order. */\n slices: PieSlice[]\n /** Padding, caption style, gaps, and swatch geometry — see {@link LegendConfig}. */\n legend: LegendConfig\n /**\n * Per-slice opacity multiplier, by index — the same signal that dims the ring,\n * so an entry fades in step with the wedge it names. Missing entries read as 1.\n */\n dims: number[]\n}\n\n/** How far an entry starts to the right of its slot when it enters, in px. */\nconst SLIDE = 16\n\n/**\n * The legend of a {@link PieChart}: a **vertical-flow column** of entries, one per\n * slice, each pairing the slice's caption with its colour swatch.\n *\n * A column rather than the cartesian charts' row, because it sits *beside* the\n * ring rather than beneath the plot — a pie is as tall as it is wide, so the\n * space it leaves over is a vertical strip. It is otherwise the same entry: the\n * swatch carries the colour, the caption reads in the legend's neutral ink, and\n * both take the shared {@link LegendConfig}.\n *\n * There is no show/hide here. A pie's marks are its *rows*, and a row that\n * shouldn't be in the chart shouldn't be in the table — removing one would\n * silently re-cut every other wedge's share, which is a different chart rather\n * than the same chart with something hidden. Selection is what a pie has\n * instead, and it arrives through {@link dims}.\n */\nexport class PieLegend extends Rect {\n @property({ default: () => [] as PieSlice[] }) declare slices: PieSlice[]\n @property({ default: DEFAULT_LEGEND }) declare legend: LegendConfig\n @property({ default: () => [] as number[] }) declare dims: number[]\n\n /** One entry per slice, ordered top → bottom, for the staggered entrance. */\n private items: Rect[] = []\n\n constructor(props?: NodeConfig<PieLegend, PieLegendProps>) {\n // `flow` with the rest of the fixed geometry, and for the reason\n // {@link ChartLegend} spells out: a `Rect` given none lays its children out\n // `freeform`, so an unstated column is every caption drawn on top of the\n // last one.\n super({\n ...(props as RectProps),\n flow: \"vertical\",\n align: \"centerLeft\",\n width: \"hug\",\n height: \"hug\",\n })\n\n // Fixed column geometry last, so it wins over what super() applied.\n this.set({ gap: this.legend.itemGap / 3, padding: this.legend.padding })\n\n for (let i = 0; i < this.slices.length; i++) {\n const item = this.buildItem(this.slices[i])\n this.items.push(item)\n // The selection dim rides a wrapper rather than the entry itself, so the\n // entrance can animate the entry's own opacity without the two fighting\n // over one value — opacity multiplies down the tree, which is exactly the\n // composition wanted here.\n this.add(\n new Rect({\n width: \"hug\",\n height: \"hug\",\n flow: \"freeform\",\n opacity: () => clampUnit(this.dims[i] ?? 1),\n children: [item],\n })\n )\n }\n }\n\n /** One entry: the slice's colour swatch beside its caption. */\n private buildItem(slice: PieSlice): Rect {\n const marker = this.legend.markerStyle\n return new Rect({ flow: \"horizontal\",\n gap: 12,\n align: \"center\",\n width: \"hug\",\n height: \"hug\",\n children: [\n new Rect({\n width: marker.height,\n height: marker.height,\n cornerRadius: marker.borderRadius,\n fill: slice.fill,\n stroke: marker.stroke,\n shadow: marker.shadow,\n }),\n new Text({\n ...textOps.styleProps(this.legend.textStyle),\n text: slice.category,\n }),\n ],\n })\n }\n\n /**\n * Prime every entry at its pre-entrance state: shifted right and transparent.\n *\n * Idempotent, and separate from {@link enter} so the chart can arm its whole\n * composition before the first frame is drawn — the resting state is the\n * finished legend, so there is nothing hidden to reveal unless a scene asks.\n */\n arm(slide = SLIDE): void {\n for (const item of this.items) item.set({ opacity: 0, x: slide })\n }\n\n /**\n * Slide + fade every entry in from the right, staggered top-to-bottom.\n *\n * `duration` is the **whole** entrance: the stagger is carved out of it rather\n * than added on top, so the command takes exactly the time the timeline laid\n * out for it.\n */\n @command()\n enter(\n args: CommandArgs<Record<string, never>> & { duration: number }\n ): Seekable {\n const duration = args.duration\n const easing = args.easing ?? easeOut()\n const items = this.items\n if (items.length === 0) return hold(duration)\n this.arm()\n\n // Each gap is a fifth of an entry's own move, the rhythm the axes use;\n // solved for from the budget rather than added to it. The offsets were\n // always `i * step` — written as `wait` inside a generator they merely only\n // existed while it ran.\n const per = duration / (1 + Math.max(0, items.length - 1) / 5)\n const step = per / 5\n\n return compose(\n items.map((item, i) => at(i * step, item.to({ data: { opacity: 1, x: 0 }, duration: per, easing })))\n )\n }\n}\n"]}
1
+ {"version":3,"file":"pie-legend.js","sourceRoot":"","sources":["../../src/pie-chart/pie-legend.ts"],"names":[],"mappings":"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","sourcesContent":["import {\r\n command,\r\n Rect,\r\n Text,\r\n easeOut,\r\n property,\r\n textOps,\r\n type CommandArgs,\r\n type NodeConfig,\r\n type RectProps,\r\n} from \"@motionscript/sdk\"\r\n\r\nimport {\r\n DEFAULT_LEGEND,\r\n clampUnit,\r\n type LegendConfig,\r\n} from \"../kit/shared\"\r\nimport { at, compose, hold, type Seekable } from \"@motionscript/sdk/component\"\r\nimport type { PieSlice } from \"./shared\"\r\n\r\nexport interface PieLegendProps extends RectProps {\r\n /** One entry per slice, in ring order. */\r\n slices: PieSlice[]\r\n /** Padding, caption style, gaps, and swatch geometry — see {@link LegendConfig}. */\r\n legend: LegendConfig\r\n /**\r\n * Per-slice opacity multiplier, by index — the same signal that dims the ring,\r\n * so an entry fades in step with the wedge it names. Missing entries read as 1.\r\n */\r\n dims: number[]\r\n}\r\n\r\n/** How far an entry starts to the right of its slot when it enters, in px. */\r\nconst SLIDE = 16\r\n\r\n/**\r\n * The legend of a {@link PieChart}: a **vertical-flow column** of entries, one per\r\n * slice, each pairing the slice's caption with its colour swatch.\r\n *\r\n * A column rather than the cartesian charts' row, because it sits *beside* the\r\n * ring rather than beneath the plot — a pie is as tall as it is wide, so the\r\n * space it leaves over is a vertical strip. It is otherwise the same entry: the\r\n * swatch carries the colour, the caption reads in the legend's neutral ink, and\r\n * both take the shared {@link LegendConfig}.\r\n *\r\n * There is no show/hide here. A pie's marks are its *rows*, and a row that\r\n * shouldn't be in the chart shouldn't be in the table — removing one would\r\n * silently re-cut every other wedge's share, which is a different chart rather\r\n * than the same chart with something hidden. Selection is what a pie has\r\n * instead, and it arrives through {@link dims}.\r\n */\r\nexport class PieLegend extends Rect {\r\n @property({ default: () => [] as PieSlice[] }) declare slices: PieSlice[]\r\n @property({ default: DEFAULT_LEGEND }) declare legend: LegendConfig\r\n @property({ default: () => [] as number[] }) declare dims: number[]\r\n\r\n /** One entry per slice, ordered top → bottom, for the staggered entrance. */\r\n private items: Rect[] = []\r\n\r\n constructor(props?: NodeConfig<PieLegend, PieLegendProps>) {\r\n // `flow` with the rest of the fixed geometry, and for the reason\r\n // {@link ChartLegend} spells out: a `Rect` given none lays its children out\r\n // `freeform`, so an unstated column is every caption drawn on top of the\r\n // last one.\r\n super({\r\n ...(props as RectProps),\r\n flow: \"vertical\",\r\n align: \"centerLeft\",\r\n width: \"hug\",\r\n height: \"hug\",\r\n })\r\n\r\n // Fixed column geometry last, so it wins over what super() applied.\r\n this.set({ gap: this.legend.itemGap / 3, padding: this.legend.padding })\r\n\r\n for (let i = 0; i < this.slices.length; i++) {\r\n const item = this.buildItem(i)\r\n this.items.push(item)\r\n // The selection dim rides a wrapper rather than the entry itself, so the\r\n // entrance can animate the entry's own opacity without the two fighting\r\n // over one value — opacity multiplies down the tree, which is exactly the\r\n // composition wanted here.\r\n this.add(\r\n new Rect({\r\n width: \"hug\",\r\n height: \"hug\",\r\n flow: \"freeform\",\r\n opacity: () => clampUnit(this.dims[i] ?? 1),\r\n children: [item],\r\n })\r\n )\r\n }\r\n }\r\n\r\n /** One entry: the slice's colour swatch beside its caption. */\r\n private buildItem(index: number): Rect {\r\n const marker = this.legend.markerStyle\r\n return new Rect({ flow: \"horizontal\",\r\n gap: 12,\r\n align: \"center\",\r\n width: \"hug\",\r\n height: \"hug\",\r\n children: [\r\n new Rect({\r\n width: marker.height,\r\n height: marker.height,\r\n cornerRadius: marker.borderRadius,\r\n fill: () => this.slices[index]?.fill ?? \"white\",\r\n stroke: marker.stroke,\r\n shadow: marker.shadow,\r\n }),\r\n new Text({\r\n ...textOps.styleProps(this.legend.textStyle),\r\n text: () => this.slices[index]?.category ?? \"\",\r\n }),\r\n ],\r\n })\r\n }\r\n\r\n /**\r\n * Prime every entry at its pre-entrance state: shifted right and transparent.\r\n *\r\n * Idempotent, and separate from {@link enter} so the chart can arm its whole\r\n * composition before the first frame is drawn — the resting state is the\r\n * finished legend, so there is nothing hidden to reveal unless a scene asks.\r\n */\r\n arm(slide = SLIDE): void {\r\n for (const item of this.items) item.set({ opacity: 0, x: slide })\r\n }\r\n\r\n /**\r\n * Slide + fade every entry in from the right, staggered top-to-bottom.\r\n *\r\n * `duration` is the **whole** entrance: the stagger is carved out of it rather\r\n * than added on top, so the command takes exactly the time the timeline laid\r\n * out for it.\r\n */\r\n @command()\r\n enter(\r\n args: CommandArgs<Record<string, never>> & { duration: number }\r\n ): Seekable {\r\n const duration = args.duration\r\n const easing = args.easing ?? easeOut()\r\n const items = this.items\r\n if (items.length === 0) return hold(duration)\r\n this.arm()\r\n\r\n // Each gap is a fifth of an entry's own move, the rhythm the axes use;\r\n // solved for from the budget rather than added to it. The offsets were\r\n // always `i * step` — written as `wait` inside a generator they merely only\r\n // existed while it ran.\r\n const per = duration / (1 + Math.max(0, items.length - 1) / 5)\r\n const step = per / 5\r\n\r\n return compose(\r\n items.map((item, i) => at(i * step, item.to({ data: { opacity: 1, x: 0 }, duration: per, easing })))\r\n )\r\n }\r\n}\r\n"]}
@@ -105,11 +105,19 @@ export declare class PieRing extends Node2D<PieRingProps> {
105
105
  centerLabel: string;
106
106
  centerCaption: string;
107
107
  centerReveal: number;
108
- /** Each slice's fill, resolved once so `renderSelf` doesn't re-resolve every frame. */
109
- private fills;
108
+ /**
109
+ * Each slice's fill, resolved when it changes rather than every frame: the
110
+ * slices are bound to the chart's rows and palette, so a theme tween hands
111
+ * over new paint and `renderSelf` must see it, but a frame that moves nothing
112
+ * must not re-resolve.
113
+ */
114
+ private fillCache;
115
+ private get resolvedFills();
110
116
  constructor(props?: NodeConfig<PieRing, PieRingProps>);
111
117
  /** Build slice `arc`'s label, in whichever form the chart's placement asks for. */
112
118
  private addLabel;
119
+ /** Slice `index`'s share of the ring as its label reads it, off the live values. */
120
+ private percentText;
113
121
  /** Build the hole's summary block. */
114
122
  private addCenterLabel;
115
123
  /** The drawn slices' angular spans, re-read every frame so a value tween re-cuts them. */
@@ -230,8 +238,9 @@ export declare class PieRing extends Node2D<PieRingProps> {
230
238
  private placementFor;
231
239
  /**
232
240
  * The ring's paints live nowhere `ShapeNode` would look: one fill per slice,
233
- * resolved once into {@link fills}, plus the leader line's own stroke. Both are
234
- * passed explicitly — see {@link declarePaints}.
241
+ * resolved once into {@link fills}, the wedge outline and drop shadow of
242
+ * {@link ring}, and the leader line's own stroke. All are passed explicitly —
243
+ * see {@link declarePaints}.
235
244
  */
236
245
  declareAssets(assets: AssetScope): void;
237
246
  protected renderSelf(ctx: RenderContext2D): void;
@@ -1 +1 @@
1
- {"version":3,"file":"pie-ring.d.ts","sourceRoot":"","sources":["../../src/pie-chart/pie-ring.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pie-ring.d.ts","sourceRoot":"","sources":["../../src/pie-chart/pie-ring.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { Rect, command, Graphics2D, Node2D, PathBuilder, Text, easeOut, fillOps, property, strokeOps, textOps, } from "@motionscript/sdk";
7
+ import { Rect, command, Graphics2D, Node2D, PathBuilder, Text, easeOut, fillOps, property, shadowOps, strokeOps, textOps, } from "@motionscript/sdk";
8
8
  import { clampUnit, formatPercent } from "../kit/shared.js";
9
9
  import { DEFAULT_PIE_CHART_THEME, pieArcs, relaxLabels, ringPoint, } from "./shared.js";
10
10
  import { declarePaints } from "@motionscript/sdk/component";
@@ -149,11 +149,27 @@ function wedgePath(a0, a1, rInner, rOuter, halfGap, cx) {
149
149
  * ring whose slices all leader one way doesn't leave a bald patch on the other.
150
150
  */
151
151
  export class PieRing extends Node2D {
152
- /** Each slice's fill, resolved once so `renderSelf` doesn't re-resolve every frame. */
153
- fills = [];
152
+ /**
153
+ * Each slice's fill, resolved when it changes rather than every frame: the
154
+ * slices are bound to the chart's rows and palette, so a theme tween hands
155
+ * over new paint and `renderSelf` must see it, but a frame that moves nothing
156
+ * must not re-resolve.
157
+ */
158
+ fillCache = [];
159
+ get resolvedFills() {
160
+ const slices = this.slices;
161
+ this.fillCache.length = slices.length;
162
+ return slices.map((slice, i) => {
163
+ const cached = this.fillCache[i];
164
+ if (cached !== undefined && cached.source === slice.fill)
165
+ return cached.resolved;
166
+ const resolved = fillOps.resolve(slice.fill);
167
+ this.fillCache[i] = { source: slice.fill, resolved };
168
+ return resolved;
169
+ });
170
+ }
154
171
  constructor(props) {
155
172
  super(props);
156
- this.fills = this.slices.map((slice) => fillOps.resolve(slice.fill));
157
173
  // One label per *drawn* slice (a zero-valued slice gets no wedge, so no
158
174
  // label either), in whichever of the two forms the chart asked for. Both are
159
175
  // real Text stacked in a hug vertical flow — the difference is only what they are
@@ -173,7 +189,6 @@ export class PieRing extends Node2D {
173
189
  }
174
190
  /** Build slice `arc`'s label, in whichever form the chart's placement asks for. */
175
191
  addLabel(arc) {
176
- const slice = this.slices[arc.index];
177
192
  // Fades in with the ring's labels, then dims along with its own slice
178
193
  // whenever another slice takes the focus.
179
194
  const opacity = () => clampUnit(this.labelReveal) * this.sliceOpacity(arc.index);
@@ -189,14 +204,14 @@ export class PieRing extends Node2D {
189
204
  if (wantsPercent) {
190
205
  inside.push(new Text({
191
206
  ...textOps.styleProps(this.label.insideStyle),
192
- text: formatPercent(arc.fraction),
207
+ text: () => this.percentText(arc.index),
193
208
  fontSize: () => this.insideFont,
194
209
  }));
195
210
  }
196
211
  if (wantsCategory) {
197
212
  inside.push(new Text({
198
213
  ...textOps.styleProps(this.label.insideStyle),
199
- text: slice.category,
214
+ text: () => this.slices[arc.index]?.category ?? "",
200
215
  fontSize: () => this.labelMode === "category"
201
216
  ? this.insideFont
202
217
  : this.insideFont * INSIDE_CAPTION_RATIO,
@@ -219,14 +234,14 @@ export class PieRing extends Node2D {
219
234
  if (wantsCategory) {
220
235
  children.push(new Text({
221
236
  ...textOps.styleProps(this.label.textStyle),
222
- text: slice.category,
237
+ text: () => this.slices[arc.index]?.category ?? "",
223
238
  textAlign: side > 0 ? "left" : "right",
224
239
  }));
225
240
  }
226
241
  if (wantsPercent) {
227
242
  children.push(new Text({
228
243
  ...textOps.styleProps(this.label.percentStyle),
229
- text: formatPercent(arc.fraction),
244
+ text: () => this.percentText(arc.index),
230
245
  textAlign: side > 0 ? "left" : "right",
231
246
  }));
232
247
  }
@@ -242,6 +257,11 @@ export class PieRing extends Node2D {
242
257
  children,
243
258
  }));
244
259
  }
260
+ /** Slice `index`'s share of the ring as its label reads it, off the live values. */
261
+ percentText(index) {
262
+ const arc = this.arcs.find((a) => a.index === index);
263
+ return formatPercent(arc?.fraction ?? 0);
264
+ }
245
265
  /** Build the hole's summary block. */
246
266
  addCenterLabel() {
247
267
  const children = [];
@@ -570,14 +590,23 @@ export class PieRing extends Node2D {
570
590
  // ---- Draw --------------------------------------------------------------
571
591
  /**
572
592
  * The ring's paints live nowhere `ShapeNode` would look: one fill per slice,
573
- * resolved once into {@link fills}, plus the leader line's own stroke. Both are
574
- * passed explicitly — see {@link declarePaints}.
593
+ * resolved once into {@link fills}, the wedge outline and drop shadow of
594
+ * {@link ring}, and the leader line's own stroke. All are passed explicitly —
595
+ * see {@link declarePaints}.
575
596
  */
576
597
  declareAssets(assets) {
577
598
  super.declareAssets(assets);
578
599
  declarePaints(this, assets, {
579
- fills: this.fills,
580
- strokes: [strokeOps.resolve(this.label.leaderStroke)],
600
+ fills: [
601
+ ...this.resolvedFills,
602
+ // A shadow's paint is a fill like any other, so an image in one is
603
+ // declared with the rest.
604
+ ...shadowOps.resolve(this.ring.shadow).map((shadow) => shadow.fill),
605
+ ],
606
+ strokes: [
607
+ strokeOps.resolve(this.ring.stroke),
608
+ strokeOps.resolve(this.label.leaderStroke),
609
+ ],
581
610
  });
582
611
  }
583
612
  renderSelf(ctx) {
@@ -609,6 +638,7 @@ export class PieRing extends Node2D {
609
638
  // carries no implied extent, so a slice filled with an image or a video needs
610
639
  // this to size its decode.
611
640
  const span = half * 2;
641
+ const fills = this.resolvedFills;
612
642
  for (const arc of arcs) {
613
643
  // Clip the wedge to the growth-revealed arc; skip it until the sweep
614
644
  // reaches it, so the ring draws in as one clockwise wipe.
@@ -618,7 +648,7 @@ export class PieRing extends Node2D {
618
648
  const path = wedgePath(arc.a0, a1, hole, this.anchorRadius(arc.index), halfGap, cx);
619
649
  const g = new Graphics2D().path(path.toPathState({ centerBounds, width: span, height: span }));
620
650
  g.shadow(this.ring.shadow);
621
- g.fill(this.fills[arc.index] ?? []);
651
+ g.fill(fills[arc.index] ?? []);
622
652
  g.stroke(this.ring.stroke);
623
653
  ctx.draw(g.opacity(this.sliceOpacity(arc.index)));
624
654
  }