@misoto22/design 0.7.0 → 0.9.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 +50 -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 +25 -2
- 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/ColorPicker.md +75 -0
- 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 +22 -1
- 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 +24 -1
- package/dist/agent/DiagramExportMenu.md +29 -3
- package/dist/agent/DiagramInspector.md +23 -1
- package/dist/agent/DiagramLegend.md +22 -1
- package/dist/agent/DiagramMinimap.md +24 -4
- package/dist/agent/DiagramToolbar.md +23 -1
- 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 +22 -1
- 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/NumberField.md +72 -0
- 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 +23 -1
- package/dist/agent/Sheet.md +25 -2
- package/dist/agent/Sidebar.md +193 -0
- package/dist/agent/Skeleton.md +24 -2
- package/dist/agent/Slider.md +31 -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 +28 -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 +24 -2
- package/dist/agent/catalog.json +5915 -699
- package/dist/agent/index.md +20 -8
- 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 +44 -9
- 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 +15 -11
- 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/lib/export.d.ts +1 -0
- 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/ColorPicker/ColorPicker.d.ts +67 -0
- package/dist/components/ColorPicker/ColorPicker.js +334 -0
- package/dist/components/ColorPicker/ColorPicker.js.map +1 -0
- 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 +14 -2
- package/dist/components/Command/Command.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.d.ts +30 -5
- package/dist/components/ContextMenu/ContextMenu.js +33 -3
- 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 +21 -3
- package/dist/components/Dialog/Dialog.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.d.ts +49 -11
- package/dist/components/DropdownMenu/DropdownMenu.js +33 -3
- 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 +12 -3
- 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/NavItem/NavItem.js +1 -1
- package/dist/components/NavItem/NavItem.js.map +1 -1
- package/dist/components/NumberField/NumberField.d.ts +69 -0
- package/dist/components/NumberField/NumberField.js +162 -0
- package/dist/components/NumberField/NumberField.js.map +1 -0
- 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/Popover/Popover.js +1 -1
- package/dist/components/Popover/Popover.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 +21 -4
- 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 +12 -3
- package/dist/components/Sheet/Sheet.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.d.ts +349 -0
- package/dist/components/Sidebar/Sidebar.js +455 -0
- package/dist/components/Sidebar/Sidebar.js.map +1 -0
- 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 +42 -3
- package/dist/components/Slider/Slider.js +121 -30
- 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 +20 -0
- package/dist/components/Table/Table.js +3 -1
- package/dist/components/Table/Table.js.map +1 -1
- package/dist/components/Tabs/Tabs.js +7 -1
- package/dist/components/Tabs/Tabs.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 +6 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js +48 -8
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js.map +1 -1
- package/dist/diagrams/DataflowFigure/DataflowFigure.js +21 -2
- package/dist/diagrams/DataflowFigure/DataflowFigure.js.map +1 -1
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.d.ts +14 -0
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js +14 -7
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js.map +1 -1
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.d.ts +38 -4
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js +14 -16
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js.map +1 -1
- package/dist/diagrams/DiagramInspector/DiagramInspector.js +2 -2
- package/dist/diagrams/DiagramInspector/DiagramInspector.js.map +1 -1
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.d.ts +23 -7
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js +45 -18
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js.map +1 -1
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js +44 -12
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js.map +1 -1
- package/dist/diagrams/SequenceFigure/SequenceFigure.js +35 -5
- package/dist/diagrams/SequenceFigure/SequenceFigure.js.map +1 -1
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.d.ts +3 -1
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js +76 -28
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js.map +1 -1
- package/dist/diagrams/index.d.ts +1 -0
- package/dist/diagrams/index.js +10 -0
- package/dist/diagrams/index.js.map +1 -1
- 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 +48 -7
- package/dist/diagrams/lib/frame.js +17 -2
- package/dist/diagrams/lib/frame.js.map +1 -1
- package/dist/diagrams/lib/route.d.ts +22 -1
- package/dist/diagrams/lib/route.js +24 -1
- package/dist/diagrams/lib/route.js.map +1 -1
- package/dist/diagrams/lib/wires.js +9 -5
- package/dist/diagrams/lib/wires.js.map +1 -1
- package/dist/diagrams/spec.d.ts +64 -12
- package/dist/index.d.ts +19 -4
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/keyframes.css +57 -3
- package/dist/lib/color.d.ts +80 -0
- package/dist/lib/color.js +189 -0
- package/dist/lib/color.js.map +1 -0
- package/dist/lib/numeric.d.ts +35 -0
- package/dist/lib/numeric.js +29 -0
- package/dist/lib/numeric.js.map +1 -0
- package/dist/lib/overlay-container.d.ts +14 -4
- package/dist/lib/overlay-container.js.map +1 -1
- 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 +17 -6
- package/dist/styles.css +1 -1
- package/dist/themes.css +8 -0
- package/dist/tokens.css +86 -5
- package/dist/tokens.d.ts +4 -1
- package/dist/tokens.js +56 -36
- package/dist/tokens.json +56 -36
- package/llms.txt +35 -0
- package/package.json +5 -2
- 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 +8 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/charts/RadialChart/RadialChart.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useState,\n type ComponentProps,\n type ReactNode,\n} from 'react'\nimport {\n PolarAngleAxis,\n RadialBar as RechartsRadialBar,\n RadialBarChart as RechartsRadialBarChart,\n Sector,\n} from 'recharts'\nimport type { SectorProps } from 'recharts'\nimport type { TypedDataKey } from 'recharts/types/util/typedDataKey'\nimport { useReducedMotion } from 'motion/react'\nimport { ChartContainer, colorStops, cssName, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartLegend, ChartLegendContent } from '../lib/legend'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport { LoadingIndicator } from '../lib/loading'\nimport { ColorStops } from '../lib/paint'\nimport type { ChartLegendSlotProps, ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\nconst CORNER_RADIUS = 5\nconst BAR_SIZE = 14\nconst LOADING_BARS = 5\n\n/** One skeleton morph, in milliseconds. */\nconst LOADING_CYCLE = 1500\n\n/**\n * The arc the bars are drawn on.\n *\n * `full` is a ring; `semi` is the half-circle a gauge wants, with the centre\n * dropped so the arc sits in the middle of its own box rather than at the top.\n */\nexport type RadialVariant = 'full' | 'semi'\n\n/** The angles and centre each arc shape needs. */\nconst ARC: Record<RadialVariant, { startAngle: number; endAngle: number; cx: string; cy: string }> =\n {\n // Starting at 90° and running to -270° draws the ring clockwise from noon,\n // which is the direction a reader expects a total to fill in.\n full: { startAngle: 90, endAngle: -270, cx: '50%', cy: '50%' },\n semi: { startAngle: 180, endAngle: 0, cx: '50%', cy: '70%' },\n }\n\ninterface RadialChartContextValue {\n config: ChartConfig\n nameKey: string\n isLoading: boolean\n selectedBar: string | null\n selectBar: (name: string | null, value?: number) => void\n}\n\nconst RadialChartContext = createContext<RadialChartContextValue | null>(null)\n\nfunction useRadialChart(): RadialChartContextValue {\n const context = useContext(RadialChartContext)\n if (!context) throw new Error('A radial chart part must be rendered inside <RadialChart>')\n return context\n}\n\nexport interface RadialChartProps<TData extends Record<string, unknown>> {\n /** Bar names → their label and paint. Keys must match the `nameKey` values. */\n config: ChartConfig\n /** The rows the chart draws. One entry per point, bar or category. */\n data: TData[]\n /** The row field naming each bar. */\n nameKey: keyof TData & string\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 /**\n * The composed parts — axes, grid, tooltip, legend, and the marks\n * themselves.\n */\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 RechartsRadialBarChart>\n /** The arc shape — a full ring, or the half circle a gauge wants. */\n variant?: RadialVariant\n /**\n * What a full sweep is worth.\n *\n * Without it the scale comes from the data, so the largest bar always fills\n * the arc — which is right for a comparison and wrong for a gauge. Set it\n * (typically 100) whenever a single value has to read against a fixed total,\n * or \"62%\" and \"98%\" will look identical.\n */\n max?: number\n /** Where the arc starts, from the centre. */\n innerRadius?: number | string\n /** Where the arc ends. */\n outerRadius?: number | string\n /** The bar lit on first render. */\n defaultSelectedBar?: string | null\n /** Fires when the selection changes, and with null when it is cleared. */\n onSelectionChange?: (selection: { name: string; value: number } | null) => void\n /**\n * Swaps the marks for an animated skeleton, keeping the measured height so\n * the page does not jump when the data lands.\n */\n isLoading?: boolean\n /** The row field holding each bar's number. Used by the table view. */\n valueKey?: keyof TData & string\n /**\n * Drops the hidden table view. Only correct when the page prints the data\n * itself.\n */\n hideDataTable?: boolean\n}\n\n/**\n * Values on an arc — a gauge, or a small set of totals against one scale.\n *\n * The caveat worth knowing before reaching for it: a radial bar's LENGTH is its\n * value, but its RADIUS is not, so an inner bar and an outer bar of the same\n * value are drawn different lengths. That makes it a poor comparison and a good\n * single-value gauge; past about four bars, a `<BarChart>` is the honest choice.\n *\n * @example\n * <RadialChart title=\"Storage used\" config={config} data={data} nameKey=\"tier\" max={100}>\n * <RadialChart.RadialBar dataKey=\"used\" />\n * <RadialChart.Tooltip />\n * </RadialChart>\n */\nexport function RadialChart<TData extends Record<string, unknown>>({\n config,\n data,\n nameKey,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n variant = 'full',\n max,\n innerRadius = '30%',\n outerRadius = '100%',\n defaultSelectedBar = null,\n onSelectionChange,\n isLoading = false,\n valueKey,\n hideDataTable = false,\n}: RadialChartProps<TData>) {\n const chartId = useId().replace(/:/g, '')\n const [selectedBar, setSelectedBar] = useState<string | null>(defaultSelectedBar)\n const loadingData = useLoadingBars(isLoading)\n const arc = ARC[variant]\n\n const selectBar = useCallback(\n (name: string | null, value?: number) => {\n setSelectedBar(name)\n onSelectionChange?.(name === null ? null : { name, value: value ?? 0 })\n },\n [onSelectionChange],\n )\n\n const painted = useMemo(\n () =>\n data.map((row) => ({\n ...row,\n fill: `url(#${chartId}-colors-${cssName(String(row[nameKey]))})`,\n })),\n [chartId, data, nameKey],\n )\n\n const context = useMemo<RadialChartContextValue>(\n () => ({ config, nameKey, isLoading, selectedBar, selectBar }),\n [config, isLoading, nameKey, selectBar, selectedBar],\n )\n\n return (\n <RadialChartContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable || !valueKey\n ? false\n : { rows: data, rowKey: nameKey, columns: [{ key: valueKey, label: 'Value' }] }\n }\n >\n <ChartContainer config={config}>\n <RechartsRadialBarChart\n id={chartId}\n data={isLoading ? loadingData : painted}\n innerRadius={innerRadius}\n outerRadius={outerRadius}\n startAngle={arc.startAngle}\n endAngle={arc.endAngle}\n cx={arc.cx}\n cy={arc.cy}\n {...chartProps}\n >\n {max != null && max > 0 && (\n <PolarAngleAxis type=\"number\" domain={[0, max]} tick={false} axisLine={false} />\n )}\n {children}\n {isLoading && <LoadingRadialBar />}\n <defs>\n {Object.entries(config).map(([key, series]) => (\n <linearGradient\n key={key}\n id={`${chartId}-colors-${cssName(key)}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"1\"\n y2=\"1\"\n >\n <ColorStops dataKey={cssName(key)} stops={colorStops(series)} />\n </linearGradient>\n ))}\n </defs>\n </RechartsRadialBarChart>\n </ChartContainer>\n <LoadingIndicator isLoading={isLoading} />\n </ChartFigure>\n </RadialChartContext.Provider>\n )\n}\n\nexport interface RadialBarProps {\n /** The row field holding each bar's number. */\n dataKey: string\n cornerRadius?: number\n /** How thick each arc is, in pixels. */\n barSize?: number\n /** Draws the unfilled remainder behind each bar. Required reading for a gauge. */\n showTrack?: boolean\n isClickable?: boolean\n radialBarProps?: Omit<ComponentProps<typeof RechartsRadialBar>, 'dataKey'>\n}\n\n/** The arcs. One per row. */\nfunction RadialBar({\n dataKey,\n cornerRadius = CORNER_RADIUS,\n barSize = BAR_SIZE,\n showTrack = true,\n isClickable = false,\n radialBarProps,\n}: RadialBarProps) {\n const { nameKey, isLoading, selectedBar, selectBar } = useRadialChart()\n\n if (isLoading) return null\n\n return (\n <RechartsRadialBar\n dataKey={dataKey as TypedDataKey<Record<string, unknown>>}\n cornerRadius={cornerRadius}\n barSize={barSize}\n background={showTrack}\n style={isClickable ? { cursor: 'pointer' } : undefined}\n onClick={(payload, index) => {\n if (!isClickable) return\n const row = payload as Record<string, unknown>\n const name = (row?.[nameKey] as string | undefined) ?? String(index)\n selectBar(selectedBar === name ? null : name, Number(row?.[dataKey] ?? 0))\n }}\n shape={(props: SectorProps) => {\n const name = (props as unknown as Record<string, unknown>)[nameKey] as string\n const isSelected = selectedBar === null || selectedBar === name\n\n return (\n <Sector\n {...props}\n opacity={isClickable && !isSelected ? 0.15 : 1}\n className=\"transition-opacity duration-(--duration-base)\"\n />\n )\n }}\n {...radialBarProps}\n />\n )\n}\n\n/** The hover panel. No heading: the bar's own name is the row label. */\nfunction Tooltip({ variant, roundness, defaultIndex }: ChartTooltipSlotProps) {\n const { nameKey, isLoading } = useRadialChart()\n if (isLoading) return null\n\n return (\n <ChartTooltip\n defaultIndex={defaultIndex}\n cursor={false}\n content={\n <ChartTooltipContent nameKey={nameKey} hideLabel roundness={roundness} variant={variant} />\n }\n />\n )\n}\n\n/** The key. Under the arc and centred — nothing else names the bars. */\nfunction Legend({\n variant,\n align = 'center',\n verticalAlign = 'bottom',\n isClickable = false,\n}: ChartLegendSlotProps) {\n const { nameKey, isLoading, selectedBar, selectBar } = useRadialChart()\n if (isLoading) return null\n\n return (\n <ChartLegend\n verticalAlign={verticalAlign}\n align={align}\n content={\n <ChartLegendContent\n selected={selectedBar}\n onSelectChange={selectBar}\n isClickable={isClickable}\n nameKey={nameKey}\n variant={variant}\n />\n }\n />\n )\n}\n\n/** The decorative plate behind the arc. */\nfunction Background({ variant = 'dots' }: { variant?: ChartBackgroundVariant }) {\n const { isLoading } = useRadialChart()\n if (isLoading) return null\n return <ChartBackground variant={variant} />\n}\n\n/** A deterministic bar length, so the server and the client agree. */\nfunction sweep(index: number, cycle: number): number {\n const x = Math.sin(index * 53.9 + cycle * 173.1) * 13_759.3\n return 40 + (x - Math.floor(x)) * 60\n}\n\n/** The skeleton's arcs, re-rolled once per cycle. */\nfunction useLoadingBars(isLoading: boolean) {\n const [cycle, setCycle] = useState(0)\n\n useEffect(() => {\n if (!isLoading) return\n const timer = setInterval(() => setCycle((previous) => previous + 1), LOADING_CYCLE)\n return () => clearInterval(timer)\n }, [isLoading])\n\n return useMemo(\n () =>\n Array.from({ length: LOADING_BARS }, (_, index) => ({\n name: `loading-${index}`,\n value: sweep(index, cycle),\n })),\n [cycle],\n )\n}\n\n/** The skeleton arcs, morphing while the data is in flight. */\nfunction LoadingRadialBar() {\n const reduceMotion = useReducedMotion()\n\n return (\n <RechartsRadialBar\n dataKey=\"value\"\n cornerRadius={CORNER_RADIUS}\n barSize={BAR_SIZE}\n background\n className=\"text-(--ink)\"\n isAnimationActive={!reduceMotion}\n animationDuration={LOADING_CYCLE}\n animationEasing=\"ease-in-out\"\n shape={(props: SectorProps) => <Sector {...props} fill=\"currentColor\" fillOpacity={0.25} />}\n />\n )\n}\n\nRadialChart.RadialBar = RadialBar\nRadialChart.Tooltip = Tooltip\nRadialChart.Legend = Legend\nRadialChart.Background = Background\n\nexport default RadialChart\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/charts/RadialChart/RadialChart.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useState,\n type ComponentProps,\n type ReactNode,\n} from 'react'\nimport {\n PolarAngleAxis,\n RadialBar as RechartsRadialBar,\n RadialBarChart as RechartsRadialBarChart,\n Sector,\n} from 'recharts'\nimport type { SectorProps } from 'recharts'\nimport type { TypedDataKey } from 'recharts/types/util/typedDataKey'\nimport { useReducedMotion } from 'motion/react'\nimport { ChartContainer, colorStops, cssName, type ChartConfig } from '../lib/chart'\nimport { findSlot } from '../lib/values'\nimport { ChartFigure } from '../lib/figure'\nimport { type ChartEmptyProps } from '../lib/empty'\nimport { useChartSelection } from '../lib/selection'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartLegend, ChartLegendContent } from '../lib/legend'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport { LoadingIndicator } from '../lib/loading'\nimport { ColorStops } from '../lib/paint'\nimport type { ChartLegendSlotProps, ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\nconst CORNER_RADIUS = 5\nconst BAR_SIZE = 14\nconst LOADING_BARS = 5\n\n/** One skeleton morph, in milliseconds. */\nconst LOADING_CYCLE = 1500\n\n/**\n * The arc the bars are drawn on.\n *\n * `full` is a ring; `semi` is the half-circle a gauge wants, with the centre\n * dropped so the arc sits in the middle of its own box rather than at the top.\n */\nexport type RadialVariant = 'full' | 'semi'\n\n/** The angles and centre each arc shape needs. */\nconst ARC: Record<RadialVariant, { startAngle: number; endAngle: number; cx: string; cy: string }> =\n {\n // Starting at 90° and running to -270° draws the ring clockwise from noon,\n // which is the direction a reader expects a total to fill in.\n full: { startAngle: 90, endAngle: -270, cx: '50%', cy: '50%' },\n semi: { startAngle: 180, endAngle: 0, cx: '50%', cy: '70%' },\n }\n\ninterface RadialChartContextValue {\n config: ChartConfig\n nameKey: string\n isLoading: boolean\n selectedBar: string | null\n selectBar: (name: string | null, value?: number) => void\n}\n\n/**\n * The row field holding each bar's number, however the call site said it.\n *\n * `valueKey` is the explicit answer; the arc's own `dataKey` is the one every\n * call site already gives. Reading the slot means the legend reports the same\n * number the arc does, and the table gets built for a chart that named neither\n * — which used to render no table at all.\n */\nfunction valueField(valueKey: string | undefined, children: ReactNode): string | null {\n return valueKey ?? findSlot<RadialBarProps>(children, RadialBar)?.dataKey ?? null\n}\n\nconst RadialChartContext = createContext<RadialChartContextValue | null>(null)\n\nfunction useRadialChart(): RadialChartContextValue {\n const context = useContext(RadialChartContext)\n if (!context) throw new Error('A radial chart part must be rendered inside <RadialChart>')\n return context\n}\n\nexport interface RadialChartProps<TData extends Record<string, unknown>> {\n /** Bar names → their label and paint. Keys must match the `nameKey` values. */\n config: ChartConfig\n /** The rows the chart draws. One entry per point, bar or category. */\n data: TData[]\n /** The row field naming each bar. */\n nameKey: keyof TData & string\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 /**\n * The composed parts — axes, grid, tooltip, legend, and the marks\n * themselves.\n */\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 RechartsRadialBarChart>\n /** The arc shape — a full ring, or the half circle a gauge wants. */\n variant?: RadialVariant\n /**\n * What a full sweep is worth.\n *\n * Without it the scale comes from the data, so the largest bar always fills\n * the arc — which is right for a comparison and wrong for a gauge. Set it\n * (typically 100) whenever a single value has to read against a fixed total,\n * or \"62%\" and \"98%\" will look identical.\n */\n max?: number\n /** Where the arc starts, from the centre. */\n innerRadius?: number | string\n /** Where the arc ends. */\n outerRadius?: number | string\n /** The bar lit on first render, when the chart keeps its own selection. */\n defaultSelectedBar?: string | null\n /**\n * The selected bar, driven from outside.\n *\n * Give this and the chart follows it; leave it undefined and the chart keeps\n * its own, starting from `defaultSelectedBar`.\n */\n selectedBar?: string | null\n /** Fires when the selection changes, and with null when it is cleared. */\n onSelectionChange?: (selection: { name: string; value: number } | null) => void\n /**\n * Swaps the marks for an animated skeleton, keeping the measured height so\n * the page does not jump when the data lands.\n */\n isLoading?: boolean\n /**\n * The row field holding each bar's number, for the table view and for the\n * selection the legend reports.\n *\n * Falls back to the `dataKey` of the composed `<RadialChart.RadialBar>`, so\n * the usual call site needs neither.\n */\n valueKey?: keyof TData & string\n /**\n * Drops the hidden table view. Only correct when the page prints the data\n * itself.\n */\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 * Values on an arc — a gauge, or a small set of totals against one scale.\n *\n * The caveat worth knowing before reaching for it: a radial bar's LENGTH is its\n * value, but its RADIUS is not, so an inner bar and an outer bar of the same\n * value are drawn different lengths. That makes it a poor comparison and a good\n * single-value gauge; past about four bars, a `<BarChart>` is the honest choice.\n *\n * @example\n * <RadialChart title=\"Storage used\" config={config} data={data} nameKey=\"tier\" max={100}>\n * <RadialChart.RadialBar dataKey=\"used\" />\n * <RadialChart.Tooltip />\n * </RadialChart>\n */\nexport function RadialChart<TData extends Record<string, unknown>>({\n config,\n data,\n nameKey,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n variant = 'full',\n max,\n innerRadius = '30%',\n outerRadius = '100%',\n defaultSelectedBar = null,\n selectedBar: controlledBar,\n onSelectionChange,\n isLoading = false,\n valueKey,\n hideDataTable = false,\n empty,\n}: RadialChartProps<TData>) {\n const chartId = useId().replace(/:/g, '')\n const loadingData = useLoadingBars(isLoading)\n const arc = ARC[variant]\n const valueSource = valueField(valueKey, children)\n\n const [selectedBar, setSelection] = useChartSelection(\n controlledBar,\n defaultSelectedBar,\n undefined,\n )\n\n const selectBar = useCallback(\n (name: string | null, value?: number) => {\n setSelection(name)\n if (!onSelectionChange) return\n if (name === null) {\n onSelectionChange(null)\n return\n }\n // The arc hands its own number in; the legend has only a name, and used\n // to be given a zero on its behalf — so one selection reported two\n // different values depending on which control the reader used. The row is\n // where the legend's number comes from now.\n const row = valueSource ? data.find((entry) => String(entry[nameKey]) === name) : undefined\n const resolved = value ?? (row && valueSource ? Number(row[valueSource]) : Number.NaN)\n onSelectionChange({ name, value: Number.isFinite(resolved) ? resolved : 0 })\n },\n [data, nameKey, onSelectionChange, setSelection, valueSource],\n )\n\n const painted = useMemo(\n () =>\n data.map((row) => ({\n ...row,\n fill: `url(#${chartId}-colors-${cssName(String(row[nameKey]))})`,\n })),\n [chartId, data, nameKey],\n )\n\n const context = useMemo<RadialChartContextValue>(\n () => ({ config, nameKey, isLoading, selectedBar, selectBar }),\n [config, isLoading, nameKey, selectBar, selectedBar],\n )\n\n return (\n <RadialChartContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={\n hideDataTable || !valueSource\n ? false\n : { rows: data, rowKey: nameKey, columns: [{ key: valueSource, label: 'Value' }] }\n }\n isEmpty={!isLoading && data.length === 0}\n empty={empty}\n >\n <ChartContainer config={config}>\n <RechartsRadialBarChart\n id={chartId}\n data={isLoading ? loadingData : painted}\n innerRadius={innerRadius}\n outerRadius={outerRadius}\n startAngle={arc.startAngle}\n endAngle={arc.endAngle}\n cx={arc.cx}\n cy={arc.cy}\n {...chartProps}\n >\n {max != null && max > 0 && (\n <PolarAngleAxis type=\"number\" domain={[0, max]} tick={false} axisLine={false} />\n )}\n {children}\n {isLoading && <LoadingRadialBar />}\n <defs>\n {Object.entries(config).map(([key, series]) => (\n <linearGradient\n key={key}\n id={`${chartId}-colors-${cssName(key)}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"1\"\n y2=\"1\"\n >\n <ColorStops dataKey={cssName(key)} stops={colorStops(series)} />\n </linearGradient>\n ))}\n </defs>\n </RechartsRadialBarChart>\n </ChartContainer>\n <LoadingIndicator isLoading={isLoading} />\n </ChartFigure>\n </RadialChartContext.Provider>\n )\n}\n\nexport interface RadialBarProps {\n /** The row field holding each bar's number. */\n dataKey: string\n cornerRadius?: number\n /** How thick each arc is, in pixels. */\n barSize?: number\n /** Draws the unfilled remainder behind each bar. Required reading for a gauge. */\n showTrack?: boolean\n isClickable?: boolean\n radialBarProps?: Omit<ComponentProps<typeof RechartsRadialBar>, 'dataKey'>\n}\n\n/** The arcs. One per row. */\nfunction RadialBar({\n dataKey,\n cornerRadius = CORNER_RADIUS,\n barSize = BAR_SIZE,\n showTrack = true,\n isClickable = false,\n radialBarProps,\n}: RadialBarProps) {\n const { nameKey, isLoading, selectedBar, selectBar } = useRadialChart()\n\n if (isLoading) return null\n\n return (\n <RechartsRadialBar\n dataKey={dataKey as TypedDataKey<Record<string, unknown>>}\n cornerRadius={cornerRadius}\n barSize={barSize}\n background={showTrack}\n style={isClickable ? { cursor: 'pointer' } : undefined}\n onClick={(payload, index) => {\n if (!isClickable) return\n const row = payload as Record<string, unknown>\n const name = (row?.[nameKey] as string | undefined) ?? String(index)\n selectBar(selectedBar === name ? null : name, Number(row?.[dataKey] ?? 0))\n }}\n shape={(props: SectorProps) => {\n const name = (props as unknown as Record<string, unknown>)[nameKey] as string\n const isSelected = selectedBar === null || selectedBar === name\n\n return (\n <Sector\n {...props}\n opacity={isClickable && !isSelected ? 0.15 : 1}\n className=\"transition-opacity duration-(--duration-base)\"\n />\n )\n }}\n {...radialBarProps}\n />\n )\n}\n\n/** The hover panel. No heading: the bar's own name is the row label. */\nfunction Tooltip({ variant, roundness, defaultIndex }: ChartTooltipSlotProps) {\n const { nameKey, isLoading } = useRadialChart()\n if (isLoading) return null\n\n return (\n <ChartTooltip\n defaultIndex={defaultIndex}\n cursor={false}\n content={\n <ChartTooltipContent nameKey={nameKey} hideLabel roundness={roundness} variant={variant} />\n }\n />\n )\n}\n\n/** The key. Under the arc and centred — nothing else names the bars. */\nfunction Legend({\n variant,\n align = 'center',\n verticalAlign = 'bottom',\n isClickable = false,\n}: ChartLegendSlotProps) {\n const { nameKey, isLoading, selectedBar, selectBar } = useRadialChart()\n if (isLoading) return null\n\n return (\n <ChartLegend\n verticalAlign={verticalAlign}\n align={align}\n content={\n <ChartLegendContent\n selected={selectedBar}\n onSelectChange={selectBar}\n isClickable={isClickable}\n nameKey={nameKey}\n variant={variant}\n />\n }\n />\n )\n}\n\n/** The decorative plate behind the arc. */\nfunction Background({ variant = 'dots' }: { variant?: ChartBackgroundVariant }) {\n const { isLoading } = useRadialChart()\n if (isLoading) return null\n return <ChartBackground variant={variant} />\n}\n\n/** A deterministic bar length, so the server and the client agree. */\nfunction sweep(index: number, cycle: number): number {\n const x = Math.sin(index * 53.9 + cycle * 173.1) * 13_759.3\n return 40 + (x - Math.floor(x)) * 60\n}\n\n/** The skeleton's arcs, re-rolled once per cycle. */\nfunction useLoadingBars(isLoading: boolean) {\n const [cycle, setCycle] = useState(0)\n\n useEffect(() => {\n if (!isLoading) return\n const timer = setInterval(() => setCycle((previous) => previous + 1), LOADING_CYCLE)\n return () => clearInterval(timer)\n }, [isLoading])\n\n return useMemo(\n () =>\n Array.from({ length: LOADING_BARS }, (_, index) => ({\n name: `loading-${index}`,\n value: sweep(index, cycle),\n })),\n [cycle],\n )\n}\n\n/** The skeleton arcs, morphing while the data is in flight. */\nfunction LoadingRadialBar() {\n const reduceMotion = useReducedMotion()\n\n return (\n <RechartsRadialBar\n dataKey=\"value\"\n cornerRadius={CORNER_RADIUS}\n barSize={BAR_SIZE}\n background\n className=\"text-(--ink)\"\n isAnimationActive={!reduceMotion}\n animationDuration={LOADING_CYCLE}\n animationEasing=\"ease-in-out\"\n shape={(props: SectorProps) => <Sector {...props} fill=\"currentColor\" fillOpacity={0.25} />}\n />\n )\n}\n\nRadialChart.RadialBar = RadialBar\nRadialChart.Tooltip = Tooltip\nRadialChart.Legend = Legend\nRadialChart.Background = Background\n\nexport default RadialChart\n"],"mappings":"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","names":[]}
|
|
@@ -2,9 +2,9 @@ import * as react from 'react';
|
|
|
2
2
|
import { ReactNode, FC } from 'react';
|
|
3
3
|
import { SankeyData, SankeyProps } from 'recharts';
|
|
4
4
|
import { ChartConfig } from '../lib/chart.js';
|
|
5
|
+
import { ChartEmptyProps } from '../lib/empty.js';
|
|
5
6
|
import { ChartBackgroundVariant } from '../lib/background.js';
|
|
6
7
|
import { ChartTooltipSlotProps } from '../AreaChart/AreaChart.js';
|
|
7
|
-
import '../lib/empty.js';
|
|
8
8
|
import '../lib/values.js';
|
|
9
9
|
import '../lib/sonify-control.js';
|
|
10
10
|
import '../lib/sonify.js';
|
|
@@ -69,10 +69,17 @@ interface SankeyChartProps {
|
|
|
69
69
|
/** How nodes are distributed within a column. */
|
|
70
70
|
verticalAlign?: 'justify' | 'top';
|
|
71
71
|
/**
|
|
72
|
-
* The node lit on first render
|
|
73
|
-
* touch.
|
|
72
|
+
* The node lit on first render, when the chart keeps its own selection.
|
|
73
|
+
* Selecting one dims every flow it does not touch.
|
|
74
74
|
*/
|
|
75
75
|
defaultSelectedNode?: string | null;
|
|
76
|
+
/**
|
|
77
|
+
* The selected node, driven from outside.
|
|
78
|
+
*
|
|
79
|
+
* Give this and the chart follows it; leave it undefined and the chart keeps
|
|
80
|
+
* its own, starting from `defaultSelectedNode`.
|
|
81
|
+
*/
|
|
82
|
+
selectedNode?: string | null;
|
|
76
83
|
/** Fires when the selection changes, and with null when it is cleared. */
|
|
77
84
|
onSelectionChange?: (selection: {
|
|
78
85
|
name: string;
|
|
@@ -88,6 +95,11 @@ interface SankeyChartProps {
|
|
|
88
95
|
* itself.
|
|
89
96
|
*/
|
|
90
97
|
hideDataTable?: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* What the chart shows when it has nothing to draw. `false` keeps the empty
|
|
100
|
+
* plot, for a chart whose emptiness is itself the reading.
|
|
101
|
+
*/
|
|
102
|
+
empty?: ChartEmptyProps | false;
|
|
91
103
|
}
|
|
92
104
|
/**
|
|
93
105
|
* Where a quantity goes as it moves through stages — the shape for a funnel, a
|
|
@@ -106,7 +118,7 @@ interface SankeyChartProps {
|
|
|
106
118
|
* <SankeyChart.Tooltip />
|
|
107
119
|
* </SankeyChart>
|
|
108
120
|
*/
|
|
109
|
-
declare function SankeyChart({ data, config, title, showTitle, description, children, className, sankeyProps, nodeWidth, nodePadding, linkCurvature, iterations, sort, align, verticalAlign, defaultSelectedNode, onSelectionChange, isLoading, hideDataTable, }: SankeyChartProps): react.JSX.Element;
|
|
121
|
+
declare function SankeyChart({ data, config, title, showTitle, description, children, className, sankeyProps, nodeWidth, nodePadding, linkCurvature, iterations, sort, align, verticalAlign, defaultSelectedNode, selectedNode: controlledNode, onSelectionChange, isLoading, hideDataTable, empty, }: SankeyChartProps): react.JSX.Element;
|
|
110
122
|
declare namespace SankeyChart {
|
|
111
123
|
var Node: FC<NodeProps>;
|
|
112
124
|
var NodeLabel: FC<NodeLabelProps>;
|
|
@@ -7,8 +7,7 @@ import {
|
|
|
7
7
|
useCallback,
|
|
8
8
|
useContext,
|
|
9
9
|
useId,
|
|
10
|
-
useMemo
|
|
11
|
-
useState
|
|
10
|
+
useMemo
|
|
12
11
|
} from "react";
|
|
13
12
|
import {
|
|
14
13
|
Layer,
|
|
@@ -17,6 +16,8 @@ import {
|
|
|
17
16
|
import { motion, useReducedMotion } from "motion/react";
|
|
18
17
|
import { ChartContainer, colorStops, cssName } from "../lib/chart.js";
|
|
19
18
|
import { ChartFigure } from "../lib/figure.js";
|
|
19
|
+
import {} from "../lib/empty.js";
|
|
20
|
+
import { useChartSelection } from "../lib/selection.js";
|
|
20
21
|
import { ChartBackground } from "../lib/background.js";
|
|
21
22
|
import { ChartTooltip, ChartTooltipContent } from "../lib/tooltip.js";
|
|
22
23
|
import { LoadingIndicator } from "../lib/loading.js";
|
|
@@ -45,20 +46,25 @@ function SankeyChart({
|
|
|
45
46
|
align = "justify",
|
|
46
47
|
verticalAlign = "justify",
|
|
47
48
|
defaultSelectedNode = null,
|
|
49
|
+
selectedNode: controlledNode,
|
|
48
50
|
onSelectionChange,
|
|
49
51
|
isLoading = false,
|
|
50
|
-
hideDataTable = false
|
|
52
|
+
hideDataTable = false,
|
|
53
|
+
empty
|
|
51
54
|
}) {
|
|
52
55
|
const chartId = useId().replace(/:/g, "");
|
|
53
|
-
const
|
|
54
|
-
const selectNode = useCallback(
|
|
56
|
+
const report = useCallback(
|
|
55
57
|
(name) => {
|
|
56
|
-
setSelectedNode(name);
|
|
57
58
|
if (!onSelectionChange) return;
|
|
58
59
|
onSelectionChange(name === null ? null : { name, value: nodeValue(data, name) });
|
|
59
60
|
},
|
|
60
61
|
[data, onSelectionChange]
|
|
61
62
|
);
|
|
63
|
+
const [selectedNode, selectNode] = useChartSelection(
|
|
64
|
+
controlledNode,
|
|
65
|
+
defaultSelectedNode,
|
|
66
|
+
report
|
|
67
|
+
);
|
|
62
68
|
const context = useMemo(
|
|
63
69
|
() => ({ data, config, chartId, isLoading, selectedNode, selectNode }),
|
|
64
70
|
[chartId, config, data, isLoading, selectNode, selectedNode]
|
|
@@ -86,6 +92,8 @@ function SankeyChart({
|
|
|
86
92
|
{ key: "value", label: "Value" }
|
|
87
93
|
]
|
|
88
94
|
},
|
|
95
|
+
isEmpty: !isLoading && (data.nodes.length === 0 || data.links.length === 0),
|
|
96
|
+
empty,
|
|
89
97
|
children: [
|
|
90
98
|
isLoading ? /* @__PURE__ */ jsx("div", { className: "relative aspect-video w-full", children: /* @__PURE__ */ jsx(
|
|
91
99
|
"svg",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/charts/SankeyChart/SankeyChart.tsx"],"sourcesContent":["'use client'\n\nimport {\n Children,\n createContext,\n isValidElement,\n useCallback,\n useContext,\n useId,\n useMemo,\n useState,\n type FC,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport {\n Layer,\n Sankey as RechartsSankey,\n type SankeyData,\n type SankeyLinkProps,\n type SankeyNodeProps,\n type SankeyProps,\n} from 'recharts'\nimport { motion, useReducedMotion } from 'motion/react'\nimport { ChartContainer, colorStops, cssName, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport { LoadingIndicator } from '../lib/loading'\nimport { ColorStops } from '../lib/paint'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/** One full skeleton pulse, in seconds. */\nconst LOADING_PULSE = 2\n\n/**\n * How a flow band is coloured.\n *\n * `gradient` fades from the source's colour to the target's, which is the one\n * that actually reads as flow. `source` and `target` attribute the whole band\n * to one end — useful when the question is \"where did this come from\" rather\n * than \"what became of it\". `solid` gives up colour entirely and lets the\n * node rectangles carry identity.\n */\nexport type SankeyLinkVariant = 'gradient' | 'solid' | 'source' | 'target'\n\n/** Where a node's label sits. */\nexport type SankeyLabelPosition = 'inside' | 'outside'\n\ninterface SankeyChartContextValue {\n data: SankeyData\n config: ChartConfig\n chartId: string\n isLoading: boolean\n selectedNode: string | null\n selectNode: (name: string | null) => void\n}\n\nconst SankeyChartContext = createContext<SankeyChartContextValue | null>(null)\n\nfunction useSankeyChart(): SankeyChartContextValue {\n const context = useContext(SankeyChartContext)\n if (!context) throw new Error('A sankey chart part must be rendered inside <SankeyChart>')\n return context\n}\n\nexport interface SankeyChartProps {\n /** The nodes and the links between them, in Recharts' own shape. */\n data: SankeyData\n /** Node names → their label and paint. */\n config: ChartConfig\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 /**\n * The composed parts — axes, grid, tooltip, legend, and the marks\n * themselves.\n */\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 Sankey element. */\n sankeyProps?: Omit<SankeyProps, 'data'>\n /** How wide each node rectangle is, in pixels. */\n nodeWidth?: number\n /** Vertical gap between nodes in the same column, in pixels. */\n nodePadding?: number\n /** 0 draws straight links, 1 the fullest curve. */\n linkCurvature?: number\n /** Layout passes. More is tidier and slower. */\n iterations?: number\n /** Lets the layout reorder nodes for the fewest crossings. */\n sort?: boolean\n /** How nodes are placed along the flow axis. */\n align?: 'left' | 'justify'\n /** How nodes are distributed within a column. */\n verticalAlign?: 'justify' | 'top'\n /**\n * The node lit on first render. Selecting one dims every flow it does not\n * touch.\n */\n defaultSelectedNode?: string | null\n /** Fires when the selection changes, and with null when it is cleared. */\n onSelectionChange?: (selection: { name: string; value: number } | null) => void\n /**\n * Swaps the marks for an animated skeleton, keeping the measured height so\n * the page does not jump when the data lands.\n */\n isLoading?: boolean\n /**\n * Drops the hidden table view. Only correct when the page prints the data\n * itself.\n */\n hideDataTable?: boolean\n}\n\n/**\n * Where a quantity goes as it moves through stages — the shape for a funnel, a\n * budget, an energy or traffic breakdown.\n *\n * The only chart here whose data is a GRAPH rather than a table, so it takes\n * `{ nodes, links }` instead of rows, and the table view lists the flows rather\n * than the nodes.\n *\n * @example\n * <SankeyChart title=\"Traffic by source and outcome\" config={config} data={data}>\n * <SankeyChart.Node radius={2}>\n * <SankeyChart.NodeLabel position=\"outside\" showValues />\n * </SankeyChart.Node>\n * <SankeyChart.Link variant=\"gradient\" />\n * <SankeyChart.Tooltip />\n * </SankeyChart>\n */\nexport function SankeyChart({\n data,\n config,\n title,\n showTitle,\n description,\n children,\n className,\n sankeyProps,\n nodeWidth = 10,\n nodePadding = 10,\n linkCurvature = 0.5,\n iterations = 32,\n sort = true,\n align = 'justify',\n verticalAlign = 'justify',\n defaultSelectedNode = null,\n onSelectionChange,\n isLoading = false,\n hideDataTable = false,\n}: SankeyChartProps) {\n const chartId = useId().replace(/:/g, '')\n const [selectedNode, setSelectedNode] = useState<string | null>(defaultSelectedNode)\n\n const selectNode = useCallback(\n (name: string | null) => {\n setSelectedNode(name)\n if (!onSelectionChange) return\n onSelectionChange(name === null ? null : { name, value: nodeValue(data, name) })\n },\n [data, onSelectionChange],\n )\n\n const context = useMemo<SankeyChartContextValue>(\n () => ({ data, config, chartId, isLoading, selectedNode, selectNode }),\n [chartId, config, data, isLoading, selectNode, selectedNode],\n )\n\n // The flows, not the nodes: a link IS the fact a sankey states, and a table\n // of node totals would lose every \"from → to\" the diagram is drawn to show.\n const flows = useMemo(\n () =>\n data.links.map((link) => ({\n from: String(data.nodes[link.source]?.name ?? link.source),\n to: String(data.nodes[link.target]?.name ?? link.target),\n value: link.value,\n })),\n [data],\n )\n\n return (\n <SankeyChartContext.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: flows,\n rowKey: 'from',\n columns: [\n { key: 'to', label: 'To' },\n { key: 'value', label: 'Value' },\n ],\n }\n }\n >\n {isLoading ? (\n <div className=\"relative aspect-video w-full\">\n <svg\n viewBox=\"0 0 500 250\"\n preserveAspectRatio=\"xMidYMid meet\"\n width=\"100%\"\n height=\"100%\"\n aria-hidden\n className=\"absolute inset-0 text-(--ink)\"\n >\n <LoadingSankey />\n </svg>\n </div>\n ) : (\n <ChartContainer config={config}>\n <RechartsSankey\n id={chartId}\n data={data}\n nodeWidth={nodeWidth}\n nodePadding={nodePadding}\n linkCurvature={linkCurvature}\n iterations={iterations}\n sort={sort}\n align={align}\n verticalAlign={verticalAlign}\n {...resolveRenderers(children)}\n {...sankeyProps}\n >\n {children}\n <defs>\n {Object.entries(config).map(([key, series]) => (\n <linearGradient\n key={key}\n id={`${chartId}-node-${cssName(key)}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <ColorStops dataKey={cssName(key)} stops={colorStops(series)} />\n </linearGradient>\n ))}\n </defs>\n </RechartsSankey>\n </ChartContainer>\n )}\n <LoadingIndicator isLoading={isLoading} />\n </ChartFigure>\n </SankeyChartContext.Provider>\n )\n}\n\nexport interface NodeProps {\n radius?: number\n isClickable?: boolean\n /** `<SankeyChart.NodeLabel>`. */\n children?: ReactNode\n}\n\n/** How the node rectangles render. A slot: the root reads it. */\nconst Node: FC<NodeProps> = () => null\n\nexport interface NodeLabelProps {\n position?: SankeyLabelPosition\n /** Prints each node's total flow under its name. */\n showValues?: boolean\n valueFormatter?: (value: number) => string\n}\n\n/** Labels for the nodes. A slot, composed inside `<Node>`. */\nconst NodeLabel: FC<NodeLabelProps> = () => null\n\nexport interface LinkProps {\n variant?: SankeyLinkVariant\n /** Thins each band where it meets a node, so neighbours stay separable. */\n verticalPadding?: number\n}\n\n/** How the flow bands render. A slot: the root reads it. */\nconst Link: FC<LinkProps> = () => null\n\n/** The hover panel. No heading: the node's own name is the row label. */\nfunction Tooltip({ variant, roundness, defaultIndex }: ChartTooltipSlotProps) {\n const { isLoading } = useSankeyChart()\n if (isLoading) return null\n\n return (\n <ChartTooltip\n defaultIndex={defaultIndex}\n content={\n <ChartTooltipContent nameKey=\"name\" hideLabel roundness={roundness} variant={variant} />\n }\n />\n )\n}\n\n/** The decorative plate behind the diagram. */\nfunction Background({ variant = 'dots' }: { variant?: ChartBackgroundVariant }) {\n const { isLoading } = useSankeyChart()\n if (isLoading) return null\n return <ChartBackground variant={variant} />\n}\n\n/** A node's total flow — outgoing, or incoming for a leaf. */\nfunction nodeValue(data: SankeyData, name: string): number {\n const index = data.nodes.findIndex((node) => node.name === name)\n if (index === -1) return 0\n\n const out = data.links\n .filter((link) => link.source === index)\n .reduce((sum, link) => sum + link.value, 0)\n const incoming = data.links\n .filter((link) => link.target === index)\n .reduce((sum, link) => sum + link.value, 0)\n\n return out > 0 ? out : incoming\n}\n\n/** Turns the composed `<Node>` and `<Link>` slots into Recharts renderers. */\nfunction resolveRenderers(children: ReactNode): Pick<SankeyProps, 'node' | 'link'> {\n let node: NodeProps | null = null\n let link: LinkProps | null = null\n\n Children.forEach(children, (child) => {\n if (!isValidElement(child)) return\n if (child.type === Node) node = (child as ReactElement<NodeProps>).props\n if (child.type === Link) link = (child as ReactElement<LinkProps>).props\n })\n\n return {\n node: (props: SankeyNodeProps) => <SankeyNodeShape {...props} nodeSlot={node} />,\n link: (props: SankeyLinkProps) => <SankeyLinkShape {...props} linkSlot={link} />,\n }\n}\n\n/** Reads the `<NodeLabel>` composed inside a `<Node>`, if any. */\nfunction resolveNodeLabel(children: ReactNode): NodeLabelProps | null {\n let label: NodeLabelProps | null = null\n\n Children.forEach(children, (child) => {\n if (isValidElement(child) && child.type === NodeLabel) {\n label = (child as ReactElement<NodeLabelProps>).props\n }\n })\n\n return label\n}\n\n/** Whether a node is the selected one, or one hop from it. */\nfunction isConnected(data: SankeyData, selected: string | null, name: string): boolean {\n if (selected === null || selected === name) return true\n\n const selectedIndex = data.nodes.findIndex((node) => node.name === selected)\n const nodeIndex = data.nodes.findIndex((node) => node.name === name)\n\n return data.links.some(\n (link) =>\n (link.source === selectedIndex && link.target === nodeIndex) ||\n (link.source === nodeIndex && link.target === selectedIndex),\n )\n}\n\n/** One node rectangle, with its label and value. */\nfunction SankeyNodeShape({\n x,\n y,\n width,\n height,\n payload,\n nodeSlot,\n}: SankeyNodeProps & { nodeSlot: NodeProps | null }) {\n const { config, chartId, data, selectedNode, selectNode } = useSankeyChart()\n\n const radius = nodeSlot?.radius ?? 0\n const isClickable = nodeSlot?.isClickable ?? false\n const label = resolveNodeLabel(nodeSlot?.children)\n\n const name = payload.name\n const value = payload.value\n const icon = (payload as { icon?: ReactNode }).icon\n\n const dimmed = isClickable && !isConnected(data, selectedNode, name)\n const painted = name in config\n const text = config[name]?.label ?? name\n\n const format = label?.valueFormatter ?? ((n: number) => n.toLocaleString())\n const showValues = label?.showValues ?? false\n\n const centreX = x + width / 2\n const centreY = showValues ? y + height / 2 - 8 : y + height / 2\n const valueY = y + height / 2 + 8\n const outsideX = x + width + 8\n const outsideY = y + height / 2\n\n return (\n <Layer>\n <rect\n x={x}\n y={y}\n width={width}\n height={height}\n rx={radius}\n ry={radius}\n fill={painted ? `url(#${chartId}-node-${cssName(name)})` : 'currentColor'}\n fillOpacity={dimmed ? 0.15 : 0.9}\n className=\"text-(--ink) transition-opacity duration-(--duration-base)\"\n style={isClickable ? { cursor: 'pointer' } : undefined}\n onClick={() => {\n if (!isClickable) return\n selectNode(selectedNode === name ? null : name)\n }}\n />\n {label?.position === 'inside' && (\n <>\n {/* A scrim inside the node, so the label reads against whatever the\n node's own gradient happens to be under it. */}\n <rect\n x={x + 1}\n y={y + 1}\n width={width - 2}\n height={height - 2}\n rx={Math.max(0, radius - 1)}\n ry={Math.max(0, radius - 1)}\n opacity={dimmed ? 0.3 : 1}\n className=\"fill-(--chart-surface)/60 transition-opacity duration-(--duration-base)\"\n style={{ pointerEvents: 'none' }}\n />\n {icon && (\n <foreignObject\n x={centreX - 8}\n y={centreY - 30}\n width={16}\n height={16}\n opacity={dimmed ? 0.3 : 1}\n className=\"transition-opacity duration-(--duration-base)\"\n style={{ pointerEvents: 'none' }}\n >\n <div className=\"flex items-center justify-center text-(--ink-2)\">{icon}</div>\n </foreignObject>\n )}\n <text\n x={centreX}\n y={icon ? centreY - 4 : centreY}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n className=\"fill-(--ink) text-[10px] font-medium transition-opacity duration-(--duration-base)\"\n opacity={dimmed ? 0.3 : 1}\n style={{ pointerEvents: 'none' }}\n >\n {text}\n </text>\n {showValues && (\n <text\n x={centreX}\n y={valueY}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n className=\"fill-(--ink) font-mono text-xs font-medium tabular-nums transition-opacity duration-(--duration-base)\"\n opacity={dimmed ? 0.3 : 0.6}\n style={{ pointerEvents: 'none' }}\n >\n {format(value)}\n </text>\n )}\n </>\n )}\n {label?.position === 'outside' && (\n <>\n <text\n x={outsideX}\n y={outsideY - (showValues ? 8 : 0)}\n textAnchor=\"start\"\n dominantBaseline=\"middle\"\n className=\"fill-(--ink) text-xs\"\n style={{ pointerEvents: 'none' }}\n >\n {text}\n </text>\n {showValues && (\n <text\n x={outsideX}\n y={outsideY + 8}\n textAnchor=\"start\"\n dominantBaseline=\"middle\"\n opacity={0.5}\n className=\"fill-(--ink) font-mono text-xs tabular-nums\"\n style={{ pointerEvents: 'none' }}\n >\n {format(value)}\n </text>\n )}\n </>\n )}\n </Layer>\n )\n}\n\n/** One flow band. */\nfunction SankeyLinkShape({\n sourceX,\n targetX,\n sourceY,\n targetY,\n sourceControlX,\n targetControlX,\n linkWidth,\n index,\n payload,\n linkSlot,\n}: SankeyLinkProps & { linkSlot: LinkProps | null }) {\n const { config, chartId, selectedNode } = useSankeyChart()\n\n const variant = linkSlot?.variant ?? 'gradient'\n const verticalPadding = linkSlot?.verticalPadding ?? 0\n\n const sourceName = payload.source.name\n const targetName = payload.target.name\n const lit =\n selectedNode === null || selectedNode === sourceName || selectedNode === targetName\n\n const half = Math.max(1, linkWidth - verticalPadding) / 2\n\n // Two mirrored cubics closed into a ribbon: the band has to be a filled shape\n // rather than a thick stroke, because its width changes along its length.\n const band = `M${sourceX},${sourceY - half}\n C${sourceControlX},${sourceY - half} ${targetControlX},${targetY - half} ${targetX},${targetY - half}\n L${targetX},${targetY + half}\n C${targetControlX},${targetY + half} ${sourceControlX},${sourceY + half} ${sourceX},${sourceY + half}\n Z`\n\n const sourceColor = sourceName in config ? `var(--color-${cssName(sourceName)}-0)` : 'currentColor'\n const targetColor = targetName in config ? `var(--color-${cssName(targetName)}-0)` : 'currentColor'\n\n return (\n <Layer className=\"text-(--ink)\">\n <defs>\n {variant === 'gradient' && (\n <linearGradient id={`${chartId}-link-${index}`} x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stopColor={sourceColor} stopOpacity={0.2} />\n <stop offset=\"50%\" stopColor={sourceColor} stopOpacity={0.5} />\n <stop offset=\"100%\" stopColor={targetColor} stopOpacity={0.2} />\n </linearGradient>\n )}\n {/* The edge that traces a band connected to the selection. It fades out\n at both ends so it reads as a highlight rather than as an outline. */}\n <linearGradient id={`${chartId}-link-edge-${index}`} x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stopColor=\"var(--accent)\" stopOpacity={0} />\n <stop offset=\"15%\" stopColor=\"var(--accent)\" stopOpacity={0.8} />\n <stop offset=\"50%\" stopColor=\"var(--accent)\" stopOpacity={1} />\n <stop offset=\"85%\" stopColor=\"var(--accent)\" stopOpacity={0.8} />\n <stop offset=\"100%\" stopColor=\"var(--accent)\" stopOpacity={0} />\n </linearGradient>\n </defs>\n <path\n d={band}\n fill={linkFill(variant, chartId, index, config, sourceName, targetName)}\n // Read from the ground rather than fixed: a band at 0.4 of ink over\n // paper reads; the same 0.4 of paper-white over near-black does not.\n fillOpacity={lit ? 'calc(var(--chart-fill) * 2.6)' : 'calc(var(--chart-fill) * 0.7)'}\n stroke={selectedNode !== null && lit ? `url(#${chartId}-link-edge-${index})` : 'none'}\n strokeWidth={1}\n className=\"transition-opacity duration-(--duration-base)\"\n />\n </Layer>\n )\n}\n\n/** The paint reference for a band, given its variant. */\nfunction linkFill(\n variant: SankeyLinkVariant,\n chartId: string,\n index: number,\n config: ChartConfig,\n sourceName: string,\n targetName: string,\n): string {\n switch (variant) {\n case 'gradient':\n return `url(#${chartId}-link-${index})`\n case 'source':\n return sourceName in config ? `url(#${chartId}-node-${cssName(sourceName)})` : 'currentColor'\n case 'target':\n return targetName in config ? `url(#${chartId}-node-${cssName(targetName)})` : 'currentColor'\n default:\n return 'currentColor'\n }\n}\n\n/** The skeleton's node columns. Fixed, so the shape reads as a sankey at once. */\nconst SKELETON_NODES = [\n { x: 30, y: 25, width: 12, height: 65, delay: 0 },\n { x: 30, y: 110, width: 12, height: 50, delay: 0.3 },\n { x: 30, y: 180, width: 12, height: 45, delay: 0.15 },\n { x: 244, y: 20, width: 12, height: 55, delay: 0.45 },\n { x: 244, y: 95, width: 12, height: 75, delay: 0.6 },\n { x: 244, y: 190, width: 12, height: 40, delay: 0.25 },\n { x: 458, y: 35, width: 12, height: 80, delay: 0.5 },\n { x: 458, y: 135, width: 12, height: 90, delay: 0.1 },\n]\n\nconst SKELETON_LINKS = [\n { from: 0, to: 3, width: 26, delay: 0.2 },\n { from: 0, to: 4, width: 18, delay: 0.7 },\n { from: 1, to: 4, width: 24, delay: 0.4 },\n { from: 1, to: 5, width: 12, delay: 0.9 },\n { from: 2, to: 4, width: 16, delay: 0.1 },\n { from: 2, to: 5, width: 14, delay: 0.55 },\n { from: 3, to: 6, width: 22, delay: 0.35 },\n { from: 3, to: 7, width: 18, delay: 0.8 },\n { from: 4, to: 6, width: 28, delay: 0.05 },\n { from: 4, to: 7, width: 32, delay: 0.65 },\n { from: 5, to: 7, width: 16, delay: 0.45 },\n]\n\n/** A bezier from one skeleton node's right edge to another's left. */\nfunction skeletonPath(fromIndex: number, toIndex: number): string {\n const from = SKELETON_NODES[fromIndex]!\n const to = SKELETON_NODES[toIndex]!\n const startX = from.x + from.width\n const startY = from.y + from.height / 2\n const endX = to.x\n const endY = to.y + to.height / 2\n return `M${startX},${startY} C${startX + (endX - startX) * 0.4},${startY} ${startX + (endX - startX) * 0.6},${endY} ${endX},${endY}`\n}\n\n/** The skeleton diagram: a fixed graph whose parts pulse out of phase. */\nfunction LoadingSankey() {\n const reduceMotion = useReducedMotion()\n\n if (reduceMotion) {\n return (\n <>\n {SKELETON_LINKS.map((link) => (\n <path\n key={`${link.from}-${link.to}`}\n d={skeletonPath(link.from, link.to)}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={link.width}\n opacity={0.09}\n />\n ))}\n {SKELETON_NODES.map((node) => (\n <rect\n key={`${node.x}-${node.y}`}\n x={node.x}\n y={node.y}\n width={node.width}\n height={node.height}\n rx={2}\n fill=\"currentColor\"\n opacity={0.25}\n />\n ))}\n </>\n )\n }\n\n return (\n <>\n {SKELETON_LINKS.map((link, index) => (\n <motion.path\n key={`${link.from}-${link.to}`}\n d={skeletonPath(link.from, link.to)}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={link.width}\n initial={{ opacity: 0.04 }}\n animate={{ opacity: [0.04, 0.14, 0.04] }}\n transition={{\n duration: LOADING_PULSE * (0.8 + (index % 3) * 0.2),\n delay: link.delay,\n repeat: Infinity,\n ease: 'easeInOut',\n }}\n />\n ))}\n {SKELETON_NODES.map((node, index) => (\n <motion.rect\n key={`${node.x}-${node.y}`}\n x={node.x}\n y={node.y}\n width={node.width}\n height={node.height}\n rx={2}\n fill=\"currentColor\"\n initial={{ opacity: 0.15 }}\n animate={{ opacity: [0.15, 0.4, 0.15] }}\n transition={{\n duration: LOADING_PULSE * (0.9 + (index % 4) * 0.1),\n delay: node.delay,\n repeat: Infinity,\n ease: 'easeInOut',\n }}\n />\n ))}\n </>\n )\n}\n\nSankeyChart.Node = Node\nSankeyChart.NodeLabel = NodeLabel\nSankeyChart.Link = Link\nSankeyChart.Tooltip = Tooltip\nSankeyChart.Background = Background\n\nexport default SankeyChart\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/charts/SankeyChart/SankeyChart.tsx"],"sourcesContent":["'use client'\n\nimport {\n Children,\n createContext,\n isValidElement,\n useCallback,\n useContext,\n useId,\n useMemo,\n type FC,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport {\n Layer,\n Sankey as RechartsSankey,\n type SankeyData,\n type SankeyLinkProps,\n type SankeyNodeProps,\n type SankeyProps,\n} from 'recharts'\nimport { motion, useReducedMotion } from 'motion/react'\nimport { ChartContainer, colorStops, cssName, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { type ChartEmptyProps } from '../lib/empty'\nimport { useChartSelection } from '../lib/selection'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport { LoadingIndicator } from '../lib/loading'\nimport { ColorStops } from '../lib/paint'\nimport type { ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\n/** One full skeleton pulse, in seconds. */\nconst LOADING_PULSE = 2\n\n/**\n * How a flow band is coloured.\n *\n * `gradient` fades from the source's colour to the target's, which is the one\n * that actually reads as flow. `source` and `target` attribute the whole band\n * to one end — useful when the question is \"where did this come from\" rather\n * than \"what became of it\". `solid` gives up colour entirely and lets the\n * node rectangles carry identity.\n */\nexport type SankeyLinkVariant = 'gradient' | 'solid' | 'source' | 'target'\n\n/** Where a node's label sits. */\nexport type SankeyLabelPosition = 'inside' | 'outside'\n\ninterface SankeyChartContextValue {\n data: SankeyData\n config: ChartConfig\n chartId: string\n isLoading: boolean\n selectedNode: string | null\n selectNode: (name: string | null) => void\n}\n\nconst SankeyChartContext = createContext<SankeyChartContextValue | null>(null)\n\nfunction useSankeyChart(): SankeyChartContextValue {\n const context = useContext(SankeyChartContext)\n if (!context) throw new Error('A sankey chart part must be rendered inside <SankeyChart>')\n return context\n}\n\nexport interface SankeyChartProps {\n /** The nodes and the links between them, in Recharts' own shape. */\n data: SankeyData\n /** Node names → their label and paint. */\n config: ChartConfig\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 /**\n * The composed parts — axes, grid, tooltip, legend, and the marks\n * themselves.\n */\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 Sankey element. */\n sankeyProps?: Omit<SankeyProps, 'data'>\n /** How wide each node rectangle is, in pixels. */\n nodeWidth?: number\n /** Vertical gap between nodes in the same column, in pixels. */\n nodePadding?: number\n /** 0 draws straight links, 1 the fullest curve. */\n linkCurvature?: number\n /** Layout passes. More is tidier and slower. */\n iterations?: number\n /** Lets the layout reorder nodes for the fewest crossings. */\n sort?: boolean\n /** How nodes are placed along the flow axis. */\n align?: 'left' | 'justify'\n /** How nodes are distributed within a column. */\n verticalAlign?: 'justify' | 'top'\n /**\n * The node lit on first render, when the chart keeps its own selection.\n * Selecting one dims every flow it does not touch.\n */\n defaultSelectedNode?: string | null\n /**\n * The selected node, driven from outside.\n *\n * Give this and the chart follows it; leave it undefined and the chart keeps\n * its own, starting from `defaultSelectedNode`.\n */\n selectedNode?: string | null\n /** Fires when the selection changes, and with null when it is cleared. */\n onSelectionChange?: (selection: { name: string; value: number } | null) => void\n /**\n * Swaps the marks for an animated skeleton, keeping the measured height so\n * the page does not jump when the data lands.\n */\n isLoading?: boolean\n /**\n * Drops the hidden table view. Only correct when the page prints the data\n * itself.\n */\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 * Where a quantity goes as it moves through stages — the shape for a funnel, a\n * budget, an energy or traffic breakdown.\n *\n * The only chart here whose data is a GRAPH rather than a table, so it takes\n * `{ nodes, links }` instead of rows, and the table view lists the flows rather\n * than the nodes.\n *\n * @example\n * <SankeyChart title=\"Traffic by source and outcome\" config={config} data={data}>\n * <SankeyChart.Node radius={2}>\n * <SankeyChart.NodeLabel position=\"outside\" showValues />\n * </SankeyChart.Node>\n * <SankeyChart.Link variant=\"gradient\" />\n * <SankeyChart.Tooltip />\n * </SankeyChart>\n */\nexport function SankeyChart({\n data,\n config,\n title,\n showTitle,\n description,\n children,\n className,\n sankeyProps,\n nodeWidth = 10,\n nodePadding = 10,\n linkCurvature = 0.5,\n iterations = 32,\n sort = true,\n align = 'justify',\n verticalAlign = 'justify',\n defaultSelectedNode = null,\n selectedNode: controlledNode,\n onSelectionChange,\n isLoading = false,\n hideDataTable = false,\n empty,\n}: SankeyChartProps) {\n const chartId = useId().replace(/:/g, '')\n\n const report = useCallback(\n (name: string | null) => {\n if (!onSelectionChange) return\n onSelectionChange(name === null ? null : { name, value: nodeValue(data, name) })\n },\n [data, onSelectionChange],\n )\n\n const [selectedNode, selectNode] = useChartSelection(\n controlledNode,\n defaultSelectedNode,\n report,\n )\n\n const context = useMemo<SankeyChartContextValue>(\n () => ({ data, config, chartId, isLoading, selectedNode, selectNode }),\n [chartId, config, data, isLoading, selectNode, selectedNode],\n )\n\n // The flows, not the nodes: a link IS the fact a sankey states, and a table\n // of node totals would lose every \"from → to\" the diagram is drawn to show.\n const flows = useMemo(\n () =>\n data.links.map((link) => ({\n from: String(data.nodes[link.source]?.name ?? link.source),\n to: String(data.nodes[link.target]?.name ?? link.target),\n value: link.value,\n })),\n [data],\n )\n\n return (\n <SankeyChartContext.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: flows,\n rowKey: 'from',\n columns: [\n { key: 'to', label: 'To' },\n { key: 'value', label: 'Value' },\n ],\n }\n }\n isEmpty={!isLoading && (data.nodes.length === 0 || data.links.length === 0)}\n empty={empty}\n >\n {isLoading ? (\n <div className=\"relative aspect-video w-full\">\n <svg\n viewBox=\"0 0 500 250\"\n preserveAspectRatio=\"xMidYMid meet\"\n width=\"100%\"\n height=\"100%\"\n aria-hidden\n className=\"absolute inset-0 text-(--ink)\"\n >\n <LoadingSankey />\n </svg>\n </div>\n ) : (\n <ChartContainer config={config}>\n <RechartsSankey\n id={chartId}\n data={data}\n nodeWidth={nodeWidth}\n nodePadding={nodePadding}\n linkCurvature={linkCurvature}\n iterations={iterations}\n sort={sort}\n align={align}\n verticalAlign={verticalAlign}\n {...resolveRenderers(children)}\n {...sankeyProps}\n >\n {children}\n <defs>\n {Object.entries(config).map(([key, series]) => (\n <linearGradient\n key={key}\n id={`${chartId}-node-${cssName(key)}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <ColorStops dataKey={cssName(key)} stops={colorStops(series)} />\n </linearGradient>\n ))}\n </defs>\n </RechartsSankey>\n </ChartContainer>\n )}\n <LoadingIndicator isLoading={isLoading} />\n </ChartFigure>\n </SankeyChartContext.Provider>\n )\n}\n\nexport interface NodeProps {\n radius?: number\n isClickable?: boolean\n /** `<SankeyChart.NodeLabel>`. */\n children?: ReactNode\n}\n\n/** How the node rectangles render. A slot: the root reads it. */\nconst Node: FC<NodeProps> = () => null\n\nexport interface NodeLabelProps {\n position?: SankeyLabelPosition\n /** Prints each node's total flow under its name. */\n showValues?: boolean\n valueFormatter?: (value: number) => string\n}\n\n/** Labels for the nodes. A slot, composed inside `<Node>`. */\nconst NodeLabel: FC<NodeLabelProps> = () => null\n\nexport interface LinkProps {\n variant?: SankeyLinkVariant\n /** Thins each band where it meets a node, so neighbours stay separable. */\n verticalPadding?: number\n}\n\n/** How the flow bands render. A slot: the root reads it. */\nconst Link: FC<LinkProps> = () => null\n\n/** The hover panel. No heading: the node's own name is the row label. */\nfunction Tooltip({ variant, roundness, defaultIndex }: ChartTooltipSlotProps) {\n const { isLoading } = useSankeyChart()\n if (isLoading) return null\n\n return (\n <ChartTooltip\n defaultIndex={defaultIndex}\n content={\n <ChartTooltipContent nameKey=\"name\" hideLabel roundness={roundness} variant={variant} />\n }\n />\n )\n}\n\n/** The decorative plate behind the diagram. */\nfunction Background({ variant = 'dots' }: { variant?: ChartBackgroundVariant }) {\n const { isLoading } = useSankeyChart()\n if (isLoading) return null\n return <ChartBackground variant={variant} />\n}\n\n/** A node's total flow — outgoing, or incoming for a leaf. */\nfunction nodeValue(data: SankeyData, name: string): number {\n const index = data.nodes.findIndex((node) => node.name === name)\n if (index === -1) return 0\n\n const out = data.links\n .filter((link) => link.source === index)\n .reduce((sum, link) => sum + link.value, 0)\n const incoming = data.links\n .filter((link) => link.target === index)\n .reduce((sum, link) => sum + link.value, 0)\n\n return out > 0 ? out : incoming\n}\n\n/** Turns the composed `<Node>` and `<Link>` slots into Recharts renderers. */\nfunction resolveRenderers(children: ReactNode): Pick<SankeyProps, 'node' | 'link'> {\n let node: NodeProps | null = null\n let link: LinkProps | null = null\n\n Children.forEach(children, (child) => {\n if (!isValidElement(child)) return\n if (child.type === Node) node = (child as ReactElement<NodeProps>).props\n if (child.type === Link) link = (child as ReactElement<LinkProps>).props\n })\n\n return {\n node: (props: SankeyNodeProps) => <SankeyNodeShape {...props} nodeSlot={node} />,\n link: (props: SankeyLinkProps) => <SankeyLinkShape {...props} linkSlot={link} />,\n }\n}\n\n/** Reads the `<NodeLabel>` composed inside a `<Node>`, if any. */\nfunction resolveNodeLabel(children: ReactNode): NodeLabelProps | null {\n let label: NodeLabelProps | null = null\n\n Children.forEach(children, (child) => {\n if (isValidElement(child) && child.type === NodeLabel) {\n label = (child as ReactElement<NodeLabelProps>).props\n }\n })\n\n return label\n}\n\n/** Whether a node is the selected one, or one hop from it. */\nfunction isConnected(data: SankeyData, selected: string | null, name: string): boolean {\n if (selected === null || selected === name) return true\n\n const selectedIndex = data.nodes.findIndex((node) => node.name === selected)\n const nodeIndex = data.nodes.findIndex((node) => node.name === name)\n\n return data.links.some(\n (link) =>\n (link.source === selectedIndex && link.target === nodeIndex) ||\n (link.source === nodeIndex && link.target === selectedIndex),\n )\n}\n\n/** One node rectangle, with its label and value. */\nfunction SankeyNodeShape({\n x,\n y,\n width,\n height,\n payload,\n nodeSlot,\n}: SankeyNodeProps & { nodeSlot: NodeProps | null }) {\n const { config, chartId, data, selectedNode, selectNode } = useSankeyChart()\n\n const radius = nodeSlot?.radius ?? 0\n const isClickable = nodeSlot?.isClickable ?? false\n const label = resolveNodeLabel(nodeSlot?.children)\n\n const name = payload.name\n const value = payload.value\n const icon = (payload as { icon?: ReactNode }).icon\n\n const dimmed = isClickable && !isConnected(data, selectedNode, name)\n const painted = name in config\n const text = config[name]?.label ?? name\n\n const format = label?.valueFormatter ?? ((n: number) => n.toLocaleString())\n const showValues = label?.showValues ?? false\n\n const centreX = x + width / 2\n const centreY = showValues ? y + height / 2 - 8 : y + height / 2\n const valueY = y + height / 2 + 8\n const outsideX = x + width + 8\n const outsideY = y + height / 2\n\n return (\n <Layer>\n <rect\n x={x}\n y={y}\n width={width}\n height={height}\n rx={radius}\n ry={radius}\n fill={painted ? `url(#${chartId}-node-${cssName(name)})` : 'currentColor'}\n fillOpacity={dimmed ? 0.15 : 0.9}\n className=\"text-(--ink) transition-opacity duration-(--duration-base)\"\n style={isClickable ? { cursor: 'pointer' } : undefined}\n onClick={() => {\n if (!isClickable) return\n selectNode(selectedNode === name ? null : name)\n }}\n />\n {label?.position === 'inside' && (\n <>\n {/* A scrim inside the node, so the label reads against whatever the\n node's own gradient happens to be under it. */}\n <rect\n x={x + 1}\n y={y + 1}\n width={width - 2}\n height={height - 2}\n rx={Math.max(0, radius - 1)}\n ry={Math.max(0, radius - 1)}\n opacity={dimmed ? 0.3 : 1}\n className=\"fill-(--chart-surface)/60 transition-opacity duration-(--duration-base)\"\n style={{ pointerEvents: 'none' }}\n />\n {icon && (\n <foreignObject\n x={centreX - 8}\n y={centreY - 30}\n width={16}\n height={16}\n opacity={dimmed ? 0.3 : 1}\n className=\"transition-opacity duration-(--duration-base)\"\n style={{ pointerEvents: 'none' }}\n >\n <div className=\"flex items-center justify-center text-(--ink-2)\">{icon}</div>\n </foreignObject>\n )}\n <text\n x={centreX}\n y={icon ? centreY - 4 : centreY}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n className=\"fill-(--ink) text-[10px] font-medium transition-opacity duration-(--duration-base)\"\n opacity={dimmed ? 0.3 : 1}\n style={{ pointerEvents: 'none' }}\n >\n {text}\n </text>\n {showValues && (\n <text\n x={centreX}\n y={valueY}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n className=\"fill-(--ink) font-mono text-xs font-medium tabular-nums transition-opacity duration-(--duration-base)\"\n opacity={dimmed ? 0.3 : 0.6}\n style={{ pointerEvents: 'none' }}\n >\n {format(value)}\n </text>\n )}\n </>\n )}\n {label?.position === 'outside' && (\n <>\n <text\n x={outsideX}\n y={outsideY - (showValues ? 8 : 0)}\n textAnchor=\"start\"\n dominantBaseline=\"middle\"\n className=\"fill-(--ink) text-xs\"\n style={{ pointerEvents: 'none' }}\n >\n {text}\n </text>\n {showValues && (\n <text\n x={outsideX}\n y={outsideY + 8}\n textAnchor=\"start\"\n dominantBaseline=\"middle\"\n opacity={0.5}\n className=\"fill-(--ink) font-mono text-xs tabular-nums\"\n style={{ pointerEvents: 'none' }}\n >\n {format(value)}\n </text>\n )}\n </>\n )}\n </Layer>\n )\n}\n\n/** One flow band. */\nfunction SankeyLinkShape({\n sourceX,\n targetX,\n sourceY,\n targetY,\n sourceControlX,\n targetControlX,\n linkWidth,\n index,\n payload,\n linkSlot,\n}: SankeyLinkProps & { linkSlot: LinkProps | null }) {\n const { config, chartId, selectedNode } = useSankeyChart()\n\n const variant = linkSlot?.variant ?? 'gradient'\n const verticalPadding = linkSlot?.verticalPadding ?? 0\n\n const sourceName = payload.source.name\n const targetName = payload.target.name\n const lit =\n selectedNode === null || selectedNode === sourceName || selectedNode === targetName\n\n const half = Math.max(1, linkWidth - verticalPadding) / 2\n\n // Two mirrored cubics closed into a ribbon: the band has to be a filled shape\n // rather than a thick stroke, because its width changes along its length.\n const band = `M${sourceX},${sourceY - half}\n C${sourceControlX},${sourceY - half} ${targetControlX},${targetY - half} ${targetX},${targetY - half}\n L${targetX},${targetY + half}\n C${targetControlX},${targetY + half} ${sourceControlX},${sourceY + half} ${sourceX},${sourceY + half}\n Z`\n\n const sourceColor = sourceName in config ? `var(--color-${cssName(sourceName)}-0)` : 'currentColor'\n const targetColor = targetName in config ? `var(--color-${cssName(targetName)}-0)` : 'currentColor'\n\n return (\n <Layer className=\"text-(--ink)\">\n <defs>\n {variant === 'gradient' && (\n <linearGradient id={`${chartId}-link-${index}`} x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stopColor={sourceColor} stopOpacity={0.2} />\n <stop offset=\"50%\" stopColor={sourceColor} stopOpacity={0.5} />\n <stop offset=\"100%\" stopColor={targetColor} stopOpacity={0.2} />\n </linearGradient>\n )}\n {/* The edge that traces a band connected to the selection. It fades out\n at both ends so it reads as a highlight rather than as an outline. */}\n <linearGradient id={`${chartId}-link-edge-${index}`} x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stopColor=\"var(--accent)\" stopOpacity={0} />\n <stop offset=\"15%\" stopColor=\"var(--accent)\" stopOpacity={0.8} />\n <stop offset=\"50%\" stopColor=\"var(--accent)\" stopOpacity={1} />\n <stop offset=\"85%\" stopColor=\"var(--accent)\" stopOpacity={0.8} />\n <stop offset=\"100%\" stopColor=\"var(--accent)\" stopOpacity={0} />\n </linearGradient>\n </defs>\n <path\n d={band}\n fill={linkFill(variant, chartId, index, config, sourceName, targetName)}\n // Read from the ground rather than fixed: a band at 0.4 of ink over\n // paper reads; the same 0.4 of paper-white over near-black does not.\n fillOpacity={lit ? 'calc(var(--chart-fill) * 2.6)' : 'calc(var(--chart-fill) * 0.7)'}\n stroke={selectedNode !== null && lit ? `url(#${chartId}-link-edge-${index})` : 'none'}\n strokeWidth={1}\n className=\"transition-opacity duration-(--duration-base)\"\n />\n </Layer>\n )\n}\n\n/** The paint reference for a band, given its variant. */\nfunction linkFill(\n variant: SankeyLinkVariant,\n chartId: string,\n index: number,\n config: ChartConfig,\n sourceName: string,\n targetName: string,\n): string {\n switch (variant) {\n case 'gradient':\n return `url(#${chartId}-link-${index})`\n case 'source':\n return sourceName in config ? `url(#${chartId}-node-${cssName(sourceName)})` : 'currentColor'\n case 'target':\n return targetName in config ? `url(#${chartId}-node-${cssName(targetName)})` : 'currentColor'\n default:\n return 'currentColor'\n }\n}\n\n/** The skeleton's node columns. Fixed, so the shape reads as a sankey at once. */\nconst SKELETON_NODES = [\n { x: 30, y: 25, width: 12, height: 65, delay: 0 },\n { x: 30, y: 110, width: 12, height: 50, delay: 0.3 },\n { x: 30, y: 180, width: 12, height: 45, delay: 0.15 },\n { x: 244, y: 20, width: 12, height: 55, delay: 0.45 },\n { x: 244, y: 95, width: 12, height: 75, delay: 0.6 },\n { x: 244, y: 190, width: 12, height: 40, delay: 0.25 },\n { x: 458, y: 35, width: 12, height: 80, delay: 0.5 },\n { x: 458, y: 135, width: 12, height: 90, delay: 0.1 },\n]\n\nconst SKELETON_LINKS = [\n { from: 0, to: 3, width: 26, delay: 0.2 },\n { from: 0, to: 4, width: 18, delay: 0.7 },\n { from: 1, to: 4, width: 24, delay: 0.4 },\n { from: 1, to: 5, width: 12, delay: 0.9 },\n { from: 2, to: 4, width: 16, delay: 0.1 },\n { from: 2, to: 5, width: 14, delay: 0.55 },\n { from: 3, to: 6, width: 22, delay: 0.35 },\n { from: 3, to: 7, width: 18, delay: 0.8 },\n { from: 4, to: 6, width: 28, delay: 0.05 },\n { from: 4, to: 7, width: 32, delay: 0.65 },\n { from: 5, to: 7, width: 16, delay: 0.45 },\n]\n\n/** A bezier from one skeleton node's right edge to another's left. */\nfunction skeletonPath(fromIndex: number, toIndex: number): string {\n const from = SKELETON_NODES[fromIndex]!\n const to = SKELETON_NODES[toIndex]!\n const startX = from.x + from.width\n const startY = from.y + from.height / 2\n const endX = to.x\n const endY = to.y + to.height / 2\n return `M${startX},${startY} C${startX + (endX - startX) * 0.4},${startY} ${startX + (endX - startX) * 0.6},${endY} ${endX},${endY}`\n}\n\n/** The skeleton diagram: a fixed graph whose parts pulse out of phase. */\nfunction LoadingSankey() {\n const reduceMotion = useReducedMotion()\n\n if (reduceMotion) {\n return (\n <>\n {SKELETON_LINKS.map((link) => (\n <path\n key={`${link.from}-${link.to}`}\n d={skeletonPath(link.from, link.to)}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={link.width}\n opacity={0.09}\n />\n ))}\n {SKELETON_NODES.map((node) => (\n <rect\n key={`${node.x}-${node.y}`}\n x={node.x}\n y={node.y}\n width={node.width}\n height={node.height}\n rx={2}\n fill=\"currentColor\"\n opacity={0.25}\n />\n ))}\n </>\n )\n }\n\n return (\n <>\n {SKELETON_LINKS.map((link, index) => (\n <motion.path\n key={`${link.from}-${link.to}`}\n d={skeletonPath(link.from, link.to)}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={link.width}\n initial={{ opacity: 0.04 }}\n animate={{ opacity: [0.04, 0.14, 0.04] }}\n transition={{\n duration: LOADING_PULSE * (0.8 + (index % 3) * 0.2),\n delay: link.delay,\n repeat: Infinity,\n ease: 'easeInOut',\n }}\n />\n ))}\n {SKELETON_NODES.map((node, index) => (\n <motion.rect\n key={`${node.x}-${node.y}`}\n x={node.x}\n y={node.y}\n width={node.width}\n height={node.height}\n rx={2}\n fill=\"currentColor\"\n initial={{ opacity: 0.15 }}\n animate={{ opacity: [0.15, 0.4, 0.15] }}\n transition={{\n duration: LOADING_PULSE * (0.9 + (index % 4) * 0.1),\n delay: node.delay,\n repeat: Infinity,\n ease: 'easeInOut',\n }}\n />\n ))}\n </>\n )\n}\n\nSankeyChart.Node = Node\nSankeyChart.NodeLabel = NodeLabel\nSankeyChart.Link = Link\nSankeyChart.Tooltip = Tooltip\nSankeyChart.Background = Background\n\nexport default SankeyChart\n"],"mappings":"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","names":[]}
|
|
@@ -3,9 +3,9 @@ import * as react from 'react';
|
|
|
3
3
|
import { ReactNode, ComponentProps } from 'react';
|
|
4
4
|
import { ScatterChart as ScatterChart$1, Scatter, XAxis, YAxis, ZAxis, CartesianGrid } from 'recharts';
|
|
5
5
|
import { ChartConfig } from '../lib/chart.js';
|
|
6
|
+
import { ChartEmptyProps } from '../lib/empty.js';
|
|
6
7
|
import { ChartBackgroundVariant } from '../lib/background.js';
|
|
7
8
|
import { ReferenceLine, ReferenceBand, Annotation } from '../lib/annotations.js';
|
|
8
|
-
import '../lib/empty.js';
|
|
9
9
|
import '../lib/values.js';
|
|
10
10
|
import '../lib/sonify-control.js';
|
|
11
11
|
import '../lib/sonify.js';
|
|
@@ -58,8 +58,15 @@ interface ScatterChartProps {
|
|
|
58
58
|
className?: string;
|
|
59
59
|
/** Escape hatch onto the raw Recharts chart element. */
|
|
60
60
|
chartProps?: ComponentProps<typeof ScatterChart$1>;
|
|
61
|
-
/** The series lit on first render
|
|
61
|
+
/** The series lit on first render, when the chart keeps its own selection. */
|
|
62
62
|
defaultSelectedDataKey?: string | null;
|
|
63
|
+
/**
|
|
64
|
+
* The selected series, driven from outside.
|
|
65
|
+
*
|
|
66
|
+
* Give this and the chart follows it; leave it undefined and the chart keeps
|
|
67
|
+
* its own, starting from `defaultSelectedDataKey`.
|
|
68
|
+
*/
|
|
69
|
+
selectedDataKey?: string | null;
|
|
63
70
|
/** Fires when the selection changes, and with null when it is cleared. */
|
|
64
71
|
onSelectionChange?: (selectedDataKey: string | null) => void;
|
|
65
72
|
/**
|
|
@@ -68,6 +75,20 @@ interface ScatterChartProps {
|
|
|
68
75
|
* other chart here the table cannot be inferred — it is declared.
|
|
69
76
|
*/
|
|
70
77
|
table?: ScatterTable | false;
|
|
78
|
+
/**
|
|
79
|
+
* Swaps the marks for an animated skeleton, keeping the measured height so
|
|
80
|
+
* the page does not jump when the data lands.
|
|
81
|
+
*/
|
|
82
|
+
isLoading?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* What the chart shows when it has nothing to draw. `false` keeps the empty
|
|
85
|
+
* plot, for a chart whose emptiness is itself the reading.
|
|
86
|
+
*
|
|
87
|
+
* Read from the declared `table` rows, for the same reason the table is
|
|
88
|
+
* declared: the observations live on each `<Scatter>`, and the root cannot
|
|
89
|
+
* see them.
|
|
90
|
+
*/
|
|
91
|
+
empty?: ChartEmptyProps | false;
|
|
71
92
|
}
|
|
72
93
|
/**
|
|
73
94
|
* Two measures against each other, one mark per observation — the shape for
|
|
@@ -89,7 +110,7 @@ interface ScatterChartProps {
|
|
|
89
110
|
* <ScatterChart.Scatter dataKey="desktop" data={desktop} />
|
|
90
111
|
* </ScatterChart>
|
|
91
112
|
*/
|
|
92
|
-
declare function ScatterChart({ config, title, showTitle, description, children, className, chartProps, defaultSelectedDataKey, onSelectionChange, table, }: ScatterChartProps): react.JSX.Element;
|
|
113
|
+
declare function ScatterChart({ config, title, showTitle, description, children, className, chartProps, defaultSelectedDataKey, selectedDataKey: controlledDataKey, onSelectionChange, table, isLoading, empty, }: ScatterChartProps): react.JSX.Element;
|
|
93
114
|
declare namespace ScatterChart {
|
|
94
115
|
var Scatter: ({ dataKey, data, shape, variant, size, glowing, isClickable, scatterProps, }: ScatterProps) => react.JSX.Element;
|
|
95
116
|
var XAxis: ({ type, tickLine, axisLine, tickMargin, tickFormatter, label, ...rest }: Omit<ComponentProps<typeof XAxis>, "label"> & {
|
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import {
|
|
4
4
|
createContext,
|
|
5
|
-
useCallback,
|
|
6
5
|
useContext,
|
|
7
6
|
useId,
|
|
8
|
-
useMemo
|
|
9
|
-
useState
|
|
7
|
+
useMemo
|
|
10
8
|
} from "react";
|
|
11
9
|
import {
|
|
12
10
|
CartesianGrid,
|
|
@@ -18,9 +16,12 @@ import {
|
|
|
18
16
|
} from "recharts";
|
|
19
17
|
import { ChartContainer } from "../lib/chart.js";
|
|
20
18
|
import { ChartFigure } from "../lib/figure.js";
|
|
19
|
+
import {} from "../lib/empty.js";
|
|
20
|
+
import { useChartSelection } from "../lib/selection.js";
|
|
21
21
|
import { ChartBackground } from "../lib/background.js";
|
|
22
22
|
import { ChartLegend, ChartLegendContent } from "../lib/legend.js";
|
|
23
23
|
import { ChartTooltip, ChartTooltipContent } from "../lib/tooltip.js";
|
|
24
|
+
import { LoadingIndicator } from "../lib/loading.js";
|
|
24
25
|
import { GlowFilter, SeriesGradient } from "../lib/paint.js";
|
|
25
26
|
import { Annotation, ReferenceBand, ReferenceLine } from "../lib/annotations.js";
|
|
26
27
|
import { axisLabel } from "../lib/axis.js";
|
|
@@ -41,23 +42,23 @@ function ScatterChart({
|
|
|
41
42
|
className,
|
|
42
43
|
chartProps,
|
|
43
44
|
defaultSelectedDataKey = null,
|
|
45
|
+
selectedDataKey: controlledDataKey,
|
|
44
46
|
onSelectionChange,
|
|
45
|
-
table
|
|
47
|
+
table,
|
|
48
|
+
isLoading = false,
|
|
49
|
+
empty
|
|
46
50
|
}) {
|
|
47
51
|
const chartId = useId().replace(/:/g, "");
|
|
48
|
-
const [selectedDataKey,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
onSelectionChange?.(next);
|
|
53
|
-
},
|
|
54
|
-
[onSelectionChange]
|
|
52
|
+
const [selectedDataKey, selectDataKey] = useChartSelection(
|
|
53
|
+
controlledDataKey,
|
|
54
|
+
defaultSelectedDataKey,
|
|
55
|
+
onSelectionChange
|
|
55
56
|
);
|
|
56
57
|
const context = useMemo(
|
|
57
|
-
() => ({ config, isLoading
|
|
58
|
-
[config, selectDataKey, selectedDataKey]
|
|
58
|
+
() => ({ config, isLoading, selectedDataKey, selectDataKey }),
|
|
59
|
+
[config, isLoading, selectDataKey, selectedDataKey]
|
|
59
60
|
);
|
|
60
|
-
return /* @__PURE__ */ jsx(ScatterChartContext.Provider, { value: context, children: /* @__PURE__ */
|
|
61
|
+
return /* @__PURE__ */ jsx(ScatterChartContext.Provider, { value: context, children: /* @__PURE__ */ jsxs(
|
|
61
62
|
ChartFigure,
|
|
62
63
|
{
|
|
63
64
|
title,
|
|
@@ -65,7 +66,12 @@ function ScatterChart({
|
|
|
65
66
|
description,
|
|
66
67
|
className,
|
|
67
68
|
table,
|
|
68
|
-
|
|
69
|
+
isEmpty: !isLoading && !!table && table.rows.length === 0,
|
|
70
|
+
empty,
|
|
71
|
+
children: [
|
|
72
|
+
/* @__PURE__ */ jsx(ChartContainer, { config, children: /* @__PURE__ */ jsx(RechartsScatterChart, { id: chartId, accessibilityLayer: true, margin: CHART_MARGIN, ...chartProps, children }) }),
|
|
73
|
+
/* @__PURE__ */ jsx(LoadingIndicator, { isLoading })
|
|
74
|
+
]
|
|
69
75
|
}
|
|
70
76
|
) });
|
|
71
77
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/charts/ScatterChart/ScatterChart.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n useCallback,\n useContext,\n useId,\n useMemo,\n useState,\n type ComponentProps,\n type ReactNode,\n} from 'react'\nimport {\n CartesianGrid,\n Scatter as RechartsScatter,\n ScatterChart as RechartsScatterChart,\n XAxis as RechartsXAxis,\n YAxis as RechartsYAxis,\n ZAxis as RechartsZAxis,\n} from 'recharts'\nimport { ChartContainer, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartLegend, ChartLegendContent } from '../lib/legend'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport { GlowFilter, SeriesGradient } from '../lib/paint'\nimport { Annotation, ReferenceBand, ReferenceLine } from '../lib/annotations'\nimport { axisLabel } from '../lib/axis'\nimport { defaultTick } from '../lib/format'\nimport type { ChartLegendSlotProps, ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\nconst CHART_MARGIN = { top: 12, right: 16, bottom: 0, left: 8 }\n\n/**\n * The mark each point is drawn as.\n *\n * Shape is doing what hue does elsewhere: with the monochrome ramp, two\n * overlapping clouds separate far better by circle-versus-cross than by two\n * steps of grey, and shape survives overprinting where a lightness step does\n * not.\n */\nexport type ScatterShape = 'circle' | 'square' | 'triangle' | 'diamond' | 'cross' | 'ring'\n\n/** How a point is filled — solid, hollow, or the series gradient. */\nexport type ScatterVariant = 'solid' | 'outline' | 'gradient'\n\ninterface ScatterChartContextValue {\n config: ChartConfig\n isLoading: boolean\n selectedDataKey: string | null\n selectDataKey: (dataKey: string | null) => void\n}\n\nconst ScatterChartContext = createContext<ScatterChartContextValue | null>(null)\n\nfunction useScatterChart(): ScatterChartContextValue {\n const context = useContext(ScatterChartContext)\n if (!context) throw new Error('A scatter chart part must be rendered inside <ScatterChart>')\n return context\n}\n\n/** The rows a scatter chart reads its hidden table view from. */\nexport interface ScatterTable {\n rows: Record<string, unknown>[]\n /** The field naming each row. */\n rowKey?: string\n columns: { key: string; label: ReactNode }[]\n}\n\nexport interface ScatterChartProps {\n /** Series keys → their label and paint. Declaration order is ramp order. */\n config: ChartConfig\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, legend, and the marks themselves. */\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 RechartsScatterChart>\n /** The series lit on first render. Selection dims every other series. */\n defaultSelectedDataKey?: string | null\n /** Fires when the selection changes, and with null when it is cleared. */\n onSelectionChange?: (selectedDataKey: string | null) => void\n /**\n * The rows behind the hidden table view, with the fields to print. Scatter\n * data lives on each `<Scatter>` rather than on the root, so unlike every\n * other chart here the table cannot be inferred — it is declared.\n */\n table?: ScatterTable | false\n}\n\n/**\n * Two measures against each other, one mark per observation — the shape for\n * \"is there a relationship here\".\n *\n * The only chart in the set whose x axis is a NUMBER rather than a category,\n * which is the whole point: a scatter answers correlation, clustering and\n * outliers, and none of those questions survive being bucketed into a bar.\n *\n * Past three series, shape stops separating them and the answer is small\n * multiples — one chart per series, same axes — rather than a fourth mark.\n *\n * @example\n * <ScatterChart title=\"Load time against bundle size\" config={config}>\n * <ScatterChart.Grid />\n * <ScatterChart.XAxis dataKey=\"kb\" name=\"Bundle\" unit=\" kB\" />\n * <ScatterChart.YAxis dataKey=\"ms\" name=\"Load\" unit=\" ms\" />\n * <ScatterChart.Tooltip />\n * <ScatterChart.Scatter dataKey=\"desktop\" data={desktop} />\n * </ScatterChart>\n */\nexport function ScatterChart({\n config,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n defaultSelectedDataKey = null,\n onSelectionChange,\n table,\n}: ScatterChartProps) {\n const chartId = useId().replace(/:/g, '')\n const [selectedDataKey, setSelectedDataKey] = useState<string | null>(defaultSelectedDataKey)\n\n const selectDataKey = useCallback(\n (next: string | null) => {\n setSelectedDataKey(next)\n onSelectionChange?.(next)\n },\n [onSelectionChange],\n )\n\n const context = useMemo<ScatterChartContextValue>(\n () => ({ config, isLoading: false, selectedDataKey, selectDataKey }),\n [config, selectDataKey, selectedDataKey],\n )\n\n return (\n <ScatterChartContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={table}\n >\n <ChartContainer config={config}>\n <RechartsScatterChart id={chartId} accessibilityLayer margin={CHART_MARGIN} {...chartProps}>\n {children}\n </RechartsScatterChart>\n </ChartContainer>\n </ChartFigure>\n </ScatterChartContext.Provider>\n )\n}\n\n/** The SVG path for one mark, centred on the origin at radius `r`. */\nfunction shapePath(shape: ScatterShape, r: number): string {\n switch (shape) {\n case 'square':\n return `M${-r},${-r}h${r * 2}v${r * 2}h${-r * 2}z`\n case 'triangle':\n return `M0,${-r}L${r},${r}L${-r},${r}z`\n case 'diamond':\n return `M0,${-r}L${r},0L0,${r}L${-r},0z`\n case 'cross': {\n const a = r * 0.34\n return `M${-a},${-r}h${a * 2}v${r - a}h${r - a}v${a * 2}h${-(r - a)}v${r - a}h${-a * 2}v${-(r - a)}h${-(r - a)}v${-a * 2}h${r - a}z`\n }\n default:\n return `M0,${-r}A${r},${r} 0 1,1 0,${r}A${r},${r} 0 1,1 0,${-r}z`\n }\n}\n\nexport interface ScatterProps {\n /** The series key. Must exist on the config. */\n dataKey: string\n /** This series' observations. */\n data: Record<string, unknown>[]\n shape?: ScatterShape\n variant?: ScatterVariant\n /** Mark radius in pixels. Ignored when a `<ScatterChart.ZAxis>` sizes them. */\n size?: number\n /** A halo behind the cloud — for the one series that is the point. */\n glowing?: boolean\n /** Lets a click on this series select it, dimming the rest. */\n isClickable?: boolean\n scatterProps?: Omit<ComponentProps<typeof RechartsScatter>, 'data' | 'dataKey'>\n}\n\n/** One cloud of observations. */\nfunction Scatter({\n dataKey,\n data,\n shape = 'circle',\n variant = 'solid',\n size = 5,\n glowing = false,\n isClickable = false,\n scatterProps,\n}: ScatterProps) {\n const { config, selectedDataKey, selectDataKey } = useScatterChart()\n const id = useId().replace(/:/g, '')\n\n const isSelected = selectedDataKey === dataKey\n const dimmed = selectedDataKey !== null && !isSelected\n const paint = `url(#${id}-colors-${dataKey})`\n\n return (\n <>\n <RechartsScatter\n name={dataKey}\n dataKey={dataKey}\n data={data}\n isAnimationActive={false}\n style={isClickable ? { cursor: 'pointer' } : undefined}\n onClick={() => {\n if (!isClickable) return\n selectDataKey(isSelected ? null : dataKey)\n }}\n shape={(props: { cx?: number; cy?: number; payload?: unknown; r?: number }) => {\n const { cx, cy } = props\n if (cx === undefined || cy === undefined) return <g />\n // `r` arrives only when a ZAxis is sizing the marks.\n const radius = typeof props.r === 'number' && props.r > 0 ? props.r : size\n const hollow = variant === 'outline' || shape === 'ring'\n\n return (\n <g\n transform={`translate(${cx}, ${cy})`}\n opacity={dimmed ? 0.2 : 1}\n filter={glowing ? `url(#${id}-glow-${dataKey})` : undefined}\n className=\"transition-opacity duration-(--duration-base)\"\n >\n {shape === 'ring' ? (\n <circle\n r={radius}\n fill=\"none\"\n stroke={paint}\n strokeWidth={Math.max(1.2, radius * 0.32)}\n />\n ) : (\n <path\n d={shapePath(shape, radius)}\n fill={hollow ? 'none' : paint}\n stroke={hollow ? paint : 'var(--chart-surface)'}\n // A surface-coloured ring on a solid mark is the 2px spacer\n // that keeps two overlapping observations countable.\n strokeWidth={hollow ? 1.4 : 1}\n />\n )}\n </g>\n )\n }}\n {...scatterProps}\n />\n <defs>\n <SeriesGradient id={id} dataKey={dataKey} config={config} />\n {glowing && <GlowFilter id={id} dataKey={dataKey} spread={5} intensity={0.8} />}\n </defs>\n </>\n )\n}\n\n/** The horizontal measure. Numeric by default — a scatter has no categories. */\nfunction XAxis({\n type = 'number',\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n tickFormatter = defaultTick,\n label,\n ...rest\n}: Omit<ComponentProps<typeof RechartsXAxis>, 'label'> & { label?: string }) {\n return (\n <RechartsXAxis\n type={type}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n tickFormatter={tickFormatter}\n {...rest}\n >\n {axisLabel(label, 'x')}\n </RechartsXAxis>\n )\n}\n\n/** The vertical measure. */\nfunction YAxis({\n type = 'number',\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n width = 'auto',\n tickFormatter = defaultTick,\n label,\n ...rest\n}: Omit<ComponentProps<typeof RechartsYAxis>, 'label'> & { label?: string }) {\n return (\n <RechartsYAxis\n type={type}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n width={width}\n tickFormatter={tickFormatter}\n {...rest}\n >\n {axisLabel(label, 'y')}\n </RechartsYAxis>\n )\n}\n\n/**\n * A third measure, mapped to each mark's AREA rather than its radius.\n *\n * Radius is the wrong encoding and the common mistake: doubling a radius\n * quadruples the ink, so a value twice as large reads as four times as large.\n * `range` is in area units, which is what Recharts scales — so the default\n * below is a ×4 area span, not a ×4 radius.\n */\nfunction ZAxis({\n range = [40, 400],\n ...rest\n}: ComponentProps<typeof RechartsZAxis>) {\n return <RechartsZAxis range={range} {...rest} />\n}\n\n/** Rules on both axes — a scatter is read in two directions. */\nfunction Grid({\n strokeDasharray = '3 3',\n ...rest\n}: ComponentProps<typeof CartesianGrid>) {\n return <CartesianGrid strokeDasharray={strokeDasharray} {...rest} />\n}\n\n/**\n * The hover panel, with a crosshair on both axes.\n *\n * `cursor` is a pair of rules rather than a band: a scatter point is located by\n * two coordinates, and a single vertical cursor answers only half of that.\n */\nfunction Tooltip({ variant, roundness, defaultIndex, cursor = true }: ChartTooltipSlotProps) {\n const { selectedDataKey } = useScatterChart()\n\n return (\n <ChartTooltip\n defaultIndex={defaultIndex}\n cursor={cursor ? { strokeDasharray: '3 3' } : false}\n content={\n <ChartTooltipContent selected={selectedDataKey} roundness={roundness} variant={variant} />\n }\n />\n )\n}\n\n/** The key. Required above one series. */\nfunction Legend({\n variant,\n align = 'right',\n verticalAlign = 'top',\n isClickable = false,\n}: ChartLegendSlotProps) {\n const { selectedDataKey, selectDataKey } = useScatterChart()\n\n return (\n <ChartLegend\n verticalAlign={verticalAlign}\n align={align}\n content={\n <ChartLegendContent\n selected={selectedDataKey}\n onSelectChange={selectDataKey}\n isClickable={isClickable}\n variant={variant}\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\nScatterChart.Scatter = Scatter\nScatterChart.XAxis = XAxis\nScatterChart.YAxis = YAxis\nScatterChart.ZAxis = ZAxis\nScatterChart.Grid = Grid\nScatterChart.Tooltip = Tooltip\nScatterChart.Legend = Legend\nScatterChart.ReferenceLine = ReferenceLine\nScatterChart.ReferenceBand = ReferenceBand\nScatterChart.Annotation = Annotation\nScatterChart.Background = Background\n\nexport default ScatterChart\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/charts/ScatterChart/ScatterChart.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 CartesianGrid,\n Scatter as RechartsScatter,\n ScatterChart as RechartsScatterChart,\n XAxis as RechartsXAxis,\n YAxis as RechartsYAxis,\n ZAxis as RechartsZAxis,\n} from 'recharts'\nimport { ChartContainer, type ChartConfig } from '../lib/chart'\nimport { ChartFigure } from '../lib/figure'\nimport { type ChartEmptyProps } from '../lib/empty'\nimport { useChartSelection } from '../lib/selection'\nimport { ChartBackground, type ChartBackgroundVariant } from '../lib/background'\nimport { ChartLegend, ChartLegendContent } from '../lib/legend'\nimport { ChartTooltip, ChartTooltipContent } from '../lib/tooltip'\nimport { LoadingIndicator } from '../lib/loading'\nimport { GlowFilter, SeriesGradient } from '../lib/paint'\nimport { Annotation, ReferenceBand, ReferenceLine } from '../lib/annotations'\nimport { axisLabel } from '../lib/axis'\nimport { defaultTick } from '../lib/format'\nimport type { ChartLegendSlotProps, ChartTooltipSlotProps } from '../AreaChart/AreaChart'\n\nconst CHART_MARGIN = { top: 12, right: 16, bottom: 0, left: 8 }\n\n/**\n * The mark each point is drawn as.\n *\n * Shape is doing what hue does elsewhere: with the monochrome ramp, two\n * overlapping clouds separate far better by circle-versus-cross than by two\n * steps of grey, and shape survives overprinting where a lightness step does\n * not.\n */\nexport type ScatterShape = 'circle' | 'square' | 'triangle' | 'diamond' | 'cross' | 'ring'\n\n/** How a point is filled — solid, hollow, or the series gradient. */\nexport type ScatterVariant = 'solid' | 'outline' | 'gradient'\n\ninterface ScatterChartContextValue {\n config: ChartConfig\n isLoading: boolean\n selectedDataKey: string | null\n selectDataKey: (dataKey: string | null) => void\n}\n\nconst ScatterChartContext = createContext<ScatterChartContextValue | null>(null)\n\nfunction useScatterChart(): ScatterChartContextValue {\n const context = useContext(ScatterChartContext)\n if (!context) throw new Error('A scatter chart part must be rendered inside <ScatterChart>')\n return context\n}\n\n/** The rows a scatter chart reads its hidden table view from. */\nexport interface ScatterTable {\n rows: Record<string, unknown>[]\n /** The field naming each row. */\n rowKey?: string\n columns: { key: string; label: ReactNode }[]\n}\n\nexport interface ScatterChartProps {\n /** Series keys → their label and paint. Declaration order is ramp order. */\n config: ChartConfig\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, legend, and the marks themselves. */\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 RechartsScatterChart>\n /** The series lit on first render, when the chart keeps its own selection. */\n defaultSelectedDataKey?: string | null\n /**\n * The selected series, driven from outside.\n *\n * Give this and the chart follows it; leave it undefined and the chart keeps\n * its own, starting from `defaultSelectedDataKey`.\n */\n selectedDataKey?: string | null\n /** Fires when the selection changes, and with null when it is cleared. */\n onSelectionChange?: (selectedDataKey: string | null) => void\n /**\n * The rows behind the hidden table view, with the fields to print. Scatter\n * data lives on each `<Scatter>` rather than on the root, so unlike every\n * other chart here the table cannot be inferred — it is declared.\n */\n table?: ScatterTable | false\n /**\n * Swaps the marks for an animated skeleton, keeping the measured height so\n * the page does not jump when the data lands.\n */\n isLoading?: 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 * Read from the declared `table` rows, for the same reason the table is\n * declared: the observations live on each `<Scatter>`, and the root cannot\n * see them.\n */\n empty?: ChartEmptyProps | false\n}\n\n/**\n * Two measures against each other, one mark per observation — the shape for\n * \"is there a relationship here\".\n *\n * The only chart in the set whose x axis is a NUMBER rather than a category,\n * which is the whole point: a scatter answers correlation, clustering and\n * outliers, and none of those questions survive being bucketed into a bar.\n *\n * Past three series, shape stops separating them and the answer is small\n * multiples — one chart per series, same axes — rather than a fourth mark.\n *\n * @example\n * <ScatterChart title=\"Load time against bundle size\" config={config}>\n * <ScatterChart.Grid />\n * <ScatterChart.XAxis dataKey=\"kb\" name=\"Bundle\" unit=\" kB\" />\n * <ScatterChart.YAxis dataKey=\"ms\" name=\"Load\" unit=\" ms\" />\n * <ScatterChart.Tooltip />\n * <ScatterChart.Scatter dataKey=\"desktop\" data={desktop} />\n * </ScatterChart>\n */\nexport function ScatterChart({\n config,\n title,\n showTitle,\n description,\n children,\n className,\n chartProps,\n defaultSelectedDataKey = null,\n selectedDataKey: controlledDataKey,\n onSelectionChange,\n table,\n isLoading = false,\n empty,\n}: ScatterChartProps) {\n const chartId = useId().replace(/:/g, '')\n const [selectedDataKey, selectDataKey] = useChartSelection(\n controlledDataKey,\n defaultSelectedDataKey,\n onSelectionChange,\n )\n\n const context = useMemo<ScatterChartContextValue>(\n () => ({ config, isLoading, selectedDataKey, selectDataKey }),\n [config, isLoading, selectDataKey, selectedDataKey],\n )\n\n return (\n <ScatterChartContext.Provider value={context}>\n <ChartFigure\n title={title}\n showTitle={showTitle}\n description={description}\n className={className}\n table={table}\n // An UNDECLARED table is not an empty one. The observations live on\n // each `<Scatter>`, so a chart that never declared its rows is a chart\n // this cannot speak for — swapping its plot for \"no data\" would be a\n // worse lie than the one being fixed.\n isEmpty={!isLoading && !!table && table.rows.length === 0}\n empty={empty}\n >\n <ChartContainer config={config}>\n <RechartsScatterChart id={chartId} accessibilityLayer margin={CHART_MARGIN} {...chartProps}>\n {children}\n </RechartsScatterChart>\n </ChartContainer>\n <LoadingIndicator isLoading={isLoading} />\n </ChartFigure>\n </ScatterChartContext.Provider>\n )\n}\n\n/** The SVG path for one mark, centred on the origin at radius `r`. */\nfunction shapePath(shape: ScatterShape, r: number): string {\n switch (shape) {\n case 'square':\n return `M${-r},${-r}h${r * 2}v${r * 2}h${-r * 2}z`\n case 'triangle':\n return `M0,${-r}L${r},${r}L${-r},${r}z`\n case 'diamond':\n return `M0,${-r}L${r},0L0,${r}L${-r},0z`\n case 'cross': {\n const a = r * 0.34\n return `M${-a},${-r}h${a * 2}v${r - a}h${r - a}v${a * 2}h${-(r - a)}v${r - a}h${-a * 2}v${-(r - a)}h${-(r - a)}v${-a * 2}h${r - a}z`\n }\n default:\n return `M0,${-r}A${r},${r} 0 1,1 0,${r}A${r},${r} 0 1,1 0,${-r}z`\n }\n}\n\nexport interface ScatterProps {\n /** The series key. Must exist on the config. */\n dataKey: string\n /** This series' observations. */\n data: Record<string, unknown>[]\n shape?: ScatterShape\n variant?: ScatterVariant\n /** Mark radius in pixels. Ignored when a `<ScatterChart.ZAxis>` sizes them. */\n size?: number\n /** A halo behind the cloud — for the one series that is the point. */\n glowing?: boolean\n /** Lets a click on this series select it, dimming the rest. */\n isClickable?: boolean\n scatterProps?: Omit<ComponentProps<typeof RechartsScatter>, 'data' | 'dataKey'>\n}\n\n/** One cloud of observations. */\nfunction Scatter({\n dataKey,\n data,\n shape = 'circle',\n variant = 'solid',\n size = 5,\n glowing = false,\n isClickable = false,\n scatterProps,\n}: ScatterProps) {\n const { config, selectedDataKey, selectDataKey } = useScatterChart()\n const id = useId().replace(/:/g, '')\n\n const isSelected = selectedDataKey === dataKey\n const dimmed = selectedDataKey !== null && !isSelected\n const paint = `url(#${id}-colors-${dataKey})`\n\n return (\n <>\n <RechartsScatter\n name={dataKey}\n dataKey={dataKey}\n data={data}\n isAnimationActive={false}\n style={isClickable ? { cursor: 'pointer' } : undefined}\n onClick={() => {\n if (!isClickable) return\n selectDataKey(isSelected ? null : dataKey)\n }}\n shape={(props: { cx?: number; cy?: number; payload?: unknown; r?: number }) => {\n const { cx, cy } = props\n if (cx === undefined || cy === undefined) return <g />\n // `r` arrives only when a ZAxis is sizing the marks.\n const radius = typeof props.r === 'number' && props.r > 0 ? props.r : size\n const hollow = variant === 'outline' || shape === 'ring'\n\n return (\n <g\n transform={`translate(${cx}, ${cy})`}\n opacity={dimmed ? 0.2 : 1}\n filter={glowing ? `url(#${id}-glow-${dataKey})` : undefined}\n className=\"transition-opacity duration-(--duration-base)\"\n >\n {shape === 'ring' ? (\n <circle\n r={radius}\n fill=\"none\"\n stroke={paint}\n strokeWidth={Math.max(1.2, radius * 0.32)}\n />\n ) : (\n <path\n d={shapePath(shape, radius)}\n fill={hollow ? 'none' : paint}\n stroke={hollow ? paint : 'var(--chart-surface)'}\n // A surface-coloured ring on a solid mark is the 2px spacer\n // that keeps two overlapping observations countable.\n strokeWidth={hollow ? 1.4 : 1}\n />\n )}\n </g>\n )\n }}\n {...scatterProps}\n />\n <defs>\n <SeriesGradient id={id} dataKey={dataKey} config={config} />\n {glowing && <GlowFilter id={id} dataKey={dataKey} spread={5} intensity={0.8} />}\n </defs>\n </>\n )\n}\n\n/** The horizontal measure. Numeric by default — a scatter has no categories. */\nfunction XAxis({\n type = 'number',\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n tickFormatter = defaultTick,\n label,\n ...rest\n}: Omit<ComponentProps<typeof RechartsXAxis>, 'label'> & { label?: string }) {\n return (\n <RechartsXAxis\n type={type}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n tickFormatter={tickFormatter}\n {...rest}\n >\n {axisLabel(label, 'x')}\n </RechartsXAxis>\n )\n}\n\n/** The vertical measure. */\nfunction YAxis({\n type = 'number',\n tickLine = false,\n axisLine = false,\n tickMargin = 8,\n width = 'auto',\n tickFormatter = defaultTick,\n label,\n ...rest\n}: Omit<ComponentProps<typeof RechartsYAxis>, 'label'> & { label?: string }) {\n return (\n <RechartsYAxis\n type={type}\n tickLine={tickLine}\n axisLine={axisLine}\n tickMargin={tickMargin}\n width={width}\n tickFormatter={tickFormatter}\n {...rest}\n >\n {axisLabel(label, 'y')}\n </RechartsYAxis>\n )\n}\n\n/**\n * A third measure, mapped to each mark's AREA rather than its radius.\n *\n * Radius is the wrong encoding and the common mistake: doubling a radius\n * quadruples the ink, so a value twice as large reads as four times as large.\n * `range` is in area units, which is what Recharts scales — so the default\n * below is a ×4 area span, not a ×4 radius.\n */\nfunction ZAxis({\n range = [40, 400],\n ...rest\n}: ComponentProps<typeof RechartsZAxis>) {\n return <RechartsZAxis range={range} {...rest} />\n}\n\n/** Rules on both axes — a scatter is read in two directions. */\nfunction Grid({\n strokeDasharray = '3 3',\n ...rest\n}: ComponentProps<typeof CartesianGrid>) {\n return <CartesianGrid strokeDasharray={strokeDasharray} {...rest} />\n}\n\n/**\n * The hover panel, with a crosshair on both axes.\n *\n * `cursor` is a pair of rules rather than a band: a scatter point is located by\n * two coordinates, and a single vertical cursor answers only half of that.\n */\nfunction Tooltip({ variant, roundness, defaultIndex, cursor = true }: ChartTooltipSlotProps) {\n const { selectedDataKey } = useScatterChart()\n\n return (\n <ChartTooltip\n defaultIndex={defaultIndex}\n cursor={cursor ? { strokeDasharray: '3 3' } : false}\n content={\n <ChartTooltipContent selected={selectedDataKey} roundness={roundness} variant={variant} />\n }\n />\n )\n}\n\n/** The key. Required above one series. */\nfunction Legend({\n variant,\n align = 'right',\n verticalAlign = 'top',\n isClickable = false,\n}: ChartLegendSlotProps) {\n const { selectedDataKey, selectDataKey } = useScatterChart()\n\n return (\n <ChartLegend\n verticalAlign={verticalAlign}\n align={align}\n content={\n <ChartLegendContent\n selected={selectedDataKey}\n onSelectChange={selectDataKey}\n isClickable={isClickable}\n variant={variant}\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\nScatterChart.Scatter = Scatter\nScatterChart.XAxis = XAxis\nScatterChart.YAxis = YAxis\nScatterChart.ZAxis = ZAxis\nScatterChart.Grid = Grid\nScatterChart.Tooltip = Tooltip\nScatterChart.Legend = Legend\nScatterChart.ReferenceLine = ReferenceLine\nScatterChart.ReferenceBand = ReferenceBand\nScatterChart.Annotation = Annotation\nScatterChart.Background = Background\n\nexport default ScatterChart\n"],"mappings":"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","names":[]}
|