@cdc/chart 4.26.4 → 4.26.6
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/CONFIG.md +353 -29
- package/LICENSE +201 -0
- package/dist/{cdcchart-DQ00cQCm.es.js → cdcchart-BWVIUcAF.es.js} +5 -55
- package/dist/cdcchart.js +37885 -35117
- package/examples/feature/heatmap/average-age-categorical.json +313 -0
- package/examples/feature/heatmap/calendar-heatmap.json +134 -0
- package/examples/feature/heatmap/cell-values-bottom-axis.json +181 -0
- package/examples/feature/heatmap/sparse-aggregation.json +158 -0
- package/examples/private/bad-y-axis.json +543 -0
- package/examples/private/bar-chart-negative-value.json +455 -0
- package/examples/private/bump-chart.json +1116 -0
- package/examples/private/test-123.json +624 -0
- package/package.json +4 -3
- package/src/CdcChart.tsx +5 -4
- package/src/CdcChartComponent.tsx +166 -62
- package/src/_stories/Chart.AltText.stories.tsx +136 -0
- package/src/_stories/Chart.HeatMap.stories.tsx +187 -0
- package/src/_stories/Chart.Sankey.smoke.stories.tsx +62 -0
- package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +183 -0
- package/src/_stories/Chart.SmallMultiples.smoke.stories.tsx +29 -0
- package/src/_stories/Chart.YAxisAutoMaxStrategy.stories.tsx +52 -0
- package/src/_stories/Chart.YAxisFilterDomain.stories.tsx +90 -0
- package/src/_stories/ChartAnnotation.smoke.stories.tsx +10 -0
- package/src/_stories/ChartAxisTitles.smoke.stories.tsx +13 -0
- package/src/_stories/ChartBar.Editor.stories.tsx +63 -0
- package/src/_stories/ChartBrush.Interaction.stories.tsx +112 -0
- package/src/_stories/ChartBrush.smoke.stories.tsx +13 -4
- package/src/_stories/_mock/alt_text_metadata.json +38 -0
- package/src/_stories/_mock/annotation_event-line_mock.json +1064 -0
- package/src/_stories/_mock/heatmap-average-age-categorical.json +315 -0
- package/src/_stories/_mock/heatmap.json +156 -0
- package/src/_stories/_mock/reported_pertussis_cases.json +1643 -0
- package/src/_stories/_mock/sankey_mock.json +78 -0
- package/src/_stories/_mock/small_multiples/small_multiples_lines.json +1 -1
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +22 -1
- package/src/components/Annotations/components/AnnotationDraggable.tsx +220 -120
- package/src/components/Annotations/components/AnnotationDropdown.tsx +5 -2
- package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +198 -0
- package/src/components/Axis/LeftAxis.tsx +17 -11
- package/src/components/Axis/LeftAxisGridlines.tsx +22 -12
- package/src/components/Axis/RightAxis.tsx +3 -0
- package/src/components/Axis/tests/AxisTickValues.test.tsx +86 -0
- package/src/components/Brush/BrushSelector.tsx +88 -187
- package/src/components/Brush/brushInteraction.ts +22 -0
- package/src/components/Brush/tests/brushInteraction.test.ts +38 -0
- package/src/components/EditorPanel/EditorPanel.tsx +677 -185
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +114 -52
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +105 -13
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +1 -0
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +5 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +2 -2
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.test.tsx +98 -0
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +10 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.test.tsx +484 -0
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +4 -1
- package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +262 -0
- package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +32 -0
- package/src/components/EditorPanel/components/panels.scss +10 -0
- package/src/components/EditorPanel/editor-panel.scss +47 -0
- package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +61 -0
- package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +74 -0
- package/src/components/EditorPanel/useEditorPermissions.test.tsx +315 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +77 -21
- package/src/components/HeatMap/components/HeatMap.tsx +767 -0
- package/src/components/HeatMap/components/HeatMapGradientLegend.tsx +128 -0
- package/src/components/HeatMap/heatmap.constants.ts +13 -0
- package/src/components/HeatMap/heatmap.css +85 -0
- package/src/components/HeatMap/helpers/index.ts +261 -0
- package/src/components/HeatMap/index.tsx +5 -0
- package/src/components/HeatMap/tests/HeatMap.test.tsx +1072 -0
- package/src/components/HeatMap/tests/helpers.test.ts +167 -0
- package/src/components/Legend/Legend.Component.tsx +2 -2
- package/src/components/Legend/helpers/createFormatLabels.test.ts +88 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +6 -1
- package/src/components/LineChart/components/LineChart.BumpCircle.tsx +19 -22
- package/src/components/LineChart/helpers.test.ts +574 -0
- package/src/components/LineChart/helpers.ts +39 -16
- package/src/components/LineChart/index.tsx +28 -5
- package/src/components/LinearChart/tests/LinearChart.test.tsx +137 -33
- package/src/components/LinearChart/tests/mockConfigContext.ts +1 -0
- package/src/components/LinearChart.tsx +103 -47
- package/src/components/PieChart/PieChart.tsx +4 -1
- package/src/components/RadarChart/RadarChart.tsx +3 -1
- package/src/components/Regions/components/Regions.tsx +195 -41
- package/src/components/Sankey/components/Sankey.tsx +7 -3
- package/src/components/Sankey/helpers/getSankeyTooltip.tsx +7 -6
- package/src/components/ScatterPlot/ScatterPlot.jsx +3 -3
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +33 -3
- package/src/components/SmallMultiples/SmallMultiples.css +12 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +50 -15
- package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +152 -0
- package/src/components/SmallMultiples/tests/SmallMultiples.test.tsx +170 -0
- package/src/components/Sparkline/components/SparkLine.tsx +4 -9
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +1 -1
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +2 -2
- package/src/data/initial-state.js +12 -0
- package/src/helpers/axisLabelFontSize.ts +8 -0
- package/src/helpers/bumpChartTooltip.ts +54 -0
- package/src/helpers/calculateLeftYAxisWidth.ts +13 -3
- package/src/helpers/colorAssignmentHelpers.ts +102 -0
- package/src/helpers/dataHelpers.ts +11 -1
- package/src/helpers/ensureSpecialChartAxisTypes.ts +11 -0
- package/src/helpers/getAxisMaxOverride.ts +16 -0
- package/src/helpers/getCleanTopTickMax.ts +64 -0
- package/src/helpers/getColorScale.ts +18 -9
- package/src/helpers/getHasBoundarySuppression.ts +26 -0
- package/src/helpers/getMinMax.ts +26 -25
- package/src/helpers/getYAxisDomainData.ts +38 -0
- package/src/helpers/getYAxisFinalizationEligibility.ts +40 -0
- package/src/helpers/handleChartAriaLabels.ts +23 -10
- package/src/helpers/hasSpacedInlineLabel.ts +10 -0
- package/src/helpers/seriesColumnSettings.ts +5 -4
- package/src/helpers/seriesHighlight.ts +26 -0
- package/src/helpers/smallMultiplesHelpers.ts +5 -0
- package/src/helpers/tests/axisLabelFontSize.test.ts +15 -0
- package/src/helpers/tests/bumpChartTooltip.test.ts +75 -0
- package/src/helpers/tests/calculateLeftYAxisWidth.test.ts +38 -0
- package/src/helpers/tests/dataHelpers.test.ts +135 -0
- package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +30 -0
- package/src/helpers/tests/getAxisMaxOverride.test.ts +36 -0
- package/src/helpers/tests/getCleanTopTickMax.test.ts +165 -0
- package/src/helpers/tests/getColorScale.test.ts +197 -0
- package/src/helpers/tests/getMinMax.test.ts +211 -0
- package/src/helpers/tests/getYAxisDomainData.test.ts +119 -0
- package/src/helpers/tests/getYAxisFinalizationEligibility.test.ts +185 -0
- package/src/helpers/tests/handleChartAriaLabels.test.ts +71 -0
- package/src/helpers/tests/hasSpacedInlineLabel.test.ts +75 -0
- package/src/helpers/tests/seriesColumnSettings.test.ts +16 -2
- package/src/helpers/tests/seriesHighlight.test.ts +61 -0
- package/src/helpers/tests/smallMultiplesHelpers.test.ts +99 -0
- package/src/hooks/tests/useRightAxis.test.tsx +127 -0
- package/src/hooks/tests/useScales.test.tsx +383 -0
- package/src/hooks/useRightAxis.ts +30 -13
- package/src/hooks/useScales.ts +48 -21
- package/src/hooks/useTooltip.tsx +3 -1
- package/src/index.jsx +5 -2
- package/src/scss/main.scss +32 -49
- package/src/test/CdcChart.dataTable.test.tsx +122 -0
- package/src/types/ChartConfig.ts +24 -1
- package/src/types/ChartContext.ts +5 -0
- package/src/helpers/getYAxisAutoPadding.ts +0 -53
|
@@ -11,6 +11,7 @@ type CalculateLeftYAxisWidthProps = {
|
|
|
11
11
|
data: any[]
|
|
12
12
|
yScale: any
|
|
13
13
|
numTicks: number
|
|
14
|
+
tickValues?: number[]
|
|
14
15
|
parentWidth: number
|
|
15
16
|
tickLabelFont: string
|
|
16
17
|
axisLabelFontSize: number
|
|
@@ -28,7 +29,11 @@ type TickValueProps = Omit<
|
|
|
28
29
|
'parentWidth' | 'tickLabelFont' | 'axisLabelFontSize' | 'handleLeftTickFormatting'
|
|
29
30
|
>
|
|
30
31
|
|
|
31
|
-
const getTickValues = ({ config, data, yScale, numTicks }: TickValueProps) => {
|
|
32
|
+
const getTickValues = ({ config, data, yScale, numTicks, tickValues }: TickValueProps) => {
|
|
33
|
+
if (tickValues) {
|
|
34
|
+
return tickValues
|
|
35
|
+
}
|
|
36
|
+
|
|
32
37
|
if (config.visualizationType === 'Forest Plot') {
|
|
33
38
|
return data.map((_, index) => index)
|
|
34
39
|
}
|
|
@@ -45,6 +50,10 @@ const getTickValues = ({ config, data, yScale, numTicks }: TickValueProps) => {
|
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
const getAxisLabelSpace = (config: any, axisLabelFontSize: number) => {
|
|
53
|
+
if (config.yAxis?.titlePlacement === 'top') {
|
|
54
|
+
return 0
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
return !config.hideYAxisLabel && config.runtime.yAxis.label ? axisLabelFontSize + AXIS_LABEL_PADDING : 0
|
|
49
58
|
}
|
|
50
59
|
|
|
@@ -138,6 +147,7 @@ export const calculateLeftYAxisWidth = ({
|
|
|
138
147
|
data,
|
|
139
148
|
yScale,
|
|
140
149
|
numTicks,
|
|
150
|
+
tickValues,
|
|
141
151
|
parentWidth,
|
|
142
152
|
tickLabelFont,
|
|
143
153
|
axisLabelFontSize,
|
|
@@ -149,10 +159,10 @@ export const calculateLeftYAxisWidth = ({
|
|
|
149
159
|
return getHorizonChartWidth({ config, parentWidth, tickLabelFont, axisLabelFontSize })
|
|
150
160
|
}
|
|
151
161
|
|
|
152
|
-
const
|
|
162
|
+
const measuredTickValues = getTickValues({ config, data, yScale, numTicks, tickValues })
|
|
153
163
|
const { hideAxis, inlineLabel, labelsAboveGridlines } = config.yAxis
|
|
154
164
|
const formattedTicks = formatMeasuredTickLabels({
|
|
155
|
-
tickValues,
|
|
165
|
+
tickValues: measuredTickValues,
|
|
156
166
|
handleLeftTickFormatting,
|
|
157
167
|
inlineLabel,
|
|
158
168
|
labelsAboveGridlines
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { type ChartConfig, type VisualizationType } from '../types/ChartConfig'
|
|
2
|
+
|
|
3
|
+
export type SeriesColorAssignment = {
|
|
4
|
+
key: string
|
|
5
|
+
color: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type SeriesColorAssignmentItem = {
|
|
9
|
+
key: string
|
|
10
|
+
label: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const SERIES_COLOR_ASSIGNMENT_TYPES: VisualizationType[] = ['Line', 'Bar', 'Combo', 'Area Chart', 'Scatter Plot']
|
|
14
|
+
|
|
15
|
+
export const hasDynamicCategorySeries = (config: ChartConfig): boolean =>
|
|
16
|
+
Array.isArray(config.series) && config.series.some(series => Boolean(series?.dynamicCategory))
|
|
17
|
+
|
|
18
|
+
export const isColorByCategoryBarChart = (config: ChartConfig): boolean =>
|
|
19
|
+
config.visualizationType === 'Bar' &&
|
|
20
|
+
config.visualizationSubType === 'regular' &&
|
|
21
|
+
Boolean(config.legend?.colorCode) &&
|
|
22
|
+
(config.series?.length || 0) === 1
|
|
23
|
+
|
|
24
|
+
export const supportsSeriesColorAssignments = (config: ChartConfig): boolean =>
|
|
25
|
+
SERIES_COLOR_ASSIGNMENT_TYPES.includes(config.visualizationType) &&
|
|
26
|
+
!hasDynamicCategorySeries(config) &&
|
|
27
|
+
!isColorByCategoryBarChart(config)
|
|
28
|
+
|
|
29
|
+
export const isSeriesColorAssignmentModeByValue = (config: ChartConfig): boolean =>
|
|
30
|
+
config.general?.palette?.colorAssignmentMode === 'by-value'
|
|
31
|
+
|
|
32
|
+
export const getSeriesColorAssignments = (config: ChartConfig): SeriesColorAssignment[] =>
|
|
33
|
+
Array.isArray(config.general?.palette?.colorAssignments) ? config.general.palette.colorAssignments : []
|
|
34
|
+
|
|
35
|
+
export const hasSeriesColorAssignmentOverrides = (config: ChartConfig): boolean => {
|
|
36
|
+
if (!supportsSeriesColorAssignments(config) || !isSeriesColorAssignmentModeByValue(config)) return false
|
|
37
|
+
|
|
38
|
+
const currentSeriesKeys = new Set(getSeriesColorAssignmentItems(config).map(({ key }) => key))
|
|
39
|
+
|
|
40
|
+
return getSeriesColorAssignments(config).some(assignment => {
|
|
41
|
+
if (!assignment?.key || !assignment?.color) return false
|
|
42
|
+
|
|
43
|
+
return currentSeriesKeys.has(assignment.key)
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const getSeriesDisplayLabel = (config: ChartConfig, key: string): string => {
|
|
48
|
+
const series = config.series?.find(item => item?.dataKey === key)
|
|
49
|
+
return String(config.runtime?.seriesLabels?.[key] || series?.name || (series as any)?.label || key)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const getSeriesColorAssignmentItems = (config: ChartConfig): SeriesColorAssignmentItem[] => {
|
|
53
|
+
if (!supportsSeriesColorAssignments(config) || !Array.isArray(config.series)) return []
|
|
54
|
+
|
|
55
|
+
return config.series
|
|
56
|
+
.filter(series => series?.dataKey && !series.dynamicCategory)
|
|
57
|
+
.map(series => ({
|
|
58
|
+
key: series.dataKey,
|
|
59
|
+
label: getSeriesDisplayLabel(config, series.dataKey)
|
|
60
|
+
}))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const getSeriesKeyByColorScaleLabel = (config: ChartConfig): Map<string, string> => {
|
|
64
|
+
const labelToKey = new Map<string, string>()
|
|
65
|
+
const runtimeKeys = Array.isArray(config.runtime?.seriesKeys) ? config.runtime.seriesKeys : []
|
|
66
|
+
|
|
67
|
+
runtimeKeys.forEach(key => {
|
|
68
|
+
labelToKey.set(String(key), key)
|
|
69
|
+
labelToKey.set(getSeriesDisplayLabel(config, key), key)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
getSeriesColorAssignmentItems(config).forEach(({ key, label }) => {
|
|
73
|
+
labelToKey.set(String(key), key)
|
|
74
|
+
labelToKey.set(label, key)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
return labelToKey
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const applySeriesColorAssignmentsToRange = (
|
|
81
|
+
config: ChartConfig,
|
|
82
|
+
domain: string[] = [],
|
|
83
|
+
range: string[] = []
|
|
84
|
+
): string[] => {
|
|
85
|
+
if (!supportsSeriesColorAssignments(config) || !isSeriesColorAssignmentModeByValue(config)) return range
|
|
86
|
+
|
|
87
|
+
const assignmentMap = new Map(
|
|
88
|
+
getSeriesColorAssignments(config)
|
|
89
|
+
.filter(assignment => assignment?.key && assignment?.color)
|
|
90
|
+
.map(assignment => [assignment.key, assignment.color])
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if (!assignmentMap.size || !range.length) return range
|
|
94
|
+
|
|
95
|
+
const labelToKey = getSeriesKeyByColorScaleLabel(config)
|
|
96
|
+
|
|
97
|
+
return domain.map((label, index) => {
|
|
98
|
+
const key = labelToKey.get(String(label))
|
|
99
|
+
const assignedColor = key ? assignmentMap.get(key) : undefined
|
|
100
|
+
return assignedColor || range[index % range.length]
|
|
101
|
+
})
|
|
102
|
+
}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { Series } from '@cdc/core/types/Series'
|
|
2
|
+
import isNumber from '@cdc/core/helpers/isNumber'
|
|
2
3
|
import { ChartConfig } from '../types/ChartConfig'
|
|
3
4
|
|
|
5
|
+
// For legend suppression (when `legend.unified` is false), treat a series as having
|
|
6
|
+
// data only if the active (filtered) dataset contains at least one numeric, non-zero
|
|
7
|
+
// value for that series. Placeholder strings (e.g. "Not applicable", "<1.0", ""),
|
|
8
|
+
// nullish values, and zeros are treated as "no visible data" for legend purposes.
|
|
9
|
+
const hasGraphableValue = (raw: unknown): boolean => {
|
|
10
|
+
const sanitized = typeof raw === 'string' ? raw.replace(/[,$]/g, '').trim() : raw
|
|
11
|
+
return isNumber(sanitized) && Number(sanitized) !== 0
|
|
12
|
+
}
|
|
13
|
+
|
|
4
14
|
export const getSeriesWithData = (config: ChartConfig) => {
|
|
5
15
|
const { filters, data, runtime, legend } = config
|
|
6
16
|
const { colorCode } = legend
|
|
@@ -21,7 +31,7 @@ export const getSeriesWithData = (config: ChartConfig) => {
|
|
|
21
31
|
.filter(d => !s.colorCodeSeries || d[colorCode] === s.colorCodeSeries)
|
|
22
32
|
.filter(d => {
|
|
23
33
|
const key = s.dynamicCategory ? s.originalDataKey : s.dataKey
|
|
24
|
-
return d[key]
|
|
34
|
+
return hasGraphableValue(d[key])
|
|
25
35
|
})
|
|
26
36
|
}))
|
|
27
37
|
.filter(s => s.data.length)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ChartConfig } from '../types/ChartConfig'
|
|
2
|
+
|
|
3
|
+
export const ensureSpecialChartAxisTypes = <T extends Pick<ChartConfig, 'visualizationType' | 'xAxis'>>(
|
|
4
|
+
config: T
|
|
5
|
+
): T => {
|
|
6
|
+
if (config.visualizationType === 'Bump Chart' && config.xAxis) {
|
|
7
|
+
config.xAxis.type = 'date-time'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return config
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type AxisMaxOverrideProps = {
|
|
2
|
+
minimumValidMax: number
|
|
3
|
+
value: unknown
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const getAxisMaxOverride = ({ minimumValidMax, value }: AxisMaxOverrideProps) => {
|
|
7
|
+
const maxNumber = Number(value)
|
|
8
|
+
const hasEnteredMax = value !== undefined && value !== null && value !== ''
|
|
9
|
+
const hasValidMax = hasEnteredMax && Number.isFinite(maxNumber) && maxNumber >= minimumValidMax
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
hasEnteredMax,
|
|
13
|
+
hasValidMax,
|
|
14
|
+
maxNumber
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const NICE_MANTISSAS = [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10] as const
|
|
2
|
+
|
|
3
|
+
type FinalizeTopTickMaxProps = {
|
|
4
|
+
currentMax: number
|
|
5
|
+
rawMax: number
|
|
6
|
+
ticks: number[]
|
|
7
|
+
shouldUseCleanTopTick: boolean
|
|
8
|
+
headroomMode?: 'none' | 'inline-label'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const getTickComparisonEpsilon = (tickGap: number) => Math.abs(tickGap) * 1e-10
|
|
12
|
+
|
|
13
|
+
export const getCleanTopTickMax = (value: number) => {
|
|
14
|
+
if (!Number.isFinite(value) || value <= 0) return value
|
|
15
|
+
|
|
16
|
+
const powerOfTenStep = Math.pow(10, Math.floor(Math.log10(value)))
|
|
17
|
+
const normalizedValue = value / powerOfTenStep
|
|
18
|
+
const mantissa = NICE_MANTISSAS.find(candidate => normalizedValue <= candidate)
|
|
19
|
+
|
|
20
|
+
return (mantissa ?? 10) * powerOfTenStep
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const getCleanTopTickMaxCandidates = () => [...NICE_MANTISSAS]
|
|
24
|
+
|
|
25
|
+
export const getFinalTopTickMax = ({
|
|
26
|
+
currentMax,
|
|
27
|
+
rawMax,
|
|
28
|
+
ticks,
|
|
29
|
+
shouldUseCleanTopTick,
|
|
30
|
+
headroomMode = 'none'
|
|
31
|
+
}: FinalizeTopTickMaxProps) => {
|
|
32
|
+
if (!shouldUseCleanTopTick || !Number.isFinite(currentMax)) return { max: currentMax }
|
|
33
|
+
if (!Array.isArray(ticks) || ticks.length === 0) return { max: currentMax }
|
|
34
|
+
|
|
35
|
+
const sortedTicks = Array.from(new Set(ticks.filter(Number.isFinite))).sort((a, b) => a - b)
|
|
36
|
+
if (sortedTicks.length === 0) return { max: currentMax }
|
|
37
|
+
|
|
38
|
+
const tickGap = sortedTicks.slice(1).reduce((smallestGap, tick, index) => {
|
|
39
|
+
const gap = tick - sortedTicks[index]
|
|
40
|
+
return gap > 0 ? Math.min(smallestGap, gap) : smallestGap
|
|
41
|
+
}, Infinity)
|
|
42
|
+
|
|
43
|
+
if (!Number.isFinite(tickGap) || tickGap <= 0) return { max: currentMax }
|
|
44
|
+
|
|
45
|
+
const targetMax = Math.max(currentMax, Number.isFinite(rawMax) ? rawMax : currentMax)
|
|
46
|
+
const tickComparisonEpsilon = getTickComparisonEpsilon(tickGap)
|
|
47
|
+
let finalMax = sortedTicks[sortedTicks.length - 1]
|
|
48
|
+
|
|
49
|
+
while (finalMax + tickComparisonEpsilon < targetMax) {
|
|
50
|
+
finalMax = Number((finalMax + tickGap).toPrecision(15))
|
|
51
|
+
sortedTicks.push(finalMax)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
headroomMode === 'inline-label' &&
|
|
56
|
+
Number.isFinite(rawMax) &&
|
|
57
|
+
finalMax - rawMax <= tickGap * 0.25 + tickComparisonEpsilon
|
|
58
|
+
) {
|
|
59
|
+
finalMax = Number((finalMax + tickGap).toPrecision(15))
|
|
60
|
+
sortedTicks.push(finalMax)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { max: finalMax, tickValues: sortedTicks }
|
|
64
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
divergentColorDistribution,
|
|
11
11
|
colorblindColorDistribution
|
|
12
12
|
} from '@cdc/core/helpers/palettes/colorDistributions'
|
|
13
|
+
import { applySeriesColorAssignmentsToRange } from './colorAssignmentHelpers'
|
|
13
14
|
|
|
14
15
|
export const getColorScale = (config: ChartConfig): ((value: string) => string) => {
|
|
15
16
|
const configPalette = ['Paired Bar', 'Deviation Bar'].includes(config.visualizationType)
|
|
@@ -32,18 +33,24 @@ export const getColorScale = (config: ChartConfig): ((value: string) => string)
|
|
|
32
33
|
// Migrate old palette name if needed
|
|
33
34
|
const migratedPaletteName = configPalette ? configPalette : getFallbackColorPalette(config)
|
|
34
35
|
|
|
36
|
+
const domain = config.runtime.seriesLabelsAll
|
|
37
|
+
|
|
38
|
+
const customColorsOrdered = config.general?.palette?.customColorsOrdered
|
|
39
|
+
|
|
35
40
|
// Check for customColorsOrdered first (direct 1-to-1 mapping, no distribution needed)
|
|
36
|
-
if (
|
|
37
|
-
const
|
|
41
|
+
if (Array.isArray(customColorsOrdered) && customColorsOrdered.length > 0) {
|
|
42
|
+
const range = applySeriesColorAssignmentsToRange(config, domain, customColorsOrdered)
|
|
38
43
|
return scaleOrdinal({
|
|
39
|
-
domain
|
|
40
|
-
range
|
|
44
|
+
domain,
|
|
45
|
+
range,
|
|
41
46
|
unknown: null
|
|
42
47
|
})
|
|
43
48
|
}
|
|
44
49
|
|
|
50
|
+
const customColors = config.general?.palette?.customColors
|
|
51
|
+
const isUsingCustomColors = Array.isArray(customColors) && customColors.length > 0
|
|
45
52
|
let palette =
|
|
46
|
-
|
|
53
|
+
(isUsingCustomColors ? customColors : undefined) ||
|
|
47
54
|
palettesSource[migratePaletteWithMap(migratedPaletteName, paletteMigrationMap, false)] ||
|
|
48
55
|
palettesSource[configPalette]
|
|
49
56
|
|
|
@@ -58,8 +65,8 @@ export const getColorScale = (config: ChartConfig): ((value: string) => string)
|
|
|
58
65
|
// Apply enhanced color distribution (same logic as pie charts)
|
|
59
66
|
const paletteVersion = getColorPaletteVersion(config)
|
|
60
67
|
|
|
61
|
-
// Skip enhanced distribution if not v2, too many keys, or wrong palette length
|
|
62
|
-
if (paletteVersion !== 2 || numberOfKeys > 9 || palette.length !== 9) {
|
|
68
|
+
// Skip enhanced distribution if using custom colors, not v2, too many keys, or wrong palette length
|
|
69
|
+
if (isUsingCustomColors || paletteVersion !== 2 || numberOfKeys > 9 || palette.length !== 9) {
|
|
63
70
|
// Use existing logic for v1 palettes and other cases
|
|
64
71
|
while (numberOfKeys > palette.length) {
|
|
65
72
|
palette = palette.concat(palette)
|
|
@@ -90,9 +97,11 @@ export const getColorScale = (config: ChartConfig): ((value: string) => string)
|
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
99
|
|
|
100
|
+
const range = applySeriesColorAssignmentsToRange(config, domain, palette)
|
|
101
|
+
|
|
93
102
|
return scaleOrdinal({
|
|
94
|
-
domain
|
|
95
|
-
range
|
|
103
|
+
domain,
|
|
104
|
+
range,
|
|
96
105
|
unknown: null
|
|
97
106
|
})
|
|
98
107
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
3
|
+
|
|
4
|
+
type GetHasBoundarySuppressionProps = {
|
|
5
|
+
rows?: Object[]
|
|
6
|
+
config: ChartConfig
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const getHasBoundarySuppression = ({ rows, config }: GetHasBoundarySuppressionProps) => {
|
|
10
|
+
if (!Array.isArray(rows) || rows.length === 0) return false
|
|
11
|
+
const suppressionRules = config.preliminaryData?.filter(({ type, style }) => type === 'suppression' && style) || []
|
|
12
|
+
if (!suppressionRules.length) return false
|
|
13
|
+
|
|
14
|
+
const boundaryRows = [rows[0], rows[rows.length - 1]].filter(Boolean)
|
|
15
|
+
|
|
16
|
+
return boundaryRows.some(item =>
|
|
17
|
+
suppressionRules.some(({ column, value }) => {
|
|
18
|
+
const values = _.values(_.pick(item, config.runtime?.seriesKeys))
|
|
19
|
+
const dynamicCategory = config.series[0]?.dynamicCategory
|
|
20
|
+
const match = column ? item[column] === value : values.includes(value)
|
|
21
|
+
const dynamic = dynamicCategory && (item[dynamicCategory] === column || !column)
|
|
22
|
+
|
|
23
|
+
return Boolean(match || dynamic)
|
|
24
|
+
})
|
|
25
|
+
)
|
|
26
|
+
}
|
package/src/helpers/getMinMax.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ChartConfig } from '../types/ChartConfig'
|
|
2
|
-
import
|
|
2
|
+
import { getCleanTopTickMax } from './getCleanTopTickMax'
|
|
3
|
+
import { getAxisMaxOverride } from './getAxisMaxOverride'
|
|
3
4
|
|
|
4
5
|
type GetMinMaxProps = {
|
|
5
6
|
/** config - standard chart config */
|
|
@@ -18,6 +19,8 @@ type GetMinMaxProps = {
|
|
|
18
19
|
isAllLine: boolean
|
|
19
20
|
/** convertLineToBarGraph - whether line charts should be rendered as bar graphs */
|
|
20
21
|
convertLineToBarGraph?: boolean
|
|
22
|
+
/** whether selected-domain boundary rows include suppression tokens */
|
|
23
|
+
hasBoundarySuppression?: boolean
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
const getMinMax = ({
|
|
@@ -28,7 +31,8 @@ const getMinMax = ({
|
|
|
28
31
|
data,
|
|
29
32
|
isAllLine,
|
|
30
33
|
tableData,
|
|
31
|
-
convertLineToBarGraph
|
|
34
|
+
convertLineToBarGraph,
|
|
35
|
+
hasBoundarySuppression = false
|
|
32
36
|
}: GetMinMaxProps) => {
|
|
33
37
|
let min = 0
|
|
34
38
|
let max = 0
|
|
@@ -45,19 +49,22 @@ const getMinMax = ({
|
|
|
45
49
|
const { max: enteredMaxValue, min: enteredMinValue } = config.runtime.yAxis
|
|
46
50
|
const paddingAddedToAxis = config.yAxis.enablePadding ? 1 + config.yAxis.scalePadding / 100 : 1
|
|
47
51
|
const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
|
|
48
|
-
//
|
|
49
|
-
const
|
|
52
|
+
// Validate before applying chart-specific domain rules.
|
|
53
|
+
const { hasValidMax: hasValidExplicitLeftMax, maxNumber: enteredMaxNumber } = getAxisMaxOverride({
|
|
54
|
+
value: enteredMaxValue,
|
|
55
|
+
minimumValidMax: existPositiveValue ? maxValue : 0
|
|
56
|
+
})
|
|
57
|
+
const shouldCleanAutoLeftMax = config.yAxis.autoMaxStrategy === 'clean-top-tick' && !hasValidExplicitLeftMax
|
|
50
58
|
const isMinValid = isLogarithmicAxis
|
|
51
59
|
? Number(enteredMinValue) >= 0
|
|
52
60
|
: (Number(enteredMinValue) <= 0 && minValue >= 0) || (Number(enteredMinValue) <= minValue && minValue < 0)
|
|
53
61
|
|
|
54
62
|
min = enteredMinValue && isMinValid ? Number(enteredMinValue) : minValue
|
|
55
|
-
max =
|
|
63
|
+
max = hasValidExplicitLeftMax ? enteredMaxNumber : Number.MIN_VALUE
|
|
56
64
|
const { lower, upper } = config?.confidenceKeys || {}
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const dataForMinMax = config.xAxis.brushActive && tableData && tableData.length > 0 ? tableData : data
|
|
66
|
+
const useBrushFullRange = config.xAxis.brushActive && !config.xAxis.brushDynamicYAxis
|
|
67
|
+
const dataForMinMax = useBrushFullRange && tableData && tableData.length > 0 ? tableData : data
|
|
61
68
|
|
|
62
69
|
if (lower && upper && config.visualizationType === 'Bar') {
|
|
63
70
|
const buffer = min < 0 ? 1.1 : 0
|
|
@@ -136,8 +143,8 @@ const getMinMax = ({
|
|
|
136
143
|
leftMax = findMaxFromSeriesKeys(dataForMinMax, leftAxisSeriesItems, leftMax, 'left')
|
|
137
144
|
rightMax = findMaxFromSeriesKeys(dataForMinMax, rightAxisSeriesItems, rightMax, 'right')
|
|
138
145
|
|
|
139
|
-
if (leftMax <
|
|
140
|
-
leftMax =
|
|
146
|
+
if (hasValidExplicitLeftMax && leftMax < enteredMaxNumber) {
|
|
147
|
+
leftMax = enteredMaxNumber
|
|
141
148
|
}
|
|
142
149
|
} catch (e) {
|
|
143
150
|
console.error(e.message)
|
|
@@ -181,23 +188,9 @@ const getMinMax = ({
|
|
|
181
188
|
const numEnteredMin = Number(enteredMinValue)
|
|
182
189
|
const isMinValid = isLogarithmicAxis ? numEnteredMin >= 0 && numEnteredMin < minValue : numEnteredMin < minValue
|
|
183
190
|
|
|
184
|
-
const suppressedMinValue = tableData?.some((item, i, arr) =>
|
|
185
|
-
config.preliminaryData?.some(({ type, style, column, value }) => {
|
|
186
|
-
if (type !== 'suppression' || !style) return false
|
|
187
|
-
|
|
188
|
-
const values = _.values(_.pick(item, config.runtime?.seriesKeys))
|
|
189
|
-
const dynamicCategory = config.series[0].dynamicCategory
|
|
190
|
-
|
|
191
|
-
const match = column ? item[column] === value : values.includes(value)
|
|
192
|
-
const dynamic = dynamicCategory && (item[dynamicCategory] === column || !column)
|
|
193
|
-
|
|
194
|
-
return (match || dynamic) && (i === 0 || i === arr.length - 1)
|
|
195
|
-
})
|
|
196
|
-
)
|
|
197
|
-
|
|
198
191
|
const isCategorical = config.yAxis.type === 'categorical'
|
|
199
192
|
|
|
200
|
-
min = enteredMinValue !== '' && isMinValid ? numEnteredMin :
|
|
193
|
+
min = enteredMinValue !== '' && isMinValid ? numEnteredMin : hasBoundarySuppression ? 0 : isCategorical ? 0 : minValue
|
|
201
194
|
}
|
|
202
195
|
|
|
203
196
|
//If data value max wasn't provided, calculate it
|
|
@@ -206,6 +199,14 @@ const getMinMax = ({
|
|
|
206
199
|
max = existPositiveValue ? maxValue : 0
|
|
207
200
|
}
|
|
208
201
|
|
|
202
|
+
if (shouldCleanAutoLeftMax) {
|
|
203
|
+
if (config.visualizationType === 'Combo') {
|
|
204
|
+
leftMax = getCleanTopTickMax(leftMax)
|
|
205
|
+
} else {
|
|
206
|
+
max = getCleanTopTickMax(max)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
209
210
|
//Adds Y Axis data padding if applicable
|
|
210
211
|
if (config.runtime.yAxis.paddingPercent) {
|
|
211
212
|
let paddingValue = (max - min) * config.runtime.yAxis.paddingPercent
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
2
|
+
import { AxisFilterDomainBehavior } from '@cdc/core/types/Axis'
|
|
3
|
+
|
|
4
|
+
const hasRows = (data?: Object[]) => Array.isArray(data) && data.length > 0
|
|
5
|
+
|
|
6
|
+
export const getYAxisFilterDomainBehavior = (config: ChartConfig): AxisFilterDomainBehavior => {
|
|
7
|
+
return config.yAxis?.filterDomainBehavior === 'stable' ? 'stable' : 'dynamic'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const getYAxisDomainData = ({
|
|
11
|
+
config,
|
|
12
|
+
data,
|
|
13
|
+
tableData,
|
|
14
|
+
fullEligibleDomainData
|
|
15
|
+
}: {
|
|
16
|
+
config: ChartConfig
|
|
17
|
+
data: Object[]
|
|
18
|
+
tableData?: Object[]
|
|
19
|
+
fullEligibleDomainData?: Object[]
|
|
20
|
+
}): Object[] => {
|
|
21
|
+
const brushActive = Boolean(config.xAxis?.brushActive)
|
|
22
|
+
const brushDynamicYAxis = Boolean(config.xAxis?.brushDynamicYAxis)
|
|
23
|
+
|
|
24
|
+
if (brushActive && brushDynamicYAxis) {
|
|
25
|
+
return data
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const behavior = getYAxisFilterDomainBehavior(config)
|
|
29
|
+
if (behavior === 'stable' && hasRows(fullEligibleDomainData)) {
|
|
30
|
+
return fullEligibleDomainData
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (brushActive && !brushDynamicYAxis && hasRows(tableData)) {
|
|
34
|
+
return tableData
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return data
|
|
38
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type ChartConfig } from '../types/ChartConfig'
|
|
2
|
+
|
|
3
|
+
type YAxisFinalizationEligibilityProps = {
|
|
4
|
+
config: ChartConfig
|
|
5
|
+
hasSpacedInlineLabel: boolean
|
|
6
|
+
hasValidExplicitLeftMax: boolean
|
|
7
|
+
isHorizontal: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const chartUsesStandardPrimaryYScale = (config: ChartConfig) => {
|
|
11
|
+
return !['Box Plot', 'Bump Chart', 'Forest Plot'].includes(config.visualizationType)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const isLinearOrLegacyYAxis = (config: ChartConfig) => {
|
|
15
|
+
const yAxisType = config.yAxis.type
|
|
16
|
+
|
|
17
|
+
return yAxisType === undefined || yAxisType === '' || yAxisType === 'linear'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const getYAxisFinalizationEligibility = ({
|
|
21
|
+
config,
|
|
22
|
+
hasSpacedInlineLabel,
|
|
23
|
+
hasValidExplicitLeftMax,
|
|
24
|
+
isHorizontal
|
|
25
|
+
}: YAxisFinalizationEligibilityProps) => {
|
|
26
|
+
const hasStandardLinearPrimaryYAxis =
|
|
27
|
+
!isHorizontal && isLinearOrLegacyYAxis(config) && chartUsesStandardPrimaryYScale(config)
|
|
28
|
+
const supportsInlineLabelHeadroom = hasStandardLinearPrimaryYAxis && config.visualizationType !== 'Combo'
|
|
29
|
+
const shouldUseInlineLabelHeadroom = hasSpacedInlineLabel && supportsInlineLabelHeadroom
|
|
30
|
+
const shouldUseCleanTopTick =
|
|
31
|
+
!hasValidExplicitLeftMax && (config.yAxis.autoMaxStrategy === 'clean-top-tick' || shouldUseInlineLabelHeadroom)
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
hasStandardLinearPrimaryYAxis,
|
|
35
|
+
supportsInlineLabelHeadroom,
|
|
36
|
+
shouldUseCleanTopTick,
|
|
37
|
+
shouldUseInlineLabelHeadroom,
|
|
38
|
+
shouldFinalizePrimaryYAxis: shouldUseCleanTopTick && hasStandardLinearPrimaryYAxis
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -1,17 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
import type { AltTextConfig } from '@cdc/core/types/AltText'
|
|
2
|
+
import { resolveAltTextDescription } from '@cdc/core/helpers/resolveAltTextDescription'
|
|
3
|
+
|
|
4
|
+
const getAutoLabel = (state: { visualizationType?: string; title?: string }): string => {
|
|
5
|
+
let ariaLabel = ''
|
|
6
|
+
if (state.visualizationType) {
|
|
7
|
+
ariaLabel += `${state.visualizationType} chart`
|
|
8
|
+
}
|
|
9
|
+
if (state.title && state.visualizationType) {
|
|
10
|
+
ariaLabel += ` with the title: ${state.title}`
|
|
11
|
+
}
|
|
12
|
+
return ariaLabel
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const handleChartAriaLabels = (state: {
|
|
16
|
+
visualizationType?: string
|
|
17
|
+
title?: string
|
|
18
|
+
altText?: AltTextConfig
|
|
19
|
+
dataMetadata?: Record<string, string>
|
|
20
|
+
}): string => {
|
|
2
21
|
try {
|
|
3
22
|
if (!state.visualizationType) throw Error('handleChartAriaLabels: no visualization type found in state')
|
|
4
|
-
let ariaLabel = ''
|
|
5
|
-
|
|
6
|
-
if (state.visualizationType) {
|
|
7
|
-
ariaLabel += `${state.visualizationType} chart`
|
|
8
|
-
}
|
|
9
23
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
24
|
+
const title = getAutoLabel(state)
|
|
25
|
+
const description = resolveAltTextDescription(state.altText, state.dataMetadata)
|
|
13
26
|
|
|
14
|
-
return
|
|
27
|
+
return description ? `${title}. ${description}` : title
|
|
15
28
|
} catch (e) {
|
|
16
29
|
console.error('COVE: ', e.message) // eslint-disable-line
|
|
17
30
|
return 'Data visualization container'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
2
|
+
|
|
3
|
+
export const hasSpacedInlineLabel = (config: ChartConfig): boolean => {
|
|
4
|
+
const inlineLabel = config.yAxis?.inlineLabel
|
|
5
|
+
if (inlineLabel && inlineLabel.includes(' ')) {
|
|
6
|
+
return true
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return false
|
|
10
|
+
}
|
|
@@ -11,6 +11,7 @@ type ColumnFormattingParams = {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const hasOwn = (object: object, key: keyof Column) => Object.prototype.hasOwnProperty.call(object, key)
|
|
14
|
+
const isNonEmptyString = (value: unknown) => typeof value === 'string' && value !== ''
|
|
14
15
|
|
|
15
16
|
export const createDefaultSeriesColumnConfig = (columnName: string): Column => ({
|
|
16
17
|
name: columnName,
|
|
@@ -94,12 +95,12 @@ export const getSeriesColumnFormattingParams = (columnConfig?: Partial<Column>):
|
|
|
94
95
|
|
|
95
96
|
const formattingParams: ColumnFormattingParams = {}
|
|
96
97
|
|
|
97
|
-
if (hasOwn(columnConfig, 'prefix')) {
|
|
98
|
-
formattingParams.addColPrefix = columnConfig.prefix
|
|
98
|
+
if (hasOwn(columnConfig, 'prefix') && isNonEmptyString(columnConfig.prefix)) {
|
|
99
|
+
formattingParams.addColPrefix = columnConfig.prefix
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
if (hasOwn(columnConfig, 'suffix')) {
|
|
102
|
-
formattingParams.addColSuffix = columnConfig.suffix
|
|
102
|
+
if (hasOwn(columnConfig, 'suffix') && isNonEmptyString(columnConfig.suffix)) {
|
|
103
|
+
formattingParams.addColSuffix = columnConfig.suffix
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
if (hasOwn(columnConfig, 'roundToPlace')) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Label } from '../types/Label'
|
|
2
|
+
|
|
3
|
+
type LegendHighlightSource = Pick<Label, 'datum'> | string
|
|
4
|
+
|
|
5
|
+
export const getLegendHighlightKey = (
|
|
6
|
+
seriesLabels: Record<string, string> = {},
|
|
7
|
+
legendSource: LegendHighlightSource
|
|
8
|
+
): string => {
|
|
9
|
+
const labelDatum = typeof legendSource === 'string' ? legendSource : legendSource.datum
|
|
10
|
+
|
|
11
|
+
return Object.entries(seriesLabels).find(([, label]) => label === labelDatum)?.[0] || labelDatum
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const shouldResetSeriesHighlight = (
|
|
15
|
+
seriesHighlight: string[],
|
|
16
|
+
seriesKeys: string[] = [],
|
|
17
|
+
highlightKey: string,
|
|
18
|
+
visualizationType?: string
|
|
19
|
+
): boolean => {
|
|
20
|
+
return (
|
|
21
|
+
seriesHighlight.length + 1 === seriesKeys.length &&
|
|
22
|
+
visualizationType !== 'Forecasting' &&
|
|
23
|
+
seriesKeys.includes(highlightKey) &&
|
|
24
|
+
!seriesHighlight.includes(highlightKey)
|
|
25
|
+
)
|
|
26
|
+
}
|