@carto/ps-react-ui 4.4.2 → 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/download-config-Dqu78h2a.js +57 -0
- package/dist/download-config-Dqu78h2a.js.map +1 -0
- package/dist/formatter-B9Bxn1k7.js +6 -0
- package/dist/formatter-B9Bxn1k7.js.map +1 -0
- package/dist/styles-Y8q7Jff3.js +118 -0
- package/dist/styles-Y8q7Jff3.js.map +1 -0
- package/dist/types/widgets/actions/brush-toggle/types.d.ts +8 -2
- package/dist/types/widgets/category/components/category-row-multi.d.ts +2 -1
- package/dist/types/widgets/category/components/category-row-single.d.ts +2 -1
- package/dist/types/widgets/category/types.d.ts +1 -0
- package/dist/types/widgets/echart/types.d.ts +2 -0
- package/dist/types/widgets/histogram/config.d.ts +15 -3
- package/dist/types/widgets/histogram/index.d.ts +2 -1
- package/dist/types/widgets/histogram/types.d.ts +6 -3
- package/dist/types/widgets/stores/types.d.ts +2 -0
- package/dist/types/widgets/utils/chart-config/index.d.ts +1 -1
- package/dist/types/widgets/utils/chart-config/option-builders.d.ts +13 -8
- package/dist/types/widgets/utils/formatter.d.ts +1 -0
- package/dist/types/widgets/utils/index.d.ts +1 -1
- package/dist/widgets/actions.js +455 -439
- package/dist/widgets/actions.js.map +1 -1
- package/dist/widgets/bar.js +52 -46
- 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/formula.js +1 -1
- package/dist/widgets/histogram.js +119 -79
- package/dist/widgets/histogram.js.map +1 -1
- package/dist/widgets/pie.js +110 -98
- package/dist/widgets/pie.js.map +1 -1
- package/dist/widgets/range.js +1 -1
- package/dist/widgets/scatterplot.js +49 -43
- package/dist/widgets/scatterplot.js.map +1 -1
- package/dist/widgets/spread.js +1 -1
- package/dist/widgets/timeseries.js +51 -45
- 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 +16 -14
- package/dist/widgets/utils.js.map +1 -1
- package/package.json +5 -4
- package/src/widgets/README.md +3 -3
- package/src/widgets/actions/brush-toggle/brush-toggle.tsx +42 -47
- package/src/widgets/actions/brush-toggle/types.ts +8 -2
- package/src/widgets/bar/config.ts +22 -14
- package/src/widgets/category/category-ui.tsx +9 -1
- package/src/widgets/category/components/category-row-multi.tsx +6 -2
- package/src/widgets/category/components/category-row-single.tsx +5 -1
- package/src/widgets/category/types.ts +1 -0
- package/src/widgets/echart/types.ts +2 -0
- package/src/widgets/histogram/config.ts +101 -20
- package/src/widgets/histogram/index.ts +6 -1
- package/src/widgets/histogram/types.ts +9 -3
- package/src/widgets/pie/config.ts +17 -5
- package/src/widgets/scatterplot/config.ts +8 -3
- package/src/widgets/stores/types.ts +2 -0
- package/src/widgets/timeseries/config.ts +21 -13
- package/src/widgets/utils/chart-config/index.ts +1 -1
- package/src/widgets/utils/chart-config/option-builders.ts +22 -12
- package/src/widgets/utils/formatter.ts +2 -1
- package/src/widgets/utils/index.ts +1 -1
- package/dist/formatter-B1Xh8XDH.js +0 -5
- package/dist/formatter-B1Xh8XDH.js.map +0 -1
- package/dist/styles-C_8vOEep.js +0 -167
- package/dist/styles-C_8vOEep.js.map +0 -1
package/dist/widgets/utils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { d as i } from "../formatter-
|
|
2
|
-
import {
|
|
1
|
+
import { d as i, a as n } from "../formatter-B9Bxn1k7.js";
|
|
2
|
+
import { c as f, f as p, s as c } from "../download-config-Dqu78h2a.js";
|
|
3
|
+
import { a as m, b as u, c as C, d as b, e as x, f as g, g as F, h as y, n as A } from "../styles-Y8q7Jff3.js";
|
|
3
4
|
function r({
|
|
4
5
|
type: a,
|
|
5
6
|
getOptions: e
|
|
@@ -13,19 +14,20 @@ function r({
|
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
export {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
m as applyXAxisFormatter,
|
|
18
|
+
u as applyYAxisFormatter,
|
|
19
|
+
C as baseSkeletonStyles,
|
|
20
|
+
b as buildGridConfig,
|
|
21
|
+
x as buildLegendConfig,
|
|
22
|
+
g as createAxisLabelFormatter,
|
|
23
|
+
f as createChartDownloadConfig,
|
|
23
24
|
r as createChartWidgetConfig,
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
F as createTooltipFormatter,
|
|
26
|
+
y as createTooltipPositioner,
|
|
26
27
|
i as defaultFormatter,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
n as defaultLabelFormatter,
|
|
29
|
+
p as flattenObjectArrayToCSV,
|
|
30
|
+
A as niceNum,
|
|
31
|
+
c as scatterplotDataToCSV
|
|
30
32
|
};
|
|
31
33
|
//# sourceMappingURL=utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../src/widgets/utils/chart-config/config-factory.ts"],"sourcesContent":["import type { EchartOptionsProps, EchartWidgetData } from '../../echart'\n\n/**\n * Base configuration interface for chart widgets\n */\nexport interface ChartWidgetBaseConfig<TData = EchartWidgetData> {\n data?: TData\n}\n\n/**\n * Parameters for creating a chart widget configuration\n */\nexport interface CreateChartWidgetConfigParams<\n TData = EchartWidgetData,\n TConfig extends ChartWidgetBaseConfig<TData> = ChartWidgetBaseConfig<TData>,\n TType extends string = string,\n> {\n /** Widget type identifier (e.g., 'bar', 'pie', 'histogram') */\n type: TType\n /** Function to get EChart options from config */\n getOptions: (config: TConfig) => EchartOptionsProps\n}\n\n/**\n * Return type of the chart widget config function\n */\nexport type ChartWidgetConfigResult<\n TData = EchartWidgetData,\n TConfig extends ChartWidgetBaseConfig<TData> = ChartWidgetBaseConfig<TData>,\n TType extends string = string,\n> = TConfig & {\n type: TType\n}\n\n/**\n * Factory function to create a standardized chart widget config function.\n * This eliminates duplication across chart widgets by providing a common structure.\n *\n * @example\n * ```ts\n * export const barConfig = createChartWidgetConfig({\n * type: 'bar' as const,\n * getOptions: ({ data, theme }) => ({\n * // EChart configuration\n * }),\n * csvModifier: (data) => flattenObjectArrayToCSV(data),\n * })\n * ```\n */\nexport function createChartWidgetConfig<\n TData = EchartWidgetData,\n TConfig extends ChartWidgetBaseConfig<TData> = ChartWidgetBaseConfig<TData>,\n TType extends string = string,\n>({\n type,\n getOptions,\n}: CreateChartWidgetConfigParams<TData, TConfig, TType>): (\n config: TConfig,\n) => ChartWidgetConfigResult<TData, TConfig, TType> {\n return function (config: TConfig) {\n return {\n ...config,\n option: getOptions(config),\n type: type,\n } as ChartWidgetConfigResult<TData, TConfig, TType>\n }\n}\n"],"names":["createChartWidgetConfig","type","getOptions","config","option"],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../src/widgets/utils/chart-config/config-factory.ts"],"sourcesContent":["import type { EchartOptionsProps, EchartWidgetData } from '../../echart'\n\n/**\n * Base configuration interface for chart widgets\n */\nexport interface ChartWidgetBaseConfig<TData = EchartWidgetData> {\n data?: TData\n}\n\n/**\n * Parameters for creating a chart widget configuration\n */\nexport interface CreateChartWidgetConfigParams<\n TData = EchartWidgetData,\n TConfig extends ChartWidgetBaseConfig<TData> = ChartWidgetBaseConfig<TData>,\n TType extends string = string,\n> {\n /** Widget type identifier (e.g., 'bar', 'pie', 'histogram') */\n type: TType\n /** Function to get EChart options from config */\n getOptions: (config: TConfig) => EchartOptionsProps\n}\n\n/**\n * Return type of the chart widget config function\n */\nexport type ChartWidgetConfigResult<\n TData = EchartWidgetData,\n TConfig extends ChartWidgetBaseConfig<TData> = ChartWidgetBaseConfig<TData>,\n TType extends string = string,\n> = TConfig & {\n type: TType\n}\n\n/**\n * Factory function to create a standardized chart widget config function.\n * This eliminates duplication across chart widgets by providing a common structure.\n *\n * @example\n * ```ts\n * export const barConfig = createChartWidgetConfig({\n * type: 'bar' as const,\n * getOptions: ({ data, theme }) => ({\n * // EChart configuration\n * }),\n * csvModifier: (data) => flattenObjectArrayToCSV(data),\n * })\n * ```\n */\nexport function createChartWidgetConfig<\n TData = EchartWidgetData,\n TConfig extends ChartWidgetBaseConfig<TData> = ChartWidgetBaseConfig<TData>,\n TType extends string = string,\n>({\n type,\n getOptions,\n}: CreateChartWidgetConfigParams<TData, TConfig, TType>): (\n config: TConfig,\n) => ChartWidgetConfigResult<TData, TConfig, TType> {\n return function (config: TConfig) {\n return {\n ...config,\n option: getOptions(config),\n type: type,\n } as ChartWidgetConfigResult<TData, TConfig, TType>\n }\n}\n"],"names":["createChartWidgetConfig","type","getOptions","config","option"],"mappings":";;;AAiDO,SAASA,EAId;AAAA,EACAC,MAAAA;AAAAA,EACAC,YAAAA;AACoD,GAEF;AAClD,SAAO,SAAUC,GAAiB;AAChC,WAAO;AAAA,MACL,GAAGA;AAAAA,MACHC,QAAQF,EAAWC,CAAM;AAAA,MACzBF,MAAAA;AAAAA,IAAAA;AAAAA,EAEJ;AACF;"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carto/ps-react-ui",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.3",
|
|
4
4
|
"description": "CARTO's Professional Service React Material library",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"devDependencies": {
|
|
7
|
-
"@carto/meridian-ds": "2.
|
|
7
|
+
"@carto/meridian-ds": "2.14.0",
|
|
8
8
|
"@dnd-kit/core": "6.3.1",
|
|
9
9
|
"@dnd-kit/sortable": "10.0.0",
|
|
10
10
|
"@dnd-kit/utilities": "3.2.2",
|
|
@@ -14,15 +14,16 @@
|
|
|
14
14
|
"echarts": "6.0.0",
|
|
15
15
|
"html2canvas": "1.4.1",
|
|
16
16
|
"react-markdown": "10.1.0",
|
|
17
|
-
"zustand": "5.0.
|
|
17
|
+
"zustand": "5.0.12",
|
|
18
18
|
"@carto/ps-common-types": "1.0.0",
|
|
19
19
|
"@carto/ps-utils": "2.0.1"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
|
+
"@carto/meridian-ds": "^2.0.0",
|
|
22
23
|
"@dnd-kit/core": "^6.0.0",
|
|
23
24
|
"@dnd-kit/sortable": "^10.0.0",
|
|
24
25
|
"@dnd-kit/utilities": "^3.0.0",
|
|
25
|
-
"@emotion/styled": "^11.
|
|
26
|
+
"@emotion/styled": "^11.0.0",
|
|
26
27
|
"@mui/icons-material": "^5.0.0",
|
|
27
28
|
"@mui/material": "^5.0.0",
|
|
28
29
|
"echarts": "^6.0.0",
|
package/src/widgets/README.md
CHANGED
|
@@ -70,7 +70,7 @@ export const myWidgetConfig = createChartWidgetConfig({
|
|
|
70
70
|
type: 'my-widget',
|
|
71
71
|
getOptions: ({ data, theme }) => ({
|
|
72
72
|
// EChart configuration
|
|
73
|
-
legend: buildLegendConfig(hasLegend),
|
|
73
|
+
legend: buildLegendConfig({ hasLegend }),
|
|
74
74
|
// ...
|
|
75
75
|
}),
|
|
76
76
|
csvModifier: (data) => flattenObjectArrayToCSV(data),
|
|
@@ -88,7 +88,7 @@ export const myWidgetConfig = createChartWidgetConfig({
|
|
|
88
88
|
|
|
89
89
|
**File:** `utils/chart-config/option-builders.ts`
|
|
90
90
|
|
|
91
|
-
- `buildLegendConfig(hasLegend)` - Standard legend configuration
|
|
91
|
+
- `buildLegendConfig({ hasLegend, labelFormatter? })` - Standard legend configuration
|
|
92
92
|
- `buildGridConfig(hasLegend, theme, additionalConfig?)` - Grid with legend-aware spacing
|
|
93
93
|
- `createTooltipPositioner(theme)` - Tooltip positioning with overflow handling
|
|
94
94
|
|
|
@@ -159,7 +159,7 @@ export const myWidgetConfig = createChartWidgetConfig({
|
|
|
159
159
|
}: Omit<MyConfig, 'refUI'>): EchartOptionsProps {
|
|
160
160
|
const hasLegend = (data?.length ?? 0) > 1
|
|
161
161
|
return {
|
|
162
|
-
legend: buildLegendConfig(hasLegend),
|
|
162
|
+
legend: buildLegendConfig({ hasLegend }),
|
|
163
163
|
grid: buildGridConfig(hasLegend, theme),
|
|
164
164
|
// ... widget-specific configuration
|
|
165
165
|
}
|
|
@@ -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
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
createTooltipPositioner,
|
|
12
12
|
createTooltipFormatter,
|
|
13
13
|
createChartDownloadConfig,
|
|
14
|
+
applyXAxisFormatter,
|
|
14
15
|
niceNum,
|
|
15
16
|
} from '../utils/chart-config'
|
|
16
17
|
|
|
@@ -29,6 +30,7 @@ export function barConfig(props: BarConfig): BarWidgetConfig {
|
|
|
29
30
|
type: 'bar',
|
|
30
31
|
option: mergeEchartWidgetConfig(getCommonOptions(props), getOption(props)),
|
|
31
32
|
formatter: props.formatter,
|
|
33
|
+
labelFormatter: props.labelFormatter,
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -36,6 +38,7 @@ function getOption({
|
|
|
36
38
|
data = [],
|
|
37
39
|
theme,
|
|
38
40
|
formatter,
|
|
41
|
+
labelFormatter,
|
|
39
42
|
}: BarConfig): EchartOptionsProps {
|
|
40
43
|
const hasLegend = (data?.length ?? 0) > 1
|
|
41
44
|
|
|
@@ -43,21 +46,24 @@ function getOption({
|
|
|
43
46
|
let niceMax = 1
|
|
44
47
|
|
|
45
48
|
return {
|
|
46
|
-
legend: buildLegendConfig(hasLegend),
|
|
49
|
+
legend: buildLegendConfig({ hasLegend, labelFormatter }),
|
|
47
50
|
grid: buildGridConfig(hasLegend, theme),
|
|
48
|
-
xAxis:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
+
},
|
|
59
64
|
},
|
|
60
|
-
|
|
65
|
+
labelFormatter,
|
|
66
|
+
),
|
|
61
67
|
yAxis: {
|
|
62
68
|
type: 'value' as const,
|
|
63
69
|
min: (extent: { min: number }) => {
|
|
@@ -110,7 +116,9 @@ function getOption({
|
|
|
110
116
|
|
|
111
117
|
const marker = typeof item.marker === 'string' ? item.marker : ''
|
|
112
118
|
const seriesName = item.seriesName ? `${item.seriesName}: ` : ''
|
|
113
|
-
const name =
|
|
119
|
+
const name = labelFormatter
|
|
120
|
+
? String(labelFormatter(item.name ?? ''))
|
|
121
|
+
: (item.name ?? '')
|
|
114
122
|
|
|
115
123
|
return { name, seriesName, marker, value: formattedValue }
|
|
116
124
|
}),
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from './components'
|
|
11
11
|
import { useShallow } from 'zustand/shallow'
|
|
12
12
|
import { useState } from 'react'
|
|
13
|
-
import { defaultFormatter } from '../utils/formatter'
|
|
13
|
+
import { defaultFormatter, defaultLabelFormatter } from '../utils/formatter'
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Renders a category widget displaying horizontal bars for categorical data with support for single and multi-series layouts, selection, and overflow grouping.
|
|
@@ -20,6 +20,11 @@ export function CategoryUI({ id }: CategoryUIProps) {
|
|
|
20
20
|
const _formatter = useWidgetStore(
|
|
21
21
|
useShallow((state) => state.getWidget<CategoryWidgetState>(id)?.formatter),
|
|
22
22
|
)
|
|
23
|
+
const _labelFormatter = useWidgetStore(
|
|
24
|
+
useShallow(
|
|
25
|
+
(state) => state.getWidget<CategoryWidgetState>(id)?.labelFormatter,
|
|
26
|
+
),
|
|
27
|
+
)
|
|
23
28
|
const _series = useWidgetStore(
|
|
24
29
|
useShallow((state) => state.getWidget<CategoryWidgetState>(id)?.series),
|
|
25
30
|
)
|
|
@@ -43,6 +48,7 @@ export function CategoryUI({ id }: CategoryUIProps) {
|
|
|
43
48
|
)
|
|
44
49
|
|
|
45
50
|
const formatter = _formatter ?? defaultFormatter
|
|
51
|
+
const labelFormatter = _labelFormatter ?? defaultLabelFormatter
|
|
46
52
|
const series = _series ?? []
|
|
47
53
|
|
|
48
54
|
const [maxHeight] = useState<string | number | undefined>(
|
|
@@ -100,6 +106,7 @@ export function CategoryUI({ id }: CategoryUIProps) {
|
|
|
100
106
|
maxValue={maxValue}
|
|
101
107
|
colors={colors}
|
|
102
108
|
formatter={formatter}
|
|
109
|
+
labelFormatter={labelFormatter}
|
|
103
110
|
onClick={onRowClick}
|
|
104
111
|
selected={selected?.(item.name) ?? true}
|
|
105
112
|
/>
|
|
@@ -113,6 +120,7 @@ export function CategoryUI({ id }: CategoryUIProps) {
|
|
|
113
120
|
maxValue={maxValue}
|
|
114
121
|
color={colors[0]!}
|
|
115
122
|
formatter={formatter}
|
|
123
|
+
labelFormatter={labelFormatter}
|
|
116
124
|
onClick={onRowClick}
|
|
117
125
|
/>
|
|
118
126
|
))}
|
|
@@ -9,6 +9,7 @@ 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
|
}
|
|
@@ -22,6 +23,7 @@ export function CategoryRowMulti({
|
|
|
22
23
|
maxValue,
|
|
23
24
|
colors,
|
|
24
25
|
formatter,
|
|
26
|
+
labelFormatter,
|
|
25
27
|
onClick,
|
|
26
28
|
selected = true,
|
|
27
29
|
}: CategoryRowMultiProps) {
|
|
@@ -30,10 +32,12 @@ export function CategoryRowMulti({
|
|
|
30
32
|
|
|
31
33
|
return (
|
|
32
34
|
<Box sx={rowStyle} onClick={handleClick}>
|
|
33
|
-
<Typography sx={styles.rowLabel}>
|
|
35
|
+
<Typography sx={styles.rowLabel}>
|
|
36
|
+
{labelFormatter ? labelFormatter(name) : name}
|
|
37
|
+
</Typography>
|
|
34
38
|
<Box sx={styles.barContainer}>
|
|
35
39
|
{values.map((value, index) => (
|
|
36
|
-
<Box key={`${name}-${value}`} sx={styles.multiBarRow}>
|
|
40
|
+
<Box key={`${name}-${value}-${index}`} sx={styles.multiBarRow}>
|
|
37
41
|
<Box sx={styles.multiBarContainer}>
|
|
38
42
|
<CategoryBar
|
|
39
43
|
value={value}
|
|
@@ -9,6 +9,7 @@ export interface CategoryRowSingleProps {
|
|
|
9
9
|
maxValue: number
|
|
10
10
|
color: string
|
|
11
11
|
formatter: NonNullable<CategoryWidgetConfig['formatter']>
|
|
12
|
+
labelFormatter?: CategoryWidgetConfig['labelFormatter']
|
|
12
13
|
onClick?: CategoryWidgetConfig['onRowClick']
|
|
13
14
|
selected?: boolean
|
|
14
15
|
}
|
|
@@ -22,6 +23,7 @@ export function CategoryRowSingle({
|
|
|
22
23
|
maxValue,
|
|
23
24
|
color,
|
|
24
25
|
formatter,
|
|
26
|
+
labelFormatter,
|
|
25
27
|
onClick,
|
|
26
28
|
selected = true,
|
|
27
29
|
}: CategoryRowSingleProps) {
|
|
@@ -36,7 +38,9 @@ export function CategoryRowSingle({
|
|
|
36
38
|
return (
|
|
37
39
|
<Box sx={rowStyle} onClick={handleClick}>
|
|
38
40
|
<Box sx={styles.rowHeader}>
|
|
39
|
-
<Typography sx={styles.rowLabel}>
|
|
41
|
+
<Typography sx={styles.rowLabel}>
|
|
42
|
+
{labelFormatter ? labelFormatter(name) : name}
|
|
43
|
+
</Typography>
|
|
40
44
|
<Typography sx={styles.rowValue}>{formatter(value)}</Typography>
|
|
41
45
|
</Box>
|
|
42
46
|
<CategoryBar
|
|
@@ -29,6 +29,7 @@ export type CategoryWidgetState = BaseWidgetState<
|
|
|
29
29
|
|
|
30
30
|
export interface CategoryWidgetConfig {
|
|
31
31
|
formatter?: (value: number) => string
|
|
32
|
+
labelFormatter?: (value: string | number) => string | number
|
|
32
33
|
series?: CategorySeriesConfig[]
|
|
33
34
|
maxItems?: number
|
|
34
35
|
labels?: CategoryLabels
|
|
@@ -33,6 +33,7 @@ export interface EchartWidgetOptionProps<D> {
|
|
|
33
33
|
data?: D
|
|
34
34
|
theme: typeof CartoTheme
|
|
35
35
|
formatter?: (value: number) => string
|
|
36
|
+
labelFormatter?: (value: string | number) => string | number
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
export interface EchartWidgetProps {
|
|
@@ -40,4 +41,5 @@ export interface EchartWidgetProps {
|
|
|
40
41
|
option: EchartUIProps['option']
|
|
41
42
|
onEvents?: EchartUIProps['onEvents']
|
|
42
43
|
formatter?: (value: number) => string
|
|
44
|
+
labelFormatter?: (value: string | number) => string | number
|
|
43
45
|
}
|
|
@@ -3,27 +3,104 @@ import {
|
|
|
3
3
|
mergeEchartWidgetConfig,
|
|
4
4
|
type EchartOptionsProps,
|
|
5
5
|
} from '../echart'
|
|
6
|
-
import type {
|
|
7
|
-
HistogramConfig,
|
|
8
|
-
HistogramWidgetConfig,
|
|
9
|
-
HistogramWidgetData,
|
|
10
|
-
} from './types'
|
|
6
|
+
import type { HistogramConfig, HistogramWidgetConfig } from './types'
|
|
11
7
|
import {
|
|
12
|
-
flattenObjectArrayToCSV,
|
|
13
8
|
buildLegendConfig,
|
|
14
9
|
buildGridConfig,
|
|
15
10
|
createTooltipPositioner,
|
|
16
11
|
createTooltipFormatter,
|
|
17
|
-
createChartDownloadConfig,
|
|
18
12
|
niceNum,
|
|
19
13
|
} from '../utils/chart-config'
|
|
14
|
+
import { downloadToCSV, downloadToPNG, type DownloadItem } from '../actions'
|
|
15
|
+
import type { ConfigProps } from '../loader/types'
|
|
16
|
+
|
|
17
|
+
export interface HistogramDownloadConfigProps extends ConfigProps {
|
|
18
|
+
ticks: number[]
|
|
19
|
+
labelFormatter?: (value: string | number) => string | number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function histogramDataToCSV(
|
|
23
|
+
data: number[][],
|
|
24
|
+
ticks: number[],
|
|
25
|
+
labelFormatter?: (value: string | number) => string | number,
|
|
26
|
+
): string[][] {
|
|
27
|
+
if (!data?.length || data[0]?.length === 0) return []
|
|
28
|
+
|
|
29
|
+
const dataLength = data[0]?.length ?? 0
|
|
30
|
+
const labels = createAxisLabels(dataLength, ticks, labelFormatter)
|
|
31
|
+
const seriesCount = data.length
|
|
32
|
+
const isMulti = seriesCount > 1
|
|
33
|
+
|
|
34
|
+
const headers = isMulti
|
|
35
|
+
? [
|
|
36
|
+
'Bin',
|
|
37
|
+
...Array.from({ length: seriesCount }, (_, i) => `Series ${i + 1}`),
|
|
38
|
+
]
|
|
39
|
+
: ['Bin', 'Value']
|
|
40
|
+
|
|
41
|
+
return [
|
|
42
|
+
headers,
|
|
43
|
+
...labels.map((label, i) => [
|
|
44
|
+
label,
|
|
45
|
+
...data.map((series) => String(series[i] ?? 0)),
|
|
46
|
+
]),
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function histogramDownloadConfig({
|
|
51
|
+
refUI,
|
|
52
|
+
ticks,
|
|
53
|
+
labelFormatter,
|
|
54
|
+
}: HistogramDownloadConfigProps): DownloadItem<number[][]>[] {
|
|
55
|
+
return [
|
|
56
|
+
{
|
|
57
|
+
...downloadToPNG,
|
|
58
|
+
modifier: () => downloadToPNG.modifier(refUI),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
...downloadToCSV,
|
|
62
|
+
modifier: async (data) =>
|
|
63
|
+
downloadToCSV.modifier(histogramDataToCSV(data, ticks, labelFormatter)),
|
|
64
|
+
},
|
|
65
|
+
]
|
|
66
|
+
}
|
|
20
67
|
|
|
21
|
-
export const histogramDownloadConfig =
|
|
22
|
-
createChartDownloadConfig<HistogramWidgetData>(flattenObjectArrayToCSV)
|
|
23
68
|
/**
|
|
24
|
-
*
|
|
69
|
+
* Creates formatted axis labels from tick boundaries.
|
|
25
70
|
*
|
|
26
|
-
* @param
|
|
71
|
+
* @param dataLength - Number of data points (determines number of labels).
|
|
72
|
+
* @param ticks - Bin boundaries. If `ticks.length === dataLength + 1`, all
|
|
73
|
+
* bins are ranges. If `ticks.length === dataLength`, the last bin is
|
|
74
|
+
* open-ended (`+`). A last tick of `Infinity` also produces `+`.
|
|
75
|
+
* @param labelFormatter - Optional formatter applied to each individual tick
|
|
76
|
+
* value when building the bin range label.
|
|
77
|
+
*/
|
|
78
|
+
function createAxisLabels(
|
|
79
|
+
dataLength: number,
|
|
80
|
+
ticks: number[],
|
|
81
|
+
labelFormatter?: (value: string | number) => string | number,
|
|
82
|
+
): string[] {
|
|
83
|
+
const fmt = (v: number) =>
|
|
84
|
+
labelFormatter ? String(labelFormatter(v)) : String(v)
|
|
85
|
+
|
|
86
|
+
return Array.from({ length: dataLength }, (_, i) => {
|
|
87
|
+
const low = ticks[i] ?? i
|
|
88
|
+
const high = ticks[i + 1]
|
|
89
|
+
return high !== undefined && isFinite(high)
|
|
90
|
+
? `${fmt(low)}-${fmt(high)}`
|
|
91
|
+
: `${fmt(low)}+`
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Generates ECharts configuration for distribution histogram widgets with
|
|
97
|
+
* adjacent bars (minimal gap) and axis formatting styled with the CARTO theme.
|
|
98
|
+
*
|
|
99
|
+
* Accepts raw `number[][]` data and `ticks` boundaries. The ticks and
|
|
100
|
+
* `labelFormatter` are used to create the x-axis category labels; the raw
|
|
101
|
+
* numeric data is embedded directly in each series.
|
|
102
|
+
*
|
|
103
|
+
* @param props - Histogram configuration including raw data, ticks, and theme.
|
|
27
104
|
* @returns Widget config with ECharts option object.
|
|
28
105
|
*/
|
|
29
106
|
export function histogramConfig(props: HistogramConfig): HistogramWidgetConfig {
|
|
@@ -33,21 +110,27 @@ export function histogramConfig(props: HistogramConfig): HistogramWidgetConfig {
|
|
|
33
110
|
formatter: props.formatter,
|
|
34
111
|
}
|
|
35
112
|
}
|
|
113
|
+
|
|
36
114
|
function getOption({
|
|
37
115
|
data = [],
|
|
116
|
+
ticks,
|
|
38
117
|
theme,
|
|
39
118
|
formatter,
|
|
119
|
+
labelFormatter,
|
|
40
120
|
}: HistogramConfig): EchartOptionsProps {
|
|
41
121
|
const hasLegend = (data?.length ?? 0) > 1
|
|
122
|
+
const dataLength = data[0]?.length ?? 0
|
|
123
|
+
const axisLabels = createAxisLabels(dataLength, ticks, labelFormatter)
|
|
42
124
|
|
|
43
125
|
let niceMin = 0
|
|
44
126
|
let niceMax = 1
|
|
45
127
|
|
|
46
128
|
return {
|
|
47
|
-
legend: buildLegendConfig(hasLegend),
|
|
129
|
+
legend: buildLegendConfig({ hasLegend }),
|
|
48
130
|
grid: buildGridConfig(hasLegend, theme),
|
|
49
131
|
xAxis: {
|
|
50
132
|
type: 'category',
|
|
133
|
+
data: axisLabels,
|
|
51
134
|
axisLine: {
|
|
52
135
|
show: false,
|
|
53
136
|
},
|
|
@@ -117,14 +200,12 @@ function getOption({
|
|
|
117
200
|
tooltip: {
|
|
118
201
|
position: createTooltipPositioner(theme),
|
|
119
202
|
formatter: createTooltipFormatter((item) => {
|
|
120
|
-
const
|
|
121
|
-
const index = item.dimensionNames?.[item.encode?.y?.at(0) ?? 1]
|
|
122
|
-
const _value = value[index ?? '']
|
|
203
|
+
const _value = item.value as number
|
|
123
204
|
|
|
124
205
|
const formattedValue =
|
|
125
206
|
typeof _value === 'number' && formatter
|
|
126
207
|
? formatter(_value)
|
|
127
|
-
: (_value ?? '')
|
|
208
|
+
: String(_value ?? '')
|
|
128
209
|
|
|
129
210
|
const marker = typeof item.marker === 'string' ? item.marker : ''
|
|
130
211
|
const seriesName = item.seriesName ? `${item.seriesName}: ` : ''
|
|
@@ -133,11 +214,11 @@ function getOption({
|
|
|
133
214
|
return { name, seriesName, marker, value: formattedValue }
|
|
134
215
|
}),
|
|
135
216
|
},
|
|
136
|
-
series: data.map((
|
|
137
|
-
datasetIndex: index,
|
|
217
|
+
series: data.map((seriesData: number[]) => ({
|
|
138
218
|
type: 'bar',
|
|
139
|
-
|
|
140
|
-
|
|
219
|
+
data: seriesData,
|
|
220
|
+
barGap: '1%',
|
|
221
|
+
barCategoryGap: '1%',
|
|
141
222
|
emphasis: {
|
|
142
223
|
focus: 'series',
|
|
143
224
|
},
|
|
@@ -4,5 +4,10 @@ export type {
|
|
|
4
4
|
HistogramWidgetData,
|
|
5
5
|
HistogramWidgetState,
|
|
6
6
|
} from './types'
|
|
7
|
-
export {
|
|
7
|
+
export type { HistogramDownloadConfigProps } from './config'
|
|
8
|
+
export {
|
|
9
|
+
histogramConfig,
|
|
10
|
+
histogramDataToCSV,
|
|
11
|
+
histogramDownloadConfig,
|
|
12
|
+
} from './config'
|
|
8
13
|
export { HistogramSkeleton } from './skeleton'
|