@cdc/chart 4.24.1 → 4.24.2

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 (58) hide show
  1. package/dist/cdcchart.js +30014 -29757
  2. package/examples/feature/line/line-chart-preliminary.json +84 -37
  3. package/examples/feature/regions/index.json +9 -5
  4. package/index.html +4 -2
  5. package/package.json +2 -2
  6. package/src/CdcChart.tsx +41 -24
  7. package/src/_stories/ChartEditor.stories.tsx +1 -1
  8. package/src/_stories/_mock/pie_config.json +4 -3
  9. package/src/components/AreaChart/components/AreaChart.jsx +1 -25
  10. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +7 -5
  11. package/src/components/BarChart/components/BarChart.Vertical.tsx +12 -13
  12. package/src/components/BoxPlot/BoxPlot.jsx +9 -8
  13. package/src/components/EditorPanel/EditorPanel.tsx +1563 -1959
  14. package/src/components/EditorPanel/EditorPanelContext.ts +40 -0
  15. package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +148 -0
  16. package/src/components/EditorPanel/components/{Panel.ForestPlotSettings.tsx → Panels/Panel.ForestPlotSettings.tsx} +16 -7
  17. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +160 -0
  18. package/src/components/EditorPanel/components/{Panel.Regions.tsx → Panels/Panel.Regions.tsx} +5 -5
  19. package/src/components/EditorPanel/components/{Panel.Series.tsx → Panels/Panel.Series.tsx} +4 -4
  20. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +297 -0
  21. package/src/components/EditorPanel/components/Panels/index.tsx +17 -0
  22. package/src/components/EditorPanel/editor-panel.scss +1 -13
  23. package/src/components/EditorPanel/useEditorPermissions.js +5 -0
  24. package/src/components/Legend/Legend.Component.tsx +199 -0
  25. package/src/components/Legend/Legend.tsx +5 -324
  26. package/src/components/Legend/helpers/createFormatLabels.tsx +140 -0
  27. package/src/components/LineChart/LineChartProps.ts +1 -1
  28. package/src/components/LineChart/components/LineChart.Circle.tsx +85 -52
  29. package/src/components/LineChart/helpers.ts +2 -2
  30. package/src/components/LineChart/index.tsx +97 -21
  31. package/src/components/LinearChart.jsx +3 -3
  32. package/src/components/PairedBarChart.jsx +4 -2
  33. package/src/components/PieChart/PieChart.tsx +78 -25
  34. package/src/components/Regions/components/Regions.tsx +14 -5
  35. package/src/data/initial-state.js +5 -2
  36. package/src/helpers/computeMarginBottom.ts +2 -2
  37. package/src/hooks/useHighlightedBars.js +1 -1
  38. package/src/hooks/useMinMax.ts +3 -3
  39. package/src/hooks/useScales.ts +18 -5
  40. package/src/hooks/useTooltip.tsx +11 -7
  41. package/src/scss/main.scss +0 -67
  42. package/src/types/ChartConfig.ts +17 -8
  43. package/src/types/ChartContext.ts +10 -4
  44. package/src/types/Label.ts +7 -0
  45. package/examples/private/chart-t.json +0 -3740
  46. package/examples/private/combo.json +0 -369
  47. package/examples/private/epi-data.csv +0 -13
  48. package/examples/private/epi-data.json +0 -62
  49. package/examples/private/epi.json +0 -403
  50. package/examples/private/occupancy.json +0 -109283
  51. package/examples/private/prod-line-config.json +0 -401
  52. package/examples/private/region-data.json +0 -822
  53. package/examples/private/region-testing.json +0 -312
  54. package/examples/private/scaling.json +0 -45325
  55. package/examples/private/testing-data.json +0 -1739
  56. package/examples/private/testing.json +0 -816
  57. package/src/components/EditorPanel/components/Panel.DateHighlighting.tsx +0 -109
  58. package/src/components/EditorPanel/components/Panels.tsx +0 -13
@@ -0,0 +1,297 @@
1
+ import { useContext, FC } from 'react'
2
+
3
+ // external libraries
4
+ import { AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
5
+
6
+ // core
7
+ import { TextField, Select, CheckBox } from '@cdc/core/components/EditorPanel/Inputs'
8
+ import Tooltip from '@cdc/core/components/ui/Tooltip'
9
+ import Icon from '@cdc/core/components/ui/Icon'
10
+ import InputToggle from '@cdc/core/components/inputs/InputToggle'
11
+
12
+ // contexts
13
+ import { useColorPalette } from '../../../../hooks/useColorPalette'
14
+ import { ChartContext } from './../../../../types/ChartContext.js'
15
+
16
+ import { useEditorPermissions } from '../../useEditorPermissions.js'
17
+ import { useEditorPanelContext } from '../../EditorPanelContext.js'
18
+ import ConfigContext from '../../../../ConfigContext.js'
19
+ import { PanelProps } from '../PanelProps'
20
+
21
+ const PanelVisual: FC<PanelProps> = props => {
22
+ const { config, updateConfig, colorPalettes, twoColorPalette } = useContext<ChartContext>(ConfigContext)
23
+ const { visual } = config
24
+ const { setLollipopShape, updateField } = useEditorPanelContext()
25
+ const { visHasBarBorders, visCanAnimate, visSupportsNonSequentialPallete, headerColors, visSupportsTooltipOpacity, visSupportsTooltipLines, visSupportsBarSpace, visSupportsBarThickness, visHasDataCutoff, visSupportsSequentialPallete, visSupportsReverseColorPalette } = useEditorPermissions()
26
+ const { twoColorPalettes, sequential, nonSequential } = useColorPalette(config, updateConfig)
27
+
28
+ return (
29
+ <AccordionItem>
30
+ <AccordionItemHeading>
31
+ <AccordionItemButton>Visual</AccordionItemButton>
32
+ </AccordionItemHeading>
33
+ <AccordionItemPanel>
34
+ {config.isLollipopChart && (
35
+ <>
36
+ <fieldset className='header'>
37
+ <legend className='edit-label'>Lollipop Shape</legend>
38
+ <div
39
+ onChange={e => {
40
+ setLollipopShape(e.target.value)
41
+ }}
42
+ >
43
+ <label className='radio-label'>
44
+ <input type='radio' name='lollipopShape' value='circle' checked={config.lollipopShape === 'circle'} />
45
+ Circle
46
+ </label>
47
+ <label className='radio-label'>
48
+ <input type='radio' name='lollipopShape' value='square' checked={config.lollipopShape === 'square'} />
49
+ Square
50
+ </label>
51
+ </div>
52
+ </fieldset>
53
+ <Select value={config.lollipopColorStyle ? config.lollipopColorStyle : 'two-tone'} fieldName='lollipopColorStyle' label='Lollipop Color Style' updateField={updateField} options={['regular', 'two-tone']} />
54
+ <Select value={config.lollipopSize ? config.lollipopSize : 'small'} fieldName='lollipopSize' label='Lollipop Size' updateField={updateField} options={['small', 'medium', 'large']} />
55
+ </>
56
+ )}
57
+
58
+ {config.visualizationType === 'Box Plot' && (
59
+ <fieldset className='fieldset fieldset--boxplot'>
60
+ <legend className=''>Box Plot Settings</legend>
61
+ <Select value={config.boxplot.borders} fieldName='borders' section='boxplot' label='Box Plot Borders' updateField={updateField} options={['true', 'false']} />
62
+ <CheckBox value={config.boxplot.plotOutlierValues} fieldName='plotOutlierValues' section='boxplot' label='Plot Outliers' updateField={updateField} />
63
+ <CheckBox value={config.boxplot.plotNonOutlierValues} fieldName='plotNonOutlierValues' section='boxplot' label='Plot non-outlier values' updateField={updateField} />
64
+ </fieldset>
65
+ )}
66
+
67
+ <Select value={config.fontSize} fieldName='fontSize' label='Font Size' updateField={updateField} options={['small', 'medium', 'large']} />
68
+ {visHasBarBorders() && <Select value={config.barHasBorder} fieldName='barHasBorder' label='Bar Borders' updateField={updateField} options={['true', 'false']} />}
69
+ {visCanAnimate() && <CheckBox value={config.animate} fieldName='animate' label='Animate Visualization' updateField={updateField} />}
70
+
71
+ {/*<CheckBox value={config.animateReplay} fieldName="animateReplay" label="Replay Animation When Filters Are Changed" updateField={updateField} />*/}
72
+
73
+ {((config.series?.some(series => series.type === 'Line' || series.type === 'dashed-lg' || series.type === 'dashed-sm' || series.type === 'dashed-md') && config.visualizationType === 'Combo') || config.visualizationType === 'Line') && (
74
+ <>
75
+ <Select value={config.lineDatapointStyle} fieldName='lineDatapointStyle' label='Line Datapoint Style' updateField={updateField} options={['hidden', 'hover', 'always show']} />
76
+ <Select value={config.lineDatapointColor} fieldName='lineDatapointColor' label='Line Datapoint Color' updateField={updateField} options={['Same as Line', 'Lighter than Line']} />
77
+ </>
78
+ )}
79
+
80
+ {/* eslint-disable */}
81
+ <label className='header'>
82
+ <span className='edit-label'>Header Theme</span>
83
+ <ul className='color-palette'>
84
+ {headerColors.map(palette => (
85
+ <button
86
+ title={palette}
87
+ key={palette}
88
+ onClick={e => {
89
+ e.preventDefault()
90
+ updateConfig({ ...config, theme: palette })
91
+ }}
92
+ className={config.theme === palette ? 'selected ' + palette : palette}
93
+ ></button>
94
+ ))}
95
+ </ul>
96
+ </label>
97
+ {/* eslint-enable */}
98
+ {(visSupportsNonSequentialPallete() || visSupportsNonSequentialPallete()) && (
99
+ <>
100
+ <label>
101
+ <span className='edit-label'>Chart Color Palette</span>
102
+ </label>
103
+ {visSupportsReverseColorPalette() && <InputToggle fieldName='isPaletteReversed' size='small' label='Use selected palette in reverse order' updateField={updateField} value={config.isPaletteReversed} />}
104
+ {visSupportsSequentialPallete() && (
105
+ <>
106
+ <span>Sequential</span>
107
+ <ul className='color-palette'>
108
+ {sequential.map(palette => {
109
+ const colorOne = {
110
+ backgroundColor: colorPalettes[palette][2]
111
+ }
112
+
113
+ const colorTwo = {
114
+ backgroundColor: colorPalettes[palette][3]
115
+ }
116
+
117
+ const colorThree = {
118
+ backgroundColor: colorPalettes[palette][5]
119
+ }
120
+
121
+ return (
122
+ <button
123
+ title={palette}
124
+ key={palette}
125
+ onClick={e => {
126
+ e.preventDefault()
127
+ updateConfig({ ...config, palette })
128
+ }}
129
+ className={config.palette === palette ? 'selected' : ''}
130
+ >
131
+ <span style={colorOne}></span>
132
+ <span style={colorTwo}></span>
133
+ <span style={colorThree}></span>
134
+ </button>
135
+ )
136
+ })}
137
+ </ul>
138
+ </>
139
+ )}
140
+ {visSupportsNonSequentialPallete() && (
141
+ <>
142
+ <span>Non-Sequential</span>
143
+ <ul className='color-palette'>
144
+ {nonSequential.map(palette => {
145
+ const colorOne = {
146
+ backgroundColor: colorPalettes[palette][2]
147
+ }
148
+
149
+ const colorTwo = {
150
+ backgroundColor: colorPalettes[palette][4]
151
+ }
152
+
153
+ const colorThree = {
154
+ backgroundColor: colorPalettes[palette][6]
155
+ }
156
+
157
+ return (
158
+ <button
159
+ title={palette}
160
+ key={palette}
161
+ onClick={e => {
162
+ e.preventDefault()
163
+ updateConfig({ ...config, palette })
164
+ }}
165
+ className={config.palette === palette ? 'selected' : ''}
166
+ >
167
+ <span style={colorOne}></span>
168
+ <span style={colorTwo}></span>
169
+ <span style={colorThree}></span>
170
+ </button>
171
+ )
172
+ })}
173
+ </ul>
174
+ </>
175
+ )}
176
+ </>
177
+ )}
178
+ {(config.visualizationType === 'Paired Bar' || config.visualizationType === 'Deviation Bar') && (
179
+ <>
180
+ <InputToggle section='twoColor' fieldName='isPaletteReversed' size='small' label='Use selected palette in reverse order' updateField={updateField} value={config.twoColor.isPaletteReversed} />
181
+ <ul className='color-palette'>
182
+ {twoColorPalettes.map(palette => {
183
+ const colorOne = {
184
+ backgroundColor: twoColorPalette[palette][0]
185
+ }
186
+
187
+ const colorTwo = {
188
+ backgroundColor: twoColorPalette[palette][1]
189
+ }
190
+
191
+ return (
192
+ <button
193
+ title={palette}
194
+ key={palette}
195
+ onClick={e => {
196
+ e.preventDefault()
197
+ updateConfig({ ...config, twoColor: { ...config.twoColor, palette } })
198
+ }}
199
+ className={config.twoColor.palette === palette ? 'selected' : ''}
200
+ >
201
+ <span className='two-color' style={colorOne}></span>
202
+ <span className='two-color' style={colorTwo}></span>
203
+ </button>
204
+ )
205
+ })}
206
+ </ul>
207
+ </>
208
+ )}
209
+
210
+ {visHasDataCutoff() && (
211
+ <>
212
+ <TextField
213
+ value={config.dataCutoff}
214
+ type='number'
215
+ fieldName='dataCutoff'
216
+ className='number-narrow'
217
+ label='Data Cutoff'
218
+ updateField={updateField}
219
+ tooltip={
220
+ <Tooltip style={{ textTransform: 'none' }}>
221
+ <Tooltip.Target>
222
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
223
+ </Tooltip.Target>
224
+ <Tooltip.Content>
225
+ <p>Any value below the cut-off value is included in a special "less than" category. This option supports special conditions like suppressed data.</p>
226
+ </Tooltip.Content>
227
+ </Tooltip>
228
+ }
229
+ />
230
+ </>
231
+ )}
232
+ {visSupportsBarThickness() && config.orientation === 'horizontal' && !config.isLollipopChart && config.yAxis.labelPlacement !== 'On Bar' && <TextField type='number' value={config.barHeight || '25'} fieldName='barHeight' label=' Bar Thickness' updateField={updateField} min={15} />}
233
+ {((config.visualizationType === 'Bar' && config.orientation !== 'horizontal') || config.visualizationType === 'Combo') && <TextField value={config.barThickness} type='number' fieldName='barThickness' label='Bar Thickness' updateField={updateField} />}
234
+ {visSupportsBarSpace() && <TextField type='number' value={config.barSpace || '15'} fieldName='barSpace' label='Bar Space' updateField={updateField} min={0} />}
235
+ {(config.visualizationType === 'Bar' || config.visualizationType === 'Line' || config.visualizationType === 'Combo') && <CheckBox value={config.topAxis.hasLine} section='topAxis' fieldName='hasLine' label='Add Top Axis Line' updateField={updateField} />}
236
+
237
+ {config.visualizationType === 'Spark Line' && (
238
+ <div className='cove-accordion__panel-section checkbox-group'>
239
+ <CheckBox value={visual?.border} section='visual' fieldName='border' label='Show Border' updateField={updateField} />
240
+ <CheckBox value={visual?.borderColorTheme} section='visual' fieldName='borderColorTheme' label='Use Border Color Theme' updateField={updateField} />
241
+ <CheckBox value={visual?.accent} section='visual' fieldName='accent' label='Use Accent Style' updateField={updateField} />
242
+ <CheckBox value={visual?.background} section='visual' fieldName='background' label='Use Theme Background Color' updateField={updateField} />
243
+ <CheckBox value={visual?.hideBackgroundColor} section='visual' fieldName='hideBackgroundColor' label='Hide Background Color' updateField={updateField} />
244
+ </div>
245
+ )}
246
+
247
+ {(config.visualizationType === 'Line' || config.visualizationType === 'Combo') && <CheckBox value={config.showLineSeriesLabels} fieldName='showLineSeriesLabels' label='Append Series Name to End of Line Charts' updateField={updateField} />}
248
+ {(config.visualizationType === 'Line' || config.visualizationType === 'Combo') && config.showLineSeriesLabels && <CheckBox value={config.colorMatchLineSeriesLabels} fieldName='colorMatchLineSeriesLabels' label='Match Series Color to Name at End of Line Charts' updateField={updateField} />}
249
+
250
+ {visSupportsTooltipLines() && (
251
+ <>
252
+ <CheckBox value={visual.verticalHoverLine} fieldName='verticalHoverLine' section='visual' label='Vertical Hover Line' updateField={updateField} />
253
+ <CheckBox value={visual.horizontalHoverLine} fieldName='horizontalHoverLine' section='visual' label='Horizontal Hover Line' updateField={updateField} />
254
+ </>
255
+ )}
256
+ {visSupportsTooltipOpacity() && (
257
+ <label>
258
+ <span className='edit-label column-heading'>Tooltip Opacity</span>
259
+ <input
260
+ type='number'
261
+ value={config.tooltips.opacity ? config.tooltips.opacity : 100}
262
+ onChange={e =>
263
+ updateConfig({
264
+ ...config,
265
+ tooltips: {
266
+ ...config.tooltips,
267
+ opacity: e.target.value
268
+ }
269
+ })
270
+ }
271
+ />
272
+ </label>
273
+ )}
274
+ {config.visualizationType === 'Bar' && <CheckBox value={config.tooltips.singleSeries} fieldName='singleSeries' section='tooltips' label='SHOW HOVER FOR SINGLE DATA SERIES' updateField={updateField} />}
275
+
276
+ <label>
277
+ <span className='edit-label column-heading'>No Data Message</span>
278
+ <input
279
+ type='text'
280
+ value={config.chartMessage.noData ? config.chartMessage.noData : ''}
281
+ onChange={e =>
282
+ updateConfig({
283
+ ...config,
284
+ chartMessage: {
285
+ ...config.chartMessage,
286
+ noData: e.target.value
287
+ }
288
+ })
289
+ }
290
+ />
291
+ </label>
292
+ </AccordionItemPanel>
293
+ </AccordionItem>
294
+ )
295
+ }
296
+
297
+ export default PanelVisual
@@ -0,0 +1,17 @@
1
+ import ForestPlotSettings from './Panel.ForestPlotSettings.js'
2
+ import Series from './Panel.Series.js'
3
+ import Regions from './Panel.Regions.js'
4
+ import General from './Panel.General.js'
5
+ import BoxPlot from './Panel.BoxPlot.js'
6
+ import Visual from './Panel.Visual.js'
7
+
8
+ const Panels = {
9
+ ForestPlot: ForestPlotSettings,
10
+ Series: Series,
11
+ Regions,
12
+ General,
13
+ BoxPlot,
14
+ Visual
15
+ }
16
+
17
+ export default Panels
@@ -8,17 +8,6 @@
8
8
 
9
9
  .cdc-open-viz-module.type-chart {
10
10
  .editor-panel {
11
- @import './components/panels.scss';
12
- display: flex;
13
- flex-direction: column;
14
- position: fixed;
15
- top: 0;
16
- bottom: 0;
17
- left: 0;
18
- width: 350px;
19
- background: #fff;
20
- z-index: 8;
21
-
22
11
  &.hidden {
23
12
  display: none;
24
13
  }
@@ -711,14 +700,13 @@
711
700
  color: #000;
712
701
  font-size: 1em;
713
702
  border: 0;
714
- position: fixed;
703
+ position: absolute;
715
704
  z-index: 100;
716
705
  transition: 0.1s background;
717
706
  cursor: pointer;
718
707
  width: 25px;
719
708
  height: 25px;
720
709
  left: 307px;
721
- top: 10px;
722
710
  box-shadow: rgba(0, 0, 0, 0.5) 0 1px 2px;
723
711
 
724
712
  &:before {
@@ -275,6 +275,10 @@ export const useEditorPermissions = () => {
275
275
  return true
276
276
  }
277
277
 
278
+ const visSupportsDateCategoryAxisPadding = () => {
279
+ return config.xAxis.type === 'date' && config.xAxis.sortDates
280
+ }
281
+
278
282
  const visSupportsReactTooltip = () => {
279
283
  if (['Deviation Bar', 'Box Plot', 'Scatter Plot', 'Paired Bar'].includes(visualizationType) || (visualizationType === 'Bar' && config.tooltips.singleSeries)) {
280
284
  return true
@@ -303,6 +307,7 @@ export const useEditorPermissions = () => {
303
307
  visSupportsDateCategoryHeight,
304
308
  visSupportsDateCategoryNumTicks,
305
309
  visSupportsDateCategoryTickRotation,
310
+ visSupportsDateCategoryAxisPadding,
306
311
  visSupportsFilters,
307
312
  visSupportsFootnotes,
308
313
  visSupportsLeftValueAxis,
@@ -0,0 +1,199 @@
1
+ import parse from 'html-react-parser'
2
+ import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
3
+ import LegendCircle from '@cdc/core/components/LegendCircle'
4
+
5
+ import useLegendClasses from '../../hooks/useLegendClasses'
6
+ import { useHighlightedBars } from '../../hooks/useHighlightedBars'
7
+ import { handleLineType } from '../../helpers/handleLineType'
8
+ import { Line } from '@visx/shape'
9
+ import { scaleOrdinal } from '@visx/scale'
10
+ import { Label } from '../../types/Label'
11
+ import { ChartConfig } from '../../types/ChartConfig'
12
+ import { ColorScale } from '../../types/ChartContext'
13
+ import { Group } from '@visx/group'
14
+
15
+ interface LegendProps {
16
+ config: ChartConfig
17
+ colorScale: ColorScale
18
+ seriesHighlight: string[]
19
+ highlight: Function
20
+ highlightReset: Function
21
+ currentViewport: string
22
+ formatLabels: (labels: Label[]) => Label[]
23
+ }
24
+
25
+ /* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
26
+ const Legend: React.FC<LegendProps> = ({ config, colorScale, seriesHighlight, highlight, highlightReset, currentViewport, formatLabels }) => {
27
+ const { innerClasses, containerClasses } = useLegendClasses(config)
28
+ const { runtime, orientation, legend } = config
29
+ if (!legend) return null
30
+ // create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
31
+ const displayScale = scaleOrdinal({
32
+ domain: config.suppressedData?.map(d => d.label),
33
+ range: ['none'],
34
+ unknown: 'block'
35
+ })
36
+
37
+ const renderDashes = style => {
38
+ const dashCount = style === 'Dashed Small' ? 3 : 2
39
+ const dashClass = `dashes ${style.toLowerCase().replace(' ', '-')}`
40
+
41
+ return (
42
+ <div className={dashClass}>
43
+ {Array.from({ length: dashCount }, (_, i) => (
44
+ <span key={i}>-</span>
45
+ ))}
46
+ </div>
47
+ )
48
+ }
49
+ const renderDashesOrCircle = style => {
50
+ if (['Dashed Small', 'Dashed Medium', 'Dashed Large'].includes(style)) {
51
+ return renderDashes(style)
52
+ } else if (style === 'Open Circles') {
53
+ return <div className='dashes open-circles'></div>
54
+ }
55
+ }
56
+
57
+ const isBottomOrSmallViewport = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
58
+
59
+ const legendClasses = {
60
+ marginBottom: isBottomOrSmallViewport ? '15px' : '0px',
61
+ marginTop: isBottomOrSmallViewport && orientation === 'horizontal' ? `${config.yAxis.label && config.isResponsiveTicks ? config.dynamicMarginTop : config.runtime.xAxis.size}px` : `${isBottomOrSmallViewport ? config.dynamicMarginTop + 15 : 0}px`
62
+ }
63
+
64
+ const { HighLightedBarUtils } = useHighlightedBars(config)
65
+
66
+ let highLightedLegendItems = HighLightedBarUtils.findDuplicates(config.highlightedBarValues)
67
+
68
+ return (
69
+ <aside style={legendClasses} id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
70
+ {legend.label && <h2>{parse(legend.label)}</h2>}
71
+ {legend.description && <p>{parse(legend.description)}</p>}
72
+ <LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
73
+ {labels => {
74
+ return (
75
+ <>
76
+ <div className={innerClasses.join(' ')}>
77
+ {formatLabels(labels as Label[]).map((label, i) => {
78
+ let className = ['legend-item', `legend-text--${label.text.replace(' ', '').toLowerCase()}`]
79
+ let itemName = label.datum
80
+
81
+ // Filter excluded data keys from legend
82
+ if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
83
+ return null
84
+ }
85
+
86
+ if (runtime.seriesLabels) {
87
+ let index = config.runtime.seriesLabelsAll.indexOf(itemName)
88
+ itemName = config.runtime.seriesKeys[index]
89
+
90
+ if (runtime?.forecastingSeriesKeys?.length > 0) {
91
+ itemName = label.text
92
+ }
93
+ }
94
+
95
+ if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
96
+ className.push('inactive')
97
+ }
98
+
99
+ return (
100
+ <LegendItem
101
+ className={className.join(' ')}
102
+ tabIndex={0}
103
+ key={`legend-quantile-${i}`}
104
+ onKeyPress={e => {
105
+ if (e.key === 'Enter') {
106
+ highlight(label)
107
+ }
108
+ }}
109
+ onClick={() => {
110
+ highlight(label)
111
+ }}
112
+ >
113
+ {config.visualizationType === 'Line' && config.legend.lineMode ? (
114
+ <svg width={40} height={20}>
115
+ <Line from={{ x: 10, y: 10 }} to={{ x: 40, y: 10 }} stroke={label.value} strokeWidth={2} strokeDasharray={handleLineType(config.series[i]?.type ? config.series[i]?.type : '')} />
116
+ </svg>
117
+ ) : (
118
+ <div style={{ display: 'flex', flexDirection: 'column' }}>
119
+ <LegendCircle margin='0' fill={label.value} display={displayScale(label.datum)} />
120
+ <div style={{ marginTop: '2px', marginRight: '6px' }}>{label.icon}</div>
121
+ </div>
122
+ )}
123
+
124
+ <LegendLabel align='left' margin='0 0 0 4px'>
125
+ {label.text}
126
+ </LegendLabel>
127
+ </LegendItem>
128
+ )
129
+ })}
130
+
131
+ {highLightedLegendItems.map((bar, i) => {
132
+ // if duplicates only return first item
133
+ let className = 'legend-item'
134
+ let itemName = bar.legendLabel
135
+
136
+ if (!itemName) return false
137
+ if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
138
+ className += ' inactive'
139
+ }
140
+ return (
141
+ <LegendItem
142
+ className={className}
143
+ tabIndex={0}
144
+ key={`legend-quantile-${i}`}
145
+ onKeyPress={e => {
146
+ if (e.key === 'Enter') {
147
+ highlight(bar.legendLabel)
148
+ }
149
+ }}
150
+ onClick={() => {
151
+ highlight(bar.legendLabel)
152
+ }}
153
+ >
154
+ <LegendCircle fill='transparent' borderColor={bar.color ? bar.color : `rgba(255, 102, 1)`} />{' '}
155
+ <LegendLabel align='left' margin='0 0 0 4px'>
156
+ {bar.legendLabel ? bar.legendLabel : bar.value}
157
+ </LegendLabel>
158
+ </LegendItem>
159
+ )
160
+ })}
161
+ {seriesHighlight.length > 0 && (
162
+ <button className={`legend-reset ${config.theme}`} onClick={labels => highlightReset(labels)} tabIndex={0}>
163
+ Reset
164
+ </button>
165
+ )}
166
+ </div>
167
+
168
+ <>
169
+ {config?.preliminaryData?.some(pd => pd.label) && config.visualizationType === 'Line' && (
170
+ <>
171
+ <hr></hr>
172
+ <div className={config.legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : ''}>
173
+ {config?.preliminaryData?.map((pd, index) => {
174
+ return (
175
+ <>
176
+ {pd.label && (
177
+ <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
178
+ <svg style={{ width: '50px' }} key={index} height={'23px'}>
179
+ {pd.style.includes('Dashed') ? <Line from={{ x: 10, y: 10 }} to={{ x: 40, y: 10 }} stroke={'#000'} strokeWidth={2} strokeDasharray={handleLineType(pd.style)} /> : <circle r={6} strokeWidth={2} stroke={'#000'} cx={22} cy={10} fill='transparent' />}
180
+ </svg>
181
+ <span style={{}}> {pd.label}</span>
182
+ </div>
183
+ )}
184
+ </>
185
+ )
186
+ })}
187
+ </div>
188
+ </>
189
+ )}
190
+ </>
191
+ </>
192
+ )
193
+ }}
194
+ </LegendOrdinal>
195
+ </aside>
196
+ )
197
+ }
198
+
199
+ export default Legend