@cdc/chart 4.26.3 → 4.26.5
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 +489 -0
- package/README.md +42 -17
- package/dist/cdcchart.js +39024 -36587
- package/examples/feature/bar/additional-column-tooltip.json +3 -3
- package/examples/feature/deviation/planet-deviation-config.json +2 -2
- 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/feature/paired-bar/paired-bar-example.json +2 -2
- package/examples/feature/tests-case-rate/case-rate-example-config.json +2 -2
- package/examples/feature/tests-covid/covid-confidence-example-config.json +2 -2
- package/examples/feature/tests-covid/covid-example-config.json +2 -2
- package/examples/feature/tests-cutoff/cutoff-example-config.json +2 -2
- package/examples/feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json +2 -2
- package/examples/metadata-variables.json +1 -1
- package/examples/minimal-example.json +24 -0
- package/examples/private/bump-chart.json +1116 -0
- package/examples/private/test-123.json +624 -0
- package/examples/private/test2.json +35012 -0
- package/package.json +5 -4
- package/src/CdcChartComponent.tsx +144 -60
- package/src/_stories/Chart.AltText.stories.tsx +136 -0
- package/src/_stories/{Chart.HTMLInDataTable.stories.tsx → Chart.HTMLInDataTable.smoke.stories.tsx} +10 -0
- package/src/_stories/Chart.HeatMap.stories.tsx +187 -0
- package/src/_stories/Chart.Minimal.smoke.stories.tsx +25 -0
- package/src/_stories/Chart.Sankey.smoke.stories.tsx +62 -0
- package/src/_stories/Chart.SmallMultiples.smoke.stories.tsx +108 -0
- package/src/_stories/{Chart.stories.tsx → Chart.smoke.stories.tsx} +12 -0
- package/src/_stories/{Chart.tooltip.stories.tsx → Chart.tooltip.smoke.stories.tsx} +68 -0
- package/src/_stories/{ChartAxisTitles.stories.tsx → ChartAxisTitles.smoke.stories.tsx} +13 -0
- package/src/_stories/ChartBar.Editor.stories.tsx +151 -85
- package/src/_stories/ChartBrush.Interaction.stories.tsx +112 -0
- package/src/_stories/{ChartBrush.stories.tsx → ChartBrush.smoke.stories.tsx} +13 -4
- package/src/_stories/ChartEditor.Editor.stories.tsx +23 -0
- package/src/_stories/ChartLine.DashedDates.smoke.stories.tsx +23 -0
- package/src/_stories/_mock/alt_text_metadata.json +38 -0
- package/src/_stories/_mock/heatmap-average-age-categorical.json +315 -0
- package/src/_stories/_mock/heatmap.json +156 -0
- package/src/_stories/_mock/line_chart_dashed_dates.json +276 -0
- package/src/_stories/_mock/paired-bar-abbr.json +1 -1
- package/src/_stories/_mock/paired-bar.json +1 -1
- 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.tsx +2 -2
- package/src/components/Annotations/components/AnnotationDropdown.tsx +5 -2
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -2
- package/src/components/Axis/BottomAxis.tsx +1 -1
- package/src/components/Axis/LeftAxis.tsx +15 -14
- package/src/components/Axis/LeftAxisGridlines.tsx +14 -13
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +9 -5
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +9 -5
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +9 -5
- package/src/components/BarChart/components/BarChart.Vertical.tsx +9 -6
- package/src/components/BarChart/components/BarChart.tsx +2 -6
- package/src/components/BarChart/helpers/useBarChart.ts +1 -1
- package/src/components/BoxPlot/BoxPlot.Vertical.tsx +3 -3
- 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 +946 -960
- package/src/components/EditorPanel/EditorPanelContext.ts +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +229 -165
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +105 -13
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +169 -116
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +159 -109
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +14 -28
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -1
- package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +57 -0
- package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +56 -0
- package/src/components/EditorPanel/useEditorPermissions.test.tsx +70 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +32 -21
- package/src/components/Forecasting/Forecasting.tsx +2 -2
- package/src/components/HeatMap/components/HeatMap.tsx +762 -0
- package/src/components/HeatMap/components/HeatMapGradientLegend.tsx +114 -0
- package/src/components/HeatMap/heatmap.constants.ts +13 -0
- package/src/components/HeatMap/heatmap.css +71 -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 +1040 -0
- package/src/components/HeatMap/tests/helpers.test.ts +167 -0
- package/src/components/HorizonChart/HorizonChart.tsx +10 -2
- package/src/components/HoverLine/HoverLine.tsx +6 -8
- package/src/components/Legend/Legend.Component.tsx +4 -4
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +5 -0
- package/src/components/Legend/LegendGroup/LegendGroup.tsx +3 -1
- package/src/components/Legend/helpers/index.ts +0 -2
- package/src/components/LineChart/LineChartProps.ts +1 -0
- package/src/components/LineChart/components/LineChart.BumpCircle.tsx +22 -24
- package/src/components/LineChart/helpers.test.ts +345 -0
- package/src/components/LineChart/helpers.ts +15 -5
- package/src/components/LineChart/index.tsx +18 -5
- package/src/components/LinearChart/VisualizationRenderer.tsx +11 -1
- package/src/components/LinearChart/tests/LinearChart.test.tsx +228 -7
- package/src/components/LinearChart/tests/mockConfigContext.ts +1 -0
- package/src/components/LinearChart.tsx +155 -77
- package/src/components/PairedBarChart.jsx +2 -2
- package/src/components/PieChart/PieChart.tsx +15 -10
- package/src/components/RadarChart/RadarChart.tsx +3 -1
- package/src/components/Regions/components/Regions.tsx +199 -35
- package/src/components/Sankey/components/Sankey.tsx +7 -3
- package/src/components/Sankey/helpers/getSankeyTooltip.tsx +7 -6
- package/src/components/ScatterPlot/ScatterPlot.jsx +19 -17
- package/src/components/SmallMultiples/SmallMultipleTile.tsx +31 -3
- package/src/components/SmallMultiples/SmallMultiples.css +12 -0
- package/src/components/SmallMultiples/SmallMultiples.tsx +15 -9
- package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +152 -0
- package/src/components/Sparkline/components/SparkLine.tsx +5 -10
- package/src/components/WarmingStripes/WarmingStripes.tsx +5 -3
- 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 +197 -0
- package/src/helpers/ensureSpecialChartAxisTypes.ts +11 -0
- package/src/helpers/getMinMax.ts +2 -3
- package/src/helpers/getYAxisAutoPadding.ts +8 -6
- package/src/helpers/getYAxisAutoPaddingMode.ts +17 -0
- package/src/helpers/handleChartAriaLabels.ts +23 -10
- package/src/helpers/seriesHighlight.ts +26 -0
- package/src/helpers/tests/axisLabelFontSize.test.ts +15 -0
- package/src/helpers/tests/bumpChartTooltip.test.ts +75 -0
- package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +30 -0
- package/src/helpers/tests/getYAxisAutoPadding.test.ts +37 -0
- package/src/helpers/tests/getYAxisAutoPaddingMode.test.ts +90 -0
- package/src/helpers/tests/handleChartAriaLabels.test.ts +71 -0
- package/src/helpers/tests/seriesHighlight.test.ts +61 -0
- package/src/helpers/tests/tooltipHelpers.test.ts +151 -0
- package/src/helpers/tooltipHelpers.ts +176 -0
- package/src/hooks/tests/useScales.test.tsx +105 -0
- package/src/hooks/useHighlightedBars.ts +9 -16
- package/src/hooks/useProgrammaticTooltip.ts +16 -2
- package/src/hooks/useScales.ts +19 -9
- package/src/hooks/useSmallMultipleSynchronization.ts +3 -4
- package/src/hooks/useTooltip.tsx +167 -38
- package/src/index.jsx +5 -2
- package/src/scss/main.scss +32 -49
- package/src/test/CdcChart.test.jsx +24 -0
- package/src/types/ChartConfig.ts +19 -0
- package/src/types/ChartContext.ts +2 -0
- package/examples/chart-regression-2.json +0 -2360
- package/examples/feature/__data__/age-adjusted-rates.json +0 -1486
- package/examples/feature/__data__/city-temperature.json +0 -2198
- package/examples/feature/bar/Barchart_with_negative.json +0 -34
- package/examples/feature/boxplot/box-plot-data.json +0 -71
- package/examples/feature/boxplot/boxplot-data.json +0 -156
- package/examples/feature/combo/combochart-categories_are_numbers .json +0 -18
- package/examples/feature/forest-plot/log.json +0 -26
- package/examples/feature/tests-date-exclusions/date-exclusions-data.json +0 -162
- package/examples/region-issue.json +0 -2065
- package/examples/xaxis.json +0 -493
- package/src/_stories/Chart.SmallMultiples.stories.tsx +0 -63
- /package/examples/{data → __data__}/data-with-metadata.json +0 -0
- /package/examples/feature/bar/{tall-data.json → __data__/tall-data.json} +0 -0
- /package/examples/feature/deviation/{planet-deviation-data.json → __data__/planet-deviation-data.json} +0 -0
- /package/examples/feature/paired-bar/{paired-bar-data.json → __data__/paired-bar-data.json} +0 -0
- /package/examples/feature/paired-bar/{paired-bar-formatted.json → __data__/paired-bar-formatted.json} +0 -0
- /package/examples/feature/tests-case-rate/{case-rate-example-data.json → __data__/case-rate-example-data.json} +0 -0
- /package/examples/feature/tests-covid/{covid-example-data-confidence.json → __data__/covid-example-data-confidence.json} +0 -0
- /package/examples/feature/tests-covid/{covid-example-data.json → __data__/covid-example-data.json} +0 -0
- /package/examples/feature/tests-cutoff/{cutoff-example-data.json → __data__/cutoff-example-data.json} +0 -0
- /package/examples/feature/tests-non-numerics/{planet-example-data-nonnumeric.json → __data__/planet-example-data-nonnumeric.json} +0 -0
- /package/src/_stories/{Chart.Anchors.stories.tsx → Chart.Anchors.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.BoxPlot.stories.tsx → Chart.BoxPlot.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.CI.stories.tsx → Chart.CI.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Combo.stories.tsx → Chart.Combo.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.CustomColors.stories.tsx → Chart.CustomColors.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Defaults.stories.tsx → Chart.Defaults.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.DynamicSeries.stories.tsx → Chart.DynamicSeries.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Filters.stories.tsx → Chart.Filters.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Forecast.stories.tsx → Chart.Forecast.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Legend.Gradient.stories.tsx → Chart.Legend.Gradient.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Patterns.stories.tsx → Chart.Patterns.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.PreserveDecimals.stories.tsx → Chart.PreserveDecimals.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Regions.Categorical.stories.tsx → Chart.Regions.Categorical.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Regions.DateScale.stories.tsx → Chart.Regions.DateScale.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.Regions.DateTimeScale.stories.tsx → Chart.Regions.DateTimeScale.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.ScatterPlot.stories.tsx → Chart.ScatterPlot.smoke.stories.tsx} +0 -0
- /package/src/_stories/{Chart.SmallestLeftAxisMax.stories.tsx → Chart.SmallestLeftAxisMax.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartAnnotation.stories.tsx → ChartAnnotation.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartAxisLabels.stories.tsx → ChartAxisLabels.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartBrush.Matrix.Continuous.stories.tsx → ChartBrush.Matrix.Continuous.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartBrush.Matrix.Date.stories.tsx → ChartBrush.Matrix.Date.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartBrush.Matrix.DateTime.stories.tsx → ChartBrush.Matrix.DateTime.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartLine.QuadrantAngles.stories.tsx → ChartLine.QuadrantAngles.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartLine.Suppression.stories.tsx → ChartLine.Suppression.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartLine.Symbols.stories.tsx → ChartLine.Symbols.smoke.stories.tsx} +0 -0
- /package/src/_stories/{ChartPrefixSuffix.stories.tsx → ChartPrefixSuffix.smoke.stories.tsx} +0 -0
- /package/src/_stories/{TechAdoptionWithLinks.stories.tsx → TechAdoptionWithLinks.smoke.stories.tsx} +0 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { createStyles, filterCircles } from './helpers'
|
|
3
|
+
import { PreliminaryDataItem } from '../../types/ChartConfig'
|
|
4
|
+
|
|
5
|
+
describe('LineChart helpers', () => {
|
|
6
|
+
describe('createStyles', () => {
|
|
7
|
+
const mockHandleLineType = vi.fn((style: string) => {
|
|
8
|
+
switch (style) {
|
|
9
|
+
case 'Dashed Small':
|
|
10
|
+
return '5 5'
|
|
11
|
+
case 'Dashed Medium':
|
|
12
|
+
return '10 5'
|
|
13
|
+
case 'Dashed Large':
|
|
14
|
+
return '15 5'
|
|
15
|
+
default:
|
|
16
|
+
return 0
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const basePreliminaryData: PreliminaryDataItem[] = [
|
|
21
|
+
{
|
|
22
|
+
type: 'effect',
|
|
23
|
+
seriesKeys: ['COVID-19'],
|
|
24
|
+
column: 'Attribute',
|
|
25
|
+
value: 'Dotted',
|
|
26
|
+
style: 'Dashed Small',
|
|
27
|
+
label: 'COVID Dotted',
|
|
28
|
+
displayTooltip: true,
|
|
29
|
+
displayLegend: true,
|
|
30
|
+
displayTable: true,
|
|
31
|
+
symbol: '',
|
|
32
|
+
iconCode: '',
|
|
33
|
+
lineCode: '',
|
|
34
|
+
hideBarSymbol: false,
|
|
35
|
+
hideLineStyle: false,
|
|
36
|
+
circleSize: 6,
|
|
37
|
+
displayGray: false
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
type: 'effect',
|
|
41
|
+
seriesKeys: ['Influenza'],
|
|
42
|
+
column: 'Attribute',
|
|
43
|
+
value: 'Dotted',
|
|
44
|
+
style: 'Dashed Medium',
|
|
45
|
+
label: 'Flu Dotted',
|
|
46
|
+
displayTooltip: true,
|
|
47
|
+
displayLegend: true,
|
|
48
|
+
displayTable: true,
|
|
49
|
+
symbol: '',
|
|
50
|
+
iconCode: '',
|
|
51
|
+
lineCode: '',
|
|
52
|
+
hideBarSymbol: false,
|
|
53
|
+
hideLineStyle: false,
|
|
54
|
+
circleSize: 6,
|
|
55
|
+
displayGray: false
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
describe('with dynamic category data (single-row/tall format)', () => {
|
|
60
|
+
const dynamicCategoryData = [
|
|
61
|
+
{ Date: '10/5/2025', Category: 'COVID-19', Value: '43.6', Attribute: 'Dotted' },
|
|
62
|
+
{ Date: '10/12/2025', Category: 'COVID-19', Value: '40.7', Attribute: '' },
|
|
63
|
+
{ Date: '10/19/2025', Category: 'COVID-19', Value: '42.6', Attribute: 'Dotted' }
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
it('should apply dashed style when Attribute matches effect value', () => {
|
|
67
|
+
const styles = createStyles({
|
|
68
|
+
preliminaryData: basePreliminaryData,
|
|
69
|
+
data: dynamicCategoryData,
|
|
70
|
+
stroke: '#000',
|
|
71
|
+
strokeWidth: 2,
|
|
72
|
+
handleLineType: mockHandleLineType,
|
|
73
|
+
lineType: 'solid-line',
|
|
74
|
+
seriesKey: 'COVID-19', // Runtime series key from Category column
|
|
75
|
+
dynamicCategory: 'Category',
|
|
76
|
+
originalSeriesKey: 'Value' // Data value column
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
expect(styles).toHaveLength(3)
|
|
80
|
+
// First point has Attribute="Dotted" - should be dashed
|
|
81
|
+
expect(styles[0].strokeDasharray).toBe('5 5')
|
|
82
|
+
// Second point has Attribute="" - but previous point was dashed, so it inherits
|
|
83
|
+
// Actually, the logic applies dashed to current AND previous point when effect is found
|
|
84
|
+
// Let's check what happens
|
|
85
|
+
expect(styles[1].strokeDasharray).toBe('5 5') // Inherits from next point's effect
|
|
86
|
+
// Third point has Attribute="Dotted" - should be dashed
|
|
87
|
+
expect(styles[2].strokeDasharray).toBe('5 5')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('should apply solid style when no effect matches', () => {
|
|
91
|
+
const dataNoEffects = [
|
|
92
|
+
{ Date: '10/5/2025', Category: 'COVID-19', Value: '43.6', Attribute: '' },
|
|
93
|
+
{ Date: '10/12/2025', Category: 'COVID-19', Value: '40.7', Attribute: '' }
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
const styles = createStyles({
|
|
97
|
+
preliminaryData: basePreliminaryData,
|
|
98
|
+
data: dataNoEffects,
|
|
99
|
+
stroke: '#000',
|
|
100
|
+
strokeWidth: 2,
|
|
101
|
+
handleLineType: mockHandleLineType,
|
|
102
|
+
lineType: 'solid-line',
|
|
103
|
+
seriesKey: 'COVID-19',
|
|
104
|
+
dynamicCategory: 'Category',
|
|
105
|
+
originalSeriesKey: 'Value'
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
expect(styles).toHaveLength(2)
|
|
109
|
+
expect(styles[0].strokeDasharray).toBe(0)
|
|
110
|
+
expect(styles[1].strokeDasharray).toBe(0)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('should only match effects for the correct series', () => {
|
|
114
|
+
// Using Influenza series key with COVID data should not match
|
|
115
|
+
const styles = createStyles({
|
|
116
|
+
preliminaryData: basePreliminaryData,
|
|
117
|
+
data: dynamicCategoryData,
|
|
118
|
+
stroke: '#000',
|
|
119
|
+
strokeWidth: 2,
|
|
120
|
+
handleLineType: mockHandleLineType,
|
|
121
|
+
lineType: 'solid-line',
|
|
122
|
+
seriesKey: 'Influenza', // Different series
|
|
123
|
+
dynamicCategory: 'Category',
|
|
124
|
+
originalSeriesKey: 'Value'
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// No effect should match since seriesKeys is ['COVID-19'] not ['Influenza']
|
|
128
|
+
// (assuming we use the COVID effect config)
|
|
129
|
+
expect(styles.every(s => s.strokeDasharray === 0 || s.strokeDasharray === '10 5')).toBe(true)
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
describe('with multi-column data (standard format)', () => {
|
|
134
|
+
const multiColumnPreliminaryData: PreliminaryDataItem[] = [
|
|
135
|
+
{
|
|
136
|
+
type: 'effect',
|
|
137
|
+
seriesKeys: ['COVID-19'],
|
|
138
|
+
column: 'COVID-19-Attribute',
|
|
139
|
+
value: 'Dotted',
|
|
140
|
+
style: 'Dashed Small',
|
|
141
|
+
label: 'COVID Dotted',
|
|
142
|
+
displayTooltip: true,
|
|
143
|
+
displayLegend: true,
|
|
144
|
+
displayTable: true,
|
|
145
|
+
symbol: '',
|
|
146
|
+
iconCode: '',
|
|
147
|
+
lineCode: '',
|
|
148
|
+
hideBarSymbol: false,
|
|
149
|
+
hideLineStyle: false,
|
|
150
|
+
circleSize: 6,
|
|
151
|
+
displayGray: false
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
const multiColumnData = [
|
|
156
|
+
{ Date: '10/5/2025', 'COVID-19': '43.6', 'COVID-19-Attribute': 'Dotted' },
|
|
157
|
+
{ Date: '10/12/2025', 'COVID-19': '40.7', 'COVID-19-Attribute': '' },
|
|
158
|
+
{ Date: '10/19/2025', 'COVID-19': '42.6', 'COVID-19-Attribute': 'Dotted' }
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
it('should apply dashed style when attribute column matches', () => {
|
|
162
|
+
const styles = createStyles({
|
|
163
|
+
preliminaryData: multiColumnPreliminaryData,
|
|
164
|
+
data: multiColumnData,
|
|
165
|
+
stroke: '#000',
|
|
166
|
+
strokeWidth: 2,
|
|
167
|
+
handleLineType: mockHandleLineType,
|
|
168
|
+
lineType: 'solid-line',
|
|
169
|
+
seriesKey: 'COVID-19',
|
|
170
|
+
dynamicCategory: '', // No dynamic category
|
|
171
|
+
originalSeriesKey: ''
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
expect(styles).toHaveLength(3)
|
|
175
|
+
// First point has attribute - dashed
|
|
176
|
+
expect(styles[0].strokeDasharray).toBe('5 5')
|
|
177
|
+
// Second point inherits from third point's effect
|
|
178
|
+
expect(styles[1].strokeDasharray).toBe('5 5')
|
|
179
|
+
// Third point has attribute - dashed
|
|
180
|
+
expect(styles[2].strokeDasharray).toBe('5 5')
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
describe('filterCircles', () => {
|
|
186
|
+
const circlesPreliminaryData: PreliminaryDataItem[] = [
|
|
187
|
+
{
|
|
188
|
+
type: 'effect',
|
|
189
|
+
seriesKeys: ['COVID-19'],
|
|
190
|
+
column: 'Attribute',
|
|
191
|
+
value: 'Marked',
|
|
192
|
+
style: 'Open Circles',
|
|
193
|
+
label: 'COVID Marked',
|
|
194
|
+
displayTooltip: true,
|
|
195
|
+
displayLegend: true,
|
|
196
|
+
displayTable: true,
|
|
197
|
+
symbol: '',
|
|
198
|
+
iconCode: '',
|
|
199
|
+
lineCode: '',
|
|
200
|
+
hideBarSymbol: false,
|
|
201
|
+
hideLineStyle: false,
|
|
202
|
+
circleSize: 8,
|
|
203
|
+
displayGray: false
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
type: 'effect',
|
|
207
|
+
seriesKeys: ['Influenza'],
|
|
208
|
+
column: 'Attribute',
|
|
209
|
+
value: 'Marked',
|
|
210
|
+
style: 'Filled Circles',
|
|
211
|
+
label: 'Flu Marked',
|
|
212
|
+
displayTooltip: true,
|
|
213
|
+
displayLegend: true,
|
|
214
|
+
displayTable: true,
|
|
215
|
+
symbol: '',
|
|
216
|
+
iconCode: '',
|
|
217
|
+
lineCode: '',
|
|
218
|
+
hideBarSymbol: false,
|
|
219
|
+
hideLineStyle: false,
|
|
220
|
+
circleSize: 6,
|
|
221
|
+
displayGray: false
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
describe('with dynamic category data', () => {
|
|
226
|
+
const dynamicData = [
|
|
227
|
+
{ Date: '10/5/2025', Category: 'COVID-19', Value: '43.6', Attribute: 'Marked' },
|
|
228
|
+
{ Date: '10/12/2025', Category: 'COVID-19', Value: '40.7', Attribute: '' },
|
|
229
|
+
{ Date: '10/19/2025', Category: 'COVID-19', Value: '42.6', Attribute: 'Marked' }
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
it('should find open circles for matching dynamic category data', () => {
|
|
233
|
+
const circles = filterCircles(
|
|
234
|
+
circlesPreliminaryData,
|
|
235
|
+
dynamicData,
|
|
236
|
+
'COVID-19', // Runtime series key
|
|
237
|
+
'Category', // Dynamic category column
|
|
238
|
+
'Value' // Value column for data existence check
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
expect(circles).toHaveLength(2) // Two data points with Attribute='Marked'
|
|
242
|
+
expect(circles[0].isFilled).toBe(false) // Open circles
|
|
243
|
+
expect(circles[0].size).toBe(8)
|
|
244
|
+
expect(circles[1].isFilled).toBe(false)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('should return empty array when no matches', () => {
|
|
248
|
+
const circles = filterCircles(
|
|
249
|
+
circlesPreliminaryData,
|
|
250
|
+
dynamicData,
|
|
251
|
+
'RSV', // Series not in preliminaryData
|
|
252
|
+
'Category',
|
|
253
|
+
'Value'
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
expect(circles).toHaveLength(0)
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
describe('with multi-column data (standard format)', () => {
|
|
261
|
+
const multiColumnCirclesConfig: PreliminaryDataItem[] = [
|
|
262
|
+
{
|
|
263
|
+
type: 'effect',
|
|
264
|
+
seriesKeys: ['COVID-19'],
|
|
265
|
+
column: 'COVID-19-Attribute',
|
|
266
|
+
value: 'Marked',
|
|
267
|
+
style: 'Open Circles',
|
|
268
|
+
label: 'COVID Marked',
|
|
269
|
+
displayTooltip: true,
|
|
270
|
+
displayLegend: true,
|
|
271
|
+
displayTable: true,
|
|
272
|
+
symbol: '',
|
|
273
|
+
iconCode: '',
|
|
274
|
+
lineCode: '',
|
|
275
|
+
hideBarSymbol: false,
|
|
276
|
+
hideLineStyle: false,
|
|
277
|
+
circleSize: 8,
|
|
278
|
+
displayGray: false
|
|
279
|
+
}
|
|
280
|
+
]
|
|
281
|
+
|
|
282
|
+
const multiColumnData = [
|
|
283
|
+
{ Date: '10/5/2025', 'COVID-19': '43.6', 'COVID-19-Attribute': 'Marked' },
|
|
284
|
+
{ Date: '10/12/2025', 'COVID-19': '40.7', 'COVID-19-Attribute': '' },
|
|
285
|
+
{ Date: '10/19/2025', 'COVID-19': '42.6', 'COVID-19-Attribute': 'Marked' }
|
|
286
|
+
]
|
|
287
|
+
|
|
288
|
+
it('should find circles in standard multi-column format', () => {
|
|
289
|
+
const circles = filterCircles(
|
|
290
|
+
multiColumnCirclesConfig,
|
|
291
|
+
multiColumnData,
|
|
292
|
+
'COVID-19'
|
|
293
|
+
// No dynamicCategory or originalSeriesKey - standard mode
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
expect(circles).toHaveLength(2)
|
|
297
|
+
expect(circles[0].isFilled).toBe(false)
|
|
298
|
+
expect(circles[0].size).toBe(8)
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it('should be backwards compatible with 3-argument call', () => {
|
|
302
|
+
// Existing code that doesn't pass dynamicCategory should still work
|
|
303
|
+
const circles = filterCircles(multiColumnCirclesConfig, multiColumnData, 'COVID-19')
|
|
304
|
+
|
|
305
|
+
expect(circles).toHaveLength(2)
|
|
306
|
+
})
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
describe('filled circles', () => {
|
|
310
|
+
const filledCirclesConfig: PreliminaryDataItem[] = [
|
|
311
|
+
{
|
|
312
|
+
type: 'effect',
|
|
313
|
+
seriesKeys: ['Influenza'],
|
|
314
|
+
column: 'Attribute',
|
|
315
|
+
value: 'Marked',
|
|
316
|
+
style: 'Filled Circles',
|
|
317
|
+
label: 'Flu Marked',
|
|
318
|
+
displayTooltip: true,
|
|
319
|
+
displayLegend: true,
|
|
320
|
+
displayTable: true,
|
|
321
|
+
symbol: '',
|
|
322
|
+
iconCode: '',
|
|
323
|
+
lineCode: '',
|
|
324
|
+
hideBarSymbol: false,
|
|
325
|
+
hideLineStyle: false,
|
|
326
|
+
circleSize: 6,
|
|
327
|
+
displayGray: false
|
|
328
|
+
}
|
|
329
|
+
]
|
|
330
|
+
|
|
331
|
+
it('should find filled circles for dynamic category data', () => {
|
|
332
|
+
const data = [
|
|
333
|
+
{ Date: '10/5/2025', Category: 'Influenza', Value: '23.1', Attribute: 'Marked' },
|
|
334
|
+
{ Date: '10/12/2025', Category: 'Influenza', Value: '31.7', Attribute: '' }
|
|
335
|
+
]
|
|
336
|
+
|
|
337
|
+
const circles = filterCircles(filledCirclesConfig, data, 'Influenza', 'Category', 'Value')
|
|
338
|
+
|
|
339
|
+
expect(circles).toHaveLength(1)
|
|
340
|
+
expect(circles[0].isFilled).toBe(true)
|
|
341
|
+
expect(circles[0].size).toBe(6)
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
})
|
|
345
|
+
})
|
|
@@ -15,17 +15,18 @@ export const createStyles = (props: StyleProps): Style[] => {
|
|
|
15
15
|
originalSeriesKey
|
|
16
16
|
} = props
|
|
17
17
|
|
|
18
|
-
const dynamicSeriesKey = dynamicCategory ? originalSeriesKey : seriesKey
|
|
19
18
|
const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(
|
|
20
19
|
pd => pd.seriesKeys?.length && pd.column && pd.value && pd.type && pd.style && pd.type === 'effect'
|
|
21
20
|
)
|
|
22
21
|
const isEffectLine = (pd, dataPoint) => {
|
|
23
22
|
if (dynamicCategory) {
|
|
23
|
+
// In dynamic category mode, check the attribute column (pd.column) for the effect marker
|
|
24
|
+
// seriesKey is the runtime series key derived from the category column
|
|
24
25
|
return (
|
|
25
26
|
pd.type === 'effect' &&
|
|
26
27
|
pd.style !== 'Open Circles' &&
|
|
27
28
|
pd.seriesKeys.includes(seriesKey) &&
|
|
28
|
-
|
|
29
|
+
dataPoint[pd.column] === pd.value
|
|
29
30
|
)
|
|
30
31
|
} else {
|
|
31
32
|
return (
|
|
@@ -67,8 +68,17 @@ export const createStyles = (props: StyleProps): Style[] => {
|
|
|
67
68
|
export const filterCircles = (
|
|
68
69
|
preliminaryData: PreliminaryDataItem[],
|
|
69
70
|
data: DataItem[],
|
|
70
|
-
seriesKey: string
|
|
71
|
+
seriesKey: string,
|
|
72
|
+
dynamicCategory?: string,
|
|
73
|
+
originalSeriesKey?: string
|
|
71
74
|
): DataItem[] => {
|
|
75
|
+
// In dynamic category mode:
|
|
76
|
+
// - seriesKey is the runtime series key for matching against seriesKeys
|
|
77
|
+
// - originalSeriesKey is the data value column (e.g., "Value") for checking if data exists
|
|
78
|
+
// In standard mode:
|
|
79
|
+
// - seriesKey is used for both purposes
|
|
80
|
+
const valueKey = dynamicCategory ? originalSeriesKey : seriesKey
|
|
81
|
+
|
|
72
82
|
// Filter and map preliminaryData to get circlesFiltered
|
|
73
83
|
const circlesFiltered = preliminaryData
|
|
74
84
|
?.filter(item => item.style.includes('Circles') && item.type === 'effect' && item.seriesKeys?.length)
|
|
@@ -86,7 +96,7 @@ export const filterCircles = (
|
|
|
86
96
|
if (
|
|
87
97
|
item[fc.column] === fc.value &&
|
|
88
98
|
fc.seriesKeys.includes(seriesKey) &&
|
|
89
|
-
item[
|
|
99
|
+
item[valueKey] &&
|
|
90
100
|
fc.style === 'Open Circles'
|
|
91
101
|
) {
|
|
92
102
|
const result = {
|
|
@@ -99,7 +109,7 @@ export const filterCircles = (
|
|
|
99
109
|
if (
|
|
100
110
|
(!fc.value || item[fc.column] === fc.value) &&
|
|
101
111
|
fc.seriesKeys.includes(seriesKey) &&
|
|
102
|
-
item[
|
|
112
|
+
item[valueKey] &&
|
|
103
113
|
fc.style === 'Filled Circles'
|
|
104
114
|
) {
|
|
105
115
|
const result = {
|
|
@@ -36,6 +36,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
36
36
|
xScale,
|
|
37
37
|
yMax,
|
|
38
38
|
yScale,
|
|
39
|
+
yAxisWidth,
|
|
39
40
|
} = props
|
|
40
41
|
|
|
41
42
|
// prettier-ignore
|
|
@@ -55,7 +56,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
55
56
|
|
|
56
57
|
return (
|
|
57
58
|
<ErrorBoundary component='LineChart'>
|
|
58
|
-
<Group left={
|
|
59
|
+
<Group left={yAxisWidth} className='line-chart-group'>
|
|
59
60
|
{/* left - expects a number not a string */}
|
|
60
61
|
{(config.runtime.lineSeriesKeys || config.runtime.seriesKeys).map((seriesKey, index) => {
|
|
61
62
|
const seriesData = config.runtime.series.find(item => item.dataKey === seriesKey)
|
|
@@ -79,7 +80,13 @@ const LineChart = (props: LineChartProps) => {
|
|
|
79
80
|
const _data = seriesData.dynamicCategory
|
|
80
81
|
? data.filter(d => d[seriesData.dynamicCategory] === seriesKey)
|
|
81
82
|
: data
|
|
82
|
-
const circleData = filterCircles(
|
|
83
|
+
const circleData = filterCircles(
|
|
84
|
+
config?.preliminaryData,
|
|
85
|
+
tableData,
|
|
86
|
+
seriesKey,
|
|
87
|
+
seriesData.dynamicCategory,
|
|
88
|
+
_seriesKey
|
|
89
|
+
)
|
|
83
90
|
|
|
84
91
|
// Prepare sorted data for line and area rendering
|
|
85
92
|
const sortedData =
|
|
@@ -264,7 +271,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
264
271
|
}
|
|
265
272
|
styles={createStyles({
|
|
266
273
|
preliminaryData: config.preliminaryData,
|
|
267
|
-
data:
|
|
274
|
+
data: _data,
|
|
268
275
|
stroke: colorScale(config.runtime.seriesLabels[seriesKey]),
|
|
269
276
|
strokeWidth: seriesData.weight || 2,
|
|
270
277
|
handleLineType,
|
|
@@ -496,7 +503,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
496
503
|
</Text>
|
|
497
504
|
)}
|
|
498
505
|
</Group>
|
|
499
|
-
<Group left={
|
|
506
|
+
<Group left={yAxisWidth} className='glyph-tooltip-group'>
|
|
500
507
|
<Group key={`tooltip-group`} display={'block'}>
|
|
501
508
|
{/* tooltips */}
|
|
502
509
|
<Bar
|
|
@@ -543,7 +550,13 @@ const LineChart = (props: LineChartProps) => {
|
|
|
543
550
|
</Group>
|
|
544
551
|
</Group>
|
|
545
552
|
{config.visualizationType === 'Bump Chart' && (
|
|
546
|
-
<LineChartBumpCircle
|
|
553
|
+
<LineChartBumpCircle
|
|
554
|
+
config={config}
|
|
555
|
+
xScale={xScale}
|
|
556
|
+
yScale={yScale}
|
|
557
|
+
parseDate={parseDate}
|
|
558
|
+
yAxisWidth={yAxisWidth}
|
|
559
|
+
/>
|
|
547
560
|
)}
|
|
548
561
|
</ErrorBoundary>
|
|
549
562
|
)
|
|
@@ -76,13 +76,16 @@ const VisualizationRenderer: React.FC<VisualizationRendererProps> = ({
|
|
|
76
76
|
)}
|
|
77
77
|
|
|
78
78
|
{/* Paired Bar Chart */}
|
|
79
|
-
{visualizationType === 'Paired Bar' &&
|
|
79
|
+
{visualizationType === 'Paired Bar' && (
|
|
80
|
+
<PairedBarChart originalWidth={parentWidth} width={xMax} height={yMax} yAxisWidth={yAxisWidth} />
|
|
81
|
+
)}
|
|
80
82
|
|
|
81
83
|
{/* Scatter Plot */}
|
|
82
84
|
{visualizationType === 'Scatter Plot' && (
|
|
83
85
|
<ScatterPlot
|
|
84
86
|
xScale={xScale}
|
|
85
87
|
yScale={yScale}
|
|
88
|
+
yAxisWidth={yAxisWidth}
|
|
86
89
|
getXAxisData={getXAxisData}
|
|
87
90
|
getYAxisData={getYAxisData}
|
|
88
91
|
xMax={xMax}
|
|
@@ -102,6 +105,7 @@ const VisualizationRenderer: React.FC<VisualizationRendererProps> = ({
|
|
|
102
105
|
yScale={yScale}
|
|
103
106
|
xMax={xMax}
|
|
104
107
|
yMax={yMax}
|
|
108
|
+
yAxisWidth={yAxisWidth}
|
|
105
109
|
synchronizedXValue={synchronizedXValue}
|
|
106
110
|
showTooltip={showTooltip}
|
|
107
111
|
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
@@ -115,6 +119,7 @@ const VisualizationRenderer: React.FC<VisualizationRendererProps> = ({
|
|
|
115
119
|
yScale={yScale}
|
|
116
120
|
xMax={xMax}
|
|
117
121
|
yMax={yMax}
|
|
122
|
+
yAxisWidth={yAxisWidth}
|
|
118
123
|
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
119
124
|
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
120
125
|
tooltipData={tooltipData}
|
|
@@ -132,6 +137,7 @@ const VisualizationRenderer: React.FC<VisualizationRendererProps> = ({
|
|
|
132
137
|
max={max}
|
|
133
138
|
xScale={xScale}
|
|
134
139
|
yScale={yScale}
|
|
140
|
+
yAxisWidth={yAxisWidth}
|
|
135
141
|
/>
|
|
136
142
|
)}
|
|
137
143
|
|
|
@@ -156,6 +162,7 @@ const VisualizationRenderer: React.FC<VisualizationRendererProps> = ({
|
|
|
156
162
|
yScale={yScale}
|
|
157
163
|
yMax={yMax}
|
|
158
164
|
xMax={xMax}
|
|
165
|
+
yAxisWidth={yAxisWidth}
|
|
159
166
|
chartRef={svgRef}
|
|
160
167
|
width={xMax}
|
|
161
168
|
height={yMax}
|
|
@@ -193,6 +200,7 @@ const VisualizationRenderer: React.FC<VisualizationRendererProps> = ({
|
|
|
193
200
|
<LineChart
|
|
194
201
|
xScale={xScale}
|
|
195
202
|
yScale={yScale}
|
|
203
|
+
yAxisWidth={yAxisWidth}
|
|
196
204
|
getXAxisData={getXAxisData}
|
|
197
205
|
getYAxisData={getYAxisData}
|
|
198
206
|
xMax={xMax}
|
|
@@ -211,6 +219,7 @@ const VisualizationRenderer: React.FC<VisualizationRendererProps> = ({
|
|
|
211
219
|
<LineChart
|
|
212
220
|
xScale={xScale}
|
|
213
221
|
yScale={yScale}
|
|
222
|
+
yAxisWidth={yAxisWidth}
|
|
214
223
|
getXAxisData={getXAxisData}
|
|
215
224
|
getYAxisData={getYAxisData}
|
|
216
225
|
xMax={xMax}
|
|
@@ -229,6 +238,7 @@ const VisualizationRenderer: React.FC<VisualizationRendererProps> = ({
|
|
|
229
238
|
tooltipData={tooltipData}
|
|
230
239
|
xScale={xScale}
|
|
231
240
|
yScale={yScale}
|
|
241
|
+
yAxisWidth={yAxisWidth}
|
|
232
242
|
width={xMax}
|
|
233
243
|
height={yMax}
|
|
234
244
|
xScaleNoPadding={xScaleNoPadding}
|