@cdc/chart 4.23.11 → 4.24.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 (103) hide show
  1. package/dist/cdcchart.js +30220 -29764
  2. package/examples/feature/bar/additional-column-tooltip.json +446 -0
  3. package/examples/feature/bar/tall-data.json +98 -0
  4. package/examples/feature/forest-plot/forest-plot.json +63 -19
  5. package/examples/feature/forest-plot/linear.json +52 -3
  6. package/examples/feature/forest-plot/log.json +26 -0
  7. package/examples/feature/forest-plot/logarithmic.json +0 -35
  8. package/examples/feature/line/line-chart-preliminary.json +346 -0
  9. package/examples/feature/scatterplot/scatterplot.json +272 -33
  10. package/examples/private/chart-t.json +3740 -0
  11. package/examples/private/combo.json +369 -0
  12. package/examples/private/epi-data.csv +13 -0
  13. package/examples/private/epi-data.json +62 -0
  14. package/examples/private/epi.json +403 -0
  15. package/examples/private/occupancy.json +109283 -0
  16. package/examples/private/prod-line-config.json +401 -0
  17. package/examples/private/region-data.json +822 -0
  18. package/examples/private/region-testing.json +312 -0
  19. package/examples/private/scaling.json +45325 -0
  20. package/examples/private/testing-data.json +1739 -0
  21. package/examples/private/testing.json +816 -0
  22. package/index.html +7 -7
  23. package/package.json +2 -2
  24. package/src/CdcChart.tsx +29 -210
  25. package/src/ConfigContext.tsx +6 -0
  26. package/src/_stories/ChartEditor.stories.tsx +22 -0
  27. package/src/_stories/ChartLine.preliminary.tsx +19 -0
  28. package/src/_stories/_mock/pie_config.json +191 -0
  29. package/src/_stories/_mock/pie_data.json +218 -0
  30. package/src/_stories/_mock/preliminary_mock.json +346 -0
  31. package/src/components/{AreaChart.Stacked.jsx → AreaChart/components/AreaChart.Stacked.jsx} +2 -2
  32. package/src/components/{AreaChart.jsx → AreaChart/components/AreaChart.jsx} +1 -1
  33. package/src/components/AreaChart/index.tsx +4 -0
  34. package/src/components/{BarChart.Horizontal.tsx → BarChart/components/BarChart.Horizontal.tsx} +8 -8
  35. package/src/components/{BarChart.StackedHorizontal.tsx → BarChart/components/BarChart.StackedHorizontal.tsx} +37 -7
  36. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +106 -0
  37. package/src/components/{BarChart.Vertical.tsx → BarChart/components/BarChart.Vertical.tsx} +41 -57
  38. package/src/components/BarChart/components/BarChart.jsx +39 -0
  39. package/src/components/{BarChartType.jsx → BarChart/components/BarChartType.jsx} +0 -2
  40. package/src/components/BarChart/components/context.tsx +13 -0
  41. package/src/components/BarChart/index.tsx +3 -0
  42. package/src/components/{BoxPlot.jsx → BoxPlot/BoxPlot.jsx} +1 -1
  43. package/src/components/BoxPlot/index.tsx +3 -0
  44. package/src/components/{EditorPanel.jsx → EditorPanel/EditorPanel.tsx} +667 -851
  45. package/src/components/EditorPanel/components/Panel.DateHighlighting.tsx +109 -0
  46. package/src/components/{ForestPlotSettings.jsx → EditorPanel/components/Panel.ForestPlotSettings.tsx} +87 -166
  47. package/src/components/EditorPanel/components/Panel.Regions.tsx +168 -0
  48. package/src/components/{Series.jsx → EditorPanel/components/Panel.Series.tsx} +1 -1
  49. package/src/components/EditorPanel/components/PanelProps.ts +3 -0
  50. package/src/components/EditorPanel/components/Panels.tsx +13 -0
  51. package/src/components/EditorPanel/components/panels.scss +72 -0
  52. package/src/components/EditorPanel/editor-panel.scss +751 -0
  53. package/src/components/EditorPanel/index.tsx +3 -0
  54. package/src/{hooks → components/EditorPanel}/useEditorPermissions.js +29 -2
  55. package/src/components/{Forecasting.jsx → Forecasting/Forecasting.jsx} +1 -1
  56. package/src/components/Forecasting/index.tsx +3 -0
  57. package/src/components/ForestPlot/ForestPlot.tsx +254 -0
  58. package/src/components/ForestPlot/ForestPlotProps.ts +7 -0
  59. package/src/components/ForestPlot/index.tsx +1 -209
  60. package/src/components/{Legend.jsx → Legend/Legend.tsx} +150 -113
  61. package/src/components/Legend/index.tsx +3 -0
  62. package/src/components/LineChart/LineChartProps.ts +29 -0
  63. package/src/components/LineChart/{LineChart.Circle.tsx → components/LineChart.Circle.tsx} +12 -3
  64. package/src/components/LineChart/helpers.ts +45 -0
  65. package/src/components/LineChart/index.tsx +20 -8
  66. package/src/components/LinearChart.jsx +52 -69
  67. package/src/components/{PieChart.jsx → PieChart/PieChart.tsx} +16 -7
  68. package/src/components/PieChart/index.tsx +3 -0
  69. package/src/components/Regions/components/Regions.tsx +135 -0
  70. package/src/components/Regions/index.tsx +3 -0
  71. package/src/components/{ScatterPlot.jsx → ScatterPlot/ScatterPlot.jsx} +3 -3
  72. package/src/components/ScatterPlot/index.tsx +3 -0
  73. package/src/components/{SparkLine.jsx → Sparkline/SparkLine.jsx} +2 -2
  74. package/src/components/Sparkline/index.tsx +3 -0
  75. package/src/data/initial-state.js +5 -6
  76. package/src/helpers/abbreviateNumber.ts +17 -0
  77. package/src/helpers/computeMarginBottom.ts +55 -0
  78. package/src/helpers/filterData.ts +18 -0
  79. package/src/helpers/generateColorsArray.ts +8 -0
  80. package/src/helpers/getQuartiles.ts +30 -0
  81. package/src/helpers/handleChartAriaLabels.ts +19 -0
  82. package/src/helpers/handleLineType.ts +18 -0
  83. package/src/helpers/lineOptions.ts +18 -0
  84. package/src/helpers/sort.ts +7 -0
  85. package/src/helpers/tests/computeMarginBottom.test.ts +20 -0
  86. package/src/hooks/useBarChart.js +7 -6
  87. package/src/hooks/useScales.ts +1 -1
  88. package/src/hooks/{useTooltip.jsx → useTooltip.tsx} +23 -21
  89. package/src/scss/main.scss +67 -3
  90. package/src/types/ChartConfig.ts +158 -23
  91. package/src/types/ChartContext.ts +26 -10
  92. package/src/types/ForestPlot.ts +7 -14
  93. package/examples/feature/scatterplot/scatterplot-continuous.csv +0 -17
  94. package/src/ConfigContext.jsx +0 -5
  95. package/src/components/BarChart.StackedVertical.tsx +0 -91
  96. package/src/components/BarChart.jsx +0 -30
  97. package/src/components/ForestPlot/Readme.md +0 -0
  98. package/src/scss/LinearChart.scss +0 -0
  99. package/src/scss/editor-panel.scss +0 -745
  100. package/src/scss/legend.scss +0 -206
  101. package/src/scss/mixins.scss +0 -0
  102. package/src/scss/variables.scss +0 -1
  103. package/src/types/ChartProps.ts +0 -7
@@ -1,43 +1,43 @@
1
- import React, { useContext, useEffect } from 'react'
2
- import ConfigContext from '../ConfigContext'
1
+ import { useContext } from 'react'
2
+ import ConfigContext from '../../ConfigContext'
3
3
  import parse from 'html-react-parser'
4
4
  import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
5
5
  import LegendCircle from '@cdc/core/components/LegendCircle'
6
6
 
7
- import useLegendClasses from './../hooks/useLegendClasses'
8
- import { useHighlightedBars } from '../hooks/useHighlightedBars'
7
+ import useLegendClasses from '../../hooks/useLegendClasses'
8
+ import { useHighlightedBars } from '../../hooks/useHighlightedBars'
9
+ import { handleLineType } from '../../helpers/handleLineType'
9
10
  import { Line } from '@visx/shape'
10
- import { sequentialPalettes } from '@cdc/core/data/colorPalettes'
11
+ import { colorPalettesChart as colorPalettes, sequentialPalettes, twoColorPalette } from '@cdc/core/data/colorPalettes'
11
12
  import { scaleOrdinal } from '@visx/scale'
12
13
  import { FaStar } from 'react-icons/fa'
13
-
14
- // * FILE REVIEW *
15
- // TODO: fix eslint-disable jsxa11y issues
16
-
17
- // * ADDITIONAL NOTES *
18
- // > recently removed dynamic legend items as they weren't used
19
- // > recently removed boxplots, they don't provide any legend settings
14
+ import { Text } from '@visx/text'
15
+ import { Group } from '@visx/group'
16
+
17
+ type Label = {
18
+ datum: string
19
+ index: number
20
+ text: string
21
+ value: string
22
+ icon?: any
23
+ }
20
24
 
21
25
  /* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
22
26
  const Legend = () => {
23
27
  // prettier-ignore
24
28
  const {
25
29
  config,
26
- legend,
27
30
  colorScale,
28
31
  seriesHighlight,
29
32
  highlight,
30
- twoColorPalette,
31
33
  tableData,
32
34
  highlightReset,
33
35
  transformedData: data,
34
- colorPalettes,
35
- currentViewport,
36
- handleLineType
36
+ currentViewport
37
37
  } = useContext(ConfigContext)
38
38
 
39
39
  const { innerClasses, containerClasses } = useLegendClasses(config)
40
- const { visualizationType, visualizationSubType, series, runtime, orientation } = config
40
+ const { visualizationType, visualizationSubType, series, runtime, orientation, legend } = config
41
41
  // create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
42
42
  const reverseLabels = labels => (config.legend.reverseLabelOrder && config.legend.position === 'bottom' ? labels.reverse() : labels)
43
43
  const displayScale = scaleOrdinal({
@@ -46,7 +46,26 @@ const Legend = () => {
46
46
  unknown: 'block'
47
47
  })
48
48
 
49
- const createLegendLabels = defaultLabels => {
49
+ const renderDashes = style => {
50
+ const dashCount = style === 'Dashed Small' ? 3 : 2
51
+ const dashClass = `dashes ${style.toLowerCase().replace(' ', '-')}`
52
+
53
+ return (
54
+ <div className={dashClass}>
55
+ {Array.from({ length: dashCount }, (_, i) => (
56
+ <span key={i}>-</span>
57
+ ))}
58
+ </div>
59
+ )
60
+ }
61
+ const renderDashesOrCircle = style => {
62
+ if (['Dashed Small', 'Dashed Medium', 'Dashed Large'].includes(style)) {
63
+ return renderDashes(style)
64
+ } else if (style === 'Open Circles') {
65
+ return <div className='dashes open-circles'></div>
66
+ }
67
+ }
68
+ const createLegendLabels = (defaultLabels): Label[] => {
50
69
  const colorCode = config.legend?.colorCode
51
70
  if (visualizationType === 'Deviation Bar') {
52
71
  const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette]
@@ -96,7 +115,6 @@ const Legend = () => {
96
115
  let seriesLabels = []
97
116
 
98
117
  //store unique values to Set by colorCode
99
-
100
118
  // loop through each stage/group/area on the chart and create a label
101
119
  config.runtime?.forecastingSeriesKeys?.map((outerGroup, index) => {
102
120
  return outerGroup?.stages?.map((stage, index) => {
@@ -166,11 +184,7 @@ const Legend = () => {
166
184
  const lastIndex = defaultLabels.length - 1
167
185
  let newLabels = []
168
186
 
169
- config.suppressedData?.forEach(({ label, icon, value }, index) => {
170
- const dataExists = data.some(d => {
171
- return runtime.seriesKeys.some(column => d[column] === value)
172
- })
173
-
187
+ config.suppressedData?.forEach(({ label, icon }, index) => {
174
188
  if (label && icon) {
175
189
  const newLabel = {
176
190
  datum: label,
@@ -208,97 +222,120 @@ const Legend = () => {
208
222
  <LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
209
223
  {labels => {
210
224
  return (
211
- <div className={innerClasses.join(' ')}>
212
- {createLegendLabels(labels).map((label, i) => {
213
- let className = ['legend-item', `legend-text--${label.text.replace(' ', '').toLowerCase()}`]
214
- let itemName = label.datum
215
-
216
- // Filter excluded data keys from legend
217
- if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
218
- return null
219
- }
220
-
221
- if (runtime.seriesLabels) {
222
- let index = config.runtime.seriesLabelsAll.indexOf(itemName)
223
- itemName = config.runtime.seriesKeys[index]
224
-
225
- if (runtime?.forecastingSeriesKeys?.length > 0) {
226
- itemName = label.text
225
+ <>
226
+ <div className={innerClasses.join(' ')}>
227
+ {createLegendLabels(labels).map((label, i) => {
228
+ let className = ['legend-item', `legend-text--${label.text.replace(' ', '').toLowerCase()}`]
229
+ let itemName = label.datum
230
+
231
+ // Filter excluded data keys from legend
232
+ if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
233
+ return null
234
+ }
235
+
236
+ if (runtime.seriesLabels) {
237
+ let index = config.runtime.seriesLabelsAll.indexOf(itemName)
238
+ itemName = config.runtime.seriesKeys[index]
239
+
240
+ if (runtime?.forecastingSeriesKeys?.length > 0) {
241
+ itemName = label.text
242
+ }
227
243
  }
228
- }
229
-
230
- if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
231
- className.push('inactive')
232
- }
233
-
234
- return (
235
- <LegendItem
236
- className={className.join(' ')}
237
- tabIndex={0}
238
- key={`legend-quantile-${i}`}
239
- onKeyPress={e => {
240
- if (e.key === 'Enter') {
244
+
245
+ if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
246
+ className.push('inactive')
247
+ }
248
+
249
+ return (
250
+ <LegendItem
251
+ className={className.join(' ')}
252
+ tabIndex={0}
253
+ key={`legend-quantile-${i}`}
254
+ onKeyPress={e => {
255
+ if (e.key === 'Enter') {
256
+ highlight(label)
257
+ }
258
+ }}
259
+ onClick={() => {
241
260
  highlight(label)
242
- }
243
- }}
244
- onClick={() => {
245
- highlight(label)
246
- }}
247
- >
248
- {config.visualizationType === 'Line' && config.legend.lineMode ? (
249
- <svg width={40} height={20}>
250
- <Line from={{ x: 10, y: 10 }} to={{ x: 40, y: 10 }} stroke={label.value} strokeWidth={2} strokeDasharray={handleLineType(config.series[i]?.type ? config.series[i]?.type : '')} />
251
- </svg>
252
- ) : (
253
- <div style={{ display: 'flex', flexDirection: 'column' }}>
254
- <LegendCircle margin='0' fill={label.value} display={displayScale(label.datum)} />
255
- <div style={{ marginTop: '2px', marginRight: '6px' }}>{label.icon}</div>
256
- </div>
257
- )}
258
-
259
- <LegendLabel align='left' margin='0 0 0 4px'>
260
- {label.text}
261
- </LegendLabel>
262
- </LegendItem>
263
- )
264
- })}
265
-
266
- {highLightedLegendItems.map((bar, i) => {
267
- // if duplicates only return first item
268
- let className = 'legend-item'
269
- let itemName = bar.legendLabel
270
-
271
- if (!itemName) return false
272
- if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
273
- className += ' inactive'
274
- }
275
- return (
276
- <LegendItem
277
- className={className}
278
- tabIndex={0}
279
- key={`legend-quantile-${i}`}
280
- onKeyPress={e => {
281
- if (e.key === 'Enter') {
261
+ }}
262
+ >
263
+ {config.visualizationType === 'Line' && config.legend.lineMode ? (
264
+ <svg width={40} height={20}>
265
+ <Line from={{ x: 10, y: 10 }} to={{ x: 40, y: 10 }} stroke={label.value} strokeWidth={2} strokeDasharray={handleLineType(config.series[i]?.type ? config.series[i]?.type : '')} />
266
+ </svg>
267
+ ) : (
268
+ <div style={{ display: 'flex', flexDirection: 'column' }}>
269
+ <LegendCircle margin='0' fill={label.value} display={displayScale(label.datum)} />
270
+ <div style={{ marginTop: '2px', marginRight: '6px' }}>{label.icon}</div>
271
+ </div>
272
+ )}
273
+
274
+ <LegendLabel align='left' margin='0 0 0 4px'>
275
+ {label.text}
276
+ </LegendLabel>
277
+ </LegendItem>
278
+ )
279
+ })}
280
+
281
+ {highLightedLegendItems.map((bar, i) => {
282
+ // if duplicates only return first item
283
+ let className = 'legend-item'
284
+ let itemName = bar.legendLabel
285
+
286
+ if (!itemName) return false
287
+ if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
288
+ className += ' inactive'
289
+ }
290
+ return (
291
+ <LegendItem
292
+ className={className}
293
+ tabIndex={0}
294
+ key={`legend-quantile-${i}`}
295
+ onKeyPress={e => {
296
+ if (e.key === 'Enter') {
297
+ highlight(bar.legendLabel)
298
+ }
299
+ }}
300
+ onClick={() => {
282
301
  highlight(bar.legendLabel)
283
- }
284
- }}
285
- onClick={() => {
286
- highlight(bar.legendLabel)
287
- }}
288
- >
289
- <LegendCircle fill='transparent' borderColor={bar.color ? bar.color : `rgba(255, 102, 1)`} />{' '}
290
- <LegendLabel align='left' margin='0 0 0 4px'>
291
- {bar.legendLabel ? bar.legendLabel : bar.value}
292
- </LegendLabel>
293
- </LegendItem>
294
- )
295
- })}
296
- {seriesHighlight.length > 0 && (
297
- <button className={`legend-reset ${config.theme}`} onClick={labels => highlightReset(labels)} tabIndex={0}>
298
- Reset
299
- </button>
300
- )}
301
- </div>
302
+ }}
303
+ >
304
+ <LegendCircle fill='transparent' borderColor={bar.color ? bar.color : `rgba(255, 102, 1)`} />{' '}
305
+ <LegendLabel align='left' margin='0 0 0 4px'>
306
+ {bar.legendLabel ? bar.legendLabel : bar.value}
307
+ </LegendLabel>
308
+ </LegendItem>
309
+ )
310
+ })}
311
+ {seriesHighlight.length > 0 && (
312
+ <button className={`legend-reset ${config.theme}`} onClick={labels => highlightReset(labels)} tabIndex={0}>
313
+ Reset
314
+ </button>
315
+ )}
316
+ </div>
317
+ <>
318
+ {config.preliminaryData.some(pd => pd.label) && (
319
+ <>
320
+ <hr></hr>
321
+ <div className={config.legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : 'dash-left'}>
322
+ {config.preliminaryData.map((pd, index) => {
323
+ return (
324
+ <div className='dash-container' key={index}>
325
+ {pd.label && (
326
+ <>
327
+ <div className='dash-inner'>{renderDashesOrCircle(pd.style)}</div>
328
+ <div style={{ marginLeft: '7px' }}>{pd.label}</div>
329
+ </>
330
+ )}
331
+ </div>
332
+ )
333
+ })}
334
+ </div>
335
+ </>
336
+ )}
337
+ </>
338
+ </>
302
339
  )
303
340
  }}
304
341
  </LegendOrdinal>
@@ -0,0 +1,3 @@
1
+ import Legend from './Legend'
2
+
3
+ export default Legend
@@ -15,3 +15,32 @@ export type LineChartProps = {
15
15
  handleTooltipClick: Function
16
16
  tooltipData: any
17
17
  }
18
+
19
+ export interface PreliminaryDataItem {
20
+ style: string
21
+ type: string
22
+ column: string
23
+ value: string
24
+ seriesKey: string
25
+ }
26
+
27
+ export interface DataItem {
28
+ [key: string]: any
29
+ }
30
+
31
+ export interface Config {
32
+ preliminaryData: PreliminaryDataItem[] | []
33
+ }
34
+ export interface StyleProps {
35
+ preliminaryData: PreliminaryDataItem[]
36
+ rawData: DataItem[]
37
+ stroke: string
38
+ handleLineType: Function
39
+ lineType: string
40
+ seriesKey: 'string'
41
+ }
42
+ export interface Style {
43
+ stroke: string
44
+ strokeWidth: number
45
+ strokeDasharray: string
46
+ }
@@ -1,9 +1,10 @@
1
1
  import React from 'react'
2
2
  import chroma from 'chroma-js'
3
- import { type ChartConfig } from '../../types/ChartConfig'
3
+ import { type ChartConfig } from '../../../types/ChartConfig'
4
4
 
5
5
  // todo: change this config obj to ChartConfig once its created
6
6
  type LineChartCircleProps = {
7
+ circleData: object[]
7
8
  config: ChartConfig
8
9
  data: object[]
9
10
  d?: Object
@@ -23,7 +24,7 @@ type LineChartCircleProps = {
23
24
  }
24
25
 
25
26
  const LineChartCircle = (props: LineChartCircleProps) => {
26
- const { config, d, displayArea, seriesKey, tooltipData, xScale, yScale, colorScale, parseDate, yScaleRight, data } = props
27
+ const { config, d, displayArea, seriesKey, tooltipData, xScale, yScale, colorScale, parseDate, yScaleRight, data, circleData } = props
27
28
  const { lineDatapointStyle } = config
28
29
  const filtered = config?.series.filter(s => s.dataKey === seriesKey)?.[0]
29
30
  // If we're not showing the circle, simply return
@@ -46,8 +47,11 @@ const LineChartCircle = (props: LineChartCircleProps) => {
46
47
  }
47
48
  return color
48
49
  }
49
-
50
50
  if (lineDatapointStyle === 'always show') {
51
+ const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === d[config.xAxis.dataKey] && cd[seriesKey] === d[seriesKey])
52
+ if (isMatch) {
53
+ return <></>
54
+ }
51
55
  return (
52
56
  <circle
53
57
  cx={config.xAxis.type === 'categorical' ? xScale(d[config.xAxis.dataKey]) : xScale(parseDate(d[config.xAxis.dataKey]))}
@@ -84,6 +88,11 @@ const LineChartCircle = (props: LineChartCircleProps) => {
84
88
  let seriesIndex = config.runtime.seriesLabelsAll.indexOf(hoveredXValue)
85
89
 
86
90
  if (isNaN(hoveredSeriesValue)) return <></>
91
+ const isMatch = circleData?.some(cd => cd[config.xAxis.dataKey] === hoveredXValue)
92
+
93
+ if (isMatch) {
94
+ return <></>
95
+ }
87
96
  return (
88
97
  <circle
89
98
  cx={config.xAxis.type === 'categorical' ? xScale(hoveredXValue) : xScale(parseDate(hoveredXValue))}
@@ -0,0 +1,45 @@
1
+ import { type PreliminaryDataItem, DataItem, StyleProps, Style } from './LineChartProps'
2
+
3
+ export const createStyles = (props: StyleProps): Style[] => {
4
+ const { preliminaryData, rawData, stroke, handleLineType, lineType, seriesKey } = props
5
+
6
+ const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(pd => pd.seriesKey && pd.column && pd.value && pd.type && pd.style)
7
+ const getMatchingPd = (point: DataItem): PreliminaryDataItem => validPreliminaryData.find(pd => pd.seriesKey === seriesKey && point[pd.column] === pd.value && pd.type === 'effect' && pd.style !== 'Open Circles')
8
+
9
+ let styles: Style[] = []
10
+ const createStyle = (lineStyle): Style => ({
11
+ stroke: stroke,
12
+ strokeWidth: 2,
13
+ strokeDasharray: lineStyle
14
+ })
15
+
16
+ rawData.forEach((d, index) => {
17
+ let matchingPd: PreliminaryDataItem = getMatchingPd(d)
18
+ let style: Style = matchingPd ? createStyle(handleLineType(matchingPd.style)) : createStyle(handleLineType(lineType))
19
+
20
+ styles.push(style)
21
+
22
+ // If matchingPd exists, update the previous style if there is a previous element
23
+ if (matchingPd && index > 0) {
24
+ styles[index - 1] = createStyle(handleLineType(matchingPd.style))
25
+ }
26
+ })
27
+ return styles as Style[]
28
+ }
29
+
30
+ export const filterCircles = (preliminaryData: PreliminaryDataItem[], data: DataItem[], seriesKey: string): DataItem[] => {
31
+ // Filter and map preliminaryData to get circlesFiltered
32
+ const circlesFiltered = preliminaryData.filter(item => item.style === 'Open Circles' && item.type === 'effect').map(item => ({ column: item.column, value: item.value, seriesKey: item.seriesKey }))
33
+
34
+ let filteredData: DataItem[] = []
35
+
36
+ // Process data to find matching items
37
+ data.forEach(item => {
38
+ if (circlesFiltered.some(d => item[d.column] === d.value && d.seriesKey === seriesKey)) {
39
+ // Add current item
40
+ filteredData.push(item)
41
+ }
42
+ })
43
+
44
+ return filteredData
45
+ }
@@ -2,13 +2,14 @@ import React, { useContext } from 'react'
2
2
 
3
3
  import * as allCurves from '@visx/curve'
4
4
  import { Group } from '@visx/group'
5
- import { LinePath, Bar } from '@visx/shape'
5
+ import { LinePath, Bar, SplitLinePath } from '@visx/shape'
6
6
  import { Text } from '@visx/text'
7
7
 
8
8
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
9
9
  import ConfigContext from '../../ConfigContext'
10
10
  import useRightAxis from '../../hooks/useRightAxis'
11
- import LineChartCircle from './LineChart.Circle'
11
+ import { filterCircles, createStyles } from './helpers'
12
+ import LineChartCircle from './components/LineChart.Circle'
12
13
 
13
14
  // types
14
15
  import { type ChartContext } from '../../types/ChartContext'
@@ -41,6 +42,7 @@ const LineChart = (props: LineChartProps) => {
41
42
  tableData,
42
43
  transformedData: data,
43
44
  updateConfig,
45
+ rawData
44
46
  } = useContext<ChartContext>(ConfigContext)
45
47
  const { yScaleRight } = useRightAxis({ config, yMax, data, updateConfig })
46
48
  if (!handleTooltipMouseOver) return
@@ -57,8 +59,10 @@ const LineChart = (props: LineChartProps) => {
57
59
  let lineType = config.series.filter(item => item.dataKey === seriesKey)[0].type
58
60
  const seriesData = config.series.filter(item => item.dataKey === seriesKey)
59
61
  const seriesAxis = seriesData[0].axis ? seriesData[0].axis : 'left'
60
-
61
62
  let displayArea = legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
63
+ const circleData = filterCircles(config.preliminaryData, rawData, seriesKey)
64
+ // styles for preliminary Data items
65
+ let styles = createStyles({ preliminaryData: config.preliminaryData, rawData, stroke: colorScale(config.runtime.seriesLabels[seriesKey]), handleLineType, lineType, seriesKey })
62
66
 
63
67
  return (
64
68
  <Group
@@ -93,6 +97,7 @@ const LineChart = (props: LineChartProps) => {
93
97
 
94
98
  {(lineDatapointStyle === 'hidden' || lineDatapointStyle === 'always show') && (
95
99
  <LineChartCircle
100
+ circleData={circleData}
96
101
  data={data}
97
102
  d={d}
98
103
  config={config}
@@ -114,7 +119,7 @@ const LineChart = (props: LineChartProps) => {
114
119
  })}
115
120
  <>
116
121
  {lineDatapointStyle === 'hover' && (
117
- <LineChartCircle data={data} config={config} seriesKey={seriesKey} displayArea={displayArea} tooltipData={tooltipData} xScale={xScale} yScale={yScale} colorScale={colorScale} parseDate={parseDate} yScaleRight={yScaleRight} seriesAxis={seriesAxis} />
122
+ <LineChartCircle circleData={circleData} data={data} config={config} seriesKey={seriesKey} displayArea={displayArea} tooltipData={tooltipData} xScale={xScale} yScale={yScale} colorScale={colorScale} parseDate={parseDate} yScaleRight={yScaleRight} seriesAxis={seriesAxis} />
118
123
  )}
119
124
  </>
120
125
  {/* STANDARD LINE */}
@@ -122,15 +127,22 @@ const LineChart = (props: LineChartProps) => {
122
127
  curve={allCurves[seriesData[0].lineType]}
123
128
  data={data}
124
129
  x={d => xScale(getXAxisData(d))}
125
- y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey)))}
126
- stroke={colorScale ? colorScale(config.runtime.seriesLabels[seriesKey]) : '#000'}
130
+ y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
131
+ stroke={colorScale(config.runtime.seriesLabels[seriesKey])}
127
132
  strokeWidth={2}
128
133
  strokeOpacity={1}
134
+ shapeRendering='geometricPrecision'
129
135
  strokeDasharray={lineType ? handleLineType(lineType) : 0}
130
136
  defined={(item, i) => {
131
137
  return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
132
138
  }}
133
139
  />
140
+
141
+ {/* circles for preliminaryData data */}
142
+ {circleData.map((d, i) => {
143
+ return <circle key={i} cx={xScale(getXAxisData(d))} cy={yScale(Number(getYAxisData(d, seriesKey)))} r={6} strokeWidth={2} stroke={colorScale ? colorScale(config.runtime.seriesLabels[seriesKey]) : '#000'} fill='#fff' />
144
+ })}
145
+
134
146
  {/* ANIMATED LINE */}
135
147
  {config.animate && (
136
148
  <LinePath
@@ -138,14 +150,14 @@ const LineChart = (props: LineChartProps) => {
138
150
  curve={seriesData.lineType}
139
151
  data={data}
140
152
  x={d => xScale(getXAxisData(d))}
141
- y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey)))}
153
+ y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
142
154
  stroke='#fff'
143
155
  strokeWidth={3}
144
156
  strokeOpacity={1}
145
157
  shapeRendering='geometricPrecision'
146
158
  strokeDasharray={lineType ? handleLineType(lineType) : 0}
147
159
  defined={(item, i) => {
148
- return isNumber(item[config.runtime.seriesLabels[seriesKey]])
160
+ return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
149
161
  }}
150
162
  />
151
163
  )}