@cdc/chart 4.24.12 → 4.25.1

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 (72) hide show
  1. package/dist/cdcchart.js +79611 -78971
  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 +394 -30
  5. package/examples/private/ehdi.json +29939 -0
  6. package/examples/private/not-loading.json +360 -0
  7. package/index.html +7 -14
  8. package/package.json +2 -2
  9. package/src/CdcChart.tsx +92 -1512
  10. package/src/CdcChartComponent.tsx +1105 -0
  11. package/src/_stories/Chart.Anchors.stories.tsx +1 -1
  12. package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
  13. package/src/_stories/Chart.DynamicSeries.stories.tsx +1 -1
  14. package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
  15. package/src/_stories/Chart.ScatterPlot.stories.tsx +19 -0
  16. package/src/_stories/Chart.tooltip.stories.tsx +1 -2
  17. package/src/_stories/ChartAnnotation.stories.tsx +1 -1
  18. package/src/_stories/ChartAxisLabels.stories.tsx +1 -1
  19. package/src/_stories/ChartAxisTitles.stories.tsx +1 -1
  20. package/src/_stories/ChartEditor.stories.tsx +1 -1
  21. package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
  22. package/src/_stories/ChartLine.Symbols.stories.tsx +18 -0
  23. package/src/_stories/ChartPrefixSuffix.stories.tsx +1 -1
  24. package/src/_stories/_mock/line_chart_symbols.json +437 -0
  25. package/src/_stories/_mock/scatterplot-image-download.json +1244 -0
  26. package/src/components/Annotations/components/AnnotationDraggable.tsx +3 -11
  27. package/src/components/Annotations/components/AnnotationDropdown.tsx +3 -3
  28. package/src/components/Axis/Categorical.Axis.tsx +3 -4
  29. package/src/components/BarChart/components/BarChart.Horizontal.tsx +14 -5
  30. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +10 -4
  31. package/src/components/BarChart/components/BarChart.Vertical.tsx +2 -2
  32. package/src/components/BoxPlot/BoxPlot.tsx +34 -32
  33. package/src/components/BoxPlot/helpers/index.ts +108 -18
  34. package/src/components/DeviationBar.jsx +2 -6
  35. package/src/components/EditorPanel/EditorPanel.tsx +62 -6
  36. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +4 -0
  37. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +44 -7
  38. package/src/components/ForestPlot/ForestPlot.tsx +176 -26
  39. package/src/components/Legend/Legend.Component.tsx +29 -38
  40. package/src/components/Legend/Legend.Suppression.tsx +3 -5
  41. package/src/components/Legend/Legend.tsx +2 -2
  42. package/src/components/Legend/LegendLine.Shape.tsx +51 -0
  43. package/src/components/Legend/helpers/createFormatLabels.tsx +29 -26
  44. package/src/components/Legend/helpers/getLegendClasses.ts +20 -38
  45. package/src/components/Legend/helpers/index.ts +14 -7
  46. package/src/components/Legend/tests/getLegendClasses.test.ts +3 -20
  47. package/src/components/LineChart/components/LineChart.Circle.tsx +90 -88
  48. package/src/components/LineChart/index.tsx +4 -0
  49. package/src/components/LinearChart.tsx +65 -31
  50. package/src/components/PairedBarChart.jsx +2 -9
  51. package/src/components/ZoomBrush.tsx +5 -7
  52. package/src/data/initial-state.js +6 -3
  53. package/src/helpers/getBoxPlotConfig.ts +68 -0
  54. package/src/helpers/getColorScale.ts +28 -0
  55. package/src/helpers/getComboChartConfig.ts +42 -0
  56. package/src/helpers/getExcludedData.ts +37 -0
  57. package/src/helpers/getTopAxis.ts +7 -0
  58. package/src/hooks/useBarChart.ts +28 -9
  59. package/src/hooks/{useHighlightedBars.js → useHighlightedBars.ts} +2 -1
  60. package/src/hooks/useIntersectionObserver.ts +37 -0
  61. package/src/hooks/useMinMax.ts +4 -0
  62. package/src/hooks/useReduceData.ts +1 -1
  63. package/src/hooks/useTooltip.tsx +9 -1
  64. package/src/index.jsx +1 -0
  65. package/src/scss/DataTable.scss +0 -5
  66. package/src/scss/main.scss +30 -115
  67. package/src/types/ChartConfig.ts +6 -3
  68. package/src/types/ChartContext.ts +1 -3
  69. package/src/helpers/getQuartiles.ts +0 -27
  70. package/src/hooks/useColorScale.ts +0 -50
  71. package/src/hooks/useIntersectionObserver.jsx +0 -29
  72. package/src/hooks/useTopAxis.js +0 -6
@@ -610,7 +610,7 @@ const EditorPanel = () => {
610
610
  lineOptions,
611
611
  rawData,
612
612
  highlight,
613
- highlightReset,
613
+ handleShowAll,
614
614
  dimensions
615
615
  } = useContext<ChartContext>(ConfigContext)
616
616
 
@@ -906,7 +906,7 @@ const EditorPanel = () => {
906
906
  return Object.keys(columns)
907
907
  }
908
908
 
909
- const getLegendStyleOptions = (option: 'style' | 'subStyle'): string[] => {
909
+ const getLegendStyleOptions = (option: 'style' | 'subStyle' | 'shapes'): string[] => {
910
910
  const options: string[] = []
911
911
 
912
912
  switch (option) {
@@ -963,7 +963,7 @@ const EditorPanel = () => {
963
963
 
964
964
  const convertStateToConfig = () => {
965
965
  let strippedState = JSON.parse(JSON.stringify(config))
966
- if (false === missingRequiredSections()) {
966
+ if (false === missingRequiredSections(config)) {
967
967
  delete strippedState.newViz
968
968
  }
969
969
  delete strippedState.runtime
@@ -1631,8 +1631,19 @@ const EditorPanel = () => {
1631
1631
  value={config.yAxis.label}
1632
1632
  section='yAxis'
1633
1633
  fieldName='label'
1634
- label='Label '
1634
+ label='Label'
1635
1635
  updateField={updateField}
1636
+ maxLength={35}
1637
+ tooltip={
1638
+ <Tooltip style={{ textTransform: 'none' }}>
1639
+ <Tooltip.Target>
1640
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1641
+ </Tooltip.Target>
1642
+ <Tooltip.Content>
1643
+ <p>35 character limit</p>
1644
+ </Tooltip.Content>
1645
+ </Tooltip>
1646
+ }
1636
1647
  />
1637
1648
  {config.runtime.seriesKeys &&
1638
1649
  config.runtime.seriesKeys.length === 1 &&
@@ -2306,6 +2317,17 @@ const EditorPanel = () => {
2306
2317
  fieldName='rightLabel'
2307
2318
  label='Label'
2308
2319
  updateField={updateField}
2320
+ maxLength={35}
2321
+ tooltip={
2322
+ <Tooltip style={{ textTransform: 'none' }}>
2323
+ <Tooltip.Target>
2324
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2325
+ </Tooltip.Target>
2326
+ <Tooltip.Content>
2327
+ <p>35 character limit</p>
2328
+ </Tooltip.Content>
2329
+ </Tooltip>
2330
+ }
2309
2331
  />
2310
2332
  <TextField
2311
2333
  value={config.yAxis.rightNumTicks}
@@ -2596,6 +2618,17 @@ const EditorPanel = () => {
2596
2618
  fieldName='label'
2597
2619
  label='Label'
2598
2620
  updateField={updateField}
2621
+ maxLength={35}
2622
+ tooltip={
2623
+ <Tooltip style={{ textTransform: 'none' }}>
2624
+ <Tooltip.Target>
2625
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2626
+ </Tooltip.Target>
2627
+ <Tooltip.Content>
2628
+ <p>35 character limit</p>
2629
+ </Tooltip.Content>
2630
+ </Tooltip>
2631
+ }
2599
2632
  />
2600
2633
 
2601
2634
  {config.xAxis.type === 'continuous' && (
@@ -3658,6 +3691,27 @@ const EditorPanel = () => {
3658
3691
  updateField={updateField}
3659
3692
  options={getLegendStyleOptions('style')}
3660
3693
  />
3694
+ <CheckBox
3695
+ tooltip={
3696
+ <Tooltip style={{ textTransform: 'none' }}>
3697
+ <Tooltip.Target>
3698
+ <Icon
3699
+ display='question'
3700
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
3701
+ />
3702
+ </Tooltip.Target>
3703
+ <Tooltip.Content>
3704
+ <p>Choose option Shapes in Line Datapoint Symbols to display.</p>
3705
+ </Tooltip.Content>
3706
+ </Tooltip>
3707
+ }
3708
+ display={!config.legend.hide && config.legend.style === 'lines'}
3709
+ value={config.legend.hasShape}
3710
+ section='legend'
3711
+ fieldName='hasShape'
3712
+ label='Shapes'
3713
+ updateField={updateField}
3714
+ />
3661
3715
 
3662
3716
  <Select
3663
3717
  display={!config.legend.hide && config.legend.style === 'gradient'}
@@ -3797,7 +3851,7 @@ const EditorPanel = () => {
3797
3851
  updatedSeriesHighlight.splice(i, 1)
3798
3852
  updateField('legend', null, 'seriesHighlight', updatedSeriesHighlight)
3799
3853
  if (!updatedSeriesHighlight.length) {
3800
- highlightReset()
3854
+ handleShowAll()
3801
3855
  }
3802
3856
  }}
3803
3857
  >
@@ -3887,7 +3941,9 @@ const EditorPanel = () => {
3887
3941
  display={
3888
3942
  ['bottom', 'top'].includes(config.legend.position) &&
3889
3943
  !config.legend.hide &&
3890
- config.legend.style !== 'gradient'
3944
+ config.legend.style !== 'gradient' &&
3945
+ !config.legend.singleRow &&
3946
+ !config.legend.singleRow
3891
3947
  }
3892
3948
  value={config.legend.verticalSorted}
3893
3949
  section='legend'
@@ -209,6 +209,10 @@ const PanelGeneral: FC<PanelProps> = props => {
209
209
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
210
210
  </Tooltip.Target>
211
211
  <Tooltip.Content>
212
+ <p>
213
+ Recommended set to display for Section 508 compliance.
214
+ </p>
215
+ <hr/>
212
216
  <p>
213
217
  Selecting this option will <i> not </i> hide the display of "zero value", "suppressed data", or
214
218
  "missing data" indicators on the chart (if applicable).
@@ -136,15 +136,23 @@ const PanelVisual: FC<PanelProps> = props => {
136
136
  />
137
137
  </fieldset>
138
138
  )}
139
- <Select
140
- value={config.fontSize}
141
- fieldName='fontSize'
142
- label='Font Size'
143
- updateField={updateField}
144
- options={['small', 'medium', 'large']}
145
- />
146
139
  {visHasBarBorders() && (
147
140
  <Select
141
+ tooltip={
142
+ <Tooltip style={{ textTransform: 'none' }}>
143
+ <Tooltip.Target>
144
+ <Icon
145
+ display='question'
146
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
147
+ />
148
+ </Tooltip.Target>
149
+ <Tooltip.Content>
150
+ <p>
151
+ Recommended set to display for Section 508 compliance.
152
+ </p>
153
+ </Tooltip.Content>
154
+ </Tooltip>
155
+ }
148
156
  value={config.barHasBorder}
149
157
  fieldName='barHasBorder'
150
158
  label='Bar Borders'
@@ -171,6 +179,35 @@ const PanelVisual: FC<PanelProps> = props => {
171
179
  config.visualizationType === 'Combo') ||
172
180
  config.visualizationType === 'Line') && (
173
181
  <>
182
+ <Select
183
+ tooltip={
184
+ <Tooltip style={{ textTransform: 'none' }}>
185
+ <Tooltip.Target>
186
+ <Icon
187
+ display='question'
188
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
189
+ />
190
+ </Tooltip.Target>
191
+ <Tooltip.Content>
192
+ <p>
193
+ Shapes will appear in the following order: circle, square, triangle, diamond, and inverted
194
+ triangle. Use with a maximum of 5 data points.
195
+ </p>
196
+ </Tooltip.Content>
197
+ </Tooltip>
198
+ }
199
+ value={config.visual.lineDatapointSymbol}
200
+ section='visual'
201
+ fieldName='lineDatapointSymbol'
202
+ label='Line Datapoint Symbols'
203
+ updateField={updateField}
204
+ options={['none', 'standard']}
205
+ />
206
+ {config.series.length > config.visual.maximumShapeAmount &&
207
+ config.visual.lineDatapointSymbol === 'standard' && (
208
+ <small className='text-danger'>Standard only supports up to 7 data points</small>
209
+ )}
210
+
174
211
  <Select
175
212
  value={config.lineDatapointStyle}
176
213
  fieldName='lineDatapointStyle'
@@ -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>