@misoto22/design 0.6.1 → 0.8.0
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/AGENTS.md +87 -0
- package/CLAUDE.md +1 -0
- package/README.md +62 -5
- package/bin/misoto22-design.mjs +59 -13
- package/dist/agent/Accordion.md +24 -1
- package/dist/agent/Alert.md +23 -1
- package/dist/agent/AppShell.md +30 -2
- package/dist/agent/ArchitectureFigure.md +71 -0
- package/dist/agent/AreaChart.md +27 -2
- package/dist/agent/Article.md +26 -3
- package/dist/agent/AspectRatio.md +57 -0
- package/dist/agent/Avatar.md +25 -5
- package/dist/agent/Badge.md +22 -1
- package/dist/agent/BarChart.md +29 -2
- package/dist/agent/BarList.md +27 -1
- package/dist/agent/BigNumber.md +26 -2
- package/dist/agent/BoxPlot.md +24 -1
- package/dist/agent/Breadcrumb.md +24 -2
- package/dist/agent/BulletChart.md +23 -1
- package/dist/agent/Button.md +25 -1
- package/dist/agent/Calendar.md +37 -3
- package/dist/agent/Card.md +24 -2
- package/dist/agent/Checkbox.md +23 -2
- package/dist/agent/Code.md +50 -0
- package/dist/agent/CodeBlock.md +74 -0
- package/dist/agent/Collapsible.md +22 -1
- package/dist/agent/Combobox.md +29 -3
- package/dist/agent/Command.md +25 -2
- package/dist/agent/ComposedChart.md +26 -2
- package/dist/agent/ContextMenu.md +37 -4
- package/dist/agent/DataflowFigure.md +48 -0
- package/dist/agent/DatePicker.md +34 -6
- package/dist/agent/DescriptionList.md +68 -0
- package/dist/agent/Diagram.md +27 -3
- package/dist/agent/DiagramCanvas.md +70 -0
- package/dist/agent/DiagramExportMenu.md +64 -0
- package/dist/agent/DiagramInspector.md +72 -0
- package/dist/agent/DiagramLegend.md +55 -0
- package/dist/agent/DiagramMinimap.md +68 -0
- package/dist/agent/DiagramToolbar.md +69 -0
- package/dist/agent/Dialog.md +26 -3
- package/dist/agent/DropdownMenu.md +45 -6
- package/dist/agent/EmptyState.md +26 -1
- package/dist/agent/ErrorState.md +27 -2
- package/dist/agent/Facet.md +25 -1
- package/dist/agent/Field.md +45 -4
- package/dist/agent/FigureBand.md +24 -1
- package/dist/agent/FloatingIconButton.md +23 -1
- package/dist/agent/FunnelChart.md +23 -1
- package/dist/agent/Heading.md +64 -0
- package/dist/agent/Heatmap.md +25 -1
- package/dist/agent/Histogram.md +26 -2
- package/dist/agent/Input.md +22 -1
- package/dist/agent/Kbd.md +19 -1
- package/dist/agent/LifecycleFigure.md +49 -0
- package/dist/agent/LineChart.md +26 -2
- package/dist/agent/LinkArrow.md +20 -1
- package/dist/agent/Markdown.md +68 -0
- package/dist/agent/NativeSelect.md +23 -2
- package/dist/agent/NavItem.md +21 -1
- package/dist/agent/Pagination.md +28 -2
- package/dist/agent/PieChart.md +26 -2
- package/dist/agent/Popover.md +23 -1
- package/dist/agent/Progress.md +27 -3
- package/dist/agent/RadarChart.md +26 -2
- package/dist/agent/RadialChart.md +26 -3
- package/dist/agent/RadioGroup.md +23 -2
- package/dist/agent/SankeyChart.md +25 -2
- package/dist/agent/ScatterChart.md +27 -2
- package/dist/agent/ScrollArea.md +24 -3
- package/dist/agent/SearchableMenu.md +23 -1
- package/dist/agent/Select.md +29 -3
- package/dist/agent/Separator.md +28 -4
- package/dist/agent/SequenceFigure.md +50 -0
- package/dist/agent/Sheet.md +25 -2
- package/dist/agent/Skeleton.md +24 -2
- package/dist/agent/Slider.md +27 -4
- package/dist/agent/Sparkline.md +23 -1
- package/dist/agent/Spinner.md +24 -2
- package/dist/agent/StatusDot.md +20 -1
- package/dist/agent/StatusPill.md +28 -2
- package/dist/agent/Steps.md +24 -1
- package/dist/agent/Switch.md +21 -1
- package/dist/agent/Table.md +26 -3
- package/dist/agent/Tabs.md +24 -2
- package/dist/agent/Tag.md +40 -9
- package/dist/agent/Text.md +65 -0
- package/dist/agent/Textarea.md +20 -1
- package/dist/agent/Timestamp.md +64 -0
- package/dist/agent/Toast.md +26 -2
- package/dist/agent/ToggleGroup.md +24 -2
- package/dist/agent/Toolbar.md +71 -0
- package/dist/agent/Tooltip.md +23 -1
- package/dist/agent/TreemapChart.md +24 -1
- package/dist/agent/WaterfallChart.md +24 -1
- package/dist/agent/WorkflowFigure.md +50 -0
- package/dist/agent/catalog.json +6086 -1009
- package/dist/agent/index.md +27 -4
- package/dist/article.css +16 -4
- package/dist/charts/AreaChart/AreaChart.d.ts +9 -2
- package/dist/charts/AreaChart/AreaChart.js +12 -13
- package/dist/charts/AreaChart/AreaChart.js.map +1 -1
- package/dist/charts/BarChart/BarChart.d.ts +14 -3
- package/dist/charts/BarChart/BarChart.js +20 -12
- package/dist/charts/BarChart/BarChart.js.map +1 -1
- package/dist/charts/BarList/BarList.d.ts +9 -1
- package/dist/charts/BarList/BarList.js +9 -1
- package/dist/charts/BarList/BarList.js.map +1 -1
- package/dist/charts/BigNumber/BigNumber.d.ts +19 -3
- package/dist/charts/BigNumber/BigNumber.js +25 -5
- package/dist/charts/BigNumber/BigNumber.js.map +1 -1
- package/dist/charts/BoxPlot/BoxPlot.js +4 -2
- package/dist/charts/BoxPlot/BoxPlot.js.map +1 -1
- package/dist/charts/BulletChart/BulletChart.js +28 -7
- package/dist/charts/BulletChart/BulletChart.js.map +1 -1
- package/dist/charts/ComposedChart/ComposedChart.d.ts +9 -2
- package/dist/charts/ComposedChart/ComposedChart.js +10 -10
- package/dist/charts/ComposedChart/ComposedChart.js.map +1 -1
- package/dist/charts/FunnelChart/FunnelChart.d.ts +7 -2
- package/dist/charts/FunnelChart/FunnelChart.js +6 -16
- package/dist/charts/FunnelChart/FunnelChart.js.map +1 -1
- package/dist/charts/Heatmap/Heatmap.d.ts +17 -3
- package/dist/charts/Heatmap/Heatmap.js +20 -11
- package/dist/charts/Heatmap/Heatmap.js.map +1 -1
- package/dist/charts/Histogram/Histogram.d.ts +24 -1
- package/dist/charts/Histogram/Histogram.js +43 -11
- package/dist/charts/Histogram/Histogram.js.map +1 -1
- package/dist/charts/LineChart/LineChart.d.ts +9 -2
- package/dist/charts/LineChart/LineChart.js +11 -12
- package/dist/charts/LineChart/LineChart.js.map +1 -1
- package/dist/charts/PieChart/PieChart.d.ts +15 -3
- package/dist/charts/PieChart/PieChart.js +14 -6
- package/dist/charts/PieChart/PieChart.js.map +1 -1
- package/dist/charts/RadarChart/RadarChart.d.ts +15 -3
- package/dist/charts/RadarChart/RadarChart.js +11 -9
- package/dist/charts/RadarChart/RadarChart.js.map +1 -1
- package/dist/charts/RadialChart/RadialChart.d.ts +22 -4
- package/dist/charts/RadialChart/RadialChart.js +28 -6
- package/dist/charts/RadialChart/RadialChart.js.map +1 -1
- package/dist/charts/SankeyChart/SankeyChart.d.ts +16 -4
- package/dist/charts/SankeyChart/SankeyChart.js +14 -6
- package/dist/charts/SankeyChart/SankeyChart.js.map +1 -1
- package/dist/charts/ScatterChart/ScatterChart.d.ts +24 -3
- package/dist/charts/ScatterChart/ScatterChart.js +21 -15
- package/dist/charts/ScatterChart/ScatterChart.js.map +1 -1
- package/dist/charts/Sparkline/Sparkline.js +5 -3
- package/dist/charts/Sparkline/Sparkline.js.map +1 -1
- package/dist/charts/TreemapChart/TreemapChart.d.ts +7 -2
- package/dist/charts/TreemapChart/TreemapChart.js +20 -11
- package/dist/charts/TreemapChart/TreemapChart.js.map +1 -1
- package/dist/charts/WaterfallChart/WaterfallChart.js +4 -2
- package/dist/charts/WaterfallChart/WaterfallChart.js.map +1 -1
- package/dist/charts/index.d.ts +2 -1
- package/dist/charts/lib/export.d.ts +34 -43
- package/dist/charts/lib/export.js +16 -158
- package/dist/charts/lib/export.js.map +1 -1
- package/dist/charts/lib/figure.d.ts +21 -1
- package/dist/charts/lib/figure.js +5 -2
- package/dist/charts/lib/figure.js.map +1 -1
- package/dist/charts/lib/scale.d.ts +23 -0
- package/dist/charts/lib/scale.js +13 -0
- package/dist/charts/lib/scale.js.map +1 -0
- package/dist/charts/lib/selection.d.ts +20 -0
- package/dist/charts/lib/selection.js +18 -0
- package/dist/charts/lib/selection.js.map +1 -0
- package/dist/charts/lib/toolbar.d.ts +1 -0
- package/dist/components/Accordion/Accordion.js +8 -1
- package/dist/components/Accordion/Accordion.js.map +1 -1
- package/dist/components/AppShell/AppShell.d.ts +6 -0
- package/dist/components/AppShell/AppShell.js +34 -4
- package/dist/components/AppShell/AppShell.js.map +1 -1
- package/dist/components/Article/Article.d.ts +11 -4
- package/dist/components/Article/Article.js +13 -0
- package/dist/components/Article/Article.js.map +1 -1
- package/dist/components/AspectRatio/AspectRatio.d.ts +46 -0
- package/dist/components/AspectRatio/AspectRatio.js +33 -0
- package/dist/components/AspectRatio/AspectRatio.js.map +1 -0
- package/dist/components/Avatar/Avatar.d.ts +12 -3
- package/dist/components/Avatar/Avatar.js +6 -1
- package/dist/components/Avatar/Avatar.js.map +1 -1
- package/dist/components/Breadcrumb/Breadcrumb.d.ts +6 -0
- package/dist/components/Breadcrumb/Breadcrumb.js +15 -0
- package/dist/components/Breadcrumb/Breadcrumb.js.map +1 -1
- package/dist/components/Button/Button.js +14 -0
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Calendar/Calendar.d.ts +29 -3
- package/dist/components/Calendar/Calendar.js +60 -12
- package/dist/components/Calendar/Calendar.js.map +1 -1
- package/dist/components/Card/Card.d.ts +6 -0
- package/dist/components/Card/Card.js +1 -1
- package/dist/components/Card/Card.js.map +1 -1
- package/dist/components/Checkbox/Checkbox.d.ts +5 -2
- package/dist/components/Checkbox/Checkbox.js +12 -2
- package/dist/components/Checkbox/Checkbox.js.map +1 -1
- package/dist/components/Code/Code.d.ts +30 -0
- package/dist/components/Code/Code.js +21 -0
- package/dist/components/Code/Code.js.map +1 -0
- package/dist/components/CodeBlock/CodeBlock.d.ts +141 -0
- package/dist/components/CodeBlock/CodeBlock.js +123 -0
- package/dist/components/CodeBlock/CodeBlock.js.map +1 -0
- package/dist/components/Collapsible/Collapsible.js +10 -2
- package/dist/components/Collapsible/Collapsible.js.map +1 -1
- package/dist/components/Combobox/Combobox.d.ts +15 -1
- package/dist/components/Combobox/Combobox.js +25 -5
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Command/Command.d.ts +4 -2
- package/dist/components/Command/Command.js +13 -1
- package/dist/components/Command/Command.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.d.ts +30 -5
- package/dist/components/ContextMenu/ContextMenu.js +32 -2
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/DatePicker/DatePicker.d.ts +36 -4
- package/dist/components/DatePicker/DatePicker.js +55 -12
- package/dist/components/DatePicker/DatePicker.js.map +1 -1
- package/dist/components/DescriptionList/DescriptionList.d.ts +73 -0
- package/dist/components/DescriptionList/DescriptionList.js +35 -0
- package/dist/components/DescriptionList/DescriptionList.js.map +1 -0
- package/dist/components/Diagram/Diagram.d.ts +8 -3
- package/dist/components/Diagram/Diagram.js +82 -10
- package/dist/components/Diagram/Diagram.js.map +1 -1
- package/dist/components/Dialog/Dialog.d.ts +18 -3
- package/dist/components/Dialog/Dialog.js +20 -2
- package/dist/components/Dialog/Dialog.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.d.ts +49 -11
- package/dist/components/DropdownMenu/DropdownMenu.js +32 -2
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/EmptyState/EmptyState.d.ts +25 -1
- package/dist/components/EmptyState/EmptyState.js +3 -1
- package/dist/components/EmptyState/EmptyState.js.map +1 -1
- package/dist/components/ErrorState/ErrorState.d.ts +22 -1
- package/dist/components/ErrorState/ErrorState.js +11 -2
- package/dist/components/ErrorState/ErrorState.js.map +1 -1
- package/dist/components/Field/Field.d.ts +70 -3
- package/dist/components/Field/Field.js +53 -12
- package/dist/components/Field/Field.js.map +1 -1
- package/dist/components/Field/field-control.d.ts +31 -0
- package/dist/components/Field/field-control.js +12 -0
- package/dist/components/Field/field-control.js.map +1 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.js +2 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.js.map +1 -1
- package/dist/components/Heading/Heading.d.ts +62 -0
- package/dist/components/Heading/Heading.js +44 -0
- package/dist/components/Heading/Heading.js.map +1 -0
- package/dist/components/Markdown/Markdown.d.ts +139 -0
- package/dist/components/Markdown/Markdown.js +135 -0
- package/dist/components/Markdown/Markdown.js.map +1 -0
- package/dist/components/Markdown/inline.d.ts +29 -0
- package/dist/components/Markdown/inline.js +74 -0
- package/dist/components/Markdown/inline.js.map +1 -0
- package/dist/components/Markdown/nodes.d.ts +65 -0
- package/dist/components/Markdown/nodes.js +1 -0
- package/dist/components/Markdown/nodes.js.map +1 -0
- package/dist/components/Markdown/parse.d.ts +24 -0
- package/dist/components/Markdown/parse.js +110 -0
- package/dist/components/Markdown/parse.js.map +1 -0
- package/dist/components/Markdown/slug.d.ts +15 -0
- package/dist/components/Markdown/slug.js +7 -0
- package/dist/components/Markdown/slug.js.map +1 -0
- package/dist/components/NativeSelect/NativeSelect.d.ts +6 -0
- package/dist/components/NativeSelect/NativeSelect.js +2 -3
- package/dist/components/NativeSelect/NativeSelect.js.map +1 -1
- package/dist/components/Pagination/Pagination.d.ts +24 -1
- package/dist/components/Pagination/Pagination.js +6 -3
- package/dist/components/Pagination/Pagination.js.map +1 -1
- package/dist/components/Progress/Progress.d.ts +17 -5
- package/dist/components/Progress/Progress.js +18 -6
- package/dist/components/Progress/Progress.js.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.d.ts +6 -0
- package/dist/components/RadioGroup/RadioGroup.js +11 -1
- package/dist/components/RadioGroup/RadioGroup.js.map +1 -1
- package/dist/components/ScrollArea/ScrollArea.d.ts +15 -0
- package/dist/components/ScrollArea/ScrollArea.js +2 -2
- package/dist/components/ScrollArea/ScrollArea.js.map +1 -1
- package/dist/components/SearchableMenu/SearchableMenu.d.ts +11 -2
- package/dist/components/SearchableMenu/SearchableMenu.js +24 -2
- package/dist/components/SearchableMenu/SearchableMenu.js.map +1 -1
- package/dist/components/Select/Select.d.ts +18 -2
- package/dist/components/Select/Select.js +20 -3
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Separator/Separator.d.ts +21 -3
- package/dist/components/Separator/Separator.js +9 -1
- package/dist/components/Separator/Separator.js.map +1 -1
- package/dist/components/Sheet/Sheet.d.ts +3 -0
- package/dist/components/Sheet/Sheet.js +11 -2
- package/dist/components/Sheet/Sheet.js.map +1 -1
- package/dist/components/Skeleton/Skeleton.d.ts +8 -1
- package/dist/components/Skeleton/Skeleton.js +1 -1
- package/dist/components/Skeleton/Skeleton.js.map +1 -1
- package/dist/components/Slider/Slider.d.ts +21 -3
- package/dist/components/Slider/Slider.js +54 -27
- package/dist/components/Slider/Slider.js.map +1 -1
- package/dist/components/Spinner/Spinner.d.ts +8 -0
- package/dist/components/Spinner/Spinner.js +3 -2
- package/dist/components/Spinner/Spinner.js.map +1 -1
- package/dist/components/StatusPill/StatusPill.d.ts +5 -0
- package/dist/components/StatusPill/StatusPill.js +6 -0
- package/dist/components/StatusPill/StatusPill.js.map +1 -1
- package/dist/components/Table/Table.d.ts +6 -0
- package/dist/components/Table/Table.js +3 -1
- package/dist/components/Table/Table.js.map +1 -1
- package/dist/components/Tag/Tag.d.ts +68 -9
- package/dist/components/Tag/Tag.js +50 -10
- package/dist/components/Tag/Tag.js.map +1 -1
- package/dist/components/Text/Text.d.ts +62 -0
- package/dist/components/Text/Text.js +30 -0
- package/dist/components/Text/Text.js.map +1 -0
- package/dist/components/Timestamp/Timestamp.d.ts +59 -0
- package/dist/components/Timestamp/Timestamp.js +56 -0
- package/dist/components/Timestamp/Timestamp.js.map +1 -0
- package/dist/components/Toast/Toast.d.ts +9 -1
- package/dist/components/Toast/Toast.js +27 -4
- package/dist/components/Toast/Toast.js.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.d.ts +6 -1
- package/dist/components/ToggleGroup/ToggleGroup.js +11 -1
- package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
- package/dist/components/Toolbar/Toolbar.d.ts +55 -0
- package/dist/components/Toolbar/Toolbar.js +43 -0
- package/dist/components/Toolbar/Toolbar.js.map +1 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.d.ts +55 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js +175 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js.map +1 -0
- package/dist/diagrams/DataflowFigure/DataflowFigure.d.ts +28 -0
- package/dist/diagrams/DataflowFigure/DataflowFigure.js +148 -0
- package/dist/diagrams/DataflowFigure/DataflowFigure.js.map +1 -0
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.d.ts +78 -0
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js +229 -0
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js.map +1 -0
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.d.ts +106 -0
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js +123 -0
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js.map +1 -0
- package/dist/diagrams/DiagramInspector/DiagramInspector.d.ts +70 -0
- package/dist/diagrams/DiagramInspector/DiagramInspector.js +94 -0
- package/dist/diagrams/DiagramInspector/DiagramInspector.js.map +1 -0
- package/dist/diagrams/DiagramLegend/DiagramLegend.d.ts +42 -0
- package/dist/diagrams/DiagramLegend/DiagramLegend.js +35 -0
- package/dist/diagrams/DiagramLegend/DiagramLegend.js.map +1 -0
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.d.ts +77 -0
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js +106 -0
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js.map +1 -0
- package/dist/diagrams/DiagramToolbar/DiagramToolbar.d.ts +48 -0
- package/dist/diagrams/DiagramToolbar/DiagramToolbar.js +53 -0
- package/dist/diagrams/DiagramToolbar/DiagramToolbar.js.map +1 -0
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.d.ts +31 -0
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js +234 -0
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js.map +1 -0
- package/dist/diagrams/SequenceFigure/SequenceFigure.d.ts +38 -0
- package/dist/diagrams/SequenceFigure/SequenceFigure.js +261 -0
- package/dist/diagrams/SequenceFigure/SequenceFigure.js.map +1 -0
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.d.ts +36 -0
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js +259 -0
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js.map +1 -0
- package/dist/diagrams/index.d.ts +17 -0
- package/dist/diagrams/index.js +29 -0
- package/dist/diagrams/index.js.map +1 -0
- package/dist/diagrams/lib/dev.d.ts +61 -0
- package/dist/diagrams/lib/dev.js +96 -0
- package/dist/diagrams/lib/dev.js.map +1 -0
- package/dist/diagrams/lib/frame.d.ts +138 -0
- package/dist/diagrams/lib/frame.js +131 -0
- package/dist/diagrams/lib/frame.js.map +1 -0
- package/dist/diagrams/lib/geometry.d.ts +114 -0
- package/dist/diagrams/lib/geometry.js +113 -0
- package/dist/diagrams/lib/geometry.js.map +1 -0
- package/dist/diagrams/lib/legend.d.ts +32 -0
- package/dist/diagrams/lib/legend.js +84 -0
- package/dist/diagrams/lib/legend.js.map +1 -0
- package/dist/diagrams/lib/marks.d.ts +207 -0
- package/dist/diagrams/lib/marks.js +480 -0
- package/dist/diagrams/lib/marks.js.map +1 -0
- package/dist/diagrams/lib/route.d.ts +85 -0
- package/dist/diagrams/lib/route.js +178 -0
- package/dist/diagrams/lib/route.js.map +1 -0
- package/dist/diagrams/lib/wires.d.ts +59 -0
- package/dist/diagrams/lib/wires.js +90 -0
- package/dist/diagrams/lib/wires.js.map +1 -0
- package/dist/diagrams/spec.d.ts +405 -0
- package/dist/diagrams/spec.js +1 -0
- package/dist/diagrams/spec.js.map +1 -0
- package/dist/index.d.ts +16 -4
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/keyframes.css +57 -3
- package/dist/lib/overlay-container.d.ts +14 -4
- package/dist/lib/overlay-container.js.map +1 -1
- package/dist/lib/svg-export.d.ts +138 -0
- package/dist/lib/svg-export.js +198 -0
- package/dist/lib/svg-export.js.map +1 -0
- package/dist/lib/warn.d.ts +73 -0
- package/dist/lib/warn.js +38 -0
- package/dist/lib/warn.js.map +1 -0
- package/dist/semantic.css +51 -6
- package/dist/styles.css +1 -1
- package/dist/tokens.css +20 -2
- package/dist/tokens.d.ts +17 -1
- package/dist/tokens.js +121 -36
- package/dist/tokens.json +121 -36
- package/llms.txt +35 -0
- package/package.json +9 -2
- package/skills/misoto22-design/SKILL.md +21 -7
- package/skills/misoto22-design/evals/evals.json +84 -0
- package/skills/misoto22-design/rules/a11y.md +55 -3
- package/skills/misoto22-design/rules/forms.md +5 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import {} from "react";
|
|
4
4
|
import { Treemap as RechartsTreemap } from "recharts";
|
|
5
5
|
import { ChartContainer, cssName } from "../lib/chart.js";
|
|
6
6
|
import { ChartFigure } from "../lib/figure.js";
|
|
7
|
+
import {} from "../lib/empty.js";
|
|
7
8
|
import { ChartTooltip, ChartTooltipContent } from "../lib/tooltip.js";
|
|
8
9
|
function TreemapChart({
|
|
9
10
|
config = {},
|
|
@@ -18,10 +19,10 @@ function TreemapChart({
|
|
|
18
19
|
showLabels = true,
|
|
19
20
|
children,
|
|
20
21
|
chartProps,
|
|
21
|
-
hideDataTable = false
|
|
22
|
+
hideDataTable = false,
|
|
23
|
+
empty
|
|
22
24
|
}) {
|
|
23
|
-
const
|
|
24
|
-
const leaves = flatten(data);
|
|
25
|
+
const leaves = flatten(data, dataKey);
|
|
25
26
|
return /* @__PURE__ */ jsx(
|
|
26
27
|
ChartFigure,
|
|
27
28
|
{
|
|
@@ -34,6 +35,8 @@ function TreemapChart({
|
|
|
34
35
|
rowKey: "path",
|
|
35
36
|
columns: [{ key: dataKey, label: "Value" }]
|
|
36
37
|
},
|
|
38
|
+
isEmpty: data.length === 0,
|
|
39
|
+
empty,
|
|
37
40
|
children: /* @__PURE__ */ jsx(ChartContainer, { config, children: /* @__PURE__ */ jsx(
|
|
38
41
|
RechartsTreemap,
|
|
39
42
|
{
|
|
@@ -42,7 +45,7 @@ function TreemapChart({
|
|
|
42
45
|
nameKey: "name",
|
|
43
46
|
aspectRatio,
|
|
44
47
|
isAnimationActive: false,
|
|
45
|
-
content: /* @__PURE__ */ jsx(TreemapTile, {
|
|
48
|
+
content: /* @__PURE__ */ jsx(TreemapTile, { variant, config, showLabels }),
|
|
46
49
|
...chartProps,
|
|
47
50
|
children
|
|
48
51
|
}
|
|
@@ -50,15 +53,22 @@ function TreemapChart({
|
|
|
50
53
|
}
|
|
51
54
|
);
|
|
52
55
|
}
|
|
53
|
-
function flatten(nodes, prefix = "") {
|
|
56
|
+
function flatten(nodes, dataKey, prefix = "") {
|
|
54
57
|
return nodes.flatMap((node) => {
|
|
55
58
|
const path = prefix ? `${prefix} \u203A ${node.name}` : node.name;
|
|
56
|
-
if (node.children?.length) return flatten(node.children, path);
|
|
57
|
-
|
|
59
|
+
if (node.children?.length) return flatten(node.children, dataKey, path);
|
|
60
|
+
const value = Number(node[dataKey]);
|
|
61
|
+
if (Number.isFinite(value) && value > 0) return [{ ...node, path }];
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
...node,
|
|
65
|
+
path,
|
|
66
|
+
[dataKey]: Number.isFinite(value) ? `${value.toLocaleString()} \u2014 not drawn` : "not drawn"
|
|
67
|
+
}
|
|
68
|
+
];
|
|
58
69
|
});
|
|
59
70
|
}
|
|
60
71
|
function TreemapTile({
|
|
61
|
-
chartId,
|
|
62
72
|
variant,
|
|
63
73
|
config,
|
|
64
74
|
showLabels,
|
|
@@ -96,8 +106,7 @@ function TreemapTile({
|
|
|
96
106
|
children: name
|
|
97
107
|
}
|
|
98
108
|
),
|
|
99
|
-
/* @__PURE__ */ jsx("title", { children: name })
|
|
100
|
-
/* @__PURE__ */ jsx("desc", { children: chartId })
|
|
109
|
+
/* @__PURE__ */ jsx("title", { children: name })
|
|
101
110
|
] });
|
|
102
111
|
}
|
|
103
112
|
function Tooltip({ variant, roundness }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/charts/TreemapChart/TreemapChart.tsx"],"sourcesContent":["'use client'\n\nimport { useId, type ComponentProps, type ReactNode } from 'react'\nimport { Treemap as RechartsTreemap } from 'recharts'\nimport { ChartContainer, cssName, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/**\n * How a tile is painted.\n *\n * `ramp` walks the series ramp so siblings separate by lightness; `nested`\n * darkens by DEPTH instead, which is the right encoding when the tree has more\n * than one level and the question is \"what is inside what\".\n */\nexport type TreemapVariant = 'ramp' | 'nested'\n\n/** A node in the tree. Children make it a branch. */\nexport interface TreemapNode {\n name: string\n /** Leaf value. Omit on a branch — a branch is the sum of its children. */\n size?: number\n children?: TreemapNode[]\n [key: string]: unknown\n}\n\n/** The raw Recharts Treemap props a consumer may still reach for. */\nexport type TreemapExtras = Omit<ComponentProps<typeof RechartsTreemap>, 'data' | 'dataKey'>\n\nexport interface TreemapChartProps {\n /** Tile names → their label and paint. Optional: the ramp covers unnamed tiles. */\n config?: ChartConfig\n /** The tree. One level is a flat set of tiles; two is a nested treemap. */\n data: TreemapNode[]\n /**\n * What the chart shows, in a sentence a reader could act on. Required, and\n * announced to a screen reader even when it is not printed.\n */\n title: string\n /** Prints the title above the plot instead of hiding it from sight. */\n showTitle?: boolean\n /** A line under the title — the unit, the window, the caveat. */\n description?: ReactNode\n /** Merged onto the figure, last, so a call site can size or space it. */\n className?: string\n variant?: TreemapVariant\n /** The leaf field holding each tile's number. */\n dataKey?: string\n /** How square the tiles are pushed to be. Recharts' own squarify parameter. */\n aspectRatio?: number\n /** Prints each tile's name on it, where the tile is big enough to hold it. */\n showLabels?: boolean\n /** The hover panel. */\n children?: ReactNode\n chartProps?: TreemapExtras\n /** Drops the hidden table view. Only correct when the page prints the data itself. */\n hideDataTable?: boolean\n}\n\n/**\n * Part of a whole, when the whole has too many parts for a pie — and the parts\n * nest.\n *\n * A treemap encodes value as AREA, which the eye reads worse than length but\n * far better than angle, and it is the only form here that stays readable at\n * fifty items. Two rules make it honest: no negative values (an area cannot be\n * negative), and the tiles have to sum to something a reader recognises as the\n * whole.\n *\n * Reach for `<BarChart>` when there are under a dozen items and the ranking\n * matters — a bar's length is the more precise encoding, and a treemap's\n * layout deliberately does not order its tiles by value alone.\n *\n * @example\n * <TreemapChart title=\"Bundle size by package\" data={packages} showLabels>\n * <TreemapChart.Tooltip />\n * </TreemapChart>\n */\nexport function TreemapChart({\n config = {},\n data,\n title,\n showTitle,\n description,\n className,\n variant = 'ramp',\n dataKey = 'size',\n aspectRatio = 4 / 3,\n showLabels = true,\n children,\n chartProps,\n hideDataTable = false,\n}: TreemapChartProps) {\n const chartId = useId().replace(/:/g, '')\n\n // Flattened for the table view, because a nested tree read aloud row by row\n // is not something anyone can follow — the leaves are the facts.\n const leaves = flatten(data)\n\n return (\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : {\n rows: leaves,\n rowKey: 'path',\n columns: [{ key: dataKey, label: 'Value' }],\n }\n }\n >\n <ChartContainer config={config}>\n <RechartsTreemap\n data={data}\n dataKey={dataKey}\n nameKey=\"name\"\n aspectRatio={aspectRatio}\n isAnimationActive={false}\n content={\n <TreemapTile chartId={chartId} variant={variant} config={config} showLabels={showLabels} />\n }\n {...chartProps}\n >\n {children}\n </RechartsTreemap>\n </ChartContainer>\n </ChartFigure>\n )\n}\n\n/** Every leaf, with the path that names it. */\nfunction flatten(nodes: TreemapNode[], prefix = ''): Record<string, unknown>[] {\n return nodes.flatMap((node) => {\n const path = prefix ? `${prefix} › ${node.name}` : node.name\n if (node.children?.length) return flatten(node.children, path)\n return [{ ...node, path }]\n })\n}\n\ninterface TileProps {\n chartId: string\n variant: TreemapVariant\n config: ChartConfig\n showLabels: boolean\n // Recharts hands the geometry in as loose props on the cloned element.\n x?: number\n y?: number\n width?: number\n height?: number\n depth?: number\n index?: number\n name?: string\n}\n\n/**\n * One tile.\n *\n * The gap between tiles is a surface-coloured stroke rather than a smaller\n * rect, so the tiles still tile — a treemap whose parts do not touch stops\n * reading as a partition of one whole.\n */\nfunction TreemapTile({\n chartId,\n variant,\n config,\n showLabels,\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n depth = 1,\n index = 0,\n name = '',\n}: TileProps) {\n if (width <= 0 || height <= 0) return null\n\n const slot =\n variant === 'nested'\n ? `var(--series-${Math.min(8, depth * 2)})`\n : name in config\n ? `var(--color-${cssName(name)}-0)`\n : `var(--series-${(index % 8) + 1})`\n\n // Only label a tile with room for one. A clipped word is worse than none.\n const roomForLabel = showLabels && width > 56 && height > 26\n\n return (\n <g>\n <rect\n x={x}\n y={y}\n width={width}\n height={height}\n fill={slot}\n stroke=\"var(--chart-surface)\"\n strokeWidth={2}\n />\n {roomForLabel && (\n <text\n x={x + 8}\n y={y + 18}\n className=\"fill-(--chart-surface) text-[11px] font-medium\"\n style={{ pointerEvents: 'none' }}\n >\n {name}\n </text>\n )}\n {/* A hidden id so the tooltip can key off the same name the tile paints. */}\n <title>{name}</title>\n <desc>{chartId}</desc>\n </g>\n )\n}\n\n/** The hover panel. No heading: the tile's own name is the row label. */\nfunction Tooltip({ variant, roundness }: ChartTooltipSlotProps) {\n return (\n <ChartTooltip\n cursor={false}\n content={<ChartTooltipContent nameKey=\"name\" hideLabel roundness={roundness} variant={variant} />}\n />\n )\n}\n\nTreemapChart.Tooltip = Tooltip\n\nexport default TreemapChart\n"],"mappings":";AA4HY,cAoER,YApEQ;AA1HZ,SAAS,aAAkD;AAC3D,SAAS,WAAW,uBAAuB;AAC3C,SAAS,gBAAgB,eAAiC;AAC1D,SAAS,mBAAmB;AAC5B,SAAS,cAAc,2BAA2B;AAyE3C,SAAS,aAAa;AAAA,EAC3B,SAAS,CAAC;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,UAAU;AAAA,EACV,cAAc,IAAI;AAAA,EAClB,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA,gBAAgB;AAClB,GAAsB;AACpB,QAAM,UAAU,MAAM,EAAE,QAAQ,MAAM,EAAE;AAIxC,QAAM,SAAS,QAAQ,IAAI;AAE3B,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OACE,gBACI,QACA;AAAA,QACE,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAS,CAAC,EAAE,KAAK,SAAS,OAAO,QAAQ,CAAC;AAAA,MAC5C;AAAA,MAGN,8BAAC,kBAAe,QACd;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA,SAAQ;AAAA,UACR;AAAA,UACA,mBAAmB;AAAA,UACnB,SACE,oBAAC,eAAY,SAAkB,SAAkB,QAAgB,YAAwB;AAAA,UAE1F,GAAG;AAAA,UAEH;AAAA;AAAA,MACH,GACF;AAAA;AAAA,EACF;AAEJ;AAGA,SAAS,QAAQ,OAAsB,SAAS,IAA+B;AAC7E,SAAO,MAAM,QAAQ,CAAC,SAAS;AAC7B,UAAM,OAAO,SAAS,GAAG,MAAM,WAAM,KAAK,IAAI,KAAK,KAAK;AACxD,QAAI,KAAK,UAAU,OAAQ,QAAO,QAAQ,KAAK,UAAU,IAAI;AAC7D,WAAO,CAAC,EAAE,GAAG,MAAM,KAAK,CAAC;AAAA,EAC3B,CAAC;AACH;AAwBA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AACT,GAAc;AACZ,MAAI,SAAS,KAAK,UAAU,EAAG,QAAO;AAEtC,QAAM,OACJ,YAAY,WACR,gBAAgB,KAAK,IAAI,GAAG,QAAQ,CAAC,CAAC,MACtC,QAAQ,SACN,eAAe,QAAQ,IAAI,CAAC,QAC5B,gBAAiB,QAAQ,IAAK,CAAC;AAGvC,QAAM,eAAe,cAAc,QAAQ,MAAM,SAAS;AAE1D,SACE,qBAAC,OACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,QAAO;AAAA,QACP,aAAa;AAAA;AAAA,IACf;AAAA,IACC,gBACC;AAAA,MAAC;AAAA;AAAA,QACC,GAAG,IAAI;AAAA,QACP,GAAG,IAAI;AAAA,QACP,WAAU;AAAA,QACV,OAAO,EAAE,eAAe,OAAO;AAAA,QAE9B;AAAA;AAAA,IACH;AAAA,IAGF,oBAAC,WAAO,gBAAK;AAAA,IACb,oBAAC,UAAM,mBAAQ;AAAA,KACjB;AAEJ;AAGA,SAAS,QAAQ,EAAE,SAAS,UAAU,GAA0B;AAC9D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,QAAQ;AAAA,MACR,SAAS,oBAAC,uBAAoB,SAAQ,QAAO,WAAS,MAAC,WAAsB,SAAkB;AAAA;AAAA,EACjG;AAEJ;AAEA,aAAa,UAAU;AAEvB,IAAO,uBAAQ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/charts/TreemapChart/TreemapChart.tsx"],"sourcesContent":["'use client'\n\nimport { type ComponentProps, type ReactNode } from 'react'\nimport { Treemap as RechartsTreemap } from 'recharts'\nimport { ChartContainer, cssName, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { type ChartEmptyProps } from '../lib/empty'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/**\n * How a tile is painted.\n *\n * `ramp` walks the series ramp so siblings separate by lightness; `nested`\n * darkens by DEPTH instead, which is the right encoding when the tree has more\n * than one level and the question is \"what is inside what\".\n */\nexport type TreemapVariant = 'ramp' | 'nested'\n\n/** A node in the tree. Children make it a branch. */\nexport interface TreemapNode {\n name: string\n /** Leaf value. Omit on a branch — a branch is the sum of its children. */\n size?: number\n children?: TreemapNode[]\n [key: string]: unknown\n}\n\n/** The raw Recharts Treemap props a consumer may still reach for. */\nexport type TreemapExtras = Omit<ComponentProps<typeof RechartsTreemap>, 'data' | 'dataKey'>\n\nexport interface TreemapChartProps {\n /** Tile names → their label and paint. Optional: the ramp covers unnamed tiles. */\n config?: ChartConfig\n /** The tree. One level is a flat set of tiles; two is a nested treemap. */\n data: TreemapNode[]\n /**\n * What the chart shows, in a sentence a reader could act on. Required, and\n * announced to a screen reader even when it is not printed.\n */\n title: string\n /** Prints the title above the plot instead of hiding it from sight. */\n showTitle?: boolean\n /** A line under the title — the unit, the window, the caveat. */\n description?: ReactNode\n /** Merged onto the figure, last, so a call site can size or space it. */\n className?: string\n variant?: TreemapVariant\n /** The leaf field holding each tile's number. */\n dataKey?: string\n /** How square the tiles are pushed to be. Recharts' own squarify parameter. */\n aspectRatio?: number\n /** Prints each tile's name on it, where the tile is big enough to hold it. */\n showLabels?: boolean\n /** The hover panel. */\n children?: ReactNode\n chartProps?: TreemapExtras\n /** Drops the hidden table view. Only correct when the page prints the data itself. */\n hideDataTable?: boolean\n /**\n * What the chart shows when it has nothing to draw. `false` keeps the empty\n * plot, for a chart whose emptiness is itself the reading.\n */\n empty?: ChartEmptyProps | false\n}\n\n/**\n * Part of a whole, when the whole has too many parts for a pie — and the parts\n * nest.\n *\n * A treemap encodes value as AREA, which the eye reads worse than length but\n * far better than angle, and it is the only form here that stays readable at\n * fifty items. Two rules make it honest: no negative values (an area cannot be\n * negative), and the tiles have to sum to something a reader recognises as the\n * whole.\n *\n * Reach for `<BarChart>` when there are under a dozen items and the ranking\n * matters — a bar's length is the more precise encoding, and a treemap's\n * layout deliberately does not order its tiles by value alone.\n *\n * @example\n * <TreemapChart title=\"Bundle size by package\" data={packages} showLabels>\n * <TreemapChart.Tooltip />\n * </TreemapChart>\n */\nexport function TreemapChart({\n config = {},\n data,\n title,\n showTitle,\n description,\n className,\n variant = 'ramp',\n dataKey = 'size',\n aspectRatio = 4 / 3,\n showLabels = true,\n children,\n chartProps,\n hideDataTable = false,\n empty,\n}: TreemapChartProps) {\n // Flattened for the table view, because a nested tree read aloud row by row\n // is not something anyone can follow — the leaves are the facts.\n const leaves = flatten(data, dataKey)\n\n return (\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : {\n rows: leaves,\n rowKey: 'path',\n columns: [{ key: dataKey, label: 'Value' }],\n }\n }\n isEmpty={data.length === 0}\n empty={empty}\n >\n <ChartContainer config={config}>\n <RechartsTreemap\n data={data}\n dataKey={dataKey}\n nameKey=\"name\"\n aspectRatio={aspectRatio}\n isAnimationActive={false}\n content={\n <TreemapTile variant={variant} config={config} showLabels={showLabels} />\n }\n {...chartProps}\n >\n {children}\n </RechartsTreemap>\n </ChartContainer>\n </ChartFigure>\n )\n}\n\n/**\n * Every leaf, with the path that names it and whether the picture can hold it.\n *\n * A tile's AREA is its value, so a leaf at zero or below has no area to be\n * drawn at: the layout gives it no width, the tile is dropped, and the leaf\n * disappears from the picture while staying in this table. The table is the\n * only place left that can say so, and saying nothing would leave the two views\n * of one tree disagreeing about how many leaves there are.\n */\nfunction flatten(\n nodes: TreemapNode[],\n dataKey: string,\n prefix = '',\n): Record<string, unknown>[] {\n return nodes.flatMap((node) => {\n const path = prefix ? `${prefix} › ${node.name}` : node.name\n if (node.children?.length) return flatten(node.children, dataKey, path)\n\n const value = Number(node[dataKey])\n if (Number.isFinite(value) && value > 0) return [{ ...node, path }]\n return [\n {\n ...node,\n path,\n [dataKey]: Number.isFinite(value)\n ? `${value.toLocaleString()} — not drawn`\n : 'not drawn',\n },\n ]\n })\n}\n\ninterface TileProps {\n variant: TreemapVariant\n config: ChartConfig\n showLabels: boolean\n // Recharts hands the geometry in as loose props on the cloned element.\n x?: number\n y?: number\n width?: number\n height?: number\n depth?: number\n index?: number\n name?: string\n}\n\n/**\n * One tile.\n *\n * The gap between tiles is a surface-coloured stroke rather than a smaller\n * rect, so the tiles still tile — a treemap whose parts do not touch stops\n * reading as a partition of one whole.\n */\nfunction TreemapTile({\n variant,\n config,\n showLabels,\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n depth = 1,\n index = 0,\n name = '',\n}: TileProps) {\n if (width <= 0 || height <= 0) return null\n\n const slot =\n variant === 'nested'\n ? `var(--series-${Math.min(8, depth * 2)})`\n : name in config\n ? `var(--color-${cssName(name)}-0)`\n : `var(--series-${(index % 8) + 1})`\n\n // Only label a tile with room for one. A clipped word is worse than none.\n const roomForLabel = showLabels && width > 56 && height > 26\n\n return (\n <g>\n <rect\n x={x}\n y={y}\n width={width}\n height={height}\n fill={slot}\n stroke=\"var(--chart-surface)\"\n strokeWidth={2}\n />\n {roomForLabel && (\n <text\n x={x + 8}\n y={y + 18}\n className=\"fill-(--chart-surface) text-[11px] font-medium\"\n style={{ pointerEvents: 'none' }}\n >\n {name}\n </text>\n )}\n {/* The tile's whole accessible reading. `<desc>` used to carry the chart's\n generated id beside it — and `<desc>` IS the accessible description,\n so every tile announced its name followed by an opaque string nothing\n on the page could explain. */}\n <title>{name}</title>\n </g>\n )\n}\n\n/** The hover panel. No heading: the tile's own name is the row label. */\nfunction Tooltip({ variant, roundness }: ChartTooltipSlotProps) {\n return (\n <ChartTooltip\n cursor={false}\n content={<ChartTooltipContent nameKey=\"name\" hideLabel roundness={roundness} variant={variant} />}\n />\n )\n}\n\nTreemapChart.Tooltip = Tooltip\n\nexport default TreemapChart\n"],"mappings":";AAmIY,cAyFR,YAzFQ;AAjIZ,eAAoD;AACpD,SAAS,WAAW,uBAAuB;AAC3C,SAAS,gBAAgB,eAAiC;AAC1D,SAAS,mBAAmB;AAC5B,eAAqC;AACrC,SAAS,cAAc,2BAA2B;AA8E3C,SAAS,aAAa;AAAA,EAC3B,SAAS,CAAC;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,UAAU;AAAA,EACV,cAAc,IAAI;AAAA,EAClB,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB;AACF,GAAsB;AAGpB,QAAM,SAAS,QAAQ,MAAM,OAAO;AAEpC,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OACE,gBACI,QACA;AAAA,QACE,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAS,CAAC,EAAE,KAAK,SAAS,OAAO,QAAQ,CAAC;AAAA,MAC5C;AAAA,MAEN,SAAS,KAAK,WAAW;AAAA,MACzB;AAAA,MAEA,8BAAC,kBAAe,QACd;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA,SAAQ;AAAA,UACR;AAAA,UACA,mBAAmB;AAAA,UACnB,SACE,oBAAC,eAAY,SAAkB,QAAgB,YAAwB;AAAA,UAExE,GAAG;AAAA,UAEH;AAAA;AAAA,MACH,GACF;AAAA;AAAA,EACF;AAEJ;AAWA,SAAS,QACP,OACA,SACA,SAAS,IACkB;AAC3B,SAAO,MAAM,QAAQ,CAAC,SAAS;AAC7B,UAAM,OAAO,SAAS,GAAG,MAAM,WAAM,KAAK,IAAI,KAAK,KAAK;AACxD,QAAI,KAAK,UAAU,OAAQ,QAAO,QAAQ,KAAK,UAAU,SAAS,IAAI;AAEtE,UAAM,QAAQ,OAAO,KAAK,OAAO,CAAC;AAClC,QAAI,OAAO,SAAS,KAAK,KAAK,QAAQ,EAAG,QAAO,CAAC,EAAE,GAAG,MAAM,KAAK,CAAC;AAClE,WAAO;AAAA,MACL;AAAA,QACE,GAAG;AAAA,QACH;AAAA,QACA,CAAC,OAAO,GAAG,OAAO,SAAS,KAAK,IAC5B,GAAG,MAAM,eAAe,CAAC,sBACzB;AAAA,MACN;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAuBA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AACT,GAAc;AACZ,MAAI,SAAS,KAAK,UAAU,EAAG,QAAO;AAEtC,QAAM,OACJ,YAAY,WACR,gBAAgB,KAAK,IAAI,GAAG,QAAQ,CAAC,CAAC,MACtC,QAAQ,SACN,eAAe,QAAQ,IAAI,CAAC,QAC5B,gBAAiB,QAAQ,IAAK,CAAC;AAGvC,QAAM,eAAe,cAAc,QAAQ,MAAM,SAAS;AAE1D,SACE,qBAAC,OACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,QAAO;AAAA,QACP,aAAa;AAAA;AAAA,IACf;AAAA,IACC,gBACC;AAAA,MAAC;AAAA;AAAA,QACC,GAAG,IAAI;AAAA,QACP,GAAG,IAAI;AAAA,QACP,WAAU;AAAA,QACV,OAAO,EAAE,eAAe,OAAO;AAAA,QAE9B;AAAA;AAAA,IACH;AAAA,IAMF,oBAAC,WAAO,gBAAK;AAAA,KACf;AAEJ;AAGA,SAAS,QAAQ,EAAE,SAAS,UAAU,GAA0B;AAC9D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,QAAQ;AAAA,MACR,SAAS,oBAAC,uBAAoB,SAAQ,QAAO,WAAS,MAAC,WAAsB,SAAkB;AAAA;AAAA,EACjG;AAEJ;AAEA,aAAa,UAAU;AAEvB,IAAO,uBAAQ;","names":[]}
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from "recharts";
|
|
18
18
|
import { ChartContainer } from "../lib/chart.js";
|
|
19
19
|
import { ChartFigure } from "../lib/figure.js";
|
|
20
|
-
import {
|
|
20
|
+
import {} from "../lib/empty.js";
|
|
21
21
|
import { ChartBackground } from "../lib/background.js";
|
|
22
22
|
import { ChartTooltip } from "../lib/tooltip.js";
|
|
23
23
|
import { BarHatchedFill, SeriesGradient } from "../lib/paint.js";
|
|
@@ -103,7 +103,9 @@ function WaterfallChart({
|
|
|
103
103
|
{ key: "total", label: "Running total" }
|
|
104
104
|
]
|
|
105
105
|
},
|
|
106
|
-
|
|
106
|
+
isEmpty,
|
|
107
|
+
empty,
|
|
108
|
+
children: /* @__PURE__ */ jsx(ChartContainer, { config, children: /* @__PURE__ */ jsx(
|
|
107
109
|
RechartsBarChart,
|
|
108
110
|
{
|
|
109
111
|
id: chartId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/charts/WaterfallChart/WaterfallChart.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n useContext,\n useId,\n useMemo,\n type ComponentProps,\n type ReactNode,\n} from 'react'\nimport {\n Bar as RechartsBar,\n BarChart as RechartsBarChart,\n CartesianGrid,\n ReferenceLine as RechartsReferenceLine,\n XAxis as RechartsXAxis,\n YAxis as RechartsYAxis,\n usePlotArea,\n} from 'recharts'\nimport { ChartContainer, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { ChartEmpty, type ChartEmptyProps } from '../lib/empty'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartTooltip } from '../lib/tooltip'\nimport { BarHatchedFill, SeriesGradient } from '../lib/paint'\nimport { Annotation, ReferenceBand, ReferenceLine } from '../lib/annotations'\nimport { axisLabel } from '../lib/axis'\nimport { defaultTick } from '../lib/format'\nimport { cn } from '../../lib/cn'\nimport type { ChartXAxisProps, ChartYAxisProps } from '../BarChart/BarChart'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/** The series a chart with only one of them falls back to. */\nconst DEFAULT_CONFIG: ChartConfig = { value: { label: 'Change' } }\n\nconst CHART_MARGIN = { top: 20, right: 12, bottom: 0, left: 12 }\n\n/** The corner every bar rounds to. */\nconst BAR_RADIUS = 2\n\n/** How a step moves the running total. */\nexport type WaterfallStepType = 'delta' | 'total'\n\n/** Which way a bar goes, which is what its texture encodes. */\nexport type WaterfallDirection = 'increase' | 'decrease' | 'total'\n\nexport interface WaterfallStep {\n /** The step's name on the category axis. Doubles as its key. */\n name: string\n /**\n * The signed change this step makes, or — on a `total` step — the absolute\n * figure the running total is set to.\n *\n * Omit it on a `total` and the running total is used, which is the right\n * default for the closing bar: an end total typed by hand can disagree with\n * the deltas above it, and the chart would draw the disagreement without\n * saying anything.\n */\n value?: number\n /**\n * `delta` (the default) adds `value` to the running total and floats the bar\n * where that lands. `total` plants a bar on the baseline — an opening\n * balance, a subtotal, a closing figure.\n */\n type?: WaterfallStepType\n}\n\n/** A step with its span, its direction and its running total worked out. */\ninterface ResolvedStep extends WaterfallStep {\n type: WaterfallStepType\n /**\n * What this step moved the total by. On a `total` step that is the gap it\n * closes — zero whenever the deltas above it already add up to it.\n */\n change: number\n /** The running total once this step has been applied. */\n total: number\n /**\n * The bar's two ends on the value axis, LOW first.\n *\n * Ascending because Recharts reads a floating bar's range that way and\n * collapses a descending one to nothing — which is every decrease in the\n * cascade. Which end the running total is at is recovered from `total`\n * rather than from the order here.\n */\n span: [number, number]\n direction: WaterfallDirection\n}\n\n/** A bar's two ends, low first — see `ResolvedStep.span`. */\nfunction ascending(a: number, b: number): [number, number] {\n return a <= b ? [a, b] : [b, a]\n}\n\n/** The steps, walked in order, each carrying the total it leaves behind. */\nfunction resolve(data: WaterfallStep[]): ResolvedStep[] {\n let running = 0\n\n return data.map((step) => {\n const type = step.type ?? 'delta'\n\n if (type === 'total') {\n const total = step.value ?? running\n const resolved: ResolvedStep = {\n ...step,\n type,\n change: total - running,\n total,\n span: ascending(0, total),\n direction: 'total',\n }\n running = total\n return resolved\n }\n\n const change = step.value ?? 0\n const from = running\n running += change\n\n return {\n ...step,\n type,\n change,\n total: running,\n span: ascending(from, running),\n direction: change < 0 ? 'decrease' : 'increase',\n }\n })\n}\n\ninterface WaterfallContextValue {\n config: ChartConfig\n seriesKey: string\n steps: ResolvedStep[]\n formatValue: (value: number) => string\n}\n\nconst WaterfallContext = createContext<WaterfallContextValue | null>(null)\n\nfunction useWaterfall(): WaterfallContextValue {\n const context = useContext(WaterfallContext)\n if (!context) throw new Error('A waterfall part must be rendered inside <WaterfallChart>')\n return context\n}\n\nexport interface WaterfallChartProps {\n /**\n * The single series — its label and its paint. Only the FIRST entry is read;\n * a waterfall has one quantity and as many bars as it has steps.\n */\n config?: ChartConfig\n /**\n * The steps, in the order they are applied. Order is the arithmetic: the\n * chart does not sort, because a different order is a different total at\n * every intermediate bar.\n */\n data: WaterfallStep[]\n /**\n * What the chart shows, in a sentence a reader could act on. Required, and\n * announced to a screen reader even when it is not printed.\n */\n title: string\n /** Prints the title above the plot instead of hiding it from sight. */\n showTitle?: boolean\n /** A line under the title — the unit, the window, the caveat. */\n description?: ReactNode\n /** The composed parts — axes, grid, tooltip, and `<WaterfallChart.Bars>`. */\n children: ReactNode\n /** Merged onto the figure, last, so a call site can size or space it. */\n className?: string\n /** Escape hatch onto the raw Recharts chart element. */\n chartProps?: ComponentProps<typeof RechartsBarChart>\n /** Formats every number the chart prints — ticks, labels, tooltip, table. */\n formatValue?: (value: number) => string\n /** Drops the hidden table view. Only correct when the page prints the data itself. */\n hideDataTable?: boolean\n /**\n * What the chart shows when it has nothing to draw. `false` keeps the axes,\n * for a chart whose emptiness is itself the reading.\n */\n empty?: ChartEmptyProps | false\n}\n\n/**\n * How a total got from one figure to another — an opening balance, the signed\n * contributions that moved it, and where it closed.\n *\n * The form for \"why did this change\", which a pair of bars cannot answer and a\n * pie chart answers wrongly, because contributions can be NEGATIVE and a slice\n * cannot. Reach for `<BarChart>` when the parts do not have to add up to the\n * gap between two totals, and for `<FunnelChart>` when the quantity only ever\n * shrinks.\n *\n * **The connectors are the claim to be careful about.** They draw the steps as\n * a sequence — this happened, then this — and most breakdowns are not\n * sequential at all: churn and expansion in the same month are simultaneous,\n * and the order they are listed in is an editorial choice. The arithmetic\n * survives any order; the STORY does not, and a reader will take the leftmost\n * bar as the first cause. Two related traps: the intermediate bars are floating\n * lengths read against no baseline, so a small step high up the cascade is hard\n * to compare with a large one near zero; and any step that is itself a net of\n * two larger opposing movements is invisible as such. Where the order is\n * arbitrary, say so in the `description`.\n *\n * Recharts earns its place for the axes, grid and tooltip. Each bar is a\n * floating range — from the running total to the new one — with a custom shape\n * over it, because Recharts has no waterfall mark and the connectors have to be\n * drawn from the same geometry as the bars they join.\n *\n * @example\n * <WaterfallChart title=\"Revenue bridge, FY25\" data={steps}>\n * <WaterfallChart.Grid />\n * <WaterfallChart.XAxis />\n * <WaterfallChart.YAxis label=\"AUD\" />\n * <WaterfallChart.Tooltip />\n * <WaterfallChart.Bars showValues />\n * </WaterfallChart>\n *\n * @example\n * const steps = [\n * { name: 'FY24', value: 1200, type: 'total' as const },\n * { name: 'New', value: 340 },\n * { name: 'Churn', value: -180 },\n * // No value: the closing bar is whatever the deltas add up to.\n * { name: 'FY25', type: 'total' as const },\n * ]\n */\nexport function WaterfallChart({\n config = DEFAULT_CONFIG,\n data,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n formatValue = defaultTick,\n hideDataTable = false,\n empty,\n}: WaterfallChartProps) {\n const chartId = useId().replace(/:/g, '')\n const seriesKey = Object.keys(config)[0] ?? 'value'\n const steps = useMemo(() => resolve(data), [data])\n\n const context = useMemo<WaterfallContextValue>(\n () => ({ config, seriesKey, steps, formatValue }),\n [config, seriesKey, steps, formatValue],\n )\n\n const isEmpty = empty !== false && steps.length === 0\n\n return (\n <WaterfallContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : {\n rows: steps as unknown as Record<string, unknown>[],\n rowKey: 'name',\n columns: [\n { key: 'change', label: 'Change' },\n { key: 'total', label: 'Running total' },\n ],\n }\n }\n >\n {isEmpty ? (\n <ChartEmpty {...(empty || {})} />\n ) : (\n <ChartContainer config={config}>\n <RechartsBarChart\n id={chartId}\n accessibilityLayer\n margin={CHART_MARGIN}\n data={steps}\n barCategoryGap=\"25%\"\n {...chartProps}\n >\n {children}\n </RechartsBarChart>\n </ChartContainer>\n )}\n </ChartFigure>\n </WaterfallContext.Provider>\n )\n}\n\nexport interface WaterfallBarsProps {\n /** The bars' corner. */\n radius?: number\n /**\n * Joins each bar's closing edge to where the next one starts.\n *\n * On by default: without them a waterfall is a row of bars floating at\n * unrelated heights, and the cascade — which is the entire point of the form\n * — has to be reconstructed by the reader.\n */\n connectors?: boolean\n /**\n * Prints each step's signed change above or below its bar.\n *\n * Worth more here than on most charts: an intermediate bar has no baseline\n * under it, so its length is the one thing a reader cannot get off the axis.\n */\n showValues?: boolean\n /** Escape hatch onto the underlying Recharts bar. */\n barProps?: ComponentProps<typeof RechartsBar>\n}\n\n/**\n * The bars and the connectors between them.\n *\n * Increases and totals take the solid series fill; decreases take the 45°\n * hatch. Texture rather than a second colour, because the whole set has to stay\n * legible in greyscale and under forced colours — and a total is told from an\n * increase by geometry instead, since it is the only kind of bar that stands on\n * the baseline.\n */\nfunction Bars({\n radius = BAR_RADIUS,\n connectors = true,\n showValues = false,\n barProps,\n}: WaterfallBarsProps) {\n const { config, seriesKey, steps, formatValue } = useWaterfall()\n const id = useId().replace(/:/g, '')\n\n const shared = {\n id,\n seriesKey,\n radius,\n connectors,\n showValues,\n stepCount: steps.length,\n formatValue,\n }\n\n return (\n <>\n {/* The baseline the totals stand on. A waterfall without a visible zero\n asks the reader to take the floating bars on trust. */}\n <RechartsReferenceLine y={0} stroke=\"var(--chart-grid)\" strokeWidth={1} />\n <RechartsBar\n dataKey={(row: unknown) => (row as ResolvedStep).span}\n name={seriesKey}\n isAnimationActive={false}\n fill=\"transparent\"\n shape={(props: unknown) => <StepShape {...(props as StepGeometry)} {...shared} />}\n activeBar={(props: unknown) => <StepShape {...(props as StepGeometry)} {...shared} />}\n {...barProps}\n />\n <defs>\n <SeriesGradient id={id} dataKey={seriesKey} config={config} direction=\"vertical\" />\n <BarHatchedFill id={id} dataKey={seriesKey} />\n </defs>\n </>\n )\n}\n\n/** The geometry Recharts hands a custom bar shape, plus the step behind it. */\ninterface StepGeometry {\n x?: number\n y?: number\n width?: number\n height?: number\n index?: number\n payload?: ResolvedStep\n}\n\ninterface StepShapeProps extends StepGeometry {\n id: string\n seriesKey: string\n radius: number\n connectors: boolean\n showValues: boolean\n stepCount: number\n formatValue: (value: number) => string\n}\n\n/** One step: its bar, its connector to the next step, and its label. */\nfunction StepShape({\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n index = -1,\n payload,\n id,\n seriesKey,\n radius,\n connectors,\n showValues,\n stepCount,\n formatValue,\n}: StepShapeProps) {\n const plot = usePlotArea()\n\n if (!payload) return null\n\n const step = payload\n const fill =\n step.direction === 'decrease'\n ? `url(#${id}-hatched-${seriesKey})`\n : `url(#${id}-colors-${seriesKey})`\n\n // Which edge of the rect the running total lands on. Everything downstream —\n // the connector's height, where the label sits — hangs off this one fact,\n // and it is a comparison rather than a re-derived scale so it stays right\n // whatever domain the axis ends up with.\n const closesAtTop = step.total >= step.span[1]\n const closingY = closesAtTop ? y : y + height\n\n // A zero-change step has no rect to see. It still has to appear, or the\n // cascade silently loses a category and the axis stops matching the bars.\n const drawnHeight = Math.max(height, 1)\n\n // Recharts leaves a gap between category bands, and the connector has to\n // cross it. One band is the plot's width over the number of steps, so the\n // next bar's near edge is exactly one band along from this one's.\n const band = plot && stepCount > 0 ? plot.width / stepCount : width\n const hasNext = index >= 0 && index < stepCount - 1\n\n return (\n <g>\n {connectors && hasNext && (\n <line\n x1={x + width}\n y1={closingY}\n x2={x + band}\n y2={closingY}\n stroke=\"var(--chart-cursor)\"\n strokeWidth={1}\n strokeDasharray=\"3 3\"\n />\n )}\n <rect x={x} y={y} width={width} height={drawnHeight} rx={radius || undefined} fill={fill} />\n {showValues && (\n <text\n x={x + width / 2}\n // Above a rise, below a fall — so the label never sits inside the bar\n // it is describing and never collides with the connector leaving it.\n y={closesAtTop ? y - 6 : y + drawnHeight + 13}\n textAnchor=\"middle\"\n className=\"fill-(--ink) font-mono text-[11px] font-medium tabular-nums\"\n style={{ pointerEvents: 'none' }}\n >\n {step.direction === 'total'\n ? formatValue(step.total)\n : `${step.change > 0 ? '+' : ''}${formatValue(step.change)}`}\n </text>\n )}\n </g>\n )\n}\n\n/** The step axis. */\nfunction XAxis({\n dataKey = 'name',\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n minTickGap = 8,\n label,\n ...rest\n}: ChartXAxisProps) {\n return (\n <RechartsXAxis\n dataKey={dataKey}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n minTickGap={minTickGap}\n {...rest}\n >\n {axisLabel(label, 'x')}\n </RechartsXAxis>\n )\n}\n\n/** The value axis. */\nfunction YAxis({\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n width = 'auto',\n label,\n tickFormatter,\n ...rest\n}: ChartYAxisProps) {\n const { formatValue } = useWaterfall()\n\n return (\n <RechartsYAxis\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n width={width}\n tickFormatter={tickFormatter ?? formatValue}\n {...rest}\n >\n {axisLabel(label, 'y')}\n </RechartsYAxis>\n )\n}\n\n/** Rules across the value axis. */\nfunction Grid({\n strokeDasharray = '3 3',\n vertical = false,\n ...rest\n}: ComponentProps<typeof CartesianGrid>) {\n return <CartesianGrid strokeDasharray={strokeDasharray} vertical={vertical} {...rest} />\n}\n\n/**\n * The hover panel, printing the change AND the total it leaves behind.\n *\n * The generic tooltip would print the bar's raw span, which is a pair of\n * running totals and reads as two unrelated numbers. Both facts a waterfall\n * carries — what this step did, and where the total stood afterwards — are\n * named here instead.\n */\nfunction Tooltip({ variant = 'solid', roundness = 'lg', defaultIndex }: ChartTooltipSlotProps) {\n const { formatValue } = useWaterfall()\n\n return (\n <ChartTooltip\n cursor={false}\n defaultIndex={defaultIndex}\n content={(props) => {\n const { active, payload } = props as unknown as {\n active?: boolean\n payload?: { payload?: ResolvedStep }[]\n }\n const step = payload?.[0]?.payload\n if (!active || !step) return <span className=\"p-4\" />\n\n return (\n <div\n className={cn(\n 'grid min-w-40 gap-1.5 border border-(--rule-2) px-2.5 py-1.5 text-xs',\n roundness === 'sm' && 'rounded-(--radius-sm)',\n roundness === 'md' && 'rounded-(--radius)',\n roundness === 'lg' && 'rounded-(--radius-lg)',\n variant === 'frosted'\n ? 'bg-(--chart-surface)/75 backdrop-blur-sm'\n : 'bg-(--chart-surface)',\n )}\n >\n <div className=\"font-medium text-(--ink)\">{step.name}</div>\n {step.direction !== 'total' && (\n <div className=\"flex justify-between gap-4 leading-none\">\n <span className=\"text-(--ink-3-aa)\">Change</span>\n <span className=\"font-mono font-medium tabular-nums text-(--ink)\">\n {step.change > 0 ? '+' : ''}\n {formatValue(step.change)}\n </span>\n </div>\n )}\n <div className=\"flex justify-between gap-4 leading-none\">\n <span className=\"text-(--ink-3-aa)\">\n {step.direction === 'total' ? 'Total' : 'Running total'}\n </span>\n <span className=\"font-mono font-medium tabular-nums text-(--ink)\">\n {formatValue(step.total)}\n </span>\n </div>\n </div>\n )\n }}\n />\n )\n}\n\n/** The decorative plate behind the plot. */\nfunction Background({ variant }: { variant: ChartBackgroundVariant }) {\n return <ChartBackground variant={variant} />\n}\n\nWaterfallChart.Bars = Bars\nWaterfallChart.XAxis = XAxis\nWaterfallChart.YAxis = YAxis\nWaterfallChart.Grid = Grid\nWaterfallChart.Tooltip = Tooltip\nWaterfallChart.Background = Background\nWaterfallChart.ReferenceLine = ReferenceLine\nWaterfallChart.ReferenceBand = ReferenceBand\nWaterfallChart.Annotation = Annotation\n\nexport default WaterfallChart\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/charts/WaterfallChart/WaterfallChart.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n useContext,\n useId,\n useMemo,\n type ComponentProps,\n type ReactNode,\n} from 'react'\nimport {\n Bar as RechartsBar,\n BarChart as RechartsBarChart,\n CartesianGrid,\n ReferenceLine as RechartsReferenceLine,\n XAxis as RechartsXAxis,\n YAxis as RechartsYAxis,\n usePlotArea,\n} from 'recharts'\nimport { ChartContainer, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { type ChartEmptyProps } from '../lib/empty'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartTooltip } from '../lib/tooltip'\nimport { BarHatchedFill, SeriesGradient } from '../lib/paint'\nimport { Annotation, ReferenceBand, ReferenceLine } from '../lib/annotations'\nimport { axisLabel } from '../lib/axis'\nimport { defaultTick } from '../lib/format'\nimport { cn } from '../../lib/cn'\nimport type { ChartXAxisProps, ChartYAxisProps } from '../BarChart/BarChart'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/** The series a chart with only one of them falls back to. */\nconst DEFAULT_CONFIG: ChartConfig = { value: { label: 'Change' } }\n\nconst CHART_MARGIN = { top: 20, right: 12, bottom: 0, left: 12 }\n\n/** The corner every bar rounds to. */\nconst BAR_RADIUS = 2\n\n/** How a step moves the running total. */\nexport type WaterfallStepType = 'delta' | 'total'\n\n/** Which way a bar goes, which is what its texture encodes. */\nexport type WaterfallDirection = 'increase' | 'decrease' | 'total'\n\nexport interface WaterfallStep {\n /** The step's name on the category axis. Doubles as its key. */\n name: string\n /**\n * The signed change this step makes, or — on a `total` step — the absolute\n * figure the running total is set to.\n *\n * Omit it on a `total` and the running total is used, which is the right\n * default for the closing bar: an end total typed by hand can disagree with\n * the deltas above it, and the chart would draw the disagreement without\n * saying anything.\n */\n value?: number\n /**\n * `delta` (the default) adds `value` to the running total and floats the bar\n * where that lands. `total` plants a bar on the baseline — an opening\n * balance, a subtotal, a closing figure.\n */\n type?: WaterfallStepType\n}\n\n/** A step with its span, its direction and its running total worked out. */\ninterface ResolvedStep extends WaterfallStep {\n type: WaterfallStepType\n /**\n * What this step moved the total by. On a `total` step that is the gap it\n * closes — zero whenever the deltas above it already add up to it.\n */\n change: number\n /** The running total once this step has been applied. */\n total: number\n /**\n * The bar's two ends on the value axis, LOW first.\n *\n * Ascending because Recharts reads a floating bar's range that way and\n * collapses a descending one to nothing — which is every decrease in the\n * cascade. Which end the running total is at is recovered from `total`\n * rather than from the order here.\n */\n span: [number, number]\n direction: WaterfallDirection\n}\n\n/** A bar's two ends, low first — see `ResolvedStep.span`. */\nfunction ascending(a: number, b: number): [number, number] {\n return a <= b ? [a, b] : [b, a]\n}\n\n/** The steps, walked in order, each carrying the total it leaves behind. */\nfunction resolve(data: WaterfallStep[]): ResolvedStep[] {\n let running = 0\n\n return data.map((step) => {\n const type = step.type ?? 'delta'\n\n if (type === 'total') {\n const total = step.value ?? running\n const resolved: ResolvedStep = {\n ...step,\n type,\n change: total - running,\n total,\n span: ascending(0, total),\n direction: 'total',\n }\n running = total\n return resolved\n }\n\n const change = step.value ?? 0\n const from = running\n running += change\n\n return {\n ...step,\n type,\n change,\n total: running,\n span: ascending(from, running),\n direction: change < 0 ? 'decrease' : 'increase',\n }\n })\n}\n\ninterface WaterfallContextValue {\n config: ChartConfig\n seriesKey: string\n steps: ResolvedStep[]\n formatValue: (value: number) => string\n}\n\nconst WaterfallContext = createContext<WaterfallContextValue | null>(null)\n\nfunction useWaterfall(): WaterfallContextValue {\n const context = useContext(WaterfallContext)\n if (!context) throw new Error('A waterfall part must be rendered inside <WaterfallChart>')\n return context\n}\n\nexport interface WaterfallChartProps {\n /**\n * The single series — its label and its paint. Only the FIRST entry is read;\n * a waterfall has one quantity and as many bars as it has steps.\n */\n config?: ChartConfig\n /**\n * The steps, in the order they are applied. Order is the arithmetic: the\n * chart does not sort, because a different order is a different total at\n * every intermediate bar.\n */\n data: WaterfallStep[]\n /**\n * What the chart shows, in a sentence a reader could act on. Required, and\n * announced to a screen reader even when it is not printed.\n */\n title: string\n /** Prints the title above the plot instead of hiding it from sight. */\n showTitle?: boolean\n /** A line under the title — the unit, the window, the caveat. */\n description?: ReactNode\n /** The composed parts — axes, grid, tooltip, and `<WaterfallChart.Bars>`. */\n children: ReactNode\n /** Merged onto the figure, last, so a call site can size or space it. */\n className?: string\n /** Escape hatch onto the raw Recharts chart element. */\n chartProps?: ComponentProps<typeof RechartsBarChart>\n /** Formats every number the chart prints — ticks, labels, tooltip, table. */\n formatValue?: (value: number) => string\n /** Drops the hidden table view. Only correct when the page prints the data itself. */\n hideDataTable?: boolean\n /**\n * What the chart shows when it has nothing to draw. `false` keeps the axes,\n * for a chart whose emptiness is itself the reading.\n */\n empty?: ChartEmptyProps | false\n}\n\n/**\n * How a total got from one figure to another — an opening balance, the signed\n * contributions that moved it, and where it closed.\n *\n * The form for \"why did this change\", which a pair of bars cannot answer and a\n * pie chart answers wrongly, because contributions can be NEGATIVE and a slice\n * cannot. Reach for `<BarChart>` when the parts do not have to add up to the\n * gap between two totals, and for `<FunnelChart>` when the quantity only ever\n * shrinks.\n *\n * **The connectors are the claim to be careful about.** They draw the steps as\n * a sequence — this happened, then this — and most breakdowns are not\n * sequential at all: churn and expansion in the same month are simultaneous,\n * and the order they are listed in is an editorial choice. The arithmetic\n * survives any order; the STORY does not, and a reader will take the leftmost\n * bar as the first cause. Two related traps: the intermediate bars are floating\n * lengths read against no baseline, so a small step high up the cascade is hard\n * to compare with a large one near zero; and any step that is itself a net of\n * two larger opposing movements is invisible as such. Where the order is\n * arbitrary, say so in the `description`.\n *\n * Recharts earns its place for the axes, grid and tooltip. Each bar is a\n * floating range — from the running total to the new one — with a custom shape\n * over it, because Recharts has no waterfall mark and the connectors have to be\n * drawn from the same geometry as the bars they join.\n *\n * @example\n * <WaterfallChart title=\"Revenue bridge, FY25\" data={steps}>\n * <WaterfallChart.Grid />\n * <WaterfallChart.XAxis />\n * <WaterfallChart.YAxis label=\"AUD\" />\n * <WaterfallChart.Tooltip />\n * <WaterfallChart.Bars showValues />\n * </WaterfallChart>\n *\n * @example\n * const steps = [\n * { name: 'FY24', value: 1200, type: 'total' as const },\n * { name: 'New', value: 340 },\n * { name: 'Churn', value: -180 },\n * // No value: the closing bar is whatever the deltas add up to.\n * { name: 'FY25', type: 'total' as const },\n * ]\n */\nexport function WaterfallChart({\n config = DEFAULT_CONFIG,\n data,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n formatValue = defaultTick,\n hideDataTable = false,\n empty,\n}: WaterfallChartProps) {\n const chartId = useId().replace(/:/g, '')\n const seriesKey = Object.keys(config)[0] ?? 'value'\n const steps = useMemo(() => resolve(data), [data])\n\n const context = useMemo<WaterfallContextValue>(\n () => ({ config, seriesKey, steps, formatValue }),\n [config, seriesKey, steps, formatValue],\n )\n\n const isEmpty = empty !== false && steps.length === 0\n\n return (\n <WaterfallContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable\n ? false\n : {\n rows: steps as unknown as Record<string, unknown>[],\n rowKey: 'name',\n columns: [\n { key: 'change', label: 'Change' },\n { key: 'total', label: 'Running total' },\n ],\n }\n }\n isEmpty={isEmpty}\n empty={empty}\n >\n <ChartContainer config={config}>\n <RechartsBarChart\n id={chartId}\n accessibilityLayer\n margin={CHART_MARGIN}\n data={steps}\n barCategoryGap=\"25%\"\n {...chartProps}\n >\n {children}\n </RechartsBarChart>\n </ChartContainer>\n </ChartFigure>\n </WaterfallContext.Provider>\n )\n}\n\nexport interface WaterfallBarsProps {\n /** The bars' corner. */\n radius?: number\n /**\n * Joins each bar's closing edge to where the next one starts.\n *\n * On by default: without them a waterfall is a row of bars floating at\n * unrelated heights, and the cascade — which is the entire point of the form\n * — has to be reconstructed by the reader.\n */\n connectors?: boolean\n /**\n * Prints each step's signed change above or below its bar.\n *\n * Worth more here than on most charts: an intermediate bar has no baseline\n * under it, so its length is the one thing a reader cannot get off the axis.\n */\n showValues?: boolean\n /** Escape hatch onto the underlying Recharts bar. */\n barProps?: ComponentProps<typeof RechartsBar>\n}\n\n/**\n * The bars and the connectors between them.\n *\n * Increases and totals take the solid series fill; decreases take the 45°\n * hatch. Texture rather than a second colour, because the whole set has to stay\n * legible in greyscale and under forced colours — and a total is told from an\n * increase by geometry instead, since it is the only kind of bar that stands on\n * the baseline.\n */\nfunction Bars({\n radius = BAR_RADIUS,\n connectors = true,\n showValues = false,\n barProps,\n}: WaterfallBarsProps) {\n const { config, seriesKey, steps, formatValue } = useWaterfall()\n const id = useId().replace(/:/g, '')\n\n const shared = {\n id,\n seriesKey,\n radius,\n connectors,\n showValues,\n stepCount: steps.length,\n formatValue,\n }\n\n return (\n <>\n {/* The baseline the totals stand on. A waterfall without a visible zero\n asks the reader to take the floating bars on trust. */}\n <RechartsReferenceLine y={0} stroke=\"var(--chart-grid)\" strokeWidth={1} />\n <RechartsBar\n dataKey={(row: unknown) => (row as ResolvedStep).span}\n name={seriesKey}\n isAnimationActive={false}\n fill=\"transparent\"\n shape={(props: unknown) => <StepShape {...(props as StepGeometry)} {...shared} />}\n activeBar={(props: unknown) => <StepShape {...(props as StepGeometry)} {...shared} />}\n {...barProps}\n />\n <defs>\n <SeriesGradient id={id} dataKey={seriesKey} config={config} direction=\"vertical\" />\n <BarHatchedFill id={id} dataKey={seriesKey} />\n </defs>\n </>\n )\n}\n\n/** The geometry Recharts hands a custom bar shape, plus the step behind it. */\ninterface StepGeometry {\n x?: number\n y?: number\n width?: number\n height?: number\n index?: number\n payload?: ResolvedStep\n}\n\ninterface StepShapeProps extends StepGeometry {\n id: string\n seriesKey: string\n radius: number\n connectors: boolean\n showValues: boolean\n stepCount: number\n formatValue: (value: number) => string\n}\n\n/** One step: its bar, its connector to the next step, and its label. */\nfunction StepShape({\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n index = -1,\n payload,\n id,\n seriesKey,\n radius,\n connectors,\n showValues,\n stepCount,\n formatValue,\n}: StepShapeProps) {\n const plot = usePlotArea()\n\n if (!payload) return null\n\n const step = payload\n const fill =\n step.direction === 'decrease'\n ? `url(#${id}-hatched-${seriesKey})`\n : `url(#${id}-colors-${seriesKey})`\n\n // Which edge of the rect the running total lands on. Everything downstream —\n // the connector's height, where the label sits — hangs off this one fact,\n // and it is a comparison rather than a re-derived scale so it stays right\n // whatever domain the axis ends up with.\n const closesAtTop = step.total >= step.span[1]\n const closingY = closesAtTop ? y : y + height\n\n // A zero-change step has no rect to see. It still has to appear, or the\n // cascade silently loses a category and the axis stops matching the bars.\n const drawnHeight = Math.max(height, 1)\n\n // Recharts leaves a gap between category bands, and the connector has to\n // cross it. One band is the plot's width over the number of steps, so the\n // next bar's near edge is exactly one band along from this one's.\n const band = plot && stepCount > 0 ? plot.width / stepCount : width\n const hasNext = index >= 0 && index < stepCount - 1\n\n return (\n <g>\n {connectors && hasNext && (\n <line\n x1={x + width}\n y1={closingY}\n x2={x + band}\n y2={closingY}\n stroke=\"var(--chart-cursor)\"\n strokeWidth={1}\n strokeDasharray=\"3 3\"\n />\n )}\n <rect x={x} y={y} width={width} height={drawnHeight} rx={radius || undefined} fill={fill} />\n {showValues && (\n <text\n x={x + width / 2}\n // Above a rise, below a fall — so the label never sits inside the bar\n // it is describing and never collides with the connector leaving it.\n y={closesAtTop ? y - 6 : y + drawnHeight + 13}\n textAnchor=\"middle\"\n className=\"fill-(--ink) font-mono text-[11px] font-medium tabular-nums\"\n style={{ pointerEvents: 'none' }}\n >\n {step.direction === 'total'\n ? formatValue(step.total)\n : `${step.change > 0 ? '+' : ''}${formatValue(step.change)}`}\n </text>\n )}\n </g>\n )\n}\n\n/** The step axis. */\nfunction XAxis({\n dataKey = 'name',\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n minTickGap = 8,\n label,\n ...rest\n}: ChartXAxisProps) {\n return (\n <RechartsXAxis\n dataKey={dataKey}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n minTickGap={minTickGap}\n {...rest}\n >\n {axisLabel(label, 'x')}\n </RechartsXAxis>\n )\n}\n\n/** The value axis. */\nfunction YAxis({\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n width = 'auto',\n label,\n tickFormatter,\n ...rest\n}: ChartYAxisProps) {\n const { formatValue } = useWaterfall()\n\n return (\n <RechartsYAxis\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n width={width}\n tickFormatter={tickFormatter ?? formatValue}\n {...rest}\n >\n {axisLabel(label, 'y')}\n </RechartsYAxis>\n )\n}\n\n/** Rules across the value axis. */\nfunction Grid({\n strokeDasharray = '3 3',\n vertical = false,\n ...rest\n}: ComponentProps<typeof CartesianGrid>) {\n return <CartesianGrid strokeDasharray={strokeDasharray} vertical={vertical} {...rest} />\n}\n\n/**\n * The hover panel, printing the change AND the total it leaves behind.\n *\n * The generic tooltip would print the bar's raw span, which is a pair of\n * running totals and reads as two unrelated numbers. Both facts a waterfall\n * carries — what this step did, and where the total stood afterwards — are\n * named here instead.\n */\nfunction Tooltip({ variant = 'solid', roundness = 'lg', defaultIndex }: ChartTooltipSlotProps) {\n const { formatValue } = useWaterfall()\n\n return (\n <ChartTooltip\n cursor={false}\n defaultIndex={defaultIndex}\n content={(props) => {\n const { active, payload } = props as unknown as {\n active?: boolean\n payload?: { payload?: ResolvedStep }[]\n }\n const step = payload?.[0]?.payload\n if (!active || !step) return <span className=\"p-4\" />\n\n return (\n <div\n className={cn(\n 'grid min-w-40 gap-1.5 border border-(--rule-2) px-2.5 py-1.5 text-xs',\n roundness === 'sm' && 'rounded-(--radius-sm)',\n roundness === 'md' && 'rounded-(--radius)',\n roundness === 'lg' && 'rounded-(--radius-lg)',\n variant === 'frosted'\n ? 'bg-(--chart-surface)/75 backdrop-blur-sm'\n : 'bg-(--chart-surface)',\n )}\n >\n <div className=\"font-medium text-(--ink)\">{step.name}</div>\n {step.direction !== 'total' && (\n <div className=\"flex justify-between gap-4 leading-none\">\n <span className=\"text-(--ink-3-aa)\">Change</span>\n <span className=\"font-mono font-medium tabular-nums text-(--ink)\">\n {step.change > 0 ? '+' : ''}\n {formatValue(step.change)}\n </span>\n </div>\n )}\n <div className=\"flex justify-between gap-4 leading-none\">\n <span className=\"text-(--ink-3-aa)\">\n {step.direction === 'total' ? 'Total' : 'Running total'}\n </span>\n <span className=\"font-mono font-medium tabular-nums text-(--ink)\">\n {formatValue(step.total)}\n </span>\n </div>\n </div>\n )\n }}\n />\n )\n}\n\n/** The decorative plate behind the plot. */\nfunction Background({ variant }: { variant: ChartBackgroundVariant }) {\n return <ChartBackground variant={variant} />\n}\n\nWaterfallChart.Bars = Bars\nWaterfallChart.XAxis = XAxis\nWaterfallChart.YAxis = YAxis\nWaterfallChart.Grid = Grid\nWaterfallChart.Tooltip = Tooltip\nWaterfallChart.Background = Background\nWaterfallChart.ReferenceLine = ReferenceLine\nWaterfallChart.ReferenceBand = ReferenceBand\nWaterfallChart.Annotation = Annotation\n\nexport default WaterfallChart\n"],"mappings":"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","names":[]}
|
package/dist/charts/index.d.ts
CHANGED
|
@@ -31,11 +31,12 @@ export { SONIFY_DEFAULTS, SonifyController, SonifyOptions, SonifyPoint, SonifySe
|
|
|
31
31
|
export { ChartSonifyButton, ChartSonifyButtonProps, Sonify, SonifyAlign, SonifyProps, UseSonifyOptions, UseSonifyResult, chartSonifySeries, useChartSonifySeries, useSonify } from './lib/sonify-control.js';
|
|
32
32
|
export { ChartControls, ChartControlsProps, ChartExportFormat, ChartToolbar, ChartToolbarProps, Toolbar, ToolbarProps } from './lib/toolbar.js';
|
|
33
33
|
export { ChartZoom, ChartZoomOptions, ChartZoomSurface, ChartZoomSurfaceProps, clampWindow, panWindow, useChartZoom, zoomWindow } from './lib/zoom.js';
|
|
34
|
-
export { ChartPngOptions, chartToCsv, chartToPng,
|
|
34
|
+
export { ChartPngOptions, chartToCsv, chartToPng, exportFilename } from './lib/export.js';
|
|
35
35
|
export { ANNOTATION_LAYER, Annotation, AnnotationProps, AnnotationWeight, ReferenceBand, ReferenceBandProps, ReferenceLine, ReferenceLineProps } from './lib/annotations.js';
|
|
36
36
|
export { ValueLabelMode, Values, ValuesProps } from './lib/values.js';
|
|
37
37
|
export { ChartEmpty, ChartEmptyProps } from './lib/empty.js';
|
|
38
38
|
export { NumberFormatOptions, NumberStyle, defaultTick, formatNumber } from './lib/format.js';
|
|
39
|
+
export { downloadBlob } from '../lib/svg-export.js';
|
|
39
40
|
import 'react';
|
|
40
41
|
import 'recharts';
|
|
41
42
|
import 'recharts/types/component/DefaultTooltipContent';
|
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { ChartColumn } from './figure.js';
|
|
2
|
+
export { downloadBlob } from '../../lib/svg-export.js';
|
|
2
3
|
import 'react';
|
|
4
|
+
import './empty.js';
|
|
3
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Taking a chart off the page: a PNG of the plot, or a CSV of its rows.
|
|
8
|
+
*
|
|
9
|
+
* The general half — walking a live tree to inline its resolved paint,
|
|
10
|
+
* serialising a standalone SVG document, rasterising one through a canvas — is
|
|
11
|
+
* not chart-shaped at all, and lives in `src/lib/svg-export.ts` shared with the
|
|
12
|
+
* diagrams entry. What is left here is what only a Recharts chart knows: which
|
|
13
|
+
* `<svg>` in the subtree is the plot, what a row of chart data looks like as a
|
|
14
|
+
* CSV record, and that the ground behind an exported plot is `--chart-surface`.
|
|
15
|
+
*
|
|
16
|
+
* Nothing here touches the DOM at module scope. Every browser API is reached
|
|
17
|
+
* for inside a function body, so this module is safe to import from a server
|
|
18
|
+
* component and only fails when a consumer actually asks for a file on a
|
|
19
|
+
* server, which is a call that cannot succeed anywhere.
|
|
20
|
+
*/
|
|
4
21
|
interface ChartPngOptions {
|
|
5
22
|
/**
|
|
6
23
|
* Printed above the plot in the exported image. Pass the figure's title: a
|
|
@@ -21,46 +38,27 @@ interface ChartPngOptions {
|
|
|
21
38
|
/**
|
|
22
39
|
* The chart's `<svg>`, rasterised to a PNG blob.
|
|
23
40
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* of its own. That document has none of the page's stylesheets, so every
|
|
28
|
-
* `var(--color-desktop-0)`, `var(--chart-fill)` and `var(--ink)` in the markup
|
|
29
|
-
* resolves to nothing — and a paint that resolves to nothing is not a fallback
|
|
30
|
-
* colour, it is an invisible mark. Serialise a chart naively and you get an
|
|
31
|
-
* empty rectangle with axes on it.
|
|
32
|
-
*
|
|
33
|
-
* ## The solution
|
|
41
|
+
* Three chart-specific decisions and nothing else. `serializeSvg` does the
|
|
42
|
+
* work, and its comment carries the reason a naive `XMLSerializer` export of a
|
|
43
|
+
* themed chart comes out as an empty rectangle with axes on it.
|
|
34
44
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* argument anywhere in this file.
|
|
42
|
-
*
|
|
43
|
-
* `<stop>` elements are walked too, which is the half that is easy to miss: a
|
|
44
|
-
* series is painted `fill="url(#…-colors-desktop)"`, the url survives
|
|
45
|
-
* serialisation because the gradient is inside the same `<svg>`, and it is the
|
|
46
|
-
* gradient's STOPS that carry the custom properties.
|
|
45
|
+
* 1. WHICH `<svg>` — the plot, not the first one in the subtree. See
|
|
46
|
+
* `findPlotSvg`.
|
|
47
|
+
* 2. WHAT GROUND — `--chart-surface`, resolved against the live element, which
|
|
48
|
+
* is what makes a dark-mode export come out dark rather than transparent.
|
|
49
|
+
* 3. WHAT INK AND FACE for the caption — `--ink` and the element's own
|
|
50
|
+
* resolved family, so the exported title matches the page it came from.
|
|
47
51
|
*
|
|
48
52
|
* ## What this cannot capture, honestly
|
|
49
53
|
*
|
|
50
54
|
* - **Anything drawn in HTML.** The legend, the tooltip, the brush strip and
|
|
51
55
|
* the hidden data table are DOM, not SVG. The PNG is the plot.
|
|
52
|
-
* - **Web fonts
|
|
53
|
-
*
|
|
54
|
-
* locally. Type metrics will differ from the screen.
|
|
55
|
-
* - **Cross-origin images.** A chart with an external `<image>` in it taints
|
|
56
|
-
* the canvas and `toBlob` throws; there is no way around it from script.
|
|
57
|
-
* - **The exact rendering.** This is the browser's SVG rasteriser re-drawing
|
|
58
|
-
* the markup, not a screenshot. Antialiasing, `backdrop-filter` and any
|
|
59
|
-
* effect a page stylesheet applied from outside the `<svg>` are not in it.
|
|
56
|
+
* - **Web fonts, cross-origin images, and the exact rendering.** See
|
|
57
|
+
* `serializeSvg` and `rasterize`.
|
|
60
58
|
*
|
|
61
59
|
* @example
|
|
62
60
|
* const blob = await chartToPng(plotRef.current, { title: 'Visitors per month' })
|
|
63
|
-
* downloadBlob(blob, '
|
|
61
|
+
* downloadBlob(blob, exportFilename('Visitors per month', 'png'))
|
|
64
62
|
*/
|
|
65
63
|
declare function chartToPng(element: HTMLElement | SVGSVGElement, options?: ChartPngOptions): Promise<Blob>;
|
|
66
64
|
/**
|
|
@@ -83,21 +81,14 @@ declare function chartToPng(element: HTMLElement | SVGSVGElement, options?: Char
|
|
|
83
81
|
* downloadBlob(new Blob([csv], { type: 'text/csv;charset=utf-8' }), 'visitors.csv')
|
|
84
82
|
*/
|
|
85
83
|
declare function chartToCsv(rows: Record<string, unknown>[], columns: ChartColumn[]): string;
|
|
86
|
-
|
|
87
|
-
* Hands a blob to the browser as a download.
|
|
88
|
-
*
|
|
89
|
-
* Throws rather than no-oping without a `document`: a download is something a
|
|
90
|
-
* person just asked for, and a silent return would look to them like a click
|
|
91
|
-
* that did nothing.
|
|
92
|
-
*/
|
|
93
|
-
declare function downloadBlob(blob: Blob, filename: string): void;
|
|
84
|
+
|
|
94
85
|
/**
|
|
95
86
|
* A figure's title as a filename.
|
|
96
87
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
88
|
+
* The shared slugger, with `chart` rather than `figure` as the fallback for a
|
|
89
|
+
* title that slugs to nothing — an export from this entry is a chart, and the
|
|
90
|
+
* name a reader finds in their downloads folder should say so.
|
|
100
91
|
*/
|
|
101
92
|
declare function exportFilename(title: string, extension: string): string;
|
|
102
93
|
|
|
103
|
-
export { type ChartPngOptions, chartToCsv, chartToPng,
|
|
94
|
+
export { type ChartPngOptions, chartToCsv, chartToPng, exportFilename };
|
|
@@ -1,154 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"fill-opacity",
|
|
9
|
-
"fill-rule",
|
|
10
|
-
"stroke",
|
|
11
|
-
"stroke-opacity",
|
|
12
|
-
"stroke-width",
|
|
13
|
-
"stroke-linecap",
|
|
14
|
-
"stroke-linejoin",
|
|
15
|
-
"stroke-dasharray",
|
|
16
|
-
"stroke-dashoffset",
|
|
17
|
-
"stop-color",
|
|
18
|
-
"stop-opacity",
|
|
19
|
-
"flood-color",
|
|
20
|
-
"flood-opacity",
|
|
21
|
-
"mask",
|
|
22
|
-
"filter",
|
|
23
|
-
"clip-path",
|
|
24
|
-
"clip-rule",
|
|
25
|
-
"paint-order",
|
|
26
|
-
"mix-blend-mode",
|
|
27
|
-
"shape-rendering",
|
|
28
|
-
"text-anchor",
|
|
29
|
-
"dominant-baseline",
|
|
30
|
-
"font-family",
|
|
31
|
-
"font-size",
|
|
32
|
-
"font-style",
|
|
33
|
-
"font-weight",
|
|
34
|
-
"letter-spacing",
|
|
35
|
-
"text-transform"
|
|
36
|
-
];
|
|
1
|
+
import {
|
|
2
|
+
downloadBlob,
|
|
3
|
+
exportFilename as figureFilename,
|
|
4
|
+
rasterize,
|
|
5
|
+
readToken,
|
|
6
|
+
serializeSvg
|
|
7
|
+
} from "../../lib/svg-export.js";
|
|
37
8
|
async function chartToPng(element, options = {}) {
|
|
38
9
|
const { title, scale = 2, background, padding = 16 } = options;
|
|
39
10
|
const svg = findPlotSvg(element);
|
|
40
11
|
if (!svg) throw new Error("chartToPng: found no <svg> to export inside the element it was given");
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
clone.setAttribute("width", String(width));
|
|
50
|
-
clone.setAttribute("height", String(height));
|
|
51
|
-
const titleBand = title ? 30 : 0;
|
|
52
|
-
const totalWidth = width + padding * 2;
|
|
53
|
-
const totalHeight = height + padding * 2 + titleBand;
|
|
54
|
-
const page = document.createElementNS(SVG_NS, "svg");
|
|
55
|
-
page.setAttribute("xmlns", SVG_NS);
|
|
56
|
-
page.setAttribute("width", String(totalWidth));
|
|
57
|
-
page.setAttribute("height", String(totalHeight));
|
|
58
|
-
page.setAttribute("viewBox", `0 0 ${totalWidth} ${totalHeight}`);
|
|
59
|
-
const plate = document.createElementNS(SVG_NS, "rect");
|
|
60
|
-
plate.setAttribute("width", String(totalWidth));
|
|
61
|
-
plate.setAttribute("height", String(totalHeight));
|
|
62
|
-
plate.setAttribute("fill", background ?? readToken(element, "--chart-surface", "#ffffff"));
|
|
63
|
-
page.append(plate);
|
|
64
|
-
if (title) {
|
|
65
|
-
const caption = document.createElementNS(SVG_NS, "text");
|
|
66
|
-
caption.setAttribute("x", String(padding));
|
|
67
|
-
caption.setAttribute("y", String(padding + 14));
|
|
68
|
-
caption.setAttribute("fill", readToken(element, "--ink", "#101010"));
|
|
69
|
-
caption.setAttribute("font-size", "13");
|
|
70
|
-
caption.setAttribute("font-weight", "500");
|
|
71
|
-
caption.setAttribute("font-family", getComputedStyle(element).fontFamily || "sans-serif");
|
|
72
|
-
caption.textContent = title;
|
|
73
|
-
page.append(caption);
|
|
74
|
-
}
|
|
75
|
-
const group = document.createElementNS(SVG_NS, "g");
|
|
76
|
-
group.setAttribute("transform", `translate(${padding}, ${padding + titleBand})`);
|
|
77
|
-
group.append(clone);
|
|
78
|
-
page.append(group);
|
|
79
|
-
const markup = new XMLSerializer().serializeToString(page);
|
|
80
|
-
const url = URL.createObjectURL(
|
|
81
|
-
new Blob([`<?xml version="1.0" encoding="UTF-8"?>
|
|
82
|
-
${markup}`], {
|
|
83
|
-
type: "image/svg+xml;charset=utf-8"
|
|
84
|
-
})
|
|
85
|
-
);
|
|
86
|
-
try {
|
|
87
|
-
const image = await loadImage(url);
|
|
88
|
-
const canvas = document.createElement("canvas");
|
|
89
|
-
canvas.width = Math.ceil(totalWidth * scale);
|
|
90
|
-
canvas.height = Math.ceil(totalHeight * scale);
|
|
91
|
-
const context = canvas.getContext("2d");
|
|
92
|
-
if (!context) throw new Error("chartToPng: this browser gave back no 2D canvas context");
|
|
93
|
-
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
94
|
-
return await canvasToBlob(canvas);
|
|
95
|
-
} finally {
|
|
96
|
-
URL.revokeObjectURL(url);
|
|
97
|
-
}
|
|
12
|
+
const markup = serializeSvg(svg, {
|
|
13
|
+
padding,
|
|
14
|
+
background: background ?? readToken(element, "--chart-surface", "#ffffff"),
|
|
15
|
+
title,
|
|
16
|
+
titleColor: readToken(element, "--ink", "#101010"),
|
|
17
|
+
titleFont: getComputedStyle(element).fontFamily || "sans-serif"
|
|
18
|
+
});
|
|
19
|
+
return rasterize(markup, "png", { scale });
|
|
98
20
|
}
|
|
99
21
|
function findPlotSvg(element) {
|
|
100
22
|
if (element.tagName.toLowerCase() === "svg") return element;
|
|
101
23
|
const scope = element.querySelector('[data-slot="chart"]') ?? element;
|
|
102
24
|
return scope.querySelector("svg.recharts-surface") ?? scope.querySelector("svg");
|
|
103
25
|
}
|
|
104
|
-
function inlinePaint(live, clone) {
|
|
105
|
-
const computed = getComputedStyle(live);
|
|
106
|
-
let css = "";
|
|
107
|
-
for (const property of PAINTED_PROPERTIES) {
|
|
108
|
-
const value = computed.getPropertyValue(property);
|
|
109
|
-
if (value) css += `${property}:${value};`;
|
|
110
|
-
}
|
|
111
|
-
if (css) clone.setAttribute("style", css);
|
|
112
|
-
const children = live.children;
|
|
113
|
-
const clones = clone.children;
|
|
114
|
-
for (let index = 0; index < children.length; index += 1) {
|
|
115
|
-
const liveChild = children[index];
|
|
116
|
-
const cloneChild = clones[index];
|
|
117
|
-
if (liveChild && cloneChild) inlinePaint(liveChild, cloneChild);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
function readToken(element, name, fallback) {
|
|
121
|
-
const value = getComputedStyle(element).getPropertyValue(name).trim();
|
|
122
|
-
return value && !value.includes("var(") ? value : fallback;
|
|
123
|
-
}
|
|
124
|
-
function loadImage(url) {
|
|
125
|
-
return new Promise((resolve, reject) => {
|
|
126
|
-
const image = new Image();
|
|
127
|
-
image.addEventListener("load", () => resolve(image));
|
|
128
|
-
image.addEventListener(
|
|
129
|
-
"error",
|
|
130
|
-
() => reject(new Error("chartToPng: the browser refused to decode the serialised chart"))
|
|
131
|
-
);
|
|
132
|
-
image.src = url;
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
function canvasToBlob(canvas) {
|
|
136
|
-
return new Promise((resolve, reject) => {
|
|
137
|
-
try {
|
|
138
|
-
canvas.toBlob((blob) => {
|
|
139
|
-
if (blob) resolve(blob);
|
|
140
|
-
else reject(new Error("chartToPng: the canvas produced no PNG data"));
|
|
141
|
-
}, "image/png");
|
|
142
|
-
} catch (cause) {
|
|
143
|
-
reject(
|
|
144
|
-
new Error(
|
|
145
|
-
"chartToPng: the canvas is tainted, which means the chart contains a cross-origin image",
|
|
146
|
-
{ cause }
|
|
147
|
-
)
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
26
|
const UTF8_BOM = "\uFEFF";
|
|
153
27
|
function chartToCsv(rows, columns) {
|
|
154
28
|
if (columns.length === 0) return UTF8_BOM;
|
|
@@ -176,24 +50,8 @@ function cellText(value) {
|
|
|
176
50
|
if (value instanceof Date) return value.toISOString();
|
|
177
51
|
return "";
|
|
178
52
|
}
|
|
179
|
-
function downloadBlob(blob, filename) {
|
|
180
|
-
if (typeof document === "undefined") {
|
|
181
|
-
throw new Error("downloadBlob: needs a browser document \u2014 call it from an event handler");
|
|
182
|
-
}
|
|
183
|
-
const url = URL.createObjectURL(blob);
|
|
184
|
-
const anchor = document.createElement("a");
|
|
185
|
-
anchor.href = url;
|
|
186
|
-
anchor.download = filename;
|
|
187
|
-
anchor.rel = "noopener";
|
|
188
|
-
anchor.style.display = "none";
|
|
189
|
-
document.body.append(anchor);
|
|
190
|
-
anchor.click();
|
|
191
|
-
anchor.remove();
|
|
192
|
-
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
193
|
-
}
|
|
194
53
|
function exportFilename(title, extension) {
|
|
195
|
-
|
|
196
|
-
return `${slug || "chart"}.${extension}`;
|
|
54
|
+
return figureFilename(title, extension, "chart");
|
|
197
55
|
}
|
|
198
56
|
export {
|
|
199
57
|
chartToCsv,
|