@cdc/chart 4.24.12 → 4.25.2-25

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 (84) hide show
  1. package/dist/cdcchart.js +79900 -78999
  2. package/examples/feature/boxplot/boxplot.json +2 -157
  3. package/examples/feature/boxplot/testing.csv +23 -38
  4. package/examples/feature/tests-non-numerics/example-combo-bar-nonnumeric.json +579 -49
  5. package/examples/private/ehdi.json +29939 -0
  6. package/examples/private/line-issue.json +497 -0
  7. package/examples/private/not-loading.json +360 -0
  8. package/index.html +11 -15
  9. package/package.json +2 -2
  10. package/src/CdcChart.tsx +92 -1512
  11. package/src/CdcChartComponent.tsx +1113 -0
  12. package/src/ConfigContext.tsx +6 -1
  13. package/src/_stories/Chart.Anchors.stories.tsx +1 -1
  14. package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
  15. package/src/_stories/Chart.DynamicSeries.stories.tsx +17 -2
  16. package/src/_stories/Chart.Filters.stories.tsx +19 -0
  17. package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
  18. package/src/_stories/Chart.ScatterPlot.stories.tsx +19 -0
  19. package/src/_stories/Chart.tooltip.stories.tsx +1 -2
  20. package/src/_stories/ChartAnnotation.stories.tsx +1 -1
  21. package/src/_stories/ChartAxisLabels.stories.tsx +1 -1
  22. package/src/_stories/ChartAxisTitles.stories.tsx +1 -1
  23. package/src/_stories/ChartEditor.stories.tsx +1 -1
  24. package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
  25. package/src/_stories/ChartLine.Symbols.stories.tsx +18 -0
  26. package/src/_stories/ChartPrefixSuffix.stories.tsx +1 -1
  27. package/src/_stories/_mock/line_chart_symbols.json +437 -0
  28. package/src/_stories/_mock/scatterplot-image-download.json +1244 -0
  29. package/src/components/Annotations/components/AnnotationDraggable.tsx +3 -11
  30. package/src/components/Annotations/components/AnnotationDropdown.tsx +3 -3
  31. package/src/components/Axis/Categorical.Axis.tsx +3 -4
  32. package/src/components/BarChart/components/BarChart.Horizontal.tsx +14 -5
  33. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +10 -4
  34. package/src/components/BarChart/components/BarChart.Vertical.tsx +5 -7
  35. package/src/components/BarChart/components/BarChart.jsx +24 -4
  36. package/src/components/BarChart/components/context.tsx +1 -0
  37. package/src/components/BoxPlot/BoxPlot.tsx +34 -32
  38. package/src/components/BoxPlot/helpers/index.ts +108 -18
  39. package/src/components/BrushChart.tsx +44 -24
  40. package/src/components/DeviationBar.jsx +2 -6
  41. package/src/components/EditorPanel/EditorPanel.tsx +64 -8
  42. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +4 -0
  43. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +3 -1
  44. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +44 -7
  45. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +6 -1
  46. package/src/components/ForestPlot/ForestPlot.tsx +176 -26
  47. package/src/components/Legend/Legend.Component.tsx +29 -38
  48. package/src/components/Legend/Legend.Suppression.tsx +3 -5
  49. package/src/components/Legend/Legend.tsx +2 -2
  50. package/src/components/Legend/LegendLine.Shape.tsx +51 -0
  51. package/src/components/Legend/helpers/createFormatLabels.tsx +29 -26
  52. package/src/components/Legend/helpers/getLegendClasses.ts +20 -38
  53. package/src/components/Legend/helpers/index.ts +22 -9
  54. package/src/components/Legend/tests/getLegendClasses.test.ts +3 -20
  55. package/src/components/LineChart/components/LineChart.Circle.tsx +104 -94
  56. package/src/components/LineChart/index.tsx +6 -2
  57. package/src/components/LinearChart.tsx +77 -43
  58. package/src/components/PairedBarChart.jsx +2 -9
  59. package/src/components/ZoomBrush.tsx +5 -7
  60. package/src/data/initial-state.js +6 -3
  61. package/src/helpers/getBoxPlotConfig.ts +68 -0
  62. package/src/helpers/getColorScale.ts +24 -0
  63. package/src/helpers/getComboChartConfig.ts +42 -0
  64. package/src/helpers/getExcludedData.ts +37 -0
  65. package/src/helpers/getTopAxis.ts +7 -0
  66. package/src/helpers/isConvertLineToBarGraph.ts +10 -3
  67. package/src/hooks/useBarChart.ts +40 -13
  68. package/src/hooks/{useHighlightedBars.js → useHighlightedBars.ts} +2 -1
  69. package/src/hooks/useIntersectionObserver.ts +37 -0
  70. package/src/hooks/useMinMax.ts +11 -8
  71. package/src/hooks/useReduceData.ts +1 -1
  72. package/src/hooks/useScales.ts +10 -0
  73. package/src/hooks/useTooltip.tsx +21 -2
  74. package/src/index.jsx +1 -0
  75. package/src/scss/DataTable.scss +0 -5
  76. package/src/scss/main.scss +31 -116
  77. package/src/store/chart.actions.ts +40 -0
  78. package/src/store/chart.reducer.ts +83 -0
  79. package/src/types/ChartConfig.ts +6 -3
  80. package/src/types/ChartContext.ts +1 -3
  81. package/src/helpers/getQuartiles.ts +0 -27
  82. package/src/hooks/useColorScale.ts +0 -50
  83. package/src/hooks/useIntersectionObserver.jsx +0 -29
  84. package/src/hooks/useTopAxis.js +0 -6
@@ -14,9 +14,18 @@ import { type ChartContext } from '@cdc/chart/src/types/ChartContext'
14
14
 
15
15
  // cdc
16
16
  import ConfigContext from '../../ConfigContext'
17
- import { getFontSize } from '@cdc/core/helpers/cove/number'
18
-
19
- const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseOff, handleTooltipMouseOver, forestPlotRightLabelRef }: ForestPlotProps) => {
17
+ import { appFontSize } from '@cdc/core/helpers/cove/fontSettings'
18
+
19
+ const ForestPlot = ({
20
+ xScale,
21
+ yScale,
22
+ config,
23
+ height,
24
+ width,
25
+ handleTooltipMouseOff,
26
+ handleTooltipMouseOver,
27
+ forestPlotRightLabelRef
28
+ }: ForestPlotProps) => {
20
29
  const { rawData: data, updateConfig } = useContext<ChartContext>(ConfigContext)
21
30
  const { forestPlot } = config as ChartConfig
22
31
  const labelPosition = config.xAxis.tickWidthMax + 10
@@ -33,7 +42,10 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
33
42
  const colsToCheck = 10
34
43
  for (let i = 0; i < colsToCheck; i++) {
35
44
  defaultColumns.forEach(col => {
36
- if (config.forestPlot[col] && config.forestPlot[col] !== newConfig.columns[config.forestPlot[`additionalColumn${i}`]]?.name) {
45
+ if (
46
+ config.forestPlot[col] &&
47
+ config.forestPlot[col] !== newConfig.columns[config.forestPlot[`additionalColumn${i}`]]?.name
48
+ ) {
37
49
  delete newConfig.columns[`additionalColumn${i}`] // Remove old value if found to prevent duplicates
38
50
  newConfig.columns[config.forestPlot[col]] = {}
39
51
  newConfig.columns[config.forestPlot[col]].dataKey = newConfig.forestPlot[col]
@@ -83,9 +95,15 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
83
95
  const regressionPoints = pooledData
84
96
  ? [
85
97
  { x: xScale(pooledData[config.forestPlot.lower]), y: height - Number(config.forestPlot.rowHeight) },
86
- { x: xScale(pooledData[config.forestPlot.estimateField]), y: height - forestPlot.pooledResult.diamondHeight - Number(config.forestPlot.rowHeight) },
98
+ {
99
+ x: xScale(pooledData[config.forestPlot.estimateField]),
100
+ y: height - forestPlot.pooledResult.diamondHeight - Number(config.forestPlot.rowHeight)
101
+ },
87
102
  { x: xScale(pooledData[config.forestPlot.upper]), y: height - Number(config.forestPlot.rowHeight) },
88
- { x: xScale(pooledData[config.forestPlot.estimateField]), y: height + forestPlot.pooledResult.diamondHeight - Number(config.forestPlot.rowHeight) },
103
+ {
104
+ x: xScale(pooledData[config.forestPlot.estimateField]),
105
+ y: height + forestPlot.pooledResult.diamondHeight - Number(config.forestPlot.rowHeight)
106
+ },
89
107
  { x: xScale(pooledData[config.forestPlot.lower]), y: height - Number(config.forestPlot.rowHeight) }
90
108
  ]
91
109
  : []
@@ -114,16 +132,37 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
114
132
  <>
115
133
  <Group width={width}>
116
134
  {forestPlot.title && (
117
- <Text className={`forest-plot--title`} x={forestPlot.type === 'Linear' ? xScale(0) : xScale(1)} y={0} textAnchor='middle' verticalAnchor='start' fontSize={getFontSize(config.fontSize)} fill={'black'}>
135
+ <Text
136
+ className={`forest-plot--title`}
137
+ x={forestPlot.type === 'Linear' ? xScale(0) : xScale(1)}
138
+ y={0}
139
+ textAnchor='middle'
140
+ verticalAnchor='start'
141
+ fill={'black'}
142
+ >
118
143
  {forestPlot.title}
119
144
  </Text>
120
145
  )}
121
146
 
122
147
  {/* Line of no effect on Continuous Scale. */}
123
- {forestPlot.lineOfNoEffect.show && forestPlot.type === 'Linear' && <Line from={{ x: xScale(0), y: 0 + topMarginOffset }} to={{ x: xScale(0), y: height }} className='forestplot__line-of-no-effect' stroke={forestPlot.regression.baseLineColor || 'black'} />}
148
+ {forestPlot.lineOfNoEffect.show && forestPlot.type === 'Linear' && (
149
+ <Line
150
+ from={{ x: xScale(0), y: 0 + topMarginOffset }}
151
+ to={{ x: xScale(0), y: height }}
152
+ className='forestplot__line-of-no-effect'
153
+ stroke={forestPlot.regression.baseLineColor || 'black'}
154
+ />
155
+ )}
124
156
 
125
157
  {/* Line of no effect on Logarithmic Scale. */}
126
- {forestPlot.lineOfNoEffect.show && forestPlot.type === 'Logarithmic' && <Line from={{ x: xScale(1), y: 0 + topMarginOffset }} to={{ x: xScale(1), y: height }} className='forestplot__line-of-no-effect' stroke={forestPlot.regression.baseLineColor || 'black'} />}
158
+ {forestPlot.lineOfNoEffect.show && forestPlot.type === 'Logarithmic' && (
159
+ <Line
160
+ from={{ x: xScale(1), y: 0 + topMarginOffset }}
161
+ to={{ x: xScale(1), y: height }}
162
+ className='forestplot__line-of-no-effect'
163
+ stroke={forestPlot.regression.baseLineColor || 'black'}
164
+ />
165
+ )}
127
166
 
128
167
  {data.map((d, i) => {
129
168
  // calculate both square and circle size based on radius.min and radius.max
@@ -133,7 +172,8 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
133
172
  })
134
173
 
135
174
  // glyph settings
136
- const rectSize = forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.radius.scalingColumn]) : 4
175
+ const rectSize =
176
+ forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.radius.scalingColumn]) : 4
137
177
  const shapeColor = forestPlot.colors.shape ? forestPlot.colors.shape : 'black'
138
178
  const lineColor = forestPlot.colors.line ? forestPlot.colors.line : 'black'
139
179
 
@@ -167,41 +207,124 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
167
207
  />
168
208
 
169
209
  {/* main line */}
170
- <line stroke={lineColor} className={`line-${d[config.yAxis.dataKey]}`} key={i} x1={xScale(d[forestPlot.lower])} x2={xScale(d[forestPlot.upper])} y1={yScale(i)} y2={yScale(i)} />
210
+ <line
211
+ stroke={lineColor}
212
+ className={`line-${d[config.yAxis.dataKey]}`}
213
+ key={i}
214
+ x1={xScale(d[forestPlot.lower])}
215
+ x2={xScale(d[forestPlot.upper])}
216
+ y1={yScale(i)}
217
+ y2={yScale(i)}
218
+ />
171
219
  {forestPlot.shape === 'circle' && (
172
- <Circle className='forest-plot--circle' cx={xScale(Number(d[forestPlot.estimateField]))} cy={yScale(i)} r={forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.radius.scalingColumn]) : 4} fill={shapeColor} style={{ opacity: 1, filter: 'unset' }} />
220
+ <Circle
221
+ className='forest-plot--circle'
222
+ cx={xScale(Number(d[forestPlot.estimateField]))}
223
+ cy={yScale(i)}
224
+ r={forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.radius.scalingColumn]) : 4}
225
+ fill={shapeColor}
226
+ style={{ opacity: 1, filter: 'unset' }}
227
+ />
228
+ )}
229
+ {forestPlot.shape === 'square' && (
230
+ <rect
231
+ className='forest-plot--square'
232
+ x={xScale(Number(d[forestPlot.estimateField]))}
233
+ y={yScale(i) - rectSize / 2}
234
+ width={rectSize}
235
+ height={rectSize}
236
+ fill={shapeColor}
237
+ style={{ opacity: 1, filter: 'unset' }}
238
+ />
173
239
  )}
174
- {forestPlot.shape === 'square' && <rect className='forest-plot--square' x={xScale(Number(d[forestPlot.estimateField]))} y={yScale(i) - rectSize / 2} width={rectSize} height={rectSize} fill={shapeColor} style={{ opacity: 1, filter: 'unset' }} />}
175
240
  {forestPlot.shape === 'text' && (
176
- <Text className='forest-plot--text' x={xScale(Number(d[forestPlot.estimateField]))} y={yScale(i)} textAnchor='middle' verticalAnchor='middle' fontSize={getFontSize(config.fontSize)} fill={shapeColor}>
241
+ <Text
242
+ className='forest-plot--text'
243
+ x={xScale(Number(d[forestPlot.estimateField]))}
244
+ y={yScale(i)}
245
+ textAnchor='middle'
246
+ verticalAnchor='middle'
247
+ fill={shapeColor}
248
+ >
177
249
  {d[forestPlot.estimateField]}
178
250
  </Text>
179
251
  )}
180
252
  </Group>
181
253
  ) : (
182
- <LinePath data={regressionPoints} x={d => d.x} y={d => d.y - getFontSize(config.fontSize) / 2} stroke='black' strokeWidth={2} fill={'black'} curve={curveLinearClosed} />
254
+ <LinePath
255
+ data={regressionPoints}
256
+ x={d => d.x}
257
+ y={d => d.y - appFontSize / 2}
258
+ stroke='black'
259
+ strokeWidth={2}
260
+ fill={'black'}
261
+ curve={curveLinearClosed}
262
+ />
183
263
  )
184
264
  })}
185
265
 
186
266
  {/* regression diamond */}
187
- {regressionPoints && forestPlot.regression.showDiamond && <LinePath data={regressionPoints} x={d => d.x} y={d => d.y} stroke='black' strokeWidth={2} fill={forestPlot.regression.baseLineColor} curve={curveLinearClosed} />}
267
+ {regressionPoints && forestPlot.regression.showDiamond && (
268
+ <LinePath
269
+ data={regressionPoints}
270
+ x={d => d.x}
271
+ y={d => d.y}
272
+ stroke='black'
273
+ strokeWidth={2}
274
+ fill={forestPlot.regression.baseLineColor}
275
+ curve={curveLinearClosed}
276
+ />
277
+ )}
188
278
  {/* regression text */}
189
279
  {forestPlot.regression.description && (
190
- <Text x={0 - Number(config.xAxis.size)} width={width} y={height - config.forestPlot.rowHeight - Number(forestPlot.rowHeight) / 3} verticalAnchor='start' textAnchor='start' style={{ fontWeight: 'bold', fontSize: 12 }}>
280
+ <Text
281
+ x={0 - Number(config.xAxis.size)}
282
+ width={width}
283
+ y={height - config.forestPlot.rowHeight - Number(forestPlot.rowHeight) / 3}
284
+ verticalAnchor='start'
285
+ textAnchor='start'
286
+ style={{ fontWeight: 'bold', fontSize: 12 }}
287
+ >
191
288
  {forestPlot.regression.description}
192
289
  </Text>
193
290
  )}
194
291
 
195
- <Bar key='forest-plot-tooltip-area' className='forest-plot-tooltip-area' width={width} height={height} fill={false ? 'red' : 'transparent'} fillOpacity={0.5} onMouseMove={e => handleTooltipMouseOver(e, data)} onMouseOut={handleTooltipMouseOff} />
292
+ <Bar
293
+ key='forest-plot-tooltip-area'
294
+ className='forest-plot-tooltip-area'
295
+ width={width}
296
+ height={height}
297
+ fill={false ? 'red' : 'transparent'}
298
+ fillOpacity={0.5}
299
+ onMouseMove={e => handleTooltipMouseOver(e, data)}
300
+ onMouseOut={handleTooltipMouseOff}
301
+ />
196
302
  </Group>
197
- <Line from={topLine[0]} to={topLine[1]} style={{ stroke: 'black', strokeWidth: 2 }} className='forestplot__top-line' />
198
- <Line from={bottomLine[0]} to={bottomLine[1]} style={{ stroke: 'black', strokeWidth: 2 }} className='forestplot__bottom-line' />
303
+ <Line
304
+ from={topLine[0]}
305
+ to={topLine[1]}
306
+ style={{ stroke: 'black', strokeWidth: 2 }}
307
+ className='forestplot__top-line'
308
+ />
309
+ <Line
310
+ from={bottomLine[0]}
311
+ to={bottomLine[1]}
312
+ style={{ stroke: 'black', strokeWidth: 2 }}
313
+ className='forestplot__bottom-line'
314
+ />
199
315
 
200
316
  {/* column data */}
201
317
  {columnsOnChart.map(column => {
202
318
  return data.map((d, i) => {
203
319
  return (
204
- <Text className={`${d[column.name]}`} x={column.forestPlotAlignRight ? width : column.forestPlotStartingPoint} y={yScale(i)} textAnchor={column.forestPlotAlignRight ? 'end' : 'start'} verticalAnchor='middle' fontSize={getFontSize(config.fontSize)} fill={'black'}>
320
+ <Text
321
+ className={`${d[column.name]}`}
322
+ x={column.forestPlotAlignRight ? width : column.forestPlotStartingPoint}
323
+ y={yScale(i)}
324
+ textAnchor={column.forestPlotAlignRight ? 'end' : 'start'}
325
+ verticalAnchor='middle'
326
+ fill={'black'}
327
+ >
205
328
  {d[column.name]}
206
329
  </Text>
207
330
  )
@@ -212,7 +335,14 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
212
335
  {!forestPlot.hideDateCategoryCol &&
213
336
  data.map((d, i) => {
214
337
  return (
215
- <Text className={`${d[config.xAxis.dataKey]}`} x={0} y={yScale(i)} textAnchor={'start'} verticalAnchor='middle' fontSize={getFontSize(config.fontSize)} fill={'black'}>
338
+ <Text
339
+ className={`${d[config.xAxis.dataKey]}`}
340
+ x={0}
341
+ y={yScale(i)}
342
+ textAnchor={'start'}
343
+ verticalAnchor='middle'
344
+ fill={'black'}
345
+ >
216
346
  {d[config.xAxis.dataKey]}
217
347
  </Text>
218
348
  )
@@ -220,7 +350,7 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
220
350
 
221
351
  {/* X Axis Datakey Header */}
222
352
  {!forestPlot.hideDateCategoryCol && config.xAxis.dataKey && (
223
- <Text className={config.xAxis.dataKey} x={0} y={0} textAnchor={'start'} verticalAnchor='start' fontSize={getFontSize(config.fontSize)} fill={'black'}>
353
+ <Text className={config.xAxis.dataKey} x={0} y={0} textAnchor={'start'} verticalAnchor='start' fill={'black'}>
224
354
  {config.xAxis.dataKey}
225
355
  </Text>
226
356
  )}
@@ -228,7 +358,14 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
228
358
  {/* column headers */}
229
359
  {columnsOnChart.map(column => {
230
360
  return (
231
- <Text className={`${column.label}`} x={column.forestPlotAlignRight ? width : column.forestPlotStartingPoint} y={0} textAnchor={column.forestPlotAlignRight ? 'end' : 'start'} verticalAnchor='start' fontSize={getFontSize(config.fontSize)} fill={'black'}>
361
+ <Text
362
+ className={`${column.label}`}
363
+ x={column.forestPlotAlignRight ? width : column.forestPlotStartingPoint}
364
+ y={0}
365
+ textAnchor={column.forestPlotAlignRight ? 'end' : 'start'}
366
+ verticalAnchor='start'
367
+ fill={'black'}
368
+ >
232
369
  {column.label}
233
370
  </Text>
234
371
  )
@@ -236,13 +373,26 @@ const ForestPlot = ({ xScale, yScale, config, height, width, handleTooltipMouseO
236
373
 
237
374
  {/* left bottom label */}
238
375
  {forestPlot.leftLabel && (
239
- <Text className='forest-plot__left-label' x={forestPlot.type === 'Linear' ? xScale(0) - 25 : xScale(1) - 25} y={height + labelPosition} textAnchor='end' verticalAnchor='start'>
376
+ <Text
377
+ className='forest-plot__left-label'
378
+ x={forestPlot.type === 'Linear' ? xScale(0) - 25 : xScale(1) - 25}
379
+ y={height + labelPosition}
380
+ textAnchor='end'
381
+ verticalAnchor='start'
382
+ >
240
383
  {forestPlot.leftLabel}
241
384
  </Text>
242
385
  )}
243
386
 
244
387
  {forestPlot.rightLabel && (
245
- <Text innerRef={forestPlotRightLabelRef} className='forest-plot__right-label' x={forestPlot.type === 'Linear' ? xScale(0) + 25 : xScale(1) + 25} y={height + labelPosition} textAnchor='start' verticalAnchor='start'>
388
+ <Text
389
+ innerRef={forestPlotRightLabelRef}
390
+ className='forest-plot__right-label'
391
+ x={forestPlot.type === 'Linear' ? xScale(0) + 25 : xScale(1) + 25}
392
+ y={height + labelPosition}
393
+ textAnchor='start'
394
+ verticalAnchor='start'
395
+ >
246
396
  {forestPlot.rightLabel}
247
397
  </Text>
248
398
  )}
@@ -1,23 +1,22 @@
1
1
  import parse from 'html-react-parser'
2
+ import React from 'react'
2
3
  import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
3
4
  import LegendShape from '@cdc/core/components/LegendShape'
4
5
  import Button from '@cdc/core/components/elements/Button'
5
6
  import { getLegendClasses } from './helpers/getLegendClasses'
6
7
  import { useHighlightedBars } from '../../hooks/useHighlightedBars'
7
- import { handleLineType } from '../../helpers/handleLineType'
8
-
9
8
  import { getMarginTop, getGradientConfig, getMarginBottom } from './helpers/index'
10
- import { Line } from '@visx/shape'
11
9
  import { Label } from '../../types/Label'
12
10
  import { ChartConfig, ViewportSize } from '../../types/ChartConfig'
13
11
  import { ColorScale } from '../../types/ChartContext'
14
- import { forwardRef, useState } from 'react'
12
+ import { forwardRef } from 'react'
15
13
  import LegendSuppression from './Legend.Suppression'
16
14
  import LegendGradient from '@cdc/core/components/Legend/Legend.Gradient'
17
15
  import { DimensionsType } from '@cdc/core/types/Dimensions'
18
16
  import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
17
+ import LegendLineShape from './LegendLine.Shape'
19
18
 
20
- const LEGEND_PADDING = 30
19
+ const LEGEND_PADDING = 36
21
20
 
22
21
  export interface LegendProps {
23
22
  colorScale: ColorScale
@@ -25,7 +24,7 @@ export interface LegendProps {
25
24
  currentViewport: ViewportSize
26
25
  formatLabels: (labels: Label[]) => Label[]
27
26
  highlight: Function
28
- highlightReset: Function
27
+ handleShowAll: Function
29
28
  ref: React.Ref<() => void>
30
29
  seriesHighlight: string[]
31
30
  skipId: string
@@ -40,7 +39,7 @@ const Legend: React.FC<LegendProps> = forwardRef(
40
39
  colorScale,
41
40
  seriesHighlight,
42
41
  highlight,
43
- highlightReset,
42
+ handleShowAll,
44
43
  currentViewport,
45
44
  formatLabels,
46
45
  skipId = 'legend',
@@ -51,14 +50,13 @@ const Legend: React.FC<LegendProps> = forwardRef(
51
50
  const { innerClasses, containerClasses } = getLegendClasses(config)
52
51
  const { runtime, legend } = config
53
52
 
54
- const [hasSuppression, setHasSuppression] = useState(false)
55
-
56
- const isBottomOrSmallViewport =
57
- legend?.position === 'bottom' || (isLegendWrapViewport(currentViewport) && !legend.hide)
53
+ const isLegendBottom =
54
+ legend?.position === 'bottom' ||
55
+ (isLegendWrapViewport(currentViewport) && !legend.hide && legend?.position !== 'top')
58
56
 
59
57
  const legendClasses = {
60
- marginBottom: getMarginBottom(config, hasSuppression),
61
- marginTop: getMarginTop(isBottomOrSmallViewport, config)
58
+ marginBottom: getMarginBottom(isLegendBottom, config),
59
+ marginTop: getMarginTop(isLegendBottom, config)
62
60
  }
63
61
 
64
62
  const { HighLightedBarUtils } = useHighlightedBars(config)
@@ -74,8 +72,12 @@ const Legend: React.FC<LegendProps> = forwardRef(
74
72
  aria-label='legend'
75
73
  tabIndex={0}
76
74
  >
77
- {legend.label && <h3>{parse(legend.label)}</h3>}
78
- {legend.description && <p>{parse(legend.description)}</p>}
75
+ {(legend.label || legend.description) && (
76
+ <div className={legend.description ? 'mb-3' : 'mb-2'}>
77
+ {legend.label && <h3 className='fw-bold'>{parse(legend.label)}</h3>}
78
+ {legend.description && <p className='mt-2'>{parse(legend.description)}</p>}
79
+ </div>
80
+ )}
79
81
  <LegendGradient
80
82
  config={config}
81
83
  {...getGradientConfig(config, formatLabels, colorScale)}
@@ -106,8 +108,10 @@ const Legend: React.FC<LegendProps> = forwardRef(
106
108
  }
107
109
  }
108
110
 
109
- if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
110
- className.push('inactive')
111
+ if (seriesHighlight.length) {
112
+ if (!seriesHighlight.includes(itemName)) {
113
+ className.push('inactive')
114
+ } else className.push('highlighted')
111
115
  }
112
116
 
113
117
  if (config.legend.style === 'gradient') {
@@ -133,15 +137,9 @@ const Legend: React.FC<LegendProps> = forwardRef(
133
137
  >
134
138
  <>
135
139
  {config.visualizationType === 'Line' && config.legend.style === 'lines' ? (
136
- <svg width={40} height={25}>
137
- <Line
138
- from={{ x: 10, y: 10 }}
139
- to={{ x: 40, y: 10 }}
140
- stroke={label.value}
141
- strokeWidth={2}
142
- strokeDasharray={handleLineType(config.series[i]?.type ? config.series[i]?.type : '')}
143
- />
144
- </svg>
140
+ <React.Fragment>
141
+ <LegendLineShape index={i} label={label} config={config} />
142
+ </React.Fragment>
145
143
  ) : (
146
144
  <>
147
145
  <LegendShape
@@ -151,8 +149,7 @@ const Legend: React.FC<LegendProps> = forwardRef(
151
149
  </>
152
150
  )}
153
151
  </>
154
-
155
- <LegendLabel align='left' margin='0 0 0 4px'>
152
+ <LegendLabel align='left' className='m-0'>
156
153
  {label.text}
157
154
  </LegendLabel>
158
155
  </LegendItem>
@@ -191,26 +188,20 @@ const Legend: React.FC<LegendProps> = forwardRef(
191
188
  fill='transparent'
192
189
  borderColor={bar.color ? bar.color : `rgba(255, 102, 1)`}
193
190
  />{' '}
194
- <LegendLabel align='left' margin='0 0 0 4px'>
195
- {bar.legendLabel ? bar.legendLabel : bar.value}
196
- </LegendLabel>
191
+ <LegendLabel align='left'>{bar.legendLabel ? bar.legendLabel : bar.value}</LegendLabel>
197
192
  </LegendItem>
198
193
  )
199
194
  })}
200
195
  </div>
201
196
 
202
- <LegendSuppression
203
- config={config}
204
- isBottomOrSmallViewport={isBottomOrSmallViewport}
205
- setHasSuppression={setHasSuppression}
206
- />
197
+ <LegendSuppression config={config} isLegendBottom={isLegendBottom} />
207
198
  </>
208
199
  )
209
200
  }}
210
201
  </LegendOrdinal>
211
202
  {seriesHighlight.length > 0 && (
212
- <Button onClick={labels => highlightReset(labels)} style={{ marginTop: '1rem' }}>
213
- Reset
203
+ <Button onClick={labels => handleShowAll(labels)} style={{ marginTop: '1rem' }}>
204
+ Show All
214
205
  </Button>
215
206
  )}
216
207
  </aside>
@@ -4,10 +4,10 @@ import Icon from '@cdc/core/components/ui/Icon'
4
4
  import { Tooltip as ReactTooltip } from 'react-tooltip'
5
5
  interface LegendProps {
6
6
  config: ChartConfig
7
- isBottomOrSmallViewport: boolean
7
+ isLegendBottom: boolean
8
8
  }
9
9
 
10
- const LegendSuppression: React.FC<LegendProps> = ({ config, isBottomOrSmallViewport, setHasSuppression }) => {
10
+ const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom }) => {
11
11
  const { preliminaryData, visualizationType, visualizationSubType, legend } = config
12
12
 
13
13
  const hasOpenCircleEffects = () =>
@@ -98,15 +98,13 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isBottomOrSmallViewp
98
98
  }
99
99
 
100
100
  const getLegendContainerClass = () =>
101
- legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : ''
101
+ legend.singleRow && isLegendBottom ? 'legend-container__inner bottom single-row' : ''
102
102
 
103
103
  const shouldShowSuppressedInfo = () =>
104
104
  !config.legend.hideSuppressionLink &&
105
105
  config.visualizationSubType !== 'stacked' &&
106
106
  preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol))
107
107
 
108
- setHasSuppression(shouldShowSuppressedInfo())
109
-
110
108
  return (
111
109
  <React.Fragment>
112
110
  {hasOpenCircleEffects() && (
@@ -13,7 +13,7 @@ const Legend = forwardRef((props, ref) => {
13
13
  seriesHighlight,
14
14
  highlight,
15
15
  tableData,
16
- highlightReset,
16
+ handleShowAll,
17
17
  transformedData: data,
18
18
  currentViewport,
19
19
  dimensions,
@@ -35,7 +35,7 @@ const Legend = forwardRef((props, ref) => {
35
35
  colorScale={colorScale}
36
36
  seriesHighlight={seriesHighlight}
37
37
  highlight={highlight}
38
- highlightReset={highlightReset}
38
+ handleShowAll={handleShowAll}
39
39
  currentViewport={currentViewport}
40
40
  formatLabels={createLegendLabels}
41
41
  />
@@ -0,0 +1,51 @@
1
+ import React from 'react'
2
+ import { handleLineType } from '../../helpers/handleLineType'
3
+ import { Line } from '@visx/shape'
4
+ import { GlyphDiamond, GlyphCircle, GlyphSquare, GlyphTriangle, GlyphCross, Glyph as CustomGlyph } from '@visx/glyph'
5
+ import { Text } from '@visx/text'
6
+
7
+ // Define glyph shapes
8
+ const Glyphs = [
9
+ GlyphCircle,
10
+ GlyphSquare,
11
+ GlyphTriangle,
12
+ GlyphDiamond,
13
+ GlyphTriangle,
14
+ GlyphCross,
15
+ ({ fill }: { fill: string }) => (
16
+ <CustomGlyph>
17
+ {/* Render Filled Pentagon */}
18
+ <Text fill={fill} fontSize={14} textAnchor='middle' verticalAnchor='middle'>
19
+ &#x2B1F;
20
+ </Text>
21
+ </CustomGlyph>
22
+ )
23
+ ]
24
+
25
+ const LegendLineShape = props => {
26
+ const { config, label, index } = props
27
+ const isReversedTriangle = index === 4
28
+
29
+ const Shape =
30
+ Glyphs[config.visual.lineDatapointSymbol === 'standard' && index < config.visual.maximumShapeAmount ? index : 0]
31
+ const transform = `translate(${15}, ${3}) ${isReversedTriangle ? 'rotate(180)' : ''}`
32
+
33
+ return (
34
+ <svg width={30} height={10} style={{ overflow: 'visible' }} className='me-2'>
35
+ {/* Render line */}
36
+ <Line
37
+ from={{ x: 0, y: 3 }}
38
+ to={{ x: 30, y: 3 }}
39
+ stroke={label.value}
40
+ strokeWidth={2}
41
+ strokeDasharray={handleLineType(config.series[index]?.type || '')}
42
+ />
43
+
44
+ <g display={config.legend.hasShape ? 'block' : 'none'} transform={transform}>
45
+ <Shape fillOpacity={1} fill={label.value} />
46
+ </g>
47
+ </svg>
48
+ )
49
+ }
50
+
51
+ export default LegendLineShape
@@ -3,13 +3,22 @@ import { FaStar } from 'react-icons/fa'
3
3
  import { Label } from '../../../types/Label'
4
4
  import { ColorScale, TransformedData } from '../../../types/ChartContext'
5
5
  import { ChartConfig } from '../../../types/ChartConfig'
6
+ import _ from 'lodash'
6
7
 
7
8
  export const createFormatLabels =
8
9
  (config: ChartConfig, tableData: Object[], data: TransformedData[], colorScale: ColorScale) =>
9
10
  (defaultLabels: Label[]): Label[] => {
10
- const { visualizationType, visualizationSubType, series, runtime } = config
11
-
12
- const reverseLabels = labels => (config.legend.reverseLabelOrder && config.legend?.position === 'bottom' ? labels.reverse() : labels)
11
+ const { visualizationType, visualizationSubType, series, runtime, legend } = config
12
+ const sortVertical = labels =>
13
+ legend.verticalSorted
14
+ ? _.sortBy(_.cloneDeep(labels), label => {
15
+ const match = label.datum?.match(/-?\d+(\.\d+)?/)
16
+ return match ? parseFloat(match[0]) : Number.MAX_SAFE_INTEGER
17
+ })
18
+ : labels
19
+ const reverseLabels = labels => {
20
+ return config.legend.reverseLabelOrder ? sortVertical(labels).reverse() : sortVertical(labels)
21
+ }
13
22
  const colorCode = config.legend?.colorCode
14
23
  if (visualizationType === 'Deviation Bar') {
15
24
  const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette]
@@ -62,7 +71,11 @@ export const createFormatLabels =
62
71
  // loop through each stage/group/area on the chart and create a label
63
72
  config.runtime?.forecastingSeriesKeys?.map((outerGroup, index) => {
64
73
  return outerGroup?.stages?.map((stage, index) => {
65
- let colorValue = sequentialPalettes[stage.color]?.[2] ? sequentialPalettes[stage.color]?.[2] : colorPalettes[stage.color]?.[2] ? colorPalettes[stage.color]?.[2] : '#ccc'
74
+ let colorValue = sequentialPalettes[stage.color]?.[2]
75
+ ? sequentialPalettes[stage.color]?.[2]
76
+ : colorPalettes[stage.color]?.[2]
77
+ ? colorPalettes[stage.color]?.[2]
78
+ : '#ccc'
66
79
 
67
80
  const newLabel = {
68
81
  datum: stage.key,
@@ -93,31 +106,21 @@ export const createFormatLabels =
93
106
  return reverseLabels(seriesLabels)
94
107
  }
95
108
 
96
- // DEV-4161: replaceable series name in the legend
97
- const hasNewSeriesName = config.series.filter(item => !!item.name).length > 0
98
- if (hasNewSeriesName) {
99
- //store unique values to Set by colorCode
100
- const set = new Set()
101
-
102
- config.series.forEach(d => {
103
- set.add(d.name || d.dataKey)
104
- })
105
-
106
- // create labels with unique values
107
- const uniqueLabels = Array.from(set).map((val, i) => {
108
- const newLabel = {
109
- datum: val,
110
- index: i,
111
- text: val,
112
- value: colorScale(val)
113
- }
114
- return newLabel
115
- })
116
-
109
+ if (config.series.some(item => item.name)) {
110
+ const uniqueLabels = Array.from(new Set(config.series.map(d => d.name || d.dataKey))).map((val, i) => ({
111
+ datum: val,
112
+ index: i,
113
+ text: val,
114
+ value: colorScale(val)
115
+ }))
117
116
  return reverseLabels(uniqueLabels)
118
117
  }
119
118
 
120
- if ((config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && config.visualizationSubType === 'regular' && config.suppressedData) {
119
+ if (
120
+ (config.visualizationType === 'Bar' || config.visualizationType === 'Combo') &&
121
+ config.visualizationSubType === 'regular' &&
122
+ config.suppressedData
123
+ ) {
121
124
  const lastIndex = defaultLabels.length - 1
122
125
  let newLabels = []
123
126