@cdc/chart 4.25.10 → 4.26.1
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 +44003 -43518
- package/examples/feature/__data__/planet-example-data.json +1 -1
- package/examples/feature/boxplot/valid-boxplot.csv +38 -17
- package/examples/feature/pie/planet-pie-example-config.json +48 -2
- package/examples/private/DEV-11825.json +573 -0
- package/examples/private/DEV-12100.json +1303 -0
- package/examples/private/cat-y.json +1235 -0
- package/examples/private/data-points.json +228 -0
- package/examples/private/height.json +3915 -0
- package/examples/private/links.json +569 -0
- package/examples/private/na.json +913 -0
- package/examples/private/quadrant.txt +30 -0
- package/examples/private/test-data.csv +28 -0
- package/examples/private/test-forecast.json +5510 -0
- package/examples/private/warming-stripe-test.json +2578 -0
- package/examples/private/warming-stripes.json +4763 -0
- package/examples/tech-adoption-with-links.json +560 -0
- package/index.html +16 -140
- package/package.json +6 -5
- package/preview.html +1616 -0
- package/src/CdcChart.tsx +8 -11
- package/src/CdcChartComponent.tsx +329 -124
- 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.Regions.Categorical.stories.tsx +148 -0
- package/src/_stories/Chart.Regions.DateScale.stories.tsx +197 -0
- package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +297 -0
- package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
- package/src/_stories/Chart.stories.tsx +8 -0
- package/src/_stories/ChartAnnotation.stories.tsx +6 -3
- package/src/_stories/ChartBar.Editor.stories.tsx +3585 -0
- package/src/_stories/ChartBrush.Editor.stories.tsx +295 -0
- package/src/_stories/ChartBrush.stories.tsx +50 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +656 -0
- package/src/_stories/ChartEditor.stories.tsx +1 -2
- package/src/_stories/TechAdoptionWithLinks.stories.tsx +27 -0
- package/src/_stories/_mock/brush_enabled.json +326 -0
- package/src/_stories/_mock/brush_mock.json +2 -69
- 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/horizontal-bars-dynamic-y-axis.json +413 -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 -7
- package/src/components/AreaChart/index.tsx +1 -2
- package/src/components/Axis/Categorical.Axis.tsx +6 -7
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +181 -27
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +3 -1
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -0
- package/src/components/BarChart/components/BarChart.Vertical.tsx +8 -9
- package/src/components/BarChart/components/context.tsx +1 -0
- package/src/components/BarChart/helpers/useBarChart.ts +14 -2
- package/src/components/BoxPlot/helpers/index.ts +3 -3
- package/src/components/Brush/BrushSelector.tsx +1258 -0
- package/src/components/Brush/MiniChartPreview.tsx +283 -0
- package/src/components/DeviationBar.jsx +9 -7
- package/src/components/EditorPanel/EditorPanel.tsx +2720 -2586
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +56 -34
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +76 -31
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +104 -55
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +54 -49
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +427 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +96 -48
- 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 +36 -31
- package/src/components/Forecasting/Forecasting.tsx +139 -21
- package/src/components/Legend/Legend.Component.tsx +16 -9
- package/src/components/Legend/Legend.tsx +3 -2
- package/src/components/Legend/helpers/createFormatLabels.tsx +325 -176
- package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
- package/src/components/Legend/helpers/index.ts +10 -6
- 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 +559 -499
- package/src/components/PairedBarChart.jsx +20 -3
- package/src/components/Regions/components/Regions.tsx +366 -144
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/components/ScatterPlot/ScatterPlot.jsx +2 -2
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +202 -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/components/WarmingStripes/WarmingStripes.tsx +160 -0
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +35 -0
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +104 -0
- package/src/components/WarmingStripes/index.tsx +3 -0
- package/src/data/initial-state.js +16 -2
- package/src/helpers/buildForecastPaletteOptions.ts +0 -38
- package/src/helpers/calculateHorizontalBarCategoryLabelWidth.ts +57 -0
- package/src/helpers/getColorScale.ts +10 -0
- package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +26 -14
- package/src/helpers/getYAxisAutoPadding.ts +53 -0
- package/src/helpers/sizeHelpers.ts +0 -20
- package/src/helpers/smallMultiplesHelpers.ts +529 -0
- package/src/hooks/useChartHoverAnalytics.tsx +10 -9
- package/src/hooks/useProgrammaticTooltip.ts +96 -0
- package/src/hooks/useScales.ts +98 -34
- package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
- package/src/hooks/useTooltip.tsx +91 -25
- package/src/scss/DataTable.scss +0 -4
- package/src/scss/main.scss +18 -83
- package/src/store/chart.actions.ts +2 -0
- package/src/store/chart.reducer.ts +4 -0
- package/src/test/CdcChart.test.jsx +1 -1
- package/src/types/ChartConfig.ts +27 -6
- package/src/types/ChartContext.ts +3 -0
- package/src/types/Label.ts +1 -0
- package/src/utils/analyticsTracking.ts +19 -0
- package/LICENSE +0 -201
- package/src/_stories/_mock/pie_data.json +0 -218
- package/src/components/AreaChart/components/AreaChart.jsx +0 -109
- package/src/components/Brush/BrushChart.tsx +0 -128
- package/src/components/Brush/BrushController.tsx +0 -71
- package/src/components/Brush/types.tsx +0 -8
- package/src/components/BrushChart.tsx +0 -223
- package/src/helpers/sort.ts +0 -7
- package/src/hooks/useActiveElement.js +0 -19
- package/src/hooks/useChartClasses.js +0 -41
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.warming-stripes-gradient-legend {
|
|
2
|
+
margin: 1rem 0;
|
|
3
|
+
width: 100%;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.warming-stripes-gradient-legend__title {
|
|
7
|
+
font-weight: 600;
|
|
8
|
+
margin-bottom: 0.5rem;
|
|
9
|
+
font-size: 16px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.warming-stripes-gradient-legend__description {
|
|
13
|
+
margin-top: 0.5rem;
|
|
14
|
+
margin-bottom: 1rem;
|
|
15
|
+
font-size: 14px;
|
|
16
|
+
color: #555;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.warming-stripes-gradient-legend__container {
|
|
20
|
+
width: 100%;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.warming-stripes-gradient-legend__svg {
|
|
24
|
+
display: block;
|
|
25
|
+
width: 100%;
|
|
26
|
+
overflow: visible;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.warming-stripes-gradient-legend__series-label {
|
|
30
|
+
text-align: center;
|
|
31
|
+
margin-top: 0.5rem;
|
|
32
|
+
font-size: 14px;
|
|
33
|
+
font-weight: 500;
|
|
34
|
+
color: #333;
|
|
35
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useContext } from 'react'
|
|
2
|
+
import ConfigContext from '../../ConfigContext'
|
|
3
|
+
import { filterChartColorPalettes } from '@cdc/core/helpers/filterColorPalettes'
|
|
4
|
+
import { getFallbackColorPalette, migratePaletteWithMap } from '@cdc/core/helpers/palettes/utils'
|
|
5
|
+
import { paletteMigrationMap } from '@cdc/core/helpers/palettes/migratePaletteName'
|
|
6
|
+
import './WarmingStripesGradientLegend.css'
|
|
7
|
+
|
|
8
|
+
const WarmingStripesGradientLegend = () => {
|
|
9
|
+
const { transformedData: data, config, formatNumber } = useContext(ConfigContext)
|
|
10
|
+
|
|
11
|
+
const valueKey = config.runtime.seriesKeys?.[0]
|
|
12
|
+
|
|
13
|
+
if (!valueKey || !data || data.length === 0 || config.legend?.hide) {
|
|
14
|
+
return null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Calculate min and max values
|
|
18
|
+
const values = data.map(d => Number(d[valueKey])).filter(v => !isNaN(v))
|
|
19
|
+
const minValue = Math.min(...values)
|
|
20
|
+
const maxValue = Math.max(...values)
|
|
21
|
+
|
|
22
|
+
// Get the color palette from config (same logic as WarmingStripes component)
|
|
23
|
+
const colorPalettes = filterChartColorPalettes(config)
|
|
24
|
+
const configPalette = config.general?.palette?.name
|
|
25
|
+
const migratedPaletteName = configPalette ? configPalette : getFallbackColorPalette(config)
|
|
26
|
+
|
|
27
|
+
const isReversedPalette = migratedPaletteName?.endsWith('reverse')
|
|
28
|
+
const basePaletteName = isReversedPalette ? migratedPaletteName.slice(0, -7) : migratedPaletteName
|
|
29
|
+
|
|
30
|
+
let palette =
|
|
31
|
+
colorPalettes[migratePaletteWithMap(basePaletteName, paletteMigrationMap, false)] ||
|
|
32
|
+
colorPalettes[basePaletteName] ||
|
|
33
|
+
colorPalettes[configPalette]
|
|
34
|
+
|
|
35
|
+
if (!palette || palette.length < 2) {
|
|
36
|
+
palette = [
|
|
37
|
+
'#053061',
|
|
38
|
+
'#2166ac',
|
|
39
|
+
'#4393c3',
|
|
40
|
+
'#92c5de',
|
|
41
|
+
'#d1e5f0',
|
|
42
|
+
'#f7f7f7',
|
|
43
|
+
'#fddbc7',
|
|
44
|
+
'#f4a582',
|
|
45
|
+
'#d6604d',
|
|
46
|
+
'#b2182b',
|
|
47
|
+
'#67001f'
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const shouldReverse = config.general?.palette?.isReversed || isReversedPalette
|
|
52
|
+
const finalPalette = shouldReverse ? [...palette].reverse() : palette
|
|
53
|
+
|
|
54
|
+
// Create gradient stops for SVG
|
|
55
|
+
const gradientStops = finalPalette.map((color, index) => {
|
|
56
|
+
const offset = (index / (finalPalette.length - 1)) * 100
|
|
57
|
+
return { offset: `${offset}%`, color }
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const seriesLabel = config.runtime.seriesLabels?.[valueKey] || valueKey
|
|
61
|
+
const uniqueId = `warming-stripes-gradient-${config.runtime.uniqueId}`
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className='warming-stripes-gradient-legend'>
|
|
65
|
+
{config.legend?.label && <h3 className='warming-stripes-gradient-legend__title'>{config.legend.label}</h3>}
|
|
66
|
+
{config.legend?.description && (
|
|
67
|
+
<p className='warming-stripes-gradient-legend__description'>{config.legend.description}</p>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
<div className='warming-stripes-gradient-legend__container'>
|
|
71
|
+
<svg className='warming-stripes-gradient-legend__svg' height='50' width='100%'>
|
|
72
|
+
<defs>
|
|
73
|
+
<linearGradient id={uniqueId} x1='0%' y1='0%' x2='100%' y2='0%'>
|
|
74
|
+
{gradientStops.map((stop, index) => (
|
|
75
|
+
<stop key={index} offset={stop.offset} stopColor={stop.color} />
|
|
76
|
+
))}
|
|
77
|
+
</linearGradient>
|
|
78
|
+
</defs>
|
|
79
|
+
|
|
80
|
+
{/* Border */}
|
|
81
|
+
<rect x='0' y='0' width='100%' height='25' fill='#d3d3d3' />
|
|
82
|
+
|
|
83
|
+
{/* Gradient bar */}
|
|
84
|
+
<rect x='1' y='1' width='calc(100% - 2px)' height='23' fill={`url(#${uniqueId})`} />
|
|
85
|
+
|
|
86
|
+
{/* Min label */}
|
|
87
|
+
<text x='0' y='40' fontSize='14' textAnchor='start' fill='#333'>
|
|
88
|
+
{formatNumber(minValue, 'left')}
|
|
89
|
+
</text>
|
|
90
|
+
|
|
91
|
+
{/* Max label */}
|
|
92
|
+
<text x='100%' y='40' fontSize='14' textAnchor='end' fill='#333'>
|
|
93
|
+
{formatNumber(maxValue, 'left')}
|
|
94
|
+
</text>
|
|
95
|
+
</svg>
|
|
96
|
+
|
|
97
|
+
{/* Series name centered below gradient */}
|
|
98
|
+
<div className='warming-stripes-gradient-legend__series-label'>{seriesLabel}</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export default WarmingStripesGradientLegend
|
|
@@ -23,6 +23,7 @@ const createInitialState = () => {
|
|
|
23
23
|
noData: 'No Data Available'
|
|
24
24
|
},
|
|
25
25
|
title: '',
|
|
26
|
+
titleStyle: 'small',
|
|
26
27
|
showTitle: true,
|
|
27
28
|
showDownloadMediaButton: false,
|
|
28
29
|
theme: 'theme-blue',
|
|
@@ -139,7 +140,8 @@ const createInitialState = () => {
|
|
|
139
140
|
padding: 5,
|
|
140
141
|
showYearsOnce: false,
|
|
141
142
|
sortByRecentDate: false,
|
|
142
|
-
brushActive: false
|
|
143
|
+
brushActive: false,
|
|
144
|
+
brushDefaultRecentDateCount: undefined
|
|
143
145
|
},
|
|
144
146
|
table: {
|
|
145
147
|
label: 'Data Table',
|
|
@@ -198,7 +200,19 @@ const createInitialState = () => {
|
|
|
198
200
|
patterns: {},
|
|
199
201
|
patternField: ''
|
|
200
202
|
},
|
|
201
|
-
|
|
203
|
+
smallMultiples: {
|
|
204
|
+
mode: '',
|
|
205
|
+
tileColumn: '',
|
|
206
|
+
tilesPerRowDesktop: 3,
|
|
207
|
+
tilesPerRowMobile: 1,
|
|
208
|
+
tileOrder: [],
|
|
209
|
+
tileOrderType: 'asc',
|
|
210
|
+
tileTitles: {},
|
|
211
|
+
independentYAxis: false,
|
|
212
|
+
colorMode: 'same',
|
|
213
|
+
synchronizedTooltips: true,
|
|
214
|
+
showAreaUnderLine: true
|
|
215
|
+
},
|
|
202
216
|
exclusions: {
|
|
203
217
|
active: false,
|
|
204
218
|
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
|
-
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
2
|
+
|
|
3
|
+
interface CalculateHorizontalBarCategoryLabelWidthProps {
|
|
4
|
+
yScale: any
|
|
5
|
+
chartWidth: number
|
|
6
|
+
formatDate: Function
|
|
7
|
+
parseDate: Function
|
|
8
|
+
tickLabelFont: string
|
|
9
|
+
xAxisType?: string
|
|
10
|
+
labelPlacement?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Helper function to calculate category label space for horizontal bar charts
|
|
15
|
+
*
|
|
16
|
+
* @param props Configuration object with chart properties
|
|
17
|
+
* @returns Calculated category label space, capped at 30% of parent width
|
|
18
|
+
*/
|
|
19
|
+
export const calculateHorizontalBarCategoryLabelWidth = ({
|
|
20
|
+
yScale,
|
|
21
|
+
chartWidth,
|
|
22
|
+
formatDate,
|
|
23
|
+
parseDate,
|
|
24
|
+
tickLabelFont,
|
|
25
|
+
xAxisType,
|
|
26
|
+
labelPlacement
|
|
27
|
+
}: CalculateHorizontalBarCategoryLabelWidthProps): number => {
|
|
28
|
+
if (labelPlacement !== 'On Date/Category Axis') return 0
|
|
29
|
+
|
|
30
|
+
const categoryValues = yScale.domain()
|
|
31
|
+
|
|
32
|
+
if (!categoryValues || categoryValues.length === 0) {
|
|
33
|
+
return chartWidth * 0.3
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const formattedLabels = categoryValues.map(value => {
|
|
37
|
+
if (xAxisType === 'date') {
|
|
38
|
+
try {
|
|
39
|
+
return formatDate(parseDate(value))
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return String(value)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return String(value)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const labelWidths = formattedLabels.map(label => getTextWidth(label, tickLabelFont))
|
|
48
|
+
const maxLabelWidth = Math.max(...labelWidths)
|
|
49
|
+
|
|
50
|
+
// We need some extra padding or visx will wrap labels too early
|
|
51
|
+
const paddedWidth = maxLabelWidth + Math.ceil(maxLabelWidth * 0.15)
|
|
52
|
+
|
|
53
|
+
// Allocate at most 30% of chart width to category labels
|
|
54
|
+
const maxAllowedWidth = chartWidth * 0.3
|
|
55
|
+
|
|
56
|
+
return Math.min(paddedWidth, maxAllowedWidth)
|
|
57
|
+
}
|
|
@@ -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
|
}
|
|
@@ -48,10 +55,15 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
48
55
|
max = enteredMaxValue && isMaxValid ? Number(enteredMaxValue) : Number.MIN_VALUE
|
|
49
56
|
const { lower, upper } = config?.confidenceKeys || {}
|
|
50
57
|
|
|
58
|
+
// When brush is active, use tableData (full dataset) for min/max calculations
|
|
59
|
+
// so the y-axis shows the full range, but still use filtered data for rendering
|
|
60
|
+
const dataForMinMax = config.xAxis.brushActive && tableData && tableData.length > 0 ? tableData : data
|
|
61
|
+
|
|
51
62
|
if (lower && upper && config.visualizationType === 'Bar') {
|
|
52
63
|
const buffer = min < 0 ? 1.1 : 0
|
|
53
|
-
const maxValueWithCI = Math.max(...
|
|
54
|
-
const minValueWithCIPlusBuffer =
|
|
64
|
+
const maxValueWithCI = Math.max(...dataForMinMax.flatMap(d => [d[upper], d[lower]])) * paddingAddedToAxis
|
|
65
|
+
const minValueWithCIPlusBuffer =
|
|
66
|
+
Math.min(...dataForMinMax.flatMap(d => [d[upper], d[lower]])) * paddingAddedToAxis * buffer
|
|
55
67
|
max = max > maxValueWithCI ? max : maxValueWithCI
|
|
56
68
|
min = min < minValueWithCIPlusBuffer ? min : minValueWithCIPlusBuffer
|
|
57
69
|
}
|
|
@@ -72,7 +84,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
72
84
|
})
|
|
73
85
|
|
|
74
86
|
// Using the columnNames or "keys" get the returned result
|
|
75
|
-
const result =
|
|
87
|
+
const result = dataForMinMax.map(obj => columnNames.map(key => obj[key]))
|
|
76
88
|
|
|
77
89
|
const highCIGroup = Math.max.apply(
|
|
78
90
|
null,
|
|
@@ -95,7 +107,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
95
107
|
|
|
96
108
|
if (visualizationType === 'Combo') {
|
|
97
109
|
try {
|
|
98
|
-
if (!
|
|
110
|
+
if (!dataForMinMax) throw new Error('COVE: missing data while getting min/max for combo chart.')
|
|
99
111
|
// seperate the left and right axis items & get each sides series keys
|
|
100
112
|
let leftAxisSeriesItems = series.filter(s => s.axis === 'Left')
|
|
101
113
|
let rightAxisSeriesItems = series.filter(s => s.axis === 'Right')
|
|
@@ -121,8 +133,8 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
121
133
|
})
|
|
122
134
|
return max
|
|
123
135
|
}
|
|
124
|
-
leftMax = findMaxFromSeriesKeys(
|
|
125
|
-
rightMax = findMaxFromSeriesKeys(
|
|
136
|
+
leftMax = findMaxFromSeriesKeys(dataForMinMax, leftAxisSeriesItems, leftMax, 'left')
|
|
137
|
+
rightMax = findMaxFromSeriesKeys(dataForMinMax, rightAxisSeriesItems, rightMax, 'right')
|
|
126
138
|
|
|
127
139
|
if (leftMax < Number(enteredMaxValue)) {
|
|
128
140
|
leftMax = Number(enteredMaxValue)
|
|
@@ -202,7 +214,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
202
214
|
}
|
|
203
215
|
|
|
204
216
|
if (config.isLollipopChart && config.yAxis.displayNumbersOnBar) {
|
|
205
|
-
const dataKey =
|
|
217
|
+
const dataKey = dataForMinMax.map(item => item[config.series[0].dataKey])
|
|
206
218
|
const maxDataVal = Math.max(...dataKey).toString().length
|
|
207
219
|
|
|
208
220
|
switch (true) {
|
|
@@ -238,4 +250,4 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
238
250
|
|
|
239
251
|
return { min, max, leftMax, rightMax }
|
|
240
252
|
}
|
|
241
|
-
export default
|
|
253
|
+
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
|
+
}
|
|
@@ -26,23 +26,3 @@ export function calcInitialHeight(
|
|
|
26
26
|
const height = Number(heights?.[renderedOrientation])
|
|
27
27
|
return isNaN(height) ? 0 : height
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
export function handleAutoPaddingRight(parentRef, xAxisLabelRefs, parentWidth): [boolean, number] {
|
|
31
|
-
const parentX = parentRef.current.getBoundingClientRect().x
|
|
32
|
-
const editorIsOpen = !!document.querySelector('.editor-panel:not(.hidden)')
|
|
33
|
-
const lastTickRect = xAxisLabelRefs.current?.[xAxisLabelRefs.current.length - 1]?.getBoundingClientRect()
|
|
34
|
-
const lastBottomTickEnd = lastTickRect ? lastTickRect.x + lastTickRect.width : 0
|
|
35
|
-
const editorWidth = editorIsOpen ? EDITOR_WIDTH : 0
|
|
36
|
-
const calculatedOverhang = lastBottomTickEnd - parentX - editorWidth - parentWidth
|
|
37
|
-
|
|
38
|
-
const paddingToAdd = clamp(calculatedOverhang, 0, 20)
|
|
39
|
-
const currentPadding = Number(parentRef.current.style.paddingRight.replace('px', ''))
|
|
40
|
-
const paddingDiff = Math.abs(currentPadding - paddingToAdd)
|
|
41
|
-
const DIFF_THRESHOLD = 5
|
|
42
|
-
|
|
43
|
-
const noChange = currentPadding === calculatedOverhang
|
|
44
|
-
const insufficientDiff = (paddingDiff < DIFF_THRESHOLD && calculatedOverhang > 0) || Math.abs(calculatedOverhang) < 1
|
|
45
|
-
const updatePadding = !noChange && !insufficientDiff
|
|
46
|
-
|
|
47
|
-
return [updatePadding, paddingToAdd]
|
|
48
|
-
}
|