@cdc/chart 4.25.10 → 4.25.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/{cdcchart-1a1724a1.es.js → cdcchart-dgT_1dIT.es.js} +136 -151
  2. package/dist/cdcchart.js +36258 -34658
  3. package/examples/feature/__data__/planet-example-data.json +1 -1
  4. package/examples/feature/boxplot/valid-boxplot.csv +38 -17
  5. package/examples/private/DEV-11825.json +573 -0
  6. package/examples/private/na.json +913 -0
  7. package/examples/private/test-data.csv +28 -0
  8. package/index.html +2 -121
  9. package/package.json +4 -4
  10. package/src/CdcChart.tsx +8 -11
  11. package/src/CdcChartComponent.tsx +256 -87
  12. package/src/_stories/Chart.Combo.stories.tsx +18 -0
  13. package/src/_stories/Chart.Forecast.stories.tsx +36 -0
  14. package/src/_stories/Chart.HTMLInDataTable.stories.tsx +520 -0
  15. package/src/_stories/Chart.Patterns.stories.tsx +2 -1
  16. package/src/_stories/Chart.PreserveDecimals.stories.tsx +220 -0
  17. package/src/_stories/Chart.SmallMultiples.stories.tsx +47 -0
  18. package/src/_stories/ChartAnnotation.stories.tsx +6 -3
  19. package/src/_stories/ChartBar.Editor.stories.tsx +3580 -0
  20. package/src/_stories/ChartEditor.Editor.stories.tsx +658 -0
  21. package/src/_stories/ChartEditor.stories.tsx +1 -2
  22. package/src/_stories/_mock/combo.json +451 -0
  23. package/src/_stories/_mock/editor-test-configs.json +376 -0
  24. package/src/_stories/_mock/editor-test-datasets.json +477 -0
  25. package/src/_stories/_mock/editor-tests/bar-chart-editor-test.json +255 -0
  26. package/src/_stories/_mock/editor-tests/bar-chart-general-test.json +267 -0
  27. package/src/_stories/_mock/editor-tests/bar-chart-test.json +237 -0
  28. package/src/_stories/_mock/forecast_combo_with_gaps.json +913 -0
  29. package/src/_stories/_mock/pie_config.json +257 -62
  30. package/src/_stories/_mock/small_multiples/small_multiples_bars.json +1944 -0
  31. package/src/_stories/_mock/small_multiples/small_multiples_big_data_bars.json +1114 -0
  32. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +2646 -0
  33. package/src/_stories/_mock/small_multiples/small_multiples_lines_colors.json +1305 -0
  34. package/src/_stories/_mock/small_multiples/small_multiples_stacked_bars.json +1936 -0
  35. package/src/components/Annotations/components/findNearestDatum.ts +6 -41
  36. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -6
  37. package/src/components/AreaChart/index.tsx +1 -2
  38. package/src/components/BarChart/components/BarChart.Horizontal.tsx +4 -4
  39. package/src/components/BarChart/components/BarChart.Vertical.tsx +3 -2
  40. package/src/components/BoxPlot/helpers/index.ts +3 -3
  41. package/src/components/Brush/BrushChart.tsx +1 -1
  42. package/src/components/EditorPanel/EditorPanel.tsx +199 -190
  43. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +96 -111
  44. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -1
  45. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +102 -55
  46. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +54 -49
  47. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +422 -0
  48. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +75 -21
  49. package/src/components/EditorPanel/components/Panels/index.tsx +3 -1
  50. package/src/components/EditorPanel/editor-panel.scss +0 -20
  51. package/src/components/EditorPanel/useEditorPermissions.ts +7 -15
  52. package/src/components/Forecasting/Forecasting.tsx +139 -21
  53. package/src/components/Legend/Legend.Component.tsx +16 -9
  54. package/src/components/Legend/helpers/createFormatLabels.tsx +181 -181
  55. package/src/components/Legend/helpers/getLegendClasses.ts +0 -1
  56. package/src/components/LineChart/LineChartProps.ts +0 -3
  57. package/src/components/LineChart/helpers.ts +1 -1
  58. package/src/components/LineChart/index.tsx +36 -13
  59. package/src/components/LinearChart.tsx +75 -80
  60. package/src/components/Regions/components/Regions.tsx +3 -24
  61. package/src/components/Sankey/types/index.ts +1 -1
  62. package/src/components/SmallMultiples/SmallMultipleTile.tsx +198 -0
  63. package/src/components/SmallMultiples/SmallMultiples.css +32 -0
  64. package/src/components/SmallMultiples/SmallMultiples.tsx +271 -0
  65. package/src/components/SmallMultiples/index.ts +2 -0
  66. package/src/data/initial-state.js +13 -1
  67. package/src/helpers/buildForecastPaletteOptions.ts +0 -38
  68. package/src/helpers/getColorScale.ts +10 -0
  69. package/src/{hooks/useMinMax.ts → helpers/getMinMax.ts} +14 -7
  70. package/src/helpers/getYAxisAutoPadding.ts +53 -0
  71. package/src/helpers/smallMultiplesHelpers.ts +529 -0
  72. package/src/hooks/useProgrammaticTooltip.ts +96 -0
  73. package/src/hooks/useScales.ts +88 -34
  74. package/src/hooks/useSmallMultipleSynchronization.ts +59 -0
  75. package/src/hooks/useTooltip.tsx +60 -15
  76. package/src/scss/main.scss +1 -80
  77. package/src/store/chart.actions.ts +2 -0
  78. package/src/store/chart.reducer.ts +4 -0
  79. package/src/types/ChartConfig.ts +24 -6
  80. package/src/types/ChartContext.ts +3 -0
  81. package/src/_stories/_mock/pie_data.json +0 -218
  82. package/src/components/AreaChart/components/AreaChart.jsx +0 -109
  83. package/src/helpers/sort.ts +0 -7
  84. package/src/hooks/useActiveElement.js +0 -19
  85. package/src/hooks/useChartClasses.js +0 -41
@@ -20,6 +20,7 @@ import InputToggle from '@cdc/core/components/inputs/InputToggle'
20
20
  import { useColorPalette } from '@cdc/core/hooks/useColorPalette'
21
21
  import { getCurrentPaletteName } from '@cdc/core/helpers/palettes/utils'
22
22
  import { getColorPaletteVersion } from '@cdc/core/helpers/getColorPaletteVersion'
23
+ import { isCoveDeveloperMode } from '@cdc/core/helpers/queryStringUtils'
23
24
  import { ChartContext } from './../../../../types/ChartContext.js'
24
25
 
25
26
  import { useEditorPermissions } from '../../useEditorPermissions.js'
@@ -28,6 +29,10 @@ import ConfigContext from '../../../../ConfigContext.js'
28
29
  import { PanelProps } from '../PanelProps'
29
30
  import { LineChartConfig } from '../../../../types/ChartConfig'
30
31
  import { PaletteSelector, DeveloperPaletteRollback } from '@cdc/core/components/PaletteSelector'
32
+ import { HeaderThemeSelector } from '@cdc/core/components/HeaderThemeSelector'
33
+ import { CustomColorsEditor } from '@cdc/core/components/CustomColorsEditor'
34
+ import { getColorScale } from '../../../../helpers/getColorScale'
35
+ import '@cdc/core/styles/v2/components/editor.scss'
31
36
  import './panelVisual.styles.css'
32
37
 
33
38
  const PanelVisual: FC<PanelProps> = props => {
@@ -40,7 +45,6 @@ const PanelVisual: FC<PanelProps> = props => {
40
45
  visHasBarBorders,
41
46
  visCanAnimate,
42
47
  visSupportsNonSequentialPallete,
43
- headerColors,
44
48
  visSupportsTooltipOpacity,
45
49
  visSupportsTooltipLines,
46
50
  visSupportsBarSpace,
@@ -86,7 +90,7 @@ const PanelVisual: FC<PanelProps> = props => {
86
90
  }
87
91
 
88
92
  return (
89
- <AccordionItem>
93
+ <AccordionItem className='panel-visual'>
90
94
  <AccordionItemHeading>
91
95
  <AccordionItemButton>Visual</AccordionItemButton>
92
96
  </AccordionItemHeading>
@@ -247,24 +251,7 @@ const PanelVisual: FC<PanelProps> = props => {
247
251
  />
248
252
  </>
249
253
  )}
250
- {/* eslint-disable */}
251
- <label className='header'>
252
- <span className='edit-label'>Header Theme</span>
253
- <ul className='color-palette'>
254
- {headerColors.map(palette => (
255
- <button
256
- title={palette}
257
- key={palette}
258
- onClick={e => {
259
- e.preventDefault()
260
- updateConfig({ ...config, theme: palette })
261
- }}
262
- className={config.theme === palette ? 'selected ' + palette : palette}
263
- ></button>
264
- ))}
265
- </ul>
266
- </label>
267
- {/* eslint-enable */}
254
+ <HeaderThemeSelector selectedTheme={config.theme} onThemeSelect={theme => updateConfig({ ...config, theme })} />
268
255
  {(visSupportsNonSequentialPallete() || visSupportsNonSequentialPallete()) && (
269
256
  <>
270
257
  <label>
@@ -344,6 +331,74 @@ const PanelVisual: FC<PanelProps> = props => {
344
331
  />
345
332
  </>
346
333
  )}
334
+
335
+ {isCoveDeveloperMode() && (visSupportsSequentialPallete() || visSupportsNonSequentialPallete()) && (
336
+ <>
337
+ <div className='mt-3'>
338
+ <label className='checkbox'>
339
+ <input
340
+ type='checkbox'
341
+ checked={
342
+ !!(config.general?.palette?.customColorsOrdered || config.general?.palette?.customColors)
343
+ }
344
+ onChange={e => {
345
+ const _state = cloneConfig(config)
346
+ // Ensure palette object exists
347
+ if (!_state.general.palette) {
348
+ _state.general.palette = {}
349
+ }
350
+ if (e.target.checked) {
351
+ // Extract colors from current color scale if runtime data available
352
+ if (config.runtime?.seriesLabelsAll && config.runtime.seriesLabelsAll.length > 0) {
353
+ const colorScale = getColorScale(config)
354
+ const extractedColors = config.runtime.seriesLabelsAll.map((label: string) =>
355
+ colorScale(label)
356
+ )
357
+ _state.general.palette.customColorsOrdered =
358
+ extractedColors.length > 0
359
+ ? extractedColors
360
+ : ['#3366cc', '#5588dd', '#77aaee', '#99ccff']
361
+ } else {
362
+ // Fallback to default colors if runtime not available
363
+ _state.general.palette.customColorsOrdered = ['#3366cc', '#5588dd', '#77aaee', '#99ccff']
364
+ }
365
+ } else {
366
+ // Remove custom colors and revert to default palette
367
+ delete _state.general.palette.customColorsOrdered
368
+ delete _state.general.palette.customColors
369
+ // Set default palette if none exists
370
+ if (!_state.general.palette.name) {
371
+ _state.general.palette.name = 'qualitative_standard'
372
+ _state.general.palette.version = '2.0'
373
+ }
374
+ }
375
+ updateConfig(_state)
376
+ }}
377
+ />
378
+ Use Custom Colors
379
+ </label>
380
+ </div>
381
+
382
+ {(config.general?.palette?.customColorsOrdered || config.general?.palette?.customColors) && (
383
+ <div className='mt-2'>
384
+ <CustomColorsEditor
385
+ colors={config.general.palette.customColorsOrdered || config.general.palette.customColors}
386
+ onChange={newColors => {
387
+ const _state = cloneConfig(config)
388
+ if (!_state.general.palette) {
389
+ _state.general.palette = {}
390
+ }
391
+ _state.general.palette.customColorsOrdered = newColors
392
+ updateConfig(_state)
393
+ }}
394
+ label='Custom Color Order'
395
+ minColors={1}
396
+ maxColors={20}
397
+ />
398
+ </div>
399
+ )}
400
+ </>
401
+ )}
347
402
  </>
348
403
  )}
349
404
  {config.visualizationType === 'Sankey' && (
@@ -443,7 +498,6 @@ const PanelVisual: FC<PanelProps> = props => {
443
498
  value={config.dataCutoff}
444
499
  type='number'
445
500
  fieldName='dataCutoff'
446
- className='number-narrow'
447
501
  label='Data Cutoff'
448
502
  updateField={updateField}
449
503
  tooltip={
@@ -7,6 +7,7 @@ import Visual from './Panel.Visual'
7
7
  import Sankey from './Panel.Sankey'
8
8
  import Annotate from './Panel.Annotate'
9
9
  import PatternSettings from './Panel.PatternSettings'
10
+ import SmallMultiples from './Panel.SmallMultiples'
10
11
 
11
12
  const Panels = {
12
13
  ForestPlot: ForestPlotSettings,
@@ -17,7 +18,8 @@ const Panels = {
17
18
  Visual,
18
19
  Sankey,
19
20
  Annotate,
20
- PatternSettings
21
+ PatternSettings,
22
+ SmallMultiples
21
23
  }
22
24
 
23
25
  export default Panels
@@ -7,26 +7,6 @@
7
7
  text-align: left;
8
8
 
9
9
  span {
10
- display: inline-block;
11
- float: right;
12
- }
13
- }
14
-
15
- .edit-block {
16
- margin-top: 0;
17
- padding: 1em;
18
- }
19
- }
20
-
21
- .viewport-overrides {
22
- button {
23
- width: 100%;
24
- padding: 1em;
25
- margin-top: 1em;
26
- text-align: left;
27
-
28
- span {
29
- display: inline-block;
30
10
  float: right;
31
11
  }
32
12
  }
@@ -22,20 +22,6 @@ export const useEditorPermissions = () => {
22
22
  'Scatter Plot',
23
23
  'Spark Line',
24
24
  'Sankey'
25
- ]
26
-
27
- const headerColors = [
28
- 'theme-blue',
29
- 'theme-purple',
30
- 'theme-brown',
31
- 'theme-teal',
32
- 'theme-pink',
33
- 'theme-orange',
34
- 'theme-slate',
35
- 'theme-indigo',
36
- 'theme-cyan',
37
- 'theme-green',
38
- 'theme-amber'
39
25
  ]
40
26
 
41
27
  const visSupportsDateCategoryAxis = () => {
@@ -383,6 +369,12 @@ export const useEditorPermissions = () => {
383
369
  )
384
370
  }
385
371
 
372
+ const visSupportsSmallMultiples = () => {
373
+ const enabledCharts = ['Line', 'Bar', 'Area Chart', 'Combo', 'Box Plot', 'Scatter Plot']
374
+ if (enabledCharts.includes(visualizationType)) return true
375
+ return false
376
+ }
377
+
386
378
  const visSupportsYPadding = () => {
387
379
  return !config.yAxis.inlineLabel || !config.yAxis.inlineLabel?.includes(' ')
388
380
  }
@@ -411,7 +403,6 @@ export const useEditorPermissions = () => {
411
403
 
412
404
  return {
413
405
  enabledChartTypes,
414
- headerColors,
415
406
  visCanAnimate,
416
407
  visHasAnchors,
417
408
  visHasBarBorders,
@@ -460,6 +451,7 @@ export const useEditorPermissions = () => {
460
451
  visSupportsValueAxisMax,
461
452
  visSupportsValueAxisMin,
462
453
  visSupportsDynamicSeries,
454
+ visSupportsSmallMultiples,
463
455
  visSupportsYPadding,
464
456
  visHasSingleSeriesTooltip,
465
457
  visHasCategoricalAxis
@@ -14,6 +14,102 @@ import { curveMonotoneX } from '@visx/curve'
14
14
  import { Bar, Area, LinePath } from '@visx/shape'
15
15
  import { Group } from '@visx/group'
16
16
 
17
+ // Helper function to check if a value is numeric/calculable
18
+ const isCalculable = (value: any): boolean => {
19
+ if (value === null || value === undefined || value === '' || value === 'NA') return false
20
+ const num = typeof value === 'string' ? parseFloat(value.replace(/,/g, '')) : Number(value)
21
+ return !isNaN(num) && isFinite(num)
22
+ }
23
+
24
+ // Helper function to filter and sort forecast data, splitting into segments at gaps
25
+ const prepareForecastData = (
26
+ data: Record<string, any>[],
27
+ xAxisKey: string,
28
+ highKey: string,
29
+ lowKey: string
30
+ ): Record<string, any>[][] => {
31
+ if (!data || data.length === 0) return []
32
+
33
+ // Filter out invalid data points (where confidence intervals are not calculable)
34
+ const validData = data.filter(d => {
35
+ const high = d[highKey]
36
+ const low = d[lowKey]
37
+ return isCalculable(high) && isCalculable(low) && d[xAxisKey]
38
+ })
39
+
40
+ if (validData.length === 0) return []
41
+
42
+ // Sort by date
43
+ const sortedData = [...validData].sort((a, b) => {
44
+ const dateA = Date.parse(a[xAxisKey])
45
+ const dateB = Date.parse(b[xAxisKey])
46
+ if (isNaN(dateA) || isNaN(dateB)) return 0
47
+ return dateA - dateB
48
+ })
49
+
50
+ // Split into segments when there are gaps
51
+ // Calculate intervals between consecutive points to detect gaps
52
+ const intervals: number[] = []
53
+ for (let i = 1; i < sortedData.length; i++) {
54
+ const currentDate = Date.parse(sortedData[i][xAxisKey])
55
+ const prevDate = Date.parse(sortedData[i - 1][xAxisKey])
56
+ if (!isNaN(currentDate) && !isNaN(prevDate)) {
57
+ intervals.push(currentDate - prevDate)
58
+ }
59
+ }
60
+
61
+ // Calculate median interval (more robust than average for detecting gaps)
62
+ const medianInterval =
63
+ intervals.length > 0
64
+ ? [...intervals].sort((a, b) => a - b)[Math.floor(intervals.length / 2)]
65
+ : 7 * 24 * 60 * 60 * 1000 // Default to 7 days if no intervals
66
+
67
+ // Threshold: gap is more than 2x the median interval, or more than 30 days
68
+ const gapThreshold = Math.max(medianInterval * 2, 30 * 24 * 60 * 60 * 1000)
69
+
70
+ const segments: Record<string, any>[][] = []
71
+ let currentSegment: Record<string, any>[] = []
72
+
73
+ for (let i = 0; i < sortedData.length; i++) {
74
+ const current = sortedData[i]
75
+ const prev = sortedData[i - 1]
76
+
77
+ if (i === 0) {
78
+ // First data point starts a new segment
79
+ currentSegment = [current]
80
+ } else {
81
+ const currentDate = Date.parse(current[xAxisKey])
82
+ const prevDate = Date.parse(prev[xAxisKey])
83
+
84
+ if (isNaN(currentDate) || isNaN(prevDate)) {
85
+ // If dates are invalid, continue current segment
86
+ currentSegment.push(current)
87
+ } else {
88
+ const interval = currentDate - prevDate
89
+ const hasGap = interval > gapThreshold
90
+
91
+ if (hasGap) {
92
+ // Save current segment and start a new one
93
+ if (currentSegment.length > 0) {
94
+ segments.push(currentSegment)
95
+ }
96
+ currentSegment = [current]
97
+ } else {
98
+ // Continue current segment
99
+ currentSegment.push(current)
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ // Add the last segment
106
+ if (currentSegment.length > 0) {
107
+ segments.push(currentSegment)
108
+ }
109
+
110
+ return segments.length > 0 ? segments : [sortedData]
111
+ }
112
+
17
113
  const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, handleTooltipMouseOff }) => {
18
114
  const { transformedData: data, rawData, config, seriesHighlight, parseDate } = useContext(ConfigContext)
19
115
  const { xAxis, yAxis, legend, runtime } = config
@@ -61,7 +157,7 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
61
157
  return (
62
158
  <Group
63
159
  className={`forecasting-areas-combo-${index}`}
64
- key={`forecasting-areas--stage-${replace(stage.key, / /g, '—')}-${index}`}
160
+ key={`forecasting-areas--stage-${replace(stage.key, / /g, '—')}-${index}`}
65
161
  >
66
162
  {group.confidenceIntervals?.map((ciGroup, ciGroupIndex) => {
67
163
  const palette = forecastingPalettes[stage.color] || false
@@ -78,35 +174,57 @@ const Forecasting = ({ xScale, yScale, height, width, handleTooltipMouseOver, ha
78
174
  return isReversed ? palette?.[1] || 'transparent' : palette?.[4] || 'transparent'
79
175
  }
80
176
 
81
- if (ciGroup.high === '' || ciGroup.low === '') return
177
+ if (ciGroup.high === '' || ciGroup.low === '') return null
178
+
179
+ // Prepare data: filter invalid values, sort by date, and split into segments at gaps
180
+ const dataSegments = prepareForecastData(bridgedData, xAxis.dataKey, ciGroup.high, ciGroup.low)
181
+
82
182
  return (
83
183
  <Group
84
184
  key={`forecasting-areas--stage-${replace(
85
185
  stage.key,
86
- / /g,
186
+ / /g,
87
187
  '—'
88
188
  )}--group-${stageIndex}-${ciGroupIndex}`}
89
189
  >
90
- {/* prettier-ignore */}
91
- <Area
92
- curve={curveMonotoneX}
93
- data={bridgedData}
94
- fill={getFill()}
95
- opacity={transparentArea? 0.1 : 0.5 }
96
- x={d => xScale(Date.parse(d[xAxis.dataKey]))}
97
- y0={d => yScale(d[ciGroup.low])}
98
- y1={d => yScale(d[ciGroup.high])}
99
- />
100
-
101
- {ciGroupIndex === 0 && (
102
- <>
190
+ {dataSegments.map((segment, segmentIndex) => (
191
+ <Group key={`segment-${segmentIndex}`}>
103
192
  {/* prettier-ignore */}
104
- <LinePath data={bridgedData} x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))} y={d => Number(yScale(d[ciGroup.high]))} curve={curveMonotoneX} stroke={getStroke()} strokeWidth={1} strokeOpacity={1} />
193
+ <Area
194
+ curve={curveMonotoneX}
195
+ data={segment}
196
+ fill={getFill()}
197
+ opacity={transparentArea ? 0.1 : 0.5}
198
+ x={d => xScale(Date.parse(d[xAxis.dataKey]))}
199
+ y0={d => yScale(d[ciGroup.low])}
200
+ y1={d => yScale(d[ciGroup.high])}
201
+ />
105
202
 
106
- {/* prettier-ignore */}
107
- <LinePath data={bridgedData} x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))} y={d => Number(yScale(d[ciGroup.low]))} curve={curveMonotoneX} stroke={getStroke()} strokeWidth={1} strokeOpacity={1} />
108
- </>
109
- )}
203
+ {ciGroupIndex === 0 && (
204
+ <>
205
+ <LinePath
206
+ data={segment}
207
+ x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))}
208
+ y={d => Number(yScale(d[ciGroup.high]))}
209
+ curve={curveMonotoneX}
210
+ stroke={getStroke()}
211
+ strokeWidth={1}
212
+ strokeOpacity={1}
213
+ />
214
+
215
+ <LinePath
216
+ data={segment}
217
+ x={d => Number(xScale(Date.parse(d[xAxis.dataKey])))}
218
+ y={d => Number(yScale(d[ciGroup.low]))}
219
+ curve={curveMonotoneX}
220
+ stroke={getStroke()}
221
+ strokeWidth={1}
222
+ strokeOpacity={1}
223
+ />
224
+ </>
225
+ )}
226
+ </Group>
227
+ ))}
110
228
  </Group>
111
229
  )
112
230
  })}
@@ -23,7 +23,7 @@ import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
23
23
 
24
24
  const LEGEND_PADDING = 36
25
25
 
26
- export interface LegendProps {
26
+ interface LegendProps {
27
27
  colorScale: ColorScale
28
28
  config: ChartConfig
29
29
  currentViewport: ViewportSize
@@ -149,9 +149,12 @@ const Legend: React.FC<LegendProps> = forwardRef(
149
149
  eventType: `chart_legend_item_toggled` as any,
150
150
  eventAction: 'keydown',
151
151
  eventLabel: interactionLabel,
152
- specifics: config.visualizationType === 'Bar'
153
- ? `label: ${label.text}, orientation: ${config.orientation === 'horizontal' ? 'horizontal' : 'vertical'}, mode: ${legend.behavior}`
154
- : `label: ${label.text}, mode: ${legend.behavior}`,
152
+ specifics:
153
+ config.visualizationType === 'Bar'
154
+ ? `label: ${label.text}, orientation: ${
155
+ config.orientation === 'horizontal' ? 'horizontal' : 'vertical'
156
+ }, mode: ${legend.behavior}`
157
+ : `label: ${label.text}, mode: ${legend.behavior}`
155
158
  })
156
159
  highlight(label)
157
160
  }
@@ -164,9 +167,12 @@ const Legend: React.FC<LegendProps> = forwardRef(
164
167
  eventType: `chart_legend_item_toggled` as any,
165
168
  eventAction: 'click',
166
169
  eventLabel: interactionLabel,
167
- specifics: config.visualizationType === 'Bar'
168
- ? `label: ${label.text}, orientation: ${config.orientation === 'horizontal' ? 'horizontal' : 'vertical'}, mode: ${legend.behavior}`
169
- : `label: ${label.text}, mode: ${legend.behavior}`,
170
+ specifics:
171
+ config.visualizationType === 'Bar'
172
+ ? `label: ${label.text}, orientation: ${
173
+ config.orientation === 'horizontal' ? 'horizontal' : 'vertical'
174
+ }, mode: ${legend.behavior}`
175
+ : `label: ${label.text}, mode: ${legend.behavior}`,
170
176
 
171
177
  vizTitle: getVizTitle(config)
172
178
  })
@@ -238,8 +244,9 @@ const Legend: React.FC<LegendProps> = forwardRef(
238
244
  {/* Pattern Legend Items */}
239
245
  {config.legend.patterns && Object.keys(config.legend.patterns).length > 0 && (
240
246
  <div
241
- className={`legend-patterns d-flex ${['top', 'bottom'].includes(config.legend.position) ? 'flex-row flex-wrap' : 'flex-column'
242
- }`}
247
+ className={`legend-patterns d-flex ${
248
+ ['top', 'bottom'].includes(config.legend.position) ? 'flex-row flex-wrap' : 'flex-column'
249
+ }`}
243
250
  >
244
251
  {Object.entries(config.legend.patterns).map(([key, pattern]) => {
245
252
  const patternId = `legend-pattern-${key}`