@cdc/chart 4.23.2 → 4.23.4

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 (94) hide show
  1. package/dist/cdcchart.js +42292 -40337
  2. package/examples/feature/__data__/area-chart.json +56 -0
  3. package/examples/{planet-example-data.json → feature/__data__/planet-example-data-max-increase.json} +4 -4
  4. package/examples/feature/__data__/planet-example-data.json +68 -0
  5. package/examples/feature/area/area-chart.json +244 -0
  6. package/examples/{example-bar-chart.json → feature/bar/example-bar-chart.json} +4 -1
  7. package/examples/feature/bar/horizontal-chart-max-increase.json +44 -0
  8. package/examples/{horizontal-chart.json → feature/bar/horizontal-chart.json} +10 -4
  9. package/examples/{horizontal-stacked-bar-chart.json → feature/bar/horizontal-stacked-bar-chart.json} +7 -3
  10. package/examples/{planet-chart-horizontal-example-config.json → feature/bar/planet-chart-horizontal-example-config.json} +8 -3
  11. package/examples/feature/bar/planet-example-config.json +156 -0
  12. package/examples/{box-plot.json → feature/boxplot/boxplot.json} +7 -8
  13. package/examples/feature/boxplot/testing.csv +38 -0
  14. package/examples/feature/combo/combochart-categories_are_numbers .json +18 -0
  15. package/examples/{planet-combo-example-config.json → feature/combo/planet-combo-example-config.json} +1 -1
  16. package/examples/feature/deviation/planet-deviation-config.json +168 -0
  17. package/examples/feature/deviation/planet-deviation-data.json +38 -0
  18. package/examples/feature/filters/filter-testing.json +178 -0
  19. package/examples/feature/forecasting/case_date_example.csv +130 -0
  20. package/examples/feature/forecasting/effective_reproduction.json +202 -0
  21. package/examples/feature/forecasting/r_data.csv +130 -0
  22. package/examples/feature/line/line-chart-max-increase.json +32 -0
  23. package/examples/feature/line/line-chart.json +124 -0
  24. package/examples/{paired-bar-example.json → feature/paired-bar/paired-bar-example.json} +10 -4
  25. package/examples/{planet-pie-example-config.json → feature/pie/planet-pie-example-config.json} +2 -2
  26. package/examples/{scatterplot-continuous.csv → feature/scatterplot/scatterplot-continuous.csv} +3 -3
  27. package/examples/{scatterplot.json → feature/scatterplot/scatterplot.json} +3 -3
  28. package/examples/feature/sparkline/example-sparkline.json +76 -0
  29. package/examples/feature/tests-big-small/big-small-test-bar.json +328 -0
  30. package/examples/feature/tests-big-small/big-small-test-line.json +328 -0
  31. package/examples/feature/tests-big-small/big-small-test-negative.json +328 -0
  32. package/examples/{case-rate-example-config.json → feature/tests-case-rate/case-rate-example-config.json} +2 -2
  33. package/examples/{covid-confidence-example-config.json → feature/tests-covid/covid-confidence-example-config.json} +8 -3
  34. package/examples/{covid-example-config.json → feature/tests-covid/covid-example-config.json} +7 -3
  35. package/examples/{cutoff-example-config.json → feature/tests-cutoff/cutoff-example-config.json} +7 -3
  36. package/examples/{date-exclusions-config.json → feature/tests-date-exclusions/date-exclusions-config.json} +2 -2
  37. package/examples/{example-bar-chart-nonnumeric.json → feature/tests-non-numerics/example-bar-chart-nonnumeric.json} +1 -1
  38. package/examples/{line-chart-nonnumeric.json → feature/tests-non-numerics/line-chart-nonnumeric.json} +5 -5
  39. package/examples/{planet-pie-example-config-nonnumeric.json → feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json} +2 -2
  40. package/examples/{sparkline-chart-nonnumeric.json → feature/tests-non-numerics/sparkline-chart-nonnumeric.json} +2 -2
  41. package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +31 -172
  42. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +145 -7
  43. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-confidence.json +1 -0
  44. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +96 -14
  45. package/examples/gallery/line/line.json +1 -0
  46. package/examples/gallery/paired-bar/paired-bar-chart.json +1 -0
  47. package/index.html +76 -35
  48. package/package.json +6 -3
  49. package/src/CdcChart.jsx +245 -106
  50. package/src/components/AreaChart.jsx +233 -0
  51. package/src/components/BarChart.jsx +103 -62
  52. package/src/components/BoxPlot.jsx +39 -18
  53. package/src/components/DataTable.jsx +26 -21
  54. package/src/components/DeviationBar.jsx +191 -0
  55. package/src/components/EditorPanel.jsx +662 -298
  56. package/src/components/Legend.jsx +59 -46
  57. package/src/components/LineChart.jsx +12 -36
  58. package/src/components/LinearChart.jsx +163 -64
  59. package/src/components/PairedBarChart.jsx +6 -7
  60. package/src/components/PieChart.jsx +12 -17
  61. package/src/components/ScatterPlot.jsx +19 -16
  62. package/src/components/SparkLine.jsx +84 -118
  63. package/src/components/useIntersectionObserver.jsx +1 -1
  64. package/src/data/initial-state.js +27 -7
  65. package/src/hooks/useColorPalette.js +58 -48
  66. package/src/hooks/useReduceData.js +3 -4
  67. package/src/index.jsx +3 -2
  68. package/src/scss/editor-panel.scss +20 -0
  69. package/src/scss/main.scss +8 -6
  70. package/src/test/CdcChart.test.jsx +6 -0
  71. package/examples/box-plot.csv +0 -5
  72. package/examples/dynamic-legends.json +0 -125
  73. package/examples/line-chart.json +0 -34
  74. package/examples/planet-example-config.json +0 -37
  75. package/examples/temp-example-config.json +0 -64
  76. package/examples/temp-example-data.json +0 -130
  77. package/src/components/Filters.jsx +0 -125
  78. /package/examples/{age-adjusted-rates.json → feature/__data__/age-adjusted-rates.json} +0 -0
  79. /package/examples/{new-data.csv → feature/__data__/new-data.csv} +0 -0
  80. /package/examples/{Barchart_with_negative.json → feature/bar/Barchart_with_negative.json} +0 -0
  81. /package/examples/{stacked-vertical-bar-example-negative.json → feature/bar/stacked-vertical-bar-example-negative.json} +0 -0
  82. /package/examples/{stacked-vertical-bar-example.json → feature/bar/stacked-vertical-bar-example.json} +0 -0
  83. /package/examples/{box-plot-data.json → feature/boxplot/box-plot-data.json} +0 -0
  84. /package/examples/{newdata.json → feature/boxplot/boxplot-data.json} +0 -0
  85. /package/examples/{paired-bar-data.json → feature/paired-bar/paired-bar-data.json} +0 -0
  86. /package/examples/{paired-bar-formatted.json → feature/paired-bar/paired-bar-formatted.json} +0 -0
  87. /package/examples/{case-rate-example-data.json → feature/tests-case-rate/case-rate-example-data.json} +0 -0
  88. /package/examples/{covid-example-data-confidence.json → feature/tests-covid/covid-example-data-confidence.json} +0 -0
  89. /package/examples/{covid-example-data.json → feature/tests-covid/covid-example-data.json} +0 -0
  90. /package/examples/{cutoff-example-data.json → feature/tests-cutoff/cutoff-example-data.json} +0 -0
  91. /package/examples/{date-exclusions-data.json → feature/tests-date-exclusions/date-exclusions-data.json} +0 -0
  92. /package/examples/{example-combo-bar-nonnumeric.json → feature/tests-non-numerics/example-combo-bar-nonnumeric.json} +0 -0
  93. /package/examples/{planet-example-data-nonnumeric.json → feature/tests-non-numerics/planet-example-data-nonnumeric.json} +0 -0
  94. /package/examples/{stacked-vertical-bar-example-nonnumerics.json → feature/tests-non-numerics/stacked-vertical-bar-example-nonnumerics.json} +0 -0
@@ -0,0 +1,233 @@
1
+ import React, { useContext, useEffect, useState } from 'react'
2
+
3
+ // cdc
4
+ import ConfigContext from '../ConfigContext'
5
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
6
+ import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
7
+
8
+ // visx & d3
9
+ import * as allCurves from '@visx/curve'
10
+ import { AreaClosed, LinePath, Bar } from '@visx/shape'
11
+ import { Group } from '@visx/group'
12
+ import { useTooltip, useTooltipInPortal, defaultStyles, Tooltip } from '@visx/tooltip'
13
+ import { localPoint } from '@visx/event'
14
+ import { bisector } from 'd3-array'
15
+
16
+ const CoveAreaChart = ({ xScale, yScale, yMax, xMax, chartRef }) => {
17
+ // enable various console logs in the file
18
+ const DEBUG = false
19
+ const [chartPosition, setChartPosition] = useState(null)
20
+
21
+ useEffect(() => {
22
+ setChartPosition(chartRef.current.getBoundingClientRect())
23
+ }, [chartRef])
24
+
25
+ // import data from context
26
+ const { transformedData: data, config, handleLineType, parseDate, formatDate, formatNumber, seriesHighlight, colorScale } = useContext(ConfigContext)
27
+ const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
28
+
29
+ // import tooltip helpers
30
+ const { tooltipData, showTooltip } = useTooltip()
31
+
32
+ // here we're inside of the svg,
33
+ // it appears we need to use TooltipInPortal.
34
+ const { TooltipInPortal } = useTooltipInPortal({
35
+ detectBounds: true,
36
+ // when tooltip containers are scrolled, this will correctly update the Tooltip position
37
+ scroll: true
38
+ })
39
+
40
+ // Draw transparent bars over the chart to get tooltip data
41
+ // Turn DEBUG on for additional context.
42
+ if (!data) return
43
+ let barThickness = xMax / data
44
+ let barThicknessAdjusted = barThickness * (config.barThickness || 0.8)
45
+ let offset = (barThickness * (1 - (config.barThickness || 0.8))) / 2
46
+
47
+ // Tooltip helper for getting data to the closest date/category hovered.
48
+ const getXValueFromCoordinate = x => {
49
+ if (config.xAxis.type === 'categorical') {
50
+ let eachBand = xScale.step()
51
+ let numerator = x
52
+ const index = Math.floor(Number(numerator) / eachBand)
53
+ return xScale.domain()[index - 1] // fixes off by 1 error
54
+ }
55
+
56
+ if (config.xAxis.type === 'date') {
57
+ const bisectDate = bisector(d => parseDate(d[config.xAxis.dataKey])).left
58
+ if (!x) return
59
+ if (!xScale) return
60
+ const x0 = xScale.invert(x)
61
+ const index = bisectDate(config.data, x0, 1)
62
+ const val = parseDate(config.data[index - 1][config.xAxis.dataKey])
63
+ return val
64
+ }
65
+ }
66
+
67
+ const handleMouseOver = (e, data) => {
68
+ // get the svg coordinates of the mouse
69
+ // and get the closest values
70
+ const eventSvgCoords = localPoint(e)
71
+ const { x, y } = eventSvgCoords
72
+
73
+ let closestXScaleValue = getXValueFromCoordinate(x)
74
+ let formattedDate = formatDate(closestXScaleValue)
75
+
76
+ let yScaleValues
77
+ if (config.xAxis.type === 'categorical') {
78
+ yScaleValues = data.filter(d => d[config.xAxis.dataKey] === closestXScaleValue)
79
+ } else {
80
+ yScaleValues = data.filter(d => formatDate(parseDate(d[config.xAxis.dataKey])) === formattedDate)
81
+ }
82
+
83
+ let seriesToInclude = []
84
+ let yScaleMaxValues = []
85
+ let itemsToLoop = [config.runtime.xAxis.dataKey, ...config.runtime.seriesKeys]
86
+
87
+ itemsToLoop.map(seriesKey => {
88
+ if (!seriesKey) return
89
+ if (!yScaleValues[0]) return
90
+ for (const item of Object.entries(yScaleValues[0])) {
91
+ if (item[0] === seriesKey) {
92
+ seriesToInclude.push(item)
93
+ }
94
+ }
95
+ })
96
+
97
+ // filter out the series that aren't added to the map.
98
+ seriesToInclude.map(series => yScaleMaxValues.push(Number(yScaleValues[0][series])))
99
+ if (!seriesToInclude) return
100
+ let tooltipDataFromSeries = Object.fromEntries(seriesToInclude) ? Object.fromEntries(seriesToInclude) : {}
101
+
102
+ let tooltipData = {}
103
+ tooltipData.data = tooltipDataFromSeries
104
+ tooltipData.dataXPosition = x + 20
105
+ tooltipData.dataYPosition = y - 100
106
+
107
+ let tooltipInformation = {
108
+ tooltipData: tooltipData,
109
+ tooltipTop: 0,
110
+ tooltipValues: yScaleValues,
111
+ tooltipLeft: x
112
+ }
113
+
114
+ showTooltip(tooltipInformation)
115
+ }
116
+
117
+ const TooltipListItem = ({ item }) => {
118
+ const [label, value] = item
119
+ return label === config.xAxis.dataKey ? `${label}: ${value}` : `${label}: ${formatNumber(value, 'left')}`
120
+ }
121
+
122
+ const handleX = d => {
123
+ return config.xAxis.type === 'date' ? xScale(parseDate(d[config.xAxis.dataKey])) : xScale(d[config.xAxis.dataKey])
124
+ }
125
+
126
+ const handleY = (d, index) => {
127
+ return yScale(d[config.series[index].dataKey])
128
+ }
129
+
130
+ return (
131
+ data && (
132
+ <ErrorBoundary component='AreaChart'>
133
+ <Group className='area-chart' key='area-wrapper' left={Number(config.yAxis.size)}>
134
+ {(config.runtime.areaSeriesKeys || config.runtime.seriesKeys).map((s, index) => {
135
+ let seriesColor = colorPalettesChart[config.palette][index]
136
+ let curveType = allCurves[s.lineType]
137
+ let transparentArea = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(s.dataKey) === -1
138
+ let displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(s.dataKey) !== -1
139
+
140
+ data.map(d => xScale(parseDate(d[config.xAxis.dataKey])))
141
+
142
+ return (
143
+ <React.Fragment key={index}>
144
+ {/* prettier-ignore */}
145
+ <LinePath
146
+ data={data}
147
+ x={d => handleX(d)}
148
+ y={d => yScale(d[config.series[index].dataKey])}
149
+ stroke={displayArea ? seriesColor : 'transparent'}
150
+ strokeWidth={2}
151
+ strokeOpacity={1}
152
+ shapeRendering='geometricPrecision'
153
+ curve={curveType}
154
+ strokeDasharray={s.type ? handleLineType(s.type) : 0}
155
+ />
156
+
157
+ {/* prettier-ignore */}
158
+ <AreaClosed
159
+ key={'area-chart'}
160
+ fill={ displayArea ? colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000' : 'transparent'}
161
+ fillOpacity={transparentArea ? 0.25 : 0.5}
162
+ data={data} x={d => handleX(d)}
163
+ y={d => handleY(d, index)}
164
+ yScale={yScale}
165
+ curve={curveType}
166
+ strokeDasharray={s.type ? handleLineType(s.typ) : 0}
167
+ />
168
+
169
+ {/* Transparent bar for tooltips */}
170
+ {/* prettier-ignore */}
171
+ <Bar
172
+ width={ Number(xMax)}
173
+ height={ Number(yMax)}
174
+ fill={DEBUG ? 'red' : 'transparent'}
175
+ fillOpacity={0.05}
176
+ style={DEBUG ? { stroke: 'black', strokeWidth: 2 } : {}}
177
+ onMouseMove={e => handleMouseOver(e, data)}
178
+ />
179
+
180
+ {/* circles that appear on hover */}
181
+ {tooltipData && (
182
+ <circle
183
+ cx={config.xAxis.type === 'categorical' ? xScale(tooltipData.data[config.xAxis.dataKey]) : xScale(parseDate(tooltipData.data[config.xAxis.dataKey]))}
184
+ cy={yScale(tooltipData.data[s.dataKey])}
185
+ r={4.5}
186
+ opacity={1}
187
+ fillOpacity={1}
188
+ fill={displayArea ? (colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s.dataKey] : s.dataKey) : '#000') : 'transparent'}
189
+ style={{ filter: 'unset', opacity: 1 }}
190
+ />
191
+ )}
192
+
193
+ {/* another tool for showing bars during debug mode. */}
194
+ {DEBUG &&
195
+ data.map((item, index) => {
196
+ return (
197
+ <Bar
198
+ className='bar-here'
199
+ x={Number(barThickness * index + offset)}
200
+ y={d => Number(yScale(d[config.series[index].dataKey]))}
201
+ yScale={yScale}
202
+ width={barThicknessAdjusted}
203
+ height={yMax}
204
+ fill={'transparent'}
205
+ fillOpacity={1}
206
+ style={{ stroke: 'black', strokeWidth: 2 }}
207
+ onMouseMove={e => handleMouseOver(e, data)}
208
+ />
209
+ )
210
+ })}
211
+
212
+ {tooltipData && (
213
+ <TooltipInPortal key={Math.random()} top={tooltipData.dataYPosition + chartPosition?.top} left={tooltipData.dataXPosition + chartPosition?.left} style={defaultStyles}>
214
+ <ul style={{ listStyle: 'none', paddingLeft: 'unset', fontFamily: 'sans-serif', margin: 'auto', lineHeight: '1rem' }} data-tooltip-id={tooltip_id}>
215
+ {typeof tooltipData === 'object' &&
216
+ Object.entries(tooltipData.data).map(item => (
217
+ <li style={{ padding: '2.5px 0' }}>
218
+ <TooltipListItem item={item} />
219
+ </li>
220
+ ))}
221
+ </ul>
222
+ </TooltipInPortal>
223
+ )}
224
+ </React.Fragment>
225
+ )
226
+ })}
227
+ </Group>
228
+ </ErrorBoundary>
229
+ )
230
+ )
231
+ }
232
+
233
+ export default CoveAreaChart
@@ -8,10 +8,7 @@ import ConfigContext from '../ConfigContext'
8
8
  import { BarStackHorizontal } from '@visx/shape'
9
9
 
10
10
  export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getXAxisData, getYAxisData, animatedChart, visible }) {
11
- const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, updateConfig, colorPalettes, formatDate, isNumber, cleanData, getTextWidth, parseDate } = useContext(ConfigContext)
12
- // Just do this once up front otherwise we end up
13
- // calling clean several times on same set of data (TT)
14
- const cleanedData = cleanData(data, config.xAxis.dataKey)
11
+ const { transformedData: data, colorScale, seriesHighlight, config, formatNumber, updateConfig, colorPalettes, formatDate, isNumber, getTextWidth, parseDate } = useContext(ConfigContext)
15
12
 
16
13
  const { orientation, visualizationSubType } = config
17
14
  const isHorizontal = orientation === 'horizontal'
@@ -32,17 +29,15 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
32
29
  const fontSize = { small: 16, medium: 18, large: 20 }
33
30
  const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
34
31
 
35
- const applyRadius = (index, isNegative) => {
32
+ const applyRadius = index => {
36
33
  if (index === undefined || index === null || !isRounded) return
37
34
  let style = {}
38
35
 
39
36
  if ((isStacked && index + 1 === stackCount) || !isStacked) {
40
- if (isNegative) {
41
- // reverse borderRadius to bottom
42
- style = isHorizontal ? { borderRadius: `0 ${radius} ${radius} 0` } : { borderRadius: `0 0 ${radius} ${radius}` }
43
- } else {
44
- style = isHorizontal ? { borderRadius: `0 ${radius} ${radius} 0` } : { borderRadius: `${radius} ${radius} 0 0` }
45
- }
37
+ style = isHorizontal ? { borderRadius: `0 ${radius} ${radius} 0` } : { borderRadius: `${radius} ${radius} 0 0` }
38
+ }
39
+ if (!isStacked && index === -1) {
40
+ style = isHorizontal ? { borderRadius: `${radius} 0 0 ${radius} ` } : { borderRadius: ` 0 0 ${radius} ${radius}` }
46
41
  }
47
42
  if (tipRounding === 'full' && isStacked && index === 0 && stackCount > 1) {
48
43
  style = isHorizontal ? { borderRadius: `${radius} 0 0 ${radius}` } : { borderRadius: `0 0 ${radius} ${radius}` }
@@ -52,7 +47,6 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
52
47
  }
53
48
  return style
54
49
  }
55
- // }
56
50
 
57
51
  const updateBars = defaultBars => {
58
52
  // function updates stacked && regular && lollipop horizontal bars
@@ -84,7 +78,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
84
78
 
85
79
  // return new updated bars/groupes
86
80
  return barsArr.map((bar, i) => {
87
- // set bars Y dynamycly to handle space between bars
81
+ // set bars Y dynamically to handle space between bars
88
82
  let y = 0
89
83
  bar.index !== 0 && (y = (barHeight + barSpace + labelHeight) * i)
90
84
 
@@ -105,13 +99,13 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
105
99
  }
106
100
  })
107
101
  }
108
- }, [config, updateConfig])
102
+ }, [config, updateConfig]) // eslint-disable-line
109
103
 
110
104
  useEffect(() => {
111
105
  if (config.isLollipopChart === false && config.barHeight < 25) {
112
106
  updateConfig({ ...config, barHeight: 25 })
113
107
  }
114
- }, [config.isLollipopChart])
108
+ }, [config.isLollipopChart]) // eslint-disable-line
115
109
 
116
110
  useEffect(() => {
117
111
  if (config.visualizationSubType === 'horizontal') {
@@ -120,7 +114,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
120
114
  orientation: 'horizontal'
121
115
  })
122
116
  }
123
- }, [])
117
+ }, []) // eslint-disable-line
124
118
 
125
119
  useEffect(() => {
126
120
  if (config.barStyle === 'lollipop' && !config.isLollipopChart) {
@@ -129,14 +123,14 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
129
123
  if (isRounded || config.barStyle === 'flat') {
130
124
  updateConfig({ ...config, isLollipopChart: false })
131
125
  }
132
- }, [config.barStyle])
126
+ }, [config.barStyle]) // eslint-disable-line
133
127
 
134
128
  return (
135
129
  <ErrorBoundary component='BarChart'>
136
130
  <Group left={parseFloat(config.runtime.yAxis.size)}>
137
131
  {/* Stacked Vertical */}
138
132
  {config.visualizationSubType === 'stacked' && !isHorizontal && (
139
- <BarStack data={cleanedData} keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys} x={d => d[config.runtime.xAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale}>
133
+ <BarStack data={data} keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys} x={d => d[config.runtime.xAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale}>
140
134
  {barStacks =>
141
135
  barStacks.reverse().map(barStack =>
142
136
  barStack.bars.map(bar => {
@@ -147,8 +141,9 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
147
141
  let offset = (barThickness * (1 - (config.barThickness || 0.8))) / 2
148
142
  // tooltips
149
143
  const xAxisValue = config.runtime.xAxis.type === 'date' ? formatDate(parseDate(data[bar.index][config.runtime.xAxis.dataKey])) : data[bar.index][config.runtime.xAxis.dataKey]
150
- const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0)
151
- const style = applyRadius(barStack.index, yAxisValue < 0)
144
+ const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0, 'left')
145
+
146
+ const style = applyRadius(barStack.index)
152
147
  let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
153
148
  const xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
154
149
  if (!hasMultipleSeries) {
@@ -161,7 +156,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
161
156
  ${xAxisTooltip}
162
157
  </div>`
163
158
  return (
164
- <>
159
+ <Group key={`${barStack.index}--${bar.index}--${orientation}`}>
165
160
  <style>
166
161
  {`
167
162
  #barStack${barStack.index}-${bar.index} rect,
@@ -173,7 +168,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
173
168
  </style>
174
169
  <Group key={`bar-stack-${barStack.index}-${bar.index}`} id={`barStack${barStack.index}-${bar.index}`} className='stack vertical'>
175
170
  <Text display={config.labels && displayBar ? 'block' : 'none'} opacity={transparentBar ? 0.5 : 1} x={barThickness * bar.index + offset} y={bar.y - 5} fill={bar.color} textAnchor='middle'>
176
- {formatNumber(bar.bar ? bar.bar.data[bar.key] : 0)}
171
+ {yAxisValue}
177
172
  </Text>
178
173
  <foreignObject
179
174
  key={`bar-stack-${barStack.index}-${bar.index}`}
@@ -188,7 +183,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
188
183
  data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
189
184
  ></foreignObject>
190
185
  </Group>
191
- </>
186
+ </Group>
192
187
  )
193
188
  })
194
189
  )
@@ -199,7 +194,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
199
194
  {/* Stacked Horizontal */}
200
195
  {config.visualizationSubType === 'stacked' && isHorizontal && (
201
196
  <>
202
- <BarStackHorizontal data={cleanedData} keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys} height={yMax} y={d => d[config.runtime.yAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale} offset='none'>
197
+ <BarStackHorizontal data={data} keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys} height={yMax} y={d => d[config.runtime.yAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale} offset='none'>
203
198
  {barStacks =>
204
199
  barStacks.map(barStack =>
205
200
  updateBars(barStack.bars).map((bar, index) => {
@@ -208,9 +203,9 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
208
203
  config.barHeight = Number(config.barHeight)
209
204
  let labelColor = '#000000'
210
205
  // tooltips
211
- const xAxisValue = formatNumber(data[bar.index][bar.key])
206
+ const xAxisValue = formatNumber(data[bar.index][bar.key], 'left')
212
207
  const yAxisValue = config.runtime.yAxis.type === 'date' ? formatDate(parseDate(data[bar.index][config.runtime.originalXAxis.dataKey])) : data[bar.index][config.runtime.originalXAxis.dataKey]
213
- const style = applyRadius(barStack.index, yAxisValue < 0)
208
+ const style = applyRadius(barStack.index)
214
209
  let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
215
210
  let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
216
211
  if (!hasMultipleSeries) {
@@ -264,7 +259,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
264
259
  </Text>
265
260
  )}
266
261
 
267
- {displayNumbersOnBar && textWidth + 50 < bar.width && (
262
+ {displayNumbersOnBar && textWidth < bar.width && (
268
263
  <Text
269
264
  display={displayBar ? 'block' : 'none'}
270
265
  x={bar.x + barStack.bars[bar.index].width / 2} // padding
@@ -280,7 +275,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
280
275
  }
281
276
  }}
282
277
  >
283
- {formatNumber(data[bar.index][bar.key])}
278
+ {xAxisValue}
284
279
  </Text>
285
280
  )}
286
281
  </Group>
@@ -297,7 +292,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
297
292
  {config.visualizationSubType !== 'stacked' && (
298
293
  <Group>
299
294
  <BarGroup
300
- data={cleanedData}
295
+ data={data}
301
296
  keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
302
297
  height={yMax}
303
298
  x0={d => d[config.runtime.originalXAxis.dataKey]}
@@ -313,6 +308,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
313
308
  <Group
314
309
  className={`bar-group-${barGroup.index}-${barGroup.x0}--${index} ${config.orientation}`}
315
310
  key={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`}
311
+ id={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`}
316
312
  top={config.runtime.horizontal ? barGroup.y : 0}
317
313
  left={config.runtime.horizontal ? 0 : (xMax / barGroups.length) * barGroup.index}
318
314
  >
@@ -323,7 +319,8 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
323
319
  let barY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
324
320
  let barGroupWidth = ((config.runtime.horizontal ? yMax : xMax) / barGroups.length) * (config.barThickness || 0.8)
325
321
  let offset = (((config.runtime.horizontal ? yMax : xMax) / barGroups.length) * (1 - (config.barThickness || 0.8))) / 2
326
-
322
+ const barX = bar.value < 0 ? Math.abs(xScale(bar.value)) : xScale(0)
323
+ const barWidthHorizontal = Math.abs(xScale(bar.value) - xScale(0))
327
324
  // ! Unsure if this should go back.
328
325
  if (config.isLollipopChart) {
329
326
  offset = (config.runtime.horizontal ? yMax : xMax) / barGroups.length / 2 - lollipopBarWidth / 2
@@ -341,7 +338,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
341
338
  }
342
339
  if (config.legend.colorCode && config.series.length === 1) barColor = palette[barGroup.index]
343
340
 
344
- let yAxisValue = formatNumber(bar.value)
341
+ let yAxisValue = formatNumber(bar.value, 'left')
345
342
  let xAxisValue = config.runtime[section].type === 'date' ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey])) : data[barGroup.index][config.runtime.originalXAxis.dataKey]
346
343
 
347
344
  if (config.runtime.horizontal) {
@@ -350,18 +347,38 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
350
347
  xAxisValue = tempValue
351
348
  barWidth = config.barHeight
352
349
  }
350
+
351
+ const barPosition = bar.value < 0 ? 'below' : 'above'
352
+ const textX = barPosition === 'below' ? 0 : 0
353
+
353
354
  // check if bar text/value string fits into each bars.
354
355
  let textWidth = getTextWidth(xAxisValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
355
- let textFits = textWidth < bar.y - 5 // minus padding 5
356
-
356
+ let textFits = textWidth < barWidthHorizontal - 5 // minus padding 5
357
357
  let labelColor = '#000000'
358
358
 
359
359
  // Set label color
360
360
  if (chroma.contrast(labelColor, barColor) < 4.9) {
361
- if (textFits) labelColor = '#FFFFFF'
361
+ textFits ? (labelColor = '#FFFFFF') : '#000000'
362
+ }
363
+
364
+ // control text position
365
+ let textAnchor = textFits ? 'end' : 'start'
366
+ let textAnchorLollipop = 'start'
367
+ let textPadding = textFits ? -5 : 5
368
+ let textPaddingLollipop = 10
369
+ // if bars are negative we change positions of text
370
+ if (barPosition === 'below') {
371
+ textAnchor = textFits ? 'start' : 'end'
372
+ textPadding = textFits ? 5 : -5
373
+ if (config.isLollipopChart) {
374
+ textAnchorLollipop = 'end'
375
+ textPaddingLollipop = -10
376
+ }
362
377
  }
363
378
 
364
- const style = applyRadius(index, yAxisValue < 0)
379
+ // create new Index based on bar value for border Radius
380
+ const newIndex = bar.value < 0 ? -1 : index
381
+ const style = applyRadius(newIndex)
365
382
 
366
383
  let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
367
384
  let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
@@ -379,7 +396,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
379
396
  </div>`
380
397
 
381
398
  return (
382
- <>
399
+ <Group key={`${barGroup.index}--${index}--${orientation}`}>
383
400
  {/* This feels gross but inline transition was not working well*/}
384
401
  <style>
385
402
  {`
@@ -393,9 +410,9 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
393
410
  <foreignObject
394
411
  id={`barGroup${barGroup.index}`}
395
412
  key={`bar-group-bar-${barGroup.index}-${bar.index}-${bar.value}-${bar.key}`}
396
- x={config.runtime.horizontal ? 0 : barWidth * bar.index + offset}
413
+ x={config.runtime.horizontal ? barX : barWidth * bar.index + offset}
397
414
  y={config.runtime.horizontal ? barWidth * bar.index : barY}
398
- width={config.runtime.horizontal ? bar.y : barWidth}
415
+ width={config.runtime.horizontal ? barWidthHorizontal : barWidth}
399
416
  height={isHorizontal && !config.isLollipopChart ? barWidth : isHorizontal && config.isLollipopChart ? lollipopBarWidth : barHeight}
400
417
  style={{
401
418
  background: config.isLollipopChart && config.lollipopColorStyle === 'regular' ? barColor : config.isLollipopChart && config.lollipopColorStyle === 'two-tone' ? chroma(barColor).brighten(1) : barColor,
@@ -413,9 +430,9 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
413
430
  x={bar.y}
414
431
  y={config.barHeight / 2 + config.barHeight * bar.index}
415
432
  fill={labelColor}
416
- dx={textFits ? -5 : 5}
433
+ dx={textPadding}
417
434
  verticalAnchor='middle'
418
- textAnchor={textFits ? 'end' : 'start'}
435
+ textAnchor={textAnchor}
419
436
  >
420
437
  {xAxisValue}
421
438
  </Text>
@@ -424,10 +441,11 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
424
441
  {orientation === 'horizontal' && config.isLollipopChart && displayNumbersOnBar && (
425
442
  <Text
426
443
  display={displayBar ? 'block' : 'none'}
427
- x={`${bar.y + (config.isLollipopChart ? 15 : 5) + (config.isLollipopChart && barGroup.bars.length === bar.index ? offset : 0)}`} // padding
444
+ x={bar.y} // padding
428
445
  y={0}
429
446
  fill={'#000000'}
430
- textAnchor='start'
447
+ dx={textPaddingLollipop}
448
+ textAnchor={textAnchorLollipop}
431
449
  verticalAnchor='middle'
432
450
  fontWeight={'normal'}
433
451
  >
@@ -446,7 +464,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
446
464
  ;
447
465
  {orientation === 'vertical' && (
448
466
  <Text display={config.labels && displayBar ? 'block' : 'none'} opacity={transparentBar ? 0.5 : 1} x={barWidth * (bar.index + 0.5) + offset} y={barY - 5} fill={barColor} textAnchor='middle'>
449
- {formatNumber(bar.value)}
467
+ {yAxisValue}
450
468
  </Text>
451
469
  )}
452
470
  ;
@@ -478,7 +496,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
478
496
  </rect>
479
497
  )}
480
498
  </Group>
481
- </>
499
+ </Group>
482
500
  )
483
501
  })}
484
502
  </Group>
@@ -488,25 +506,48 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
488
506
 
489
507
  {Object.keys(config.confidenceKeys).length > 0
490
508
  ? data.map(d => {
491
- let xPos = xScale(getXAxisData(d))
492
- let upperPos = yScale(getYAxisData(d, config.confidenceKeys.lower))
493
- let lowerPos = yScale(getYAxisData(d, config.confidenceKeys.upper))
509
+ let xPos, yPos
510
+ let upperPos
511
+ let lowerPos
494
512
  let tickWidth = 5
495
-
496
- return (
497
- <path
498
- key={`confidence-interval-${d[config.runtime.originalXAxis.dataKey]}`}
499
- stroke='#333'
500
- strokeWidth='2px'
501
- d={`
502
- M${xPos - tickWidth} ${upperPos}
503
- L${xPos + tickWidth} ${upperPos}
504
- M${xPos} ${upperPos}
505
- L${xPos} ${lowerPos}
506
- M${xPos - tickWidth} ${lowerPos}
507
- L${xPos + tickWidth} ${lowerPos}`}
508
- />
509
- )
513
+ // DEV-3264 Make Confidence Intervals work on horizontal bar charts
514
+ if (orientation === 'horizontal') {
515
+ yPos = yScale(getXAxisData(d)) - 0.75 * config.barHeight
516
+ upperPos = xScale(getYAxisData(d, config.confidenceKeys.upper))
517
+ lowerPos = xScale(getYAxisData(d, config.confidenceKeys.lower))
518
+ return (
519
+ <path
520
+ key={`confidence-interval-h-${yPos}-${d[config.runtime.originalXAxis.dataKey]}`}
521
+ stroke='#333'
522
+ strokeWidth='px'
523
+ d={`
524
+ M${lowerPos} ${yPos - tickWidth}
525
+ L${lowerPos} ${yPos + tickWidth}
526
+ M${lowerPos} ${yPos}
527
+ L${upperPos} ${yPos}
528
+ M${upperPos} ${yPos - tickWidth}
529
+ L${upperPos} ${yPos + tickWidth} `}
530
+ />
531
+ )
532
+ } else {
533
+ xPos = xScale(getXAxisData(d))
534
+ upperPos = yScale(getYAxisData(d, config.confidenceKeys.lower))
535
+ lowerPos = yScale(getYAxisData(d, config.confidenceKeys.upper))
536
+ return (
537
+ <path
538
+ key={`confidence-interval-v-${yPos}-${d[config.runtime.originalXAxis.dataKey]}`}
539
+ stroke='#333'
540
+ strokeWidth='px'
541
+ d={`
542
+ M${xPos - tickWidth} ${upperPos}
543
+ L${xPos + tickWidth} ${upperPos}
544
+ M${xPos} ${upperPos}
545
+ L${xPos} ${lowerPos}
546
+ M${xPos - tickWidth} ${lowerPos}
547
+ L${xPos + tickWidth} ${lowerPos}`}
548
+ />
549
+ )
550
+ }
510
551
  })
511
552
  : ''}
512
553
  </Group>