@k2b/ui 0.50.0 → 0.52.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/dist/base.css +35 -1
- package/dist/browser/actions/Button.js.map +1 -1
- package/dist/browser/actions/CommandList.js +861 -0
- package/dist/browser/actions/CommandList.js.map +1 -0
- package/dist/browser/actions/CommandMenu.js +237 -0
- package/dist/browser/actions/CommandMenu.js.map +1 -0
- package/dist/browser/actions/Dropdown.js +43 -10
- package/dist/browser/actions/Dropdown.js.map +1 -1
- package/dist/browser/actions/Toolbar.js +8 -2
- package/dist/browser/actions/Toolbar.js.map +1 -1
- package/dist/browser/actions/command-dock.js +47 -0
- package/dist/browser/actions/command-dock.js.map +1 -0
- package/dist/browser/actions/command-items.js +84 -0
- package/dist/browser/actions/command-items.js.map +1 -0
- package/dist/browser/actions/command-messages.js +42 -0
- package/dist/browser/actions/command-messages.js.map +1 -0
- package/dist/browser/actions/index.js +1 -0
- package/dist/browser/actions/index.js.map +1 -1
- package/dist/browser/charts/core/area.js +3 -5
- package/dist/browser/charts/core/area.js.map +1 -1
- package/dist/browser/charts/core/bar-labels.js +5 -0
- package/dist/browser/charts/core/bar-labels.js.map +1 -0
- package/dist/browser/charts/core/bubble.js +166 -0
- package/dist/browser/charts/core/bubble.js.map +1 -0
- package/dist/browser/charts/core/calendar.js +1 -1
- package/dist/browser/charts/core/calendar.js.map +1 -1
- package/dist/browser/charts/core/chart-messages.js +17 -0
- package/dist/browser/charts/core/chart-messages.js.map +1 -0
- package/dist/browser/charts/core/default-styles.js +83 -0
- package/dist/browser/charts/core/default-styles.js.map +1 -0
- package/dist/browser/charts/core/funnel-layout.js +79 -0
- package/dist/browser/charts/core/funnel-layout.js.map +1 -0
- package/dist/browser/charts/core/funnel.js +27 -0
- package/dist/browser/charts/core/funnel.js.map +1 -0
- package/dist/browser/charts/core/hbar-layout.js +118 -0
- package/dist/browser/charts/core/hbar-layout.js.map +1 -0
- package/dist/browser/charts/core/hbar.js +39 -0
- package/dist/browser/charts/core/hbar.js.map +1 -0
- package/dist/browser/charts/core/index.js +12 -2
- package/dist/browser/charts/core/index.js.map +1 -1
- package/dist/browser/charts/core/map.js +116 -0
- package/dist/browser/charts/core/map.js.map +1 -0
- package/dist/browser/charts/core/pareto.js +120 -0
- package/dist/browser/charts/core/pareto.js.map +1 -0
- package/dist/browser/charts/core/radar.js +101 -0
- package/dist/browser/charts/core/radar.js.map +1 -0
- package/dist/browser/charts/core/registry.js +33 -16
- package/dist/browser/charts/core/registry.js.map +1 -1
- package/dist/browser/charts/core/render-size.js +15 -0
- package/dist/browser/charts/core/render-size.js.map +1 -0
- package/dist/browser/charts/core/render.js +39 -10
- package/dist/browser/charts/core/render.js.map +1 -1
- package/dist/browser/charts/core/snapshot.js +4 -4
- package/dist/browser/charts/core/snapshot.js.map +1 -1
- package/dist/browser/charts/core/svg-barGauge.js +67 -0
- package/dist/browser/charts/core/svg-barGauge.js.map +1 -0
- package/dist/browser/charts/core/svg-boxplot.js +127 -0
- package/dist/browser/charts/core/svg-boxplot.js.map +1 -0
- package/dist/browser/charts/core/svg-common.js +1065 -0
- package/dist/browser/charts/core/svg-common.js.map +1 -0
- package/dist/browser/charts/core/svg-gauge.js +185 -0
- package/dist/browser/charts/core/svg-gauge.js.map +1 -0
- package/dist/browser/charts/core/svg-heatmap.js +80 -0
- package/dist/browser/charts/core/svg-heatmap.js.map +1 -0
- package/dist/browser/charts/core/svg-histogram.js +73 -0
- package/dist/browser/charts/core/svg-histogram.js.map +1 -0
- package/dist/browser/charts/core/svg-renderers.js +638 -0
- package/dist/browser/charts/core/svg-renderers.js.map +1 -0
- package/dist/browser/charts/core/svg-scatter.js +114 -0
- package/dist/browser/charts/core/svg-scatter.js.map +1 -0
- package/dist/browser/charts/core/svg.js +4 -2525
- package/dist/browser/charts/core/svg.js.map +1 -1
- package/dist/browser/charts/core/treemap.js +138 -0
- package/dist/browser/charts/core/treemap.js.map +1 -0
- package/dist/browser/charts/core/types.js +0 -2
- package/dist/browser/charts/core/types.js.map +1 -1
- package/dist/browser/charts/core/waterfall.js +162 -0
- package/dist/browser/charts/core/waterfall.js.map +1 -0
- package/dist/browser/charts/description.js +2 -0
- package/dist/browser/charts/description.js.map +1 -0
- package/dist/browser/charts.js.map +1 -1
- package/dist/browser/content/Chart.js +14 -628
- package/dist/browser/content/Chart.js.map +1 -1
- package/dist/browser/content/ChartRoot.js +646 -0
- package/dist/browser/content/ChartRoot.js.map +1 -0
- package/dist/browser/content/ChartSnapshotView.js +4 -1
- package/dist/browser/content/ChartSnapshotView.js.map +1 -1
- package/dist/browser/content/ChartSparkline.js +24 -0
- package/dist/browser/content/ChartSparkline.js.map +1 -0
- package/dist/browser/content/DataTable.js +186 -193
- package/dist/browser/content/DataTable.js.map +1 -1
- package/dist/browser/content/FileView.js +111 -61
- package/dist/browser/content/FileView.js.map +1 -1
- package/dist/browser/content/Lightbox.js +62 -30
- package/dist/browser/content/Lightbox.js.map +1 -1
- package/dist/browser/content/MapView.js +30 -28
- package/dist/browser/content/MapView.js.map +1 -1
- package/dist/browser/content/PdfPreview.js +84 -52
- package/dist/browser/content/PdfPreview.js.map +1 -1
- package/dist/browser/content/PdfViewer.js +588 -0
- package/dist/browser/content/PdfViewer.js.map +1 -0
- package/dist/browser/content/ThumbnailStrip.js +174 -0
- package/dist/browser/content/ThumbnailStrip.js.map +1 -0
- package/dist/browser/content/chart-bar.js +344 -16
- package/dist/browser/content/chart-bar.js.map +1 -1
- package/dist/browser/content/chart-inspection-core.js +50 -29
- package/dist/browser/content/chart-inspection-core.js.map +1 -1
- package/dist/browser/content/chart-line-gaps.js +1 -1
- package/dist/browser/content/chart-line-gaps.js.map +1 -1
- package/dist/browser/content/chart-message-catalog.js +28 -0
- package/dist/browser/content/chart-message-catalog.js.map +1 -1
- package/dist/browser/content/chart-state-timeline.js +1 -1
- package/dist/browser/content/chart-state-timeline.js.map +1 -1
- package/dist/browser/content/chart-svg.js +2 -1
- package/dist/browser/content/chart-svg.js.map +1 -1
- package/dist/browser/content/index.js +2 -0
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/content/pdf-document.js +29 -0
- package/dist/browser/content/pdf-document.js.map +1 -0
- package/dist/browser/content/pdf-limits.js +31 -0
- package/dist/browser/content/pdf-limits.js.map +1 -0
- package/dist/browser/content/pdf-messages.js +34 -2
- package/dist/browser/content/pdf-messages.js.map +1 -1
- package/dist/browser/content/pdf-viewer-engine.js +57 -0
- package/dist/browser/content/pdf-viewer-worker.js +28 -0
- package/dist/browser/content/thumbnail-strip-messages.js +24 -0
- package/dist/browser/content/thumbnail-strip-messages.js.map +1 -0
- package/dist/browser/inputs/ChoiceInputs.js +150 -76
- package/dist/browser/inputs/ChoiceInputs.js.map +1 -1
- package/dist/browser/inputs/ChoiceSlider.js +75 -58
- package/dist/browser/inputs/ChoiceSlider.js.map +1 -1
- package/dist/browser/inputs/DatePicker.js +113 -98
- package/dist/browser/inputs/DatePicker.js.map +1 -1
- package/dist/browser/inputs/PeriodPicker.js +874 -0
- package/dist/browser/inputs/PeriodPicker.js.map +1 -0
- package/dist/browser/inputs/index.js +5 -1
- package/dist/browser/inputs/index.js.map +1 -1
- package/dist/browser/inputs/input-messages.js +8 -2
- package/dist/browser/inputs/input-messages.js.map +1 -1
- package/dist/browser/inputs/markdown/MarkdownEditor.js +14 -10
- package/dist/browser/inputs/markdown/MarkdownEditor.js.map +1 -1
- package/dist/browser/inputs/markdown/MarkdownSelectionToolbar.js +330 -0
- package/dist/browser/inputs/markdown/MarkdownSelectionToolbar.js.map +1 -0
- package/dist/browser/inputs/markdown/SlashCommands.js +402 -0
- package/dist/browser/inputs/markdown/SlashCommands.js.map +1 -0
- package/dist/browser/inputs/period-picker-messages.js +71 -0
- package/dist/browser/inputs/period-picker-messages.js.map +1 -0
- package/dist/browser/inputs/period.js +263 -0
- package/dist/browser/inputs/period.js.map +1 -0
- package/dist/browser/internal/field.js +96 -77
- package/dist/browser/internal/field.js.map +1 -1
- package/dist/browser/layout/AppWorkspace.js +7 -5
- package/dist/browser/layout/AppWorkspace.js.map +1 -1
- package/dist/browser/layout/DetailPanel.js +147 -120
- package/dist/browser/layout/DetailPanel.js.map +1 -1
- package/dist/browser/layout/Discussion.js +101 -108
- package/dist/browser/layout/Discussion.js.map +1 -1
- package/dist/browser/layout/PanelDialog.js +92 -68
- package/dist/browser/layout/PanelDialog.js.map +1 -1
- package/dist/browser/layout/index.js.map +1 -1
- package/dist/browser/surfaces/DescriptionList.js +5 -2
- package/dist/browser/surfaces/DescriptionList.js.map +1 -1
- package/dist/browser/surfaces/StatCell.js +95 -78
- package/dist/browser/surfaces/StatCell.js.map +1 -1
- package/dist/browser/surfaces/delta-badge.js +16 -2
- package/dist/browser/surfaces/delta-badge.js.map +1 -1
- package/dist/browser/surfaces/stat-cell-messages.js +4 -2
- package/dist/browser/surfaces/stat-cell-messages.js.map +1 -1
- package/dist/browser/widgets/DashboardFrame.js +315 -0
- package/dist/browser/widgets/DashboardFrame.js.map +1 -0
- package/dist/browser/widgets/index.js +1 -0
- package/dist/browser/widgets/index.js.map +1 -1
- package/dist/licenses/pdfjs-dist.txt +177 -0
- package/dist/ssr/actions/Button.js.map +1 -1
- package/dist/ssr/actions/CommandList.js +638 -0
- package/dist/ssr/actions/CommandList.js.map +1 -0
- package/dist/ssr/actions/CommandMenu.js +197 -0
- package/dist/ssr/actions/CommandMenu.js.map +1 -0
- package/dist/ssr/actions/Dropdown.js +43 -10
- package/dist/ssr/actions/Dropdown.js.map +1 -1
- package/dist/ssr/actions/Toolbar.js +8 -2
- package/dist/ssr/actions/Toolbar.js.map +1 -1
- package/dist/ssr/actions/command-dock.js +47 -0
- package/dist/ssr/actions/command-dock.js.map +1 -0
- package/dist/ssr/actions/command-items.js +84 -0
- package/dist/ssr/actions/command-items.js.map +1 -0
- package/dist/ssr/actions/command-messages.js +42 -0
- package/dist/ssr/actions/command-messages.js.map +1 -0
- package/dist/ssr/actions/index.js +1 -0
- package/dist/ssr/actions/index.js.map +1 -1
- package/dist/ssr/charts/core/area.js +3 -5
- package/dist/ssr/charts/core/area.js.map +1 -1
- package/dist/ssr/charts/core/bar-labels.js +5 -0
- package/dist/ssr/charts/core/bar-labels.js.map +1 -0
- package/dist/ssr/charts/core/bubble.js +166 -0
- package/dist/ssr/charts/core/bubble.js.map +1 -0
- package/dist/ssr/charts/core/calendar.js +1 -1
- package/dist/ssr/charts/core/calendar.js.map +1 -1
- package/dist/ssr/charts/core/chart-messages.js +17 -0
- package/dist/ssr/charts/core/chart-messages.js.map +1 -0
- package/dist/ssr/charts/core/default-styles.js +83 -0
- package/dist/ssr/charts/core/default-styles.js.map +1 -0
- package/dist/ssr/charts/core/funnel-layout.js +79 -0
- package/dist/ssr/charts/core/funnel-layout.js.map +1 -0
- package/dist/ssr/charts/core/funnel.js +27 -0
- package/dist/ssr/charts/core/funnel.js.map +1 -0
- package/dist/ssr/charts/core/hbar-layout.js +118 -0
- package/dist/ssr/charts/core/hbar-layout.js.map +1 -0
- package/dist/ssr/charts/core/hbar.js +39 -0
- package/dist/ssr/charts/core/hbar.js.map +1 -0
- package/dist/ssr/charts/core/index.js +12 -2
- package/dist/ssr/charts/core/index.js.map +1 -1
- package/dist/ssr/charts/core/map.js +116 -0
- package/dist/ssr/charts/core/map.js.map +1 -0
- package/dist/ssr/charts/core/pareto.js +120 -0
- package/dist/ssr/charts/core/pareto.js.map +1 -0
- package/dist/ssr/charts/core/radar.js +101 -0
- package/dist/ssr/charts/core/radar.js.map +1 -0
- package/dist/ssr/charts/core/registry.js +33 -16
- package/dist/ssr/charts/core/registry.js.map +1 -1
- package/dist/ssr/charts/core/render-size.js +15 -0
- package/dist/ssr/charts/core/render-size.js.map +1 -0
- package/dist/ssr/charts/core/render.js +39 -10
- package/dist/ssr/charts/core/render.js.map +1 -1
- package/dist/ssr/charts/core/snapshot.js +4 -4
- package/dist/ssr/charts/core/snapshot.js.map +1 -1
- package/dist/ssr/charts/core/svg-barGauge.js +67 -0
- package/dist/ssr/charts/core/svg-barGauge.js.map +1 -0
- package/dist/ssr/charts/core/svg-boxplot.js +127 -0
- package/dist/ssr/charts/core/svg-boxplot.js.map +1 -0
- package/dist/ssr/charts/core/svg-common.js +1065 -0
- package/dist/ssr/charts/core/svg-common.js.map +1 -0
- package/dist/ssr/charts/core/svg-gauge.js +185 -0
- package/dist/ssr/charts/core/svg-gauge.js.map +1 -0
- package/dist/ssr/charts/core/svg-heatmap.js +80 -0
- package/dist/ssr/charts/core/svg-heatmap.js.map +1 -0
- package/dist/ssr/charts/core/svg-histogram.js +73 -0
- package/dist/ssr/charts/core/svg-histogram.js.map +1 -0
- package/dist/ssr/charts/core/svg-renderers.js +638 -0
- package/dist/ssr/charts/core/svg-renderers.js.map +1 -0
- package/dist/ssr/charts/core/svg-scatter.js +114 -0
- package/dist/ssr/charts/core/svg-scatter.js.map +1 -0
- package/dist/ssr/charts/core/svg.js +4 -2525
- package/dist/ssr/charts/core/svg.js.map +1 -1
- package/dist/ssr/charts/core/treemap.js +138 -0
- package/dist/ssr/charts/core/treemap.js.map +1 -0
- package/dist/ssr/charts/core/types.js +0 -2
- package/dist/ssr/charts/core/types.js.map +1 -1
- package/dist/ssr/charts/core/waterfall.js +162 -0
- package/dist/ssr/charts/core/waterfall.js.map +1 -0
- package/dist/ssr/charts/description.js +2 -0
- package/dist/ssr/charts/description.js.map +1 -0
- package/dist/ssr/charts.js.map +1 -1
- package/dist/ssr/content/Chart.js +14 -471
- package/dist/ssr/content/Chart.js.map +1 -1
- package/dist/ssr/content/ChartRoot.js +489 -0
- package/dist/ssr/content/ChartRoot.js.map +1 -0
- package/dist/ssr/content/ChartSnapshotView.js +1 -0
- package/dist/ssr/content/ChartSnapshotView.js.map +1 -1
- package/dist/ssr/content/ChartSparkline.js +24 -0
- package/dist/ssr/content/ChartSparkline.js.map +1 -0
- package/dist/ssr/content/DataTable.js +12 -16
- package/dist/ssr/content/DataTable.js.map +1 -1
- package/dist/ssr/content/FileView.js +56 -7
- package/dist/ssr/content/FileView.js.map +1 -1
- package/dist/ssr/content/Lightbox.js +39 -10
- package/dist/ssr/content/Lightbox.js.map +1 -1
- package/dist/ssr/content/MapView.js +9 -9
- package/dist/ssr/content/MapView.js.map +1 -1
- package/dist/ssr/content/PdfPreview.js +49 -20
- package/dist/ssr/content/PdfPreview.js.map +1 -1
- package/dist/ssr/content/PdfViewer.js +484 -0
- package/dist/ssr/content/PdfViewer.js.map +1 -0
- package/dist/ssr/content/ThumbnailStrip.js +103 -0
- package/dist/ssr/content/ThumbnailStrip.js.map +1 -0
- package/dist/ssr/content/chart-bar.js +344 -16
- package/dist/ssr/content/chart-bar.js.map +1 -1
- package/dist/ssr/content/chart-inspection-core.js +50 -29
- package/dist/ssr/content/chart-inspection-core.js.map +1 -1
- package/dist/ssr/content/chart-line-gaps.js +1 -1
- package/dist/ssr/content/chart-line-gaps.js.map +1 -1
- package/dist/ssr/content/chart-message-catalog.js +28 -0
- package/dist/ssr/content/chart-message-catalog.js.map +1 -1
- package/dist/ssr/content/chart-state-timeline.js +1 -1
- package/dist/ssr/content/chart-state-timeline.js.map +1 -1
- package/dist/ssr/content/chart-svg.js +2 -1
- package/dist/ssr/content/chart-svg.js.map +1 -1
- package/dist/ssr/content/index.js +2 -0
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/content/pdf-document.js +29 -0
- package/dist/ssr/content/pdf-document.js.map +1 -0
- package/dist/ssr/content/pdf-limits.js +31 -0
- package/dist/ssr/content/pdf-limits.js.map +1 -0
- package/dist/ssr/content/pdf-messages.js +34 -2
- package/dist/ssr/content/pdf-messages.js.map +1 -1
- package/dist/ssr/content/thumbnail-strip-messages.js +24 -0
- package/dist/ssr/content/thumbnail-strip-messages.js.map +1 -0
- package/dist/ssr/inputs/ChoiceInputs.js +69 -17
- package/dist/ssr/inputs/ChoiceInputs.js.map +1 -1
- package/dist/ssr/inputs/ChoiceSlider.js +17 -12
- package/dist/ssr/inputs/ChoiceSlider.js.map +1 -1
- package/dist/ssr/inputs/DatePicker.js +14 -9
- package/dist/ssr/inputs/DatePicker.js.map +1 -1
- package/dist/ssr/inputs/PeriodPicker.js +598 -0
- package/dist/ssr/inputs/PeriodPicker.js.map +1 -0
- package/dist/ssr/inputs/index.js +5 -1
- package/dist/ssr/inputs/index.js.map +1 -1
- package/dist/ssr/inputs/input-messages.js +8 -2
- package/dist/ssr/inputs/input-messages.js.map +1 -1
- package/dist/ssr/inputs/markdown/MarkdownEditor.js +3 -2
- package/dist/ssr/inputs/markdown/MarkdownEditor.js.map +1 -1
- package/dist/ssr/inputs/markdown/MarkdownSelectionToolbar.js +293 -0
- package/dist/ssr/inputs/markdown/MarkdownSelectionToolbar.js.map +1 -0
- package/dist/ssr/inputs/markdown/SlashCommands.js +339 -0
- package/dist/ssr/inputs/markdown/SlashCommands.js.map +1 -0
- package/dist/ssr/inputs/period-picker-messages.js +71 -0
- package/dist/ssr/inputs/period-picker-messages.js.map +1 -0
- package/dist/ssr/inputs/period.js +263 -0
- package/dist/ssr/inputs/period.js.map +1 -0
- package/dist/ssr/internal/field.js +36 -24
- package/dist/ssr/internal/field.js.map +1 -1
- package/dist/ssr/layout/AppWorkspace.js +5 -1
- package/dist/ssr/layout/AppWorkspace.js.map +1 -1
- package/dist/ssr/layout/DetailPanel.js +27 -4
- package/dist/ssr/layout/DetailPanel.js.map +1 -1
- package/dist/ssr/layout/Discussion.js +16 -2
- package/dist/ssr/layout/Discussion.js.map +1 -1
- package/dist/ssr/layout/PanelDialog.js +40 -26
- package/dist/ssr/layout/PanelDialog.js.map +1 -1
- package/dist/ssr/layout/index.js.map +1 -1
- package/dist/ssr/surfaces/DescriptionList.js +1 -1
- package/dist/ssr/surfaces/DescriptionList.js.map +1 -1
- package/dist/ssr/surfaces/StatCell.js +31 -21
- package/dist/ssr/surfaces/StatCell.js.map +1 -1
- package/dist/ssr/surfaces/delta-badge.js +16 -2
- package/dist/ssr/surfaces/delta-badge.js.map +1 -1
- package/dist/ssr/surfaces/stat-cell-messages.js +4 -2
- package/dist/ssr/surfaces/stat-cell-messages.js.map +1 -1
- package/dist/ssr/widgets/DashboardFrame.js +195 -0
- package/dist/ssr/widgets/DashboardFrame.js.map +1 -0
- package/dist/ssr/widgets/index.js +1 -0
- package/dist/ssr/widgets/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/actions/Button.d.ts +1 -1
- package/dist/types/actions/CommandList.d.ts +120 -0
- package/dist/types/actions/CommandMenu.d.ts +39 -0
- package/dist/types/actions/Dropdown.d.ts +17 -0
- package/dist/types/actions/Toolbar.d.ts +2 -0
- package/dist/types/actions/command-dock.d.ts +8 -0
- package/dist/types/actions/command-items.d.ts +134 -0
- package/dist/types/actions/command-messages.d.ts +19 -0
- package/dist/types/actions/index.d.ts +3 -0
- package/dist/types/charts/core/bar-labels.d.ts +1 -0
- package/dist/types/charts/core/bubble.d.ts +66 -0
- package/dist/types/charts/core/calendar.d.ts +1 -1
- package/dist/types/charts/core/chart-messages.d.ts +5 -0
- package/dist/types/charts/core/default-styles.d.ts +1 -0
- package/dist/types/charts/core/funnel-layout.d.ts +43 -0
- package/dist/types/charts/core/funnel.d.ts +13 -0
- package/dist/types/charts/core/hbar-layout.d.ts +74 -0
- package/dist/types/charts/core/hbar.d.ts +13 -0
- package/dist/types/charts/core/index.d.ts +12 -2
- package/dist/types/charts/core/map.d.ts +3 -0
- package/dist/types/charts/core/pareto.d.ts +40 -0
- package/dist/types/charts/core/radar.d.ts +47 -0
- package/dist/types/charts/core/registry.d.ts +27 -11
- package/dist/types/charts/core/render-size.d.ts +7 -0
- package/dist/types/charts/core/render.d.ts +1 -4
- package/dist/types/charts/core/snapshot.d.ts +2 -2
- package/dist/types/charts/core/svg-barGauge.d.ts +3 -0
- package/dist/types/charts/core/svg-boxplot.d.ts +18 -0
- package/dist/types/charts/core/svg-common.d.ts +612 -0
- package/dist/types/charts/core/svg-gauge.d.ts +6 -0
- package/dist/types/charts/core/svg-heatmap.d.ts +6 -0
- package/dist/types/charts/core/svg-histogram.d.ts +16 -0
- package/dist/types/charts/core/svg-renderers.d.ts +118 -0
- package/dist/types/charts/core/svg-scatter.d.ts +23 -0
- package/dist/types/charts/core/svg.d.ts +4 -698
- package/dist/types/charts/core/treemap.d.ts +45 -0
- package/dist/types/charts/core/types.d.ts +1 -12
- package/dist/types/charts/core/waterfall.d.ts +68 -0
- package/dist/types/charts/description.d.ts +289 -0
- package/dist/types/charts.d.ts +1 -0
- package/dist/types/content/Chart.d.ts +3 -91
- package/dist/types/content/ChartRoot.d.ts +92 -0
- package/dist/types/content/ChartSparkline.d.ts +5 -0
- package/dist/types/content/Lightbox.d.ts +2 -0
- package/dist/types/content/PdfViewer.d.ts +32 -0
- package/dist/types/content/ThumbnailStrip.d.ts +29 -0
- package/dist/types/content/chart-bar.d.ts +131 -2
- package/dist/types/content/chart-inspection-core.d.ts +7 -0
- package/dist/types/content/chart-inspection.d.ts +1 -0
- package/dist/types/content/chart-message-catalog.d.ts +8 -0
- package/dist/types/content/index.d.ts +3 -0
- package/dist/types/content/pdf-document.d.ts +5 -0
- package/dist/types/content/pdf-engine.d.ts +9 -0
- package/dist/types/content/pdf-limits.d.ts +43 -0
- package/dist/types/content/pdf-messages.d.ts +16 -0
- package/dist/types/content/thumbnail-strip-messages.d.ts +10 -0
- package/dist/types/inputs/ChoiceInputs.d.ts +6 -0
- package/dist/types/inputs/ChoiceSlider.d.ts +12 -0
- package/dist/types/inputs/DatePicker.d.ts +22 -0
- package/dist/types/inputs/PeriodPicker.d.ts +24 -0
- package/dist/types/inputs/index.d.ts +10 -2
- package/dist/types/inputs/input-messages.d.ts +3 -0
- package/dist/types/inputs/markdown/MarkdownEditor.d.ts +5 -0
- package/dist/types/inputs/markdown/MarkdownSelectionToolbar.d.ts +19 -0
- package/dist/types/inputs/markdown/SlashCommands.d.ts +20 -0
- package/dist/types/inputs/period-picker-messages.d.ts +34 -0
- package/dist/types/inputs/period.d.ts +134 -0
- package/dist/types/internal/field.d.ts +2 -0
- package/dist/types/layout/DetailPanel.d.ts +12 -0
- package/dist/types/layout/PanelDialog.d.ts +5 -0
- package/dist/types/layout/index.d.ts +1 -1
- package/dist/types/surfaces/DescriptionList.d.ts +5 -0
- package/dist/types/surfaces/StatCell.d.ts +6 -1
- package/dist/types/surfaces/stat-cell-messages.d.ts +1 -0
- package/dist/types/widgets/DashboardFrame.d.ts +61 -0
- package/dist/types/widgets/index.d.ts +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,1065 @@
|
|
|
1
|
+
// Ported from @k2b/stdlib 0.30.0 (ISC); retain SVG and numeric algorithms until parity proves changes.
|
|
2
|
+
// ==========================
|
|
3
|
+
// SVG Chart Generation
|
|
4
|
+
// ==========================
|
|
5
|
+
//
|
|
6
|
+
// Pure-native SVG generation for the four basic chart types: scatter, line,
|
|
7
|
+
// bar, and pie/donut. Returns SVG strings — inject directly into the DOM,
|
|
8
|
+
// write to disk, or send over the wire. No peer dependencies.
|
|
9
|
+
//
|
|
10
|
+
// Charts ship with embedded default CSS using semantic class names and CSS
|
|
11
|
+
// custom properties for colors. Consumers override styles via class selectors
|
|
12
|
+
// (their CSS wins on specificity) or CSS variables (`--stdlib-chart-c1` ...
|
|
13
|
+
// `--stdlib-chart-c8`). Axes and labels use `currentColor` so parent `color`
|
|
14
|
+
// drives theming (dark mode "just works").
|
|
15
|
+
|
|
16
|
+
import { DEFAULT_STYLES } from "./default-styles";
|
|
17
|
+
|
|
18
|
+
// ==========================
|
|
19
|
+
// PUBLIC TYPES
|
|
20
|
+
// ==========================
|
|
21
|
+
|
|
22
|
+
/** Marker shapes for `scatter` data points. Auto-cycles per series when
|
|
23
|
+
* `autoVariant` is enabled on the chart. */
|
|
24
|
+
|
|
25
|
+
/** Line dash patterns for `line` series. Auto-cycles per series when
|
|
26
|
+
* `autoVariant` is enabled. */
|
|
27
|
+
|
|
28
|
+
export const itemColorIndex = (item, fallback) => {
|
|
29
|
+
const index = item.colorIndex ?? fallback;
|
|
30
|
+
if (!Number.isInteger(index) || index < 0) throw new RangeError("Chart colorIndex must be a nonnegative integer");
|
|
31
|
+
return index % 8;
|
|
32
|
+
};
|
|
33
|
+
/** One selectable datum in an inspected SVG. Indices refer to the source arrays.
|
|
34
|
+
* `anchor` uses the containing SVG coordinate system, including nested map SVGs.
|
|
35
|
+
* Values are raw data; `formatted` contains the renderer's axis/value formatting.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/** Wrap renderer-owned geometry with escaped, inert inspection metadata. */
|
|
39
|
+
export const chartDatumSvg = (svg, datum, inspect, titleText) => {
|
|
40
|
+
if (!inspect || !datum.anchor.every(Number.isFinite)) return svg;
|
|
41
|
+
const title = titleText ?? [datum.label, ...datum.values.map(field => `${field.key}: ${field.formatted ?? field.value}`)].filter(Boolean).join(" · ");
|
|
42
|
+
return `<g data-chart-role="${escapeXml(datum.role)}" data-chart-datum="${escapeXml(JSON.stringify(datum))}"><title>${escapeXml(title)}</title>${svg}</g>`;
|
|
43
|
+
};
|
|
44
|
+
export const inspectionPoint = (datum, inspect) => inspect ? chartDatumSvg(`<circle class="stdlib-chart-inspection-point" cx="${fmt(datum.anchor[0])}" cy="${fmt(datum.anchor[1])}" r="3" fill="transparent" stroke="none"/>`, datum, true) : "";
|
|
45
|
+
export const pointValues = (p, xAxis, yAxis) => [{
|
|
46
|
+
key: "x",
|
|
47
|
+
value: p.x,
|
|
48
|
+
formatted: xAxis?.format?.(p.x)
|
|
49
|
+
}, {
|
|
50
|
+
key: "y",
|
|
51
|
+
value: p.y,
|
|
52
|
+
formatted: yAxis?.format?.(p.y)
|
|
53
|
+
}, ...Object.entries(p).filter(([key, value]) => key !== "x" && key !== "y" && typeof value === "number" && Number.isFinite(value)).map(([key, value]) => ({
|
|
54
|
+
key,
|
|
55
|
+
value
|
|
56
|
+
}))];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A horizontal or vertical reference line drawn on a cartesian chart, e.g.
|
|
60
|
+
* for thresholds, targets, or averages.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/** Internal shared attributes for raw cartesian charts and UI bar layouts. */
|
|
64
|
+
export const referenceLineAttributes = (ref, label = false) => {
|
|
65
|
+
const declarations = [];
|
|
66
|
+
if (!label && ref.style !== undefined) {
|
|
67
|
+
const styles = {
|
|
68
|
+
solid: "none",
|
|
69
|
+
dashed: "4 4",
|
|
70
|
+
dotted: "1 3"
|
|
71
|
+
};
|
|
72
|
+
const dash = styles[ref.style];
|
|
73
|
+
if (typeof dash !== "string") throw new RangeError("Invalid reference line style");
|
|
74
|
+
declarations.push(`stroke-dasharray:${dash}`);
|
|
75
|
+
if (ref.style === "dotted") declarations.push("stroke-linecap:round");
|
|
76
|
+
}
|
|
77
|
+
if (ref.tone !== undefined) {
|
|
78
|
+
const tones = {
|
|
79
|
+
neutral: "currentColor",
|
|
80
|
+
info: "var(--k2b-info-text,#1d4ed8)",
|
|
81
|
+
success: "var(--k2b-success-text,#047857)",
|
|
82
|
+
warning: "var(--k2b-warning-text,#92400e)",
|
|
83
|
+
danger: "var(--k2b-danger-text,#b91c1c)"
|
|
84
|
+
};
|
|
85
|
+
const color = tones[ref.tone];
|
|
86
|
+
if (typeof color !== "string") throw new RangeError("Invalid reference line tone");
|
|
87
|
+
declarations.push(`color:${color}`);
|
|
88
|
+
}
|
|
89
|
+
return declarations.length ? ` style="${declarations.join(";")}"` : "";
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// ==========================
|
|
93
|
+
// CONSTANTS
|
|
94
|
+
// ==========================
|
|
95
|
+
|
|
96
|
+
export const DEFAULT_WIDTH = 400;
|
|
97
|
+
export const DEFAULT_HEIGHT = 240;
|
|
98
|
+
const DEFAULT_PADDING = {
|
|
99
|
+
top: 16,
|
|
100
|
+
right: 16,
|
|
101
|
+
bottom: 32,
|
|
102
|
+
left: 40
|
|
103
|
+
};
|
|
104
|
+
const DEFAULT_TICKS = 5;
|
|
105
|
+
export const DEFAULT_MARKERS = ["circle", "square", "triangle", "diamond", "plus", "cross"];
|
|
106
|
+
export const DEFAULT_LINE_STYLES = ["solid", "dashed", "dotted", "dashdot"];
|
|
107
|
+
|
|
108
|
+
/** SVG `stroke-dasharray` values per line style. Empty string = no dasharray. */
|
|
109
|
+
export const LINE_DASH_PATTERNS = {
|
|
110
|
+
solid: "",
|
|
111
|
+
dashed: "6 4",
|
|
112
|
+
dotted: "2 3",
|
|
113
|
+
dashdot: "6 3 2 3"
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// ==========================
|
|
117
|
+
// HELPERS (pure)
|
|
118
|
+
// ==========================
|
|
119
|
+
|
|
120
|
+
/** XML-escape a string for safe inclusion in attribute values or text content. */
|
|
121
|
+
export const escapeXml = s => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
122
|
+
|
|
123
|
+
/** Normalize a `Padding` input (number or partial object) to a full `Padding`. */
|
|
124
|
+
export const normalizePadding = p => {
|
|
125
|
+
if (p === undefined) return {
|
|
126
|
+
...DEFAULT_PADDING
|
|
127
|
+
};
|
|
128
|
+
if (typeof p === "number") return {
|
|
129
|
+
top: p,
|
|
130
|
+
right: p,
|
|
131
|
+
bottom: p,
|
|
132
|
+
left: p
|
|
133
|
+
};
|
|
134
|
+
return {
|
|
135
|
+
top: p.top ?? DEFAULT_PADDING.top,
|
|
136
|
+
right: p.right ?? DEFAULT_PADDING.right,
|
|
137
|
+
bottom: p.bottom ?? DEFAULT_PADDING.bottom,
|
|
138
|
+
left: p.left ?? DEFAULT_PADDING.left
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Compute `[min, max]` from a list of numeric values, ignoring non-finite
|
|
144
|
+
* entries. Returns `[0, 1]` for an empty/all-non-finite list and pads ±1
|
|
145
|
+
* around a single finite value.
|
|
146
|
+
*/
|
|
147
|
+
export const computeDomain = values => {
|
|
148
|
+
let min = Number.POSITIVE_INFINITY;
|
|
149
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
150
|
+
for (const v of values) {
|
|
151
|
+
if (!Number.isFinite(v)) continue;
|
|
152
|
+
if (v < min) min = v;
|
|
153
|
+
if (v > max) max = v;
|
|
154
|
+
}
|
|
155
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) return [0, 1];
|
|
156
|
+
if (min === max) return [min - 1, max + 1];
|
|
157
|
+
return [min, max];
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Pick a "nice" axis step (1, 2, 5, 10, 20, 50, ...) that splits `range`
|
|
162
|
+
* into roughly `target` intervals. Avoids ugly tick labels like 0.473.
|
|
163
|
+
*/
|
|
164
|
+
export const niceStep = (range, target) => {
|
|
165
|
+
if (range <= 0 || target <= 0) return 1;
|
|
166
|
+
const rough = range / target;
|
|
167
|
+
const mag = Math.pow(10, Math.floor(Math.log10(rough)));
|
|
168
|
+
const norm = rough / mag;
|
|
169
|
+
const nice = norm < 1.5 ? 1 : norm < 3 ? 2 : norm < 7 ? 5 : 10;
|
|
170
|
+
return nice * mag;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/** The most ticks one linear axis counts, the same budget as a time axis; more never fit a readable axis. */
|
|
174
|
+
export const MAX_AXIS_TICKS = 1_000;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Round `min` down and `max` up to the nearest multiples of `step`, so the
|
|
178
|
+
* domain edges fall on tick boundaries. Returns the extended domain plus the
|
|
179
|
+
* tick values within it (inclusive of both ends).
|
|
180
|
+
*/
|
|
181
|
+
export const extendDomainToNice = (min, max, step) => {
|
|
182
|
+
const lo = Math.floor(min / step) * step;
|
|
183
|
+
const hi = Math.ceil(max / step) * step;
|
|
184
|
+
const ticks = [];
|
|
185
|
+
// Use rounding to avoid floating-point drift at tick positions.
|
|
186
|
+
const decimals = Math.max(0, -Math.floor(Math.log10(step)));
|
|
187
|
+
const round = v => Number(v.toFixed(Math.min(decimals + 2, 12)));
|
|
188
|
+
// A step below the float spacing of large values never advances, and a tiny step counts without end: keep the ends.
|
|
189
|
+
const ends = {
|
|
190
|
+
domain: [lo, hi],
|
|
191
|
+
ticks: [...new Set([round(lo), round(hi)])]
|
|
192
|
+
};
|
|
193
|
+
if ((hi - lo) / step > MAX_AXIS_TICKS) return ends;
|
|
194
|
+
for (let v = lo; v <= hi + step / 2;) {
|
|
195
|
+
ticks.push(round(v));
|
|
196
|
+
const next = v + step;
|
|
197
|
+
if (next <= v) return ends;
|
|
198
|
+
v = next;
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
domain: [lo, hi],
|
|
202
|
+
ticks
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/** Linear map of `value` from input domain `[d0, d1]` to output range `[r0, r1]`. */
|
|
207
|
+
export const mapRange = (value, [d0, d1], [r0, r1]) => {
|
|
208
|
+
if (d1 === d0) return (r0 + r1) / 2;
|
|
209
|
+
return r0 + (value - d0) / (d1 - d0) * (r1 - r0);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Logarithmic (base-10) map of `value` from input domain `[d0, d1]` to
|
|
214
|
+
* output range `[r0, r1]`. Both domain endpoints must be strictly positive.
|
|
215
|
+
* Non-positive values are clamped to a small epsilon to avoid `-Infinity`.
|
|
216
|
+
*/
|
|
217
|
+
export const mapLog = (value, [d0, d1], [r0, r1]) => {
|
|
218
|
+
if (d0 <= 0 || d1 <= 0) return (r0 + r1) / 2;
|
|
219
|
+
const v = Math.max(value, 1e-300);
|
|
220
|
+
const logD0 = Math.log10(d0);
|
|
221
|
+
const logD1 = Math.log10(d1);
|
|
222
|
+
if (logD0 === logD1) return (r0 + r1) / 2;
|
|
223
|
+
return r0 + (Math.log10(v) - logD0) / (logD1 - logD0) * (r1 - r0);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Compute logarithmic ticks (powers of 10) within `[min, max]`. The
|
|
228
|
+
* returned array always includes both decade boundaries enclosing the
|
|
229
|
+
* domain so the axis line spans the data.
|
|
230
|
+
*/
|
|
231
|
+
export const niceLogTicks = (min, max) => {
|
|
232
|
+
const safeMin = Math.max(min, 1e-300);
|
|
233
|
+
const safeMax = Math.max(max, safeMin);
|
|
234
|
+
// Clamp the log-tick range to a finite, SVG-safe span so callers passing
|
|
235
|
+
// Number.MAX_VALUE (or non-finite extremes) don't trigger an infinite loop.
|
|
236
|
+
// 1e-30 .. 1e30 covers every real-world scientific magnitude.
|
|
237
|
+
const MIN_EXP = -30;
|
|
238
|
+
const MAX_EXP = 30;
|
|
239
|
+
const loExp = Math.max(MIN_EXP, Math.floor(Math.log10(safeMin)));
|
|
240
|
+
const hiExp = Math.min(MAX_EXP, Math.ceil(Math.log10(safeMax)));
|
|
241
|
+
const lo = Math.pow(10, loExp);
|
|
242
|
+
const hi = Math.pow(10, Math.max(loExp, hiExp));
|
|
243
|
+
const ticks = [];
|
|
244
|
+
// Iterate by exponent to avoid floating-point drift at extreme ranges.
|
|
245
|
+
for (let e = loExp; e <= Math.max(loExp, hiExp); e++) {
|
|
246
|
+
ticks.push(Math.pow(10, e));
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
domain: [lo, hi],
|
|
250
|
+
ticks
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Least-squares linear regression. Returns slope, intercept, and r² for the
|
|
256
|
+
* regression line `y = slope * x + intercept`. Returns null for fewer than
|
|
257
|
+
* two finite points or all-x-equal input.
|
|
258
|
+
*/
|
|
259
|
+
export const linearRegression = points => {
|
|
260
|
+
const finite = points.filter(p => Number.isFinite(p.x) && Number.isFinite(p.y));
|
|
261
|
+
const n = finite.length;
|
|
262
|
+
if (n < 2) return null;
|
|
263
|
+
let sumX = 0;
|
|
264
|
+
let sumY = 0;
|
|
265
|
+
for (const p of finite) {
|
|
266
|
+
sumX += p.x;
|
|
267
|
+
sumY += p.y;
|
|
268
|
+
}
|
|
269
|
+
const meanX = sumX / n;
|
|
270
|
+
const meanY = sumY / n;
|
|
271
|
+
let num = 0;
|
|
272
|
+
let denom = 0;
|
|
273
|
+
let totalSS = 0;
|
|
274
|
+
for (const p of finite) {
|
|
275
|
+
const dx = p.x - meanX;
|
|
276
|
+
const dy = p.y - meanY;
|
|
277
|
+
num += dx * dy;
|
|
278
|
+
denom += dx * dx;
|
|
279
|
+
totalSS += dy * dy;
|
|
280
|
+
}
|
|
281
|
+
if (denom === 0) return null;
|
|
282
|
+
const slope = num / denom;
|
|
283
|
+
const intercept = meanY - slope * meanX;
|
|
284
|
+
// r² via 1 - SSres / SStot
|
|
285
|
+
let resSS = 0;
|
|
286
|
+
for (const p of finite) {
|
|
287
|
+
const predicted = slope * p.x + intercept;
|
|
288
|
+
const resid = p.y - predicted;
|
|
289
|
+
resSS += resid * resid;
|
|
290
|
+
}
|
|
291
|
+
const r2 = totalSS === 0 ? 1 : 1 - resSS / totalSS;
|
|
292
|
+
return {
|
|
293
|
+
slope,
|
|
294
|
+
intercept,
|
|
295
|
+
r2
|
|
296
|
+
};
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Compute box-plot statistics for a set of observations:
|
|
301
|
+
* min/max, quartiles (q1/q2/q3 via R-7 linear interpolation), Tukey whiskers
|
|
302
|
+
* (1.5×IQR rule clamped to data extents), and outliers beyond the whiskers.
|
|
303
|
+
*
|
|
304
|
+
* Returns null for an empty/all-non-finite input.
|
|
305
|
+
*/
|
|
306
|
+
export const computeBoxStats = values => {
|
|
307
|
+
const finite = values.filter(v => Number.isFinite(v));
|
|
308
|
+
if (finite.length === 0) return null;
|
|
309
|
+
const sorted = [...finite].sort((a, b) => a - b);
|
|
310
|
+
const quantile = q => {
|
|
311
|
+
// R-7 (linear interpolation between consecutive order statistics).
|
|
312
|
+
const pos = q * (sorted.length - 1);
|
|
313
|
+
const lo = Math.floor(pos);
|
|
314
|
+
const hi = Math.ceil(pos);
|
|
315
|
+
if (lo === hi) return sorted[lo];
|
|
316
|
+
return sorted[lo] + (pos - lo) * (sorted[hi] - sorted[lo]);
|
|
317
|
+
};
|
|
318
|
+
const q1 = quantile(0.25);
|
|
319
|
+
const q2 = quantile(0.5);
|
|
320
|
+
const q3 = quantile(0.75);
|
|
321
|
+
const iqr = q3 - q1;
|
|
322
|
+
const fenceLow = q1 - 1.5 * iqr;
|
|
323
|
+
const fenceHigh = q3 + 1.5 * iqr;
|
|
324
|
+
// Whiskers extend to the most-extreme data point still inside the fences.
|
|
325
|
+
let whiskerLow = q1;
|
|
326
|
+
let whiskerHigh = q3;
|
|
327
|
+
for (const v of sorted) {
|
|
328
|
+
if (v >= fenceLow && v < whiskerLow) whiskerLow = v;
|
|
329
|
+
if (v <= fenceHigh && v > whiskerHigh) whiskerHigh = v;
|
|
330
|
+
}
|
|
331
|
+
const outliers = sorted.filter(v => v < fenceLow || v > fenceHigh);
|
|
332
|
+
return {
|
|
333
|
+
min: sorted[0],
|
|
334
|
+
max: sorted[sorted.length - 1],
|
|
335
|
+
q1,
|
|
336
|
+
q2,
|
|
337
|
+
q3,
|
|
338
|
+
whiskerLow,
|
|
339
|
+
whiskerHigh,
|
|
340
|
+
outliers
|
|
341
|
+
};
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Bin a numeric dataset into a histogram. `bins` may be:
|
|
346
|
+
* - undefined → Sturges' formula `ceil(log2(n)) + 1`
|
|
347
|
+
* - a number → that many equal-width bins
|
|
348
|
+
* - an array of edges (length k+1 → k bins)
|
|
349
|
+
*
|
|
350
|
+
* Non-finite input values are filtered. Returns bin edges and per-bin counts.
|
|
351
|
+
*/
|
|
352
|
+
export const autoBin = (values, bins) => {
|
|
353
|
+
const finite = values.filter(v => Number.isFinite(v));
|
|
354
|
+
if (finite.length === 0) return {
|
|
355
|
+
edges: [0, 1],
|
|
356
|
+
counts: [0]
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// Cap bin count so a malicious / mistyped `bins: 1e9` doesn't allocate
|
|
360
|
+
// gigabytes. 1024 is generous: 30 is Sturges' for 1e9 samples.
|
|
361
|
+
const MAX_BINS = 1024;
|
|
362
|
+
let edges;
|
|
363
|
+
if (Array.isArray(bins)) {
|
|
364
|
+
const sorted = [...bins].filter(e => Number.isFinite(e)).sort((a, b) => a - b);
|
|
365
|
+
if (sorted.length >= 2) {
|
|
366
|
+
edges = sorted;
|
|
367
|
+
} else {
|
|
368
|
+
// Empty or single-edge input → fall back to a single auto-bin span over
|
|
369
|
+
// the data so we never emit `[undefined, NaN]` edges.
|
|
370
|
+
const dmin = Math.min(...finite);
|
|
371
|
+
const dmax = Math.max(...finite);
|
|
372
|
+
edges = dmin === dmax ? [dmin - 0.5, dmin + 0.5] : [dmin, dmax];
|
|
373
|
+
}
|
|
374
|
+
} else {
|
|
375
|
+
// Single-loop min/max — `Math.min(...arr)` fails with RangeError on huge
|
|
376
|
+
// arrays (call-stack overflow) and is also O(n) anyway.
|
|
377
|
+
let min = Infinity;
|
|
378
|
+
let max = -Infinity;
|
|
379
|
+
for (const v of finite) {
|
|
380
|
+
if (v < min) min = v;
|
|
381
|
+
if (v > max) max = v;
|
|
382
|
+
}
|
|
383
|
+
const range = max - min;
|
|
384
|
+
let k;
|
|
385
|
+
if (typeof bins === "number" && bins > 0) {
|
|
386
|
+
k = Math.min(MAX_BINS, Math.floor(bins));
|
|
387
|
+
} else {
|
|
388
|
+
k = Math.max(1, Math.min(MAX_BINS, Math.ceil(Math.log2(finite.length)) + 1));
|
|
389
|
+
}
|
|
390
|
+
if (range === 0) {
|
|
391
|
+
edges = [min - 0.5, min + 0.5];
|
|
392
|
+
} else {
|
|
393
|
+
const step = range / k;
|
|
394
|
+
edges = Array.from({
|
|
395
|
+
length: k + 1
|
|
396
|
+
}, (_, i) => min + i * step);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const counts = new Array(edges.length - 1).fill(0);
|
|
400
|
+
for (const v of finite) {
|
|
401
|
+
// Right-open intervals [e_i, e_{i+1}); last bin closed-closed.
|
|
402
|
+
let bin = -1;
|
|
403
|
+
for (let i = 0; i < counts.length; i++) {
|
|
404
|
+
const isLast = i === counts.length - 1;
|
|
405
|
+
if (v >= edges[i] && (isLast ? v <= edges[i + 1] : v < edges[i + 1])) {
|
|
406
|
+
bin = i;
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (bin >= 0) counts[bin] += 1;
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
edges,
|
|
414
|
+
counts
|
|
415
|
+
};
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
// ==========================
|
|
419
|
+
// SVG SKELETON
|
|
420
|
+
// ==========================
|
|
421
|
+
|
|
422
|
+
export const computePlotArea = (width, height, padding, hasXAxisLabel, hasYAxisLabel, headerHeight = 0, legendHeight = 0) => {
|
|
423
|
+
// Reserve extra space when axis labels are present.
|
|
424
|
+
const extraBottom = hasXAxisLabel ? 14 : 0;
|
|
425
|
+
const extraLeft = hasYAxisLabel ? 14 : 0;
|
|
426
|
+
return {
|
|
427
|
+
x0: padding.left + extraLeft,
|
|
428
|
+
y0: padding.top + headerHeight,
|
|
429
|
+
x1: width - padding.right,
|
|
430
|
+
y1: height - padding.bottom - extraBottom - legendHeight
|
|
431
|
+
};
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Render an optional centered header (title + subtitle) at the top of an
|
|
436
|
+
* SVG. Returns the SVG fragment plus the pixel height it consumed so the
|
|
437
|
+
* caller can shift the plot area down accordingly.
|
|
438
|
+
*/
|
|
439
|
+
export const renderHeader = (width, title, subtitle) => {
|
|
440
|
+
if (!title && !subtitle) return {
|
|
441
|
+
svg: "",
|
|
442
|
+
height: 0
|
|
443
|
+
};
|
|
444
|
+
const cx = width / 2;
|
|
445
|
+
const parts = [];
|
|
446
|
+
let y = 16;
|
|
447
|
+
if (title) {
|
|
448
|
+
parts.push(`<text class="stdlib-chart-title" x="${fmt(cx)}" y="${fmt(y)}" text-anchor="middle">${escapeXml(title)}</text>`);
|
|
449
|
+
y += 14;
|
|
450
|
+
}
|
|
451
|
+
if (subtitle) {
|
|
452
|
+
parts.push(`<text class="stdlib-chart-subtitle" x="${fmt(cx)}" y="${fmt(y)}" text-anchor="middle">${escapeXml(subtitle)}</text>`);
|
|
453
|
+
y += 6;
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
svg: parts.join(""),
|
|
457
|
+
height: y
|
|
458
|
+
};
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Render reference lines (horizontal or vertical thresholds) within the plot
|
|
463
|
+
* area. Out-of-domain values are silently skipped. Honors per-axis scale
|
|
464
|
+
* (linear or log).
|
|
465
|
+
*/
|
|
466
|
+
export const renderReferenceLines = (refs, xAxis, yAxis, area) => {
|
|
467
|
+
const xMap = pickMapper(xAxis.scale);
|
|
468
|
+
const yMap = pickMapper(yAxis.scale);
|
|
469
|
+
const parts = [];
|
|
470
|
+
for (const ref of refs) {
|
|
471
|
+
if (!Number.isFinite(ref.value)) continue;
|
|
472
|
+
const axis = ref.axis ?? "y";
|
|
473
|
+
if (axis === "y") {
|
|
474
|
+
if (ref.value < yAxis.domain[0] || ref.value > yAxis.domain[1]) continue;
|
|
475
|
+
const y = yMap(ref.value, yAxis.domain, [area.y1, area.y0]);
|
|
476
|
+
parts.push(`<line class="stdlib-chart-reference"${referenceLineAttributes(ref)} x1="${fmt(area.x0)}" y1="${fmt(y)}" x2="${fmt(area.x1)}" y2="${fmt(y)}"/>`);
|
|
477
|
+
if (ref.label) {
|
|
478
|
+
parts.push(`<text class="stdlib-chart-reference-label"${referenceLineAttributes(ref, true)} x="${fmt(area.x1 - 4)}" y="${fmt(y - 4)}" text-anchor="end">${escapeXml(ref.label)}</text>`);
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
if (ref.value < xAxis.domain[0] || ref.value > xAxis.domain[1]) continue;
|
|
482
|
+
const x = xMap(ref.value, xAxis.domain, [area.x0, area.x1]);
|
|
483
|
+
parts.push(`<line class="stdlib-chart-reference"${referenceLineAttributes(ref)} x1="${fmt(x)}" y1="${fmt(area.y0)}" x2="${fmt(x)}" y2="${fmt(area.y1)}"/>`);
|
|
484
|
+
if (ref.label) {
|
|
485
|
+
parts.push(`<text class="stdlib-chart-reference-label"${referenceLineAttributes(ref, true)} x="${fmt(x + 4)}" y="${fmt(area.y0 + 12)}">${escapeXml(ref.label)}</text>`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return parts.join("");
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Resolve a point's effective error extents into `[low, high]` magnitudes
|
|
494
|
+
* (relative deltas, both ≥ 0). Asymmetric `errYHigh`/`errYLow` take precedence
|
|
495
|
+
* over symmetric `errY`. Returns null when no error info is set.
|
|
496
|
+
*/
|
|
497
|
+
export const resolveErrorY = p => {
|
|
498
|
+
const high = p.errYHigh ?? p.errY;
|
|
499
|
+
const low = p.errYLow ?? p.errY;
|
|
500
|
+
if (high === undefined && low === undefined) return null;
|
|
501
|
+
return [Number.isFinite(low) ? Math.abs(low) : 0, Number.isFinite(high) ? Math.abs(high) : 0];
|
|
502
|
+
};
|
|
503
|
+
const resolveErrorX = p => {
|
|
504
|
+
const high = p.errXHigh ?? p.errX;
|
|
505
|
+
const low = p.errXLow ?? p.errX;
|
|
506
|
+
if (high === undefined && low === undefined) return null;
|
|
507
|
+
return [Number.isFinite(low) ? Math.abs(low) : 0, Number.isFinite(high) ? Math.abs(high) : 0];
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
/** Build the SVG fragment for the per-point error bars across all series. */
|
|
511
|
+
export const renderErrorBars = (series, xAxis, yAxis, area) => {
|
|
512
|
+
const xMap = pickMapper(xAxis.scale);
|
|
513
|
+
const yMap = pickMapper(yAxis.scale);
|
|
514
|
+
const CAP = 4; // half-width of error bar caps in pixels
|
|
515
|
+
const parts = [];
|
|
516
|
+
for (const s of series) {
|
|
517
|
+
for (const p of s.data) {
|
|
518
|
+
if (!Number.isFinite(p.x) || !Number.isFinite(p.y)) continue;
|
|
519
|
+
const cx = xMap(p.x, xAxis.domain, [area.x0, area.x1]);
|
|
520
|
+
const cy = yMap(p.y, yAxis.domain, [area.y1, area.y0]);
|
|
521
|
+
const yErr = resolveErrorY(p);
|
|
522
|
+
if (yErr) {
|
|
523
|
+
const [low, high] = yErr;
|
|
524
|
+
const yHi = yMap(p.y + high, yAxis.domain, [area.y1, area.y0]);
|
|
525
|
+
const yLo = yMap(p.y - low, yAxis.domain, [area.y1, area.y0]);
|
|
526
|
+
parts.push(`<line class="stdlib-chart-errorbar" x1="${fmt(cx)}" y1="${fmt(yHi)}" x2="${fmt(cx)}" y2="${fmt(yLo)}"/>`);
|
|
527
|
+
parts.push(`<line class="stdlib-chart-errorbar" x1="${fmt(cx - CAP)}" y1="${fmt(yHi)}" x2="${fmt(cx + CAP)}" y2="${fmt(yHi)}"/>`);
|
|
528
|
+
parts.push(`<line class="stdlib-chart-errorbar" x1="${fmt(cx - CAP)}" y1="${fmt(yLo)}" x2="${fmt(cx + CAP)}" y2="${fmt(yLo)}"/>`);
|
|
529
|
+
// Mark we need cy to match data point — ensure we use it (silences unused warnings if any).
|
|
530
|
+
void cy;
|
|
531
|
+
}
|
|
532
|
+
const xErr = resolveErrorX(p);
|
|
533
|
+
if (xErr) {
|
|
534
|
+
const [low, high] = xErr;
|
|
535
|
+
const xHi = xMap(p.x + high, xAxis.domain, [area.x0, area.x1]);
|
|
536
|
+
const xLo = xMap(p.x - low, xAxis.domain, [area.x0, area.x1]);
|
|
537
|
+
parts.push(`<line class="stdlib-chart-errorbar" x1="${fmt(xLo)}" y1="${fmt(cy)}" x2="${fmt(xHi)}" y2="${fmt(cy)}"/>`);
|
|
538
|
+
parts.push(`<line class="stdlib-chart-errorbar" x1="${fmt(xLo)}" y1="${fmt(cy - CAP)}" x2="${fmt(xLo)}" y2="${fmt(cy + CAP)}"/>`);
|
|
539
|
+
parts.push(`<line class="stdlib-chart-errorbar" x1="${fmt(xHi)}" y1="${fmt(cy - CAP)}" x2="${fmt(xHi)}" y2="${fmt(cy + CAP)}"/>`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return parts.join("");
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
/** Collect all values relevant for axis-domain computation, including error
|
|
547
|
+
* extents so error bars don't get clipped. */
|
|
548
|
+
export const collectAxisValues = (series, axis) => {
|
|
549
|
+
const out = [];
|
|
550
|
+
for (const s of series) {
|
|
551
|
+
for (const p of s.data) {
|
|
552
|
+
const v = axis === "x" ? p.x : p.y;
|
|
553
|
+
if (!Number.isFinite(v)) continue;
|
|
554
|
+
out.push(v);
|
|
555
|
+
const err = axis === "x" ? resolveErrorX(p) : resolveErrorY(p);
|
|
556
|
+
if (err) {
|
|
557
|
+
out.push(v - err[0]);
|
|
558
|
+
out.push(v + err[1]);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return out;
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Render a single-row centered legend at the bottom of the SVG. Returns the
|
|
567
|
+
* SVG fragment plus the height it consumed so callers can shift the plot
|
|
568
|
+
* area up accordingly. Empty input yields height 0.
|
|
569
|
+
*/
|
|
570
|
+
export const renderLegend = (items, width, yTop) => {
|
|
571
|
+
if (items.length === 0) return {
|
|
572
|
+
svg: "",
|
|
573
|
+
height: 0
|
|
574
|
+
};
|
|
575
|
+
const ROW_HEIGHT = 20;
|
|
576
|
+
const ITEM_GAP = 16;
|
|
577
|
+
const SWATCH = 8;
|
|
578
|
+
const SWATCH_GAP = 4;
|
|
579
|
+
const SIDE_PADDING = 4;
|
|
580
|
+
// Approximate text width per character at 11px — good enough for centering.
|
|
581
|
+
const charWidth = 6.5;
|
|
582
|
+
const itemWidths = items.map(it => SWATCH + SWATCH_GAP + Math.max(it.label.length, 1) * charWidth);
|
|
583
|
+
|
|
584
|
+
// Greedy line-break: pack items into rows so each row fits inside the
|
|
585
|
+
// available width. Single-row legends (≤ a few short labels) still render
|
|
586
|
+
// on one line — no visual change for the line/scatter/bar callers that
|
|
587
|
+
// typically have only 2-4 series. Pie/donut with long "Label (XX%)" entries
|
|
588
|
+
// and 5+ slices wrap to multiple rows instead of overflowing the viewBox.
|
|
589
|
+
const availableWidth = Math.max(40, width - 2 * SIDE_PADDING);
|
|
590
|
+
const rows = [];
|
|
591
|
+
let cur = [];
|
|
592
|
+
let curW = 0;
|
|
593
|
+
items.forEach((item, i) => {
|
|
594
|
+
const w = itemWidths[i];
|
|
595
|
+
const projected = cur.length === 0 ? w : curW + ITEM_GAP + w;
|
|
596
|
+
if (cur.length > 0 && projected > availableWidth) {
|
|
597
|
+
rows.push({
|
|
598
|
+
items: cur,
|
|
599
|
+
widths: cur.map((_, j) => itemWidths[i - cur.length + j]),
|
|
600
|
+
totalWidth: curW
|
|
601
|
+
});
|
|
602
|
+
cur = [item];
|
|
603
|
+
curW = w;
|
|
604
|
+
} else {
|
|
605
|
+
cur = [...cur, item];
|
|
606
|
+
curW = projected;
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
if (cur.length > 0) {
|
|
610
|
+
rows.push({
|
|
611
|
+
items: cur,
|
|
612
|
+
widths: cur.map((_, j) => itemWidths[items.length - cur.length + j]),
|
|
613
|
+
totalWidth: curW
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
const parts = [`<g class="stdlib-chart-legend">`];
|
|
617
|
+
rows.forEach((row, rowIdx) => {
|
|
618
|
+
let cursor = Math.max(SIDE_PADDING, (width - row.totalWidth) / 2);
|
|
619
|
+
const baseY = yTop + ROW_HEIGHT / 2 + rowIdx * ROW_HEIGHT;
|
|
620
|
+
row.items.forEach((item, i) => {
|
|
621
|
+
parts.push(`<g class="stdlib-chart-legend-item stdlib-chart-series-${item.seriesIndex % 8}">`);
|
|
622
|
+
parts.push(`<rect class="stdlib-chart-legend-swatch" x="${fmt(cursor)}" y="${fmt(baseY - SWATCH / 2)}" width="${SWATCH}" height="${SWATCH}" rx="1"/>`);
|
|
623
|
+
parts.push(`<text class="stdlib-chart-legend-label" x="${fmt(cursor + SWATCH + SWATCH_GAP)}" y="${fmt(baseY)}" dominant-baseline="middle">${escapeXml(item.label)}</text>`);
|
|
624
|
+
parts.push(`</g>`);
|
|
625
|
+
cursor += row.widths[i] + ITEM_GAP;
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
parts.push(`</g>`);
|
|
629
|
+
return {
|
|
630
|
+
svg: parts.join(""),
|
|
631
|
+
height: rows.length * ROW_HEIGHT
|
|
632
|
+
};
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
/** Measure the total height a legend with `items` will consume in `width`,
|
|
636
|
+
* without actually rendering. Used by chart functions to reserve vertical
|
|
637
|
+
* space before computing the plot area. */
|
|
638
|
+
export const measureLegendHeight = (items, width) => {
|
|
639
|
+
if (items.length === 0) return 0;
|
|
640
|
+
const ROW_HEIGHT = 20;
|
|
641
|
+
const ITEM_GAP = 16;
|
|
642
|
+
const SWATCH = 8;
|
|
643
|
+
const SWATCH_GAP = 4;
|
|
644
|
+
const charWidth = 6.5;
|
|
645
|
+
const availableWidth = Math.max(40, width - 8);
|
|
646
|
+
let curW = 0;
|
|
647
|
+
let rows = 1;
|
|
648
|
+
items.forEach((it, i) => {
|
|
649
|
+
const w = SWATCH + SWATCH_GAP + Math.max(it.label.length, 1) * charWidth;
|
|
650
|
+
const projected = i === 0 ? w : curW + ITEM_GAP + w;
|
|
651
|
+
if (i > 0 && projected > availableWidth) {
|
|
652
|
+
rows++;
|
|
653
|
+
curW = w;
|
|
654
|
+
} else {
|
|
655
|
+
curW = projected;
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
return rows * ROW_HEIGHT;
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
/** Wrap chart body in a root `<svg>` with embedded default styles. */
|
|
662
|
+
export const svgRoot = (opts, body) => {
|
|
663
|
+
const cls = opts.className ? `stdlib-chart ${escapeXml(opts.className)}` : "stdlib-chart";
|
|
664
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${opts.width} ${opts.height}" class="${cls}"><style>${DEFAULT_STYLES}</style>${body}</svg>`;
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
// ==========================
|
|
668
|
+
// GEOMETRY HELPERS
|
|
669
|
+
// ==========================
|
|
670
|
+
|
|
671
|
+
/** Format an SVG path `d` attribute for a polyline through `points`. */
|
|
672
|
+
export const linePathD = points => {
|
|
673
|
+
if (points.length === 0) return "";
|
|
674
|
+
let d = `M ${fmt(points[0].x)} ${fmt(points[0].y)}`;
|
|
675
|
+
for (let i = 1; i < points.length; i++) {
|
|
676
|
+
d += ` L ${fmt(points[i].x)} ${fmt(points[i].y)}`;
|
|
677
|
+
}
|
|
678
|
+
return d;
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Format an SVG path `d` for a smooth curve through `points` using cubic
|
|
683
|
+
* Bezier segments with Catmull-Rom-derived vertical control points.
|
|
684
|
+
*
|
|
685
|
+
* Vertical control points use neighboring `points[i-1]` and `points[i+2]`
|
|
686
|
+
* (reflecting the end points at boundaries) with the standard Catmull-Rom
|
|
687
|
+
* tension factor of 1/6. Horizontal control points stay at the segment's
|
|
688
|
+
* thirds, so x progresses linearly and never doubles back when point spacing
|
|
689
|
+
* is uneven.
|
|
690
|
+
*/
|
|
691
|
+
export const smoothPathD = points => {
|
|
692
|
+
const n = points.length;
|
|
693
|
+
if (n === 0) return "";
|
|
694
|
+
if (n === 1) return `M ${fmt(points[0].x)} ${fmt(points[0].y)}`;
|
|
695
|
+
if (n === 2) return linePathD(points);
|
|
696
|
+
let d = `M ${fmt(points[0].x)} ${fmt(points[0].y)}`;
|
|
697
|
+
for (let i = 0; i < n - 1; i++) {
|
|
698
|
+
const p0 = i > 0 ? points[i - 1] : points[i];
|
|
699
|
+
const p1 = points[i];
|
|
700
|
+
const p2 = points[i + 1];
|
|
701
|
+
const p3 = i + 2 < n ? points[i + 2] : points[i + 1];
|
|
702
|
+
const dx = p2.x - p1.x;
|
|
703
|
+
const cp1x = p1.x + dx / 3;
|
|
704
|
+
const cp1y = p1.y + (p2.y - p0.y) / 6;
|
|
705
|
+
const cp2x = p2.x - dx / 3;
|
|
706
|
+
const cp2y = p2.y - (p3.y - p1.y) / 6;
|
|
707
|
+
d += ` C ${fmt(cp1x)} ${fmt(cp1y)} ${fmt(cp2x)} ${fmt(cp2y)} ${fmt(p2.x)} ${fmt(p2.y)}`;
|
|
708
|
+
}
|
|
709
|
+
return d;
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Build an SVG path `d` attribute for a pie slice (or donut sector).
|
|
714
|
+
*
|
|
715
|
+
* Angles are in radians, measured from the +X axis. Slices grow clockwise,
|
|
716
|
+
* which in SVG's y-down coordinate space means `a1 > a0` produces a clockwise
|
|
717
|
+
* arc when drawn with the large-arc and sweep flags set appropriately.
|
|
718
|
+
*
|
|
719
|
+
* Full-circle (a1 - a0 ≈ 2π) is split into two 180° arcs because a single
|
|
720
|
+
* `A` command can't draw a complete circle unambiguously.
|
|
721
|
+
*/
|
|
722
|
+
export const arcPathD = (cx, cy, r, a0, a1, innerR = 0) => {
|
|
723
|
+
const sweep = a1 - a0;
|
|
724
|
+
// Full circle: split into two halves to avoid degenerate `A` command.
|
|
725
|
+
if (sweep >= Math.PI * 2 - 1e-9) {
|
|
726
|
+
const aMid = a0 + Math.PI;
|
|
727
|
+
if (innerR <= 0) {
|
|
728
|
+
const ox0 = cx + r * Math.cos(a0),
|
|
729
|
+
oy0 = cy + r * Math.sin(a0);
|
|
730
|
+
const oxM = cx + r * Math.cos(aMid),
|
|
731
|
+
oyM = cy + r * Math.sin(aMid);
|
|
732
|
+
return `M ${fmt(ox0)} ${fmt(oy0)} A ${fmt(r)} ${fmt(r)} 0 0 1 ${fmt(oxM)} ${fmt(oyM)} A ${fmt(r)} ${fmt(r)} 0 0 1 ${fmt(ox0)} ${fmt(oy0)} Z`;
|
|
733
|
+
}
|
|
734
|
+
const ox0 = cx + r * Math.cos(a0),
|
|
735
|
+
oy0 = cy + r * Math.sin(a0);
|
|
736
|
+
const oxM = cx + r * Math.cos(aMid),
|
|
737
|
+
oyM = cy + r * Math.sin(aMid);
|
|
738
|
+
const ix0 = cx + innerR * Math.cos(a0),
|
|
739
|
+
iy0 = cy + innerR * Math.sin(a0);
|
|
740
|
+
const ixM = cx + innerR * Math.cos(aMid),
|
|
741
|
+
iyM = cy + innerR * Math.sin(aMid);
|
|
742
|
+
// Outer ring CW, inner ring CCW (using even-odd fill rule via SVG default).
|
|
743
|
+
return `M ${fmt(ox0)} ${fmt(oy0)} A ${fmt(r)} ${fmt(r)} 0 0 1 ${fmt(oxM)} ${fmt(oyM)} A ${fmt(r)} ${fmt(r)} 0 0 1 ${fmt(ox0)} ${fmt(oy0)} Z` + ` M ${fmt(ix0)} ${fmt(iy0)} A ${fmt(innerR)} ${fmt(innerR)} 0 0 0 ${fmt(ixM)} ${fmt(iyM)} A ${fmt(innerR)} ${fmt(innerR)} 0 0 0 ${fmt(ix0)} ${fmt(iy0)} Z`;
|
|
744
|
+
}
|
|
745
|
+
const large = sweep > Math.PI ? 1 : 0;
|
|
746
|
+
const ox0 = cx + r * Math.cos(a0),
|
|
747
|
+
oy0 = cy + r * Math.sin(a0);
|
|
748
|
+
const ox1 = cx + r * Math.cos(a1),
|
|
749
|
+
oy1 = cy + r * Math.sin(a1);
|
|
750
|
+
if (innerR <= 0) {
|
|
751
|
+
return `M ${fmt(cx)} ${fmt(cy)} L ${fmt(ox0)} ${fmt(oy0)} A ${fmt(r)} ${fmt(r)} 0 ${large} 1 ${fmt(ox1)} ${fmt(oy1)} Z`;
|
|
752
|
+
}
|
|
753
|
+
const ix0 = cx + innerR * Math.cos(a0),
|
|
754
|
+
iy0 = cy + innerR * Math.sin(a0);
|
|
755
|
+
const ix1 = cx + innerR * Math.cos(a1),
|
|
756
|
+
iy1 = cy + innerR * Math.sin(a1);
|
|
757
|
+
return `M ${fmt(ox0)} ${fmt(oy0)} A ${fmt(r)} ${fmt(r)} 0 ${large} 1 ${fmt(ox1)} ${fmt(oy1)} L ${fmt(ix1)} ${fmt(iy1)} A ${fmt(innerR)} ${fmt(innerR)} 0 ${large} 0 ${fmt(ix0)} ${fmt(iy0)} Z`;
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* SVG path `d` for a marker shape centered at `(cx, cy)` with circumradius `r`.
|
|
762
|
+
*
|
|
763
|
+
* The shapes are sized so a typical visual radius matches `r`:
|
|
764
|
+
* - circle: full radius
|
|
765
|
+
* - square: side = sqrt(2)·r so the diagonal equals 2·r (visually balanced
|
|
766
|
+
* next to a circle of the same r)
|
|
767
|
+
* - triangle: equilateral pointing up, circumradius r
|
|
768
|
+
* - diamond: square rotated 45°, half-diagonal = r
|
|
769
|
+
* - plus: cross-shape at thickness r·0.5
|
|
770
|
+
* - cross: X-shape at thickness r·0.4
|
|
771
|
+
*/
|
|
772
|
+
export const markerPath = (shape, cx, cy, r) => {
|
|
773
|
+
switch (shape) {
|
|
774
|
+
case "circle":
|
|
775
|
+
// Single arc command pair to draw a closed circle.
|
|
776
|
+
return `M ${fmt(cx - r)} ${fmt(cy)} A ${fmt(r)} ${fmt(r)} 0 1 0 ${fmt(cx + r)} ${fmt(cy)} A ${fmt(r)} ${fmt(r)} 0 1 0 ${fmt(cx - r)} ${fmt(cy)} Z`;
|
|
777
|
+
case "square":
|
|
778
|
+
{
|
|
779
|
+
const s = r * Math.SQRT2 / 2; // half-side
|
|
780
|
+
return `M ${fmt(cx - s)} ${fmt(cy - s)} L ${fmt(cx + s)} ${fmt(cy - s)} L ${fmt(cx + s)} ${fmt(cy + s)} L ${fmt(cx - s)} ${fmt(cy + s)} Z`;
|
|
781
|
+
}
|
|
782
|
+
case "triangle":
|
|
783
|
+
{
|
|
784
|
+
// Equilateral pointing up. Circumradius r.
|
|
785
|
+
const h = r * Math.sin(Math.PI / 3) * 1.5; // altitude from base to apex
|
|
786
|
+
return `M ${fmt(cx)} ${fmt(cy - r)} L ${fmt(cx + h * Math.cos(Math.PI / 6))} ${fmt(cy + r * 0.5)} L ${fmt(cx - h * Math.cos(Math.PI / 6))} ${fmt(cy + r * 0.5)} Z`;
|
|
787
|
+
}
|
|
788
|
+
case "diamond":
|
|
789
|
+
return `M ${fmt(cx)} ${fmt(cy - r)} L ${fmt(cx + r)} ${fmt(cy)} L ${fmt(cx)} ${fmt(cy + r)} L ${fmt(cx - r)} ${fmt(cy)} Z`;
|
|
790
|
+
case "plus":
|
|
791
|
+
{
|
|
792
|
+
const t = r * 0.32; // half-thickness
|
|
793
|
+
return `M ${fmt(cx - r)} ${fmt(cy - t)} L ${fmt(cx - t)} ${fmt(cy - t)} L ${fmt(cx - t)} ${fmt(cy - r)} L ${fmt(cx + t)} ${fmt(cy - r)} L ${fmt(cx + t)} ${fmt(cy - t)} L ${fmt(cx + r)} ${fmt(cy - t)} L ${fmt(cx + r)} ${fmt(cy + t)} L ${fmt(cx + t)} ${fmt(cy + t)} L ${fmt(cx + t)} ${fmt(cy + r)} L ${fmt(cx - t)} ${fmt(cy + r)} L ${fmt(cx - t)} ${fmt(cy + t)} L ${fmt(cx - r)} ${fmt(cy + t)} Z`;
|
|
794
|
+
}
|
|
795
|
+
case "cross":
|
|
796
|
+
{
|
|
797
|
+
// X shape — same as plus rotated 45°. Build by composing two oriented bars.
|
|
798
|
+
const a = r * 0.7;
|
|
799
|
+
const t = r * 0.28;
|
|
800
|
+
return `M ${fmt(cx - a + t)} ${fmt(cy - a - t)} L ${fmt(cx - a - t)} ${fmt(cy - a + t)} L ${fmt(cx + a - t)} ${fmt(cy + a + t)} L ${fmt(cx + a + t)} ${fmt(cy + a - t)} Z` + ` M ${fmt(cx + a - t)} ${fmt(cy - a - t)} L ${fmt(cx + a + t)} ${fmt(cy - a + t)} L ${fmt(cx - a + t)} ${fmt(cy + a + t)} L ${fmt(cx - a - t)} ${fmt(cy + a - t)} Z`;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* SVG path `d` for a step (stair) line. `mode` controls where the vertical
|
|
807
|
+
* step happens relative to the data points:
|
|
808
|
+
* - `before` — vertical step at the new x BEFORE the value updates (state
|
|
809
|
+
* machine "Mealy" style: change at input)
|
|
810
|
+
* - `after` — value updates at the data point's x and persists until next x
|
|
811
|
+
* - `middle` — vertical step at the midpoint between consecutive x-values
|
|
812
|
+
*/
|
|
813
|
+
export const stepPathD = (points, mode) => {
|
|
814
|
+
if (points.length === 0) return "";
|
|
815
|
+
if (points.length === 1) return `M ${fmt(points[0].x)} ${fmt(points[0].y)}`;
|
|
816
|
+
let d = `M ${fmt(points[0].x)} ${fmt(points[0].y)}`;
|
|
817
|
+
for (let i = 1; i < points.length; i++) {
|
|
818
|
+
const p = points[i];
|
|
819
|
+
const prev = points[i - 1];
|
|
820
|
+
if (mode === "before") {
|
|
821
|
+
// step at next-x: extend horizontally to new x at OLD y, then jump to new y.
|
|
822
|
+
d += ` L ${fmt(p.x)} ${fmt(prev.y)} L ${fmt(p.x)} ${fmt(p.y)}`;
|
|
823
|
+
} else if (mode === "after") {
|
|
824
|
+
// step at current-x: jump to new y immediately, persist until next x.
|
|
825
|
+
d += ` L ${fmt(prev.x)} ${fmt(p.y)} L ${fmt(p.x)} ${fmt(p.y)}`;
|
|
826
|
+
} else {
|
|
827
|
+
// middle: step at midpoint of x.
|
|
828
|
+
const mx = (prev.x + p.x) / 2;
|
|
829
|
+
d += ` L ${fmt(mx)} ${fmt(prev.y)} L ${fmt(mx)} ${fmt(p.y)} L ${fmt(p.x)} ${fmt(p.y)}`;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return d;
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
/** Format a number for SVG attributes — short, no scientific notation. */
|
|
836
|
+
export const fmt = n => {
|
|
837
|
+
if (!Number.isFinite(n)) return "0";
|
|
838
|
+
// 2 decimals is enough for sub-pixel precision at typical sizes.
|
|
839
|
+
const rounded = Math.round(n * 100) / 100;
|
|
840
|
+
return Number.isInteger(rounded) ? `${rounded}` : `${rounded}`;
|
|
841
|
+
};
|
|
842
|
+
export const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
843
|
+
export const normalizeMinMax = (min, max) => {
|
|
844
|
+
const lo = Number.isFinite(min) ? min : 0;
|
|
845
|
+
const hi = Number.isFinite(max) ? max : 100;
|
|
846
|
+
return lo === hi ? [lo, lo + 1] : lo < hi ? [lo, hi] : [hi, lo];
|
|
847
|
+
};
|
|
848
|
+
export const formatNumberValue = (value, format, unit = "") => {
|
|
849
|
+
if (!Number.isFinite(value)) return "";
|
|
850
|
+
return `${format ? format(value) : fmt(value)}${unit}`;
|
|
851
|
+
};
|
|
852
|
+
export const thresholdFillAttr = color => color ? ` fill="${escapeXml(color)}"` : "";
|
|
853
|
+
export const thresholdStrokeAttr = color => color ? ` stroke="${escapeXml(color)}"` : "";
|
|
854
|
+
|
|
855
|
+
// ==========================
|
|
856
|
+
// AXIS RENDERERS
|
|
857
|
+
// ==========================
|
|
858
|
+
|
|
859
|
+
export const pickMapper = scale => scale === "log" ? mapLog : mapRange;
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Whether a value is renderable on the given axis. Log axes reject zero and
|
|
863
|
+
* negative values (since `log(<=0)` is undefined). Used to filter data
|
|
864
|
+
* points before rendering so we don't emit huge off-plot SVG coordinates.
|
|
865
|
+
*/
|
|
866
|
+
export const isOnAxis = (axis, v) => Number.isFinite(v) && (axis.scale !== "log" || v > 0);
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Compute the domain, major ticks, and (optional) minor ticks for an axis,
|
|
870
|
+
* choosing linear or log scale per `axisOpts`. Filters non-positive values
|
|
871
|
+
* out under log scale and falls back to `[1, 10]` if none remain.
|
|
872
|
+
*/
|
|
873
|
+
export const computeAxisScale = (values, axisOpts, fallbackTicks = DEFAULT_TICKS) => {
|
|
874
|
+
const scale = axisOpts?.scale === "log" ? "log" : "linear";
|
|
875
|
+
if (axisOpts?.domain) {
|
|
876
|
+
const [lo, hi] = axisOpts.domain;
|
|
877
|
+
if (!Number.isFinite(lo) || !Number.isFinite(hi) || lo >= hi || !Number.isFinite(hi - lo) || scale === "log" && lo <= 0) {
|
|
878
|
+
throw new RangeError("Chart axis domain must have finite increasing bounds (positive for log scales)");
|
|
879
|
+
}
|
|
880
|
+
if (values.some(v => Number.isFinite(v) && (scale !== "log" || v > 0) && (v < lo || v > hi))) {
|
|
881
|
+
throw new RangeError("Chart axis domain must contain all plotted values and bounds");
|
|
882
|
+
}
|
|
883
|
+
const ticks = [];
|
|
884
|
+
if (scale === "log") {
|
|
885
|
+
for (let exponent = Math.floor(Math.log10(lo)); exponent <= Math.ceil(Math.log10(hi)); exponent++) {
|
|
886
|
+
const value = 10 ** exponent;
|
|
887
|
+
if (value > lo && value < hi) ticks.push(value);
|
|
888
|
+
}
|
|
889
|
+
} else {
|
|
890
|
+
const step = niceStep(hi - lo, axisOpts.ticks ?? fallbackTicks);
|
|
891
|
+
// At extreme magnitudes the step may be smaller than one representable
|
|
892
|
+
// increment. Keep endpoint ticks instead of entering a non-advancing loop.
|
|
893
|
+
if (step > 0 && Number.isFinite(step) && (hi - lo) / step <= MAX_AXIS_TICKS) {
|
|
894
|
+
for (let value = Math.ceil(lo / step) * step; value < hi;) {
|
|
895
|
+
if (value > lo && (!axisOpts.insetEndpointLabels || value - lo >= step && hi - value >= step)) ticks.push(value);
|
|
896
|
+
const next = value + step;
|
|
897
|
+
if (next <= value) break;
|
|
898
|
+
value = next;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return {
|
|
903
|
+
domain: [lo, hi],
|
|
904
|
+
ticks: [...new Set([lo, ...ticks.filter(v => v > lo && v < hi), hi])],
|
|
905
|
+
minorTicks: [],
|
|
906
|
+
scale
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
if (scale === "log") {
|
|
910
|
+
const positive = values.filter(v => Number.isFinite(v) && v > 0);
|
|
911
|
+
if (positive.length === 0) {
|
|
912
|
+
return {
|
|
913
|
+
domain: [1, 10],
|
|
914
|
+
ticks: [1, 10],
|
|
915
|
+
minorTicks: [],
|
|
916
|
+
scale
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
const min = Math.min(...positive);
|
|
920
|
+
const max = Math.max(...positive);
|
|
921
|
+
const r = niceLogTicks(min, max);
|
|
922
|
+
const minorTicks = [];
|
|
923
|
+
if (axisOpts?.minorTicks) {
|
|
924
|
+
for (let i = 0; i < r.ticks.length - 1; i++) {
|
|
925
|
+
const decade = r.ticks[i];
|
|
926
|
+
for (let m = 2; m <= 9; m++) minorTicks.push(decade * m);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return {
|
|
930
|
+
domain: r.domain,
|
|
931
|
+
ticks: r.ticks,
|
|
932
|
+
minorTicks,
|
|
933
|
+
scale
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
const [minRaw, maxRaw] = computeDomain(values);
|
|
937
|
+
const step = niceStep(maxRaw - minRaw, axisOpts?.ticks ?? fallbackTicks);
|
|
938
|
+
const r = extendDomainToNice(minRaw, maxRaw, step);
|
|
939
|
+
const minorTicks = [];
|
|
940
|
+
if (axisOpts?.minorTicks && r.ticks.length > 1) {
|
|
941
|
+
const SUBDIVISIONS = 5;
|
|
942
|
+
for (let i = 0; i < r.ticks.length - 1; i++) {
|
|
943
|
+
const a = r.ticks[i];
|
|
944
|
+
const b = r.ticks[i + 1];
|
|
945
|
+
const sub = (b - a) / SUBDIVISIONS;
|
|
946
|
+
for (let m = 1; m < SUBDIVISIONS; m++) minorTicks.push(a + sub * m);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return {
|
|
950
|
+
domain: r.domain,
|
|
951
|
+
ticks: r.ticks,
|
|
952
|
+
minorTicks,
|
|
953
|
+
scale
|
|
954
|
+
};
|
|
955
|
+
};
|
|
956
|
+
export const renderYAxis = (axis, area, opts) => {
|
|
957
|
+
const format = opts?.format ?? (v => String(v));
|
|
958
|
+
const map = pickMapper(axis.scale);
|
|
959
|
+
const parts = [];
|
|
960
|
+
for (const t of axis.ticks) {
|
|
961
|
+
const y = map(t, axis.domain, [area.y1, area.y0]);
|
|
962
|
+
parts.push(`<line class="stdlib-chart-grid" x1="${fmt(area.x0)}" y1="${fmt(y)}" x2="${fmt(area.x1)}" y2="${fmt(y)}"/>`);
|
|
963
|
+
parts.push(`<text class="stdlib-chart-tick-label" x="${fmt(area.x0 - 6)}" y="${fmt(y + 3)}" text-anchor="end">${escapeXml(format(t))}</text>`);
|
|
964
|
+
}
|
|
965
|
+
for (const mt of axis.minorTicks) {
|
|
966
|
+
const y = map(mt, axis.domain, [area.y1, area.y0]);
|
|
967
|
+
parts.push(`<line class="stdlib-chart-minor-tick" x1="${fmt(area.x0 - 3)}" y1="${fmt(y)}" x2="${fmt(area.x0)}" y2="${fmt(y)}"/>`);
|
|
968
|
+
}
|
|
969
|
+
parts.push(`<line class="stdlib-chart-axis" x1="${fmt(area.x0)}" y1="${fmt(area.y0)}" x2="${fmt(area.x0)}" y2="${fmt(area.y1)}"/>`);
|
|
970
|
+
if (opts?.label) {
|
|
971
|
+
const cx = 12;
|
|
972
|
+
const cy = (area.y0 + area.y1) / 2;
|
|
973
|
+
parts.push(`<text class="stdlib-chart-axis-label" x="${fmt(cx)}" y="${fmt(cy)}" text-anchor="middle" transform="rotate(-90 ${fmt(cx)} ${fmt(cy)})">${escapeXml(opts.label)}</text>`);
|
|
974
|
+
}
|
|
975
|
+
return parts.join("");
|
|
976
|
+
};
|
|
977
|
+
export const renderXAxisNumeric = (axis, area, height, opts) => {
|
|
978
|
+
const format = opts?.format ?? (v => String(v));
|
|
979
|
+
const map = pickMapper(axis.scale);
|
|
980
|
+
const parts = [];
|
|
981
|
+
for (const t of axis.ticks) {
|
|
982
|
+
const x = map(t, axis.domain, [area.x0, area.x1]);
|
|
983
|
+
const anchor = opts?.domain && opts.insetEndpointLabels ? t === axis.domain[0] ? "start" : t === axis.domain[1] ? "end" : "middle" : "middle";
|
|
984
|
+
parts.push(`<text class="stdlib-chart-tick-label" x="${fmt(x)}" y="${fmt(area.y1 + 14)}" text-anchor="${anchor}">${escapeXml(format(t))}</text>`);
|
|
985
|
+
}
|
|
986
|
+
for (const mt of axis.minorTicks) {
|
|
987
|
+
const x = map(mt, axis.domain, [area.x0, area.x1]);
|
|
988
|
+
parts.push(`<line class="stdlib-chart-minor-tick" x1="${fmt(x)}" y1="${fmt(area.y1)}" x2="${fmt(x)}" y2="${fmt(area.y1 + 3)}"/>`);
|
|
989
|
+
}
|
|
990
|
+
parts.push(`<line class="stdlib-chart-axis" x1="${fmt(area.x0)}" y1="${fmt(area.y1)}" x2="${fmt(area.x1)}" y2="${fmt(area.y1)}"/>`);
|
|
991
|
+
if (opts?.label) {
|
|
992
|
+
parts.push(`<text class="stdlib-chart-axis-label" x="${fmt((area.x0 + area.x1) / 2)}" y="${fmt(height - 4)}" text-anchor="middle">${escapeXml(opts.label)}</text>`);
|
|
993
|
+
}
|
|
994
|
+
return parts.join("");
|
|
995
|
+
};
|
|
996
|
+
export const renderXAxisCategorical = (labels, area, height, axisLabel) => {
|
|
997
|
+
const parts = [];
|
|
998
|
+
if (labels.length > 0) {
|
|
999
|
+
const step = (area.x1 - area.x0) / labels.length;
|
|
1000
|
+
for (let i = 0; i < labels.length; i++) {
|
|
1001
|
+
const cx = area.x0 + step * (i + 0.5);
|
|
1002
|
+
parts.push(`<text class="stdlib-chart-tick-label" x="${fmt(cx)}" y="${fmt(area.y1 + 14)}" text-anchor="middle">${escapeXml(labels[i])}</text>`);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
parts.push(`<line class="stdlib-chart-axis" x1="${fmt(area.x0)}" y1="${fmt(area.y1)}" x2="${fmt(area.x1)}" y2="${fmt(area.y1)}"/>`);
|
|
1006
|
+
if (axisLabel) {
|
|
1007
|
+
parts.push(`<text class="stdlib-chart-axis-label" x="${fmt((area.x0 + area.x1) / 2)}" y="${fmt(height - 4)}" text-anchor="middle">${escapeXml(axisLabel)}</text>`);
|
|
1008
|
+
}
|
|
1009
|
+
return parts.join("");
|
|
1010
|
+
};
|
|
1011
|
+
export const emptyChart = (width, height, className, message = "no data") => svgRoot({
|
|
1012
|
+
width,
|
|
1013
|
+
height,
|
|
1014
|
+
className
|
|
1015
|
+
}, `<text class="stdlib-chart-empty-text" x="${fmt(width / 2)}" y="${fmt(height / 2)}" text-anchor="middle" dominant-baseline="middle">${escapeXml(message)}</text>`);
|
|
1016
|
+
|
|
1017
|
+
// ==========================
|
|
1018
|
+
// CHART FUNCTIONS
|
|
1019
|
+
// ==========================
|
|
1020
|
+
|
|
1021
|
+
export const collectSeriesLegend = opts => {
|
|
1022
|
+
if (!opts.legend) return [];
|
|
1023
|
+
return opts.series.map((s, i) => ({
|
|
1024
|
+
label: s.label && s.label.length > 0 ? s.label : `Series ${i + 1}`,
|
|
1025
|
+
seriesIndex: i
|
|
1026
|
+
}));
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
// ==========================
|
|
1030
|
+
// BAR
|
|
1031
|
+
// ==========================
|
|
1032
|
+
|
|
1033
|
+
// ==========================
|
|
1034
|
+
// PIE / DONUT
|
|
1035
|
+
// ==========================
|
|
1036
|
+
|
|
1037
|
+
// ==========================
|
|
1038
|
+
// HISTOGRAM
|
|
1039
|
+
// ==========================
|
|
1040
|
+
|
|
1041
|
+
// ==========================
|
|
1042
|
+
// BOX PLOT
|
|
1043
|
+
// ==========================
|
|
1044
|
+
|
|
1045
|
+
// ==========================
|
|
1046
|
+
// DASHBOARD PANELS
|
|
1047
|
+
// ==========================
|
|
1048
|
+
|
|
1049
|
+
export const thresholdIndexForValue = (value, thresholds, min, max) => {
|
|
1050
|
+
if (!thresholds || thresholds.length === 0) return 0;
|
|
1051
|
+
const sorted = [...thresholds].filter(t => Number.isFinite(t.value)).sort((a, b) => a.value - b.value);
|
|
1052
|
+
const clamped = clamp(value, min, max);
|
|
1053
|
+
const idx = sorted.findIndex(t => clamped <= t.value);
|
|
1054
|
+
return idx >= 0 ? idx : Math.max(0, sorted.length - 1);
|
|
1055
|
+
};
|
|
1056
|
+
export const thresholdForIndex = (thresholds, index) => {
|
|
1057
|
+
if (!thresholds || thresholds.length === 0) return undefined;
|
|
1058
|
+
const sorted = [...thresholds].filter(t => Number.isFinite(t.value)).sort((a, b) => a.value - b.value);
|
|
1059
|
+
return sorted[index];
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
// ==========================
|
|
1063
|
+
// SPARKLINE
|
|
1064
|
+
// ==========================
|
|
1065
|
+
//# sourceMappingURL=svg-common.js.map
|