@cdc/chart 4.23.10 → 4.23.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 (56) hide show
  1. package/dist/cdcchart.js +30989 -29057
  2. package/examples/feature/bar/example-bar-chart.json +1 -46
  3. package/examples/feature/bar/lollipop.json +156 -0
  4. package/examples/feature/combo/planet-combo-example-config.json +99 -9
  5. package/examples/feature/dev-4261.json +399 -0
  6. package/examples/feature/forest-plot/{broken.json → linear.json} +55 -50
  7. package/examples/feature/forest-plot/logarithmic.json +306 -0
  8. package/examples/feature/line/line-points.json +340 -0
  9. package/examples/feature/regions/index.json +462 -0
  10. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +181 -48
  11. package/examples/sparkline-multilple.json +846 -0
  12. package/index.html +10 -6
  13. package/package.json +3 -3
  14. package/src/CdcChart.tsx +75 -63
  15. package/src/_stories/Chart.stories.tsx +188 -0
  16. package/src/_stories/Chart.tooltip.stories.tsx +305 -0
  17. package/src/_stories/ChartBrush.stories.tsx +19 -0
  18. package/src/_stories/ChartSuppress.stories.tsx +19 -0
  19. package/src/_stories/_mock/brush_mock.json +393 -0
  20. package/src/_stories/_mock/suppress_mock.json +911 -0
  21. package/src/components/AreaChart.Stacked.jsx +4 -5
  22. package/src/components/AreaChart.jsx +6 -35
  23. package/src/components/{BarChart.Horizontal.jsx → BarChart.Horizontal.tsx} +106 -29
  24. package/src/components/{BarChart.StackedHorizontal.jsx → BarChart.StackedHorizontal.tsx} +22 -17
  25. package/src/components/{BarChart.StackedVertical.jsx → BarChart.StackedVertical.tsx} +22 -26
  26. package/src/components/{BarChart.Vertical.jsx → BarChart.Vertical.tsx} +175 -31
  27. package/src/components/BarChart.jsx +1 -1
  28. package/src/components/DeviationBar.jsx +4 -3
  29. package/src/components/EditorPanel.jsx +176 -50
  30. package/src/components/ForestPlot/ForestPlotProps.ts +11 -0
  31. package/src/components/ForestPlot/Readme.md +0 -0
  32. package/src/components/ForestPlot/index.scss +1 -0
  33. package/src/components/{ForestPlot.jsx → ForestPlot/index.tsx} +51 -31
  34. package/src/components/ForestPlotSettings.jsx +162 -113
  35. package/src/components/Legend.jsx +36 -3
  36. package/src/components/{LineChart.Circle.tsx → LineChart/LineChart.Circle.tsx} +26 -29
  37. package/src/components/LineChart/LineChartProps.ts +17 -0
  38. package/src/components/LineChart/index.scss +1 -0
  39. package/src/components/{LineChart.tsx → LineChart/index.tsx} +64 -35
  40. package/src/components/LinearChart.jsx +120 -60
  41. package/src/components/PairedBarChart.jsx +2 -2
  42. package/src/components/Series.jsx +22 -3
  43. package/src/components/ZoomBrush.tsx +168 -0
  44. package/src/data/initial-state.js +27 -12
  45. package/src/hooks/useBarChart.js +71 -7
  46. package/src/hooks/useColorScale.ts +50 -0
  47. package/src/hooks/useEditorPermissions.js +22 -4
  48. package/src/hooks/{useMinMax.js → useMinMax.ts} +75 -23
  49. package/src/hooks/{useRightAxis.js → useRightAxis.ts} +10 -2
  50. package/src/hooks/{useScales.js → useScales.ts} +64 -17
  51. package/src/hooks/useTooltip.jsx +68 -41
  52. package/src/scss/main.scss +3 -35
  53. package/src/types/ChartConfig.ts +43 -0
  54. package/src/types/ChartContext.ts +38 -0
  55. package/src/types/ChartProps.ts +7 -0
  56. package/src/types/ForestPlot.ts +60 -0
@@ -26,6 +26,7 @@ import ConfigContext from '../ConfigContext'
26
26
  import useReduceData from '../hooks/useReduceData'
27
27
  import useRightAxis from '../hooks/useRightAxis'
28
28
  import WarningImage from '../images/warning.svg'
29
+ import useMinMax from './../hooks/useMinMax'
29
30
 
30
31
  /* eslint-disable react-hooks/rules-of-hooks */
31
32
  const TextField = memo(({ label, tooltip, section = null, subsection = null, fieldName, updateField, value: stateValue, type = 'input', i = null, min = null, ...attributes }) => {
@@ -132,6 +133,81 @@ const Select = memo(({ label, value, options, fieldName, section = null, subsect
132
133
  )
133
134
  })
134
135
 
136
+ const DataSuppression = memo(({ config, updateConfig, data }) => {
137
+ const getColumnOptions = () => {
138
+ const keys = new Set()
139
+ data.forEach(d => {
140
+ Object.keys(d).forEach(key => {
141
+ keys.add(key)
142
+ })
143
+ })
144
+ return [...keys]
145
+ }
146
+
147
+ const getIconOptions = () => {
148
+ return ['star']
149
+ }
150
+
151
+ let removeColumn = i => {
152
+ let suppressedData = []
153
+
154
+ if (config.suppressedData) {
155
+ suppressedData = [...config.suppressedData]
156
+ }
157
+
158
+ suppressedData.splice(i, 1)
159
+
160
+ updateConfig({ ...config, suppressedData })
161
+ }
162
+
163
+ let addColumn = () => {
164
+ let suppressedData = config.suppressedData ? [...config.suppressedData] : []
165
+ suppressedData.push({ label: '', column: '', value: '', icon: '' })
166
+ updateConfig({ ...config, suppressedData })
167
+ }
168
+
169
+ let update = (fieldName, value, i) => {
170
+ let suppressedData = []
171
+
172
+ if (config.suppressedData) {
173
+ suppressedData = [...config.suppressedData]
174
+ }
175
+
176
+ suppressedData[i][fieldName] = value
177
+ updateConfig({ ...config, suppressedData })
178
+ }
179
+
180
+ return (
181
+ <>
182
+ {config.suppressedData &&
183
+ config.suppressedData.map(({ label, column, value, icon }, i) => {
184
+ return (
185
+ <div key={`suppressed-${i}`} className='edit-block'>
186
+ <button
187
+ type='button'
188
+ className='remove-column'
189
+ onClick={event => {
190
+ event.preventDefault()
191
+ removeColumn(i)
192
+ }}
193
+ >
194
+ Remove
195
+ </button>
196
+ <Select value={column} initial='Select' fieldName='column' label='Column' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getColumnOptions()} />
197
+ <TextField value={value} fieldName='value' label='Value' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} />
198
+ <Select value={icon} initial='Select' fieldName='icon' label='Icon' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getIconOptions()} />
199
+ <TextField value={label} fieldName='label' label='Label' placeholder='suppressed' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} />
200
+ </div>
201
+ )
202
+ })}
203
+
204
+ <button type='button' onClick={addColumn} className='btn full-width'>
205
+ Add Suppression Class
206
+ </button>
207
+ </>
208
+ )
209
+ })
210
+
135
211
  const Regions = memo(({ config, updateConfig }) => {
136
212
  let regionUpdate = (fieldName, value, i) => {
137
213
  let regions = []
@@ -192,7 +268,22 @@ const Regions = memo(({ config, updateConfig }) => {
192
268
  <TextField value={background} label='Background' fieldName='background' updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)} />
193
269
  </div>
194
270
  <div className='two-col-inputs'>
195
- <TextField value={from} label='From Value' fieldName='from' updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)} />
271
+ <TextField
272
+ value={from}
273
+ label='From Value'
274
+ fieldName='from'
275
+ updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
276
+ tooltip={
277
+ <Tooltip style={{ textTransform: 'none' }}>
278
+ <Tooltip.Target>
279
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
280
+ </Tooltip.Target>
281
+ <Tooltip.Content>
282
+ <p>The date needs to be in the original format of the data. Not the displayed format of the data.</p>
283
+ </Tooltip.Content>
284
+ </Tooltip>
285
+ }
286
+ />
196
287
  <TextField value={to} label='To Value' fieldName='to' updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)} />
197
288
  </div>
198
289
  </div>
@@ -219,6 +310,9 @@ const EditorPanel = () => {
219
310
 
220
311
  const { twoColorPalettes, sequential, nonSequential } = useColorPalette(config, updateConfig)
221
312
 
313
+ const properties = { data, config }
314
+ const { leftMax, rightMax } = useMinMax(properties)
315
+
222
316
  const {
223
317
  enabledChartTypes,
224
318
  headerColors,
@@ -233,6 +327,7 @@ const EditorPanel = () => {
233
327
  visHasDataCutoff,
234
328
  visCanAnimate,
235
329
  visHasLegend,
330
+ visHasBrushChart,
236
331
  visSupportsDateCategoryAxisLabel,
237
332
  visSupportsDateCategoryAxisLine,
238
333
  visSupportsDateCategoryAxisTicks,
@@ -254,7 +349,8 @@ const EditorPanel = () => {
254
349
  visSupportsTooltipOpacity,
255
350
  visSupportsRankByValue,
256
351
  visSupportsResponsiveTicks,
257
- visSupportsDateCategoryHeight
352
+ visSupportsDateCategoryHeight,
353
+ visHasDataSuppression
258
354
  } = useEditorPermissions()
259
355
 
260
356
  // argument acts as props
@@ -280,11 +376,6 @@ const EditorPanel = () => {
280
376
  ...config,
281
377
  series: newSeries
282
378
  })
283
-
284
- // disable brush if categorical - or - for now if not Area Chart
285
- if (config.xAxis.type === 'categorical' || config.visualizationType !== 'Area Chart') {
286
- config.showChartBrush = false
287
- }
288
379
  }, [config.visualizationType]) // eslint-disable-line
289
380
 
290
381
  // Scatter Plots default date/category axis is 'continuous'
@@ -300,6 +391,12 @@ const EditorPanel = () => {
300
391
  }
301
392
  }, [])
302
393
 
394
+ useEffect(() => {
395
+ if (config.visualizationType !== 'Bar') {
396
+ updateConfig({ ...config, tooltips: { ...config.tooltips, singleSeries: false } })
397
+ }
398
+ }, [config.visualizationType])
399
+
303
400
  const { hasRightAxis } = useRightAxis({ config: config, yMax: config.yAxis.size, data: config.data, updateConfig })
304
401
 
305
402
  const getItemStyle = (isDragging, draggableStyle) => ({
@@ -745,23 +842,45 @@ const EditorPanel = () => {
745
842
  }
746
843
 
747
844
  const section = config.orientation === 'horizontal' ? 'xAxis' : 'yAxis'
748
- const [warningMsg, setWarningMsg] = useState({ maxMsg: '', minMsg: '' })
845
+ const [warningMsg, setWarningMsg] = useState({ maxMsg: '', minMsg: '', rightMaxMessage: '', minMsgRight: '' })
749
846
 
750
847
  const validateMaxValue = () => {
751
848
  const enteredValue = config[section].max
849
+ const enteredRightMax = config[section].rightMax
850
+
752
851
  let message = ''
852
+ let rightMaxMessage = ''
853
+
854
+ if (config.visualizationType !== 'Combo') {
855
+ switch (true) {
856
+ case enteredValue && parseFloat(enteredValue) < parseFloat(maxValue) && existPositiveValue:
857
+ message = 'Max value must be more than ' + maxValue
858
+ break
859
+ case enteredValue && parseFloat(enteredValue) < 0 && !existPositiveValue:
860
+ message = 'Value must be more than or equal to 0'
861
+ break
862
+ default:
863
+ message = ''
864
+ }
865
+ }
753
866
 
754
- switch (true) {
755
- case enteredValue && parseFloat(enteredValue) < parseFloat(maxValue) && existPositiveValue:
756
- message = 'Max value must be more than ' + maxValue
757
- break
758
- case enteredValue && parseFloat(enteredValue) < 0 && !existPositiveValue:
759
- message = 'Value must be more than or equal to 0'
760
- break
761
- default:
762
- message = ''
867
+ if (config.visualizationType === 'Combo') {
868
+ switch (true) {
869
+ case enteredValue && parseFloat(enteredValue) < leftMax:
870
+ message = 'Max value must be more than ' + leftMax
871
+ break
872
+ case enteredRightMax && parseFloat(enteredRightMax) < rightMax:
873
+ rightMaxMessage = 'Max value must be more than ' + rightMax
874
+ break
875
+ case enteredValue && parseFloat(enteredValue) < 0 && !existPositiveValue:
876
+ message = 'Value must be more than or equal to 0'
877
+ break
878
+ default:
879
+ message = ''
880
+ }
763
881
  }
764
- setWarningMsg(prevMsg => ({ ...prevMsg, maxMsg: message }))
882
+
883
+ setWarningMsg(prevMsg => ({ ...prevMsg, maxMsg: message, rightMaxMessage: rightMaxMessage }))
765
884
  }
766
885
 
767
886
  const validateMinValue = () => {
@@ -840,7 +959,7 @@ const EditorPanel = () => {
840
959
  ]
841
960
 
842
961
  if (config.data && config.series) {
843
- Object.keys(config.data[0]).map(colName => {
962
+ Object.keys(config.data?.[0] || []).map(colName => {
844
963
  // OMIT ANY COLUMNS THAT ARE IN DATA SERIES!
845
964
  const found = config?.series.some(series => series.dataKey === colName)
846
965
  if (colName !== config.xAxis.dataKey && !found) {
@@ -1092,7 +1211,7 @@ const EditorPanel = () => {
1092
1211
  />
1093
1212
 
1094
1213
  <TextField
1095
- type='text'
1214
+ type='textarea'
1096
1215
  value={config.description}
1097
1216
  fieldName='description'
1098
1217
  label='Subtext/Citation'
@@ -1184,7 +1303,6 @@ const EditorPanel = () => {
1184
1303
  </Series.Wrapper>
1185
1304
  )}
1186
1305
  </>
1187
-
1188
1306
  {config.series && config.series.length <= 1 && config.visualizationType === 'Bar' && (
1189
1307
  <>
1190
1308
  <span className='divider-heading'>Confidence Keys</span>
@@ -1192,8 +1310,8 @@ const EditorPanel = () => {
1192
1310
  <Select value={config.confidenceKeys.lower || ''} section='confidenceKeys' fieldName='lower' label='Lower' updateField={updateField} initial='Select' options={getColumns()} />
1193
1311
  </>
1194
1312
  )}
1195
-
1196
1313
  {visSupportsRankByValue() && config.series && config.series.length === 1 && <Select fieldName='visualizationType' label='Rank by Value' initial='Select' onChange={e => sortSeries(e.target.value)} options={['asc', 'desc']} />}
1314
+ {/* {visHasDataSuppression() && <DataSuppression config={config} updateConfig={updateConfig} data={data} />} */}
1197
1315
  </AccordionItemPanel>
1198
1316
  </AccordionItem>
1199
1317
  )}
@@ -1367,7 +1485,9 @@ const EditorPanel = () => {
1367
1485
  {config.visualizationType !== 'Pie' && (
1368
1486
  <>
1369
1487
  <TextField value={config.yAxis.label} section='yAxis' fieldName='label' label='Label' updateField={updateField} />
1370
- {config.runtime.seriesKeys && config.runtime.seriesKeys.length === 1 && config.visualizationType !== 'Box Plot' && <CheckBox value={config.isLegendValue} fieldName='isLegendValue' label='Use Legend Value in Hover' updateField={updateField} />}
1488
+ {config.runtime.seriesKeys && config.runtime.seriesKeys.length === 1 && !['Box Plot', 'Deviation Bar', 'Forest Plot'].includes(config.visualizationType) && (
1489
+ <CheckBox value={config.isLegendValue} fieldName='isLegendValue' label='Use Legend Value in Hover' updateField={updateField} />
1490
+ )}
1371
1491
  <TextField value={config.yAxis.numTicks} placeholder='Auto' type='number' section='yAxis' fieldName='numTicks' label='Number of ticks' className='number-narrow' updateField={updateField} />
1372
1492
  {config.visualizationType === 'Paired Bar' && <TextField value={config.yAxis.tickRotation || 0} type='number' min='0' section='yAxis' fieldName='tickRotation' label='Tick rotation (Degrees)' className='number-narrow' updateField={updateField} />}
1373
1493
  <TextField
@@ -1505,9 +1625,9 @@ const EditorPanel = () => {
1505
1625
  <CheckBox value={config.yAxis.hideLabel} section='yAxis' fieldName='hideLabel' label='Hide Label' updateField={updateField} />
1506
1626
  <CheckBox value={config.yAxis.hideTicks} section='yAxis' fieldName='hideTicks' label='Hide Ticks' updateField={updateField} />
1507
1627
 
1508
- <TextField value={config.yAxis.max} section='yAxis' fieldName='max' type='number' label='max value' placeholder='Auto' updateField={updateField} />
1628
+ <TextField value={config.yAxis.max} section='yAxis' fieldName='max' type='number' label='left axis max value' placeholder='Auto' updateField={updateField} />
1509
1629
  <span style={{ color: 'red', display: 'block' }}>{warningMsg.maxMsg}</span>
1510
- <TextField value={config.yAxis.min} section='yAxis' fieldName='min' type='number' label='min value' placeholder='Auto' updateField={updateField} />
1630
+ <TextField value={config.yAxis.min} section='yAxis' fieldName='min' type='number' label='left axis min value' placeholder='Auto' updateField={updateField} />
1511
1631
  <span style={{ color: 'red', display: 'block' }}>{warningMsg.minMsg}</span>
1512
1632
  </>
1513
1633
  )
@@ -1826,6 +1946,11 @@ const EditorPanel = () => {
1826
1946
  <CheckBox value={config.yAxis.rightHideAxis} section='yAxis' fieldName='rightHideAxis' label='Hide Axis' updateField={updateField} />
1827
1947
  <CheckBox value={config.yAxis.rightHideLabel} section='yAxis' fieldName='rightHideLabel' label='Hide Label' updateField={updateField} />
1828
1948
  <CheckBox value={config.yAxis.rightHideTicks} section='yAxis' fieldName='rightHideTicks' label='Hide Ticks' updateField={updateField} />
1949
+
1950
+ <TextField value={config.yAxis.max} section='yAxis' fieldName='rightMax' type='number' label='right axis max value' placeholder='Auto' updateField={updateField} />
1951
+ <span style={{ color: 'red', display: 'block' }}>{warningMsg.rightMaxMessage}</span>
1952
+ <TextField value={config.yAxis.min} section='yAxis' fieldName='rightMin' type='number' label='right axis min value' placeholder='Auto' updateField={updateField} />
1953
+ <span style={{ color: 'red', display: 'block' }}>{warningMsg.minMsg}</span>
1829
1954
  </AccordionItemPanel>
1830
1955
  </AccordionItem>
1831
1956
  )}
@@ -1984,6 +2109,7 @@ const EditorPanel = () => {
1984
2109
  }
1985
2110
  updateField={updateField}
1986
2111
  />
2112
+ {/* {visHasBrushChart && <CheckBox value={config.brush.active} section='brush' fieldName='active' label='Brush Slider ' updateField={updateField} />} */}
1987
2113
 
1988
2114
  {config.exclusions.active && (
1989
2115
  <>
@@ -2027,8 +2153,7 @@ const EditorPanel = () => {
2027
2153
 
2028
2154
  {/* Hiding this for now, not interested in moving the axis lines away from chart comp. right now. */}
2029
2155
  {/* <TextField value={config.xAxis.axisPadding} type='number' max={10} min={0} section='xAxis' fieldName='axisPadding' label={'Axis Padding'} className='number-narrow' updateField={updateField} /> */}
2030
-
2031
- {config.xAxis.type === 'continuous' && (
2156
+ {(config.xAxis.type === 'continuous' || config.forestPlot.type === 'Logarithmic') && (
2032
2157
  <>
2033
2158
  <CheckBox value={config.dataFormat.bottomCommas} section='dataFormat' fieldName='bottomCommas' label='Add commas' updateField={updateField} />
2034
2159
  <TextField value={config.dataFormat.bottomRoundTo} type='number' section='dataFormat' fieldName='bottomRoundTo' label='Round to decimal point' className='number-narrow' updateField={updateField} min={0} />
@@ -2428,7 +2553,7 @@ const EditorPanel = () => {
2428
2553
  </AccordionItem>
2429
2554
  )}{' '}
2430
2555
  {/* Columns */}
2431
- {config.visualizationType !== 'Box Plot' && config.table.showVertical && (
2556
+ {config.visualizationType !== 'Box Plot' && (
2432
2557
  <AccordionItem>
2433
2558
  <AccordionItemHeading>
2434
2559
  <AccordionItemButton>Columns</AccordionItemButton>
@@ -2491,32 +2616,33 @@ const EditorPanel = () => {
2491
2616
  </label>
2492
2617
  </li>
2493
2618
  <li>
2494
- <label className='checkbox'>
2495
- <input
2496
- type='checkbox'
2497
- checked={config.columns[val].dataTable}
2498
- onChange={event => {
2499
- editColumn(val, 'dataTable', event.target.checked)
2500
- }}
2501
- />
2502
- <span className='edit-label'>Show in Data Table</span>
2503
- </label>
2619
+ {config.table.showVertical && (
2620
+ <label className='checkbox'>
2621
+ <input
2622
+ type='checkbox'
2623
+ checked={config.columns[val].dataTable}
2624
+ onChange={event => {
2625
+ editColumn(val, 'dataTable', event.target.checked)
2626
+ }}
2627
+ />
2628
+ <span className='edit-label'>Show in Data Table</span>
2629
+ </label>
2630
+ )}
2504
2631
  </li>
2505
2632
  {/* disable for now */}
2506
- {/*
2633
+
2507
2634
  <li>
2508
2635
  <label className='checkbox'>
2509
2636
  <input
2510
2637
  type='checkbox'
2511
- checked={config.columns[val].tooltip}
2638
+ checked={config.columns[val].tooltips || false}
2512
2639
  onChange={event => {
2513
- editColumn(val, 'tooltip', event.target.checked)
2640
+ updateSeriesTooltip(val, event.target.checked)
2514
2641
  }}
2515
2642
  />
2516
- <span className='edit-label'>Display in Tooltips</span>
2643
+ <span className='edit-label'>Show in tooltip</span>
2517
2644
  </label>
2518
2645
  </li>
2519
- */}
2520
2646
 
2521
2647
  {config.visualizationType === 'Forest Plot' && (
2522
2648
  <>
@@ -2684,22 +2810,18 @@ const EditorPanel = () => {
2684
2810
  </Tooltip>
2685
2811
  }
2686
2812
  />
2687
-
2688
2813
  {/* {config.visualizationType === 'Box Plot' &&
2689
2814
  <>
2690
2815
  <CheckBox value={config.boxplot.legend.displayHowToReadText} fieldName='displayHowToReadText' section='boxplot' subsection='legend' label='Display How To Read Text' updateField={updateField} />
2691
2816
  <TextField type='textarea' value={config.boxplot.legend.howToReadText} updateField={updateField} fieldName='howToReadText' section='boxplot' subsection='legend' label='How to read text' />
2692
2817
  </>
2693
2818
  } */}
2694
-
2695
- {config.visualizationType !== 'Box Plot' && <CheckBox value={config.legend.showLegendValuesTooltip ? true : false} section='legend' fieldName='showLegendValuesTooltip' label='Show Legend Values in Tooltip' updateField={updateField} />}
2696
-
2697
2819
  {config.visualizationType === 'Line' && <CheckBox value={config.legend.lineMode} section='legend' fieldName='lineMode' label='Show Lined Style Legend' updateField={updateField} />}
2698
-
2699
2820
  {config.visualizationType === 'Bar' && config.visualizationSubType === 'regular' && config.runtime.seriesKeys.length === 1 && (
2700
2821
  <Select value={config.legend.colorCode} section='legend' fieldName='colorCode' label='Color code by category' initial='Select' updateField={updateField} options={getDataValueOptions(data)} />
2701
2822
  )}
2702
2823
  <Select value={config.legend.behavior} section='legend' fieldName='behavior' label='Legend Behavior (When clicked)' updateField={updateField} options={['highlight', 'isolate']} />
2824
+ {config.legend.behavior === 'highlight' && config.tooltips.singleSeries && <CheckBox value={config.legend.highlightOnHover} section='legend' fieldName='highlightOnHover' label='HIGHLIGHT DATA SERIES ON HOVER' updateField={updateField} />}
2703
2825
  <TextField value={config.legend.label} section='legend' fieldName='label' label='Title' updateField={updateField} />
2704
2826
  <Select value={config.legend.position} section='legend' fieldName='position' label='Position' updateField={updateField} options={['right', 'left', 'bottom']} />
2705
2827
  {config.legend.position === 'bottom' && (
@@ -2906,7 +3028,10 @@ const EditorPanel = () => {
2906
3028
  {/*<CheckBox value={config.animateReplay} fieldName="animateReplay" label="Replay Animation When Filters Are Changed" updateField={updateField} />*/}
2907
3029
 
2908
3030
  {((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') && (
2909
- <Select value={config.lineDatapointStyle} fieldName='lineDatapointStyle' label='Line Datapoint Style' updateField={updateField} options={['hidden', 'hover', 'always show']} />
3031
+ <>
3032
+ <Select value={config.lineDatapointStyle} fieldName='lineDatapointStyle' label='Line Datapoint Style' updateField={updateField} options={['hidden', 'hover', 'always show']} />
3033
+ <Select value={config.lineDatapointColor} fieldName='lineDatapointColor' label='Line Datapoint Color' updateField={updateField} options={['Same as Line', 'Lighter than Line']} />
3034
+ </>
2910
3035
  )}
2911
3036
 
2912
3037
  {/* eslint-disable */}
@@ -3105,6 +3230,7 @@ const EditorPanel = () => {
3105
3230
  />
3106
3231
  </label>
3107
3232
  )}
3233
+ {config.visualizationType === 'Bar' && <CheckBox value={config.tooltips.singleSeries} fieldName='singleSeries' section='tooltips' label='SHOW HOVER FOR SINGLE DATA SERIES' updateField={updateField} />}
3108
3234
 
3109
3235
  <label>
3110
3236
  <span className='edit-label column-heading'>No Data Message</span>
@@ -3195,7 +3321,7 @@ const EditorPanel = () => {
3195
3321
  section='table'
3196
3322
  type='textarea'
3197
3323
  fieldName='caption'
3198
- label='Data Table Caption'
3324
+ label='Screen Reader Description'
3199
3325
  placeholder=' Data table'
3200
3326
  tooltip={
3201
3327
  <Tooltip style={{ textTransform: 'none' }}>
@@ -0,0 +1,11 @@
1
+ import { type ChartConfig } from '@cdc/chart/src/types/ChartConfig'
2
+
3
+ export type ForestPlotProps = {
4
+ xScale: Function
5
+ yScale: Function
6
+ config: ChartConfig
7
+ height: number
8
+ width: number
9
+ handleTooltipMouseOff: Function
10
+ handleTooltipMouseOver: Function
11
+ }
File without changes
@@ -0,0 +1 @@
1
+ // Forest Plot Styles...
@@ -1,22 +1,25 @@
1
- import React, { useContext, useEffect } from 'react'
1
+ import { useContext, useEffect } from 'react'
2
2
 
3
3
  // visx
4
4
  import { Group } from '@visx/group'
5
5
  import { Line, Bar, Circle, LinePath } from '@visx/shape'
6
- import { GlyphDiamond } from '@visx/glyph'
7
6
  import { Text } from '@visx/text'
8
7
  import { scaleLinear } from '@visx/scale'
9
8
  import { curveLinearClosed } from '@visx/curve'
10
9
 
10
+ // types
11
+ import { type ForestPlotProps } from '@cdc/chart/src/components/ForestPlot/ForestPlotProps'
12
+ import { type ChartConfig } from '@cdc/chart/src/types/ChartConfig'
13
+ import { type ChartContext } from '@cdc/chart/src/types/ChartContext'
14
+
11
15
  // cdc
12
- import ConfigContext from '../ConfigContext'
16
+ import ConfigContext from '../../ConfigContext'
13
17
  import { getFontSize } from '@cdc/core/helpers/cove/number'
14
18
 
15
- const ForestPlot = props => {
16
- const { transformedData: data, updateConfig, dimensions, rawData } = useContext(ConfigContext)
17
- const { xScale, yScale, config, height, width, handleTooltipMouseOff, handleTooltipMouseOver, maxWidth, maxHeight } = props
18
- const { forestPlot, runtime, dataFormat } = config
19
- const [screenWidth, screenHeight] = dimensions
19
+ const ForestPlot = (props: ForestPlotProps) => {
20
+ const { rawData: data, updateConfig } = useContext<ChartContext>(ConfigContext)
21
+ const { xScale, yScale, config, height, width, handleTooltipMouseOff, handleTooltipMouseOver } = props
22
+ const { forestPlot } = config as ChartConfig
20
23
 
21
24
  // Requirements for forest plot
22
25
  // - force legend to be hidden for this chart type
@@ -37,16 +40,17 @@ const ForestPlot = props => {
37
40
  }
38
41
  }, [])
39
42
 
40
- const diamondHeight = 5
43
+ const pooledData = config.data.find(d => d[config.xAxis.dataKey] === config.forestPlot.pooledResult.column)
41
44
 
42
- // diamond path
43
- const regressionPoints = [
44
- { x: xScale(forestPlot.regression.lower), y: height - Number(config.forestPlot.rowHeight) },
45
- { x: xScale(forestPlot.regression.estimateField), y: height - diamondHeight - Number(config.forestPlot.rowHeight) },
46
- { x: xScale(forestPlot.regression.upper), y: height - Number(config.forestPlot.rowHeight) },
47
- { x: xScale(forestPlot.regression.estimateField), y: height + diamondHeight - Number(config.forestPlot.rowHeight) },
48
- { x: xScale(forestPlot.regression.lower), y: height - Number(config.forestPlot.rowHeight) }
49
- ]
45
+ const regressionPoints = pooledData
46
+ ? [
47
+ { x: xScale(pooledData[config.forestPlot.lower]), y: height - Number(config.forestPlot.rowHeight) },
48
+ { x: xScale(pooledData[config.forestPlot.estimateField]), y: height - forestPlot.pooledResult.diamondHeight - Number(config.forestPlot.rowHeight) },
49
+ { x: xScale(pooledData[config.forestPlot.upper]), y: height - Number(config.forestPlot.rowHeight) },
50
+ { x: xScale(pooledData[config.forestPlot.estimateField]), y: height + forestPlot.pooledResult.diamondHeight - Number(config.forestPlot.rowHeight) },
51
+ { x: xScale(pooledData[config.forestPlot.lower]), y: height - Number(config.forestPlot.rowHeight) }
52
+ ]
53
+ : []
50
54
 
51
55
  const topMarginOffset = config.forestPlot.rowHeight
52
56
 
@@ -64,38 +68,40 @@ const ForestPlot = props => {
64
68
  .map(entry => entry[1])
65
69
  .filter(entry => entry.forestPlot === true)
66
70
 
67
- const rightOffset = forestPlot.rightWidthOffset !== 0 ? (Number(forestPlot.rightWidthOffset) / 100) * width : width
68
- const leftOffset = forestPlot.leftWidthOffset !== 0 ? (Number(forestPlot.leftWidthOffset) / 100) * width : width
69
- const chartWidth = width - rightOffset - leftOffset
70
-
71
71
  return (
72
72
  <>
73
73
  <Group>
74
- {forestPlot.title !== '' && (
75
- <Text className={`forest-plot--title`} x={xScale(0)} y={0} textAnchor='middle' verticalAnchor='start' fontSize={getFontSize(config.fontSize)} fill={'black'}>
74
+ {forestPlot.title && (
75
+ <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'}>
76
76
  {forestPlot.title}
77
77
  </Text>
78
78
  )}
79
- {forestPlot.regression.showBaseLine && <Line from={{ x: xScale(forestPlot.regression.estimateField), y: 0 + topMarginOffset }} to={{ x: xScale(forestPlot.regression.estimateField), y: height }} className='forestplot__baseline' stroke={forestPlot.regression.baseLineColor || 'black'} />}
80
- {forestPlot.showZeroLine && <Line from={{ x: xScale(0), y: 0 + topMarginOffset }} to={{ x: xScale(0), y: height }} className='forestplot__zero-line' stroke='gray' strokeDasharray={'5 5'} />}
79
+
80
+ {/* Line of no effect on Continuous Scale. */}
81
+ {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'} />}
82
+
83
+ {/* Line of no effect on Logarithmic Scale. */}
84
+ {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'} />}
81
85
 
82
86
  {data.map((d, i) => {
83
87
  // calculate both square and circle size based on radius.min and radius.max
84
88
  const scaleRadius = scaleLinear({
85
- domain: xScale.domain(),
89
+ domain: data.map(d => d[forestPlot.radius.scalingColumn]),
86
90
  range: [forestPlot.radius.min, forestPlot.radius.max]
87
91
  })
88
92
 
89
93
  // glyph settings
90
- const diamondSize = forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.estimateField]) * 5 : 4
91
- const rectSize = forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.estimateField]) : 4
94
+ const rectSize = forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.radius.scalingColumn]) : 4
92
95
  const shapeColor = forestPlot.colors.shape ? forestPlot.colors.shape : 'black'
93
96
  const lineColor = forestPlot.colors.line ? forestPlot.colors.line : 'black'
94
97
 
95
98
  // ci size
96
99
  const ciEndSize = 4
97
100
 
98
- return (
101
+ // Don't run calculations on the pooled column
102
+ const isTotalColumn = d[config.xAxis.dataKey] === forestPlot.pooledResult.column
103
+
104
+ return !isTotalColumn ? (
99
105
  <Group>
100
106
  {/* Confidence Interval Paths */}
101
107
  <path
@@ -121,16 +127,17 @@ const ForestPlot = props => {
121
127
  {/* main line */}
122
128
  <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)} />
123
129
  {forestPlot.shape === 'circle' && (
124
- <Circle className='forest-plot--circle' cx={xScale(Number(d[forestPlot.estimateField]))} cy={yScale(i)} r={forestPlot.radius.scalingColumn !== '' ? scaleRadius(data[i][forestPlot.estimateField]) : 4} fill={shapeColor} style={{ opacity: 1, filter: 'unset' }} />
130
+ <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' }} />
125
131
  )}
126
132
  {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' }} />}
127
- {forestPlot.shape === 'diamond' && <GlyphDiamond className='forest-plot--diamond' size={diamondSize} top={yScale(i)} left={xScale(Number(d[forestPlot.estimateField]))} fill={shapeColor} />}
128
133
  {forestPlot.shape === 'text' && (
129
134
  <Text className='forest-plot--text' x={xScale(Number(d[forestPlot.estimateField]))} y={yScale(i)} textAnchor='middle' verticalAnchor='middle' fontSize={getFontSize(config.fontSize)} fill={shapeColor}>
130
135
  {d[forestPlot.estimateField]}
131
136
  </Text>
132
137
  )}
133
138
  </Group>
139
+ ) : (
140
+ <LinePath data={regressionPoints} x={d => d.x} y={d => d.y - getFontSize(config.fontSize) / 2} stroke='black' strokeWidth={2} fill={'black'} curve={curveLinearClosed} />
134
141
  )
135
142
  })}
136
143
 
@@ -184,6 +191,19 @@ const ForestPlot = props => {
184
191
  </Text>
185
192
  )
186
193
  })}
194
+
195
+ {/* left bottom label */}
196
+ {forestPlot.leftLabel && (
197
+ <Text className='forest-plot__left-label' x={forestPlot.type === 'Linear' ? xScale(0) - 25 : xScale(1) - 25} y={height + 50} textAnchor='end'>
198
+ {forestPlot.leftLabel}
199
+ </Text>
200
+ )}
201
+
202
+ {forestPlot.rightLabel && (
203
+ <Text className='forest-plot__right-label' x={forestPlot.type === 'Linear' ? xScale(0) + 25 : xScale(1) + 25} y={height + 50} textAnchor='start'>
204
+ {forestPlot.rightLabel}
205
+ </Text>
206
+ )}
187
207
  </>
188
208
  )
189
209
  }