@cdc/chart 4.26.1 → 4.26.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/CLAUDE.local.md +79 -0
- package/LICENSE +201 -0
- package/dist/{cdcchart-dgT_1dIT.es.js → cdcchart-DQ00cQCm.es.js} +1 -20
- package/dist/cdcchart.js +54742 -49796
- package/examples/data/data-with-metadata.json +10 -0
- package/examples/default.json +378 -0
- package/examples/feature/__data__/horizon-chart-data.json +373 -0
- package/examples/feature/annotations/index.json +3 -6
- package/examples/feature/horizon/horizon-chart.json +395 -0
- package/examples/feature/pie/planet-pie-example-config.json +2 -1
- package/examples/line-chart-states.json +1085 -0
- package/examples/metadata-variables.json +58 -0
- package/examples/private/123.json +694 -0
- package/examples/private/anchor-issue.json +4094 -0
- package/examples/private/backwards-slider.json +10430 -0
- package/examples/private/georgia.csv +160 -0
- package/examples/private/timeline-data.json +1 -0
- package/examples/private/timeline.json +389 -0
- package/examples/radar-chart-simple.json +133 -0
- package/examples/radar-chart.json +148 -0
- package/index.html +1 -31
- package/package.json +57 -59
- package/src/CdcChart.tsx +8 -4
- package/src/CdcChartComponent.tsx +398 -284
- package/src/_stories/Chart.Anchors.stories.tsx +10 -0
- package/src/_stories/Chart.BoxPlot.stories.tsx +7 -0
- package/src/_stories/Chart.CI.stories.tsx +13 -0
- package/src/_stories/Chart.Combo.stories.tsx +17 -0
- package/src/_stories/Chart.CustomColors.stories.tsx +78 -0
- package/src/_stories/Chart.Defaults.stories.tsx +95 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +19 -0
- package/src/_stories/Chart.Filters.stories.tsx +4 -0
- package/src/_stories/Chart.Forecast.stories.tsx +4 -0
- package/src/_stories/Chart.HTMLInDataTable.stories.tsx +22 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +28 -0
- package/src/_stories/Chart.Patterns.stories.tsx +4 -0
- package/src/_stories/Chart.PreserveDecimals.stories.tsx +25 -0
- package/src/_stories/Chart.Regions.Categorical.stories.tsx +13 -0
- package/src/_stories/Chart.Regions.DateScale.stories.tsx +19 -0
- package/src/_stories/Chart.Regions.DateTimeScale.stories.tsx +25 -10
- package/src/_stories/Chart.ScatterPlot.stories.tsx +4 -0
- package/src/_stories/Chart.SmallMultiples.stories.tsx +16 -0
- package/src/_stories/Chart.SmallestLeftAxisMax.stories.tsx +64 -0
- package/src/_stories/Chart.stories.tsx +72 -1
- package/src/_stories/Chart.tooltip.stories.tsx +7 -0
- package/src/_stories/ChartAnnotation.stories.tsx +10 -0
- package/src/_stories/ChartAxisLabels.stories.tsx +4 -0
- package/src/_stories/ChartAxisTitles.stories.tsx +10 -0
- package/src/_stories/ChartBar.Editor.stories.tsx +97 -38
- package/src/_stories/ChartBrush.Editor.stories.tsx +11 -25
- package/src/_stories/ChartBrush.Matrix.Continuous.stories.tsx +41 -0
- package/src/_stories/ChartBrush.Matrix.Date.stories.tsx +114 -0
- package/src/_stories/ChartBrush.Matrix.DateTime.stories.tsx +78 -0
- package/src/_stories/ChartBrush.stories.tsx +7 -0
- package/src/_stories/ChartEditor.Editor.stories.tsx +1 -1
- package/src/_stories/ChartEditor.stories.tsx +7 -0
- package/src/_stories/ChartLine.QuadrantAngles.stories.tsx +89 -0
- package/src/_stories/ChartLine.Suppression.stories.tsx +7 -0
- package/src/_stories/ChartLine.Symbols.stories.tsx +4 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +46 -1
- package/src/_stories/TechAdoptionWithLinks.stories.tsx +7 -0
- package/src/_stories/_mock/brush_continuous.json +86 -0
- package/src/_stories/_mock/brush_date_large.json +176 -0
- package/src/_stories/_mock/line_chart_angle_near_zero_fall.json +195 -0
- package/src/_stories/_mock/line_chart_angle_near_zero_rise.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q1_steep_upward.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q2_gentle_downward.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q3_steep_downward.json +195 -0
- package/src/_stories/_mock/line_chart_angle_q4_gentle_upward.json +195 -0
- package/src/_stories/_mock/line_chart_quadrant_angles.json +264 -0
- package/src/_stories/_mock/paired-bar-abbr.json +421 -0
- package/src/_stories/_mock/pie_custom_colors.json +268 -0
- package/src/_stories/_mock/smallest_left_axis_max.json +104 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +14 -20
- package/src/components/Annotations/components/AnnotationDraggable.tsx +240 -116
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -2
- package/src/components/Annotations/components/AnnotationDropdown.tsx +8 -12
- package/src/components/Annotations/components/AnnotationList.styles.css +12 -18
- package/src/components/Annotations/components/AnnotationList.tsx +5 -4
- package/src/components/Annotations/components/findNearestDatum.ts +75 -85
- package/src/components/Annotations/helpers/getVisibleAnnotations.ts +38 -0
- package/src/components/Axis/BottomAxis.tsx +277 -0
- package/src/components/Axis/LeftAxis.tsx +404 -0
- package/src/components/Axis/LeftAxisGridlines.tsx +77 -0
- package/src/components/Axis/PairedBarAxis.tsx +192 -0
- package/src/components/Axis/README.md +94 -0
- package/src/components/Axis/RightAxis.tsx +108 -0
- package/src/components/Axis/axis.constants.ts +21 -0
- package/src/components/Axis/index.ts +7 -0
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +12 -28
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -30
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +12 -31
- package/src/components/BarChart/components/BarChart.Vertical.tsx +12 -28
- package/src/components/BarChart/components/BarChart.tsx +7 -1
- package/src/components/BarChart/helpers/getPatternUrl.ts +94 -0
- package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +134 -0
- package/src/components/BarChart/helpers/useBarChart.ts +3 -0
- package/src/components/Brush/BrushSelector.tsx +155 -22
- package/src/components/Brush/MiniChartPreview.tsx +133 -21
- package/src/components/EditorPanel/EditorPanel.tsx +81 -54
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +67 -29
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +0 -78
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +120 -2
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +25 -43
- package/src/components/EditorPanel/components/Panels/Panel.Radar.tsx +353 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +83 -3
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +66 -43
- package/src/components/EditorPanel/components/Panels/index.tsx +2 -0
- package/src/components/EditorPanel/editor-panel.scss +1 -1
- package/src/components/EditorPanel/useEditorPermissions.ts +55 -26
- package/src/components/ForestPlot/ForestPlot.tsx +26 -22
- package/src/components/HorizonChart/HorizonChart.tsx +131 -0
- package/src/components/HorizonChart/components/HorizonBand.tsx +160 -0
- package/src/components/HorizonChart/helpers/calculateHorizonBands.ts +27 -0
- package/src/components/HorizonChart/helpers/getHorizonLayerColors.ts +40 -0
- package/src/components/HorizonChart/index.tsx +3 -0
- package/src/components/Legend/Legend.Component.tsx +52 -4
- package/src/components/Legend/Legend.tsx +1 -1
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +4 -4
- package/src/components/Legend/LegendValueRange.tsx +77 -0
- package/src/components/Legend/helpers/createFormatLabels.tsx +16 -2
- package/src/components/Legend/helpers/generateValueRanges.ts +92 -0
- package/src/components/LineChart/helpers/README.md +292 -0
- package/src/components/LineChart/helpers/labelPositioning.test.ts +245 -0
- package/src/components/LineChart/helpers/labelPositioning.ts +304 -0
- package/src/components/LineChart/index.tsx +44 -8
- package/src/components/LinearChart/README.md +109 -0
- package/src/components/LinearChart/VisualizationRenderer.tsx +267 -0
- package/src/components/LinearChart/linearChart.constants.ts +84 -0
- package/src/components/LinearChart/tests/LinearChart.test.tsx +278 -0
- package/src/components/LinearChart/tests/mockConfigContext.ts +131 -0
- package/src/components/LinearChart/utils/tickFormatting.ts +146 -0
- package/src/components/LinearChart.tsx +268 -1057
- package/src/components/PieChart/PieChart.tsx +20 -5
- package/src/components/RadarChart/RadarAxis.tsx +78 -0
- package/src/components/RadarChart/RadarChart.tsx +298 -0
- package/src/components/RadarChart/RadarGrid.tsx +64 -0
- package/src/components/RadarChart/RadarPolygon.tsx +91 -0
- package/src/components/RadarChart/helpers.ts +83 -0
- package/src/components/RadarChart/index.tsx +3 -0
- package/src/components/Regions/components/Regions.tsx +6 -6
- package/src/components/Sankey/components/Sankey.tsx +3 -3
- package/src/components/Sankey/sankey.scss +1 -1
- package/src/components/SmallMultiples/SmallMultiples.css +5 -5
- package/src/components/Sparkline/index.scss +4 -2
- package/src/components/WarmingStripes/WarmingStripes.tsx +95 -25
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +8 -8
- package/src/data/initial-state.js +37 -15
- package/src/data/legacy-defaults.ts +18 -0
- package/src/helpers/abbreviateNumber.ts +24 -17
- package/src/helpers/getChartPatternId.ts +17 -0
- package/src/helpers/getExcludedData.ts +4 -0
- package/src/helpers/getMinMax.ts +16 -2
- package/src/helpers/handleChartAriaLabels.ts +19 -19
- package/src/helpers/handleLineType.ts +22 -18
- package/src/helpers/seriesColumnSettings.ts +114 -0
- package/src/helpers/tests/countNumOfTicks.test.ts +77 -0
- package/src/helpers/tests/seriesColumnSettings.test.ts +84 -0
- package/src/hooks/useProgrammaticTooltip.ts +23 -2
- package/src/hooks/useRightAxis.ts +14 -0
- package/src/hooks/useScales.ts +99 -56
- package/src/hooks/useTooltip.tsx +23 -3
- package/src/scss/main.scss +157 -79
- package/src/selectors/README.md +68 -0
- package/src/store/chart.reducer.ts +2 -0
- package/src/test/CdcChart.test.jsx +2 -2
- package/src/types/ChartConfig.ts +22 -0
- package/src/types/ChartContext.ts +1 -0
- package/src/types/Horizon.ts +64 -0
- package/tests/fixtures/chart-config-with-metadata.json +29 -0
- package/tests/fixtures/data-with-metadata.json +10 -0
- package/preview.html +0 -1616
- package/src/components/Annotations/components/helpers/index.tsx +0 -46
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
case '
|
|
12
|
-
return
|
|
13
|
-
case '
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
const DASH_PATTERNS = {
|
|
2
|
+
SMALL: '5 5',
|
|
3
|
+
MEDIUM: '10 5',
|
|
4
|
+
LARGE: '15 5',
|
|
5
|
+
SOLID: 0
|
|
6
|
+
} as const
|
|
7
|
+
|
|
8
|
+
export const handleLineType = (lineType: string): string | number => {
|
|
9
|
+
switch (lineType) {
|
|
10
|
+
case 'dashed-sm':
|
|
11
|
+
case 'Dashed Small':
|
|
12
|
+
return DASH_PATTERNS.SMALL
|
|
13
|
+
case 'dashed-md':
|
|
14
|
+
case 'Dashed Medium':
|
|
15
|
+
return DASH_PATTERNS.MEDIUM
|
|
16
|
+
case 'dashed-lg':
|
|
17
|
+
case 'Dashed Large':
|
|
18
|
+
return DASH_PATTERNS.LARGE
|
|
19
|
+
default:
|
|
20
|
+
return DASH_PATTERNS.SOLID
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Column } from '@cdc/core/types/Column'
|
|
2
|
+
import { Series } from '@cdc/core/types/Series'
|
|
3
|
+
|
|
4
|
+
type ChartColumns = Record<string, Partial<Column>>
|
|
5
|
+
type SeriesItem = Series[number]
|
|
6
|
+
type ColumnFormattingParams = {
|
|
7
|
+
addColPrefix?: string
|
|
8
|
+
addColSuffix?: string
|
|
9
|
+
addColRoundTo?: number
|
|
10
|
+
addColCommas?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const hasOwn = (object: object, key: keyof Column) => Object.prototype.hasOwnProperty.call(object, key)
|
|
14
|
+
|
|
15
|
+
export const createDefaultSeriesColumnConfig = (columnName: string): Column => ({
|
|
16
|
+
name: columnName,
|
|
17
|
+
label: columnName,
|
|
18
|
+
prefix: '',
|
|
19
|
+
suffix: '',
|
|
20
|
+
roundToPlace: 0,
|
|
21
|
+
commas: false,
|
|
22
|
+
dataTable: true,
|
|
23
|
+
order: undefined,
|
|
24
|
+
showInViz: false,
|
|
25
|
+
startingPoint: '0',
|
|
26
|
+
series: undefined,
|
|
27
|
+
tooltips: false,
|
|
28
|
+
forestPlot: false,
|
|
29
|
+
forestPlotAlignRight: false,
|
|
30
|
+
forestPlotStartingPoint: 0
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export const getSeriesOwnedColumnNames = (series: Partial<SeriesItem>[] = []): string[] => {
|
|
34
|
+
return series.map(item => item?.dataKey).filter(Boolean)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const findColumnConfigByName = (
|
|
38
|
+
columns: ChartColumns = {},
|
|
39
|
+
columnName: string
|
|
40
|
+
): { columnKey: string; columnConfig: Partial<Column> } | null => {
|
|
41
|
+
for (const [columnKey, columnConfig] of Object.entries(columns)) {
|
|
42
|
+
if (columnConfig?.name === columnName || (!columnConfig?.name && columnKey === columnName)) {
|
|
43
|
+
return { columnKey, columnConfig }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const getSeriesColumnConfig = (columns: ChartColumns = {}, seriesKey: string) => {
|
|
51
|
+
const existingEntry = findColumnConfigByName(columns, seriesKey)
|
|
52
|
+
const baseColumnConfig = createDefaultSeriesColumnConfig(seriesKey)
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
columnKey: existingEntry?.columnKey || seriesKey,
|
|
56
|
+
columnConfig: {
|
|
57
|
+
...baseColumnConfig,
|
|
58
|
+
...(existingEntry?.columnConfig || {}),
|
|
59
|
+
name: seriesKey,
|
|
60
|
+
label: existingEntry?.columnConfig?.label ?? baseColumnConfig.label
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const upsertSeriesColumnConfig = (
|
|
66
|
+
columns: ChartColumns = {},
|
|
67
|
+
seriesKey: string,
|
|
68
|
+
updates: Partial<Column>
|
|
69
|
+
): ChartColumns => {
|
|
70
|
+
const existingEntry = findColumnConfigByName(columns, seriesKey)
|
|
71
|
+
const columnKey = existingEntry?.columnKey || seriesKey
|
|
72
|
+
const nextColumnConfig = {
|
|
73
|
+
...(existingEntry?.columnConfig || {}),
|
|
74
|
+
...updates,
|
|
75
|
+
name: seriesKey
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
nextColumnConfig.label === undefined &&
|
|
80
|
+
!hasOwn(existingEntry?.columnConfig || {}, 'label') &&
|
|
81
|
+
!hasOwn(updates, 'label')
|
|
82
|
+
) {
|
|
83
|
+
delete nextColumnConfig.label
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
...columns,
|
|
88
|
+
[columnKey]: nextColumnConfig
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const getSeriesColumnFormattingParams = (columnConfig?: Partial<Column>): ColumnFormattingParams | undefined => {
|
|
93
|
+
if (!columnConfig) return undefined
|
|
94
|
+
|
|
95
|
+
const formattingParams: ColumnFormattingParams = {}
|
|
96
|
+
|
|
97
|
+
if (hasOwn(columnConfig, 'prefix')) {
|
|
98
|
+
formattingParams.addColPrefix = columnConfig.prefix ?? ''
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (hasOwn(columnConfig, 'suffix')) {
|
|
102
|
+
formattingParams.addColSuffix = columnConfig.suffix ?? ''
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (hasOwn(columnConfig, 'roundToPlace')) {
|
|
106
|
+
formattingParams.addColRoundTo = columnConfig.roundToPlace ?? 0
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (hasOwn(columnConfig, 'commas')) {
|
|
110
|
+
formattingParams.addColCommas = columnConfig.commas ?? false
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return Object.keys(formattingParams).length ? formattingParams : undefined
|
|
114
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { countNumOfTicks } from '../countNumOfTicks'
|
|
2
|
+
import { expect, describe, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
const baseArgs = {
|
|
5
|
+
max: 100,
|
|
6
|
+
min: 0,
|
|
7
|
+
data: [{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }, { a: 5 }],
|
|
8
|
+
config: { visualizationType: 'Bar' } as any
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('countNumOfTicks', () => {
|
|
12
|
+
it('uses viewport-specific tick count when viewportNumTicks[currentViewport] is set', () => {
|
|
13
|
+
const result = countNumOfTicks({
|
|
14
|
+
...baseArgs,
|
|
15
|
+
axis: 'xAxis',
|
|
16
|
+
runtime: { xAxis: { numTicks: 6, viewportNumTicks: { xs: 3, xxs: 2 } } },
|
|
17
|
+
currentViewport: 'xs',
|
|
18
|
+
isHorizontal: false
|
|
19
|
+
})
|
|
20
|
+
expect(result).toBe(3)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('falls back to numTicks when viewportNumTicks is absent', () => {
|
|
24
|
+
const result = countNumOfTicks({
|
|
25
|
+
...baseArgs,
|
|
26
|
+
axis: 'xAxis',
|
|
27
|
+
runtime: { xAxis: { numTicks: 6 } },
|
|
28
|
+
currentViewport: 'xs',
|
|
29
|
+
isHorizontal: false
|
|
30
|
+
})
|
|
31
|
+
expect(result).toBe(6)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('falls back to numTicks when current viewport has no entry in viewportNumTicks', () => {
|
|
35
|
+
const result = countNumOfTicks({
|
|
36
|
+
...baseArgs,
|
|
37
|
+
axis: 'xAxis',
|
|
38
|
+
runtime: { xAxis: { numTicks: 6, viewportNumTicks: { xxs: 2 } } },
|
|
39
|
+
currentViewport: 'lg',
|
|
40
|
+
isHorizontal: false
|
|
41
|
+
})
|
|
42
|
+
expect(result).toBe(6)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('xAxis vertical with numTicks: 6 returns 6', () => {
|
|
46
|
+
const result = countNumOfTicks({
|
|
47
|
+
...baseArgs,
|
|
48
|
+
axis: 'xAxis',
|
|
49
|
+
runtime: { xAxis: { numTicks: 6 } },
|
|
50
|
+
currentViewport: 'lg',
|
|
51
|
+
isHorizontal: false
|
|
52
|
+
})
|
|
53
|
+
expect(result).toBe(6)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('yAxis with numTicks: 4 returns 4', () => {
|
|
57
|
+
const result = countNumOfTicks({
|
|
58
|
+
...baseArgs,
|
|
59
|
+
axis: 'yAxis',
|
|
60
|
+
runtime: { yAxis: { numTicks: 4 } },
|
|
61
|
+
currentViewport: 'lg',
|
|
62
|
+
isHorizontal: false
|
|
63
|
+
})
|
|
64
|
+
expect(result).toBe(4)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('xAxis horizontal with no numTicks returns 4 (hardcoded fallback)', () => {
|
|
68
|
+
const result = countNumOfTicks({
|
|
69
|
+
...baseArgs,
|
|
70
|
+
axis: 'xAxis',
|
|
71
|
+
runtime: { xAxis: { numTicks: '' } },
|
|
72
|
+
currentViewport: 'lg',
|
|
73
|
+
isHorizontal: true
|
|
74
|
+
})
|
|
75
|
+
expect(result).toBe(4)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createDefaultSeriesColumnConfig,
|
|
3
|
+
findColumnConfigByName,
|
|
4
|
+
getSeriesColumnConfig,
|
|
5
|
+
getSeriesColumnFormattingParams,
|
|
6
|
+
getSeriesOwnedColumnNames,
|
|
7
|
+
upsertSeriesColumnConfig
|
|
8
|
+
} from '../seriesColumnSettings'
|
|
9
|
+
|
|
10
|
+
describe('seriesColumnSettings', () => {
|
|
11
|
+
it('returns all current series data keys as owned column names', () => {
|
|
12
|
+
expect(getSeriesOwnedColumnNames([{ dataKey: 'cases' }, { dataKey: 'deaths' }, {}])).toEqual(['cases', 'deaths'])
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('finds an existing column config by configured name', () => {
|
|
16
|
+
const result = findColumnConfigByName(
|
|
17
|
+
{
|
|
18
|
+
additionalColumn1: { name: 'cases', label: 'Cases', tooltips: true }
|
|
19
|
+
},
|
|
20
|
+
'cases'
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
expect(result).toEqual({
|
|
24
|
+
columnKey: 'additionalColumn1',
|
|
25
|
+
columnConfig: { name: 'cases', label: 'Cases', tooltips: true }
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('returns a default-backed series column config when none exists yet', () => {
|
|
30
|
+
const result = getSeriesColumnConfig({}, 'cases')
|
|
31
|
+
|
|
32
|
+
expect(result.columnKey).toBe('cases')
|
|
33
|
+
expect(result.columnConfig).toEqual(createDefaultSeriesColumnConfig('cases'))
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('updates an existing matching column config without changing its key', () => {
|
|
37
|
+
const updatedColumns = upsertSeriesColumnConfig(
|
|
38
|
+
{
|
|
39
|
+
additionalColumn1: { name: 'cases', label: 'Cases', dataTable: false }
|
|
40
|
+
},
|
|
41
|
+
'cases',
|
|
42
|
+
{ prefix: '$', commas: true }
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
expect(updatedColumns).toEqual({
|
|
46
|
+
additionalColumn1: {
|
|
47
|
+
name: 'cases',
|
|
48
|
+
label: 'Cases',
|
|
49
|
+
dataTable: false,
|
|
50
|
+
prefix: '$',
|
|
51
|
+
commas: true
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('does not persist display defaults when creating a new series-owned column config', () => {
|
|
57
|
+
expect(upsertSeriesColumnConfig({}, 'cases', { label: 'Cases' })).toEqual({
|
|
58
|
+
cases: {
|
|
59
|
+
name: 'cases',
|
|
60
|
+
label: 'Cases'
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('only returns explicit formatting overrides and preserves falsey values', () => {
|
|
66
|
+
expect(
|
|
67
|
+
getSeriesColumnFormattingParams({
|
|
68
|
+
prefix: '',
|
|
69
|
+
suffix: ' units',
|
|
70
|
+
roundToPlace: 0,
|
|
71
|
+
commas: false
|
|
72
|
+
})
|
|
73
|
+
).toEqual({
|
|
74
|
+
addColPrefix: '',
|
|
75
|
+
addColSuffix: ' units',
|
|
76
|
+
addColRoundTo: 0,
|
|
77
|
+
addColCommas: false
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('returns undefined when no formatting overrides were explicitly configured', () => {
|
|
82
|
+
expect(getSeriesColumnFormattingParams({ label: 'Cases' })).toBeUndefined()
|
|
83
|
+
})
|
|
84
|
+
})
|
|
@@ -8,6 +8,7 @@ interface UseProgrammaticTooltipProps {
|
|
|
8
8
|
setShowHoverLine: (show: boolean) => void
|
|
9
9
|
handleTooltipMouseOver: (event: MouseEvent, additionalChartData?: any) => void
|
|
10
10
|
hideTooltip: () => void
|
|
11
|
+
setSynchronizedXValue?: (value: any) => void
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -21,7 +22,8 @@ export const useProgrammaticTooltip = ({
|
|
|
21
22
|
setPoint,
|
|
22
23
|
setShowHoverLine,
|
|
23
24
|
handleTooltipMouseOver,
|
|
24
|
-
hideTooltip
|
|
25
|
+
hideTooltip,
|
|
26
|
+
setSynchronizedXValue
|
|
25
27
|
}: UseProgrammaticTooltipProps) => {
|
|
26
28
|
// Internal SVG ref for DOM manipulation
|
|
27
29
|
const internalSvgRef = useRef<SVGSVGElement>(null)
|
|
@@ -50,6 +52,15 @@ export const useProgrammaticTooltip = ({
|
|
|
50
52
|
* @param {number} yCoordinate - Exact Y coordinate to use
|
|
51
53
|
*/
|
|
52
54
|
triggerTooltipAtDataValue: (xAxisValue: any, yCoordinate: number) => {
|
|
55
|
+
// Warming Stripes positions rects by index (with data sampling), not via xScale,
|
|
56
|
+
// so synthetic mouse events won't map to the correct data points.
|
|
57
|
+
// Route through synchronizedXValue state instead, which WarmingStripes
|
|
58
|
+
// resolves to the matching stripe and calls showTooltip directly.
|
|
59
|
+
if (config.visualizationType === 'Warming Stripes') {
|
|
60
|
+
setSynchronizedXValue?.(xAxisValue)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
const pixelX = getCoordinateFromXValue(xAxisValue)
|
|
54
65
|
const adjustedX = pixelX + Number(config.yAxis.size || 0)
|
|
55
66
|
|
|
@@ -86,10 +97,20 @@ export const useProgrammaticTooltip = ({
|
|
|
86
97
|
hideTooltip: () => {
|
|
87
98
|
hideTooltip()
|
|
88
99
|
setShowHoverLine(false)
|
|
100
|
+
setSynchronizedXValue?.(null)
|
|
89
101
|
}
|
|
90
102
|
})
|
|
91
103
|
},
|
|
92
|
-
[
|
|
104
|
+
[
|
|
105
|
+
getCoordinateFromXValue,
|
|
106
|
+
config.yAxis.size,
|
|
107
|
+
config.visualizationType,
|
|
108
|
+
setPoint,
|
|
109
|
+
setShowHoverLine,
|
|
110
|
+
handleTooltipMouseOver,
|
|
111
|
+
hideTooltip,
|
|
112
|
+
setSynchronizedXValue
|
|
113
|
+
]
|
|
93
114
|
)
|
|
94
115
|
|
|
95
116
|
return internalSvgRef
|
|
@@ -27,6 +27,20 @@ export default function useRightAxis({ config, yMax = 0, data = [] }) {
|
|
|
27
27
|
minValue = config.yAxis.rightMin
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// Enforce smallest right axis max so small-data charts don't show misleading decimal ticks
|
|
31
|
+
const smallestRightAxisMaxRaw = config.yAxis.smallestRightAxisMax
|
|
32
|
+
let smallestRightAxisMax: number | null = null
|
|
33
|
+
|
|
34
|
+
if (smallestRightAxisMaxRaw !== null && smallestRightAxisMaxRaw !== undefined && smallestRightAxisMaxRaw !== '') {
|
|
35
|
+
const coercedSmallestRightAxisMax = Number(smallestRightAxisMaxRaw)
|
|
36
|
+
if (!Number.isNaN(coercedSmallestRightAxisMax)) {
|
|
37
|
+
smallestRightAxisMax = coercedSmallestRightAxisMax
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (smallestRightAxisMax !== null && max < smallestRightAxisMax) {
|
|
42
|
+
max = smallestRightAxisMax
|
|
43
|
+
}
|
|
30
44
|
// if there is a bar series & the right axis doesn't include a negative number, default to zero
|
|
31
45
|
const hasBarSeries = config.runtime?.barSeriesKeys?.length > 0
|
|
32
46
|
const hasLineSeries = config.runtime?.lineSeriesKeys?.length > 0
|
package/src/hooks/useScales.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getTicks
|
|
10
10
|
} from '@visx/scale'
|
|
11
11
|
import { useContext } from 'react'
|
|
12
|
+
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
12
13
|
import ConfigContext from '../ConfigContext'
|
|
13
14
|
import { ChartConfig } from '../types/ChartConfig'
|
|
14
15
|
import { ChartContext } from '../types/ChartContext'
|
|
@@ -59,9 +60,8 @@ const useScales = (properties: useScaleProps) => {
|
|
|
59
60
|
} = properties
|
|
60
61
|
|
|
61
62
|
const context = useContext<ChartContext>(ConfigContext)
|
|
62
|
-
const {
|
|
63
|
+
const { convertLineToBarGraph = false } = context
|
|
63
64
|
|
|
64
|
-
const [screenWidth] = dimensions
|
|
65
65
|
const isHorizontal = config.orientation === 'horizontal'
|
|
66
66
|
const { visualizationType, xAxis, forestPlot, runtime } = config
|
|
67
67
|
const isForestPlot = visualizationType === 'Forest Plot'
|
|
@@ -121,6 +121,11 @@ const useScales = (properties: useScaleProps) => {
|
|
|
121
121
|
range: [0, xMax]
|
|
122
122
|
})
|
|
123
123
|
|
|
124
|
+
let yScaleAnnotation = scaleLinear({
|
|
125
|
+
domain: [0, 100],
|
|
126
|
+
range: [0, yMax]
|
|
127
|
+
})
|
|
128
|
+
|
|
124
129
|
// handle Horizontal bars
|
|
125
130
|
if (isHorizontal) {
|
|
126
131
|
xScale = composeXScale({ min: min * 1.03, max, xMax, config })
|
|
@@ -303,65 +308,38 @@ const useScales = (properties: useScaleProps) => {
|
|
|
303
308
|
}
|
|
304
309
|
|
|
305
310
|
yScale = scaleLinear({
|
|
306
|
-
domain: [0,
|
|
311
|
+
domain: [0, data.length],
|
|
307
312
|
range: resolvedYRange()
|
|
308
313
|
})
|
|
309
314
|
|
|
310
315
|
const xAxisPadding = 5
|
|
316
|
+
const [plotStart, plotEnd] = getForestPlotRange(config, data as Record<string, any>[], xMax)
|
|
317
|
+
|
|
318
|
+
if (forestPlot.type === 'Linear') {
|
|
319
|
+
xScale = scaleLinear<LinearScaleConfig>({
|
|
320
|
+
domain: [
|
|
321
|
+
Math.min(...data.map(d => parseFloat(d[forestPlot.lower]))) - xAxisPadding,
|
|
322
|
+
Math.max(...data.map(d => parseFloat(d[forestPlot.upper]))) + xAxisPadding
|
|
323
|
+
],
|
|
324
|
+
range: [plotStart, plotEnd],
|
|
325
|
+
type: scaleTypes.LINEAR
|
|
326
|
+
})
|
|
327
|
+
xScale.type = scaleTypes.LINEAR
|
|
328
|
+
}
|
|
311
329
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
range: [leftWidthOffset, Number(screenWidth) - rightWidthOffset]
|
|
326
|
-
})
|
|
327
|
-
xScale.type = scaleTypes.LINEAR
|
|
328
|
-
}
|
|
329
|
-
if (forestPlot.type === 'Logarithmic') {
|
|
330
|
-
let max = Math.max(...data.map(d => parseFloat(d[forestPlot.upper])))
|
|
331
|
-
let fp_min = Math.min(...data.map(d => parseFloat(d[forestPlot.lower])))
|
|
332
|
-
|
|
333
|
-
xScale = scaleLog<LogScaleConfig>({
|
|
334
|
-
domain: [fp_min, max],
|
|
335
|
-
range: [leftWidthOffset, xMax - rightWidthOffset],
|
|
336
|
-
nice: true
|
|
337
|
-
})
|
|
338
|
-
xScale.type = scaleTypes.LOG
|
|
339
|
-
}
|
|
340
|
-
} else {
|
|
341
|
-
if (forestPlot.type === 'Linear') {
|
|
342
|
-
xScale = scaleLinear<LinearScaleConfig>({
|
|
343
|
-
domain: [
|
|
344
|
-
Math.min(...data.map(d => parseFloat(d[forestPlot.lower]))) - xAxisPadding,
|
|
345
|
-
Math.max(...data.map(d => parseFloat(d[forestPlot.upper]))) + xAxisPadding
|
|
346
|
-
],
|
|
347
|
-
range: [leftWidthOffsetMobile, xMax - rightWidthOffsetMobile],
|
|
348
|
-
type: scaleTypes.LINEAR
|
|
349
|
-
})
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (forestPlot.type === 'Logarithmic') {
|
|
353
|
-
let max = Math.max(...data.map(d => parseFloat(d[forestPlot.upper])))
|
|
354
|
-
let fp_min = Math.min(...data.map(d => parseFloat(d[forestPlot.lower])))
|
|
355
|
-
|
|
356
|
-
xScale = scaleLog<LogScaleConfig>({
|
|
357
|
-
domain: [fp_min, max],
|
|
358
|
-
range: [leftWidthOffset, xMax - rightWidthOffset],
|
|
359
|
-
nice: true,
|
|
360
|
-
base: max > 1 ? 10 : 2,
|
|
361
|
-
round: false,
|
|
362
|
-
type: scaleTypes.LOG
|
|
363
|
-
})
|
|
364
|
-
}
|
|
330
|
+
if (forestPlot.type === 'Logarithmic') {
|
|
331
|
+
const max = Math.max(...data.map(d => parseFloat(d[forestPlot.upper])))
|
|
332
|
+
const fp_min = Math.min(...data.map(d => parseFloat(d[forestPlot.lower])))
|
|
333
|
+
|
|
334
|
+
xScale = scaleLog<LogScaleConfig>({
|
|
335
|
+
domain: [fp_min, max],
|
|
336
|
+
range: [plotStart, plotEnd],
|
|
337
|
+
nice: true,
|
|
338
|
+
base: max > 1 ? 10 : 2,
|
|
339
|
+
round: false,
|
|
340
|
+
type: scaleTypes.LOG
|
|
341
|
+
})
|
|
342
|
+
xScale.type = scaleTypes.LOG
|
|
365
343
|
}
|
|
366
344
|
}
|
|
367
345
|
return {
|
|
@@ -372,6 +350,7 @@ const useScales = (properties: useScaleProps) => {
|
|
|
372
350
|
g2xScale,
|
|
373
351
|
xScaleNoPadding,
|
|
374
352
|
xScaleAnnotation,
|
|
353
|
+
yScaleAnnotation,
|
|
375
354
|
min,
|
|
376
355
|
max,
|
|
377
356
|
leftMax,
|
|
@@ -387,6 +366,7 @@ const getFirstDayOfMonth = ms => {
|
|
|
387
366
|
}
|
|
388
367
|
|
|
389
368
|
const dateFormatHasMonthButNoDays = dateFormat => {
|
|
369
|
+
if (!dateFormat) return false
|
|
390
370
|
return (
|
|
391
371
|
(dateFormat.includes('%b') ||
|
|
392
372
|
dateFormat.includes('%B') ||
|
|
@@ -518,3 +498,66 @@ const sortXAxisData = (xAxisData, sortByRecentDate) => {
|
|
|
518
498
|
return xAxisData.sort((a, b) => Number(a) - Number(b))
|
|
519
499
|
}
|
|
520
500
|
}
|
|
501
|
+
|
|
502
|
+
const FOREST_PLOT_FONT = 'normal 12px Nunito, sans-serif'
|
|
503
|
+
const FOREST_PLOT_GAP = 24
|
|
504
|
+
const FOREST_PLOT_MIN_WIDTH = 120
|
|
505
|
+
const FOREST_PLOT_MAX_LEFT_RATIO = 0.45
|
|
506
|
+
const FOREST_PLOT_MAX_RIGHT_RATIO = 0.35
|
|
507
|
+
|
|
508
|
+
const getForestPlotRange = (config: ChartConfig, data: Record<string, any>[], xMax: number): [number, number] => {
|
|
509
|
+
if (!xMax) return [0, 0]
|
|
510
|
+
|
|
511
|
+
const leftReserve = getForestPlotLeftReserve(config, data, xMax)
|
|
512
|
+
const rightReserve = getForestPlotRightReserve(config, data, xMax)
|
|
513
|
+
const availableReserve = Math.max(xMax - FOREST_PLOT_MIN_WIDTH, 0)
|
|
514
|
+
const totalReserve = leftReserve + rightReserve
|
|
515
|
+
|
|
516
|
+
if (totalReserve <= availableReserve) {
|
|
517
|
+
return [leftReserve, xMax - rightReserve]
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (!availableReserve || !totalReserve) {
|
|
521
|
+
return [0, xMax]
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const reserveScale = availableReserve / totalReserve
|
|
525
|
+
return [leftReserve * reserveScale, xMax - rightReserve * reserveScale]
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const getForestPlotLeftReserve = (config: ChartConfig, data: Record<string, any>[], xMax: number) => {
|
|
529
|
+
const { forestPlot, xAxis } = config
|
|
530
|
+
const columns = Object.values(config.columns || {}) as Record<string, any>[]
|
|
531
|
+
const studyTextWidth = forestPlot.hideDateCategoryCol
|
|
532
|
+
? 0
|
|
533
|
+
: getForestPlotTextWidth([xAxis.dataKey, ...data.map(row => row?.[xAxis.dataKey])])
|
|
534
|
+
|
|
535
|
+
const leftColumnExtent = columns
|
|
536
|
+
.filter(column => column?.forestPlot && !column?.forestPlotAlignRight)
|
|
537
|
+
.reduce((maxExtent, column) => {
|
|
538
|
+
const columnStart = Number(column.forestPlotStartingPoint ?? column.startingPoint ?? 0)
|
|
539
|
+
const columnWidth = getForestPlotTextWidth([column.label, ...data.map(row => row?.[column.name])])
|
|
540
|
+
return Math.max(maxExtent, columnStart + columnWidth)
|
|
541
|
+
}, 0)
|
|
542
|
+
|
|
543
|
+
const reserve = Math.max(studyTextWidth, leftColumnExtent)
|
|
544
|
+
return reserve ? Math.min(reserve + FOREST_PLOT_GAP, xMax * FOREST_PLOT_MAX_LEFT_RATIO) : 0
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const getForestPlotRightReserve = (config: ChartConfig, data: Record<string, any>[], xMax: number) => {
|
|
548
|
+
const columns = Object.values(config.columns || {}) as Record<string, any>[]
|
|
549
|
+
const rightColumnWidth = columns
|
|
550
|
+
.filter(column => column?.forestPlot && column?.forestPlotAlignRight)
|
|
551
|
+
.reduce((maxWidth, column) => {
|
|
552
|
+
const columnWidth = getForestPlotTextWidth([column.label, ...data.map(row => row?.[column.name])])
|
|
553
|
+
return Math.max(maxWidth, columnWidth)
|
|
554
|
+
}, 0)
|
|
555
|
+
|
|
556
|
+
return rightColumnWidth ? Math.min(rightColumnWidth + FOREST_PLOT_GAP, xMax * FOREST_PLOT_MAX_RIGHT_RATIO) : 0
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const getForestPlotTextWidth = (values: unknown[]) =>
|
|
560
|
+
values.reduce((maxWidth, value) => {
|
|
561
|
+
const text = value === null || value === undefined ? '' : String(value)
|
|
562
|
+
return Math.max(maxWidth, getTextWidth(text, FOREST_PLOT_FONT) || 0)
|
|
563
|
+
}, 0)
|
package/src/hooks/useTooltip.tsx
CHANGED
|
@@ -10,6 +10,11 @@ import { localPoint } from '@visx/event'
|
|
|
10
10
|
import { bisector } from 'd3-array'
|
|
11
11
|
import _, { get } from 'lodash'
|
|
12
12
|
import { getHorizontalBarHeights } from '../components/BarChart/helpers/getBarHeights'
|
|
13
|
+
import {
|
|
14
|
+
findColumnConfigByName,
|
|
15
|
+
getSeriesColumnFormattingParams,
|
|
16
|
+
getSeriesOwnedColumnNames
|
|
17
|
+
} from '../helpers/seriesColumnSettings'
|
|
13
18
|
|
|
14
19
|
export const useTooltip = props => {
|
|
15
20
|
// Track the last X-axis value to prevent duplicate analytics events
|
|
@@ -27,6 +32,7 @@ export const useTooltip = props => {
|
|
|
27
32
|
} = useContext<ChartContext>(ConfigContext)
|
|
28
33
|
const { xScale, yScale, seriesScale, showTooltip, hideTooltip, interactionLabel = '' } = props
|
|
29
34
|
const { xAxis, visualizationType, orientation, yAxis, runtime } = config
|
|
35
|
+
const seriesOwnedColumnNames = getSeriesOwnedColumnNames(config.series)
|
|
30
36
|
|
|
31
37
|
// Track the latest xScale in a ref to prevent stale closures
|
|
32
38
|
const xScaleRef = useRef(xScale)
|
|
@@ -72,8 +78,14 @@ export const useTooltip = props => {
|
|
|
72
78
|
|
|
73
79
|
const getFormattedValue = (seriesKey, value, config, getAxisPosition) => {
|
|
74
80
|
// handle case where data is missing
|
|
75
|
-
const showMissingDataValue =
|
|
76
|
-
|
|
81
|
+
const showMissingDataValue =
|
|
82
|
+
config.general.showMissingDataLabel && (value === null || value === undefined || value === '' || value === 'null')
|
|
83
|
+
const seriesColumnConfig = findColumnConfigByName(config.columns || {}, seriesKey)?.columnConfig
|
|
84
|
+
const formattingParams = getSeriesColumnFormattingParams(seriesColumnConfig)
|
|
85
|
+
const formattedValue =
|
|
86
|
+
seriesKey === config.xAxis.dataKey
|
|
87
|
+
? value
|
|
88
|
+
: formatColNumber(value, getAxisPosition(seriesKey), true, config, formattingParams)
|
|
77
89
|
|
|
78
90
|
return showMissingDataValue ? 'N/A' : formattedValue
|
|
79
91
|
}
|
|
@@ -98,6 +110,9 @@ export const useTooltip = props => {
|
|
|
98
110
|
const columnsWithTooltips = []
|
|
99
111
|
const tooltipItems = [] as any[][]
|
|
100
112
|
for (const [colKey, column] of Object.entries(config.columns)) {
|
|
113
|
+
const columnName = column.name || colKey
|
|
114
|
+
if (seriesOwnedColumnNames.includes(columnName)) continue
|
|
115
|
+
|
|
101
116
|
const formattingParams = {
|
|
102
117
|
addColPrefix: column.prefix,
|
|
103
118
|
addColSuffix: column.suffix,
|
|
@@ -592,6 +607,7 @@ export const useTooltip = props => {
|
|
|
592
607
|
case 'Line':
|
|
593
608
|
case 'Area Chart':
|
|
594
609
|
case 'Pie':
|
|
610
|
+
case 'Horizon Chart':
|
|
595
611
|
return common
|
|
596
612
|
case 'Combo':
|
|
597
613
|
return [...common, ...ciItems]
|
|
@@ -600,6 +616,8 @@ export const useTooltip = props => {
|
|
|
600
616
|
|
|
601
617
|
case 'Bar':
|
|
602
618
|
return orientation === 'vertical' ? common : [runtime.yAxis.dataKey, ...runtime?.seriesKeys]
|
|
619
|
+
case 'Warming Stripes':
|
|
620
|
+
return common
|
|
603
621
|
default:
|
|
604
622
|
throw new Error('No visualization type found in handleTooltipMouseOver')
|
|
605
623
|
}
|
|
@@ -630,7 +648,9 @@ export const useTooltip = props => {
|
|
|
630
648
|
*/
|
|
631
649
|
const getSeriesNameFromLabel = originalColumnName => {
|
|
632
650
|
let series = config.runtime.series.filter(s => s.dataKey === originalColumnName)
|
|
633
|
-
if (series[0]
|
|
651
|
+
if (series[0] && series[0].name !== undefined) return series[0]?.name
|
|
652
|
+
const columnConfig = findColumnConfigByName(config.columns || {}, originalColumnName)?.columnConfig
|
|
653
|
+
if (columnConfig?.label !== undefined) return columnConfig.label
|
|
634
654
|
return originalColumnName
|
|
635
655
|
}
|
|
636
656
|
|