@cdc/chart 4.24.10 → 4.24.12

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 (93) hide show
  1. package/dist/cdcchart.js +34651 -33978
  2. package/examples/feature/boxplot/boxplot-data.json +88 -22
  3. package/examples/feature/boxplot/boxplot.json +540 -16
  4. package/examples/feature/boxplot/testing.csv +7 -7
  5. package/examples/feature/sankey/sankey-example-data.json +126 -14
  6. package/examples/feature/tests-date-exclusions/date-exclusions-config.json +372 -12
  7. package/examples/private/DEV-8850-2.json +493 -0
  8. package/examples/private/DEV-9822.json +574 -0
  9. package/examples/private/DEV-9840.json +553 -0
  10. package/examples/private/DEV-9850-3.json +461 -0
  11. package/examples/private/chart.json +1084 -0
  12. package/examples/private/ci_formatted.json +202 -0
  13. package/examples/private/ci_issue.json +3016 -0
  14. package/examples/private/completed.json +634 -0
  15. package/examples/private/dem-data-long.csv +20 -0
  16. package/examples/private/dem-data-long.json +36 -0
  17. package/examples/private/demographic_data.csv +157 -0
  18. package/examples/private/demographic_data.json +2654 -0
  19. package/examples/private/demographic_dynamic.json +443 -0
  20. package/examples/private/demographic_standard.json +560 -0
  21. package/examples/private/test.json +493 -0
  22. package/index.html +10 -7
  23. package/package.json +2 -2
  24. package/src/CdcChart.tsx +132 -152
  25. package/src/_stories/Chart.Anchors.stories.tsx +31 -0
  26. package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
  27. package/src/_stories/Chart.DynamicSeries.stories.tsx +34 -0
  28. package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
  29. package/src/_stories/Chart.stories.tsx +37 -6
  30. package/src/_stories/ChartAxisLabels.stories.tsx +4 -1
  31. package/src/_stories/ChartEditor.stories.tsx +27 -0
  32. package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
  33. package/src/_stories/ChartPrefixSuffix.stories.tsx +8 -0
  34. package/{examples/feature/area/area-chart-date-city-temperature.json → src/_stories/_mock/area_chart_stacked.json} +125 -27
  35. package/src/_stories/_mock/boxplot_multiseries.json +647 -0
  36. package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
  37. package/src/_stories/_mock/dynamic_series_config.json +979 -0
  38. package/src/_stories/_mock/line_chart_dynamic_ci.json +493 -0
  39. package/src/_stories/_mock/line_chart_non_dynamic_ci.json +522 -0
  40. package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
  41. package/src/_stories/_mock/short_dates.json +288 -0
  42. package/src/_stories/_mock/suppression_mock.json +1549 -0
  43. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +15 -3
  44. package/src/components/Axis/Categorical.Axis.tsx +2 -2
  45. package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -37
  46. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
  47. package/src/components/BarChart/components/BarChart.Vertical.tsx +53 -47
  48. package/src/components/BarChart/helpers/getBarData.ts +28 -0
  49. package/src/components/BarChart/helpers/index.ts +1 -2
  50. package/src/components/BarChart/helpers/tests/getBarData.test.ts +74 -0
  51. package/src/components/BoxPlot/BoxPlot.tsx +131 -0
  52. package/src/components/BoxPlot/helpers/index.ts +54 -0
  53. package/src/components/BrushChart.tsx +23 -26
  54. package/src/components/EditorPanel/EditorPanel.tsx +117 -139
  55. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +3 -3
  56. package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
  57. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
  58. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
  59. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +122 -56
  60. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
  61. package/src/components/EditorPanel/useEditorPermissions.ts +20 -2
  62. package/src/components/Legend/Legend.Component.tsx +11 -12
  63. package/src/components/Legend/Legend.tsx +16 -16
  64. package/src/components/Legend/helpers/getLegendClasses.ts +59 -0
  65. package/src/components/Legend/helpers/index.ts +2 -1
  66. package/src/components/Legend/tests/getLegendClasses.test.ts +115 -0
  67. package/src/components/LineChart/components/LineChart.Circle.tsx +1 -1
  68. package/src/components/LineChart/helpers.ts +49 -43
  69. package/src/components/LineChart/index.tsx +135 -83
  70. package/src/components/LinearChart.tsx +187 -181
  71. package/src/components/PieChart/PieChart.tsx +7 -1
  72. package/src/components/Sankey/components/ColumnList.tsx +19 -0
  73. package/src/components/Sankey/components/Sankey.tsx +479 -0
  74. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +33 -0
  75. package/src/components/Sankey/index.tsx +1 -492
  76. package/src/components/Sankey/sankey.scss +22 -21
  77. package/src/components/Sankey/types/index.ts +1 -1
  78. package/src/components/Sankey/useSankeyAlert.tsx +60 -0
  79. package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
  80. package/src/data/initial-state.js +7 -12
  81. package/src/helpers/countNumOfTicks.ts +57 -0
  82. package/src/helpers/getQuartiles.ts +15 -18
  83. package/src/hooks/useMinMax.ts +44 -16
  84. package/src/hooks/useReduceData.ts +43 -10
  85. package/src/hooks/useScales.ts +90 -35
  86. package/src/hooks/useTooltip.tsx +59 -50
  87. package/src/scss/DataTable.scss +5 -0
  88. package/src/scss/main.scss +6 -20
  89. package/src/types/ChartConfig.ts +6 -19
  90. package/src/types/ChartContext.ts +4 -1
  91. package/src/types/ForestPlot.ts +8 -0
  92. package/src/components/BoxPlot/BoxPlot.jsx +0 -111
  93. package/src/hooks/useLegendClasses.ts +0 -72
@@ -33,7 +33,13 @@ const AreaChartStacked = ({ xScale, yScale, yMax, xMax, handleTooltipMouseOver,
33
33
  data && (
34
34
  <svg height={Number(yMax)}>
35
35
  <ErrorBoundary component='AreaChartStacked'>
36
- <Group className='area-chart' key='area-wrapper' left={Number(config.yAxis.size) + strokeWidth / 2} height={Number(yMax)} style={{ overflow: 'hidden' }}>
36
+ <Group
37
+ className='area-chart'
38
+ key='area-wrapper'
39
+ left={Number(config.yAxis.size) + strokeWidth / 2}
40
+ height={Number(yMax)}
41
+ style={{ overflow: 'hidden' }}
42
+ >
37
43
  <AreaStack
38
44
  data={data}
39
45
  keys={config.runtime.areaSeriesKeys.map(s => s.dataKey) || config.series.map(s => s.dataKey)}
@@ -44,8 +50,14 @@ const AreaChartStacked = ({ xScale, yScale, yMax, xMax, handleTooltipMouseOver,
44
50
  >
45
51
  {({ stacks, path }) => {
46
52
  return stacks.map((stack, stackIndex) => {
47
- let transparentArea = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(stack.key) === -1
48
- let displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(stack.key) !== -1
53
+ let transparentArea =
54
+ config.legend.behavior === 'highlight' &&
55
+ seriesHighlight.length > 0 &&
56
+ seriesHighlight.indexOf(stack.key) === -1
57
+ let displayArea =
58
+ config.legend.behavior === 'highlight' ||
59
+ seriesHighlight.length === 0 ||
60
+ seriesHighlight.indexOf(stack.key) !== -1
49
61
 
50
62
  return (
51
63
  // prettier-ignore
@@ -44,7 +44,7 @@ const CategoricalYAxis = ({ yMax, leftSize, max, xMax }) => {
44
44
  if (categoryObj[lastheight] === '') {
45
45
  // Calculate the sum of the numeric values of all other heights
46
46
  const sumOfValues = heights.slice(0, -1).reduce((sum, label) => {
47
- const value = parseInt(categoryObj[label], 10)
47
+ const value = Number(categoryObj[label])
48
48
  return sum + (isNaN(value) ? 0 : value)
49
49
  }, 0)
50
50
 
@@ -52,7 +52,7 @@ const CategoricalYAxis = ({ yMax, leftSize, max, xMax }) => {
52
52
  const newValue = max - sumOfValues
53
53
 
54
54
  // Update the last height with the new value
55
- categoryObj[lastheight] = newValue.toString()
55
+ categoryObj[lastheight] = newValue
56
56
  }
57
57
 
58
58
  return [categoryObj]
@@ -22,6 +22,8 @@ import chroma from 'chroma-js'
22
22
  // Local context and types
23
23
  import BarChartContext, { BarChartContextValues } from './context'
24
24
  import { ChartContext } from '../../../types/ChartContext'
25
+ import _ from 'lodash'
26
+ import { getBarData } from '../helpers/getBarData'
25
27
 
26
28
  export const BarChartHorizontal = () => {
27
29
  const { xScale, yScale, yMax, seriesScale } = useContext<BarChartContextValues>(BarChartContext)
@@ -35,9 +37,7 @@ export const BarChartHorizontal = () => {
35
37
  formatDate,
36
38
  parseDate,
37
39
  setSharedFilter,
38
- isNumber,
39
- getYAxisData,
40
- getXAxisData
40
+ isNumber
41
41
  } = useContext<ChartContext>(ConfigContext)
42
42
  const {
43
43
  isHorizontal,
@@ -60,13 +60,21 @@ export const BarChartHorizontal = () => {
60
60
 
61
61
  const { HighLightedBarUtils } = useHighlightedBars(config)
62
62
 
63
+ const hasConfidenceInterval = Object.keys(config.confidenceKeys).length > 0
64
+
65
+ const _data = getBarData(config, data, hasConfidenceInterval)
66
+
67
+ const root = document.documentElement
68
+
69
+ const coolGray90 = getComputedStyle(root).getPropertyValue('--cool-gray-90')
70
+
63
71
  return (
64
72
  config.visualizationSubType !== 'stacked' &&
65
73
  config.visualizationType === 'Bar' &&
66
74
  config.orientation === 'horizontal' && (
67
75
  <Group>
68
76
  <BarGroup
69
- data={config.preliminaryData?.some(pd => pd.value && pd.type === 'suppression') ? tableData : data}
77
+ data={config.preliminaryData?.some(pd => pd.value && pd.type === 'suppression') ? tableData : _data}
70
78
  keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
71
79
  height={yMax}
72
80
  x0={d => d[config.runtime.originalXAxis.dataKey]}
@@ -86,6 +94,8 @@ export const BarChartHorizontal = () => {
86
94
  top={barGroup.y}
87
95
  >
88
96
  {barGroup.bars.map((bar, index) => {
97
+ const datum = _data[barGroup.index]
98
+ const dataValue = datum[config.runtime.originalXAxis.dataKey]
89
99
  const scaleVal = config.yAxis.type === 'logarithmic' ? 0.1 : 0
90
100
  let highlightedBarValues = config.highlightedBarValues
91
101
  .map(item => item.value)
@@ -119,9 +129,7 @@ export const BarChartHorizontal = () => {
119
129
  const barX = bar.value < 0 ? Math.abs(xScale(bar.value)) : xScale(scaleVal)
120
130
  const yAxisValue = formatNumber(bar.value, 'left')
121
131
  const xAxisValue =
122
- config.runtime[section].type === 'date'
123
- ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey]))
124
- : data[barGroup.index][config.runtime.originalXAxis.dataKey]
132
+ config.runtime[section].type === 'date' ? formatDate(parseDate(dataValue)) : dataValue
125
133
 
126
134
  const barPosition = !isPositiveBar ? 'below' : 'above'
127
135
  const absentDataLabel = getAbsentDataLabel(yAxisValue)
@@ -167,7 +175,10 @@ export const BarChartHorizontal = () => {
167
175
  config.runtime.seriesLabels && config.runtime.seriesLabels[bar.key]
168
176
  ? colorScale(config.runtime.seriesLabels[bar.key])
169
177
  : colorScale(bar.key)
170
- barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor) // Color code by category
178
+ const hasDynamicCategory = config.series.find(s => s.dynamicCategory)
179
+ if (!hasDynamicCategory) {
180
+ barColor = assignColorsToValues(barGroups.length, barGroup.index, barColor) // Color code by category
181
+ }
171
182
  const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
172
183
  const isTwoToneLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'two-tone'
173
184
  const isHighlightedBar = highlightedBarValues?.includes(xAxisValue)
@@ -199,6 +210,16 @@ export const BarChartHorizontal = () => {
199
210
  return barColor
200
211
  }
201
212
 
213
+ // Confidence Interval Variables
214
+ const tickWidth = 5
215
+ const yPos = barHeight * bar.index + barHeight / 2
216
+ const [upperPos, lowerPos] = ['upper', 'lower'].map(position => {
217
+ if (!hasConfidenceInterval) return
218
+ const d = datum.dynamicData ? datum.CI[bar.key][position] : datum[config.confidenceKeys[position]]
219
+ return xScale(d)
220
+ })
221
+ // End Confidence Interval Variables
222
+
202
223
  return (
203
224
  <Group key={`${barGroup.index}--${index}`}>
204
225
  <Group key={`bar-sub-group-${barGroup.index}-${barGroup.x0}-${barY}--${index}`}>
@@ -321,10 +342,10 @@ export const BarChartHorizontal = () => {
321
342
  textAnchor={'start'}
322
343
  >
323
344
  {config.runtime.yAxis.type === 'date'
324
- ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey]))
345
+ ? formatDate(parseDate(dataValue))
325
346
  : isHorizontal
326
- ? data[barGroup.index][config.runtime.originalXAxis.dataKey]
327
- : formatNumber(data[barGroup.index][config.runtime.originalXAxis.dataKey])}
347
+ ? dataValue
348
+ : formatNumber(dataValue)}
328
349
  </Text>
329
350
  )}
330
351
 
@@ -357,6 +378,20 @@ export const BarChartHorizontal = () => {
357
378
  <animate attributeName='height' values={`0, ${lollipopShapeSize}`} dur='2.5s' />
358
379
  </rect>
359
380
  )}
381
+ {hasConfidenceInterval && (
382
+ <path
383
+ key={`confidence-interval-h-${yPos}-${datum[config.runtime.originalXAxis.dataKey]}`}
384
+ stroke={coolGray90}
385
+ strokeWidth='px'
386
+ d={`
387
+ M${lowerPos} ${yPos - tickWidth}
388
+ L${lowerPos} ${yPos + tickWidth}
389
+ M${lowerPos} ${yPos}
390
+ L${upperPos} ${yPos}
391
+ M${upperPos} ${yPos - tickWidth}
392
+ L${upperPos} ${yPos + tickWidth} `}
393
+ />
394
+ )}
360
395
  </Group>
361
396
  </Group>
362
397
  )
@@ -365,32 +400,6 @@ export const BarChartHorizontal = () => {
365
400
  ))
366
401
  }}
367
402
  </BarGroup>
368
-
369
- {Object.keys(config.confidenceKeys).length > 0
370
- ? data.map(d => {
371
- let xPos, yPos
372
- let upperPos
373
- let lowerPos
374
- let tickWidth = 5
375
- yPos = yScale(getXAxisData(d)) - 0.75 * config.barHeight
376
- upperPos = xScale(getYAxisData(d, config.confidenceKeys.upper))
377
- lowerPos = xScale(getYAxisData(d, config.confidenceKeys.lower))
378
- return (
379
- <path
380
- key={`confidence-interval-h-${yPos}-${d[config.runtime.originalXAxis.dataKey]}`}
381
- stroke='#333'
382
- strokeWidth='px'
383
- d={`
384
- M${lowerPos} ${yPos - tickWidth}
385
- L${lowerPos} ${yPos + tickWidth}
386
- M${lowerPos} ${yPos}
387
- L${upperPos} ${yPos}
388
- M${upperPos} ${yPos - tickWidth}
389
- L${upperPos} ${yPos + tickWidth} `}
390
- />
391
- )
392
- })
393
- : ''}
394
403
  </Group>
395
404
  )
396
405
  )
@@ -13,32 +13,62 @@ import createBarElement from '@cdc/core/components/createBarElement'
13
13
  const BarChartStackedVertical = () => {
14
14
  const [barWidth, setBarWidth] = useState(0)
15
15
  const { xScale, yScale, seriesScale, xMax, yMax } = useContext(BarChartContext)
16
- const { transformedData, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, setSharedFilter } = useContext(ConfigContext)
17
- const { isHorizontal, barBorderWidth, applyRadius, hoveredBar, getAdditionalColumn, onMouseLeaveBar, onMouseOverBar, barStackedSeriesKeys } = useBarChart()
16
+ const { transformedData, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, setSharedFilter } =
17
+ useContext(ConfigContext)
18
+ const {
19
+ isHorizontal,
20
+ barBorderWidth,
21
+ applyRadius,
22
+ hoveredBar,
23
+ getAdditionalColumn,
24
+ onMouseLeaveBar,
25
+ onMouseOverBar,
26
+ barStackedSeriesKeys
27
+ } = useBarChart()
18
28
  const { orientation } = config
19
29
 
20
30
  const data = config.brush?.active && config.brush.data?.length ? config.brush.data : transformedData
21
31
  const isDateAxisType = config.runtime.xAxis.type === 'date-time' || config.runtime.xAxis.type === 'date'
32
+ const isDateTimeScaleAxisType = config.runtime.xAxis.type === 'date-time'
22
33
 
23
34
  return (
24
35
  config.visualizationSubType === 'stacked' &&
25
36
  !isHorizontal && (
26
37
  <>
27
- <BarStack data={data} keys={barStackedSeriesKeys} x={d => d[config.runtime.xAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale}>
38
+ <BarStack
39
+ data={data}
40
+ keys={barStackedSeriesKeys}
41
+ x={d => d[config.runtime.xAxis.dataKey]}
42
+ xScale={xScale}
43
+ yScale={yScale}
44
+ color={colorScale}
45
+ >
28
46
  {barStacks =>
29
47
  barStacks.reverse().map(barStack =>
30
48
  barStack.bars.map(bar => {
31
- let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
32
- let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
33
- let barThickness = isDateAxisType ? seriesScale.range()[1] - seriesScale.range()[0] : xMax / barStack.bars.length
49
+ let transparentBar =
50
+ config.legend.behavior === 'highlight' &&
51
+ seriesHighlight.length > 0 &&
52
+ seriesHighlight.indexOf(bar.key) === -1
53
+ let displayBar =
54
+ config.legend.behavior === 'highlight' ||
55
+ seriesHighlight.length === 0 ||
56
+ seriesHighlight.indexOf(bar.key) !== -1
57
+ let barThickness = isDateAxisType
58
+ ? seriesScale.range()[1] - seriesScale.range()[0]
59
+ : xMax / barStack.bars.length
34
60
  if (config.runtime.xAxis.type !== 'date') barThickness = config.barThickness * barThickness
35
61
  // tooltips
36
62
  const rawXValue = bar.bar.data[config.runtime.xAxis.dataKey]
37
63
  const xAxisValue = isDateAxisType ? formatDate(parseDate(rawXValue)) : rawXValue
38
64
  const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0, 'left')
39
65
  if (!yAxisValue) return
40
- const barX = xScale(isDateAxisType ? parseDate(rawXValue) : rawXValue) - (isDateAxisType ? barThickness / 2 : 0)
41
- const xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
66
+ const barX =
67
+ xScale(isDateAxisType ? parseDate(rawXValue) : rawXValue) -
68
+ (isDateTimeScaleAxisType ? barThickness / 2 : 0)
69
+ const xAxisTooltip = config.runtime.xAxis.label
70
+ ? `${config.runtime.xAxis.label}: ${xAxisValue}`
71
+ : xAxisValue
42
72
  const additionalColTooltip = getAdditionalColumn(hoveredBar)
43
73
  const tooltipBody = `${config.runtime.seriesLabels[bar.key]}: ${yAxisValue}`
44
74
  const tooltip = `<ul>
@@ -51,7 +81,11 @@ const BarChartStackedVertical = () => {
51
81
 
52
82
  return (
53
83
  <Group key={`${barStack.index}--${bar.index}--${orientation}`}>
54
- <Group key={`bar-stack-${barStack.index}-${bar.index}`} id={`barStack${barStack.index}-${bar.index}`} className='stack vertical'>
84
+ <Group
85
+ key={`bar-stack-${barStack.index}-${bar.index}`}
86
+ id={`barStack${barStack.index}-${bar.index}`}
87
+ className='stack vertical'
88
+ >
55
89
  {createBarElement({
56
90
  config: config,
57
91
  seriesHighlight,
@@ -15,11 +15,14 @@ import { BarGroup } from '@visx/shape'
15
15
  import Regions from '../../Regions'
16
16
  // CDC core components and helpers
17
17
  import { isDateScale } from '@cdc/core/helpers/cove/date'
18
+ import isNumber from '@cdc/core/helpers/isNumber'
18
19
  import createBarElement from '@cdc/core/components/createBarElement'
19
20
  // Third party libraries
20
21
  import chroma from 'chroma-js'
21
22
  // Types
22
23
  import { type ChartContext } from '../../../types/ChartContext'
24
+ import _ from 'lodash'
25
+ import { getBarData } from '../helpers/getBarData'
23
26
 
24
27
  export const BarChartVertical = () => {
25
28
  const { xScale, yScale, xMax, yMax, seriesScale } = useContext<BarChartContextValues>(BarChartContext)
@@ -42,8 +45,14 @@ export const BarChartVertical = () => {
42
45
  } = useBarChart()
43
46
 
44
47
  // prettier-ignore
45
- const { colorScale, config, dashboardConfig, tableData, formatDate, formatNumber, getXAxisData, getYAxisData, isNumber, parseDate, seriesHighlight, setSharedFilter, transformedData, brushConfig } = useContext<ChartContext>(ConfigContext)
48
+ const { colorScale, config, dashboardConfig, tableData, formatDate, formatNumber, parseDate, seriesHighlight, setSharedFilter, transformedData, brushConfig } = useContext<ChartContext>(ConfigContext)
49
+
46
50
  const { HighLightedBarUtils } = useHighlightedBars(config)
51
+
52
+ const root = document.documentElement
53
+
54
+ const coolGray90 = getComputedStyle(root).getPropertyValue('--cool-gray-90')
55
+
47
56
  let data = transformedData
48
57
  // check if user add suppression
49
58
  const isSuppressionActive = config.preliminaryData.some(pd => pd.value && pd.type === 'suppression')
@@ -56,6 +65,9 @@ export const BarChartVertical = () => {
56
65
  data = brushConfig.data
57
66
  }
58
67
 
68
+ const hasConfidenceInterval = Object.keys(config.confidenceKeys).length > 0
69
+
70
+ const _data = getBarData(config, data, hasConfidenceInterval)
59
71
  return (
60
72
  config.visualizationSubType !== 'stacked' &&
61
73
  (config.visualizationType === 'Bar' ||
@@ -64,7 +76,7 @@ export const BarChartVertical = () => {
64
76
  config.orientation === 'vertical' && (
65
77
  <Group>
66
78
  <BarGroup
67
- data={data}
79
+ data={_data}
68
80
  keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
69
81
  height={yMax}
70
82
  x0={d => {
@@ -79,14 +91,16 @@ export const BarChartVertical = () => {
79
91
  }}
80
92
  >
81
93
  {barGroups => {
82
- return barGroups.map((barGroup, index) => (
94
+ return barGroups.map((barGroup, _index) => (
83
95
  <Group
84
- className={`bar-group-${barGroup.index}-${barGroup.x0}--${index} ${config.orientation}`}
85
- key={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`}
86
- id={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`}
96
+ className={`bar-group-${barGroup.index}-${barGroup.x0}--${_index} ${config.orientation}`}
97
+ key={`bar-group-${barGroup.index}-${barGroup.x0}--${_index}`}
98
+ id={`bar-group-${barGroup.index}-${barGroup.x0}--${_index}`}
87
99
  left={barGroup.x0}
88
100
  >
89
101
  {barGroup.bars.map((bar, index) => {
102
+ const datum = _data[barGroup.index]
103
+ const dataValue = datum[config.runtime.originalXAxis.dataKey]
90
104
  const scaleVal = config.yAxis.type === 'logarithmic' ? 0.1 : 0
91
105
  let highlightedBarValues = config.highlightedBarValues
92
106
  .map(item => item.value)
@@ -116,17 +130,12 @@ export const BarChartVertical = () => {
116
130
  setTotalBarsInGroup(barGroup.bars.length)
117
131
  const yAxisValue = formatNumber(/[a-zA-Z]/.test(String(bar.value)) ? '' : bar.value, 'left')
118
132
  const xAxisValue =
119
- config.runtime[section].type === 'date'
120
- ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey]))
121
- : data[barGroup.index][config.runtime.originalXAxis.dataKey]
133
+ config.runtime[section].type === 'date' ? formatDate(parseDate(dataValue)) : dataValue
122
134
 
123
135
  // create new Index for bars with negative values
124
136
  const newIndex = bar.value < 0 ? -1 : index
125
137
  // tooltips
126
- const additionalColTooltip = getAdditionalColumn(
127
- bar.key,
128
- data[barGroup.index][config.runtime.originalXAxis.dataKey]
129
- )
138
+ const additionalColTooltip = getAdditionalColumn(bar.key, dataValue)
130
139
  let xAxisTooltip = config.runtime.xAxis.label
131
140
  ? `${config.runtime.xAxis.label}: ${xAxisValue}`
132
141
  : xAxisValue
@@ -141,10 +150,6 @@ export const BarChartVertical = () => {
141
150
  // configure colors
142
151
  let labelColor = '#000000'
143
152
  labelColor = HighLightedBarUtils.checkFontColor(yAxisValue, highlightedBarValues, labelColor) // Set if background is transparent'
144
- let barColor =
145
- config.runtime.seriesLabels && config.runtime.seriesLabels[bar.key]
146
- ? colorScale(config.runtime.seriesLabels[bar.key])
147
- : colorScale(bar.key)
148
153
  const isRegularLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'regular'
149
154
  const isTwoToneLollipopColor = config.isLollipopChart && config.lollipopColorStyle === 'two-tone'
150
155
  const isHighlightedBar = highlightedBarValues?.includes(xAxisValue)
@@ -167,10 +172,8 @@ export const BarChartVertical = () => {
167
172
  bar,
168
173
  defaultBarHeight,
169
174
  config,
170
- isNumber,
171
175
  barWidth,
172
- isVertical: true,
173
- yAxisValue
176
+ isVertical: true
174
177
  })
175
178
 
176
179
  const absentDataLabel = getAbsentDataLabel(yAxisValue)
@@ -225,6 +228,22 @@ export const BarChartVertical = () => {
225
228
  return _barColor
226
229
  }
227
230
 
231
+ // Confidence Interval Variables
232
+ const tickWidth = 5
233
+ const xPos = barX + (config.xAxis.type !== 'date-time' ? barWidth / 2 : 0)
234
+
235
+ const upperPos = yScale(
236
+ datum.dynamicData && datum.CI[bar.key]
237
+ ? datum.CI[bar.key].upper
238
+ : datum[config.confidenceKeys.upper]
239
+ )
240
+ const lowerPos = yScale(
241
+ datum.dynamicData && datum.CI[bar.key]
242
+ ? datum.CI[bar.key].lower
243
+ : datum[config.confidenceKeys.lower]
244
+ )
245
+ // End Confidence Interval Variables
246
+
228
247
  return (
229
248
  <Group key={`${barGroup.index}--${index}`}>
230
249
  <Group key={`bar-sub-group-${barGroup.index}-${barGroup.x0}-${barY}--${index}`}>
@@ -341,7 +360,7 @@ export const BarChartVertical = () => {
341
360
  <rect
342
361
  display={displaylollipopShape}
343
362
  x={barX - lollipopBarWidth / 2}
344
- y={barY}
363
+ y={bar.y}
345
364
  width={lollipopShapeSize}
346
365
  height={lollipopShapeSize}
347
366
  fill={getBarBackgroundColor(colorScale(config.runtime.seriesLabels[bar.key]))}
@@ -353,6 +372,19 @@ export const BarChartVertical = () => {
353
372
  <animate attributeName='height' values={`0, ${lollipopShapeSize}`} dur='2.5s' />
354
373
  </rect>
355
374
  )}
375
+ {hasConfidenceInterval && bar.value !== undefined && datum && (
376
+ <path
377
+ key={`confidence-interval-v-${datum[config.runtime.originalXAxis.dataKey]}`}
378
+ stroke={coolGray90}
379
+ strokeWidth='px'
380
+ d={`M${xPos - tickWidth} ${upperPos}
381
+ L${xPos + tickWidth} ${upperPos}
382
+ M${xPos} ${upperPos}
383
+ L${xPos} ${lowerPos}
384
+ M${xPos - tickWidth} ${lowerPos}
385
+ L${xPos + tickWidth} ${lowerPos}`}
386
+ />
387
+ )}
356
388
  </Group>
357
389
  </Group>
358
390
  )
@@ -362,32 +394,6 @@ export const BarChartVertical = () => {
362
394
  }}
363
395
  </BarGroup>
364
396
 
365
- {Object.keys(config.confidenceKeys).length > 0
366
- ? data.map(d => {
367
- let xPos, yPos
368
- let upperPos
369
- let lowerPos
370
- let tickWidth = 5
371
- xPos = xScale(getXAxisData(d)) + (config.xAxis.type !== 'date-time' ? seriesScale.range()[1] / 2 : 0)
372
- upperPos = yScale(getYAxisData(d, config.confidenceKeys.lower))
373
- lowerPos = yScale(getYAxisData(d, config.confidenceKeys.upper))
374
- return (
375
- <path
376
- key={`confidence-interval-v-${yPos}-${d[config.runtime.originalXAxis.dataKey]}`}
377
- stroke='#333'
378
- strokeWidth='px'
379
- d={`
380
- M${xPos - tickWidth} ${upperPos}
381
- L${xPos + tickWidth} ${upperPos}
382
- M${xPos} ${upperPos}
383
- L${xPos} ${lowerPos}
384
- M${xPos - tickWidth} ${lowerPos}
385
- L${xPos + tickWidth} ${lowerPos}`}
386
- />
387
- )
388
- })
389
- : ''}
390
-
391
397
  <Regions xScale={xScale} yMax={yMax} barWidth={barWidth} totalBarsInGroup={totalBarsInGroup} />
392
398
  </Group>
393
399
  )
@@ -0,0 +1,28 @@
1
+ import _ from 'lodash'
2
+ import { TransformedData } from '../../../types/ChartContext'
3
+ import { ChartConfig } from '../../../types/ChartConfig'
4
+
5
+ export const getBarData = (config: ChartConfig, data: TransformedData[], hasConfidenceInterval: boolean) => {
6
+ const dynamicSeries = config.series.find(s => s.dynamicCategory)
7
+ if (!dynamicSeries) return data
8
+ const { dynamicCategory, dataKey } = dynamicSeries
9
+ const xAxisKey = config.runtime.originalXAxis.dataKey
10
+ const xAxisGroupDataLookup = _.groupBy(data, xAxisKey)
11
+ return Object.values(xAxisGroupDataLookup).map(group => {
12
+ return group.reduce((acc, datum) => {
13
+ const dataValue = datum[dataKey]
14
+ const dataCategory = datum[dynamicCategory]
15
+ if (hasConfidenceInterval) {
16
+ const { lower, upper } = config.confidenceKeys
17
+ if (!acc.CI) acc.CI = {}
18
+ const lowerValue = datum[lower]
19
+ const upperValue = datum[upper]
20
+ acc.CI[dataCategory] = { lower: lowerValue, upper: upperValue }
21
+ }
22
+ acc[dataCategory] = dataValue
23
+ acc[xAxisKey] = datum[xAxisKey]
24
+ acc.dynamicData = true
25
+ return acc
26
+ }, {})
27
+ })
28
+ }
@@ -1,11 +1,11 @@
1
1
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
2
+ import isNumber from '@cdc/core/helpers/isNumber'
2
3
 
3
4
  // Define an interface for the function's parameter
4
5
  interface BarConfigProps {
5
6
  defaultBarWidth?: number
6
7
  defaultBarHeight?: number
7
8
  bar?: { [key: string]: any }
8
- isNumber?: Function
9
9
  config: { [key: string]: any }
10
10
  barWidth: number
11
11
  isVertical: boolean
@@ -17,7 +17,6 @@ export const getBarConfig = ({
17
17
  defaultBarHeight,
18
18
  defaultBarWidth,
19
19
  config,
20
- isNumber,
21
20
  barWidth,
22
21
  isVertical
23
22
  }: BarConfigProps) => {
@@ -0,0 +1,74 @@
1
+ import { getBarData } from '../getBarData'
2
+
3
+ import { TransformedData } from '../../../../types/ChartContext'
4
+ import { ChartConfig } from '../../../../types/ChartConfig'
5
+
6
+ describe('getBarData', () => {
7
+ it('should return the original data when there is no dynamicSeries', () => {
8
+ const config = {
9
+ series: [{ dataKey: 'value' }],
10
+ runtime: { originalXAxis: { dataKey: 'category' } }
11
+ } as ChartConfig
12
+ const data: TransformedData[] = [
13
+ { category: 'A', value: 10 },
14
+ { category: 'B', value: 20 }
15
+ ]
16
+ const hasConfidenceInterval = false
17
+
18
+ const result = getBarData(config, data, hasConfidenceInterval)
19
+
20
+ expect(result).toEqual(data)
21
+ })
22
+
23
+ it('should return transformed data when there is a dynamicSeries but no confidence interval', () => {
24
+ const config = {
25
+ series: [{ dataKey: 'value', dynamicCategory: 'subCategory' }],
26
+ runtime: { originalXAxis: { dataKey: 'category' } }
27
+ } as ChartConfig
28
+ const data: TransformedData[] = [
29
+ { category: 'A', subCategory: 'X', value: 10 },
30
+ { category: 'A', subCategory: 'Y', value: 20 },
31
+ { category: 'B', subCategory: 'X', value: 30 }
32
+ ]
33
+ const hasConfidenceInterval = false
34
+
35
+ const result = getBarData(config, data, hasConfidenceInterval)
36
+
37
+ expect(result).toEqual([
38
+ { category: 'A', X: 10, Y: 20, dynamicData: true },
39
+ { category: 'B', X: 30, dynamicData: true }
40
+ ])
41
+ })
42
+
43
+ it('should return transformed data with confidence intervals when there is a dynamicSeries and confidence interval', () => {
44
+ const config = {
45
+ series: [{ dataKey: 'value', dynamicCategory: 'subCategory' }],
46
+ runtime: { originalXAxis: { dataKey: 'category' } },
47
+ confidenceKeys: { lower: 'lowerCI', upper: 'upperCI' }
48
+ } as ChartConfig
49
+ const data: TransformedData[] = [
50
+ { category: 'A', subCategory: 'X', value: 10, lowerCI: 5, upperCI: 15 },
51
+ { category: 'A', subCategory: 'Y', value: 20, lowerCI: 15, upperCI: 25 },
52
+ { category: 'B', subCategory: 'X', value: 30, lowerCI: 25, upperCI: 35 }
53
+ ]
54
+ const hasConfidenceInterval = true
55
+
56
+ const result = getBarData(config, data, hasConfidenceInterval)
57
+
58
+ expect(result).toEqual([
59
+ {
60
+ category: 'A',
61
+ X: 10,
62
+ Y: 20,
63
+ CI: { X: { lower: 5, upper: 15 }, Y: { lower: 15, upper: 25 } },
64
+ dynamicData: true
65
+ },
66
+ {
67
+ category: 'B',
68
+ X: 30,
69
+ CI: { X: { lower: 25, upper: 35 } },
70
+ dynamicData: true
71
+ }
72
+ ])
73
+ })
74
+ })