@zerodep-dev/vue-tsx-pro-ui 0.2.0 → 0.3.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/Badge.js +3 -3
- package/dist/base/Badge.js.map +1 -1
- package/dist/base/Can.d.ts +12 -0
- package/dist/base/Can.js +20 -0
- package/dist/base/Can.js.map +1 -0
- package/dist/base/ClientOnly.d.ts +10 -0
- package/dist/base/ClientOnly.js +18 -0
- package/dist/base/ClientOnly.js.map +1 -0
- package/dist/base/CloseButton.js +3 -3
- package/dist/base/CloseButton.js.map +1 -1
- package/dist/base/CodeBlock.js +5 -5
- package/dist/base/CodeBlock.js.map +1 -1
- package/dist/base/CopyButton.js +3 -3
- package/dist/base/CopyButton.js.map +1 -1
- package/dist/base/Icon.js +3 -3
- package/dist/base/Icon.js.map +1 -1
- package/dist/base/Indicator.js +3 -3
- package/dist/base/Indicator.js.map +1 -1
- package/dist/base/StatusDot.js +3 -3
- package/dist/base/StatusDot.js.map +1 -1
- package/dist/base/Tag.js +5 -5
- package/dist/base/Tag.js.map +1 -1
- package/dist/base/button-core.js +4 -4
- package/dist/base/button-core.js.map +1 -1
- package/dist/charts/AreaChart.js +3 -3
- package/dist/charts/AreaChart.js.map +1 -1
- package/dist/charts/BarChart.js +3 -3
- package/dist/charts/BarChart.js.map +1 -1
- package/dist/charts/ChordChart.js +3 -3
- package/dist/charts/ChordChart.js.map +1 -1
- package/dist/charts/ComboChart.js +3 -3
- package/dist/charts/ComboChart.js.map +1 -1
- package/dist/charts/DonutChart.js +3 -3
- package/dist/charts/DonutChart.js.map +1 -1
- package/dist/charts/FunnelChart.js +5 -5
- package/dist/charts/FunnelChart.js.map +1 -1
- package/dist/charts/GraphChart.js +3 -3
- package/dist/charts/GraphChart.js.map +1 -1
- package/dist/charts/LineChart.js +3 -3
- package/dist/charts/LineChart.js.map +1 -1
- package/dist/charts/MapChart.js +7 -7
- package/dist/charts/MapChart.js.map +1 -1
- package/dist/charts/RadarChart.js +16 -15
- package/dist/charts/RadarChart.js.map +1 -1
- package/dist/charts/SankeyChart.js +3 -3
- package/dist/charts/SankeyChart.js.map +1 -1
- package/dist/charts/ScatterChart.js +3 -3
- package/dist/charts/ScatterChart.js.map +1 -1
- package/dist/charts/TimelineChart.js +6 -5
- package/dist/charts/TimelineChart.js.map +1 -1
- package/dist/charts/TreemapChart.js +3 -3
- package/dist/charts/TreemapChart.js.map +1 -1
- package/dist/charts/WaterfallChart.js +3 -3
- package/dist/charts/WaterfallChart.js.map +1 -1
- package/dist/charts/XYChart.js +9 -7
- package/dist/charts/XYChart.js.map +1 -1
- package/dist/charts/frame.js +10 -8
- package/dist/charts/frame.js.map +1 -1
- package/dist/charts/single.js +4 -3
- package/dist/charts/single.js.map +1 -1
- package/dist/config/Provider.d.ts +3 -1
- package/dist/config/Provider.js +9 -4
- package/dist/config/Provider.js.map +1 -1
- package/dist/config/context.d.ts +18 -0
- package/dist/config/context.js +16 -1
- package/dist/config/context.js.map +1 -1
- package/dist/config/createUi.d.ts +20 -3
- package/dist/config/createUi.js +80 -49
- package/dist/config/createUi.js.map +1 -1
- package/dist/config/layout/preference.d.ts +43 -0
- package/dist/config/layout/preference.js +108 -0
- package/dist/config/layout/preference.js.map +1 -0
- package/dist/date/Calendar.js +3 -3
- package/dist/date/Calendar.js.map +1 -1
- package/dist/date/CalendarBoard.js +9 -9
- package/dist/date/CalendarBoard.js.map +1 -1
- package/dist/date/CalendarView.js +7 -7
- package/dist/date/CalendarView.js.map +1 -1
- package/dist/date/DateField.js +3 -3
- package/dist/date/DateField.js.map +1 -1
- package/dist/date/DatePicker.js +5 -5
- package/dist/date/DatePicker.js.map +1 -1
- package/dist/date/DateRangePicker.js +5 -5
- package/dist/date/DateRangePicker.js.map +1 -1
- package/dist/date/DateSegmentGroup.js +5 -5
- package/dist/date/DateSegmentGroup.js.map +1 -1
- package/dist/date/QuarterCalendar.js +6 -6
- package/dist/date/QuarterCalendar.js.map +1 -1
- package/dist/date/RangeCalendar.js +3 -3
- package/dist/date/RangeCalendar.js.map +1 -1
- package/dist/date/SegmentedField.js +3 -3
- package/dist/date/SegmentedField.js.map +1 -1
- package/dist/date/TimeField.js +3 -3
- package/dist/date/TimeField.js.map +1 -1
- package/dist/date/TimePicker.js +4 -4
- package/dist/date/TimePicker.js.map +1 -1
- package/dist/date/date-segments.js +1 -1
- package/dist/date/date-segments.js.map +1 -1
- package/dist/date/picker-popover.js +3 -2
- package/dist/date/picker-popover.js.map +1 -1
- package/dist/diagram/Diagram.js +12 -12
- package/dist/diagram/Diagram.js.map +1 -1
- package/dist/diagram/DiagramLanes.js +3 -3
- package/dist/diagram/DiagramLanes.js.map +1 -1
- package/dist/diagram/DiagramMinimap.js +4 -4
- package/dist/diagram/DiagramMinimap.js.map +1 -1
- package/dist/display/Accordion.js +6 -6
- package/dist/display/Accordion.js.map +1 -1
- package/dist/display/AnimatedNumber.js +4 -4
- package/dist/display/AnimatedNumber.js.map +1 -1
- package/dist/display/Avatar.js +4 -4
- package/dist/display/Avatar.js.map +1 -1
- package/dist/display/Checklist.js +7 -7
- package/dist/display/Checklist.js.map +1 -1
- package/dist/display/Collapse.js +8 -5
- package/dist/display/Collapse.js.map +1 -1
- package/dist/display/Collapsible.js +3 -3
- package/dist/display/Collapsible.js.map +1 -1
- package/dist/display/Descriptions.js +5 -5
- package/dist/display/Descriptions.js.map +1 -1
- package/dist/display/Ellipsis.js +3 -3
- package/dist/display/Ellipsis.js.map +1 -1
- package/dist/display/FieldCompare.js +4 -4
- package/dist/display/FieldCompare.js.map +1 -1
- package/dist/display/Gauge.js +11 -11
- package/dist/display/Gauge.js.map +1 -1
- package/dist/display/Image.js +4 -4
- package/dist/display/Image.js.map +1 -1
- package/dist/display/Marquee.js +3 -3
- package/dist/display/Marquee.js.map +1 -1
- package/dist/display/ProportionBar.js +4 -4
- package/dist/display/ProportionBar.js.map +1 -1
- package/dist/display/Sortable.js +4 -4
- package/dist/display/Sortable.js.map +1 -1
- package/dist/display/Sparkline.js +6 -6
- package/dist/display/Sparkline.js.map +1 -1
- package/dist/display/Stat.js +3 -3
- package/dist/display/Stat.js.map +1 -1
- package/dist/display/Timeline.js +7 -7
- package/dist/display/Timeline.js.map +1 -1
- package/dist/display/Tree.js +10 -10
- package/dist/display/Tree.js.map +1 -1
- package/dist/display/VirtualList.js +7 -7
- package/dist/display/VirtualList.js.map +1 -1
- package/dist/display/Watermark.js +3 -3
- package/dist/display/Watermark.js.map +1 -1
- package/dist/docs-kit/CodeGroup.js +5 -5
- package/dist/docs-kit/CodeGroup.js.map +1 -1
- package/dist/docs-kit/CodePreview.js +6 -6
- package/dist/docs-kit/CodePreview.js.map +1 -1
- package/dist/docs-kit/PropsTable.js +4 -4
- package/dist/docs-kit/PropsTable.js.map +1 -1
- package/dist/feedback/Alert.js +5 -5
- package/dist/feedback/Alert.js.map +1 -1
- package/dist/feedback/Banner.js +5 -5
- package/dist/feedback/Banner.js.map +1 -1
- package/dist/feedback/Empty.js +4 -4
- package/dist/feedback/Empty.js.map +1 -1
- package/dist/feedback/LoadingBar.js +20 -13
- package/dist/feedback/LoadingBar.js.map +1 -1
- package/dist/feedback/LoadingOverlay.js +3 -3
- package/dist/feedback/LoadingOverlay.js.map +1 -1
- package/dist/feedback/Progress.js +14 -14
- package/dist/feedback/Progress.js.map +1 -1
- package/dist/feedback/Result.js +4 -4
- package/dist/feedback/Result.js.map +1 -1
- package/dist/feedback/SkeletonText.js +3 -3
- package/dist/feedback/SkeletonText.js.map +1 -1
- package/dist/feedback/Toast.js +6 -6
- package/dist/feedback/Toast.js.map +1 -1
- package/dist/form/Field.js +7 -7
- package/dist/form/Field.js.map +1 -1
- package/dist/form/Form.js +3 -3
- package/dist/form/Form.js.map +1 -1
- package/dist/form/FormErrorSummary.js +3 -3
- package/dist/form/FormErrorSummary.js.map +1 -1
- package/dist/form/FormSection.js +6 -6
- package/dist/form/FormSection.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.js +10 -5
- package/dist/input/Autocomplete.js +3 -3
- package/dist/input/Autocomplete.js.map +1 -1
- package/dist/input/Cascader.js +7 -7
- package/dist/input/Cascader.js.map +1 -1
- package/dist/input/CharacterCount.js +3 -3
- package/dist/input/CharacterCount.js.map +1 -1
- package/dist/input/Checkbox.js +5 -5
- package/dist/input/Checkbox.js.map +1 -1
- package/dist/input/ColorField.js +4 -4
- package/dist/input/ColorField.js.map +1 -1
- package/dist/input/Combobox.js +4 -4
- package/dist/input/Combobox.js.map +1 -1
- package/dist/input/ConditionBuilder.js +15 -14
- package/dist/input/ConditionBuilder.js.map +1 -1
- package/dist/input/FileDrop.js +3 -3
- package/dist/input/FileDrop.js.map +1 -1
- package/dist/input/Listbox.js +9 -9
- package/dist/input/Listbox.js.map +1 -1
- package/dist/input/Mentions.js +3 -3
- package/dist/input/Mentions.js.map +1 -1
- package/dist/input/NumberField.js +3 -3
- package/dist/input/NumberField.js.map +1 -1
- package/dist/input/NumberRangeField.js +5 -5
- package/dist/input/NumberRangeField.js.map +1 -1
- package/dist/input/OtpField.js +5 -5
- package/dist/input/OtpField.js.map +1 -1
- package/dist/input/PeoplePicker.js +6 -6
- package/dist/input/PeoplePicker.js.map +1 -1
- package/dist/input/PeoplePickerPanel.js +4 -4
- package/dist/input/PeoplePickerPanel.js.map +1 -1
- package/dist/input/Radio.js +4 -4
- package/dist/input/Radio.js.map +1 -1
- package/dist/input/Rating.js +5 -5
- package/dist/input/Rating.js.map +1 -1
- package/dist/input/Select.js +4 -4
- package/dist/input/Select.js.map +1 -1
- package/dist/input/SelectedTags.js +4 -4
- package/dist/input/SelectedTags.js.map +1 -1
- package/dist/input/Slider.js +6 -6
- package/dist/input/Slider.js.map +1 -1
- package/dist/input/Switch.js +3 -3
- package/dist/input/Switch.js.map +1 -1
- package/dist/input/TagsInput.js +5 -5
- package/dist/input/TagsInput.js.map +1 -1
- package/dist/input/TextField.js +5 -5
- package/dist/input/TextField.js.map +1 -1
- package/dist/input/Textarea.js +4 -4
- package/dist/input/Textarea.js.map +1 -1
- package/dist/input/ToggleGroup.js +4 -4
- package/dist/input/ToggleGroup.js.map +1 -1
- package/dist/input/Transfer.js +8 -8
- package/dist/input/Transfer.js.map +1 -1
- package/dist/input/TreeSelect.js +6 -6
- package/dist/input/TreeSelect.js.map +1 -1
- package/dist/input/Upload.js +8 -8
- package/dist/input/Upload.js.map +1 -1
- package/dist/layout/Affix.js +4 -4
- package/dist/layout/Affix.js.map +1 -1
- package/dist/layout/ResizeHandle.d.ts +6 -2
- package/dist/layout/ResizeHandle.js +32 -9
- package/dist/layout/ResizeHandle.js.map +1 -1
- package/dist/layout/ScrollArea.js +3 -3
- package/dist/layout/ScrollArea.js.map +1 -1
- package/dist/layout/Separator.js +3 -3
- package/dist/layout/Separator.js.map +1 -1
- package/dist/layout/Splitter.js +3 -3
- package/dist/layout/Splitter.js.map +1 -1
- package/dist/layout/ViewportFill.d.ts +2 -2
- package/dist/layout/ViewportFill.js +4 -4
- package/dist/layout/ViewportFill.js.map +1 -1
- package/dist/navigation/Breadcrumbs.js +4 -4
- package/dist/navigation/Breadcrumbs.js.map +1 -1
- package/dist/navigation/NavBar.js +6 -6
- package/dist/navigation/NavBar.js.map +1 -1
- package/dist/navigation/NavList.d.ts +1 -0
- package/dist/navigation/NavList.js +20 -12
- package/dist/navigation/NavList.js.map +1 -1
- package/dist/navigation/NavMenu.d.ts +27 -0
- package/dist/navigation/NavMenu.js +54 -0
- package/dist/navigation/NavMenu.js.map +1 -0
- package/dist/navigation/PageJump.js +3 -3
- package/dist/navigation/PageJump.js.map +1 -1
- package/dist/navigation/PageSizeSelect.js +4 -4
- package/dist/navigation/PageSizeSelect.js.map +1 -1
- package/dist/navigation/Pagination.js +6 -6
- package/dist/navigation/Pagination.js.map +1 -1
- package/dist/navigation/PaginationBar.js +5 -5
- package/dist/navigation/PaginationBar.js.map +1 -1
- package/dist/navigation/Steps.js +4 -4
- package/dist/navigation/Steps.js.map +1 -1
- package/dist/navigation/TableOfContents.js +5 -5
- package/dist/navigation/TableOfContents.js.map +1 -1
- package/dist/navigation/Tabs.js +7 -7
- package/dist/navigation/Tabs.js.map +1 -1
- package/dist/navigation/Toolbar.js +3 -3
- package/dist/navigation/Toolbar.js.map +1 -1
- package/dist/overlay/AlertDialog.js +4 -4
- package/dist/overlay/AlertDialog.js.map +1 -1
- package/dist/overlay/CommandPalette.js +4 -4
- package/dist/overlay/CommandPalette.js.map +1 -1
- package/dist/overlay/ContextMenu.js +3 -3
- package/dist/overlay/ContextMenu.js.map +1 -1
- package/dist/overlay/Dialog.js +4 -4
- package/dist/overlay/Dialog.js.map +1 -1
- package/dist/overlay/Drawer.js +5 -5
- package/dist/overlay/Drawer.js.map +1 -1
- package/dist/overlay/HoverCard.js +5 -3
- package/dist/overlay/HoverCard.js.map +1 -1
- package/dist/overlay/Lightbox.js +5 -5
- package/dist/overlay/Lightbox.js.map +1 -1
- package/dist/overlay/Menu.js +7 -7
- package/dist/overlay/Menu.js.map +1 -1
- package/dist/overlay/ModalCore.js +9 -9
- package/dist/overlay/ModalCore.js.map +1 -1
- package/dist/overlay/Popconfirm.js +6 -6
- package/dist/overlay/Popconfirm.js.map +1 -1
- package/dist/overlay/Popover.js +5 -3
- package/dist/overlay/Popover.js.map +1 -1
- package/dist/overlay/Tooltip.js +5 -3
- package/dist/overlay/Tooltip.js.map +1 -1
- package/dist/overlay/Tour.js +8 -8
- package/dist/overlay/Tour.js.map +1 -1
- package/dist/overlay/detached.d.ts +2 -0
- package/dist/overlay/detached.js +73 -42
- package/dist/overlay/detached.js.map +1 -1
- package/dist/patterns/ActionBar.js +3 -3
- package/dist/patterns/ActionBar.js.map +1 -1
- package/dist/patterns/AppShell.d.ts +28 -1
- package/dist/patterns/AppShell.js +158 -52
- package/dist/patterns/AppShell.js.map +1 -1
- package/dist/patterns/AuthLayout.d.ts +28 -0
- package/dist/patterns/AuthLayout.js +77 -0
- package/dist/patterns/AuthLayout.js.map +1 -0
- package/dist/patterns/CommentThread.js +9 -9
- package/dist/patterns/CommentThread.js.map +1 -1
- package/dist/patterns/DataImport.js +4 -4
- package/dist/patterns/DataImport.js.map +1 -1
- package/dist/patterns/DeadlineBadge.js +3 -3
- package/dist/patterns/DeadlineBadge.js.map +1 -1
- package/dist/patterns/FilterBar.d.ts +11 -1
- package/dist/patterns/FilterBar.js +39 -10
- package/dist/patterns/FilterBar.js.map +1 -1
- package/dist/patterns/FormDialog.d.ts +18 -0
- package/dist/patterns/FormDialog.js +46 -0
- package/dist/patterns/FormDialog.js.map +1 -0
- package/dist/patterns/KanbanBoard.js +8 -8
- package/dist/patterns/KanbanBoard.js.map +1 -1
- package/dist/patterns/LayoutSwitcher.d.ts +12 -0
- package/dist/patterns/LayoutSwitcher.js +79 -0
- package/dist/patterns/LayoutSwitcher.js.map +1 -0
- package/dist/patterns/NotificationCenter.js +8 -8
- package/dist/patterns/NotificationCenter.js.map +1 -1
- package/dist/patterns/PageTabs.d.ts +12 -7
- package/dist/patterns/PageTabs.js +41 -29
- package/dist/patterns/PageTabs.js.map +1 -1
- package/dist/patterns/QueryTable.d.ts +7 -1
- package/dist/patterns/QueryTable.js +116 -81
- package/dist/patterns/QueryTable.js.map +1 -1
- package/dist/patterns/SlideCaptcha.d.ts +80 -0
- package/dist/patterns/SlideCaptcha.js +354 -0
- package/dist/patterns/SlideCaptcha.js.map +1 -0
- package/dist/patterns/StepsForm.js +7 -7
- package/dist/patterns/StepsForm.js.map +1 -1
- package/dist/patterns/ThemeSwitcher.js +3 -3
- package/dist/patterns/ThemeSwitcher.js.map +1 -1
- package/dist/patterns/index.d.ts +7 -3
- package/dist/patterns/index.js +5 -1
- package/dist/patterns/messages.d.ts +37 -1
- package/dist/patterns/messages.js +38 -2
- package/dist/patterns/messages.js.map +1 -1
- package/dist/table/ColumnHeaderActions.js +3 -3
- package/dist/table/ColumnHeaderActions.js.map +1 -1
- package/dist/table/ColumnResizer.js +3 -3
- package/dist/table/ColumnResizer.js.map +1 -1
- package/dist/table/ColumnSettings.js +5 -5
- package/dist/table/ColumnSettings.js.map +1 -1
- package/dist/table/ColumnTable.js +8 -8
- package/dist/table/ColumnTable.js.map +1 -1
- package/dist/table/DataTable.js +8 -8
- package/dist/table/DataTable.js.map +1 -1
- package/dist/table/DataTableView.js +21 -21
- package/dist/table/DataTableView.js.map +1 -1
- package/dist/table/PivotTable.js +3 -3
- package/dist/table/PivotTable.js.map +1 -1
- package/dist/table/SelectionBar.js +4 -4
- package/dist/table/SelectionBar.js.map +1 -1
- package/dist/table/TreeDataTable.js +5 -5
- package/dist/table/TreeDataTable.js.map +1 -1
- package/dist/theme-customize/ThemeCustomizer.js +7 -6
- package/dist/theme-customize/ThemeCustomizer.js.map +1 -1
- package/dist/theme-customize/ThemeThumbnail.js +3 -3
- package/dist/theme-customize/ThemeThumbnail.js.map +1 -1
- package/dist/three/background/AuroraBackground.js +3 -3
- package/dist/three/background/AuroraBackground.js.map +1 -1
- package/dist/three/background/MeshGradient.js +3 -3
- package/dist/three/background/MeshGradient.js.map +1 -1
- package/dist/three/background/ShaderBackground.js +3 -3
- package/dist/three/background/ShaderBackground.js.map +1 -1
- package/dist/three/core/EffectSurface.js +3 -3
- package/dist/three/core/EffectSurface.js.map +1 -1
- package/dist/three/core/ThreeCanvas.js +4 -4
- package/dist/three/core/ThreeCanvas.js.map +1 -1
- package/dist/three/effects/Confetti.js +3 -3
- package/dist/three/effects/Confetti.js.map +1 -1
- package/dist/three/effects/ParticleField.js +3 -3
- package/dist/three/effects/ParticleField.js.map +1 -1
- package/dist/three/effects/WaveGrid.js +3 -3
- package/dist/three/effects/WaveGrid.js.map +1 -1
- package/dist/three/globe/Globe.js +3 -3
- package/dist/three/globe/Globe.js.map +1 -1
- package/dist/three/model/ModelViewer.js +3 -3
- package/dist/three/model/ModelViewer.js.map +1 -1
- package/dist/utils/data/number.d.ts +5 -0
- package/dist/utils/data/number.js +8 -1
- package/dist/utils/data/number.js.map +1 -1
- package/dist/utils/dom/element.d.ts +3 -1
- package/dist/utils/dom/element.js +8 -4
- package/dist/utils/dom/element.js.map +1 -1
- package/dist/utils/dom/fullscreen.d.ts +1 -1
- package/dist/utils/dom/fullscreen.js +12 -8
- package/dist/utils/dom/fullscreen.js.map +1 -1
- package/dist/utils/dom/mounted.d.ts +7 -0
- package/dist/utils/dom/mounted.js +16 -0
- package/dist/utils/dom/mounted.js.map +1 -0
- package/dist/utils/dom/overflow.js +2 -1
- package/dist/utils/dom/overflow.js.map +1 -1
- package/dist/utils/dom/pan-zoom.js +2 -1
- package/dist/utils/dom/pan-zoom.js.map +1 -1
- package/dist/utils/props.js +3 -13
- package/dist/utils/props.js.map +1 -1
- package/dist/utils/request/http.d.ts +12 -0
- package/dist/utils/request/http.js +6 -2
- package/dist/utils/request/http.js.map +1 -1
- package/dist/utils/request/use-request.js +4 -2
- package/dist/utils/request/use-request.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { svgNumber } from "../utils/data/number.js";
|
|
1
2
|
import { useLang } from "../config/context.js";
|
|
2
3
|
import { useFormat } from "../config/lang/format.js";
|
|
3
4
|
import { Skeleton } from "../feedback/Skeleton.js";
|
|
4
5
|
import { niceStep } from "./axis-ticks.js";
|
|
5
6
|
import { ChartFrame } from "./frame.js";
|
|
6
7
|
import { resolveSeries, seriesNumber, seriesValue } from "./series.js";
|
|
7
|
-
import { computed, createVNode,
|
|
8
|
-
import { _component, bindable, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
8
|
+
import { computed, createVNode, ref } from "vue";
|
|
9
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
9
10
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
10
11
|
//#region src/charts/RadarChart.tsx
|
|
11
12
|
/** 未声明的属性落到外层 div(role="figure")上;aria-label 是图表的名称 */
|
|
@@ -75,8 +76,8 @@ var LABEL_RADIUS = 124;
|
|
|
75
76
|
function polar(index, count, ratio) {
|
|
76
77
|
const angle = Math.PI * 2 * index / count - Math.PI / 2;
|
|
77
78
|
return {
|
|
78
|
-
x: CX + Math.cos(angle) * RADIUS * ratio,
|
|
79
|
-
y: CY + Math.sin(angle) * RADIUS * ratio,
|
|
79
|
+
x: svgNumber(CX + Math.cos(angle) * RADIUS * ratio),
|
|
80
|
+
y: svgNumber(CY + Math.sin(angle) * RADIUS * ratio),
|
|
80
81
|
angle
|
|
81
82
|
};
|
|
82
83
|
}
|
|
@@ -111,19 +112,19 @@ var RadarChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
111
112
|
const top = max.value ?? niceStep(Math.max(...values, 1) / levels.value) * levels.value;
|
|
112
113
|
const ratioOf = (value) => Math.max(0, Math.min(value / top, 1));
|
|
113
114
|
const ring = (ratio) => grid.value === "circle" ? null : data.value.map((_row, position) => polar(position, count, ratio)).map((p) => `${p.x},${p.y}`).join(" ");
|
|
114
|
-
const plot = count < 3 ? null : createVNode("svg",
|
|
115
|
+
const plot = count < 3 ? null : createVNode("svg", mergeJsxProps(css.attrs(radarChartStyles.svg), {
|
|
115
116
|
"viewBox": `0 0 ${WIDTH} ${HEIGHT}`,
|
|
116
117
|
"aria-hidden": "true",
|
|
117
118
|
"onPointerleave": _cache[0] || (_cache[0] = () => active.value = null)
|
|
118
119
|
}), [
|
|
119
120
|
Array.from({ length: levels.value }, (_, level) => {
|
|
120
121
|
const ratio = (level + 1) / levels.value;
|
|
121
|
-
return grid.value === "circle" ? createVNode("circle",
|
|
122
|
+
return grid.value === "circle" ? createVNode("circle", mergeJsxProps({
|
|
122
123
|
"key": level,
|
|
123
124
|
"cx": CX,
|
|
124
125
|
"cy": CY,
|
|
125
126
|
"r": RADIUS * ratio
|
|
126
|
-
}, css.attrs(radarChartStyles.grid)), null) : createVNode("polygon",
|
|
127
|
+
}, css.attrs(radarChartStyles.grid)), null) : createVNode("polygon", mergeJsxProps({
|
|
127
128
|
"key": level,
|
|
128
129
|
"points": ring(ratio)
|
|
129
130
|
}, css.attrs(radarChartStyles.grid)), null);
|
|
@@ -132,19 +133,19 @@ var RadarChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
132
133
|
const end = polar(position, count, 1);
|
|
133
134
|
const label = polar(position, count, LABEL_RADIUS / RADIUS);
|
|
134
135
|
const cos = Math.cos(label.angle);
|
|
135
|
-
return createVNode("g", { "key": position }, [createVNode("line",
|
|
136
|
+
return createVNode("g", { "key": position }, [createVNode("line", mergeJsxProps({
|
|
136
137
|
"x1": CX,
|
|
137
138
|
"y1": CY,
|
|
138
139
|
"x2": end.x,
|
|
139
140
|
"y2": end.y
|
|
140
|
-
}, css.attrs(radarChartStyles.spoke, active.value === position && radarChartStyles.spokeActive)), null), createVNode("text",
|
|
141
|
+
}, css.attrs(radarChartStyles.spoke, active.value === position && radarChartStyles.spokeActive)), null), createVNode("text", mergeJsxProps({
|
|
141
142
|
"x": label.x,
|
|
142
143
|
"y": label.y,
|
|
143
144
|
"text-anchor": Math.abs(cos) < .2 ? "middle" : cos > 0 ? "start" : "end",
|
|
144
145
|
"dominant-baseline": "middle"
|
|
145
146
|
}, css.attrs(radarChartStyles.label, active.value === position && radarChartStyles.labelActive)), [String(row[index.value] ?? "")])]);
|
|
146
147
|
}),
|
|
147
|
-
Array.from({ length: levels.value }, (_, level) => createVNode("text",
|
|
148
|
+
Array.from({ length: levels.value }, (_, level) => createVNode("text", mergeJsxProps({
|
|
148
149
|
"key": `tick-${level}`,
|
|
149
150
|
"x": 183,
|
|
150
151
|
"y": CY - RADIUS * (level + 1) / levels.value,
|
|
@@ -153,14 +154,14 @@ var RadarChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
153
154
|
visible.value.map((item) => {
|
|
154
155
|
const points = data.value.map((row, position) => polar(position, count, ratioOf(seriesNumber(row, item.key) ?? 0)));
|
|
155
156
|
return createVNode("g", { "key": item.key }, [
|
|
156
|
-
createVNode("polygon",
|
|
157
|
-
points.map((p, position) => createVNode("circle",
|
|
157
|
+
createVNode("polygon", mergeJsxProps({ "points": points.map((p) => `${p.x},${p.y}`).join(" ") }, css.attrs(radarChartStyles.area, css.bind(radarChartStyles.stroke, () => [item.color]))), null),
|
|
158
|
+
points.map((p, position) => createVNode("circle", mergeJsxProps({
|
|
158
159
|
"key": position,
|
|
159
160
|
"cx": p.x,
|
|
160
161
|
"cy": p.y,
|
|
161
162
|
"r": active.value === position ? 4 : 2.5
|
|
162
163
|
}, css.attrs(radarChartStyles.vertex, css.bind(radarChartStyles.stroke, () => [item.color]))), null)),
|
|
163
|
-
active.value !== null && createVNode("text",
|
|
164
|
+
active.value !== null && createVNode("text", mergeJsxProps({
|
|
164
165
|
"x": points[active.value].x,
|
|
165
166
|
"y": points[active.value].y - 8,
|
|
166
167
|
"text-anchor": "middle"
|
|
@@ -170,13 +171,13 @@ var RadarChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
170
171
|
data.value.map((_row, position) => {
|
|
171
172
|
const a = polar(position - .5, count, 1.2);
|
|
172
173
|
const b = polar(position + .5, count, 1.2);
|
|
173
|
-
return createVNode("path",
|
|
174
|
+
return createVNode("path", mergeJsxProps({
|
|
174
175
|
"key": `sector-${position}`,
|
|
175
176
|
"d": `M${CX} ${CY} L${a.x} ${a.y} L${b.x} ${b.y} Z`
|
|
176
177
|
}, css.attrs(radarChartStyles.sector), { "onPointerenter": () => active.value = position }), null);
|
|
177
178
|
})
|
|
178
179
|
]);
|
|
179
|
-
return createVNode(ChartFrame,
|
|
180
|
+
return createVNode(ChartFrame, mergeJsxProps(rest, {
|
|
180
181
|
"unit": unit.value,
|
|
181
182
|
"height": height.value,
|
|
182
183
|
"legend": legend.value ?? resolved.value.length > 1 ? resolved.value : [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RadarChart.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_computed","_vue_ref","_createVNode","_mergeProps"],"sources":["../../src/charts/RadarChart.tsx"],"sourcesContent":["/**\n * 雷达图:几个维度上同时比较几个对象(供应商的质量、交期、价格、服务评分,方案的各项指标)。\n * 每个维度一根轴,从中心往外是 0 到 max;每个系列连成一个多边形,填一层浅色。\n *\n * <RadarChart\n * aria-label=\"供应商评分\"\n * data={[{ dimension: '质量', huachen: 86, hengyuan: 78 }, …]}\n * index=\"dimension\"\n * series={[{ key: 'huachen', label: '华辰精密' }, { key: 'hengyuan', label: '恒远五金' }]}\n * max={100}\n * />\n *\n * Unovis 没有雷达图:这里用 SVG 直接画,外层结构(图例、单位、加载与空状态、给读屏的数据表)同其他图表。\n * 悬停一个维度时那根轴加粗,各系列在这一维的数值标在顶点旁边;点图例隐藏系列。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { useFormat, type NumericFormat } from '../config/lang/format';\nimport { Skeleton } from '../feedback/Skeleton';\nimport type { PartProps } from '../utils/props';\nimport { niceStep } from './axis-ticks';\nimport { ChartFrame, type ChartFrameParts } from './frame';\nimport { resolveSeries, seriesNumber, seriesValue, type ChartSeries } from './series';\n\n/** 未声明的属性落到外层 div(role=\"figure\")上;aria-label 是图表的名称 */\nexport interface RadarChartProps<T extends object>\n extends PartProps<'div', 'role' | 'aria-busy'>, ChartFrameParts {\n /** 数据:每一行是一个维度(质量、交期…) */\n data: readonly T[];\n /** 维度名的字段 */\n index: keyof T & string;\n /** 系列:每个系列连成一个多边形 */\n series: readonly ChartSeries<T>[];\n /** 轴的最大值;不写时按数据取一个整齐的值 */\n max?: number;\n /**\n * 网格的圈数\n * @default 4\n */\n levels?: number;\n /** 网格画成多边形(默认)或圆 */\n grid?: 'polygon' | 'circle';\n /** 数值的格式:刻度、顶点旁的数值、数据表共用 */\n valueFormat?: NumericFormat;\n /** 单位:显示在左上方 */\n unit?: string;\n /**\n * 绘图区的高度;数字按 px\n * @default 300\n */\n height?: number | string;\n /** 图例:默认多于一个系列时显示 */\n legend?: boolean;\n /** 隐藏的系列:点图例时写回,父级可以写 _hiddenSeries={keys} */\n _hiddenSeries?: string[];\n /** 加载中:绘图区显示骨架 */\n loading?: boolean;\n /** 没有数据时显示的内容 */\n empty?: VNodeChild;\n}\n\n/** RadarChart 的样式 */\nexport const radarChartStyles = css.create({\n svg: { display: 'block', width: '100%', height: '100%', overflow: 'visible' },\n grid: { fill: 'none', stroke: '_borderMuted', strokeWidth: 1 },\n spoke: { stroke: '_borderMuted', strokeWidth: 1 },\n spokeActive: { stroke: '_textMuted', strokeWidth: 1.5 },\n tick: { fill: '_textMuted', fontSize: '9px' },\n label: { fill: '_text', fontSize: '11px' },\n labelActive: { fontWeight: '_semibold' },\n // 几个系列叠在一起:填充很浅,混在一起也不发灰\n area: { fillOpacity: 0.08, strokeWidth: 2, strokeLinejoin: 'round' },\n vertex: { strokeWidth: 0 },\n value: { fontSize: '10px', fontWeight: '_semibold', paintOrder: 'stroke', strokeWidth: 3 },\n // 悬停用的扇区:透明,只接指针\n sector: { fill: 'transparent' },\n skeleton: {\n display: 'block',\n height: '100%',\n aspectRatio: '1',\n marginInline: 'auto',\n borderRadius: '50%',\n },\n stroke: (color: string) => ({ stroke: color, fill: color }),\n surfaceStroke: { stroke: '_surface' },\n});\n\n// 画布:360 × 300,中心 (180, 150),半径 108;文字在半径 124 的圈上\nconst WIDTH = 360;\nconst HEIGHT = 300;\nconst CX = WIDTH / 2;\nconst CY = HEIGHT / 2;\nconst RADIUS = 108;\nconst LABEL_RADIUS = 124;\n\n/** 第 index 个维度(共 count 个)、比例 ratio 处的点:第一个维度在正上方,顺时针 */\nfunction polar(index: number, count: number, ratio: number) {\n const angle = (Math.PI * 2 * index) / count - Math.PI / 2;\n return {\n x: CX + Math.cos(angle) * RADIUS * ratio,\n y: CY + Math.sin(angle) * RADIUS * ratio,\n angle,\n };\n}\n\n/** 雷达图 */\nexport const RadarChart = _component(\n <T extends object>({\n data,\n index,\n series,\n max,\n levels = 4,\n grid = 'polygon',\n valueFormat,\n unit,\n height = 300,\n legend,\n _hiddenSeries: hiddenSeries = _bindable<string[]>([]),\n loading = false,\n empty,\n ...rest\n }: RadarChartProps<T>) => {\n const lang = useLang();\n const format = useFormat();\n const resolved = _derived(resolveSeries(series));\n const visible = _derived(resolved.filter((item) => !hiddenSeries.includes(item.key)));\n // 悬停的维度\n let active = _state<number | null>(null);\n\n function toggle(key: string) {\n hiddenSeries = hiddenSeries.includes(key)\n ? hiddenSeries.filter((item) => item !== key)\n : [...hiddenSeries, key];\n }\n\n return () => {\n const count = data.length;\n const values = data.flatMap((row) => visible.map((item) => seriesNumber(row, item.key) ?? 0));\n // 轴的最大值:给了用它,否则取 levels 格整齐步长的整数倍\n const top = max ?? niceStep(Math.max(...values, 1) / levels) * levels;\n const ratioOf = (value: number) => Math.max(0, Math.min(value / top, 1));\n const ring = (ratio: number) =>\n grid === 'circle'\n ? null\n : data\n .map((_row, position) => polar(position, count, ratio))\n .map((p) => `${p.x},${p.y}`)\n .join(' ');\n\n const plot =\n count < 3 ? null : (\n <svg\n {...css.attrs(radarChartStyles.svg)}\n viewBox={`0 0 ${WIDTH} ${HEIGHT}`}\n aria-hidden=\"true\"\n onPointerleave={() => (active = null)}\n >\n {Array.from({ length: levels }, (_, level) => {\n const ratio = (level + 1) / levels;\n return grid === 'circle' ? (\n <circle\n key={level}\n cx={CX}\n cy={CY}\n r={RADIUS * ratio}\n {...css.attrs(radarChartStyles.grid)}\n />\n ) : (\n <polygon key={level} points={ring(ratio)!} {...css.attrs(radarChartStyles.grid)} />\n );\n })}\n {data.map((row, position) => {\n const end = polar(position, count, 1);\n const label = polar(position, count, LABEL_RADIUS / RADIUS);\n const cos = Math.cos(label.angle);\n return (\n <g key={position}>\n <line\n x1={CX}\n y1={CY}\n x2={end.x}\n y2={end.y}\n {...css.attrs(\n radarChartStyles.spoke,\n active === position && radarChartStyles.spokeActive,\n )}\n />\n <text\n x={label.x}\n y={label.y}\n text-anchor={Math.abs(cos) < 0.2 ? 'middle' : cos > 0 ? 'start' : 'end'}\n dominant-baseline=\"middle\"\n {...css.attrs(\n radarChartStyles.label,\n active === position && radarChartStyles.labelActive,\n )}\n >\n {String(row[index] ?? '')}\n </text>\n </g>\n );\n })}\n {Array.from({ length: levels }, (_, level) => (\n <text\n key={`tick-${level}`}\n x={CX + 3}\n y={CY - (RADIUS * (level + 1)) / levels}\n dominant-baseline=\"middle\"\n {...css.attrs(radarChartStyles.tick)}\n >\n {format.number((top * (level + 1)) / levels, valueFormat)}\n </text>\n ))}\n {visible.map((item) => {\n const points = data.map((row, position) =>\n polar(position, count, ratioOf(seriesNumber(row, item.key) ?? 0)),\n );\n return (\n <g key={item.key}>\n <polygon\n points={points.map((p) => `${p.x},${p.y}`).join(' ')}\n {...css.attrs(radarChartStyles.area, radarChartStyles.stroke(item.color))}\n />\n {points.map((p, position) => (\n <circle\n key={position}\n cx={p.x}\n cy={p.y}\n r={active === position ? 4 : 2.5}\n {...css.attrs(radarChartStyles.vertex, radarChartStyles.stroke(item.color))}\n />\n ))}\n {active !== null && (\n <text\n x={points[active]!.x}\n y={points[active]!.y - 8}\n text-anchor=\"middle\"\n {...css.attrs(\n radarChartStyles.value,\n radarChartStyles.stroke(item.color),\n radarChartStyles.surfaceStroke,\n )}\n >\n {format.number(seriesValue(data[active]!, item.key) ?? 0, valueFormat)}\n </text>\n )}\n </g>\n );\n })}\n {/* 悬停:每个维度一块扇区 */}\n {data.map((_row, position) => {\n const a = polar(position - 0.5, count, 1.2);\n const b = polar(position + 0.5, count, 1.2);\n return (\n <path\n key={`sector-${position}`}\n d={`M${CX} ${CY} L${a.x} ${a.y} L${b.x} ${b.y} Z`}\n {...css.attrs(radarChartStyles.sector)}\n onPointerenter={() => (active = position)}\n />\n );\n })}\n </svg>\n );\n\n return (\n <ChartFrame\n {...rest}\n unit={unit}\n height={height}\n legend={(legend ?? resolved.length > 1) ? resolved : []}\n hiddenKeys={hiddenSeries}\n onLegendToggle={toggle}\n loading={loading}\n skeleton={<Skeleton sx={radarChartStyles.skeleton} />}\n isEmpty={data.length === 0}\n empty={empty}\n table={{\n columns: [\n lang.chartCategory,\n ...resolved.map((item) => (unit ? `${item.label}(${unit})` : item.label)),\n ],\n rows: data.map((row) => ({\n header: String(row[index] ?? ''),\n cells: resolved.map((item) => {\n const value = seriesValue(row, item.key);\n return value === undefined ? lang.emptyValue : format.number(value, valueFormat);\n }),\n })),\n }}\n plot={plot}\n />\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RadarChart.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_computed","_vue_ref","_createVNode","_mergeProps"],"sources":["../../src/charts/RadarChart.tsx"],"sourcesContent":["/**\n * 雷达图:几个维度上同时比较几个对象(供应商的质量、交期、价格、服务评分,方案的各项指标)。\n * 每个维度一根轴,从中心往外是 0 到 max;每个系列连成一个多边形,填一层浅色。\n *\n * <RadarChart\n * aria-label=\"供应商评分\"\n * data={[{ dimension: '质量', huachen: 86, hengyuan: 78 }, …]}\n * index=\"dimension\"\n * series={[{ key: 'huachen', label: '华辰精密' }, { key: 'hengyuan', label: '恒远五金' }]}\n * max={100}\n * />\n *\n * Unovis 没有雷达图:这里用 SVG 直接画,外层结构(图例、单位、加载与空状态、给读屏的数据表)同其他图表。\n * 悬停一个维度时那根轴加粗,各系列在这一维的数值标在顶点旁边;点图例隐藏系列。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { useFormat, type NumericFormat } from '../config/lang/format';\nimport { Skeleton } from '../feedback/Skeleton';\nimport type { PartProps } from '../utils/props';\nimport { niceStep } from './axis-ticks';\nimport { ChartFrame, type ChartFrameParts } from './frame';\nimport { resolveSeries, seriesNumber, seriesValue, type ChartSeries } from './series';\nimport { svgNumber } from '../utils/data/number';\n\n/** 未声明的属性落到外层 div(role=\"figure\")上;aria-label 是图表的名称 */\nexport interface RadarChartProps<T extends object>\n extends PartProps<'div', 'role' | 'aria-busy'>, ChartFrameParts {\n /** 数据:每一行是一个维度(质量、交期…) */\n data: readonly T[];\n /** 维度名的字段 */\n index: keyof T & string;\n /** 系列:每个系列连成一个多边形 */\n series: readonly ChartSeries<T>[];\n /** 轴的最大值;不写时按数据取一个整齐的值 */\n max?: number;\n /**\n * 网格的圈数\n * @default 4\n */\n levels?: number;\n /** 网格画成多边形(默认)或圆 */\n grid?: 'polygon' | 'circle';\n /** 数值的格式:刻度、顶点旁的数值、数据表共用 */\n valueFormat?: NumericFormat;\n /** 单位:显示在左上方 */\n unit?: string;\n /**\n * 绘图区的高度;数字按 px\n * @default 300\n */\n height?: number | string;\n /** 图例:默认多于一个系列时显示 */\n legend?: boolean;\n /** 隐藏的系列:点图例时写回,父级可以写 _hiddenSeries={keys} */\n _hiddenSeries?: string[];\n /** 加载中:绘图区显示骨架 */\n loading?: boolean;\n /** 没有数据时显示的内容 */\n empty?: VNodeChild;\n}\n\n/** RadarChart 的样式 */\nexport const radarChartStyles = css.create({\n svg: { display: 'block', width: '100%', height: '100%', overflow: 'visible' },\n grid: { fill: 'none', stroke: '_borderMuted', strokeWidth: 1 },\n spoke: { stroke: '_borderMuted', strokeWidth: 1 },\n spokeActive: { stroke: '_textMuted', strokeWidth: 1.5 },\n tick: { fill: '_textMuted', fontSize: '9px' },\n label: { fill: '_text', fontSize: '11px' },\n labelActive: { fontWeight: '_semibold' },\n // 几个系列叠在一起:填充很浅,混在一起也不发灰\n area: { fillOpacity: 0.08, strokeWidth: 2, strokeLinejoin: 'round' },\n vertex: { strokeWidth: 0 },\n value: { fontSize: '10px', fontWeight: '_semibold', paintOrder: 'stroke', strokeWidth: 3 },\n // 悬停用的扇区:透明,只接指针\n sector: { fill: 'transparent' },\n skeleton: {\n display: 'block',\n height: '100%',\n aspectRatio: '1',\n marginInline: 'auto',\n borderRadius: '50%',\n },\n stroke: (color: string) => ({ stroke: color, fill: color }),\n surfaceStroke: { stroke: '_surface' },\n});\n\n// 画布:360 × 300,中心 (180, 150),半径 108;文字在半径 124 的圈上\nconst WIDTH = 360;\nconst HEIGHT = 300;\nconst CX = WIDTH / 2;\nconst CY = HEIGHT / 2;\nconst RADIUS = 108;\nconst LABEL_RADIUS = 124;\n\n/** 第 index 个维度(共 count 个)、比例 ratio 处的点:第一个维度在正上方,顺时针 */\nfunction polar(index: number, count: number, ratio: number) {\n const angle = (Math.PI * 2 * index) / count - Math.PI / 2;\n return {\n x: svgNumber(CX + Math.cos(angle) * RADIUS * ratio),\n y: svgNumber(CY + Math.sin(angle) * RADIUS * ratio),\n angle,\n };\n}\n\n/** 雷达图 */\nexport const RadarChart = _component(\n <T extends object>({\n data,\n index,\n series,\n max,\n levels = 4,\n grid = 'polygon',\n valueFormat,\n unit,\n height = 300,\n legend,\n _hiddenSeries: hiddenSeries = _bindable<string[]>([]),\n loading = false,\n empty,\n ...rest\n }: RadarChartProps<T>) => {\n const lang = useLang();\n const format = useFormat();\n const resolved = _derived(resolveSeries(series));\n const visible = _derived(resolved.filter((item) => !hiddenSeries.includes(item.key)));\n // 悬停的维度\n let active = _state<number | null>(null);\n\n function toggle(key: string) {\n hiddenSeries = hiddenSeries.includes(key)\n ? hiddenSeries.filter((item) => item !== key)\n : [...hiddenSeries, key];\n }\n\n return () => {\n const count = data.length;\n const values = data.flatMap((row) => visible.map((item) => seriesNumber(row, item.key) ?? 0));\n // 轴的最大值:给了用它,否则取 levels 格整齐步长的整数倍\n const top = max ?? niceStep(Math.max(...values, 1) / levels) * levels;\n const ratioOf = (value: number) => Math.max(0, Math.min(value / top, 1));\n const ring = (ratio: number) =>\n grid === 'circle'\n ? null\n : data\n .map((_row, position) => polar(position, count, ratio))\n .map((p) => `${p.x},${p.y}`)\n .join(' ');\n\n const plot =\n count < 3 ? null : (\n <svg\n {...css.attrs(radarChartStyles.svg)}\n viewBox={`0 0 ${WIDTH} ${HEIGHT}`}\n aria-hidden=\"true\"\n onPointerleave={() => (active = null)}\n >\n {Array.from({ length: levels }, (_, level) => {\n const ratio = (level + 1) / levels;\n return grid === 'circle' ? (\n <circle\n key={level}\n cx={CX}\n cy={CY}\n r={RADIUS * ratio}\n {...css.attrs(radarChartStyles.grid)}\n />\n ) : (\n <polygon key={level} points={ring(ratio)!} {...css.attrs(radarChartStyles.grid)} />\n );\n })}\n {data.map((row, position) => {\n const end = polar(position, count, 1);\n const label = polar(position, count, LABEL_RADIUS / RADIUS);\n const cos = Math.cos(label.angle);\n return (\n <g key={position}>\n <line\n x1={CX}\n y1={CY}\n x2={end.x}\n y2={end.y}\n {...css.attrs(\n radarChartStyles.spoke,\n active === position && radarChartStyles.spokeActive,\n )}\n />\n <text\n x={label.x}\n y={label.y}\n text-anchor={Math.abs(cos) < 0.2 ? 'middle' : cos > 0 ? 'start' : 'end'}\n dominant-baseline=\"middle\"\n {...css.attrs(\n radarChartStyles.label,\n active === position && radarChartStyles.labelActive,\n )}\n >\n {String(row[index] ?? '')}\n </text>\n </g>\n );\n })}\n {Array.from({ length: levels }, (_, level) => (\n <text\n key={`tick-${level}`}\n x={CX + 3}\n y={CY - (RADIUS * (level + 1)) / levels}\n dominant-baseline=\"middle\"\n {...css.attrs(radarChartStyles.tick)}\n >\n {format.number((top * (level + 1)) / levels, valueFormat)}\n </text>\n ))}\n {visible.map((item) => {\n const points = data.map((row, position) =>\n polar(position, count, ratioOf(seriesNumber(row, item.key) ?? 0)),\n );\n return (\n <g key={item.key}>\n <polygon\n points={points.map((p) => `${p.x},${p.y}`).join(' ')}\n {...css.attrs(radarChartStyles.area, radarChartStyles.stroke(item.color))}\n />\n {points.map((p, position) => (\n <circle\n key={position}\n cx={p.x}\n cy={p.y}\n r={active === position ? 4 : 2.5}\n {...css.attrs(radarChartStyles.vertex, radarChartStyles.stroke(item.color))}\n />\n ))}\n {active !== null && (\n <text\n x={points[active]!.x}\n y={points[active]!.y - 8}\n text-anchor=\"middle\"\n {...css.attrs(\n radarChartStyles.value,\n radarChartStyles.stroke(item.color),\n radarChartStyles.surfaceStroke,\n )}\n >\n {format.number(seriesValue(data[active]!, item.key) ?? 0, valueFormat)}\n </text>\n )}\n </g>\n );\n })}\n {/* 悬停:每个维度一块扇区 */}\n {data.map((_row, position) => {\n const a = polar(position - 0.5, count, 1.2);\n const b = polar(position + 0.5, count, 1.2);\n return (\n <path\n key={`sector-${position}`}\n d={`M${CX} ${CY} L${a.x} ${a.y} L${b.x} ${b.y} Z`}\n {...css.attrs(radarChartStyles.sector)}\n onPointerenter={() => (active = position)}\n />\n );\n })}\n </svg>\n );\n\n return (\n <ChartFrame\n {...rest}\n unit={unit}\n height={height}\n legend={(legend ?? resolved.length > 1) ? resolved : []}\n hiddenKeys={hiddenSeries}\n onLegendToggle={toggle}\n loading={loading}\n skeleton={<Skeleton sx={radarChartStyles.skeleton} />}\n isEmpty={data.length === 0}\n empty={empty}\n table={{\n columns: [\n lang.chartCategory,\n ...resolved.map((item) => (unit ? `${item.label}(${unit})` : item.label)),\n ],\n rows: data.map((row) => ({\n header: String(row[index] ?? ''),\n cells: resolved.map((item) => {\n const value = seriesValue(row, item.key);\n return value === undefined ? lang.emptyValue : format.number(value, valueFormat);\n }),\n })),\n }}\n plot={plot}\n />\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
@@ -5,8 +5,8 @@ import { Skeleton } from "../feedback/Skeleton.js";
|
|
|
5
5
|
import { ChartFrame, chartTooltip, useChartValueText } from "./frame.js";
|
|
6
6
|
import { flowData, resolveFlowNodes } from "./flow.js";
|
|
7
7
|
import { useSingleChart } from "./single.js";
|
|
8
|
-
import { computed, createVNode,
|
|
9
|
-
import { _component, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
8
|
+
import { computed, createVNode, shallowRef } from "vue";
|
|
9
|
+
import { _component, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
10
10
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
11
11
|
import { FitMode, Sankey, SankeyNodeAlign } from "@unovis/ts";
|
|
12
12
|
//#region src/charts/SankeyChart.tsx
|
|
@@ -109,7 +109,7 @@ var SankeyChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
111
|
}));
|
|
112
|
-
return () => createVNode(ChartFrame,
|
|
112
|
+
return () => createVNode(ChartFrame, mergeJsxProps(rest, {
|
|
113
113
|
"sx": [sankeyChartStyles.root, sx.value],
|
|
114
114
|
"unit": unit.value,
|
|
115
115
|
"height": height.value,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SankeyChart.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_computed","_vue_shallowRef","_createVNode","_mergeProps"],"sources":["../../src/charts/SankeyChart.tsx"],"sourcesContent":["/**\n * 桑基图:节点之间的流量画成粗细不同的带子,看量从哪来、到哪去、在哪流失(转化漏斗、预算分配、能源流向)。\n *\n * <SankeyChart aria-label=\"注册转化\" nodes={steps} links={flows} unit=\"人\" />\n *\n * 节点是 { id, label?, color? },流向是 { source, target, value }(value 可以是 Decimal、bigint,要大于 0;不能成环)。\n * 带子取起点节点的颜色;悬停带子出“起点 → 终点”的流量,悬停节点出它的总量。节点旁写名字与总量。\n * 单位、加载与空状态、给读屏的数据表(每条流向一行)同其他图表(见 ChartFrame)。\n */\nimport { FitMode, Sankey, SankeyNodeAlign } from '@unovis/ts';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { useFormat, type NumericFormat } from '../config/lang/format';\nimport { colors, fontSizes } from '../config/theme/tokens';\nimport { Skeleton } from '../feedback/Skeleton';\nimport type { PartProps } from '../utils/props';\nimport { flowData, resolveFlowNodes, type FlowLink, type FlowNode } from './flow';\nimport { ChartFrame, chartTooltip, useChartValueText, type ChartFrameParts } from './frame';\nimport { useSingleChart, type SingleChart } from './single';\n\n/** 节点怎样排:justify 两端对齐(默认),left、right 靠向一侧,center 居中 */\nexport interface SankeyAligns {\n /** 两端对齐(默认):按离起点的远近分列,没有流出的节点都排到最右一列 */\n justify: unknown;\n /** 靠左:每个节点排在离起点最近的一列 */\n left: unknown;\n /** 靠右:每个节点排在离终点最近的一列 */\n right: unknown;\n /** 居中:同靠左,但没有流入的节点往右挪,贴着它流向的节点 */\n center: unknown;\n}\n\n/** 未声明的属性落到外层 div(role=\"figure\")上,aria-label 是图表的名称 */\nexport interface SankeyChartProps extends PartProps<'div', 'role' | 'aria-busy'>, ChartFrameParts {\n /** 节点 */\n nodes: readonly FlowNode[];\n /** 流向 */\n links: readonly FlowLink[];\n /** 数值的格式(可缩放,如以万元为单位):节点旁的总量、提示框、数据表共用 */\n valueFormat?: NumericFormat;\n /** 单位:显示在左上方,数值后面带上 */\n unit?: string;\n /**\n * 绘图区的高度;数字按 px\n * @default 320\n */\n height?: number | string;\n /**\n * 节点的排法\n * @default 'justify'\n */\n align?: keyof SankeyAligns;\n /**\n * 悬停时的提示框\n * @default true\n */\n tooltip?: boolean;\n /**\n * 加载中:绘图区显示骨架,带 aria-busy\n * @default false\n */\n loading?: boolean;\n /** 没有数据时显示的内容,默认是“暂无数据” */\n empty?: VNodeChild;\n}\n\n/** SankeyChart 的样式:root(--vis-sankey-* 主题变量)、skeleton */\nexport const sankeyChartStyles = css.create({\n root: {\n '--vis-sankey-node-label-color': colors.text,\n '--vis-sankey-node-label-font-size': fontSizes.xs,\n '--vis-sankey-node-label-font-weight': '500',\n '--vis-sankey-node-label-background-fill-color': colors.surface,\n '--vis-sankey-node-label-background-stroke-color': colors.borderMuted,\n '--vis-sankey-node-sublabel-font-size': fontSizes.xs,\n '--vis-sankey-link-opacity': '0.35',\n '--vis-sankey-link-hover-opacity': '0.6',\n },\n skeleton: { display: 'block', width: '100%', height: '100%' },\n});\n\nconst ALIGNS: Record<keyof SankeyAligns, SankeyNodeAlign> = {\n justify: SankeyNodeAlign.Justify,\n left: SankeyNodeAlign.Left,\n right: SankeyNodeAlign.Right,\n center: SankeyNodeAlign.Center,\n};\n\n/** 交给 Unovis 的节点、流向 */\ntype Data = ReturnType<typeof flowData>;\ntype Node = Data['nodes'][number];\ntype Link = Data['links'][number];\n/** Unovis 布局后:流向的起点、终点换成了节点对象,节点带上了总量 value */\ntype LaidOutNode = Node & { value?: number };\ntype LaidOutLink = Omit<Link, 'source' | 'target'> & { source: LaidOutNode; target: LaidOutNode };\n\n/** 桑基图 */\nexport const SankeyChart = _component(\n ({\n nodes,\n links,\n valueFormat,\n unit,\n height = 320,\n align = 'justify',\n tooltip = true,\n loading = false,\n empty,\n sx,\n ...rest\n }: SankeyChartProps) => {\n const lang = useLang();\n const format = useFormat();\n const resolved = _derived(resolveFlowNodes(nodes));\n const valueText = useChartValueText(() => ({ valueFormat, unit }));\n const labelOf = (id: string) => resolved.get(id)?.label ?? id;\n\n let plot = _state.raw<HTMLElement | null>(null);\n const chart: SingleChart = useSingleChart<{ nodes: Node[]; links?: Link[] }>(() => ({\n element: plot,\n ready: !loading && nodes.length > 0,\n tooltip,\n build: (instance) => {\n const sankey = instance(\n 'sankey',\n () => new Sankey<Node, Link>({ linkValue: (link) => link.amount }),\n );\n sankey.setConfig({\n linkValue: (link) => link.amount,\n nodeColor: (node) => node.color,\n linkColor: (link) => link.source.color,\n nodeAlign: ALIGNS[align],\n label: (node) => node.label,\n subLabel: (node) => (node.value === undefined ? '' : valueText(node.value)),\n nodePadding: 16,\n nodeWidth: 12,\n labelMaxWidth: 120,\n labelMaxWidthTakeAvailableSpace: true,\n labelFit: FitMode.Wrap,\n labelBackground: true,\n enableZoom: false,\n });\n return {\n component: sankey,\n data: flowData(resolved, links),\n triggers: {\n [Sankey.selectors.link]: (link: LaidOutLink) =>\n chart.tooltip(\n chartTooltip(lang.chartFlowLabel(link.source.label, link.target.label), [\n {\n label: lang.chartValue,\n color: link.source.color,\n value: valueText(link.value),\n },\n ]),\n ),\n [Sankey.selectors.node]: (node: LaidOutNode) =>\n chart.tooltip(\n chartTooltip(node.label, [\n { label: lang.chartTotal, color: node.color, value: valueText(node.value) },\n ]),\n ),\n },\n };\n },\n }));\n\n return () => (\n <ChartFrame\n {...rest}\n sx={[sankeyChartStyles.root, sx]}\n unit={unit}\n height={height}\n legend={[]}\n hiddenKeys={[]}\n onLegendToggle={() => {}}\n loading={loading}\n skeleton={<Skeleton sx={sankeyChartStyles.skeleton} />}\n isEmpty={nodes.length === 0}\n empty={empty}\n table={{\n columns: [lang.chartFlow, unit ? `${lang.chartValue}(${unit})` : lang.chartValue],\n rows: links.map((link) => ({\n header: lang.chartFlowLabel(labelOf(link.source), labelOf(link.target)),\n cells: [format.number(link.value, valueFormat)],\n })),\n }}\n plotRef={(element) => (plot = element)}\n />\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAqEA,IAAa,oBAAoB,IAAI,OAAO;CAC1C,MAAM;EACJ,iCAAiC,OAAO;EACxC,qCAAqC,UAAU;EAC/C,uCAAuC;EACvC,iDAAiD,OAAO;EACxD,mDAAmD,OAAO;EAC1D,wCAAwC,UAAU;EAClD,6BAA6B;EAC7B,mCAAmC;CACrC;CACA,UAAU;EAAE,SAAS;EAAS,OAAO;EAAQ,QAAQ;CAAO;AAC9D,CAAC;AAED,IAAM,SAAsD;CAC1D,SAAS,gBAAgB;CACzB,MAAM,gBAAgB;CACtB,OAAO,gBAAgB;CACvB,QAAQ,gBAAgB;AAC1B;;;;AAWA,IAAa,cAAc,0BACzB,QAAA,SAYwB;CAAA,MAAA,SAAA,CAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,cAAAA,aAAA,QAAA,aAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,UAPb,GAAG;CAAA,MAAA,QAAAA,aAAA,QAAA,SACJ,SAAS;CAAA,MAAA,UAAAA,aAAA,QAAA,WACP,IAAI;CAAA,MAAA,UAAAA,aAAA,QAAA,WACJ,KAAK;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,KAAAA,aAAA,QAAA,IAAA;CAAA,MAGZ,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,OAAO,QAAQ;CACrB,MAAM,SAAS,UAAU;CACzB,MAAM,WAAQC,eAAY,iBAAgB,MAAA,KAAM,CAAC;CACjD,MAAM,YAAY,yBAAyB;EAAE,aAAW,YAAA;EAAE,MAAI,KAAA;CAAC,EAAE;CACjE,MAAM,WAAW,OAAe,SAAA,MAAS,IAAI,EAAE,CAAC,EAAE,SAAS;CAE3D,IAAI,OAAIC,WAAkC,IAAI;CAC9C,MAAM,QAAqB,sBAAyD;EAClF,SAAO,KAAA;EACP,OAAO,CAAA,QAAA,SAAY,MAAA,MAAM,SAAS;EAClC,SAAO,QAAA;EACP,QAAQ,aAAa;GACnB,MAAM,SAAS,SACb,gBACM,IAAI,OAAmB,EAAE,YAAY,SAAS,KAAK,OAAO,CAAC,CACnE;GACA,OAAO,UAAU;IACf,YAAY,SAAS,KAAK;IAC1B,YAAY,SAAS,KAAK;IAC1B,YAAY,SAAS,KAAK,OAAO;IACjC,WAAW,OAAM,MAAA;IACjB,QAAQ,SAAS,KAAK;IACtB,WAAW,SAAU,KAAK,UAAU,KAAA,IAAY,KAAK,UAAU,KAAK,KAAK;IACzE,aAAa;IACb,WAAW;IACX,eAAe;IACf,iCAAiC;IACjC,UAAU,QAAQ;IAClB,iBAAiB;IACjB,YAAY;GACd,CAAC;GACD,OAAO;IACL,WAAW;IACX,MAAM,SAAQ,SAAA,OAAA,MAAA,KAAgB;IAC9B,UAAU;MACP,OAAO,UAAU,QAAQ,SACxB,MAAM,QACJ,aAAa,KAAK,eAAe,KAAK,OAAO,OAAO,KAAK,OAAO,KAAK,GAAG,CACtE;MACE,OAAO,KAAK;MACZ,OAAO,KAAK,OAAO;MACnB,OAAO,UAAU,KAAK,KAAK;KAC7B,CAAC,CACF,CACH;MACD,OAAO,UAAU,QAAQ,SACxB,MAAM,QACJ,aAAa,KAAK,OAAO,CACvB;MAAE,OAAO,KAAK;MAAY,OAAO,KAAK;MAAO,OAAO,UAAU,KAAK,KAAK;KAAE,CAAC,CAC5E,CACH;IACJ;GACF;EACF;CACF,EAAE;CAEF,aAAOC,YAAA,YAAAC,
|
|
1
|
+
{"version":3,"file":"SankeyChart.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_computed","_vue_shallowRef","_createVNode","_mergeProps"],"sources":["../../src/charts/SankeyChart.tsx"],"sourcesContent":["/**\n * 桑基图:节点之间的流量画成粗细不同的带子,看量从哪来、到哪去、在哪流失(转化漏斗、预算分配、能源流向)。\n *\n * <SankeyChart aria-label=\"注册转化\" nodes={steps} links={flows} unit=\"人\" />\n *\n * 节点是 { id, label?, color? },流向是 { source, target, value }(value 可以是 Decimal、bigint,要大于 0;不能成环)。\n * 带子取起点节点的颜色;悬停带子出“起点 → 终点”的流量,悬停节点出它的总量。节点旁写名字与总量。\n * 单位、加载与空状态、给读屏的数据表(每条流向一行)同其他图表(见 ChartFrame)。\n */\nimport { FitMode, Sankey, SankeyNodeAlign } from '@unovis/ts';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { useFormat, type NumericFormat } from '../config/lang/format';\nimport { colors, fontSizes } from '../config/theme/tokens';\nimport { Skeleton } from '../feedback/Skeleton';\nimport type { PartProps } from '../utils/props';\nimport { flowData, resolveFlowNodes, type FlowLink, type FlowNode } from './flow';\nimport { ChartFrame, chartTooltip, useChartValueText, type ChartFrameParts } from './frame';\nimport { useSingleChart, type SingleChart } from './single';\n\n/** 节点怎样排:justify 两端对齐(默认),left、right 靠向一侧,center 居中 */\nexport interface SankeyAligns {\n /** 两端对齐(默认):按离起点的远近分列,没有流出的节点都排到最右一列 */\n justify: unknown;\n /** 靠左:每个节点排在离起点最近的一列 */\n left: unknown;\n /** 靠右:每个节点排在离终点最近的一列 */\n right: unknown;\n /** 居中:同靠左,但没有流入的节点往右挪,贴着它流向的节点 */\n center: unknown;\n}\n\n/** 未声明的属性落到外层 div(role=\"figure\")上,aria-label 是图表的名称 */\nexport interface SankeyChartProps extends PartProps<'div', 'role' | 'aria-busy'>, ChartFrameParts {\n /** 节点 */\n nodes: readonly FlowNode[];\n /** 流向 */\n links: readonly FlowLink[];\n /** 数值的格式(可缩放,如以万元为单位):节点旁的总量、提示框、数据表共用 */\n valueFormat?: NumericFormat;\n /** 单位:显示在左上方,数值后面带上 */\n unit?: string;\n /**\n * 绘图区的高度;数字按 px\n * @default 320\n */\n height?: number | string;\n /**\n * 节点的排法\n * @default 'justify'\n */\n align?: keyof SankeyAligns;\n /**\n * 悬停时的提示框\n * @default true\n */\n tooltip?: boolean;\n /**\n * 加载中:绘图区显示骨架,带 aria-busy\n * @default false\n */\n loading?: boolean;\n /** 没有数据时显示的内容,默认是“暂无数据” */\n empty?: VNodeChild;\n}\n\n/** SankeyChart 的样式:root(--vis-sankey-* 主题变量)、skeleton */\nexport const sankeyChartStyles = css.create({\n root: {\n '--vis-sankey-node-label-color': colors.text,\n '--vis-sankey-node-label-font-size': fontSizes.xs,\n '--vis-sankey-node-label-font-weight': '500',\n '--vis-sankey-node-label-background-fill-color': colors.surface,\n '--vis-sankey-node-label-background-stroke-color': colors.borderMuted,\n '--vis-sankey-node-sublabel-font-size': fontSizes.xs,\n '--vis-sankey-link-opacity': '0.35',\n '--vis-sankey-link-hover-opacity': '0.6',\n },\n skeleton: { display: 'block', width: '100%', height: '100%' },\n});\n\nconst ALIGNS: Record<keyof SankeyAligns, SankeyNodeAlign> = {\n justify: SankeyNodeAlign.Justify,\n left: SankeyNodeAlign.Left,\n right: SankeyNodeAlign.Right,\n center: SankeyNodeAlign.Center,\n};\n\n/** 交给 Unovis 的节点、流向 */\ntype Data = ReturnType<typeof flowData>;\ntype Node = Data['nodes'][number];\ntype Link = Data['links'][number];\n/** Unovis 布局后:流向的起点、终点换成了节点对象,节点带上了总量 value */\ntype LaidOutNode = Node & { value?: number };\ntype LaidOutLink = Omit<Link, 'source' | 'target'> & { source: LaidOutNode; target: LaidOutNode };\n\n/** 桑基图 */\nexport const SankeyChart = _component(\n ({\n nodes,\n links,\n valueFormat,\n unit,\n height = 320,\n align = 'justify',\n tooltip = true,\n loading = false,\n empty,\n sx,\n ...rest\n }: SankeyChartProps) => {\n const lang = useLang();\n const format = useFormat();\n const resolved = _derived(resolveFlowNodes(nodes));\n const valueText = useChartValueText(() => ({ valueFormat, unit }));\n const labelOf = (id: string) => resolved.get(id)?.label ?? id;\n\n let plot = _state.raw<HTMLElement | null>(null);\n const chart: SingleChart = useSingleChart<{ nodes: Node[]; links?: Link[] }>(() => ({\n element: plot,\n ready: !loading && nodes.length > 0,\n tooltip,\n build: (instance) => {\n const sankey = instance(\n 'sankey',\n () => new Sankey<Node, Link>({ linkValue: (link) => link.amount }),\n );\n sankey.setConfig({\n linkValue: (link) => link.amount,\n nodeColor: (node) => node.color,\n linkColor: (link) => link.source.color,\n nodeAlign: ALIGNS[align],\n label: (node) => node.label,\n subLabel: (node) => (node.value === undefined ? '' : valueText(node.value)),\n nodePadding: 16,\n nodeWidth: 12,\n labelMaxWidth: 120,\n labelMaxWidthTakeAvailableSpace: true,\n labelFit: FitMode.Wrap,\n labelBackground: true,\n enableZoom: false,\n });\n return {\n component: sankey,\n data: flowData(resolved, links),\n triggers: {\n [Sankey.selectors.link]: (link: LaidOutLink) =>\n chart.tooltip(\n chartTooltip(lang.chartFlowLabel(link.source.label, link.target.label), [\n {\n label: lang.chartValue,\n color: link.source.color,\n value: valueText(link.value),\n },\n ]),\n ),\n [Sankey.selectors.node]: (node: LaidOutNode) =>\n chart.tooltip(\n chartTooltip(node.label, [\n { label: lang.chartTotal, color: node.color, value: valueText(node.value) },\n ]),\n ),\n },\n };\n },\n }));\n\n return () => (\n <ChartFrame\n {...rest}\n sx={[sankeyChartStyles.root, sx]}\n unit={unit}\n height={height}\n legend={[]}\n hiddenKeys={[]}\n onLegendToggle={() => {}}\n loading={loading}\n skeleton={<Skeleton sx={sankeyChartStyles.skeleton} />}\n isEmpty={nodes.length === 0}\n empty={empty}\n table={{\n columns: [lang.chartFlow, unit ? `${lang.chartValue}(${unit})` : lang.chartValue],\n rows: links.map((link) => ({\n header: lang.chartFlowLabel(labelOf(link.source), labelOf(link.target)),\n cells: [format.number(link.value, valueFormat)],\n })),\n }}\n plotRef={(element) => (plot = element)}\n />\n );\n },\n);\n"],"mappings":"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"}
|
|
@@ -2,8 +2,8 @@ import { colors } from "../config/theme/tokens.js";
|
|
|
2
2
|
import { useFormat } from "../config/lang/format.js";
|
|
3
3
|
import { seriesNumber } from "./series.js";
|
|
4
4
|
import { XYChart } from "./XYChart.js";
|
|
5
|
-
import { computed, createVNode
|
|
6
|
-
import { _component, bindable, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
5
|
+
import { computed, createVNode } from "vue";
|
|
6
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
7
7
|
import { Scatter } from "@unovis/ts";
|
|
8
8
|
//#region src/charts/ScatterChart.tsx
|
|
9
9
|
/**
|
|
@@ -60,7 +60,7 @@ var ScatterChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
60
60
|
return points;
|
|
61
61
|
})
|
|
62
62
|
}));
|
|
63
|
-
return () => createVNode(XYChart,
|
|
63
|
+
return () => createVNode(XYChart, mergeJsxProps(rest, {
|
|
64
64
|
"_hiddenSeries": hiddenSeries.value,
|
|
65
65
|
"onUpdate:_hiddenSeries": _cache[0] || (_cache[0] = ($event) => hiddenSeries.value = $event),
|
|
66
66
|
"kind": kind.value
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScatterChart.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/charts/ScatterChart.tsx"],"sourcesContent":["/**\n * 散点图:横轴、纵轴都是数值,每一行在每个系列上画一个点;size 给成字段时点的大小随它变(气泡图)。\n * 悬停一个点,提示框列出这一行的名称、横轴的值与各系列的值。\n *\n * <ScatterChart data={cities} index=\"name\" x=\"gdp\" xLabel=\"人均 GDP\" series={[{ key: 'life', label: '预期寿命' }]} />\n * <ScatterChart data={products} index=\"name\" x=\"price\" series={[{ key: 'rating', label: '评分' }]} size=\"sales\" />\n *\n * 纵轴不强制从 0 起(按数据取);图例、单位、加载与空状态、给读屏的数据表同其他图表(见 XYChart)。\n */\nimport { Scatter } from '@unovis/ts';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { colors } from '../config/theme/tokens';\nimport { useFormat } from '../config/lang/format';\nimport { seriesNumber, type NumericKey } from './series';\nimport { XYChart, type XYChartKind, type XYChartProps } from './XYChart';\n\n/** 未声明的属性落到外层 div(role=\"figure\")上 */\nexport interface ScatterChartProps<T extends object> extends XYChartProps<T> {\n /** 横轴的数值字段 */\n x: NumericKey<T>;\n /** 横轴的名字:显示在横轴下方,提示框与数据表里也用;不写时轴下不显示,提示框与数据表用字段名 */\n xLabel?: string;\n /** 横轴数值的格式(Intl.NumberFormatOptions) */\n xFormat?: Intl.NumberFormatOptions;\n /**\n * 点的直径(px),或者一个数值字段(气泡图:按它在 6–32px 之间缩放)\n * @default 9\n */\n size?: number | NumericKey<T>;\n}\n\n/** 气泡图的大小范围(px) */\nconst BUBBLE_RANGE: [number, number] = [6, 32];\n\n/** 散点图 */\nexport const ScatterChart = _component(\n <T extends object>({\n x: xKey,\n xLabel,\n xFormat,\n size = 9,\n _hiddenSeries: hiddenSeries = _bindable<string[]>([]),\n ...rest\n }: ScatterChartProps<T>) => {\n const format = useFormat();\n const kind = _derived<XYChartKind<T>>({\n zeroBaseline: false,\n pointTooltip: Scatter.selectors.point,\n // 留出半个最大气泡的边:贴边的点不被裁掉\n padding: { left: 18, right: 18, top: 18, bottom: 8 },\n continuousX: {\n value: (row) => seriesNumber(row, xKey),\n format: (value) => format.number(value, xFormat),\n label: xLabel ?? xKey,\n title: xLabel,\n },\n components: ({ series, x, y, instance }) =>\n series.map((item) => {\n const points = instance(`points:${item.key}`, () => new Scatter<T>({ x, y: () => 0 }));\n points.setConfig({\n x,\n y: y(item),\n color: item.color,\n size: typeof size === 'number' ? size : (row: T) => seriesNumber(row, size) ?? 0,\n sizeRange: typeof size === 'number' ? undefined : BUBBLE_RANGE,\n // 气泡互相重叠时用表面色描一圈边分开\n strokeColor: colors.surface,\n strokeWidth: 1,\n });\n return points;\n }),\n });\n return () => <XYChart {...rest} _hiddenSeries={hiddenSeries} kind={kind} />;\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgCA,IAAM,eAAiC,CAAC,GAAG,EAAE;;AAG7C,IAAa,eAAe,0BAC1B,QAAA,SAO4B;CAAA,MAAA,SAAA,CAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,GAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,QAHnB,CAAC;CAAA,MAAA,eAAAC,SAAA,QAAA,uBAC0C,CAAA,GAAE,KAAA,IAAA;CAAA,MACjD,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,SAAS,UAAU;CACzB,MAAM,OAAIC,gBAA4B;EACpC,cAAc;EACd,cAAc,QAAQ,UAAU;EAEhC,SAAS;GAAE,MAAM;GAAI,OAAO;GAAI,KAAK;GAAI,QAAQ;EAAE;EACnD,aAAa;GACX,QAAQ,QAAQ,aAAa,KAAG,KAAA,KAAM;GACtC,SAAS,UAAU,OAAO,OAAO,OAAK,QAAA,KAAS;GAC/C,OAAO,OAAA,SAAA,KAAA;GACP,OAAK,OAAA;EACP;EACA,aAAa,EAAE,QAAQ,GAAG,GAAG,eAC3B,OAAO,KAAK,SAAS;GACnB,MAAM,SAAS,SAAS,UAAU,KAAK,aAAa,IAAI,QAAW;IAAE;IAAG,SAAS;GAAE,CAAC,CAAC;GACrF,OAAO,UAAU;IACf;IACA,GAAG,EAAE,IAAI;IACT,OAAO,KAAK;IACZ,MAAM,OAAA,KAAA,UAAgB,WAAQ,KAAA,SAAW,QAAW,aAAa,KAAG,KAAA,KAAM,KAAK;IAC/E,WAAW,OAAA,KAAA,UAAgB,WAAW,KAAA,IAAY;IAElD,aAAa,OAAO;IACpB,aAAa;GACf,CAAC;GACD,OAAO;EACT,CAAC;CACL,EAAC;CACD,aAAOC,YAAA,SAAAC,
|
|
1
|
+
{"version":3,"file":"ScatterChart.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/charts/ScatterChart.tsx"],"sourcesContent":["/**\n * 散点图:横轴、纵轴都是数值,每一行在每个系列上画一个点;size 给成字段时点的大小随它变(气泡图)。\n * 悬停一个点,提示框列出这一行的名称、横轴的值与各系列的值。\n *\n * <ScatterChart data={cities} index=\"name\" x=\"gdp\" xLabel=\"人均 GDP\" series={[{ key: 'life', label: '预期寿命' }]} />\n * <ScatterChart data={products} index=\"name\" x=\"price\" series={[{ key: 'rating', label: '评分' }]} size=\"sales\" />\n *\n * 纵轴不强制从 0 起(按数据取);图例、单位、加载与空状态、给读屏的数据表同其他图表(见 XYChart)。\n */\nimport { Scatter } from '@unovis/ts';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { colors } from '../config/theme/tokens';\nimport { useFormat } from '../config/lang/format';\nimport { seriesNumber, type NumericKey } from './series';\nimport { XYChart, type XYChartKind, type XYChartProps } from './XYChart';\n\n/** 未声明的属性落到外层 div(role=\"figure\")上 */\nexport interface ScatterChartProps<T extends object> extends XYChartProps<T> {\n /** 横轴的数值字段 */\n x: NumericKey<T>;\n /** 横轴的名字:显示在横轴下方,提示框与数据表里也用;不写时轴下不显示,提示框与数据表用字段名 */\n xLabel?: string;\n /** 横轴数值的格式(Intl.NumberFormatOptions) */\n xFormat?: Intl.NumberFormatOptions;\n /**\n * 点的直径(px),或者一个数值字段(气泡图:按它在 6–32px 之间缩放)\n * @default 9\n */\n size?: number | NumericKey<T>;\n}\n\n/** 气泡图的大小范围(px) */\nconst BUBBLE_RANGE: [number, number] = [6, 32];\n\n/** 散点图 */\nexport const ScatterChart = _component(\n <T extends object>({\n x: xKey,\n xLabel,\n xFormat,\n size = 9,\n _hiddenSeries: hiddenSeries = _bindable<string[]>([]),\n ...rest\n }: ScatterChartProps<T>) => {\n const format = useFormat();\n const kind = _derived<XYChartKind<T>>({\n zeroBaseline: false,\n pointTooltip: Scatter.selectors.point,\n // 留出半个最大气泡的边:贴边的点不被裁掉\n padding: { left: 18, right: 18, top: 18, bottom: 8 },\n continuousX: {\n value: (row) => seriesNumber(row, xKey),\n format: (value) => format.number(value, xFormat),\n label: xLabel ?? xKey,\n title: xLabel,\n },\n components: ({ series, x, y, instance }) =>\n series.map((item) => {\n const points = instance(`points:${item.key}`, () => new Scatter<T>({ x, y: () => 0 }));\n points.setConfig({\n x,\n y: y(item),\n color: item.color,\n size: typeof size === 'number' ? size : (row: T) => seriesNumber(row, size) ?? 0,\n sizeRange: typeof size === 'number' ? undefined : BUBBLE_RANGE,\n // 气泡互相重叠时用表面色描一圈边分开\n strokeColor: colors.surface,\n strokeWidth: 1,\n });\n return points;\n }),\n });\n return () => <XYChart {...rest} _hiddenSeries={hiddenSeries} kind={kind} />;\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgCA,IAAM,eAAiC,CAAC,GAAG,EAAE;;AAG7C,IAAa,eAAe,0BAC1B,QAAA,SAO4B;CAAA,MAAA,SAAA,CAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,GAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,QAHnB,CAAC;CAAA,MAAA,eAAAC,SAAA,QAAA,uBAC0C,CAAA,GAAE,KAAA,IAAA;CAAA,MACjD,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,SAAS,UAAU;CACzB,MAAM,OAAIC,gBAA4B;EACpC,cAAc;EACd,cAAc,QAAQ,UAAU;EAEhC,SAAS;GAAE,MAAM;GAAI,OAAO;GAAI,KAAK;GAAI,QAAQ;EAAE;EACnD,aAAa;GACX,QAAQ,QAAQ,aAAa,KAAG,KAAA,KAAM;GACtC,SAAS,UAAU,OAAO,OAAO,OAAK,QAAA,KAAS;GAC/C,OAAO,OAAA,SAAA,KAAA;GACP,OAAK,OAAA;EACP;EACA,aAAa,EAAE,QAAQ,GAAG,GAAG,eAC3B,OAAO,KAAK,SAAS;GACnB,MAAM,SAAS,SAAS,UAAU,KAAK,aAAa,IAAI,QAAW;IAAE;IAAG,SAAS;GAAE,CAAC,CAAC;GACrF,OAAO,UAAU;IACf;IACA,GAAG,EAAE,IAAI;IACT,OAAO,KAAK;IACZ,MAAM,OAAA,KAAA,UAAgB,WAAQ,KAAA,SAAW,QAAW,aAAa,KAAG,KAAA,KAAM,KAAK;IAC/E,WAAW,OAAA,KAAA,UAAgB,WAAW,KAAA,IAAY;IAElD,aAAa,OAAO;IACpB,aAAa;GACf,CAAC;GACD,OAAO;EACT,CAAC;CACL,EAAC;CACD,aAAOC,YAAA,SAAAC,cAAmB,MAAI;EAAA,iBAAA,aAAA;EAAA,0BAAA,OAAA,OAAA,OAAA,MAAA,WAAA,aAAA,QAAA;EAAA,QAAA,KAAA;CAAA,CAAA,GAAA,IAAA;AAChC,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
|
@@ -2,11 +2,12 @@ import { useLang } from "../config/context.js";
|
|
|
2
2
|
import { parseIsoDate, parseIsoDateTime } from "../utils/data/date.js";
|
|
3
3
|
import { useFormat } from "../config/lang/format.js";
|
|
4
4
|
import { Skeleton } from "../feedback/Skeleton.js";
|
|
5
|
+
import { useDetachedRender } from "../overlay/detached.js";
|
|
5
6
|
import { ChartFrame, chartDuration, chartTooltip, createChartInstances, uniqueHighlightFilter, usePlotSeen } from "./frame.js";
|
|
6
7
|
import { resolveColor } from "./series.js";
|
|
7
8
|
import { xyChartStyles } from "./XYChart.js";
|
|
8
|
-
import { computed, createVNode,
|
|
9
|
-
import { _component, bindable, effect, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
9
|
+
import { computed, createVNode, shallowRef, useId } from "vue";
|
|
10
|
+
import { _component, bindable, effect, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
10
11
|
import { getLocalTimeZone } from "@internationalized/date";
|
|
11
12
|
import { Axis, Plotline, PlotlineLabelPosition, PlotlineLineStylePresets, Scale, Timeline, Tooltip, XYContainer } from "@unovis/ts";
|
|
12
13
|
//#region src/charts/TimelineChart.tsx
|
|
@@ -110,13 +111,13 @@ var TimelineChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
110
111
|
}] : [], ...markers.value ?? []];
|
|
111
112
|
const instances = createChartInstances();
|
|
112
113
|
let tooltipElement;
|
|
114
|
+
const renderDetached = useDetachedRender();
|
|
113
115
|
let plot = shallowRef(null);
|
|
114
116
|
const seen = usePlotSeen(() => plot.value);
|
|
115
117
|
let container;
|
|
116
118
|
let containerElement;
|
|
117
119
|
effect(() => () => {
|
|
118
120
|
container?.destroy();
|
|
119
|
-
if (tooltipElement) render(null, tooltipElement);
|
|
120
121
|
});
|
|
121
122
|
effect(() => {
|
|
122
123
|
const data = segments();
|
|
@@ -171,7 +172,7 @@ var TimelineChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
171
172
|
tooltipBox.setConfig({ triggers: { [Timeline.selectors.line]: (segment) => {
|
|
172
173
|
tooltipElement ??= document.createElement("div");
|
|
173
174
|
const name = series.value.find((entry) => entry.key === segment.item.series)?.label;
|
|
174
|
-
|
|
175
|
+
renderDetached(chartTooltip(segment.item.label ?? segment.item.row, [
|
|
175
176
|
...name ? [{
|
|
176
177
|
label: name,
|
|
177
178
|
color: segment.color,
|
|
@@ -206,7 +207,7 @@ var TimelineChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
206
207
|
});
|
|
207
208
|
return () => {
|
|
208
209
|
const shown = segments();
|
|
209
|
-
return createVNode(ChartFrame,
|
|
210
|
+
return createVNode(ChartFrame, mergeJsxProps(rest, {
|
|
210
211
|
"sx": [xyChartStyles.hideCrosshairLine, rest.sx],
|
|
211
212
|
"height": rowOrder.value.length * rowHeight.value + 36,
|
|
212
213
|
"legend": series.value.map((item, position) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimelineChart.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_computed","_vue_shallowRef","_createVNode","_mergeProps"],"sources":["../../src/charts/TimelineChart.tsx"],"sourcesContent":["/**\n * 时间条图(甘特图式):每行一个任务(项目、设备、人),横轴是时间,每项一段时间条;\n * 计划画成宽而浅的轨道、实际画在上面,一眼看出提前还是拖期。可以标出“今天”与里程碑。\n *\n * <TimelineChart\n * aria-label=\"项目进度\"\n * items={[\n * { row: '官网改版', series: 'plan', start: '2026-03-01', end: '2026-06-30' },\n * { row: '官网改版', series: 'actual', start: '2026-03-10', end: '2026-07-20' },\n * ]}\n * series={[{ key: 'plan', label: '计划', variant: 'track' }, { key: 'actual', label: '实际' }]}\n * today\n * />\n *\n * 时间可以是 Date、时间戳或 ISO 字符串;只有日期('2026-06-30')的结束含这一天。行按 rows 的顺序,没给时按第一次出现。\n * 悬停一段显示名称、起止与天数;读屏的数据表列出每一项。绘图交给 Unovis 的 Timeline(XYContainer、时间轴)。\n */\nimport {\n Axis,\n Scale,\n Plotline,\n PlotlineLabelPosition,\n PlotlineLineStylePresets,\n Timeline,\n Tooltip,\n XYContainer,\n} from '@unovis/ts';\nimport { getLocalTimeZone } from '@internationalized/date';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { render, useId, type VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { useFormat } from '../config/lang/format';\nimport { Skeleton } from '../feedback/Skeleton';\nimport { parseIsoDate, parseIsoDateTime } from '../utils/data/date';\nimport type { PartProps } from '../utils/props';\nimport {\n ChartFrame,\n chartDuration,\n chartTooltip,\n createChartInstances,\n uniqueHighlightFilter,\n usePlotSeen,\n type ChartFrameParts,\n} from './frame';\nimport { resolveColor, type ChartColor } from './series';\nimport { xyChartStyles } from './XYChart';\n\n/** 一个时间点:Date、时间戳(毫秒)或 ISO 字符串(日期或日期时间) */\nexport type TimelineTime = Date | number | string;\n\n/** 一段时间条 */\nexport interface TimelineChartItem {\n /** 所在的行(任务、项目、设备) */\n row: string;\n /** 开始 */\n start: TimelineTime;\n /** 结束;只有日期时含这一天 */\n end: TimelineTime;\n /** 名称:提示框的标题;不写时是行名 */\n label?: string;\n /** 系列的 key(计划、实际):决定颜色、粗细与图例 */\n series?: string;\n /** 颜色:覆盖系列的颜色(拖期的一段标红) */\n color?: ChartColor;\n}\n\n/** 时间条的系列 */\nexport interface TimelineChartSeries {\n /** 标识:项的 series 指向它 */\n key: string;\n /** 名称:图例、提示框 */\n label: string;\n /** 颜色;不写时按顺序取色板 */\n color?: ChartColor;\n /** bar 实心的条(默认);track 宽而浅的轨道,画在下面(计划) */\n variant?: 'bar' | 'track';\n}\n\n/** 时间轴上的一条竖线:里程碑、截止日 */\nexport interface TimelineChartMarker {\n /** 时间 */\n at: TimelineTime;\n /** 文字 */\n label: string;\n /** 颜色,默认次要色 */\n color?: ChartColor;\n}\n\n/** 未声明的属性落到外层 div(role=\"figure\")上;aria-label 是图表的名称 */\nexport interface TimelineChartProps\n extends PartProps<'div', 'role' | 'aria-busy'>, ChartFrameParts {\n /** 各段时间条 */\n items: readonly TimelineChartItem[];\n /** 系列:颜色、图例与画法;不写时所有条同一个颜色 */\n series?: readonly TimelineChartSeries[];\n /** 行的顺序;不写时按第一次出现 */\n rows?: readonly string[];\n /** 标出今天(true)或指定的“今天” */\n today?: boolean | TimelineTime;\n /** 里程碑、截止日:竖线带文字 */\n markers?: readonly TimelineChartMarker[];\n /**\n * 每行的高度(px)\n * @default 32\n */\n rowHeight?: number;\n /** 刻度上日期的写法;不写时按刻度的间隔选(几个月一格写“4月”,几天一格写“10/24”,几小时一格写“14:00”) */\n dateFormat?: Intl.DateTimeFormatOptions;\n /** 加载中:绘图区显示骨架 */\n loading?: boolean;\n /** 隐藏的系列:点图例时写回,父级可以写 _hiddenSeries={keys} */\n _hiddenSeries?: string[];\n /** 没有数据时显示的内容 */\n empty?: VNodeChild;\n /** 悬停时的提示框,默认 true */\n tooltip?: boolean;\n}\n\n/** 画图用的一段:时间换成毫秒 */\ninterface Segment {\n item: TimelineChartItem;\n start: number;\n end: number;\n color: string;\n width: number;\n track: boolean;\n}\n\nconst DAY = 86_400_000;\nconst ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** 轨道(计划)的颜色:系列色调浅 */\nconst trackColor = (color: string) => `color-mix(in srgb, ${color} 30%, transparent)`;\n\n/** 按刻度的间隔选日期的写法:几个月一格写月,几天一格写月日,不到一天写时分 */\nfunction autoFormat(ticks: readonly Date[]): Intl.DateTimeFormatOptions {\n const step = ticks.length > 1 ? ticks[1]!.getTime() - ticks[0]!.getTime() : DAY;\n if (step >= 28 * DAY) return { month: 'short' };\n if (step >= DAY) return { month: 'numeric', day: 'numeric' };\n return { hour: '2-digit', minute: '2-digit' };\n}\n\n/** 时间点换成毫秒:ISO 字符串按语言的时区;只有日期的结束取到这一天的末尾 */\nfunction toTime(value: TimelineTime, timeZone: string, end = false): number {\n if (typeof value === 'number') return value;\n if (value instanceof Date) return value.getTime();\n // 只有日期:结束取到这一天的末尾(parseIsoDateTime 也接受只有日期的写法,要先认出来)\n const date = ISO_DATE.test(value) ? parseIsoDate(value) : null;\n if (date) return date.toDate(timeZone).getTime() + (end ? DAY : 0);\n const dateTime = parseIsoDateTime(value);\n if (dateTime) return dateTime.toDate(timeZone).getTime();\n return Date.parse(value);\n}\n\n/** 时间条图 */\nexport const TimelineChart = _component(\n ({\n items,\n series = [],\n rows,\n today,\n markers,\n rowHeight = 32,\n dateFormat,\n loading = false,\n _hiddenSeries: hiddenSeries = _bindable<string[]>([]),\n empty,\n tooltip = true,\n ...rest\n }: TimelineChartProps) => {\n const lang = useLang();\n const format = useFormat();\n const id = useId();\n const zone = () => lang.timeZone ?? getLocalTimeZone();\n const colorOf = (key: string | undefined) => {\n const position = series.findIndex((item) => item.key === key);\n return resolveColor(series[position]?.color, Math.max(position, 0));\n };\n const rowOrder = _derived(rows ?? [...new Set(items.map((item) => item.row))]);\n const visible = _derived(\n items.filter((item) => item.series === undefined || !hiddenSeries.includes(item.series)),\n );\n const dateText = (time: number) => format.date(new Date(time), { dateStyle: 'medium' });\n const daysText = (segment: Segment) =>\n lang.chartDays(Math.round((segment.end - segment.start) / DAY));\n\n function toggle(key: string) {\n hiddenSeries = hiddenSeries.includes(key)\n ? hiddenSeries.filter((item) => item !== key)\n : [...hiddenSeries, key];\n }\n\n /** 画图的各段:轨道先画(在下面),同一行里按开始时间 */\n const segments = (): Segment[] =>\n visible\n .map((item): Segment => {\n const track = series.find((entry) => entry.key === item.series)?.variant === 'track';\n return {\n item,\n start: toTime(item.start, zone()),\n end: toTime(item.end, zone(), true),\n color: item.color ? resolveColor(item.color, 0) : colorOf(item.series),\n width: track ? rowHeight * 0.62 : rowHeight * 0.32,\n track,\n };\n })\n .sort(\n (a, b) =>\n rowOrder.indexOf(a.item.row) - rowOrder.indexOf(b.item.row) ||\n Number(b.track) - Number(a.track) ||\n a.start - b.start,\n );\n\n /** 竖线:今天与里程碑 */\n const allMarkers = (): TimelineChartMarker[] => [\n ...(today !== undefined && today !== false\n ? [{ at: today === true ? Date.now() : today, label: lang.chartToday, color: 'danger' }]\n : []),\n ...(markers ?? []),\n ];\n\n const instances = createChartInstances();\n let tooltipElement: HTMLElement | undefined;\n let plot = _state.raw<HTMLElement | null>(null);\n const seen = usePlotSeen(() => plot);\n let container: XYContainer<Segment> | undefined;\n let containerElement: HTMLElement | undefined;\n _effect(() => () => {\n container?.destroy();\n if (tooltipElement) render(null, tooltipElement);\n });\n _effect(() => {\n const data = segments();\n const element = plot;\n if (!element || loading || items.length === 0 || !seen()) return;\n if (container && containerElement !== element) {\n container.destroy();\n container = undefined;\n instances.reset();\n }\n const times = [\n ...data.flatMap((segment) => [segment.start, segment.end]),\n ...allMarkers().map((marker) => toTime(marker.at, zone())),\n ];\n const ticks = Scale.scaleTime()\n .domain([Math.min(...times), Math.max(...times)])\n .ticks(6);\n const timeline = instances.get('timeline', () => new Timeline<Segment>({ x: () => 0 }));\n timeline.setConfig({\n x: (segment) => segment.start,\n lineDuration: (segment) => segment.end - segment.start,\n lineRow: (segment) => segment.item.row,\n lineWidth: (segment) => segment.width,\n color: (segment) => (segment.track ? trackColor(segment.color) : segment.color),\n lineCap: true,\n rowHeight,\n showRowLabels: true,\n rowMaxLabelWidth: 160,\n alternatingRowColors: true,\n });\n const lines = allMarkers().map((marker, position) => {\n const line = instances.get(`marker-${position}`, () => new Plotline<Segment>({}));\n line.setConfig({\n axis: 'x',\n value: toTime(marker.at, zone()),\n color: resolveColor(marker.color ?? 'neutral', 0),\n lineWidth: 1,\n lineStyle: PlotlineLineStylePresets.ShortDash,\n labelText: marker.label,\n labelPosition: PlotlineLabelPosition.TopRight,\n labelOffsetX: 4,\n labelOffsetY: 2,\n });\n return line;\n });\n const xAxis = instances.get('xAxis', () => new Axis<Segment>({ type: 'x' }));\n xAxis.setConfig({\n type: 'x',\n // 整齐的时间刻度:月初、周、日、整点(d3 的时间刻度)\n tickValues: ticks.map((tick) => tick.getTime()),\n tickFormat: (tick) => format.date(new Date(Number(tick)), dateFormat ?? autoFormat(ticks)),\n gridLine: true,\n tickLine: false,\n domainLine: false,\n tickPadding: 8,\n });\n const tooltipBox = instances.get('tooltip', () => new Tooltip());\n tooltipBox.setConfig({\n triggers: {\n [Timeline.selectors.line]: (segment: Segment) => {\n tooltipElement ??= document.createElement('div');\n const name = series.find((entry) => entry.key === segment.item.series)?.label;\n render(\n chartTooltip(segment.item.label ?? segment.item.row, [\n ...(name ? [{ label: name, color: segment.color, value: daysText(segment) }] : []),\n { label: lang.chartStart, color: 'transparent', value: dateText(segment.start) },\n { label: lang.chartEnd, color: 'transparent', value: dateText(segment.end - 1) },\n ]),\n tooltipElement,\n );\n return tooltipElement;\n },\n },\n });\n const config = {\n components: [timeline, ...lines],\n xAxis,\n tooltip: tooltip ? tooltipBox : undefined,\n ariaLabel: null,\n duration: chartDuration(element),\n };\n if (container) container.update(config, undefined, data);\n else {\n container = new XYContainer<Segment>(element, config, data);\n containerElement = element;\n uniqueHighlightFilter(element, `${id}-highlight`);\n }\n });\n\n return () => {\n const shown = segments();\n return (\n <ChartFrame\n {...rest}\n sx={[xyChartStyles.hideCrosshairLine, rest.sx]}\n // 行数 × 行高,加上时间轴\n height={rowOrder.length * rowHeight + 36}\n legend={series.map((item, position) => {\n const color = resolveColor(item.color, position);\n return {\n key: item.key,\n label: item.label,\n color: item.variant === 'track' ? trackColor(color) : color,\n };\n })}\n hiddenKeys={hiddenSeries}\n onLegendToggle={toggle}\n loading={loading}\n skeleton={<Skeleton sx={xyChartStyles.skeleton} />}\n isEmpty={items.length === 0}\n empty={empty}\n table={{\n columns: [\n lang.chartCategory,\n lang.chartSeries,\n lang.chartStart,\n lang.chartEnd,\n lang.chartDuration,\n ],\n rows: shown.map((segment) => ({\n header: segment.item.label ?? segment.item.row,\n cells: [\n series.find((entry) => entry.key === segment.item.series)?.label ?? lang.emptyValue,\n dateText(segment.start),\n dateText(segment.end - 1),\n daysText(segment),\n ],\n })),\n }}\n plotRef={(element) => (plot = element)}\n />\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TimelineChart.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_computed","_vue_shallowRef","_createVNode","_mergeProps"],"sources":["../../src/charts/TimelineChart.tsx"],"sourcesContent":["/**\n * 时间条图(甘特图式):每行一个任务(项目、设备、人),横轴是时间,每项一段时间条;\n * 计划画成宽而浅的轨道、实际画在上面,一眼看出提前还是拖期。可以标出“今天”与里程碑。\n *\n * <TimelineChart\n * aria-label=\"项目进度\"\n * items={[\n * { row: '官网改版', series: 'plan', start: '2026-03-01', end: '2026-06-30' },\n * { row: '官网改版', series: 'actual', start: '2026-03-10', end: '2026-07-20' },\n * ]}\n * series={[{ key: 'plan', label: '计划', variant: 'track' }, { key: 'actual', label: '实际' }]}\n * today\n * />\n *\n * 时间可以是 Date、时间戳或 ISO 字符串;只有日期('2026-06-30')的结束含这一天。行按 rows 的顺序,没给时按第一次出现。\n * 悬停一段显示名称、起止与天数;读屏的数据表列出每一项。绘图交给 Unovis 的 Timeline(XYContainer、时间轴)。\n */\nimport {\n Axis,\n Scale,\n Plotline,\n PlotlineLabelPosition,\n PlotlineLineStylePresets,\n Timeline,\n Tooltip,\n XYContainer,\n} from '@unovis/ts';\nimport { getLocalTimeZone } from '@internationalized/date';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { useId, type VNodeChild } from 'vue';\nimport { useDetachedRender } from '../overlay/detached';\nimport { useLang } from '../config/context';\nimport { useFormat } from '../config/lang/format';\nimport { Skeleton } from '../feedback/Skeleton';\nimport { parseIsoDate, parseIsoDateTime } from '../utils/data/date';\nimport type { PartProps } from '../utils/props';\nimport {\n ChartFrame,\n chartDuration,\n chartTooltip,\n createChartInstances,\n uniqueHighlightFilter,\n usePlotSeen,\n type ChartFrameParts,\n} from './frame';\nimport { resolveColor, type ChartColor } from './series';\nimport { xyChartStyles } from './XYChart';\n\n/** 一个时间点:Date、时间戳(毫秒)或 ISO 字符串(日期或日期时间) */\nexport type TimelineTime = Date | number | string;\n\n/** 一段时间条 */\nexport interface TimelineChartItem {\n /** 所在的行(任务、项目、设备) */\n row: string;\n /** 开始 */\n start: TimelineTime;\n /** 结束;只有日期时含这一天 */\n end: TimelineTime;\n /** 名称:提示框的标题;不写时是行名 */\n label?: string;\n /** 系列的 key(计划、实际):决定颜色、粗细与图例 */\n series?: string;\n /** 颜色:覆盖系列的颜色(拖期的一段标红) */\n color?: ChartColor;\n}\n\n/** 时间条的系列 */\nexport interface TimelineChartSeries {\n /** 标识:项的 series 指向它 */\n key: string;\n /** 名称:图例、提示框 */\n label: string;\n /** 颜色;不写时按顺序取色板 */\n color?: ChartColor;\n /** bar 实心的条(默认);track 宽而浅的轨道,画在下面(计划) */\n variant?: 'bar' | 'track';\n}\n\n/** 时间轴上的一条竖线:里程碑、截止日 */\nexport interface TimelineChartMarker {\n /** 时间 */\n at: TimelineTime;\n /** 文字 */\n label: string;\n /** 颜色,默认次要色 */\n color?: ChartColor;\n}\n\n/** 未声明的属性落到外层 div(role=\"figure\")上;aria-label 是图表的名称 */\nexport interface TimelineChartProps\n extends PartProps<'div', 'role' | 'aria-busy'>, ChartFrameParts {\n /** 各段时间条 */\n items: readonly TimelineChartItem[];\n /** 系列:颜色、图例与画法;不写时所有条同一个颜色 */\n series?: readonly TimelineChartSeries[];\n /** 行的顺序;不写时按第一次出现 */\n rows?: readonly string[];\n /** 标出今天(true)或指定的“今天” */\n today?: boolean | TimelineTime;\n /** 里程碑、截止日:竖线带文字 */\n markers?: readonly TimelineChartMarker[];\n /**\n * 每行的高度(px)\n * @default 32\n */\n rowHeight?: number;\n /** 刻度上日期的写法;不写时按刻度的间隔选(几个月一格写“4月”,几天一格写“10/24”,几小时一格写“14:00”) */\n dateFormat?: Intl.DateTimeFormatOptions;\n /** 加载中:绘图区显示骨架 */\n loading?: boolean;\n /** 隐藏的系列:点图例时写回,父级可以写 _hiddenSeries={keys} */\n _hiddenSeries?: string[];\n /** 没有数据时显示的内容 */\n empty?: VNodeChild;\n /** 悬停时的提示框,默认 true */\n tooltip?: boolean;\n}\n\n/** 画图用的一段:时间换成毫秒 */\ninterface Segment {\n item: TimelineChartItem;\n start: number;\n end: number;\n color: string;\n width: number;\n track: boolean;\n}\n\nconst DAY = 86_400_000;\nconst ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** 轨道(计划)的颜色:系列色调浅 */\nconst trackColor = (color: string) => `color-mix(in srgb, ${color} 30%, transparent)`;\n\n/** 按刻度的间隔选日期的写法:几个月一格写月,几天一格写月日,不到一天写时分 */\nfunction autoFormat(ticks: readonly Date[]): Intl.DateTimeFormatOptions {\n const step = ticks.length > 1 ? ticks[1]!.getTime() - ticks[0]!.getTime() : DAY;\n if (step >= 28 * DAY) return { month: 'short' };\n if (step >= DAY) return { month: 'numeric', day: 'numeric' };\n return { hour: '2-digit', minute: '2-digit' };\n}\n\n/** 时间点换成毫秒:ISO 字符串按语言的时区;只有日期的结束取到这一天的末尾 */\nfunction toTime(value: TimelineTime, timeZone: string, end = false): number {\n if (typeof value === 'number') return value;\n if (value instanceof Date) return value.getTime();\n // 只有日期:结束取到这一天的末尾(parseIsoDateTime 也接受只有日期的写法,要先认出来)\n const date = ISO_DATE.test(value) ? parseIsoDate(value) : null;\n if (date) return date.toDate(timeZone).getTime() + (end ? DAY : 0);\n const dateTime = parseIsoDateTime(value);\n if (dateTime) return dateTime.toDate(timeZone).getTime();\n return Date.parse(value);\n}\n\n/** 时间条图 */\nexport const TimelineChart = _component(\n ({\n items,\n series = [],\n rows,\n today,\n markers,\n rowHeight = 32,\n dateFormat,\n loading = false,\n _hiddenSeries: hiddenSeries = _bindable<string[]>([]),\n empty,\n tooltip = true,\n ...rest\n }: TimelineChartProps) => {\n const lang = useLang();\n const format = useFormat();\n const id = useId();\n const zone = () => lang.timeZone ?? getLocalTimeZone();\n const colorOf = (key: string | undefined) => {\n const position = series.findIndex((item) => item.key === key);\n return resolveColor(series[position]?.color, Math.max(position, 0));\n };\n const rowOrder = _derived(rows ?? [...new Set(items.map((item) => item.row))]);\n const visible = _derived(\n items.filter((item) => item.series === undefined || !hiddenSeries.includes(item.series)),\n );\n const dateText = (time: number) => format.date(new Date(time), { dateStyle: 'medium' });\n const daysText = (segment: Segment) =>\n lang.chartDays(Math.round((segment.end - segment.start) / DAY));\n\n function toggle(key: string) {\n hiddenSeries = hiddenSeries.includes(key)\n ? hiddenSeries.filter((item) => item !== key)\n : [...hiddenSeries, key];\n }\n\n /** 画图的各段:轨道先画(在下面),同一行里按开始时间 */\n const segments = (): Segment[] =>\n visible\n .map((item): Segment => {\n const track = series.find((entry) => entry.key === item.series)?.variant === 'track';\n return {\n item,\n start: toTime(item.start, zone()),\n end: toTime(item.end, zone(), true),\n color: item.color ? resolveColor(item.color, 0) : colorOf(item.series),\n width: track ? rowHeight * 0.62 : rowHeight * 0.32,\n track,\n };\n })\n .sort(\n (a, b) =>\n rowOrder.indexOf(a.item.row) - rowOrder.indexOf(b.item.row) ||\n Number(b.track) - Number(a.track) ||\n a.start - b.start,\n );\n\n /** 竖线:今天与里程碑 */\n const allMarkers = (): TimelineChartMarker[] => [\n ...(today !== undefined && today !== false\n ? [{ at: today === true ? Date.now() : today, label: lang.chartToday, color: 'danger' }]\n : []),\n ...(markers ?? []),\n ];\n\n const instances = createChartInstances();\n let tooltipElement: HTMLElement | undefined;\n // 提示框的内容渲染进 Unovis 管的元素:带上调用处的主题与语言,组件卸载时一起卸载\n const renderDetached = useDetachedRender();\n let plot = _state.raw<HTMLElement | null>(null);\n const seen = usePlotSeen(() => plot);\n let container: XYContainer<Segment> | undefined;\n let containerElement: HTMLElement | undefined;\n _effect(() => () => {\n container?.destroy();\n });\n _effect(() => {\n const data = segments();\n const element = plot;\n if (!element || loading || items.length === 0 || !seen()) return;\n if (container && containerElement !== element) {\n container.destroy();\n container = undefined;\n instances.reset();\n }\n const times = [\n ...data.flatMap((segment) => [segment.start, segment.end]),\n ...allMarkers().map((marker) => toTime(marker.at, zone())),\n ];\n const ticks = Scale.scaleTime()\n .domain([Math.min(...times), Math.max(...times)])\n .ticks(6);\n const timeline = instances.get('timeline', () => new Timeline<Segment>({ x: () => 0 }));\n timeline.setConfig({\n x: (segment) => segment.start,\n lineDuration: (segment) => segment.end - segment.start,\n lineRow: (segment) => segment.item.row,\n lineWidth: (segment) => segment.width,\n color: (segment) => (segment.track ? trackColor(segment.color) : segment.color),\n lineCap: true,\n rowHeight,\n showRowLabels: true,\n rowMaxLabelWidth: 160,\n alternatingRowColors: true,\n });\n const lines = allMarkers().map((marker, position) => {\n const line = instances.get(`marker-${position}`, () => new Plotline<Segment>({}));\n line.setConfig({\n axis: 'x',\n value: toTime(marker.at, zone()),\n color: resolveColor(marker.color ?? 'neutral', 0),\n lineWidth: 1,\n lineStyle: PlotlineLineStylePresets.ShortDash,\n labelText: marker.label,\n labelPosition: PlotlineLabelPosition.TopRight,\n labelOffsetX: 4,\n labelOffsetY: 2,\n });\n return line;\n });\n const xAxis = instances.get('xAxis', () => new Axis<Segment>({ type: 'x' }));\n xAxis.setConfig({\n type: 'x',\n // 整齐的时间刻度:月初、周、日、整点(d3 的时间刻度)\n tickValues: ticks.map((tick) => tick.getTime()),\n tickFormat: (tick) => format.date(new Date(Number(tick)), dateFormat ?? autoFormat(ticks)),\n gridLine: true,\n tickLine: false,\n domainLine: false,\n tickPadding: 8,\n });\n const tooltipBox = instances.get('tooltip', () => new Tooltip());\n tooltipBox.setConfig({\n triggers: {\n [Timeline.selectors.line]: (segment: Segment) => {\n tooltipElement ??= document.createElement('div');\n const name = series.find((entry) => entry.key === segment.item.series)?.label;\n renderDetached(\n chartTooltip(segment.item.label ?? segment.item.row, [\n ...(name ? [{ label: name, color: segment.color, value: daysText(segment) }] : []),\n { label: lang.chartStart, color: 'transparent', value: dateText(segment.start) },\n { label: lang.chartEnd, color: 'transparent', value: dateText(segment.end - 1) },\n ]),\n tooltipElement,\n );\n return tooltipElement;\n },\n },\n });\n const config = {\n components: [timeline, ...lines],\n xAxis,\n tooltip: tooltip ? tooltipBox : undefined,\n ariaLabel: null,\n duration: chartDuration(element),\n };\n if (container) container.update(config, undefined, data);\n else {\n container = new XYContainer<Segment>(element, config, data);\n containerElement = element;\n uniqueHighlightFilter(element, `${id}-highlight`);\n }\n });\n\n return () => {\n const shown = segments();\n return (\n <ChartFrame\n {...rest}\n sx={[xyChartStyles.hideCrosshairLine, rest.sx]}\n // 行数 × 行高,加上时间轴\n height={rowOrder.length * rowHeight + 36}\n legend={series.map((item, position) => {\n const color = resolveColor(item.color, position);\n return {\n key: item.key,\n label: item.label,\n color: item.variant === 'track' ? trackColor(color) : color,\n };\n })}\n hiddenKeys={hiddenSeries}\n onLegendToggle={toggle}\n loading={loading}\n skeleton={<Skeleton sx={xyChartStyles.skeleton} />}\n isEmpty={items.length === 0}\n empty={empty}\n table={{\n columns: [\n lang.chartCategory,\n lang.chartSeries,\n lang.chartStart,\n lang.chartEnd,\n lang.chartDuration,\n ],\n rows: shown.map((segment) => ({\n header: segment.item.label ?? segment.item.row,\n cells: [\n series.find((entry) => entry.key === segment.item.series)?.label ?? lang.emptyValue,\n dateText(segment.start),\n dateText(segment.end - 1),\n daysText(segment),\n ],\n })),\n }}\n plotRef={(element) => (plot = element)}\n />\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
@@ -5,8 +5,8 @@ import { Skeleton } from "../feedback/Skeleton.js";
|
|
|
5
5
|
import { ChartFrame, chartTooltip } from "./frame.js";
|
|
6
6
|
import { resolveColor, seriesValue } from "./series.js";
|
|
7
7
|
import { useSingleChart } from "./single.js";
|
|
8
|
-
import { computed, createVNode,
|
|
9
|
-
import { _component, bindable, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
8
|
+
import { computed, createVNode, shallowRef } from "vue";
|
|
9
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
10
10
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
11
11
|
import { Treemap } from "@unovis/ts";
|
|
12
12
|
//#region src/charts/TreemapChart.tsx
|
|
@@ -105,7 +105,7 @@ var TreemapChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
}));
|
|
108
|
-
return () => createVNode(ChartFrame,
|
|
108
|
+
return () => createVNode(ChartFrame, mergeJsxProps(rest, {
|
|
109
109
|
"unit": unit.value,
|
|
110
110
|
"height": height.value,
|
|
111
111
|
"legend": [...topColors.value].map(([key, color]) => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TreemapChart.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_computed","_vue_shallowRef","_createVNode","_mergeProps"],"sources":["../../src/charts/TreemapChart.tsx"],"sourcesContent":["/**\n * 矩形树图:一个总量按层级分块(销售额按区域 → 供应商、费用按科目),块的面积是数值,一眼看出谁占得多。\n *\n * <TreemapChart\n * aria-label=\"采购额构成\"\n * data={savings}\n * groups={['region', 'supplier']} // 从外到内的层级\n * value=\"amount\"\n * unit=\"万元\"\n * />\n *\n * 数据是扁平的明细,按 groups 的字段逐级分组(Unovis 的 Treemap);同一个顶层分组同一种颜色(按色板),\n * 里面的块按亮度区分;点图例隐藏一个顶层分组,占比按剩下的算。悬停看路径、数值与占比;读屏的数据表列出每一块。\n */\nimport { Treemap, type TreemapNode } from '@unovis/ts';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { useFormat, type NumericFormat } from '../config/lang/format';\nimport { Skeleton } from '../feedback/Skeleton';\nimport { toNumber, type NumericValue } from '../utils/data/numeric';\nimport type { PartProps } from '../utils/props';\nimport { chartTooltip, ChartFrame, type ChartFrameParts } from './frame';\nimport { resolveColor, seriesValue, type NumericKey } from './series';\nimport { useSingleChart, type SingleChart } from './single';\n\n/** 未声明的属性落到外层 div(role=\"figure\")上;aria-label 是图表的名称 */\nexport interface TreemapChartProps<T extends object>\n extends PartProps<'div', 'role' | 'aria-busy'>, ChartFrameParts {\n /** 明细 */\n data: readonly T[];\n /** 分组的字段,从外到内(区域、供应商) */\n groups: readonly (keyof T & string)[];\n /** 面积的数值字段 */\n value: NumericKey<T>;\n /** 数值的格式 */\n valueFormat?: NumericFormat;\n /** 单位:显示在左上方,提示框与数据表的数值后面带上 */\n unit?: string;\n /**\n * 绘图区的高度;数字按 px\n * @default 300\n */\n height?: number | string;\n /** 外层分组也写名字(块够大时) */\n groupLabels?: boolean;\n /** 隐藏的顶层分组:点图例时写回,父级可以写 _hiddenGroups={keys} */\n _hiddenGroups?: string[];\n /** 加载中:绘图区显示骨架 */\n loading?: boolean;\n /** 没有数据时显示的内容 */\n empty?: VNodeChild;\n /** 悬停时的提示框,默认 true */\n tooltip?: boolean;\n}\n\n/** TreemapChart 的样式 */\nexport const treemapChartStyles = css.create({\n skeleton: { display: 'block', width: '100%', height: '100%' },\n});\n\n/** 矩形树图 */\nexport const TreemapChart = _component(\n <T extends object>({\n data,\n groups,\n value,\n valueFormat,\n unit,\n height = 300,\n groupLabels = false,\n _hiddenGroups: hiddenGroups = _bindable<string[]>([]),\n loading = false,\n empty,\n tooltip = true,\n ...rest\n }: TreemapChartProps<T>) => {\n const lang = useLang();\n const format = useFormat();\n const numberOf = (row: T) => {\n const amount = seriesValue(row, value);\n return amount === undefined ? 0 : Math.max(toNumber(amount), 0);\n };\n const topOf = (row: T) => String(row[groups[0]!] ?? '');\n const visible = _derived(data.filter((row) => !hiddenGroups.includes(topOf(row))));\n const total = _derived(visible.reduce((sum, row) => sum + numberOf(row), 0));\n // 顶层分组的颜色:按第一次出现的顺序取色板\n const topColors = _derived(\n new Map(\n [...new Set(data.map(topOf))].map((key, position) => [\n key,\n resolveColor(undefined, position),\n ]),\n ),\n );\n const valueText = (amount: NumericValue) =>\n format.number(amount, unit ? { ...valueFormat, suffix: unit } : valueFormat);\n const shareText = (amount: number) =>\n total === 0\n ? lang.emptyValue\n : format.number(amount / total, { style: 'percent', maximumFractionDigits: 1 });\n /** 一个节点的路径:从顶层到它 */\n const pathOf = (node: TreemapNode<T>) =>\n node\n .ancestors()\n .reverse()\n .slice(1)\n .map((item) => item.data.key ?? '')\n .join(' / ');\n\n function toggle(key: string) {\n hiddenGroups = hiddenGroups.includes(key)\n ? hiddenGroups.filter((item) => item !== key)\n : [...hiddenGroups, key];\n }\n\n let plot = _state.raw<HTMLElement | null>(null);\n const chart: SingleChart = useSingleChart<T[]>(() => ({\n element: plot,\n ready: !loading && visible.length > 0,\n tooltip,\n build: (instance) => {\n const treemap = instance('treemap', () => new Treemap<T>({ layers: [] }));\n treemap.setConfig({\n value: numberOf,\n layers: groups.map((key) => (row: T) => String(row[key] ?? '')),\n // 外层分组是浅色的底,叶子是实色:同一组里的块靠底色的缝隙分开\n tileColor: (node) => {\n const base =\n topColors.get(node.topLevelParent?.data.key ?? '') ?? resolveColor(undefined, 0);\n return node.children ? `color-mix(in srgb, ${base} 18%, transparent)` : base;\n },\n tileLabel: (node) => node.data.key ?? '',\n labelInternalNodes: groupLabels,\n enableTileLabelFontSizeVariation: false,\n tilePadding: 3,\n // 分组的名字写在顶边留出的地方\n tilePaddingTop: groupLabels ? 20 : undefined,\n tileBorderRadius: 4,\n });\n return {\n component: treemap,\n data: [...visible],\n triggers: {\n [Treemap.selectors.tile]: (node: TreemapNode<T>) =>\n chart.tooltip(\n chartTooltip(pathOf(node), [\n {\n label: lang.chartValue,\n color: topColors.get(node.topLevelParent?.data.key ?? '') ?? 'transparent',\n value: valueText(node.value),\n },\n { label: lang.chartShare, color: 'transparent', value: shareText(node.value) },\n ]),\n ),\n },\n };\n },\n }));\n\n return () => (\n <ChartFrame\n {...rest}\n unit={unit}\n height={height}\n legend={[...topColors].map(([key, color]) => ({ key, label: key, color }))}\n hiddenKeys={hiddenGroups}\n onLegendToggle={toggle}\n loading={loading}\n skeleton={<Skeleton sx={treemapChartStyles.skeleton} />}\n isEmpty={data.length === 0}\n empty={empty}\n table={{\n columns: [\n lang.chartCategory,\n unit ? `${lang.chartValue}(${unit})` : lang.chartValue,\n lang.chartShare,\n ],\n rows: visible.map((row) => ({\n header: groups.map((key) => String(row[key] ?? '')).join(' / '),\n cells: [valueText(seriesValue(row, value) ?? 0), shareText(numberOf(row))],\n })),\n }}\n plotRef={(element) => (plot = element)}\n />\n );\n },\n);\n"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"TreemapChart.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_computed","_vue_shallowRef","_createVNode","_mergeProps"],"sources":["../../src/charts/TreemapChart.tsx"],"sourcesContent":["/**\n * 矩形树图:一个总量按层级分块(销售额按区域 → 供应商、费用按科目),块的面积是数值,一眼看出谁占得多。\n *\n * <TreemapChart\n * aria-label=\"采购额构成\"\n * data={savings}\n * groups={['region', 'supplier']} // 从外到内的层级\n * value=\"amount\"\n * unit=\"万元\"\n * />\n *\n * 数据是扁平的明细,按 groups 的字段逐级分组(Unovis 的 Treemap);同一个顶层分组同一种颜色(按色板),\n * 里面的块按亮度区分;点图例隐藏一个顶层分组,占比按剩下的算。悬停看路径、数值与占比;读屏的数据表列出每一块。\n */\nimport { Treemap, type TreemapNode } from '@unovis/ts';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { useFormat, type NumericFormat } from '../config/lang/format';\nimport { Skeleton } from '../feedback/Skeleton';\nimport { toNumber, type NumericValue } from '../utils/data/numeric';\nimport type { PartProps } from '../utils/props';\nimport { chartTooltip, ChartFrame, type ChartFrameParts } from './frame';\nimport { resolveColor, seriesValue, type NumericKey } from './series';\nimport { useSingleChart, type SingleChart } from './single';\n\n/** 未声明的属性落到外层 div(role=\"figure\")上;aria-label 是图表的名称 */\nexport interface TreemapChartProps<T extends object>\n extends PartProps<'div', 'role' | 'aria-busy'>, ChartFrameParts {\n /** 明细 */\n data: readonly T[];\n /** 分组的字段,从外到内(区域、供应商) */\n groups: readonly (keyof T & string)[];\n /** 面积的数值字段 */\n value: NumericKey<T>;\n /** 数值的格式 */\n valueFormat?: NumericFormat;\n /** 单位:显示在左上方,提示框与数据表的数值后面带上 */\n unit?: string;\n /**\n * 绘图区的高度;数字按 px\n * @default 300\n */\n height?: number | string;\n /** 外层分组也写名字(块够大时) */\n groupLabels?: boolean;\n /** 隐藏的顶层分组:点图例时写回,父级可以写 _hiddenGroups={keys} */\n _hiddenGroups?: string[];\n /** 加载中:绘图区显示骨架 */\n loading?: boolean;\n /** 没有数据时显示的内容 */\n empty?: VNodeChild;\n /** 悬停时的提示框,默认 true */\n tooltip?: boolean;\n}\n\n/** TreemapChart 的样式 */\nexport const treemapChartStyles = css.create({\n skeleton: { display: 'block', width: '100%', height: '100%' },\n});\n\n/** 矩形树图 */\nexport const TreemapChart = _component(\n <T extends object>({\n data,\n groups,\n value,\n valueFormat,\n unit,\n height = 300,\n groupLabels = false,\n _hiddenGroups: hiddenGroups = _bindable<string[]>([]),\n loading = false,\n empty,\n tooltip = true,\n ...rest\n }: TreemapChartProps<T>) => {\n const lang = useLang();\n const format = useFormat();\n const numberOf = (row: T) => {\n const amount = seriesValue(row, value);\n return amount === undefined ? 0 : Math.max(toNumber(amount), 0);\n };\n const topOf = (row: T) => String(row[groups[0]!] ?? '');\n const visible = _derived(data.filter((row) => !hiddenGroups.includes(topOf(row))));\n const total = _derived(visible.reduce((sum, row) => sum + numberOf(row), 0));\n // 顶层分组的颜色:按第一次出现的顺序取色板\n const topColors = _derived(\n new Map(\n [...new Set(data.map(topOf))].map((key, position) => [\n key,\n resolveColor(undefined, position),\n ]),\n ),\n );\n const valueText = (amount: NumericValue) =>\n format.number(amount, unit ? { ...valueFormat, suffix: unit } : valueFormat);\n const shareText = (amount: number) =>\n total === 0\n ? lang.emptyValue\n : format.number(amount / total, { style: 'percent', maximumFractionDigits: 1 });\n /** 一个节点的路径:从顶层到它 */\n const pathOf = (node: TreemapNode<T>) =>\n node\n .ancestors()\n .reverse()\n .slice(1)\n .map((item) => item.data.key ?? '')\n .join(' / ');\n\n function toggle(key: string) {\n hiddenGroups = hiddenGroups.includes(key)\n ? hiddenGroups.filter((item) => item !== key)\n : [...hiddenGroups, key];\n }\n\n let plot = _state.raw<HTMLElement | null>(null);\n const chart: SingleChart = useSingleChart<T[]>(() => ({\n element: plot,\n ready: !loading && visible.length > 0,\n tooltip,\n build: (instance) => {\n const treemap = instance('treemap', () => new Treemap<T>({ layers: [] }));\n treemap.setConfig({\n value: numberOf,\n layers: groups.map((key) => (row: T) => String(row[key] ?? '')),\n // 外层分组是浅色的底,叶子是实色:同一组里的块靠底色的缝隙分开\n tileColor: (node) => {\n const base =\n topColors.get(node.topLevelParent?.data.key ?? '') ?? resolveColor(undefined, 0);\n return node.children ? `color-mix(in srgb, ${base} 18%, transparent)` : base;\n },\n tileLabel: (node) => node.data.key ?? '',\n labelInternalNodes: groupLabels,\n enableTileLabelFontSizeVariation: false,\n tilePadding: 3,\n // 分组的名字写在顶边留出的地方\n tilePaddingTop: groupLabels ? 20 : undefined,\n tileBorderRadius: 4,\n });\n return {\n component: treemap,\n data: [...visible],\n triggers: {\n [Treemap.selectors.tile]: (node: TreemapNode<T>) =>\n chart.tooltip(\n chartTooltip(pathOf(node), [\n {\n label: lang.chartValue,\n color: topColors.get(node.topLevelParent?.data.key ?? '') ?? 'transparent',\n value: valueText(node.value),\n },\n { label: lang.chartShare, color: 'transparent', value: shareText(node.value) },\n ]),\n ),\n },\n };\n },\n }));\n\n return () => (\n <ChartFrame\n {...rest}\n unit={unit}\n height={height}\n legend={[...topColors].map(([key, color]) => ({ key, label: key, color }))}\n hiddenKeys={hiddenGroups}\n onLegendToggle={toggle}\n loading={loading}\n skeleton={<Skeleton sx={treemapChartStyles.skeleton} />}\n isEmpty={data.length === 0}\n empty={empty}\n table={{\n columns: [\n lang.chartCategory,\n unit ? `${lang.chartValue}(${unit})` : lang.chartValue,\n lang.chartShare,\n ],\n rows: visible.map((row) => ({\n header: groups.map((key) => String(row[key] ?? '')).join(' / '),\n cells: [valueText(seriesValue(row, value) ?? 0), shareText(numberOf(row))],\n })),\n }}\n plotRef={(element) => (plot = element)}\n />\n );\n },\n);\n"],"mappings":"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"}
|
|
@@ -4,8 +4,8 @@ import { useFormat } from "../config/lang/format.js";
|
|
|
4
4
|
import { useChartValueText } from "./frame.js";
|
|
5
5
|
import { resolveColor } from "./series.js";
|
|
6
6
|
import { XYChart } from "./XYChart.js";
|
|
7
|
-
import { computed, createVNode
|
|
8
|
-
import { _component, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
7
|
+
import { computed, createVNode } from "vue";
|
|
8
|
+
import { _component, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
9
9
|
import { StackedBar } from "@unovis/ts";
|
|
10
10
|
//#region src/charts/WaterfallChart.tsx
|
|
11
11
|
/**
|
|
@@ -137,7 +137,7 @@ var WaterfallChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
137
137
|
const span = Math.max(...values) - low || low * .1;
|
|
138
138
|
return [Math.max(0, low - span * .5), void 0];
|
|
139
139
|
};
|
|
140
|
-
return () => createVNode(XYChart,
|
|
140
|
+
return () => createVNode(XYChart, mergeJsxProps(rest, {
|
|
141
141
|
"valueDomain": rest.valueDomain ?? (fromZero.value || rows.value.length === 0 ? void 0 : zoomedDomain()),
|
|
142
142
|
"data": rows.value,
|
|
143
143
|
"index": "label",
|