@carto/ps-react-ui 4.4.1 → 4.4.3
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/components.js.map +1 -1
- package/dist/download-config-Dqu78h2a.js +57 -0
- package/dist/download-config-Dqu78h2a.js.map +1 -0
- package/dist/error-CEkRPccv.js.map +1 -1
- package/dist/exports-Cr43OCul.js.map +1 -1
- package/dist/formatter-B9Bxn1k7.js +6 -0
- package/dist/formatter-B9Bxn1k7.js.map +1 -0
- package/dist/lasso-tool-BYbxrJ-7.js.map +1 -1
- package/dist/note-t51drNe0.js.map +1 -1
- package/dist/options-D9wflre6.js.map +1 -1
- package/dist/row-DTCV0Ocm.js.map +1 -1
- package/dist/series-CYNOu2Ju.js.map +1 -1
- package/dist/smart-tooltip-D4vwQpFf.js.map +1 -1
- package/dist/styles-Y8q7Jff3.js +118 -0
- package/dist/styles-Y8q7Jff3.js.map +1 -0
- package/dist/tooltip-BDnrRKrp.js.map +1 -1
- package/dist/types/components/basemaps/basemaps.d.ts +20 -0
- package/dist/types/components/geolocation-controls/geolocation-controls.d.ts +11 -0
- package/dist/types/components/lasso-tool/lasso-tool-inline.d.ts +17 -0
- package/dist/types/components/lasso-tool/lasso-tool.d.ts +21 -0
- package/dist/types/components/list-data/list-data.d.ts +16 -0
- package/dist/types/components/measurement-tools/measurement-tools.d.ts +20 -0
- package/dist/types/components/smart-tooltip/smart-tooltip.d.ts +17 -0
- package/dist/types/components/tooltip/tooltip.d.ts +13 -0
- package/dist/types/components/zoom-controls/zoom-controls.d.ts +16 -0
- package/dist/types/hooks/use-widget-ref.d.ts +4 -4
- package/dist/types/widgets/actions/brush-toggle/types.d.ts +8 -2
- package/dist/types/widgets/actions/download/download.d.ts +11 -0
- package/dist/types/widgets/actions/download/exports.d.ts +15 -0
- package/dist/types/widgets/actions/fullscreen/fullscreen.d.ts +13 -0
- package/dist/types/widgets/actions/index.d.ts +1 -1
- package/dist/types/widgets/actions/relative-data/relative-data.d.ts +1 -0
- package/dist/types/widgets/bar/config.d.ts +8 -4
- package/dist/types/widgets/category/category-ui.d.ts +3 -0
- package/dist/types/widgets/category/components/category-bar.d.ts +3 -0
- package/dist/types/widgets/category/components/category-legend.d.ts +3 -0
- package/dist/types/widgets/category/components/category-row-multi.d.ts +5 -1
- package/dist/types/widgets/category/components/category-row-other.d.ts +3 -0
- package/dist/types/widgets/category/components/category-row-single.d.ts +5 -1
- package/dist/types/widgets/category/config.d.ts +11 -0
- package/dist/types/widgets/category/types.d.ts +1 -0
- package/dist/types/widgets/echart/echart-ui.d.ts +7 -0
- package/dist/types/widgets/echart/echart.d.ts +6 -0
- package/dist/types/widgets/echart/options.d.ts +7 -0
- package/dist/types/widgets/echart/types.d.ts +3 -0
- package/dist/types/widgets/echart/utils.d.ts +41 -0
- package/dist/types/widgets/error/error.d.ts +10 -0
- package/dist/types/widgets/formula/components/item.d.ts +3 -0
- package/dist/types/widgets/formula/components/prefix.d.ts +3 -0
- package/dist/types/widgets/formula/components/row.d.ts +3 -0
- package/dist/types/widgets/formula/components/series.d.ts +3 -0
- package/dist/types/widgets/formula/components/suffix.d.ts +3 -0
- package/dist/types/widgets/formula/components/value.d.ts +3 -0
- package/dist/types/widgets/formula/config.d.ts +11 -0
- package/dist/types/widgets/formula/formula-ui.d.ts +3 -0
- package/dist/types/widgets/histogram/config.d.ts +18 -2
- package/dist/types/widgets/histogram/index.d.ts +2 -1
- package/dist/types/widgets/histogram/types.d.ts +6 -3
- package/dist/types/widgets/loader/loader.d.ts +22 -0
- package/dist/types/widgets/loader/utils.d.ts +26 -3
- package/dist/types/widgets/markdown/config.d.ts +10 -0
- package/dist/types/widgets/markdown/markdown-ui.d.ts +7 -0
- package/dist/types/widgets/markdown/markdown.d.ts +3 -0
- package/dist/types/widgets/note/note.d.ts +10 -0
- package/dist/types/widgets/pie/config.d.ts +8 -4
- package/dist/types/widgets/range/components/range-item.d.ts +3 -0
- package/dist/types/widgets/range/config.d.ts +5 -0
- package/dist/types/widgets/range/range-ui.d.ts +3 -0
- package/dist/types/widgets/scatterplot/config.d.ts +7 -3
- package/dist/types/widgets/selection-summary/selection-summary.d.ts +11 -0
- package/dist/types/widgets/skeleton-loader/skeleton-loader.d.ts +10 -0
- package/dist/types/widgets/spread/components/max-value.d.ts +3 -0
- package/dist/types/widgets/spread/components/min-value.d.ts +3 -0
- package/dist/types/widgets/spread/components/separator.d.ts +3 -0
- package/dist/types/widgets/spread/config.d.ts +11 -0
- package/dist/types/widgets/spread/spread-ui.d.ts +3 -0
- package/dist/types/widgets/stores/types.d.ts +2 -0
- package/dist/types/widgets/subheader/subheader.d.ts +11 -0
- package/dist/types/widgets/table/config.d.ts +8 -3
- package/dist/types/widgets/table/hooks/use-pagination.d.ts +11 -3
- package/dist/types/widgets/table/hooks/use-selection.d.ts +11 -2
- package/dist/types/widgets/table/hooks/use-sort.d.ts +11 -3
- package/dist/types/widgets/timeseries/config.d.ts +8 -4
- package/dist/types/widgets/utils/chart-config/download-config.d.ts +3 -0
- package/dist/types/widgets/{_shared → utils}/chart-config/index.d.ts +2 -0
- package/dist/types/widgets/{_shared → utils}/chart-config/option-builders.d.ts +14 -9
- package/dist/types/widgets/utils/formatter.d.ts +2 -0
- package/dist/types/widgets/utils/index.d.ts +7 -0
- package/dist/types/widgets/wrapper/components/actions.d.ts +3 -0
- package/dist/types/widgets/wrapper/components/options.d.ts +3 -0
- package/dist/types/widgets/wrapper/components/title.d.ts +3 -0
- package/dist/types/widgets/wrapper/wrapper-ui.d.ts +14 -0
- package/dist/types/widgets/wrapper/wrapper.d.ts +14 -0
- package/dist/use-widget-ref-wtFLDFCD.js.map +1 -1
- package/dist/utils-BOhInag6.js.map +1 -1
- package/dist/widgets/actions.js +720 -681
- package/dist/widgets/actions.js.map +1 -1
- package/dist/widgets/bar.js +78 -92
- package/dist/widgets/bar.js.map +1 -1
- package/dist/widgets/category.js +206 -197
- package/dist/widgets/category.js.map +1 -1
- package/dist/widgets/echart.js.map +1 -1
- package/dist/widgets/formula.js +54 -54
- package/dist/widgets/formula.js.map +1 -1
- package/dist/widgets/histogram.js +106 -86
- package/dist/widgets/histogram.js.map +1 -1
- package/dist/widgets/loader.js.map +1 -1
- package/dist/widgets/markdown.js.map +1 -1
- package/dist/widgets/pie.js +147 -112
- package/dist/widgets/pie.js.map +1 -1
- package/dist/widgets/range.js +23 -22
- package/dist/widgets/range.js.map +1 -1
- package/dist/widgets/scatterplot.js +46 -60
- package/dist/widgets/scatterplot.js.map +1 -1
- package/dist/widgets/selection-summary.js.map +1 -1
- package/dist/widgets/skeleton-loader.js.map +1 -1
- package/dist/widgets/spread.js +40 -41
- package/dist/widgets/spread.js.map +1 -1
- package/dist/widgets/subheader.js.map +1 -1
- package/dist/widgets/table.js.map +1 -1
- package/dist/widgets/timeseries.js +51 -65
- package/dist/widgets/timeseries.js.map +1 -1
- package/dist/widgets/toolbar-actions.js +101 -6693
- package/dist/widgets/toolbar-actions.js.map +1 -1
- package/dist/widgets/utils.js +33 -0
- package/dist/widgets/utils.js.map +1 -0
- package/dist/widgets/wrapper.js.map +1 -1
- package/package.json +9 -4
- package/src/components/basemaps/basemaps.tsx +20 -0
- package/src/components/geolocation-controls/geolocation-controls.tsx +11 -0
- package/src/components/lasso-tool/lasso-tool-inline.tsx +17 -0
- package/src/components/lasso-tool/lasso-tool.tsx +21 -0
- package/src/components/list-data/list-data.tsx +16 -0
- package/src/components/measurement-tools/measurement-tools.tsx +20 -0
- package/src/components/smart-tooltip/smart-tooltip.tsx +17 -0
- package/src/components/tooltip/tooltip.tsx +13 -0
- package/src/components/zoom-controls/zoom-controls.tsx +16 -0
- package/src/hooks/use-widget-ref.ts +4 -4
- package/src/widgets/README.md +13 -13
- package/src/widgets/actions/brush-toggle/brush-toggle.tsx +42 -47
- package/src/widgets/actions/brush-toggle/types.ts +8 -2
- package/src/widgets/actions/download/download.tsx +11 -0
- package/src/widgets/actions/download/exports.tsx +15 -0
- package/src/widgets/actions/fullscreen/fullscreen.tsx +13 -0
- package/src/widgets/actions/index.ts +1 -0
- package/src/widgets/actions/relative-data/relative-data.test.tsx +62 -1
- package/src/widgets/actions/relative-data/relative-data.tsx +62 -39
- package/src/widgets/bar/config.ts +34 -34
- package/src/widgets/bar/style.ts +1 -1
- package/src/widgets/category/category-ui.tsx +12 -2
- package/src/widgets/category/components/category-bar.tsx +3 -0
- package/src/widgets/category/components/category-legend.tsx +3 -0
- package/src/widgets/category/components/category-row-multi.tsx +9 -2
- package/src/widgets/category/components/category-row-other.tsx +3 -0
- package/src/widgets/category/components/category-row-single.tsx +8 -1
- package/src/widgets/category/config.ts +11 -0
- package/src/widgets/category/types.ts +1 -0
- package/src/widgets/echart/echart-ui.tsx +7 -0
- package/src/widgets/echart/echart.tsx +6 -0
- package/src/widgets/echart/options.ts +7 -0
- package/src/widgets/echart/types.ts +3 -0
- package/src/widgets/echart/utils.ts +41 -0
- package/src/widgets/error/error.tsx +10 -0
- package/src/widgets/formula/components/item.tsx +3 -0
- package/src/widgets/formula/components/prefix.tsx +3 -0
- package/src/widgets/formula/components/row.tsx +3 -0
- package/src/widgets/formula/components/series.tsx +3 -0
- package/src/widgets/formula/components/suffix.tsx +3 -0
- package/src/widgets/formula/components/value.tsx +4 -2
- package/src/widgets/formula/config.ts +11 -0
- package/src/widgets/formula/formula-ui.tsx +3 -0
- package/src/widgets/histogram/config.ts +93 -21
- package/src/widgets/histogram/index.ts +6 -1
- package/src/widgets/histogram/style.ts +1 -1
- package/src/widgets/histogram/types.ts +9 -3
- package/src/widgets/loader/loader.tsx +22 -0
- package/src/widgets/loader/utils.ts +26 -3
- package/src/widgets/markdown/config.ts +10 -0
- package/src/widgets/markdown/markdown-ui.tsx +7 -0
- package/src/widgets/markdown/markdown.tsx +3 -0
- package/src/widgets/note/note.tsx +10 -0
- package/src/widgets/pie/config.ts +100 -33
- package/src/widgets/pie/style.ts +1 -1
- package/src/widgets/range/components/range-item.tsx +5 -2
- package/src/widgets/range/config.ts +5 -0
- package/src/widgets/range/range-ui.tsx +3 -0
- package/src/widgets/scatterplot/config.ts +19 -23
- package/src/widgets/scatterplot/style.ts +1 -1
- package/src/widgets/selection-summary/selection-summary.tsx +11 -0
- package/src/widgets/skeleton-loader/skeleton-loader.tsx +10 -0
- package/src/widgets/spread/components/max-value.tsx +4 -2
- package/src/widgets/spread/components/min-value.tsx +4 -2
- package/src/widgets/spread/components/separator.tsx +3 -0
- package/src/widgets/spread/config.ts +11 -0
- package/src/widgets/spread/spread-ui.tsx +3 -0
- package/src/widgets/stores/types.ts +2 -0
- package/src/widgets/subheader/subheader.tsx +11 -0
- package/src/widgets/table/config.ts +8 -3
- package/src/widgets/table/hooks/use-pagination.ts +11 -3
- package/src/widgets/table/hooks/use-selection.ts +11 -2
- package/src/widgets/table/hooks/use-sort.ts +11 -3
- package/src/widgets/timeseries/config.ts +32 -33
- package/src/widgets/timeseries/style.ts +1 -1
- package/src/widgets/utils/chart-config/download-config.ts +22 -0
- package/src/widgets/{_shared → utils}/chart-config/index.ts +4 -0
- package/src/widgets/{_shared → utils}/chart-config/option-builders.ts +23 -13
- package/src/widgets/utils/formatter.ts +2 -0
- package/src/widgets/utils/index.ts +26 -0
- package/src/widgets/wrapper/components/actions.tsx +3 -0
- package/src/widgets/wrapper/components/options.tsx +3 -0
- package/src/widgets/wrapper/components/title.tsx +3 -0
- package/src/widgets/wrapper/wrapper-ui.tsx +14 -0
- package/src/widgets/wrapper/wrapper.tsx +14 -0
- package/dist/styles-CAroD5Rc.js +0 -123
- package/dist/styles-CAroD5Rc.js.map +0 -1
- /package/dist/types/widgets/{_shared → utils}/chart-config/config-factory.d.ts +0 -0
- /package/dist/types/widgets/{_shared → utils}/chart-config/csv-modifiers.d.ts +0 -0
- /package/dist/types/widgets/{_shared → utils}/chart-config/option-builders.test.d.ts +0 -0
- /package/dist/types/widgets/{_shared → utils}/skeleton/index.d.ts +0 -0
- /package/dist/types/widgets/{_shared → utils}/skeleton/styles.d.ts +0 -0
- /package/src/widgets/{_shared → utils}/chart-config/config-factory.ts +0 -0
- /package/src/widgets/{_shared → utils}/chart-config/csv-modifiers.ts +0 -0
- /package/src/widgets/{_shared → utils}/chart-config/option-builders.test.ts +0 -0
- /package/src/widgets/{_shared → utils}/skeleton/index.ts +0 -0
- /package/src/widgets/{_shared → utils}/skeleton/styles.ts +0 -0
|
@@ -2,15 +2,11 @@ import { Box, IconButton } from '@mui/material'
|
|
|
2
2
|
import { HighlightAltOutlined } from '@mui/icons-material'
|
|
3
3
|
import { useEffect, useCallback, useRef } from 'react'
|
|
4
4
|
import { useWidgetStore } from '../../stores/widget-store'
|
|
5
|
-
import type { BrushToggleProps } from './types'
|
|
5
|
+
import type { BrushSelectedItems, BrushToggleProps } from './types'
|
|
6
6
|
import { styles } from './style'
|
|
7
7
|
import { Tooltip } from '../../../components'
|
|
8
8
|
import { getEChartBrushConfig } from '../../echart/utils'
|
|
9
|
-
import type {
|
|
10
|
-
EchartOptionsProps,
|
|
11
|
-
EchartWidgetData,
|
|
12
|
-
EchartWidgetState,
|
|
13
|
-
} from '../../echart/types'
|
|
9
|
+
import type { EchartOptionsProps, EchartWidgetState } from '../../echart/types'
|
|
14
10
|
import { useShallow } from 'zustand/shallow'
|
|
15
11
|
|
|
16
12
|
export const BRUSH_TOGGLE_TOOL_ID = 'brush-toggle'
|
|
@@ -44,7 +40,7 @@ export function BrushToggle({
|
|
|
44
40
|
const registerTool = useWidgetStore((state) => state.registerTool)
|
|
45
41
|
const unregisterTool = useWidgetStore((state) => state.unregisterTool)
|
|
46
42
|
const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
|
|
47
|
-
const selected = useRef<(
|
|
43
|
+
const selected = useRef<BrushSelectedItems>({ dataIndex: [], seriesIndex: 0 })
|
|
48
44
|
|
|
49
45
|
const brushTool = useWidgetStore(
|
|
50
46
|
useShallow((state) => {
|
|
@@ -67,7 +63,7 @@ export function BrushToggle({
|
|
|
67
63
|
toggleTool(newBrush)
|
|
68
64
|
|
|
69
65
|
if (newBrush) {
|
|
70
|
-
onBrushSelected?.([])
|
|
66
|
+
onBrushSelected?.({ dataIndex: [], seriesIndex: 0 }) // Clear selection when enabling brush
|
|
71
67
|
}
|
|
72
68
|
}, [brush, onBrushSelected, toggleTool])
|
|
73
69
|
|
|
@@ -102,46 +98,47 @@ export function BrushToggle({
|
|
|
102
98
|
}, [brush, getWidget, id])
|
|
103
99
|
|
|
104
100
|
// Handle brushSelected event to capture selected bar indices
|
|
105
|
-
const handleBrushSelected = useCallback(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
seriesIndex?: number
|
|
112
|
-
}[]
|
|
101
|
+
const handleBrushSelected = useCallback((event: unknown) => {
|
|
102
|
+
const brushEvent = event as {
|
|
103
|
+
batch?: {
|
|
104
|
+
selected?: {
|
|
105
|
+
dataIndex?: number[]
|
|
106
|
+
seriesIndex?: number
|
|
113
107
|
}[]
|
|
108
|
+
}[]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const allSelected =
|
|
112
|
+
brushEvent.batch?.flatMap((batchItem) => batchItem.selected ?? []) ?? []
|
|
113
|
+
|
|
114
|
+
if (!allSelected.length) {
|
|
115
|
+
selected.current = {
|
|
116
|
+
dataIndex: [],
|
|
117
|
+
seriesIndex: 0,
|
|
114
118
|
}
|
|
119
|
+
return
|
|
120
|
+
}
|
|
115
121
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
) ?? [],
|
|
137
|
-
),
|
|
138
|
-
),
|
|
139
|
-
]
|
|
140
|
-
|
|
141
|
-
selected.current = items ?? []
|
|
142
|
-
},
|
|
143
|
-
[getWidget, id],
|
|
144
|
-
)
|
|
122
|
+
// Use the first seriesIndex as the primary one (matches previous behavior)
|
|
123
|
+
const primarySeriesIndex = allSelected[0]?.seriesIndex ?? 0
|
|
124
|
+
|
|
125
|
+
const mergedDataIndex = Array.from(
|
|
126
|
+
new Set(
|
|
127
|
+
allSelected
|
|
128
|
+
.filter(
|
|
129
|
+
(item) =>
|
|
130
|
+
item.seriesIndex === undefined ||
|
|
131
|
+
item.seriesIndex === primarySeriesIndex,
|
|
132
|
+
)
|
|
133
|
+
.flatMap((item) => item.dataIndex ?? []),
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
selected.current = {
|
|
138
|
+
dataIndex: mergedDataIndex,
|
|
139
|
+
seriesIndex: primarySeriesIndex,
|
|
140
|
+
}
|
|
141
|
+
}, [])
|
|
145
142
|
|
|
146
143
|
const handleBrushEnd = useCallback(() => {
|
|
147
144
|
onBrushSelected?.(selected.current)
|
|
@@ -169,8 +166,6 @@ export function BrushToggle({
|
|
|
169
166
|
|
|
170
167
|
const brushConfig = getEChartBrushConfig()
|
|
171
168
|
|
|
172
|
-
const onEventsWithoutBrush = { ...currentOnEvents }
|
|
173
|
-
delete onEventsWithoutBrush.brushSelected
|
|
174
169
|
const onEvents = {
|
|
175
170
|
...currentOnEvents,
|
|
176
171
|
brushSelected: handleBrushSelected,
|
|
@@ -3,9 +3,15 @@ import type { ReactNode } from 'react'
|
|
|
3
3
|
import type { BaseWidgetState } from '../../stores/types'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Brush selection result emitted by BrushToggle.
|
|
7
|
+
* Contains raw indices so consumers can resolve data according to their widget type.
|
|
7
8
|
*/
|
|
8
|
-
export
|
|
9
|
+
export interface BrushSelectedItems {
|
|
10
|
+
/** Data indices of the selected items in the dataset */
|
|
11
|
+
dataIndex: number[]
|
|
12
|
+
/** Series index from the brush event (defaults to 0) */
|
|
13
|
+
seriesIndex: number
|
|
14
|
+
}
|
|
9
15
|
|
|
10
16
|
/**
|
|
11
17
|
* State stored in widget store for brush functionality
|
|
@@ -14,6 +14,17 @@ import { useShallow } from 'zustand/shallow'
|
|
|
14
14
|
|
|
15
15
|
const EMPTY_LABELS: NonNullable<DownloadProps['labels']> = {}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Dropdown menu action for exporting widget data in various formats (CSV, PNG, etc.).
|
|
19
|
+
*
|
|
20
|
+
* Reads widget data from the store and triggers downloads using the modifier
|
|
21
|
+
* function defined in each `DownloadItem`.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* <Download id={widgetId} items={barDownloadConfig({ refUI })} />
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
17
28
|
export function Download({
|
|
18
29
|
id,
|
|
19
30
|
items,
|
|
@@ -28,6 +28,12 @@ async function downloadFileToCSV<D>(data: D[][]) {
|
|
|
28
28
|
return Promise.resolve(URL.createObjectURL(blob))
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Pre-configured download item for exporting widget data as a CSV file.
|
|
33
|
+
*
|
|
34
|
+
* Converts a 2D array of data into CSV format with proper escaping and
|
|
35
|
+
* triggers a browser download. Revokes the object URL after download.
|
|
36
|
+
*/
|
|
31
37
|
export const downloadToCSV: DownloadItem<unknown[][]> = {
|
|
32
38
|
id: 'csv',
|
|
33
39
|
label: 'CSV',
|
|
@@ -91,6 +97,15 @@ async function downloadFileToPNG(ref: Ref<HTMLElement | null> | undefined) {
|
|
|
91
97
|
return Promise.resolve(result)
|
|
92
98
|
}
|
|
93
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Pre-configured download item for exporting a widget as a PNG image.
|
|
102
|
+
*
|
|
103
|
+
* Uses html2canvas to capture the widget DOM element referenced by a React ref.
|
|
104
|
+
* Strips toolbar and action elements before capturing.
|
|
105
|
+
*
|
|
106
|
+
* @remarks
|
|
107
|
+
* The modifier expects a React ref to the widget's root HTML element, not raw data.
|
|
108
|
+
*/
|
|
94
109
|
export const downloadToPNG: Omit<DownloadItem, 'modifier'> & {
|
|
95
110
|
modifier: (
|
|
96
111
|
ref: Ref<HTMLElement | null> | undefined,
|
|
@@ -19,6 +19,19 @@ const EMPTY_DIALOG_CONTENT_PROPS: NonNullable<
|
|
|
19
19
|
FullScreenProps['DialogContentProps']
|
|
20
20
|
> = {}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Displays widget content in a fullscreen modal dialog.
|
|
24
|
+
*
|
|
25
|
+
* Manages fullscreen state via the widget store and renders a MUI Dialog
|
|
26
|
+
* with the widget title and a close button.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <FullScreen id="my-widget" labels={{ ariaLabel: 'Expand chart' }}>
|
|
31
|
+
* <ChartContent id="my-widget" />
|
|
32
|
+
* </FullScreen>
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
22
35
|
export function FullScreen({
|
|
23
36
|
id,
|
|
24
37
|
labels,
|
|
@@ -11,6 +11,7 @@ export { downloadToCSV, downloadToPNG } from './download/exports'
|
|
|
11
11
|
export {
|
|
12
12
|
RelativeData,
|
|
13
13
|
RELATIVE_DATA_TOOL_ID,
|
|
14
|
+
RELATIVE_DATA_CONFIG_TOOL_ID,
|
|
14
15
|
} from './relative-data/relative-data'
|
|
15
16
|
export type { RelativeDataProps } from './relative-data/types'
|
|
16
17
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { describe, test, expect, beforeEach } from 'vitest'
|
|
2
2
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
RelativeData,
|
|
5
|
+
RELATIVE_DATA_TOOL_ID,
|
|
6
|
+
RELATIVE_DATA_CONFIG_TOOL_ID,
|
|
7
|
+
} from './relative-data'
|
|
4
8
|
import { useWidgetStore } from '../../stores/widget-store'
|
|
5
9
|
import type { EchartWidgetData } from '../../echart/types'
|
|
6
10
|
|
|
@@ -223,6 +227,63 @@ describe('RelativeData', () => {
|
|
|
223
227
|
expect(button.hasAttribute('disabled')).toBeTruthy()
|
|
224
228
|
})
|
|
225
229
|
|
|
230
|
+
test('registers config tool on mount', async () => {
|
|
231
|
+
render(<RelativeData id={widgetId} />)
|
|
232
|
+
|
|
233
|
+
await waitFor(() => {
|
|
234
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
235
|
+
const tool = widget?.registeredTools?.find(
|
|
236
|
+
(t) => t.id === RELATIVE_DATA_CONFIG_TOOL_ID,
|
|
237
|
+
)
|
|
238
|
+
expect(tool).toBeTruthy()
|
|
239
|
+
expect(tool?.type).toBe('config')
|
|
240
|
+
expect(tool?.enabled).toBe(true)
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
test('sets formatter via config pipeline when toggling to relative mode', async () => {
|
|
245
|
+
useWidgetStore.getState().setWidget(widgetId, { max: 500 })
|
|
246
|
+
|
|
247
|
+
render(<RelativeData id={widgetId} />)
|
|
248
|
+
|
|
249
|
+
const button = screen.getByRole('button')
|
|
250
|
+
fireEvent.click(button)
|
|
251
|
+
|
|
252
|
+
await waitFor(() => {
|
|
253
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
254
|
+
const tool = widget?.registeredTools?.find(
|
|
255
|
+
(t) => t.id === RELATIVE_DATA_CONFIG_TOOL_ID,
|
|
256
|
+
)
|
|
257
|
+
expect(tool?.config?.isRelative).toBe(true)
|
|
258
|
+
expect(tool?.config?.originalFormatter).toBeUndefined()
|
|
259
|
+
expect(tool?.config?.originalMax).toBe(500)
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
test('restores original formatter via config pipeline when toggling back', () => {
|
|
264
|
+
const customFormatter = (value: number) => `$${value}`
|
|
265
|
+
useWidgetStore
|
|
266
|
+
.getState()
|
|
267
|
+
.setWidget(widgetId, { formatter: customFormatter, max: 200 })
|
|
268
|
+
|
|
269
|
+
render(<RelativeData id={widgetId} />)
|
|
270
|
+
|
|
271
|
+
const button = screen.getByRole('button')
|
|
272
|
+
|
|
273
|
+
// Toggle to relative
|
|
274
|
+
fireEvent.click(button)
|
|
275
|
+
// Toggle back to absolute
|
|
276
|
+
fireEvent.click(button)
|
|
277
|
+
|
|
278
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
279
|
+
const tool = widget?.registeredTools?.find(
|
|
280
|
+
(t) => t.id === RELATIVE_DATA_CONFIG_TOOL_ID,
|
|
281
|
+
)
|
|
282
|
+
expect(tool?.config?.isRelative).toBe(false)
|
|
283
|
+
expect(tool?.config?.originalFormatter).toBe(customFormatter)
|
|
284
|
+
expect(tool?.config?.originalMax).toBe(200)
|
|
285
|
+
})
|
|
286
|
+
|
|
226
287
|
test('recalculates relative values when store data changes externally while in relative mode', async () => {
|
|
227
288
|
const initialData: EchartWidgetData = [
|
|
228
289
|
[
|
|
@@ -2,13 +2,14 @@ import { IconButton } from '@mui/material'
|
|
|
2
2
|
import { PercentOutlined } from '@mui/icons-material'
|
|
3
3
|
import { useCallback, useEffect, useRef } from 'react'
|
|
4
4
|
import { useWidgetStore } from '../../stores/widget-store'
|
|
5
|
-
import type { RelativeDataProps
|
|
5
|
+
import type { RelativeDataProps } from './types'
|
|
6
6
|
import { actionButtonStyles } from '../shared/styles'
|
|
7
7
|
import { Tooltip } from '../../../components'
|
|
8
8
|
import { calculateTotal, toRelativeData } from './utils'
|
|
9
9
|
import type { EchartWidgetData } from '../../../widgets/echart'
|
|
10
10
|
|
|
11
11
|
export const RELATIVE_DATA_TOOL_ID = 'relative-data'
|
|
12
|
+
export const RELATIVE_DATA_CONFIG_TOOL_ID = 'relative-data-config'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Widget action to toggle between relative (percentage) and absolute data display.
|
|
@@ -33,36 +34,30 @@ export function RelativeData({
|
|
|
33
34
|
Icon,
|
|
34
35
|
IconButtonProps,
|
|
35
36
|
}: RelativeDataProps) {
|
|
36
|
-
const
|
|
37
|
-
const originalFormatter = useRef<((value: number) => string) | undefined>(
|
|
37
|
+
const percentFormatterRef = useRef<((value: number) => string) | undefined>(
|
|
38
38
|
undefined,
|
|
39
39
|
)
|
|
40
|
-
const setWidget = useWidgetStore((state) => state.setWidget)
|
|
41
40
|
const getWidget = useWidgetStore((state) => state.getWidget)
|
|
42
41
|
const registerTool = useWidgetStore((state) => state.registerTool)
|
|
43
42
|
const unregisterTool = useWidgetStore((state) => state.unregisterTool)
|
|
44
43
|
const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
|
|
44
|
+
const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
|
|
45
45
|
|
|
46
46
|
const storeIsRelative = useWidgetStore(
|
|
47
|
-
(state) =>
|
|
47
|
+
(state) =>
|
|
48
|
+
state.widgets[id]?.registeredTools?.find(
|
|
49
|
+
(t) => t.id === RELATIVE_DATA_CONFIG_TOOL_ID,
|
|
50
|
+
)?.config?.isRelative as boolean | undefined,
|
|
48
51
|
)
|
|
49
52
|
|
|
50
53
|
const isRelative = storeIsRelative ?? defaultIsRelative
|
|
51
54
|
|
|
52
|
-
// Initialize store with default value on mount
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
const currentValue = getWidget<RelativeDataState>(id)?.isRelative
|
|
55
|
-
if (currentValue === undefined) {
|
|
56
|
-
setWidget(id, { isRelative: defaultIsRelative })
|
|
57
|
-
}
|
|
58
|
-
}, [defaultIsRelative, getWidget, id, setWidget])
|
|
59
|
-
|
|
60
55
|
// Register tool on mount
|
|
61
56
|
useEffect(() => {
|
|
62
57
|
registerTool(id, {
|
|
63
58
|
id: RELATIVE_DATA_TOOL_ID,
|
|
64
59
|
order,
|
|
65
|
-
enabled:
|
|
60
|
+
enabled: defaultIsRelative,
|
|
66
61
|
fn: (data) => {
|
|
67
62
|
const echartData = data as EchartWidgetData
|
|
68
63
|
const total = calculateTotal(echartData)
|
|
@@ -71,42 +66,70 @@ export function RelativeData({
|
|
|
71
66
|
})
|
|
72
67
|
|
|
73
68
|
return () => unregisterTool(id, RELATIVE_DATA_TOOL_ID)
|
|
74
|
-
}, [id, order, registerTool, unregisterTool,
|
|
69
|
+
}, [id, order, registerTool, unregisterTool, defaultIsRelative])
|
|
70
|
+
|
|
71
|
+
// Register config tool for formatter management
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
registerTool(id, {
|
|
74
|
+
id: RELATIVE_DATA_CONFIG_TOOL_ID,
|
|
75
|
+
type: 'config',
|
|
76
|
+
order,
|
|
77
|
+
enabled: true,
|
|
78
|
+
fn: (currentConfig, toolConfig) => {
|
|
79
|
+
const config = currentConfig as Record<string, unknown>
|
|
80
|
+
if (toolConfig?.isRelative) {
|
|
81
|
+
if (!percentFormatterRef.current) {
|
|
82
|
+
const locale = toolConfig?.locale as string | undefined
|
|
83
|
+
percentFormatterRef.current = (value: number) =>
|
|
84
|
+
new Intl.NumberFormat(locale, {
|
|
85
|
+
style: 'percent',
|
|
86
|
+
minimumFractionDigits: 1,
|
|
87
|
+
maximumFractionDigits: 1,
|
|
88
|
+
}).format(value / 100)
|
|
89
|
+
}
|
|
90
|
+
return { ...config, formatter: percentFormatterRef.current, max: 100 }
|
|
91
|
+
}
|
|
92
|
+
// Switching back from relative mode
|
|
93
|
+
percentFormatterRef.current = undefined
|
|
94
|
+
if (toolConfig && 'originalFormatter' in toolConfig) {
|
|
95
|
+
return {
|
|
96
|
+
...config,
|
|
97
|
+
formatter: toolConfig.originalFormatter,
|
|
98
|
+
max: toolConfig.originalMax,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return config
|
|
102
|
+
},
|
|
103
|
+
config: {
|
|
104
|
+
isRelative: defaultIsRelative,
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
return () => unregisterTool(id, RELATIVE_DATA_CONFIG_TOOL_ID)
|
|
108
|
+
}, [id, order, registerTool, unregisterTool, defaultIsRelative])
|
|
75
109
|
|
|
76
110
|
const handleToggle = useCallback(() => {
|
|
77
111
|
const newIsRelative = !isRelative
|
|
78
112
|
setToolEnabled(id, RELATIVE_DATA_TOOL_ID, newIsRelative)
|
|
79
|
-
let max = previousMaxValue.current
|
|
80
113
|
|
|
81
114
|
if (newIsRelative) {
|
|
82
|
-
// Backup current formatter to ref
|
|
83
115
|
const widget = getWidget(id) as {
|
|
84
116
|
formatter?: (value: number) => string
|
|
85
117
|
locale?: string
|
|
118
|
+
max?: number
|
|
86
119
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
120
|
+
updateToolConfig(id, RELATIVE_DATA_CONFIG_TOOL_ID, {
|
|
121
|
+
isRelative: true,
|
|
122
|
+
originalFormatter: widget?.formatter,
|
|
123
|
+
originalMax: widget?.max,
|
|
124
|
+
locale: widget?.locale,
|
|
125
|
+
})
|
|
126
|
+
} else {
|
|
127
|
+
percentFormatterRef.current = undefined
|
|
128
|
+
updateToolConfig(id, RELATIVE_DATA_CONFIG_TOOL_ID, {
|
|
129
|
+
isRelative: false,
|
|
130
|
+
})
|
|
93
131
|
}
|
|
94
|
-
|
|
95
|
-
setWidget(id, {
|
|
96
|
-
isRelative: newIsRelative,
|
|
97
|
-
max,
|
|
98
|
-
formatter: newIsRelative
|
|
99
|
-
? (value: number) => {
|
|
100
|
-
const widget = getWidget(id) as { locale?: string }
|
|
101
|
-
return new Intl.NumberFormat(widget?.locale, {
|
|
102
|
-
style: 'percent',
|
|
103
|
-
minimumFractionDigits: 1,
|
|
104
|
-
maximumFractionDigits: 1,
|
|
105
|
-
}).format(value / 100)
|
|
106
|
-
}
|
|
107
|
-
: originalFormatter.current,
|
|
108
|
-
})
|
|
109
|
-
}, [isRelative, setWidget, id, getWidget, setToolEnabled])
|
|
132
|
+
}, [isRelative, id, getWidget, setToolEnabled, updateToolConfig])
|
|
110
133
|
|
|
111
134
|
const tooltipLabel = isRelative
|
|
112
135
|
? (labels?.absolute ?? 'Show absolute values')
|
|
@@ -10,33 +10,27 @@ import {
|
|
|
10
10
|
buildGridConfig,
|
|
11
11
|
createTooltipPositioner,
|
|
12
12
|
createTooltipFormatter,
|
|
13
|
+
createChartDownloadConfig,
|
|
14
|
+
applyXAxisFormatter,
|
|
13
15
|
niceNum,
|
|
14
|
-
} from '../
|
|
15
|
-
import { downloadToCSV, downloadToPNG, type DownloadItem } from '../actions'
|
|
16
|
-
import type { ConfigProps } from '../loader/types'
|
|
16
|
+
} from '../utils/chart-config'
|
|
17
17
|
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return [
|
|
22
|
-
{
|
|
23
|
-
...downloadToPNG,
|
|
24
|
-
modifier: () => downloadToPNG.modifier(refUI),
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
...downloadToCSV,
|
|
28
|
-
modifier: async (data) => {
|
|
29
|
-
const rows = flattenObjectArrayToCSV(data)
|
|
30
|
-
return downloadToCSV.modifier(rows)
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
]
|
|
34
|
-
}
|
|
18
|
+
export const barDownloadConfig = createChartDownloadConfig<BarWidgetData>(
|
|
19
|
+
flattenObjectArrayToCSV,
|
|
20
|
+
)
|
|
35
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Generates ECharts configuration for bar chart widgets (vertical and horizontal), including axis, tooltip, legend, and series options styled with the CARTO theme.
|
|
24
|
+
*
|
|
25
|
+
* @param props - Bar chart configuration including data and theme.
|
|
26
|
+
* @returns Widget config with ECharts option object.
|
|
27
|
+
*/
|
|
36
28
|
export function barConfig(props: BarConfig): BarWidgetConfig {
|
|
37
29
|
return {
|
|
38
30
|
type: 'bar',
|
|
39
31
|
option: mergeEchartWidgetConfig(getCommonOptions(props), getOption(props)),
|
|
32
|
+
formatter: props.formatter,
|
|
33
|
+
labelFormatter: props.labelFormatter,
|
|
40
34
|
}
|
|
41
35
|
}
|
|
42
36
|
|
|
@@ -44,6 +38,7 @@ function getOption({
|
|
|
44
38
|
data = [],
|
|
45
39
|
theme,
|
|
46
40
|
formatter,
|
|
41
|
+
labelFormatter,
|
|
47
42
|
}: BarConfig): EchartOptionsProps {
|
|
48
43
|
const hasLegend = (data?.length ?? 0) > 1
|
|
49
44
|
|
|
@@ -51,21 +46,24 @@ function getOption({
|
|
|
51
46
|
let niceMax = 1
|
|
52
47
|
|
|
53
48
|
return {
|
|
54
|
-
legend: buildLegendConfig(hasLegend),
|
|
49
|
+
legend: buildLegendConfig({ hasLegend, labelFormatter }),
|
|
55
50
|
grid: buildGridConfig(hasLegend, theme),
|
|
56
|
-
xAxis:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
51
|
+
xAxis: applyXAxisFormatter(
|
|
52
|
+
{
|
|
53
|
+
type: 'category',
|
|
54
|
+
axisLine: {
|
|
55
|
+
show: false,
|
|
56
|
+
},
|
|
57
|
+
axisTick: {
|
|
58
|
+
show: false,
|
|
59
|
+
},
|
|
60
|
+
axisLabel: {
|
|
61
|
+
padding: [parseInt(theme.spacing(0.5)), 0, 0, 0],
|
|
62
|
+
margin: 0,
|
|
63
|
+
},
|
|
67
64
|
},
|
|
68
|
-
|
|
65
|
+
labelFormatter,
|
|
66
|
+
),
|
|
69
67
|
yAxis: {
|
|
70
68
|
type: 'value' as const,
|
|
71
69
|
min: (extent: { min: number }) => {
|
|
@@ -118,7 +116,9 @@ function getOption({
|
|
|
118
116
|
|
|
119
117
|
const marker = typeof item.marker === 'string' ? item.marker : ''
|
|
120
118
|
const seriesName = item.seriesName ? `${item.seriesName}: ` : ''
|
|
121
|
-
const name =
|
|
119
|
+
const name = labelFormatter
|
|
120
|
+
? String(labelFormatter(item.name ?? ''))
|
|
121
|
+
: (item.name ?? '')
|
|
122
122
|
|
|
123
123
|
return { name, seriesName, marker, value: formattedValue }
|
|
124
124
|
}),
|
package/src/widgets/bar/style.ts
CHANGED
|
@@ -10,14 +10,21 @@ import {
|
|
|
10
10
|
} from './components'
|
|
11
11
|
import { useShallow } from 'zustand/shallow'
|
|
12
12
|
import { useState } from 'react'
|
|
13
|
+
import { defaultFormatter, defaultLabelFormatter } from '../utils/formatter'
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Renders a category widget displaying horizontal bars for categorical data with support for single and multi-series layouts, selection, and overflow grouping.
|
|
17
|
+
*/
|
|
16
18
|
export function CategoryUI({ id }: CategoryUIProps) {
|
|
17
19
|
const theme = useTheme()
|
|
18
20
|
const _formatter = useWidgetStore(
|
|
19
21
|
useShallow((state) => state.getWidget<CategoryWidgetState>(id)?.formatter),
|
|
20
22
|
)
|
|
23
|
+
const _labelFormatter = useWidgetStore(
|
|
24
|
+
useShallow(
|
|
25
|
+
(state) => state.getWidget<CategoryWidgetState>(id)?.labelFormatter,
|
|
26
|
+
),
|
|
27
|
+
)
|
|
21
28
|
const _series = useWidgetStore(
|
|
22
29
|
useShallow((state) => state.getWidget<CategoryWidgetState>(id)?.series),
|
|
23
30
|
)
|
|
@@ -41,6 +48,7 @@ export function CategoryUI({ id }: CategoryUIProps) {
|
|
|
41
48
|
)
|
|
42
49
|
|
|
43
50
|
const formatter = _formatter ?? defaultFormatter
|
|
51
|
+
const labelFormatter = _labelFormatter ?? defaultLabelFormatter
|
|
44
52
|
const series = _series ?? []
|
|
45
53
|
|
|
46
54
|
const [maxHeight] = useState<string | number | undefined>(
|
|
@@ -98,6 +106,7 @@ export function CategoryUI({ id }: CategoryUIProps) {
|
|
|
98
106
|
maxValue={maxValue}
|
|
99
107
|
colors={colors}
|
|
100
108
|
formatter={formatter}
|
|
109
|
+
labelFormatter={labelFormatter}
|
|
101
110
|
onClick={onRowClick}
|
|
102
111
|
selected={selected?.(item.name) ?? true}
|
|
103
112
|
/>
|
|
@@ -111,6 +120,7 @@ export function CategoryUI({ id }: CategoryUIProps) {
|
|
|
111
120
|
maxValue={maxValue}
|
|
112
121
|
color={colors[0]!}
|
|
113
122
|
formatter={formatter}
|
|
123
|
+
labelFormatter={labelFormatter}
|
|
114
124
|
onClick={onRowClick}
|
|
115
125
|
/>
|
|
116
126
|
))}
|
|
@@ -7,6 +7,9 @@ export interface CategoryLegendProps {
|
|
|
7
7
|
colors: string[]
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Renders a color-coded legend for multi-series category widgets.
|
|
12
|
+
*/
|
|
10
13
|
export function CategoryLegend({ series, colors }: CategoryLegendProps) {
|
|
11
14
|
if (series.length === 0) {
|
|
12
15
|
return null
|
|
@@ -9,16 +9,21 @@ export interface CategoryRowMultiProps {
|
|
|
9
9
|
maxValue: number
|
|
10
10
|
colors: string[]
|
|
11
11
|
formatter: NonNullable<CategoryWidgetConfig['formatter']>
|
|
12
|
+
labelFormatter?: CategoryWidgetConfig['labelFormatter']
|
|
12
13
|
onClick?: CategoryWidgetConfig['onRowClick']
|
|
13
14
|
selected?: boolean
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Renders a multi-series category row with a label and multiple color-coded bars stacked vertically.
|
|
19
|
+
*/
|
|
16
20
|
export function CategoryRowMulti({
|
|
17
21
|
name,
|
|
18
22
|
values,
|
|
19
23
|
maxValue,
|
|
20
24
|
colors,
|
|
21
25
|
formatter,
|
|
26
|
+
labelFormatter,
|
|
22
27
|
onClick,
|
|
23
28
|
selected = true,
|
|
24
29
|
}: CategoryRowMultiProps) {
|
|
@@ -27,10 +32,12 @@ export function CategoryRowMulti({
|
|
|
27
32
|
|
|
28
33
|
return (
|
|
29
34
|
<Box sx={rowStyle} onClick={handleClick}>
|
|
30
|
-
<Typography sx={styles.rowLabel}>
|
|
35
|
+
<Typography sx={styles.rowLabel}>
|
|
36
|
+
{labelFormatter ? labelFormatter(name) : name}
|
|
37
|
+
</Typography>
|
|
31
38
|
<Box sx={styles.barContainer}>
|
|
32
39
|
{values.map((value, index) => (
|
|
33
|
-
<Box key={`${name}-${value}`} sx={styles.multiBarRow}>
|
|
40
|
+
<Box key={`${name}-${value}-${index}`} sx={styles.multiBarRow}>
|
|
34
41
|
<Box sx={styles.multiBarContainer}>
|
|
35
42
|
<CategoryBar
|
|
36
43
|
value={value}
|