@motionscript/charts 0.1.0-alpha.7 → 0.1.0-alpha.8
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.
- package/CHANGELOG.md +2 -0
- package/NOTICE +15 -14
- package/dist/bar-chart/bar-chart.d.ts +2 -2
- package/dist/bar-chart/bar-chart.d.ts.map +1 -1
- package/dist/bar-chart/bar-chart.js +13 -5
- package/dist/bar-chart/bar-chart.js.map +1 -1
- package/dist/bar-chart/chart-bars.d.ts.map +1 -1
- package/dist/bar-chart/chart-bars.js +6 -7
- package/dist/bar-chart/chart-bars.js.map +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/index.js.map +3 -3
- package/dist/browser/manifest.json +1 -1
- package/dist/line-chart/line-chart.d.ts +2 -2
- package/dist/line-chart/line-chart.d.ts.map +1 -1
- package/dist/line-chart/line-chart.js +16 -7
- package/dist/line-chart/line-chart.js.map +1 -1
- package/dist/line-chart/x-scale.d.ts +3 -10
- package/dist/line-chart/x-scale.d.ts.map +1 -1
- package/dist/line-chart/x-scale.js +15 -4
- package/dist/line-chart/x-scale.js.map +1 -1
- package/dist/pie-chart/pie-chart.d.ts +2 -2
- package/dist/pie-chart/pie-chart.d.ts.map +1 -1
- package/dist/pie-chart/pie-chart.js +4 -1
- package/dist/pie-chart/pie-chart.js.map +1 -1
- package/dist/pie-chart/shared.d.ts +6 -0
- package/dist/pie-chart/shared.d.ts.map +1 -1
- package/dist/pie-chart/shared.js +8 -2
- package/dist/pie-chart/shared.js.map +1 -1
- package/package.json +5 -4
- package/src/bar-chart/bar-chart.ts +13 -5
- package/src/bar-chart/chart-bars.ts +6 -7
- package/src/line-chart/line-chart.ts +20 -7
- package/src/line-chart/x-scale.ts +18 -3
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/src/pie-chart/pie-chart.ts +4 -1
- package/src/pie-chart/shared.ts +8 -2
|
@@ -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 {\r\n Rect,\r\n Types,\r\n component2d,\r\n easeOut,\r\n type ChildView,\r\n type DataRecord,\r\n type NodeOf,\r\n // Named so this component's emitted type reaches `Node2D` through the sdk, not core.\r\n type Node2D,\r\n type Node2DProps,\r\n type Timeline,\r\n} from \"@motionscript/sdk\"\r\n\r\nimport { asParts, fresh } from \"../kit/entries\"\r\nimport { beats, type Beat } from \"../kit/motion\"\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\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 `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 runs `revealCenter` when it gets there.\r\n */\r\n showCenter?: boolean\r\n}\r\n\r\n/** What the selection reads: the rows, how they are cut, and the pops the commands move. */\r\nexport interface PieChartValues {\r\n readonly data: DataRecord[]\r\n readonly categoryField: string\r\n readonly valueField: string\r\n readonly theme: PieChartConfig\r\n readonly pops: number[]\r\n}\r\n\r\n/**\r\n * How `enter` divides its duration: the wedges sweeping in, then the labels and\r\n * the legend arriving together. Shares of the whole, so they sum to 1.\r\n */\r\nconst SWEEP_SHARE = 0.65\r\nconst LABEL_SHARE = 0.35\r\n\r\nconst SLICE_ARG = Types.number(0, { min: 0, step: 1 })\r\n\r\n/** The slices the ring and the legend draw, resolved from the rows and the palette. */\r\nfunction slicesOf(s: PieChartValues): PieSlice[] {\r\n return resolvePieSlices(s.data, s.categoryField, s.valueField, s.theme.sliceFills)\r\n}\r\n\r\n/** How far slice `index` is pushed out, in `[0, 1]`. */\r\nfunction popAt(s: PieChartValues, index: number): number {\r\n return clampUnit(s.pops[index] ?? 0)\r\n}\r\n\r\n/** Whether slice `index` is grown out past the ring's rim: past half way, so a slice mid-swell is not yet counted. */\r\nexport function isSliceSelected(s: PieChartValues, index: number): boolean {\r\n return popAt(s, index) > 0.5\r\n}\r\n\r\n/**\r\n * How far each slice is dimmed by the selection, one entry per slice: 1 while\r\n * nothing is out, and for the selected slice itself; falling toward the theme's\r\n * `dimOpacity` for the rest in proportion to how far the selection has grown.\r\n */\r\nfunction sliceDims(s: PieChartValues): number[] {\r\n let focus = 0\r\n for (let i = 0; i < s.data.length; i++) focus = Math.max(focus, popAt(s, i))\r\n const dim = s.theme.spotlight.dimOpacity\r\n if (focus <= 0) return s.data.map(() => 1)\r\n return s.data.map((_, i) => 1 - (1 - clampUnit(dim)) * focus * (1 - popAt(s, i)))\r\n}\r\n\r\ntype AnyTimeline = Timeline<any, any>\r\n\r\n/**\r\n * Every slice's pop eased from where it is to what `pop` asks for. A move that\r\n * changes nothing still takes its slot, writing nothing.\r\n */\r\nfunction popping(timeline: AnyTimeline, s: PieChartValues, pop: (index: number) => number) {\r\n const from = s.data.map((_, i) => popAt(s, i))\r\n const targets = s.data.map((_, i) => clampUnit(pop(i)))\r\n if (from.every((value, i) => value === targets[i])) return timeline.wait(timeline.duration)\r\n return timeline.animate((t) => ({ pops: from.map((value, i) => value + (targets[i] - value) * t) }))\r\n}\r\n\r\n/** The ring and the legend, found through the chart's children: the last row of them is the one it built. */\r\nfunction partsOf(children: readonly ChildView[]) {\r\n for (let i = children.length - 1; i >= 0; i--) {\r\n const row = children[i]\r\n const ring = Rect.is(row) ? row.children.find(PieRing.is) : undefined\r\n if (ring) return { ring, legend: row.children.find(PieLegend.is) ?? null }\r\n }\r\n return null\r\n}\r\n\r\n/**\r\n * A pie or donut chart, composed from two parts laid out by a horizontal-flow\r\n * {@link Rect} — the ring filling whatever the hugging legend leaves, both\r\n * centred:\r\n *\r\n * - {@link PieRing} — the wedges, each sized by its share of the total, with its\r\n * label inside its own band or pinned outside on a leader line, and the\r\n * summary in the hole.\r\n * - {@link PieLegend} — a column of swatch-and-caption rows naming each slice.\r\n *\r\n * **One mark per row, not per column**: a pie's palette is indexed by row and\r\n * there is nothing to hide, since dropping a slice would re-cut every other\r\n * wedge. `selectSlice` and its siblings are what a pie has instead — a slice\r\n * swells out of the ring and everything else dims behind it.\r\n *\r\n * **Resting state is the finished chart**; `enter` arms every part on its first\r\n * frame. The one exception is the hole's summary, which a scene can hold back\r\n * with `showCenter: false`, because it is usually the chart's *conclusion*.\r\n */\r\nexport const PieChart = component2d({\r\n key: \"pieChart\",\r\n state: {\r\n data: fresh<DataRecord[]>(() => []),\r\n categoryField: \"\",\r\n valueField: \"\",\r\n theme: Types.custom<PieChartTheme, PieChartConfig>(DEFAULT_PIE_CHART_THEME, { resolve: resolvePieChartTheme, lerp: lerpPieChartTheme }),\r\n labelMode: Types.custom<PieLabelMode>(\"percent\"),\r\n labelPlacement: Types.custom<PieLabelPlacement>(\"leader\"),\r\n gap: 40,\r\n centerLabel: \"\",\r\n centerCaption: \"\",\r\n /**\r\n * How far each slice is pushed out of the ring, `0`–`1` per index. Held here\r\n * rather than on the ring because two parts read it: the wedges grow out by\r\n * it, and the legend entries dim by it. Seeded flush, one to a row.\r\n */\r\n pops: fresh<number[]>(() => []),\r\n /** Read as the chart is built: the legend is a part the chart builds or doesn't. */\r\n showLegend: Types.boolean(true, { animatable: false }),\r\n /** Read as the chart is built: whether the hole's summary is drawn at rest. */\r\n showCenter: Types.boolean(true, { animatable: false }),\r\n },\r\n defaults: {\r\n // Its parts fill it; a node made of them would hug them.\r\n width: \"fill\",\r\n height: \"fill\",\r\n pops: (state) => slicesOf(state).map(() => 0),\r\n },\r\n // The slice *structure* (one label and one legend row per row) is read once;\r\n // the slices themselves, their paint and the selection are bound, so a theme\r\n // tween re-paints and a `set` of `data` re-cuts the wedges.\r\n children: (state) => {\r\n const theme = () => state.theme\r\n const slices = () => slicesOf(state)\r\n const parts: Node2D[] = [\r\n new PieRing({\r\n width: \"fill\",\r\n height: \"fill\",\r\n slices,\r\n ring: () => theme().ring,\r\n label: () => theme().label,\r\n centerStyle: () => theme().center,\r\n labelMode: state.labelMode,\r\n labelPlacement: state.labelPlacement,\r\n pops: () => state.pops,\r\n dimOpacity: () => theme().spotlight.dimOpacity,\r\n centerLabel: state.centerLabel,\r\n centerCaption: state.centerCaption,\r\n centerReveal: state.showCenter === false ? 0 : 1,\r\n }) as unknown as Node2D,\r\n ]\r\n if (state.showLegend !== false) {\r\n parts.push(\r\n new PieLegend({\r\n slices,\r\n legend: () => theme().legend,\r\n dims: () => sliceDims(state),\r\n // The column's spacing is the theme's as the chart was built, like the\r\n // entries' type and chips.\r\n gap: state.theme.legend.itemGap / 3,\r\n padding: state.theme.legend.padding,\r\n }) as unknown as Node2D\r\n )\r\n }\r\n const row = new Rect({ flow: \"horizontal\", width: \"fill\", height: \"fill\", gap: state.gap, align: \"center\", children: parts })\r\n return asParts([...(state.children as unknown as Node2D[]), row as unknown as Node2D])\r\n },\r\n commands: {\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 * Its first frame arms the wedges, the labels and the legend; the hole's\r\n * summary keeps its own signal. `duration` is the **whole** entrance.\r\n */\r\n enter: {\r\n args: {},\r\n easing: easeOut(),\r\n run: (timeline, _state, children) => {\r\n const parts = partsOf(children)\r\n if (!parts) return timeline.wait(timeline.duration)\r\n const { ring, legend } = parts\r\n const sweep = timeline.duration * SWEEP_SHARE\r\n const labels = timeline.duration * LABEL_SHARE\r\n const at0 = (piece: Beat[\"piece\"]): Beat => ({ at: 0, duration: 0, piece })\r\n return beats(timeline, [\r\n at0(ring.to({ growth: 0, labelReveal: 0 })),\r\n ...(legend ? [at0(legend.run(\"arm\"))] : []),\r\n { at: 0, duration: sweep, piece: ring.run(\"sweep\") },\r\n { at: sweep, duration: labels, piece: ring.run(\"revealLabels\") },\r\n ...(legend ? [{ at: sweep, duration: labels, piece: legend.run(\"enter\") }] : []),\r\n ])\r\n },\r\n },\r\n /**\r\n * Fade the hole's summary up. Held out of `enter` on purpose: what sits in a\r\n * donut's hole is usually the chart's conclusion, so the scene lands it on\r\n * its own beat. Pair it with `showCenter: false`.\r\n */\r\n revealCenter: {\r\n args: {},\r\n easing: easeOut(),\r\n run: (timeline, _state, children) => partsOf(children)?.ring.run(\"revealCenter\") ?? timeline.wait(timeline.duration),\r\n },\r\n /** Grow slice `index` out past the ring's rim and dim the rest, leaving any other selected slice where it is. */\r\n selectSlice: {\r\n args: { sliceIndex: SLICE_ARG },\r\n easing: easeOut(),\r\n run: (timeline, state) => {\r\n const index = timeline.args.sliceIndex\r\n return popping(timeline, state, (i) => (i === index ? 1 : popAt(state, i)))\r\n },\r\n },\r\n /** Settle slice `index` back flush with the ring. */\r\n deselectSlice: {\r\n args: { sliceIndex: SLICE_ARG },\r\n easing: easeOut(),\r\n run: (timeline, state) => {\r\n const index = timeline.args.sliceIndex\r\n return popping(timeline, state, (i) => (i === index ? 0 : popAt(state, i)))\r\n },\r\n },\r\n /** Move the focus to slice `index` alone, settling whatever else was out. */\r\n selectOnlySlice: {\r\n args: { sliceIndex: SLICE_ARG },\r\n easing: easeOut(),\r\n run: (timeline, state) => {\r\n const index = timeline.args.sliceIndex\r\n return popping(timeline, state, (i) => (i === index ? 1 : 0))\r\n },\r\n },\r\n /** Settle every slice flush and bring the whole chart back to full. */\r\n clearSelection: {\r\n args: {},\r\n easing: easeOut(),\r\n run: (timeline, state) => popping(timeline, state, () => 0),\r\n },\r\n },\r\n})\r\n\r\nexport type PieChart = NodeOf<typeof PieChart>\r\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 Types,\r\n component2d,\r\n easeOut,\r\n type ChildView,\r\n type DataRecord,\r\n type NodeOf,\r\n // Named so this component's emitted type reaches `Node2D` through the sdk, not core.\r\n type Node2D,\r\n type Node2DProps,\r\n type Timeline,\r\n} from \"@motionscript/sdk\"\r\n\r\nimport { asParts, fresh } from \"../kit/entries\"\r\nimport { beats, type Beat } from \"../kit/motion\"\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\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 `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 runs `revealCenter` when it gets there.\r\n */\r\n showCenter?: boolean\r\n}\r\n\r\n/** What the selection reads: the rows, how they are cut, and the pops the commands move. */\r\nexport interface PieChartValues {\r\n readonly data: DataRecord[]\r\n readonly categoryField: string\r\n readonly valueField: string\r\n readonly theme: PieChartConfig\r\n readonly pops: number[]\r\n}\r\n\r\n/**\r\n * How `enter` divides its duration: the wedges sweeping in, then the labels and\r\n * the legend arriving together. Shares of the whole, so they sum to 1.\r\n */\r\nconst SWEEP_SHARE = 0.65\r\nconst LABEL_SHARE = 0.35\r\n\r\nconst SLICE_ARG = Types.number(0, { min: 0, step: 1 })\r\n\r\n/** The slices the ring and the legend draw, resolved from the rows and the palette. */\r\nfunction slicesOf(s: PieChartValues): PieSlice[] {\r\n return resolvePieSlices(s.data, s.categoryField, s.valueField, s.theme.sliceFills)\r\n}\r\n\r\n/** How far slice `index` is pushed out, in `[0, 1]`. */\r\nfunction popAt(s: PieChartValues, index: number): number {\r\n return clampUnit(s.pops[index] ?? 0)\r\n}\r\n\r\n/** Whether slice `index` is grown out past the ring's rim: past half way, so a slice mid-swell is not yet counted. */\r\nexport function isSliceSelected(s: PieChartValues, index: number): boolean {\r\n return popAt(s, index) > 0.5\r\n}\r\n\r\n/**\r\n * How far each slice is dimmed by the selection, one entry per slice: 1 while\r\n * nothing is out, and for the selected slice itself; falling toward the theme's\r\n * `dimOpacity` for the rest in proportion to how far the selection has grown.\r\n */\r\nfunction sliceDims(s: PieChartValues): number[] {\r\n let focus = 0\r\n for (let i = 0; i < s.data.length; i++) focus = Math.max(focus, popAt(s, i))\r\n const dim = s.theme.spotlight.dimOpacity\r\n if (focus <= 0) return s.data.map(() => 1)\r\n return s.data.map((_, i) => 1 - (1 - clampUnit(dim)) * focus * (1 - popAt(s, i)))\r\n}\r\n\r\ntype AnyTimeline = Timeline<any, any>\r\n\r\n/**\r\n * Every slice's pop eased from where it is to what `pop` asks for. A move that\r\n * changes nothing still takes its slot, writing nothing.\r\n */\r\nfunction popping(timeline: AnyTimeline, s: PieChartValues, pop: (index: number) => number) {\r\n const from = s.data.map((_, i) => popAt(s, i))\r\n const targets = s.data.map((_, i) => clampUnit(pop(i)))\r\n if (from.every((value, i) => value === targets[i])) return timeline.wait(timeline.duration)\r\n return timeline.animate((t) => ({ pops: from.map((value, i) => value + (targets[i] - value) * t) }))\r\n}\r\n\r\n/** The ring and the legend, found through the chart's children: the last row of them is the one it built. */\r\nfunction partsOf(children: readonly ChildView[]) {\r\n for (let i = children.length - 1; i >= 0; i--) {\r\n const row = children[i]\r\n const ring = Rect.is(row) ? row.children.find(PieRing.is) : undefined\r\n if (ring) return { ring, legend: row.children.find(PieLegend.is) ?? null }\r\n }\r\n return null\r\n}\r\n\r\n/**\r\n * A pie or donut chart, composed from two parts laid out by a horizontal-flow\r\n * {@link Rect} — the ring filling whatever the hugging legend leaves, both\r\n * centred:\r\n *\r\n * - {@link PieRing} — the wedges, each sized by its share of the total, with its\r\n * label inside its own band or pinned outside on a leader line, and the\r\n * summary in the hole.\r\n * - {@link PieLegend} — a column of swatch-and-caption rows naming each slice.\r\n *\r\n * **One mark per row, not per column**: a pie's palette is indexed by row and\r\n * there is nothing to hide, since dropping a slice would re-cut every other\r\n * wedge. `selectSlice` and its siblings are what a pie has instead — a slice\r\n * swells out of the ring and everything else dims behind it.\r\n *\r\n * **Resting state is the finished chart**; `enter` arms every part on its first\r\n * frame. The one exception is the hole's summary, which a scene can hold back\r\n * with `showCenter: false`, because it is usually the chart's *conclusion*.\r\n */\r\nexport const PieChart = component2d({\r\n key: \"pieChart\",\r\n state: {\r\n data: fresh<DataRecord[]>(() => []),\r\n categoryField: \"\",\r\n valueField: \"\",\r\n theme: Types.custom<PieChartTheme, PieChartConfig>(DEFAULT_PIE_CHART_THEME, { resolve: resolvePieChartTheme, lerp: lerpPieChartTheme }),\r\n labelMode: Types.custom<PieLabelMode>(\"percent\"),\r\n labelPlacement: Types.custom<PieLabelPlacement>(\"leader\"),\r\n gap: 40,\r\n centerLabel: \"\",\r\n centerCaption: \"\",\r\n /**\r\n * How far each slice is pushed out of the ring, `0`–`1` per index. Held here\r\n * rather than on the ring because two parts read it: the wedges grow out by\r\n * it, and the legend entries dim by it. Seeded flush, one to a row.\r\n */\r\n pops: fresh<number[]>(() => []),\r\n /** Read as the chart is built: the legend is a part the chart builds or doesn't. */\r\n showLegend: Types.boolean(true, { animatable: false }),\r\n /** Read as the chart is built: whether the hole's summary is drawn at rest. */\r\n showCenter: Types.boolean(true, { animatable: false }),\r\n },\r\n defaults: {\r\n // Its parts fill it; a node made of them would hug them.\r\n width: \"fill\",\r\n height: \"fill\",\r\n pops: (state) => slicesOf(state).map(() => 0),\r\n },\r\n allowedChildren: [],\r\n // The slice *structure* (one label and one legend row per row) is read once;\r\n // the slices themselves, their paint and the selection are bound, so a theme\r\n // tween re-paints and a `set` of `data` re-cuts the wedges.\r\n children: (state) => {\r\n const theme = () => state.theme\r\n const slices = () => slicesOf(state)\r\n const parts: Node2D[] = [\r\n new PieRing({\r\n width: \"fill\",\r\n height: \"fill\",\r\n slices,\r\n ring: () => theme().ring,\r\n label: () => theme().label,\r\n centerStyle: () => theme().center,\r\n labelMode: state.labelMode,\r\n labelPlacement: state.labelPlacement,\r\n pops: () => state.pops,\r\n dimOpacity: () => theme().spotlight.dimOpacity,\r\n centerLabel: state.centerLabel,\r\n centerCaption: state.centerCaption,\r\n centerReveal: state.showCenter === false ? 0 : 1,\r\n }) as unknown as Node2D,\r\n ]\r\n if (state.showLegend !== false) {\r\n parts.push(\r\n new PieLegend({\r\n slices,\r\n legend: () => theme().legend,\r\n dims: () => sliceDims(state),\r\n // The column's spacing is the theme's as the chart was built, like the\r\n // entries' type and chips.\r\n gap: state.theme.legend.itemGap / 3,\r\n padding: state.theme.legend.padding,\r\n }) as unknown as Node2D\r\n )\r\n }\r\n const row = new Rect({ flow: \"horizontal\", width: \"fill\", height: \"fill\", gap: state.gap, align: \"center\", children: parts })\r\n return asParts([row as unknown as Node2D])\r\n },\r\n commands: {\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 * Its first frame arms the wedges, the labels and the legend; the hole's\r\n * summary keeps its own signal. `duration` is the **whole** entrance.\r\n */\r\n enter: {\r\n args: {},\r\n // Held from the chart's arrival, so it never shows finished ahead of its draw.\r\n entrance: true,\r\n easing: easeOut(),\r\n run: (timeline, _state, children) => {\r\n const parts = partsOf(children)\r\n if (!parts) return timeline.wait(timeline.duration)\r\n const { ring, legend } = parts\r\n const sweep = timeline.duration * SWEEP_SHARE\r\n const labels = timeline.duration * LABEL_SHARE\r\n const at0 = (piece: Beat[\"piece\"]): Beat => ({ at: 0, duration: 0, piece })\r\n return beats(timeline, [\r\n at0(ring.to({ growth: 0, labelReveal: 0 })),\r\n ...(legend ? [at0(legend.run(\"arm\"))] : []),\r\n { at: 0, duration: sweep, piece: ring.run(\"sweep\") },\r\n { at: sweep, duration: labels, piece: ring.run(\"revealLabels\") },\r\n ...(legend ? [{ at: sweep, duration: labels, piece: legend.run(\"enter\") }] : []),\r\n ])\r\n },\r\n },\r\n /**\r\n * Fade the hole's summary up. Held out of `enter` on purpose: what sits in a\r\n * donut's hole is usually the chart's conclusion, so the scene lands it on\r\n * its own beat. Pair it with `showCenter: false`.\r\n */\r\n revealCenter: {\r\n args: {},\r\n easing: easeOut(),\r\n run: (timeline, _state, children) => partsOf(children)?.ring.run(\"revealCenter\") ?? timeline.wait(timeline.duration),\r\n },\r\n /** Grow slice `index` out past the ring's rim and dim the rest, leaving any other selected slice where it is. */\r\n selectSlice: {\r\n args: { sliceIndex: SLICE_ARG },\r\n easing: easeOut(),\r\n run: (timeline, state) => {\r\n const index = timeline.args.sliceIndex\r\n return popping(timeline, state, (i) => (i === index ? 1 : popAt(state, i)))\r\n },\r\n },\r\n /** Settle slice `index` back flush with the ring. */\r\n deselectSlice: {\r\n args: { sliceIndex: SLICE_ARG },\r\n easing: easeOut(),\r\n run: (timeline, state) => {\r\n const index = timeline.args.sliceIndex\r\n return popping(timeline, state, (i) => (i === index ? 0 : popAt(state, i)))\r\n },\r\n },\r\n /** Move the focus to slice `index` alone, settling whatever else was out. */\r\n selectOnlySlice: {\r\n args: { sliceIndex: SLICE_ARG },\r\n easing: easeOut(),\r\n run: (timeline, state) => {\r\n const index = timeline.args.sliceIndex\r\n return popping(timeline, state, (i) => (i === index ? 1 : 0))\r\n },\r\n },\r\n /** Settle every slice flush and bring the whole chart back to full. */\r\n clearSelection: {\r\n args: {},\r\n easing: easeOut(),\r\n run: (timeline, state) => popping(timeline, state, () => 0),\r\n },\r\n },\r\n})\r\n\r\nexport type PieChart = NodeOf<typeof PieChart>\r\n"]}
|
|
@@ -196,6 +196,12 @@ export type PieChartTheme = {
|
|
|
196
196
|
legend?: LegendTheme;
|
|
197
197
|
spotlight?: Partial<PieChartConfig["spotlight"]>;
|
|
198
198
|
};
|
|
199
|
+
/**
|
|
200
|
+
* Opacity the other slices fade to under a selection. Fainter than the line and
|
|
201
|
+
* bar charts' `SPOTLIGHT_DIM`: a ring's slices touch, so at that a selected
|
|
202
|
+
* slice barely separates from its neighbours.
|
|
203
|
+
*/
|
|
204
|
+
export declare const PIE_SPOTLIGHT_DIM = 0.1;
|
|
199
205
|
/** The pie chart's out-of-the-box look. */
|
|
200
206
|
export declare const DEFAULT_PIE_CHART_THEME: PieChartConfig;
|
|
201
207
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/pie-chart/shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAKL,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,MAAM,EAEX,KAAK,MAAM,EAEX,KAAK,SAAS,EACd,KAAK,OAAO,EACb,MAAM,mBAAmB,CAAA;AAE1B,OAAO,
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/pie-chart/shared.ts"],"names":[],"mappings":"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"}
|
package/dist/pie-chart/shared.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* pulling the node classes in.
|
|
15
15
|
*/
|
|
16
16
|
import { lerpNumber, shadowOps, strokeOps, textOps, } from "@motionscript/sdk";
|
|
17
|
-
import { CHART_PALETTE, DEFAULT_LEGEND, DEFAULT_SERIES_FILLS,
|
|
17
|
+
import { CHART_PALETTE, DEFAULT_LEGEND, DEFAULT_SERIES_FILLS, lerpLegendConfig, lerpSeriesFills, num, resolveLegendConfig, resolveSeriesFills, text, } from "../kit/shared.js";
|
|
18
18
|
/**
|
|
19
19
|
* Resolve tabular rows into slices: one per row, captioned by the `category`
|
|
20
20
|
* column, sized by the `value` column, painted from `palette` by row order
|
|
@@ -115,6 +115,12 @@ const LABEL_TEXT_STYLE = {
|
|
|
115
115
|
textAlign: "center",
|
|
116
116
|
fill: CHART_PALETTE.markerColor,
|
|
117
117
|
};
|
|
118
|
+
/**
|
|
119
|
+
* Opacity the other slices fade to under a selection. Fainter than the line and
|
|
120
|
+
* bar charts' `SPOTLIGHT_DIM`: a ring's slices touch, so at that a selected
|
|
121
|
+
* slice barely separates from its neighbours.
|
|
122
|
+
*/
|
|
123
|
+
export const PIE_SPOTLIGHT_DIM = 0.1;
|
|
118
124
|
/** The pie chart's out-of-the-box look. */
|
|
119
125
|
export const DEFAULT_PIE_CHART_THEME = {
|
|
120
126
|
sliceFills: DEFAULT_SERIES_FILLS,
|
|
@@ -152,7 +158,7 @@ export const DEFAULT_PIE_CHART_THEME = {
|
|
|
152
158
|
},
|
|
153
159
|
},
|
|
154
160
|
legend: DEFAULT_LEGEND,
|
|
155
|
-
spotlight: { dimOpacity:
|
|
161
|
+
spotlight: { dimOpacity: PIE_SPOTLIGHT_DIM },
|
|
156
162
|
};
|
|
157
163
|
function resolveRingConfig(ring, previous) {
|
|
158
164
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/pie-chart/shared.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * Types and pure helpers specific to the {@link PieChart} family: how rows\r\n * become slices, how slices become arcs on a ring, where the labels around it\r\n * end up, and the theme all of that is drawn from.\r\n *\r\n * The one chart in the family that draws **one mark per row** rather than one\r\n * per column, which is why almost nothing about its geometry is shared: there is\r\n * no scale, no axis and no baseline, only a circle divided by share. What it\r\n * *does* share — the legend, the theme leaf resolvers, the palette, the\r\n * percentage format — comes from `../kit/shared.ts`.\r\n *\r\n * Kept renderer-agnostic (no `Node` imports) so a host's inspector mapping\r\n * can build a partial {@link PieChartTheme} without\r\n * pulling the node classes in.\r\n */\r\n\r\nimport {\r\n lerpNumber,\r\n shadowOps,\r\n strokeOps,\r\n textOps,\r\n type DataRecord,\r\n type Fill,\r\n type Shadow,\r\n type ShadowResolved,\r\n type Stroke,\r\n type StrokeResolved,\r\n type TextStyle,\r\n type Vector2,\r\n} from \"@motionscript/sdk\"\r\n\r\nimport {\r\n CHART_PALETTE,\r\n DEFAULT_LEGEND,\r\n DEFAULT_SERIES_FILLS,\r\n SPOTLIGHT_DIM,\r\n lerpLegendConfig,\r\n lerpSeriesFills,\r\n num,\r\n resolveLegendConfig,\r\n resolveSeriesFills,\r\n text,\r\n type LegendConfig,\r\n type LegendTheme,\r\n} from \"../kit/shared\"\r\n\r\n/**\r\n * One **resolved** slice of a {@link PieChart}: its caption, its magnitude, and\r\n * the paint its wedge and legend swatch take.\r\n *\r\n * This is what the ring and the legend consume, not what a scene authors: the\r\n * chart takes tabular `data` plus two field names and resolves it into these, so\r\n * the parts stay unaware of where a slice's caption, magnitude and paint came\r\n * from.\r\n */\r\nexport interface PieSlice {\r\n /** Legend caption and label text for this slice. */\r\n category: string\r\n /** This slice's magnitude. Its wedge covers `value / (sum of all values)`. */\r\n value: number\r\n /** Wedge and swatch paint. */\r\n fill: Fill\r\n}\r\n\r\n/**\r\n * Resolve tabular rows into slices: one per row, captioned by the `category`\r\n * column, sized by the `value` column, painted from `palette` by row order\r\n * (cycling when the palette is shorter than the data).\r\n *\r\n * Paint comes from the palette rather than from the rows because a pie draws one\r\n * mark per *row* — unlike the line and bar charts, where a colour belongs to a\r\n * series (a whole column) and rides on the series entry instead. Keeping it out\r\n * of the rows is what lets a pie be fed straight from a CSV of plain numbers.\r\n */\r\nexport function resolvePieSlices(\r\n data: DataRecord[],\r\n category: string,\r\n value: string,\r\n palette: Fill[]\r\n): PieSlice[] {\r\n return data.map((row, i) => ({\r\n category: text(row, category),\r\n value: num(row, value),\r\n fill:\r\n palette.length > 0 ? palette[i % palette.length] : (\"white\" as Fill),\r\n }))\r\n}\r\n\r\n/** What a {@link PieChart}'s labels say. */\r\nexport type PieLabelMode =\r\n /**\r\n * The category and its percentage, two lines. Their order flips with the\r\n * placement: a **leader** label reads category over percentage, an **inside**\r\n * label reads the percentage big over a small category caption — inside a\r\n * wedge the reader already knows which slice they're on, so the magnitude is\r\n * the thing being read.\r\n */\r\n | \"both\"\r\n /** The category only. */\r\n | \"category\"\r\n /** The percentage only — the pick when a legend already names the categories. */\r\n | \"percent\"\r\n /** No labels and no leader lines at all. */\r\n | \"none\"\r\n\r\n/**\r\n * Where a slice's label sits.\r\n *\r\n * `\"inside\"` drops the leader line entirely and centres the label in the wedge's\r\n * own band, which costs no gutter — so the ring draws bigger — and costs the eye\r\n * no round trip. `\"leader\"` is the classic treatment, and what a sliver too\r\n * narrow to hold text needs.\r\n */\r\nexport type PieLabelPlacement = \"inside\" | \"leader\" | \"none\"\r\n\r\n/** Degrees → radians. */\r\nexport const DEG = Math.PI / 180\r\n\r\n/** A slice's plotted value: negatives and non-finite values count as 0. */\r\nexport function sliceValue(slice: PieSlice): number {\r\n return slice.value > 0 && Number.isFinite(slice.value) ? slice.value : 0\r\n}\r\n\r\n/** Sum of every slice's plotted value — the denominator every share is taken against. */\r\nexport function sliceTotal(slices: PieSlice[]): number {\r\n let sum = 0\r\n for (const slice of slices) sum += sliceValue(slice)\r\n return sum\r\n}\r\n\r\n/**\r\n * The point at radius `r` and angle `a` on a ring centred on the origin, in the\r\n * node's centred, **y-up** space. Angles run **clockwise from 12 o'clock**:\r\n * `a = 0` points straight up (+y), and increasing `a` sweeps toward +x then −y.\r\n */\r\nexport function ringPoint(r: number, a: number): Vector2 {\r\n return { x: r * Math.sin(a), y: r * Math.cos(a) }\r\n}\r\n\r\n/** One slice's resolved angular span on the ring. */\r\nexport interface PieArc {\r\n /** Index of the slice in the source list. */\r\n index: number\r\n /** Start angle, radians clockwise from 12 o'clock. */\r\n a0: number\r\n /** End angle, radians clockwise from 12 o'clock. */\r\n a1: number\r\n /** Bisector angle — where this slice's label and pop-out point. */\r\n mid: number\r\n /** This slice's share of the total, in `[0, 1]`. */\r\n fraction: number\r\n}\r\n\r\n/**\r\n * Resolve slices into per-slice arcs: each covers exactly its share of the\r\n * circle, walking clockwise from `startDegrees` (0 = 12 o'clock), with the arcs\r\n * abutting. Zero/negative slices are skipped entirely; an all-zero list yields\r\n * no arcs.\r\n *\r\n * Arcs are the *undivided* shares — the gap between neighbours is not carved out\r\n * here, because a fixed angular gap reads as a wedge: wide at the rim, pinched\r\n * shut at the hole. It's applied when the wedge is drawn instead, as a\r\n * constant-width inset off both ends of every arc, so a slice still covers its\r\n * true percentage of the circle.\r\n */\r\nexport function pieArcs(slices: PieSlice[], startDegrees: number): PieArc[] {\r\n const total = sliceTotal(slices)\r\n if (total <= 0) return []\r\n\r\n const arcs: PieArc[] = []\r\n let cursor = startDegrees * DEG\r\n slices.forEach((slice, index) => {\r\n const value = sliceValue(slice)\r\n if (value <= 0) return\r\n const fraction = value / total\r\n const a0 = cursor\r\n const a1 = a0 + fraction * Math.PI * 2\r\n cursor = a1\r\n arcs.push({ index, a0, a1, mid: (a0 + a1) / 2, fraction })\r\n })\r\n return arcs\r\n}\r\n\r\n/** A label competing for vertical space in one of the two label columns. */\r\nexport interface LabelSlot {\r\n /** Where the label would sit if nothing else were in the way. */\r\n ideal: number\r\n /** Where it ends up once the column has been de-conflicted. Written by {@link relaxLabels}. */\r\n y: number\r\n}\r\n\r\n/**\r\n * Spread one column of labels so no two overlap, keeping each as close to its\r\n * `ideal` y as possible and the whole column inside `[bottom, top]`.\r\n *\r\n * Two passes, the standard treatment. Walking top-to-bottom, each label takes\r\n * the highest slot at or below its ideal that still clears the label above it by\r\n * `minGap`; if that pushes the tail past `bottom`, a reverse pass lifts it back\r\n * up. When there are more labels than the column can hold, the reverse pass wins\r\n * and the crowding is shared evenly rather than dumped off the bottom edge.\r\n *\r\n * Mutates each slot's `y` and returns `slots` re-ordered top-to-bottom.\r\n */\r\nexport function relaxLabels<T extends LabelSlot>(\r\n slots: T[],\r\n minGap: number,\r\n top: number,\r\n bottom: number\r\n): T[] {\r\n slots.sort((a, b) => b.ideal - a.ideal)\r\n\r\n let ceiling = top\r\n for (const slot of slots) {\r\n slot.y = Math.min(slot.ideal, ceiling)\r\n ceiling = slot.y - minGap\r\n }\r\n\r\n let floor = bottom\r\n for (let i = slots.length - 1; i >= 0; i--) {\r\n slots[i].y = Math.max(slots[i].y, floor)\r\n floor = slots[i].y + minGap\r\n }\r\n\r\n return slots\r\n}\r\n\r\n// ---- Theme ---------------------------------------------------------------\r\n\r\n/** The ring's geometry and paint — everything about the wedges but their colour. */\r\nexport interface RingConfig {\r\n /**\r\n * Width of the channel between adjacent slices, in px, held constant all the\r\n * way across the band — the same clearance at the hole as at the rim. (An\r\n * angular gap would splay open toward the rim instead.)\r\n */\r\n gap: number\r\n /**\r\n * Radius of the hole punched through the middle. Below 1 it reads as a\r\n * fraction of the ring's outer radius (`0.6` leaves a band 40% of the radius\r\n * thick); 1 and up as an explicit pixel radius; `0` fills the middle in,\r\n * drawing solid pie wedges.\r\n */\r\n holeRadius: number\r\n /** Angle in degrees where the first slice begins, clockwise from 12 o'clock. */\r\n startAngle: number\r\n /**\r\n * How far out a selected slice's rim reaches, as a **multiple of the ring's\r\n * outer radius**. Being relative, the emphasis scales with the chart instead\r\n * of shrinking on a big one and swamping a small one. Clamped to `[1, 1.6]`.\r\n */\r\n selectedGrow: number\r\n /** Optional outline on every wedge. */\r\n stroke: Stroke\r\n /** Optional drop shadow on every wedge. */\r\n shadow: Shadow\r\n}\r\n\r\n/** How the ring's labels are set, and the leader lines that point at them. */\r\nexport interface LabelConfig {\r\n /** The category caption on a label. */\r\n textStyle: TextStyle\r\n /** The percentage on a label. */\r\n percentStyle: TextStyle\r\n /**\r\n * Ink for a label set **inside** its wedge, both lines. Separate because an\r\n * inside label sits on the wedge's own paint rather than on the background, so\r\n * it usually wants the opposite end of the value range from everything else.\r\n */\r\n insideStyle: TextStyle\r\n /** The leader line running from a wedge out to its label. */\r\n leaderStroke: Stroke\r\n}\r\n\r\n/** The summary set in the ring's hole. */\r\nexport interface CenterConfig {\r\n labelStyle: TextStyle\r\n captionStyle: TextStyle\r\n}\r\n\r\n/** A {@link PieChart}'s full, resolved visual theme. */\r\nexport interface PieChartConfig {\r\n /** Wedge and swatch paint, cycled by **row** index (`i % length`). */\r\n sliceFills: Fill[]\r\n ring: RingConfig\r\n label: LabelConfig\r\n center: CenterConfig\r\n legend: LegendConfig\r\n /** How far everything outside the selection fades while any slice is selected. */\r\n spotlight: { dimOpacity: number }\r\n}\r\n\r\n/** Author-facing partial theme — every branch optional and deep-merged. */\r\nexport type PieChartTheme = {\r\n sliceFills?: Fill[]\r\n ring?: Partial<RingConfig>\r\n label?: Partial<LabelConfig>\r\n center?: Partial<CenterConfig>\r\n legend?: LegendTheme\r\n spotlight?: Partial<PieChartConfig[\"spotlight\"]>\r\n}\r\n\r\nconst LABEL_TEXT_STYLE: TextStyle = {\r\n fontSize: 26,\r\n fontWeight: 600,\r\n textAlign: \"center\",\r\n fill: CHART_PALETTE.markerColor,\r\n}\r\n\r\n/** The pie chart's out-of-the-box look. */\r\nexport const DEFAULT_PIE_CHART_THEME: PieChartConfig = {\r\n sliceFills: DEFAULT_SERIES_FILLS,\r\n ring: {\r\n gap: 6,\r\n // A donut rather than a solid pie by default: the hole is the largest\r\n // uninterrupted area the chart owns and the one place a conclusion can be\r\n // set. `0` here would give that away before anyone had asked for it.\r\n holeRadius: 0.6,\r\n startAngle: 0,\r\n selectedGrow: 1.08,\r\n stroke: [],\r\n shadow: [],\r\n },\r\n label: {\r\n textStyle: LABEL_TEXT_STYLE,\r\n percentStyle: { ...LABEL_TEXT_STYLE, fontWeight: 700 },\r\n // Inside labels sit on the wedge's paint, which is a mid-tone from the\r\n // palette — so they lead with the panel colour rather than the axis ink.\r\n insideStyle: { ...LABEL_TEXT_STYLE, fontWeight: 800, fill: \"#15130e\" },\r\n leaderStroke: { weight: 2, fill: CHART_PALETTE.markerColor },\r\n },\r\n center: {\r\n labelStyle: {\r\n fontSize: 46,\r\n fontWeight: 800,\r\n textAlign: \"center\",\r\n fill: CHART_PALETTE.markerColor,\r\n },\r\n captionStyle: {\r\n fontSize: 22,\r\n fontWeight: 500,\r\n textAlign: \"center\",\r\n fill: CHART_PALETTE.markerColor,\r\n },\r\n },\r\n legend: DEFAULT_LEGEND,\r\n spotlight: { dimOpacity: SPOTLIGHT_DIM },\r\n}\r\n\r\nfunction resolveRingConfig(\r\n ring: Partial<RingConfig> | undefined,\r\n previous: RingConfig\r\n): RingConfig {\r\n return {\r\n gap: ring?.gap ?? previous.gap,\r\n holeRadius: ring?.holeRadius ?? previous.holeRadius,\r\n startAngle: ring?.startAngle ?? previous.startAngle,\r\n selectedGrow: ring?.selectedGrow ?? previous.selectedGrow,\r\n stroke: strokeOps.resolve(ring?.stroke ?? previous.stroke),\r\n shadow: shadowOps.resolve(ring?.shadow ?? previous.shadow),\r\n }\r\n}\r\n\r\nfunction lerpRingConfig(\r\n from: RingConfig,\r\n to: RingConfig,\r\n t: number\r\n): RingConfig {\r\n return {\r\n gap: lerpNumber(from.gap, to.gap, t),\r\n holeRadius: lerpNumber(from.holeRadius, to.holeRadius, t),\r\n startAngle: lerpNumber(from.startAngle, to.startAngle, t),\r\n selectedGrow: lerpNumber(from.selectedGrow, to.selectedGrow, t),\r\n stroke: strokeOps.lerp(\r\n from.stroke as StrokeResolved[],\r\n to.stroke as StrokeResolved[],\r\n t\r\n ),\r\n shadow: shadowOps.lerp(\r\n from.shadow as ShadowResolved[],\r\n to.shadow as ShadowResolved[],\r\n t\r\n ),\r\n }\r\n}\r\n\r\nfunction resolveLabelConfig(\r\n label: Partial<LabelConfig> | undefined,\r\n previous: LabelConfig\r\n): LabelConfig {\r\n return {\r\n textStyle: textOps.resolveStyle(label?.textStyle, previous.textStyle),\r\n percentStyle: textOps.resolveStyle(\r\n label?.percentStyle,\r\n previous.percentStyle\r\n ),\r\n insideStyle: textOps.resolveStyle(label?.insideStyle, previous.insideStyle),\r\n leaderStroke: strokeOps.resolve(\r\n label?.leaderStroke ?? previous.leaderStroke\r\n ),\r\n }\r\n}\r\n\r\nfunction lerpLabelConfig(\r\n from: LabelConfig,\r\n to: LabelConfig,\r\n t: number\r\n): LabelConfig {\r\n return {\r\n textStyle: textOps.lerpStyle(from.textStyle, to.textStyle, t),\r\n percentStyle: textOps.lerpStyle(from.percentStyle, to.percentStyle, t),\r\n insideStyle: textOps.lerpStyle(from.insideStyle, to.insideStyle, t),\r\n leaderStroke: strokeOps.lerp(\r\n from.leaderStroke as StrokeResolved[],\r\n to.leaderStroke as StrokeResolved[],\r\n t\r\n ),\r\n }\r\n}\r\n\r\nfunction resolveCenterConfig(\r\n center: Partial<CenterConfig> | undefined,\r\n previous: CenterConfig\r\n): CenterConfig {\r\n return {\r\n labelStyle: textOps.resolveStyle(center?.labelStyle, previous.labelStyle),\r\n captionStyle: textOps.resolveStyle(\r\n center?.captionStyle,\r\n previous.captionStyle\r\n ),\r\n }\r\n}\r\n\r\nfunction lerpCenterConfig(\r\n from: CenterConfig,\r\n to: CenterConfig,\r\n t: number\r\n): CenterConfig {\r\n return {\r\n labelStyle: textOps.lerpStyle(from.labelStyle, to.labelStyle, t),\r\n captionStyle: textOps.lerpStyle(from.captionStyle, to.captionStyle, t),\r\n }\r\n}\r\n\r\n/**\r\n * {@link PieChart}'s `theme` property mapper: merges the author's partial\r\n * {@link PieChartTheme} onto `previous` (falling back to\r\n * {@link DEFAULT_PIE_CHART_THEME} on first resolution), fully resolving every\r\n * leaf so the tween below always has resolved data to interpolate.\r\n */\r\nexport function resolvePieChartTheme(\r\n theme: PieChartTheme | undefined,\r\n previous?: PieChartConfig\r\n): PieChartConfig {\r\n const base = previous ?? DEFAULT_PIE_CHART_THEME\r\n return {\r\n sliceFills: resolveSeriesFills(theme?.sliceFills, base.sliceFills),\r\n ring: resolveRingConfig(theme?.ring, base.ring),\r\n label: resolveLabelConfig(theme?.label, base.label),\r\n center: resolveCenterConfig(theme?.center, base.center),\r\n legend: resolveLegendConfig(theme?.legend, base.legend),\r\n spotlight: {\r\n dimOpacity: theme?.spotlight?.dimOpacity ?? base.spotlight.dimOpacity,\r\n },\r\n }\r\n}\r\n\r\n/** {@link PieChart}'s `theme` property tween — leaf by leaf. */\r\nexport function lerpPieChartTheme(\r\n from: PieChartConfig,\r\n to: PieChartConfig,\r\n t: number\r\n): PieChartConfig {\r\n return {\r\n // A palette is a list of independent paints rather than one value, so it\r\n // interpolates pairwise by index; one that grows or shrinks holds the longer\r\n // side's tail, exactly as the cartesian charts' palettes do.\r\n sliceFills: lerpSeriesFills(from.sliceFills, to.sliceFills, t),\r\n ring: lerpRingConfig(from.ring, to.ring, t),\r\n label: lerpLabelConfig(from.label, to.label, t),\r\n center: lerpCenterConfig(from.center, to.center, t),\r\n legend: lerpLegendConfig(from.legend, to.legend, t),\r\n spotlight: {\r\n dimOpacity: lerpNumber(\r\n from.spotlight.dimOpacity,\r\n to.spotlight.dimOpacity,\r\n t\r\n ),\r\n },\r\n }\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/pie-chart/shared.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * Types and pure helpers specific to the {@link PieChart} family: how rows\r\n * become slices, how slices become arcs on a ring, where the labels around it\r\n * end up, and the theme all of that is drawn from.\r\n *\r\n * The one chart in the family that draws **one mark per row** rather than one\r\n * per column, which is why almost nothing about its geometry is shared: there is\r\n * no scale, no axis and no baseline, only a circle divided by share. What it\r\n * *does* share — the legend, the theme leaf resolvers, the palette, the\r\n * percentage format — comes from `../kit/shared.ts`.\r\n *\r\n * Kept renderer-agnostic (no `Node` imports) so a host's inspector mapping\r\n * can build a partial {@link PieChartTheme} without\r\n * pulling the node classes in.\r\n */\r\n\r\nimport {\r\n lerpNumber,\r\n shadowOps,\r\n strokeOps,\r\n textOps,\r\n type DataRecord,\r\n type Fill,\r\n type Shadow,\r\n type ShadowResolved,\r\n type Stroke,\r\n type StrokeResolved,\r\n type TextStyle,\r\n type Vector2,\r\n} from \"@motionscript/sdk\"\r\n\r\nimport {\r\n CHART_PALETTE,\r\n DEFAULT_LEGEND,\r\n DEFAULT_SERIES_FILLS,\r\n lerpLegendConfig,\r\n lerpSeriesFills,\r\n num,\r\n resolveLegendConfig,\r\n resolveSeriesFills,\r\n text,\r\n type LegendConfig,\r\n type LegendTheme,\r\n} from \"../kit/shared\"\r\n\r\n/**\r\n * One **resolved** slice of a {@link PieChart}: its caption, its magnitude, and\r\n * the paint its wedge and legend swatch take.\r\n *\r\n * This is what the ring and the legend consume, not what a scene authors: the\r\n * chart takes tabular `data` plus two field names and resolves it into these, so\r\n * the parts stay unaware of where a slice's caption, magnitude and paint came\r\n * from.\r\n */\r\nexport interface PieSlice {\r\n /** Legend caption and label text for this slice. */\r\n category: string\r\n /** This slice's magnitude. Its wedge covers `value / (sum of all values)`. */\r\n value: number\r\n /** Wedge and swatch paint. */\r\n fill: Fill\r\n}\r\n\r\n/**\r\n * Resolve tabular rows into slices: one per row, captioned by the `category`\r\n * column, sized by the `value` column, painted from `palette` by row order\r\n * (cycling when the palette is shorter than the data).\r\n *\r\n * Paint comes from the palette rather than from the rows because a pie draws one\r\n * mark per *row* — unlike the line and bar charts, where a colour belongs to a\r\n * series (a whole column) and rides on the series entry instead. Keeping it out\r\n * of the rows is what lets a pie be fed straight from a CSV of plain numbers.\r\n */\r\nexport function resolvePieSlices(\r\n data: DataRecord[],\r\n category: string,\r\n value: string,\r\n palette: Fill[]\r\n): PieSlice[] {\r\n return data.map((row, i) => ({\r\n category: text(row, category),\r\n value: num(row, value),\r\n fill:\r\n palette.length > 0 ? palette[i % palette.length] : (\"white\" as Fill),\r\n }))\r\n}\r\n\r\n/** What a {@link PieChart}'s labels say. */\r\nexport type PieLabelMode =\r\n /**\r\n * The category and its percentage, two lines. Their order flips with the\r\n * placement: a **leader** label reads category over percentage, an **inside**\r\n * label reads the percentage big over a small category caption — inside a\r\n * wedge the reader already knows which slice they're on, so the magnitude is\r\n * the thing being read.\r\n */\r\n | \"both\"\r\n /** The category only. */\r\n | \"category\"\r\n /** The percentage only — the pick when a legend already names the categories. */\r\n | \"percent\"\r\n /** No labels and no leader lines at all. */\r\n | \"none\"\r\n\r\n/**\r\n * Where a slice's label sits.\r\n *\r\n * `\"inside\"` drops the leader line entirely and centres the label in the wedge's\r\n * own band, which costs no gutter — so the ring draws bigger — and costs the eye\r\n * no round trip. `\"leader\"` is the classic treatment, and what a sliver too\r\n * narrow to hold text needs.\r\n */\r\nexport type PieLabelPlacement = \"inside\" | \"leader\" | \"none\"\r\n\r\n/** Degrees → radians. */\r\nexport const DEG = Math.PI / 180\r\n\r\n/** A slice's plotted value: negatives and non-finite values count as 0. */\r\nexport function sliceValue(slice: PieSlice): number {\r\n return slice.value > 0 && Number.isFinite(slice.value) ? slice.value : 0\r\n}\r\n\r\n/** Sum of every slice's plotted value — the denominator every share is taken against. */\r\nexport function sliceTotal(slices: PieSlice[]): number {\r\n let sum = 0\r\n for (const slice of slices) sum += sliceValue(slice)\r\n return sum\r\n}\r\n\r\n/**\r\n * The point at radius `r` and angle `a` on a ring centred on the origin, in the\r\n * node's centred, **y-up** space. Angles run **clockwise from 12 o'clock**:\r\n * `a = 0` points straight up (+y), and increasing `a` sweeps toward +x then −y.\r\n */\r\nexport function ringPoint(r: number, a: number): Vector2 {\r\n return { x: r * Math.sin(a), y: r * Math.cos(a) }\r\n}\r\n\r\n/** One slice's resolved angular span on the ring. */\r\nexport interface PieArc {\r\n /** Index of the slice in the source list. */\r\n index: number\r\n /** Start angle, radians clockwise from 12 o'clock. */\r\n a0: number\r\n /** End angle, radians clockwise from 12 o'clock. */\r\n a1: number\r\n /** Bisector angle — where this slice's label and pop-out point. */\r\n mid: number\r\n /** This slice's share of the total, in `[0, 1]`. */\r\n fraction: number\r\n}\r\n\r\n/**\r\n * Resolve slices into per-slice arcs: each covers exactly its share of the\r\n * circle, walking clockwise from `startDegrees` (0 = 12 o'clock), with the arcs\r\n * abutting. Zero/negative slices are skipped entirely; an all-zero list yields\r\n * no arcs.\r\n *\r\n * Arcs are the *undivided* shares — the gap between neighbours is not carved out\r\n * here, because a fixed angular gap reads as a wedge: wide at the rim, pinched\r\n * shut at the hole. It's applied when the wedge is drawn instead, as a\r\n * constant-width inset off both ends of every arc, so a slice still covers its\r\n * true percentage of the circle.\r\n */\r\nexport function pieArcs(slices: PieSlice[], startDegrees: number): PieArc[] {\r\n const total = sliceTotal(slices)\r\n if (total <= 0) return []\r\n\r\n const arcs: PieArc[] = []\r\n let cursor = startDegrees * DEG\r\n slices.forEach((slice, index) => {\r\n const value = sliceValue(slice)\r\n if (value <= 0) return\r\n const fraction = value / total\r\n const a0 = cursor\r\n const a1 = a0 + fraction * Math.PI * 2\r\n cursor = a1\r\n arcs.push({ index, a0, a1, mid: (a0 + a1) / 2, fraction })\r\n })\r\n return arcs\r\n}\r\n\r\n/** A label competing for vertical space in one of the two label columns. */\r\nexport interface LabelSlot {\r\n /** Where the label would sit if nothing else were in the way. */\r\n ideal: number\r\n /** Where it ends up once the column has been de-conflicted. Written by {@link relaxLabels}. */\r\n y: number\r\n}\r\n\r\n/**\r\n * Spread one column of labels so no two overlap, keeping each as close to its\r\n * `ideal` y as possible and the whole column inside `[bottom, top]`.\r\n *\r\n * Two passes, the standard treatment. Walking top-to-bottom, each label takes\r\n * the highest slot at or below its ideal that still clears the label above it by\r\n * `minGap`; if that pushes the tail past `bottom`, a reverse pass lifts it back\r\n * up. When there are more labels than the column can hold, the reverse pass wins\r\n * and the crowding is shared evenly rather than dumped off the bottom edge.\r\n *\r\n * Mutates each slot's `y` and returns `slots` re-ordered top-to-bottom.\r\n */\r\nexport function relaxLabels<T extends LabelSlot>(\r\n slots: T[],\r\n minGap: number,\r\n top: number,\r\n bottom: number\r\n): T[] {\r\n slots.sort((a, b) => b.ideal - a.ideal)\r\n\r\n let ceiling = top\r\n for (const slot of slots) {\r\n slot.y = Math.min(slot.ideal, ceiling)\r\n ceiling = slot.y - minGap\r\n }\r\n\r\n let floor = bottom\r\n for (let i = slots.length - 1; i >= 0; i--) {\r\n slots[i].y = Math.max(slots[i].y, floor)\r\n floor = slots[i].y + minGap\r\n }\r\n\r\n return slots\r\n}\r\n\r\n// ---- Theme ---------------------------------------------------------------\r\n\r\n/** The ring's geometry and paint — everything about the wedges but their colour. */\r\nexport interface RingConfig {\r\n /**\r\n * Width of the channel between adjacent slices, in px, held constant all the\r\n * way across the band — the same clearance at the hole as at the rim. (An\r\n * angular gap would splay open toward the rim instead.)\r\n */\r\n gap: number\r\n /**\r\n * Radius of the hole punched through the middle. Below 1 it reads as a\r\n * fraction of the ring's outer radius (`0.6` leaves a band 40% of the radius\r\n * thick); 1 and up as an explicit pixel radius; `0` fills the middle in,\r\n * drawing solid pie wedges.\r\n */\r\n holeRadius: number\r\n /** Angle in degrees where the first slice begins, clockwise from 12 o'clock. */\r\n startAngle: number\r\n /**\r\n * How far out a selected slice's rim reaches, as a **multiple of the ring's\r\n * outer radius**. Being relative, the emphasis scales with the chart instead\r\n * of shrinking on a big one and swamping a small one. Clamped to `[1, 1.6]`.\r\n */\r\n selectedGrow: number\r\n /** Optional outline on every wedge. */\r\n stroke: Stroke\r\n /** Optional drop shadow on every wedge. */\r\n shadow: Shadow\r\n}\r\n\r\n/** How the ring's labels are set, and the leader lines that point at them. */\r\nexport interface LabelConfig {\r\n /** The category caption on a label. */\r\n textStyle: TextStyle\r\n /** The percentage on a label. */\r\n percentStyle: TextStyle\r\n /**\r\n * Ink for a label set **inside** its wedge, both lines. Separate because an\r\n * inside label sits on the wedge's own paint rather than on the background, so\r\n * it usually wants the opposite end of the value range from everything else.\r\n */\r\n insideStyle: TextStyle\r\n /** The leader line running from a wedge out to its label. */\r\n leaderStroke: Stroke\r\n}\r\n\r\n/** The summary set in the ring's hole. */\r\nexport interface CenterConfig {\r\n labelStyle: TextStyle\r\n captionStyle: TextStyle\r\n}\r\n\r\n/** A {@link PieChart}'s full, resolved visual theme. */\r\nexport interface PieChartConfig {\r\n /** Wedge and swatch paint, cycled by **row** index (`i % length`). */\r\n sliceFills: Fill[]\r\n ring: RingConfig\r\n label: LabelConfig\r\n center: CenterConfig\r\n legend: LegendConfig\r\n /** How far everything outside the selection fades while any slice is selected. */\r\n spotlight: { dimOpacity: number }\r\n}\r\n\r\n/** Author-facing partial theme — every branch optional and deep-merged. */\r\nexport type PieChartTheme = {\r\n sliceFills?: Fill[]\r\n ring?: Partial<RingConfig>\r\n label?: Partial<LabelConfig>\r\n center?: Partial<CenterConfig>\r\n legend?: LegendTheme\r\n spotlight?: Partial<PieChartConfig[\"spotlight\"]>\r\n}\r\n\r\nconst LABEL_TEXT_STYLE: TextStyle = {\r\n fontSize: 26,\r\n fontWeight: 600,\r\n textAlign: \"center\",\r\n fill: CHART_PALETTE.markerColor,\r\n}\r\n\r\n/**\r\n * Opacity the other slices fade to under a selection. Fainter than the line and\r\n * bar charts' `SPOTLIGHT_DIM`: a ring's slices touch, so at that a selected\r\n * slice barely separates from its neighbours.\r\n */\r\nexport const PIE_SPOTLIGHT_DIM = 0.1\r\n\r\n/** The pie chart's out-of-the-box look. */\r\nexport const DEFAULT_PIE_CHART_THEME: PieChartConfig = {\r\n sliceFills: DEFAULT_SERIES_FILLS,\r\n ring: {\r\n gap: 6,\r\n // A donut rather than a solid pie by default: the hole is the largest\r\n // uninterrupted area the chart owns and the one place a conclusion can be\r\n // set. `0` here would give that away before anyone had asked for it.\r\n holeRadius: 0.6,\r\n startAngle: 0,\r\n selectedGrow: 1.08,\r\n stroke: [],\r\n shadow: [],\r\n },\r\n label: {\r\n textStyle: LABEL_TEXT_STYLE,\r\n percentStyle: { ...LABEL_TEXT_STYLE, fontWeight: 700 },\r\n // Inside labels sit on the wedge's paint, which is a mid-tone from the\r\n // palette — so they lead with the panel colour rather than the axis ink.\r\n insideStyle: { ...LABEL_TEXT_STYLE, fontWeight: 800, fill: \"#15130e\" },\r\n leaderStroke: { weight: 2, fill: CHART_PALETTE.markerColor },\r\n },\r\n center: {\r\n labelStyle: {\r\n fontSize: 46,\r\n fontWeight: 800,\r\n textAlign: \"center\",\r\n fill: CHART_PALETTE.markerColor,\r\n },\r\n captionStyle: {\r\n fontSize: 22,\r\n fontWeight: 500,\r\n textAlign: \"center\",\r\n fill: CHART_PALETTE.markerColor,\r\n },\r\n },\r\n legend: DEFAULT_LEGEND,\r\n spotlight: { dimOpacity: PIE_SPOTLIGHT_DIM },\r\n}\r\n\r\nfunction resolveRingConfig(\r\n ring: Partial<RingConfig> | undefined,\r\n previous: RingConfig\r\n): RingConfig {\r\n return {\r\n gap: ring?.gap ?? previous.gap,\r\n holeRadius: ring?.holeRadius ?? previous.holeRadius,\r\n startAngle: ring?.startAngle ?? previous.startAngle,\r\n selectedGrow: ring?.selectedGrow ?? previous.selectedGrow,\r\n stroke: strokeOps.resolve(ring?.stroke ?? previous.stroke),\r\n shadow: shadowOps.resolve(ring?.shadow ?? previous.shadow),\r\n }\r\n}\r\n\r\nfunction lerpRingConfig(\r\n from: RingConfig,\r\n to: RingConfig,\r\n t: number\r\n): RingConfig {\r\n return {\r\n gap: lerpNumber(from.gap, to.gap, t),\r\n holeRadius: lerpNumber(from.holeRadius, to.holeRadius, t),\r\n startAngle: lerpNumber(from.startAngle, to.startAngle, t),\r\n selectedGrow: lerpNumber(from.selectedGrow, to.selectedGrow, t),\r\n stroke: strokeOps.lerp(\r\n from.stroke as StrokeResolved[],\r\n to.stroke as StrokeResolved[],\r\n t\r\n ),\r\n shadow: shadowOps.lerp(\r\n from.shadow as ShadowResolved[],\r\n to.shadow as ShadowResolved[],\r\n t\r\n ),\r\n }\r\n}\r\n\r\nfunction resolveLabelConfig(\r\n label: Partial<LabelConfig> | undefined,\r\n previous: LabelConfig\r\n): LabelConfig {\r\n return {\r\n textStyle: textOps.resolveStyle(label?.textStyle, previous.textStyle),\r\n percentStyle: textOps.resolveStyle(\r\n label?.percentStyle,\r\n previous.percentStyle\r\n ),\r\n insideStyle: textOps.resolveStyle(label?.insideStyle, previous.insideStyle),\r\n leaderStroke: strokeOps.resolve(\r\n label?.leaderStroke ?? previous.leaderStroke\r\n ),\r\n }\r\n}\r\n\r\nfunction lerpLabelConfig(\r\n from: LabelConfig,\r\n to: LabelConfig,\r\n t: number\r\n): LabelConfig {\r\n return {\r\n textStyle: textOps.lerpStyle(from.textStyle, to.textStyle, t),\r\n percentStyle: textOps.lerpStyle(from.percentStyle, to.percentStyle, t),\r\n insideStyle: textOps.lerpStyle(from.insideStyle, to.insideStyle, t),\r\n leaderStroke: strokeOps.lerp(\r\n from.leaderStroke as StrokeResolved[],\r\n to.leaderStroke as StrokeResolved[],\r\n t\r\n ),\r\n }\r\n}\r\n\r\nfunction resolveCenterConfig(\r\n center: Partial<CenterConfig> | undefined,\r\n previous: CenterConfig\r\n): CenterConfig {\r\n return {\r\n labelStyle: textOps.resolveStyle(center?.labelStyle, previous.labelStyle),\r\n captionStyle: textOps.resolveStyle(\r\n center?.captionStyle,\r\n previous.captionStyle\r\n ),\r\n }\r\n}\r\n\r\nfunction lerpCenterConfig(\r\n from: CenterConfig,\r\n to: CenterConfig,\r\n t: number\r\n): CenterConfig {\r\n return {\r\n labelStyle: textOps.lerpStyle(from.labelStyle, to.labelStyle, t),\r\n captionStyle: textOps.lerpStyle(from.captionStyle, to.captionStyle, t),\r\n }\r\n}\r\n\r\n/**\r\n * {@link PieChart}'s `theme` property mapper: merges the author's partial\r\n * {@link PieChartTheme} onto `previous` (falling back to\r\n * {@link DEFAULT_PIE_CHART_THEME} on first resolution), fully resolving every\r\n * leaf so the tween below always has resolved data to interpolate.\r\n */\r\nexport function resolvePieChartTheme(\r\n theme: PieChartTheme | undefined,\r\n previous?: PieChartConfig\r\n): PieChartConfig {\r\n const base = previous ?? DEFAULT_PIE_CHART_THEME\r\n return {\r\n sliceFills: resolveSeriesFills(theme?.sliceFills, base.sliceFills),\r\n ring: resolveRingConfig(theme?.ring, base.ring),\r\n label: resolveLabelConfig(theme?.label, base.label),\r\n center: resolveCenterConfig(theme?.center, base.center),\r\n legend: resolveLegendConfig(theme?.legend, base.legend),\r\n spotlight: {\r\n dimOpacity: theme?.spotlight?.dimOpacity ?? base.spotlight.dimOpacity,\r\n },\r\n }\r\n}\r\n\r\n/** {@link PieChart}'s `theme` property tween — leaf by leaf. */\r\nexport function lerpPieChartTheme(\r\n from: PieChartConfig,\r\n to: PieChartConfig,\r\n t: number\r\n): PieChartConfig {\r\n return {\r\n // A palette is a list of independent paints rather than one value, so it\r\n // interpolates pairwise by index; one that grows or shrinks holds the longer\r\n // side's tail, exactly as the cartesian charts' palettes do.\r\n sliceFills: lerpSeriesFills(from.sliceFills, to.sliceFills, t),\r\n ring: lerpRingConfig(from.ring, to.ring, t),\r\n label: lerpLabelConfig(from.label, to.label, t),\r\n center: lerpCenterConfig(from.center, to.center, t),\r\n legend: lerpLegendConfig(from.legend, to.legend, t),\r\n spotlight: {\r\n dimOpacity: lerpNumber(\r\n from.spotlight.dimOpacity,\r\n to.spotlight.dimOpacity,\r\n t\r\n ),\r\n },\r\n }\r\n}\r\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motionscript/charts",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Animated data charts for Motion Script: a line chart, a bar chart and a pie chart sharing one axis, legend and theme kit.",
|
|
6
6
|
"repository": {
|
|
@@ -48,8 +48,9 @@
|
|
|
48
48
|
"animation"
|
|
49
49
|
],
|
|
50
50
|
"license": "Apache-2.0",
|
|
51
|
+
"author": "Motionscript",
|
|
51
52
|
"peerDependencies": {
|
|
52
|
-
"@motionscript/sdk": "^0.1.0-alpha.
|
|
53
|
+
"@motionscript/sdk": "^0.1.0-alpha.8"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
56
|
"@types/node": "^25.6.2",
|
|
@@ -60,8 +61,8 @@
|
|
|
60
61
|
"typescript": "^6.0.3",
|
|
61
62
|
"typescript-eslint": "^8.48.0",
|
|
62
63
|
"vitest": "^4.1.5",
|
|
63
|
-
"@motionscript/
|
|
64
|
-
"@motionscript/
|
|
64
|
+
"@motionscript/sdk": "0.1.0-alpha.8",
|
|
65
|
+
"@motionscript/core": "0.1.0-alpha.8"
|
|
65
66
|
},
|
|
66
67
|
"scripts": {
|
|
67
68
|
"build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json --resolve-full-paths && node ../../../scripts/build-browser.mjs",
|
|
@@ -128,9 +128,17 @@ function drawnValue(s: BarChartValues, domain: AxisDomainConfig): AxisDomainConf
|
|
|
128
128
|
return { ...domain, range, ticks: domain.ticks ?? markerValues(range, DEFAULT_MARKERS - 1) }
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
/** The last domain worked out from each data array, and what else it was read from. */
|
|
132
|
+
const domainsDrawn = new WeakMap<DataRecord[], Omit<BarChartValues, "data"> & { readonly domain: BarChartDomain }>()
|
|
133
|
+
|
|
131
134
|
/** The value axis as a bar chart draws it now, an auto range or tick row read back as itself. */
|
|
132
135
|
export function barChartDomain(s: BarChartValues): BarChartDomain {
|
|
133
|
-
|
|
136
|
+
const { data, series, stacked, valueAxis } = s
|
|
137
|
+
const kept = domainsDrawn.get(data)
|
|
138
|
+
if (kept && kept.series === series && kept.stacked === stacked && kept.valueAxis === valueAxis) return kept.domain
|
|
139
|
+
const domain = settleAxisDomain(valueAxis, (d) => drawnValue(s, d)) as BarChartDomain
|
|
140
|
+
domainsDrawn.set(data, { series, stacked, valueAxis, domain })
|
|
141
|
+
return domain
|
|
134
142
|
}
|
|
135
143
|
|
|
136
144
|
/**
|
|
@@ -334,6 +342,7 @@ export const BarChart = component2d({
|
|
|
334
342
|
weights: (state) => state.series.map((s) => ((s.enabled ?? true) ? 1 : 0)),
|
|
335
343
|
showing: (state) => state.series.map((s) => s.enabled ?? true),
|
|
336
344
|
},
|
|
345
|
+
allowedChildren: [],
|
|
337
346
|
children: (state) => {
|
|
338
347
|
const theme = () => state.theme
|
|
339
348
|
const domain = () => barChartDomain(state)
|
|
@@ -426,10 +435,7 @@ export const BarChart = component2d({
|
|
|
426
435
|
height: 0,
|
|
427
436
|
}) as unknown as Node2D
|
|
428
437
|
|
|
429
|
-
return asParts([
|
|
430
|
-
...(state.children as unknown as Node2D[]),
|
|
431
|
-
cartesianFrame({ axis: vAxis as unknown as Node2D, plot, gutter, below }),
|
|
432
|
-
])
|
|
438
|
+
return asParts([cartesianFrame({ axis: vAxis as unknown as Node2D, plot, gutter, below })])
|
|
433
439
|
},
|
|
434
440
|
commands: {
|
|
435
441
|
/**
|
|
@@ -441,6 +447,8 @@ export const BarChart = component2d({
|
|
|
441
447
|
*/
|
|
442
448
|
enter: {
|
|
443
449
|
args: {},
|
|
450
|
+
// Held from the chart's arrival, so it never shows finished ahead of its draw.
|
|
451
|
+
entrance: true,
|
|
444
452
|
easing: easeOut(),
|
|
445
453
|
run: (timeline, _state, children) => {
|
|
446
454
|
const parts = partsOf(children)
|
|
@@ -263,16 +263,15 @@ export const ChartBars = component2d({
|
|
|
263
263
|
y: (ends.base + ends.end) / 2,
|
|
264
264
|
width: bar.width,
|
|
265
265
|
height,
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
// single capped column.
|
|
266
|
+
// Corner keys are visual (the renderer keeps them in y-up author space),
|
|
267
|
+
// so a column reaching *up* rounds its `top*` corners and one reaching
|
|
268
|
+
// down its `bottom*` ones. Only the far end rounds, which is what makes
|
|
269
|
+
// a stack read as a single capped column.
|
|
271
270
|
cornerRadius:
|
|
272
271
|
radius > 0
|
|
273
272
|
? ends.end >= ends.base
|
|
274
|
-
? {
|
|
275
|
-
: {
|
|
273
|
+
? { topLeft: radius, topRight: radius, bottomLeft: 0, bottomRight: 0 }
|
|
274
|
+
: { bottomLeft: radius, bottomRight: radius, topLeft: 0, topRight: 0 }
|
|
276
275
|
: 0,
|
|
277
276
|
})
|
|
278
277
|
rect.shadow(state.barShadow)
|
|
@@ -169,12 +169,25 @@ function drawnY(s: LineChartValues, domain: AxisDomainConfig): AxisDomainConfig
|
|
|
169
169
|
return { ...domain, range, ticks: domain.ticks ?? markerValues(range, DEFAULT_MARKERS - 1) }
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
/** The last domains worked out from each data array, and what else they were read from. */
|
|
173
|
+
const domainsDrawn = new WeakMap<
|
|
174
|
+
DataRecord[],
|
|
175
|
+
Pick<LineChartValues, "xField" | "xAxis" | "yAxis" | "series"> & { readonly domains: LineChartDomains }
|
|
176
|
+
>()
|
|
177
|
+
|
|
172
178
|
/** Both axes as a line chart draws them now, an auto range or tick row read back as itself. */
|
|
173
179
|
export function lineChartDomains(s: LineChartValues): LineChartDomains {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
180
|
+
const { data, xField, xAxis, yAxis, series } = s
|
|
181
|
+
const kept = domainsDrawn.get(data)
|
|
182
|
+
if (kept && kept.xField === xField && kept.xAxis === xAxis && kept.yAxis === yAxis && kept.series === series) {
|
|
183
|
+
return kept.domains
|
|
184
|
+
}
|
|
185
|
+
const domains = {
|
|
186
|
+
x: settleAxisDomain(xAxis, (d) => drawnX(s, d)) as LineChartDomains["x"],
|
|
187
|
+
y: settleAxisDomain(yAxis, (d) => drawnY(s, d)) as LineChartDomains["y"],
|
|
177
188
|
}
|
|
189
|
+
domainsDrawn.set(data, { xField, xAxis, yAxis, series, domains })
|
|
190
|
+
return domains
|
|
178
191
|
}
|
|
179
192
|
|
|
180
193
|
/**
|
|
@@ -333,6 +346,7 @@ export const LineChart = component2d({
|
|
|
333
346
|
},
|
|
334
347
|
// Its parts fill it; a node made of them would hug them.
|
|
335
348
|
defaults: { width: "fill", height: "fill" },
|
|
349
|
+
allowedChildren: [],
|
|
336
350
|
children: (state) => {
|
|
337
351
|
const theme = () => state.theme
|
|
338
352
|
const domains = () => lineChartDomains(state)
|
|
@@ -440,10 +454,7 @@ export const LineChart = component2d({
|
|
|
440
454
|
height: 0,
|
|
441
455
|
}) as unknown as Node2D
|
|
442
456
|
|
|
443
|
-
return asParts([
|
|
444
|
-
...(state.children as unknown as Node2D[]),
|
|
445
|
-
cartesianFrame({ axis: vAxis as unknown as Node2D, plot, gutter, below }),
|
|
446
|
-
])
|
|
457
|
+
return asParts([cartesianFrame({ axis: vAxis as unknown as Node2D, plot, gutter, below })])
|
|
447
458
|
},
|
|
448
459
|
commands: {
|
|
449
460
|
/**
|
|
@@ -457,6 +468,8 @@ export const LineChart = component2d({
|
|
|
457
468
|
*/
|
|
458
469
|
enter: {
|
|
459
470
|
args: {},
|
|
471
|
+
// Held from the chart's arrival, so it never shows finished ahead of its draw.
|
|
472
|
+
entrance: true,
|
|
460
473
|
easing: easeOut(),
|
|
461
474
|
run: (timeline, state, children) => {
|
|
462
475
|
const parts = partsOf(children)
|
|
@@ -304,15 +304,30 @@ function labelBySample(
|
|
|
304
304
|
return i >= 0 ? captions[i] : fallback(value)
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
+
/** Scales already resolved, by data array, then by field and pattern. */
|
|
308
|
+
const scales = new WeakMap<DataRecord[], Map<string, XScale>>()
|
|
309
|
+
|
|
307
310
|
/**
|
|
308
311
|
* How `field` positions the rows: the number it holds, the instant it names, or
|
|
309
312
|
* failing both, the row's own place in the file.
|
|
310
313
|
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
* array that a rebuild replaces on every edit would miss every time anyway.
|
|
314
|
+
* Kept per `data` array: a chart's bound props ask a dozen times a frame, and
|
|
315
|
+
* parsing every row's instant each time was most of a frame.
|
|
314
316
|
*/
|
|
315
317
|
export function resolveXScale(
|
|
318
|
+
data: DataRecord[],
|
|
319
|
+
field: string,
|
|
320
|
+
pattern?: string
|
|
321
|
+
): XScale {
|
|
322
|
+
let byKey = scales.get(data)
|
|
323
|
+
if (!byKey) scales.set(data, (byKey = new Map()))
|
|
324
|
+
const key = JSON.stringify([field, pattern ?? null])
|
|
325
|
+
let scale = byKey.get(key)
|
|
326
|
+
if (!scale) byKey.set(key, (scale = xScaleOf(data, field, pattern)))
|
|
327
|
+
return scale
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function xScaleOf(
|
|
316
331
|
data: DataRecord[],
|
|
317
332
|
field: string,
|
|
318
333
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"4.1.9","results":[[":document.test.ts",{"duration":547.3535000000002,"failed":false}],[":pie-chart/pie-ring.test.ts",{"duration":115.20639999999958,"failed":false}],[":bar-chart/bar-chart.test.ts",{"duration":337.1051000000002,"failed":false}],[":line-chart/line-chart.test.ts",{"duration":256.0781999999999,"failed":false}],[":kit/theme.test.ts",{"duration":23.301500000000033,"failed":false}],[":pie-chart/pie-chart.test.ts",{"duration":243.7885000000001,"failed":false}],[":kit/chart-legend.test.ts",{"duration":107.26519999999982,"failed":false}],[":line-chart/x-scale.test.ts",{"duration":6.663699999999608,"failed":false}],[":kit/shared.test.ts",{"duration":8.266599999999926,"failed":false}],[":nodes.test.ts",{"duration":34.65560000000005,"failed":false}],[":line-chart/chart-line.test.ts",{"duration":25.551500000000033,"failed":false}],[":kit/vertical-axis.test.ts",{"duration":57.74340000000029,"failed":false}],[":line-chart/shared.test.ts",{"duration":8.284700000000157,"failed":false}],[":line-chart/horizontal-axis.test.ts",{"duration":45.18509999999969,"failed":false}],[":bar-chart/category-axis.test.ts",{"duration":43.834299999999985,"failed":false}],[":kit/chart-canvas.test.ts",{"duration":14.003599999999892,"failed":false}],[":bar-chart/shared.test.ts",{"duration":6.999300000000062,"failed":false}],[":pie-chart/shared.test.ts",{"duration":5.208399999999983,"failed":false}],[":kit/declare-paints.test.ts",{"duration":11.566000000000031,"failed":false}],[":tests/leaf.test.ts",{"duration":15.790100000000052,"failed":false}]]}
|
|
@@ -179,6 +179,7 @@ export const PieChart = component2d({
|
|
|
179
179
|
height: "fill",
|
|
180
180
|
pops: (state) => slicesOf(state).map(() => 0),
|
|
181
181
|
},
|
|
182
|
+
allowedChildren: [],
|
|
182
183
|
// The slice *structure* (one label and one legend row per row) is read once;
|
|
183
184
|
// the slices themselves, their paint and the selection are bound, so a theme
|
|
184
185
|
// tween re-paints and a `set` of `data` re-cuts the wedges.
|
|
@@ -216,7 +217,7 @@ export const PieChart = component2d({
|
|
|
216
217
|
)
|
|
217
218
|
}
|
|
218
219
|
const row = new Rect({ flow: "horizontal", width: "fill", height: "fill", gap: state.gap, align: "center", children: parts })
|
|
219
|
-
return asParts([
|
|
220
|
+
return asParts([row as unknown as Node2D])
|
|
220
221
|
},
|
|
221
222
|
commands: {
|
|
222
223
|
/**
|
|
@@ -227,6 +228,8 @@ export const PieChart = component2d({
|
|
|
227
228
|
*/
|
|
228
229
|
enter: {
|
|
229
230
|
args: {},
|
|
231
|
+
// Held from the chart's arrival, so it never shows finished ahead of its draw.
|
|
232
|
+
entrance: true,
|
|
230
233
|
easing: easeOut(),
|
|
231
234
|
run: (timeline, _state, children) => {
|
|
232
235
|
const parts = partsOf(children)
|
package/src/pie-chart/shared.ts
CHANGED
|
@@ -33,7 +33,6 @@ import {
|
|
|
33
33
|
CHART_PALETTE,
|
|
34
34
|
DEFAULT_LEGEND,
|
|
35
35
|
DEFAULT_SERIES_FILLS,
|
|
36
|
-
SPOTLIGHT_DIM,
|
|
37
36
|
lerpLegendConfig,
|
|
38
37
|
lerpSeriesFills,
|
|
39
38
|
num,
|
|
@@ -306,6 +305,13 @@ const LABEL_TEXT_STYLE: TextStyle = {
|
|
|
306
305
|
fill: CHART_PALETTE.markerColor,
|
|
307
306
|
}
|
|
308
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Opacity the other slices fade to under a selection. Fainter than the line and
|
|
310
|
+
* bar charts' `SPOTLIGHT_DIM`: a ring's slices touch, so at that a selected
|
|
311
|
+
* slice barely separates from its neighbours.
|
|
312
|
+
*/
|
|
313
|
+
export const PIE_SPOTLIGHT_DIM = 0.1
|
|
314
|
+
|
|
309
315
|
/** The pie chart's out-of-the-box look. */
|
|
310
316
|
export const DEFAULT_PIE_CHART_THEME: PieChartConfig = {
|
|
311
317
|
sliceFills: DEFAULT_SERIES_FILLS,
|
|
@@ -343,7 +349,7 @@ export const DEFAULT_PIE_CHART_THEME: PieChartConfig = {
|
|
|
343
349
|
},
|
|
344
350
|
},
|
|
345
351
|
legend: DEFAULT_LEGEND,
|
|
346
|
-
spotlight: { dimOpacity:
|
|
352
|
+
spotlight: { dimOpacity: PIE_SPOTLIGHT_DIM },
|
|
347
353
|
}
|
|
348
354
|
|
|
349
355
|
function resolveRingConfig(
|