@cdc/chart 4.25.10 → 4.25.11
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/{cdcchart-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
- package/dist/cdcchart.js +36258 -34658
- package/examples/feature/__data__/planet-example-data.json +1 -1
- package/examples/feature/boxplot/valid-boxplot.csv +38 -17
- package/examples/private/DEV-11825.json +573 -0
- package/examples/private/na.json +913 -0
- package/examples/private/test-data.csv +28 -0
- package/index.html +2 -121
- package/package.json +4 -4
- package/src/CdcChart.tsx +8 -11
- package/src/CdcChartComponent.tsx +256 -87
- package/src/_stories/Chart.Combo.stories.tsx +18 -0
- package/src/_stories/Chart.Forecast.stories.tsx +36 -0
- package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
- package/src/_stories/Chart.Patterns.stories.tsx +2 -1
- package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
- package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
- package/src/_stories/ChartAnnotation.stories.tsx +6 -3
- package/src/_stories/ChartBar.Editor.stories.tsx +3580 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +658 -0
- package/src/_stories/ChartEditor.stories.tsx +1 -2
- package/src/_stories/_mock/combo.json +451 -0
- package/src/_stories/_mock/editor-test-configs.json +376 -0
- package/src/_stories/_mock/editor-test-datasets.json +477 -0
- package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
- package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
- package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
- package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
- package/src/_stories/_mock/pie_config.json +257 -62
- package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
- package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
- package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
- package/src/components/Annotations/components/findNearestDatum.ts +6 -41
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -6
- package/src/components/AreaChart/index.tsx +1 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +4 -4
- package/src/components/BarChart/components/BarChart.Vertical.tsx +3 -2
- package/src/components/BoxPlot/helpers/index.ts +3 -3
- package/src/components/Brush/BrushChart.tsx +1 -1
- package/src/components/EditorPanel/EditorPanel.tsx +199 -190
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -1
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +102 -55
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +54 -49
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +422 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +75 -21
- package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
- package/src/components/EditorPanel/editor-panel.scss +0 -20
- package/src/components/EditorPanel/useEditorPermissions.ts +7 -15
- package/src/components/Forecasting/Forecasting.tsx +139 -21
- package/src/components/Legend/Legend.Component.tsx +16 -9
- package/src/components/Legend/helpers/createFormatLabels.tsx +181 -181
- package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
- package/src/components/LineChart/LineChartProps.ts +0 -3
- package/src/components/LineChart/helpers.ts +1 -1
- package/src/components/LineChart/index.tsx +36 -13
- package/src/components/LinearChart.tsx +75 -80
- package/src/components/Regions/components/Regions.tsx +3 -24
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +198 -0
- package/src/components/SmallMultiples/SmallMultiples.css +32 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
- package/src/components/SmallMultiples/index.ts +2 -0
- package/src/data/initial-state.js +13 -1
- package/src/helpers/buildForecastPaletteOptions.ts +0 -38
- package/src/helpers/getColorScale.ts +10 -0
- package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +14 -7
- package/src/helpers/getYAxisAutoPadding.ts +53 -0
- package/src/helpers/smallMultiplesHelpers.ts +529 -0
- package/src/hooks/useProgrammaticTooltip.ts +96 -0
- package/src/hooks/useScales.ts +88 -34
- package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
- package/src/hooks/useTooltip.tsx +60 -15
- package/src/scss/main.scss +1 -80
- package/src/store/chart.actions.ts +2 -0
- package/src/store/chart.reducer.ts +4 -0
- package/src/types/ChartConfig.ts +24 -6
- package/src/types/ChartContext.ts +3 -0
- package/src/_stories/_mock/pie_data.json +0 -218
- package/src/components/AreaChart/components/AreaChart.jsx +0 -109
- package/src/helpers/sort.ts +0 -7
- package/src/hooks/useActiveElement.js +0 -19
- package/src/hooks/useChartClasses.js +0 -41
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import React, { useContext, useEffect, useRef, useState, useMemo, useCallback } from 'react'
|
|
2
|
+
import SmallMultipleTile from './SmallMultipleTile'
|
|
3
|
+
import ConfigContext from '../../ConfigContext'
|
|
4
|
+
import useReduceData from '../../hooks/useReduceData'
|
|
5
|
+
import useScales from '../../hooks/useScales'
|
|
6
|
+
import { createCombinedDataForYAxis, applyTileOrder, createTileColorScale } from '../../helpers/smallMultiplesHelpers'
|
|
7
|
+
import { isMobileSmallMultiplesViewport } from '@cdc/core/helpers/viewports'
|
|
8
|
+
import './SmallMultiples.css'
|
|
9
|
+
import { ChartConfig } from '../../types/ChartConfig'
|
|
10
|
+
|
|
11
|
+
interface SmallMultiplesProps {
|
|
12
|
+
config: ChartConfig
|
|
13
|
+
data: object[]
|
|
14
|
+
svgRef?: React.RefObject<SVGAElement>
|
|
15
|
+
parentWidth?: number
|
|
16
|
+
parentHeight?: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type TileItem = {
|
|
20
|
+
key: string | number
|
|
21
|
+
mode: 'by-series' | 'by-column'
|
|
22
|
+
seriesKey?: string
|
|
23
|
+
tileValue?: any
|
|
24
|
+
tileColumn?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type ChartRefWithTooltipMethods = {
|
|
28
|
+
triggerTooltipAtDataValue?: (xAxisValue: any, yCoordinate: number) => void
|
|
29
|
+
hideTooltip?: () => void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type TileHeaderRows = Array<Array<HTMLDivElement>>
|
|
33
|
+
|
|
34
|
+
type TileHeaderEntries = Array<[string, HTMLDivElement]>
|
|
35
|
+
|
|
36
|
+
const SmallMultiples: React.FC<SmallMultiplesProps> = ({ config, data, svgRef, parentWidth, parentHeight }) => {
|
|
37
|
+
const { currentViewport, colorScale, parentRef } = useContext(ConfigContext)
|
|
38
|
+
const { mode, tileColumn, tilesPerRowDesktop, tilesPerRowMobile } = config.smallMultiples || {}
|
|
39
|
+
|
|
40
|
+
const isMobile = isMobileSmallMultiplesViewport(currentViewport)
|
|
41
|
+
const tilesPerRow = isMobile ? tilesPerRowMobile || 1 : tilesPerRowDesktop || 3
|
|
42
|
+
|
|
43
|
+
// Figure out what objects to iterate over based on mode - memoized to prevent recalculation
|
|
44
|
+
const tileItems = useMemo<Array<TileItem>>(() => {
|
|
45
|
+
let items: Array<TileItem> = []
|
|
46
|
+
|
|
47
|
+
if (mode === 'by-series') {
|
|
48
|
+
items = config.series.map(series => ({
|
|
49
|
+
key: series.dataKey,
|
|
50
|
+
mode: 'by-series' as const,
|
|
51
|
+
seriesKey: series.dataKey
|
|
52
|
+
}))
|
|
53
|
+
} else if (mode === 'by-column') {
|
|
54
|
+
const uniqueValues = Array.from(new Set(data.map(row => row[tileColumn])))
|
|
55
|
+
.filter(val => val != null)
|
|
56
|
+
.sort()
|
|
57
|
+
items = uniqueValues.map(value => ({
|
|
58
|
+
key: value,
|
|
59
|
+
mode: 'by-column' as const,
|
|
60
|
+
tileValue: value,
|
|
61
|
+
tileColumn: tileColumn
|
|
62
|
+
}))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Apply tile ordering based on user preference
|
|
66
|
+
return applyTileOrder(
|
|
67
|
+
items,
|
|
68
|
+
config.smallMultiples?.tileOrderType || 'asc',
|
|
69
|
+
config.smallMultiples?.tileOrder,
|
|
70
|
+
config
|
|
71
|
+
)
|
|
72
|
+
}, [
|
|
73
|
+
mode,
|
|
74
|
+
config.series,
|
|
75
|
+
data,
|
|
76
|
+
tileColumn,
|
|
77
|
+
config.smallMultiples?.tileOrderType,
|
|
78
|
+
config.smallMultiples?.tileOrder,
|
|
79
|
+
config.smallMultiples?.tileTitles
|
|
80
|
+
])
|
|
81
|
+
|
|
82
|
+
// Calculate the grid styling based on tiles per row
|
|
83
|
+
const gridGap = isMobile ? '1rem' : '2rem'
|
|
84
|
+
const gridStyle = {
|
|
85
|
+
gridTemplateColumns: `repeat(${tilesPerRow}, 1fr)`,
|
|
86
|
+
gap: gridGap
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const [tileHeights, setTileHeights] = useState<Record<string, number>>({})
|
|
90
|
+
|
|
91
|
+
// Refs to all LinearChart instances for tooltip coordination
|
|
92
|
+
const chartRefs = useRef<Record<string, ChartRefWithTooltipMethods>>({})
|
|
93
|
+
|
|
94
|
+
// Refs to all tile header elements for height alignment
|
|
95
|
+
const headerRefs = useRef<Record<string, HTMLDivElement | null>>({})
|
|
96
|
+
|
|
97
|
+
// Create combined data and config for consistent Y-axis calculation
|
|
98
|
+
const combinedDataForYAxis = useMemo(
|
|
99
|
+
() => createCombinedDataForYAxis(config, data, tileItems),
|
|
100
|
+
[config, data, tileItems]
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(
|
|
104
|
+
combinedDataForYAxis.config,
|
|
105
|
+
combinedDataForYAxis.data
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const inlineLabel = config.yAxis?.inlineLabel
|
|
109
|
+
const inlineLabelHasNoSpace = !inlineLabel?.includes(' ')
|
|
110
|
+
const needsYAxisAutoPadding = inlineLabel && !inlineLabelHasNoSpace
|
|
111
|
+
|
|
112
|
+
const { min, max } = useScales({
|
|
113
|
+
config: combinedDataForYAxis.config,
|
|
114
|
+
data: combinedDataForYAxis.data,
|
|
115
|
+
tableData: combinedDataForYAxis.data,
|
|
116
|
+
minValue,
|
|
117
|
+
maxValue,
|
|
118
|
+
existPositiveValue,
|
|
119
|
+
isAllLine,
|
|
120
|
+
xAxisDataMapped: [],
|
|
121
|
+
xMax: parentWidth,
|
|
122
|
+
yMax: parentHeight,
|
|
123
|
+
needsYAxisAutoPadding,
|
|
124
|
+
currentViewport
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Use consistent Y-axis if the feature is enabled and we have valid values
|
|
128
|
+
const globalYAxisValues = useMemo(() => {
|
|
129
|
+
if (config.smallMultiples?.independentYAxis) return null
|
|
130
|
+
if (typeof min !== 'number' || typeof max !== 'number') return null
|
|
131
|
+
if (combinedDataForYAxis.data.length === 0) return null
|
|
132
|
+
|
|
133
|
+
return { min, max }
|
|
134
|
+
}, [config.smallMultiples?.independentYAxis, min, max, combinedDataForYAxis.data.length])
|
|
135
|
+
|
|
136
|
+
const numberOfRows = useMemo(() => Math.ceil(tileItems.length / tilesPerRow), [tileItems.length, tilesPerRow])
|
|
137
|
+
|
|
138
|
+
// Handle tile height changes from ResizeObserver
|
|
139
|
+
const handleTileHeightChange = useCallback((tileKey: string, height: number) => {
|
|
140
|
+
setTileHeights(prev => ({ ...prev, [tileKey]: height }))
|
|
141
|
+
}, [])
|
|
142
|
+
|
|
143
|
+
// Handle tooltip synchronization across small multiple tiles
|
|
144
|
+
const handleChartHover = useCallback(
|
|
145
|
+
(sourceTileKey: string, xAxisValue: any, yCoordinate: number) => {
|
|
146
|
+
if (!config.smallMultiples?.synchronizedTooltips) return
|
|
147
|
+
|
|
148
|
+
// If xAxisValue is null, it means mouse left the chart - hide all tooltips
|
|
149
|
+
if (xAxisValue === null) {
|
|
150
|
+
Object.entries(chartRefs.current).forEach(([tileKey, chartRef]) => {
|
|
151
|
+
if (tileKey !== sourceTileKey && chartRef?.hideTooltip) {
|
|
152
|
+
chartRef.hideTooltip()
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// For each OTHER chart in the grid, trigger tooltip at same X-axis data value and exact Y coordinate
|
|
159
|
+
Object.entries(chartRefs.current).forEach(([tileKey, chartRef]) => {
|
|
160
|
+
if (tileKey === sourceTileKey || !chartRef) return
|
|
161
|
+
|
|
162
|
+
if (chartRef.triggerTooltipAtDataValue) {
|
|
163
|
+
chartRef.triggerTooltipAtDataValue(xAxisValue, yCoordinate)
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
},
|
|
167
|
+
[config.smallMultiples?.synchronizedTooltips]
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
// Align tile header heights per row
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
const headerEntries = Object.entries(headerRefs.current).filter(([_, ref]) => ref) as TileHeaderEntries
|
|
173
|
+
if (headerEntries.length === 0) return
|
|
174
|
+
|
|
175
|
+
// Group headers by row based on their index in tileItems
|
|
176
|
+
const headersByRow: TileHeaderRows = []
|
|
177
|
+
|
|
178
|
+
tileItems.forEach((item, index) => {
|
|
179
|
+
const rowIndex = Math.floor(index / tilesPerRow)
|
|
180
|
+
const header = headerRefs.current[String(item.key)]
|
|
181
|
+
|
|
182
|
+
headersByRow[rowIndex] ||= []
|
|
183
|
+
headersByRow[rowIndex].push(header)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// For each row, find the header with longest text and align others to it
|
|
187
|
+
headersByRow.forEach(rowHeaders => {
|
|
188
|
+
let longestHeader: HTMLDivElement | null = null
|
|
189
|
+
let maxTextLength = 0
|
|
190
|
+
|
|
191
|
+
rowHeaders.forEach(header => {
|
|
192
|
+
const textLength = header.textContent?.length || 0
|
|
193
|
+
if (textLength > maxTextLength) {
|
|
194
|
+
maxTextLength = textLength
|
|
195
|
+
longestHeader = header
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
if (!longestHeader) return
|
|
200
|
+
|
|
201
|
+
// Get the height of the longest header in this row
|
|
202
|
+
const targetHeight = longestHeader.offsetHeight
|
|
203
|
+
|
|
204
|
+
// Apply that height to all other headers in this row
|
|
205
|
+
rowHeaders.forEach(header => {
|
|
206
|
+
header.style.minHeight = header !== longestHeader ? `${targetHeight}px` : 'auto'
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
}, [tileItems, tilesPerRow])
|
|
210
|
+
|
|
211
|
+
// Calculate container height from measured tile heights
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
if (!parentRef.current) return
|
|
214
|
+
|
|
215
|
+
const measuredHeights = Object.values(tileHeights)
|
|
216
|
+
if (measuredHeights.length === 0) {
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const maxTileHeight = Math.max(...measuredHeights)
|
|
221
|
+
const gapSize = isMobile ? 18 : 36
|
|
222
|
+
const totalGapsHeight = (numberOfRows - 1) * gapSize
|
|
223
|
+
const totalHeight = numberOfRows * maxTileHeight + totalGapsHeight
|
|
224
|
+
|
|
225
|
+
parentRef.current.style.height = `${totalHeight}px`
|
|
226
|
+
}, [tileHeights, numberOfRows, isMobile, parentRef, tilesPerRow])
|
|
227
|
+
|
|
228
|
+
if (tileItems.length === 0) {
|
|
229
|
+
return null
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<div className='small-multiples-container'>
|
|
234
|
+
<div className='small-multiples-grid' style={gridStyle}>
|
|
235
|
+
{tileItems.map((item, index) => {
|
|
236
|
+
const customColorScale = createTileColorScale(item, config, colorScale, index, tileItems.length)
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<SmallMultipleTile
|
|
240
|
+
key={item.key}
|
|
241
|
+
tileKey={String(item.key)}
|
|
242
|
+
mode={item.mode}
|
|
243
|
+
seriesKey={item.seriesKey}
|
|
244
|
+
tileValue={item.tileValue}
|
|
245
|
+
tileColumn={item.tileColumn}
|
|
246
|
+
customColorScale={customColorScale}
|
|
247
|
+
config={config}
|
|
248
|
+
data={data}
|
|
249
|
+
svgRef={svgRef}
|
|
250
|
+
parentWidth={parentWidth}
|
|
251
|
+
parentHeight={parentHeight}
|
|
252
|
+
globalYAxisMax={globalYAxisValues?.max}
|
|
253
|
+
globalYAxisMin={globalYAxisValues?.min}
|
|
254
|
+
isFirstInRow={index % tilesPerRow === 0}
|
|
255
|
+
onHeightChange={handleTileHeightChange}
|
|
256
|
+
onChartRef={ref => {
|
|
257
|
+
chartRefs.current[String(item.key)] = ref
|
|
258
|
+
}}
|
|
259
|
+
onHeaderRef={ref => {
|
|
260
|
+
headerRefs.current[String(item.key)] = ref
|
|
261
|
+
}}
|
|
262
|
+
onChartHover={(xAxisValue, yCoordinate) => handleChartHover(String(item.key), xAxisValue, yCoordinate)}
|
|
263
|
+
/>
|
|
264
|
+
)
|
|
265
|
+
})}
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export default SmallMultiples
|
|
@@ -198,7 +198,19 @@ const createInitialState = () => {
|
|
|
198
198
|
patterns: {},
|
|
199
199
|
patternField: ''
|
|
200
200
|
},
|
|
201
|
-
|
|
201
|
+
smallMultiples: {
|
|
202
|
+
mode: '',
|
|
203
|
+
tileColumn: '',
|
|
204
|
+
tilesPerRowDesktop: 3,
|
|
205
|
+
tilesPerRowMobile: 1,
|
|
206
|
+
tileOrder: [],
|
|
207
|
+
tileOrderType: 'asc',
|
|
208
|
+
tileTitles: {},
|
|
209
|
+
independentYAxis: false,
|
|
210
|
+
colorMode: 'same',
|
|
211
|
+
synchronizedTooltips: true,
|
|
212
|
+
showAreaUnderLine: true
|
|
213
|
+
},
|
|
202
214
|
exclusions: {
|
|
203
215
|
active: false,
|
|
204
216
|
keys: []
|
|
@@ -69,41 +69,3 @@ export const buildForecastPaletteOptions = (
|
|
|
69
69
|
|
|
70
70
|
return paletteOptions
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Normalizes a palette value to match the standardized hyphenated format
|
|
75
|
-
* and migrates v1 palette names to v2 equivalents
|
|
76
|
-
*
|
|
77
|
-
* @param value - The palette name to normalize
|
|
78
|
-
* @param paletteVersion - The palette version (1 or 2) from the config
|
|
79
|
-
* @returns The normalized palette name in lowercase with hyphens, or 'Select' if empty
|
|
80
|
-
*/
|
|
81
|
-
export const normalizePaletteValue = (value: string | undefined, paletteVersion: number = 1): string => {
|
|
82
|
-
if (!value) return 'Select'
|
|
83
|
-
|
|
84
|
-
// Convert to lowercase with hyphens for consistent matching
|
|
85
|
-
const normalized = value.toLowerCase().replace(/ /g, '-').replace(/_/g, '-')
|
|
86
|
-
|
|
87
|
-
// If v2, migrate v1-only palette names to their v2 equivalents
|
|
88
|
-
if (paletteVersion === 2) {
|
|
89
|
-
const V1_TO_V2_MIGRATION: Record<string, string> = {
|
|
90
|
-
// Sequential Blue variants → sequential-blue
|
|
91
|
-
'sequential-blue-two': 'sequential-blue',
|
|
92
|
-
'sequential-blue-three': 'sequential-blue',
|
|
93
|
-
'sequential-blue-2-(mpx)': 'sequential-blue',
|
|
94
|
-
'sequential-blue-tworeverse': 'sequential-bluereverse',
|
|
95
|
-
'sequential-blue-threereverse': 'sequential-bluereverse',
|
|
96
|
-
'sequential-blue-2-(mpx)reverse': 'sequential-bluereverse',
|
|
97
|
-
|
|
98
|
-
// Sequential Orange variants → sequential-orange
|
|
99
|
-
'sequential-orange-two': 'sequential-orange',
|
|
100
|
-
'sequential-orange-(mpx)': 'sequential-orange',
|
|
101
|
-
'sequential-orange-tworeverse': 'sequential-orangereverse',
|
|
102
|
-
'sequential-orange-(mpx)reverse': 'sequential-orangereverse'
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return V1_TO_V2_MIGRATION[normalized] || normalized
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return normalized
|
|
109
|
-
}
|
|
@@ -32,6 +32,16 @@ export const getColorScale = (config: ChartConfig): ((value: string) => string)
|
|
|
32
32
|
// Migrate old palette name if needed
|
|
33
33
|
const migratedPaletteName = configPalette ? configPalette : getFallbackColorPalette(config)
|
|
34
34
|
|
|
35
|
+
// Check for customColorsOrdered first (direct 1-to-1 mapping, no distribution needed)
|
|
36
|
+
if (config.general?.palette?.customColorsOrdered && Array.isArray(config.general.palette.customColorsOrdered)) {
|
|
37
|
+
const customColorsOrdered = config.general.palette.customColorsOrdered
|
|
38
|
+
return scaleOrdinal({
|
|
39
|
+
domain: config.runtime.seriesLabelsAll,
|
|
40
|
+
range: customColorsOrdered,
|
|
41
|
+
unknown: null
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
let palette =
|
|
36
46
|
config.general?.palette?.customColors ||
|
|
37
47
|
palettesSource[migratePaletteWithMap(migratedPaletteName, paletteMigrationMap, false)] ||
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { ChartConfig } from '../types/ChartConfig'
|
|
2
2
|
import _ from 'lodash'
|
|
3
|
-
import ConfigContext from '../ConfigContext'
|
|
4
|
-
import { useContext } from 'react'
|
|
5
3
|
|
|
6
|
-
type
|
|
4
|
+
type GetMinMaxProps = {
|
|
7
5
|
/** config - standard chart config */
|
|
8
6
|
config: ChartConfig
|
|
9
7
|
/** minValue - starting minimum value */
|
|
@@ -18,9 +16,20 @@ type UseMinMaxProps = {
|
|
|
18
16
|
tableData: Object[]
|
|
19
17
|
/** isAllLine: if all series are line type including dashed lines */
|
|
20
18
|
isAllLine: boolean
|
|
19
|
+
/** convertLineToBarGraph - whether line charts should be rendered as bar graphs */
|
|
20
|
+
convertLineToBarGraph?: boolean
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const getMinMax = ({
|
|
24
|
+
config,
|
|
25
|
+
minValue,
|
|
26
|
+
maxValue,
|
|
27
|
+
existPositiveValue,
|
|
28
|
+
data,
|
|
29
|
+
isAllLine,
|
|
30
|
+
tableData,
|
|
31
|
+
convertLineToBarGraph
|
|
32
|
+
}: GetMinMaxProps) => {
|
|
24
33
|
let min = 0
|
|
25
34
|
let max = 0
|
|
26
35
|
|
|
@@ -28,8 +37,6 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
28
37
|
let leftMax = 0
|
|
29
38
|
let rightMax = 0
|
|
30
39
|
|
|
31
|
-
const { convertLineToBarGraph } = useContext(ConfigContext)
|
|
32
|
-
|
|
33
40
|
if (!data) {
|
|
34
41
|
return { min, max }
|
|
35
42
|
}
|
|
@@ -238,4 +245,4 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
238
245
|
|
|
239
246
|
return { min, max, leftMax, rightMax }
|
|
240
247
|
}
|
|
241
|
-
export default
|
|
248
|
+
export default getMinMax
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calculates the Y-axis auto padding to prevent data labels from overlapping with axis tick labels.
|
|
5
|
+
* This is used when inline labels are enabled and there's potential for overlap.
|
|
6
|
+
*
|
|
7
|
+
* @param yScale - The D3 scale object for the Y-axis (must have .ticks() method)
|
|
8
|
+
* @param handleNumTicks - The number of ticks to display on the axis
|
|
9
|
+
* @param maxValue - The maximum data value (from useReduceData)
|
|
10
|
+
* @param minValue - The minimum data value (from useReduceData)
|
|
11
|
+
* @param config - The chart configuration object
|
|
12
|
+
* @returns The calculated auto padding percentage (0-100+), or 0 if no padding needed
|
|
13
|
+
*/
|
|
14
|
+
export const getYAxisAutoPadding = (
|
|
15
|
+
yScale: any,
|
|
16
|
+
handleNumTicks: number,
|
|
17
|
+
maxValue: number,
|
|
18
|
+
minValue: number,
|
|
19
|
+
config: ChartConfig
|
|
20
|
+
): number => {
|
|
21
|
+
// Early returns for cases where auto padding is not needed
|
|
22
|
+
if (!yScale?.ticks || config.orientation === 'horizontal' || config.yAxis?.max) {
|
|
23
|
+
return 0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ticks = yScale.ticks(handleNumTicks)
|
|
27
|
+
|
|
28
|
+
if (!Array.isArray(ticks) || ticks.length === 0) {
|
|
29
|
+
return 0
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// minimum percentage of the max value that the distance should be from the top grid line
|
|
33
|
+
const MINIMUM_DISTANCE_PERCENTAGE = 0.025
|
|
34
|
+
|
|
35
|
+
const topGridLine = Math.max(...ticks)
|
|
36
|
+
const needsPaddingThreshold = topGridLine - maxValue * MINIMUM_DISTANCE_PERCENTAGE
|
|
37
|
+
const maxValueIsGreaterThanThreshold = maxValue > needsPaddingThreshold
|
|
38
|
+
|
|
39
|
+
if (!maxValueIsGreaterThanThreshold) return 0
|
|
40
|
+
|
|
41
|
+
const tickGap = ticks.length === 1 ? ticks[0] : ticks[1] - ticks[0]
|
|
42
|
+
const nextTick = Math.max(...ticks) + tickGap
|
|
43
|
+
const divideBy = minValue < 0 ? maxValue / 2 : maxValue
|
|
44
|
+
const calculatedPadding = (nextTick - maxValue) / divideBy
|
|
45
|
+
|
|
46
|
+
// if auto padding is too close to next tick, add one more ticks worth of padding
|
|
47
|
+
const newPadding =
|
|
48
|
+
calculatedPadding > MINIMUM_DISTANCE_PERCENTAGE ? calculatedPadding : calculatedPadding + tickGap / divideBy
|
|
49
|
+
|
|
50
|
+
/* sometimes even though the padding is getting to the next tick exactly,
|
|
51
|
+
d3 still doesn't show the tick. we add 0.1 to ensure to tip it over the edge */
|
|
52
|
+
return newPadding * 100 + 0.1
|
|
53
|
+
}
|