@cdc/chart 4.23.1 → 4.23.3

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 (74) hide show
  1. package/dist/cdcchart.js +56289 -702
  2. package/examples/Barchart_with_negative.json +34 -0
  3. package/examples/area-chart.json +187 -0
  4. package/examples/big-small-test-bar.json +328 -0
  5. package/examples/big-small-test-line.json +328 -0
  6. package/examples/big-small-test-negative.json +328 -0
  7. package/examples/box-plot.json +1 -2
  8. package/examples/dynamic-legends.json +1 -1
  9. package/examples/example-bar-chart-nonnumeric.json +36 -0
  10. package/examples/example-bar-chart.json +36 -0
  11. package/examples/example-combo-bar-nonnumeric.json +105 -0
  12. package/examples/example-sparkline.json +76 -0
  13. package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +31 -172
  14. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +1 -1
  15. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-confidence.json +1 -0
  16. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +96 -14
  17. package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +2 -2
  18. package/examples/gallery/line/line.json +1 -0
  19. package/examples/gallery/paired-bar/paired-bar-chart.json +65 -13
  20. package/examples/horizontal-chart-max-increase.json +38 -0
  21. package/examples/line-chart-max-increase.json +32 -0
  22. package/examples/line-chart-nonnumeric.json +32 -0
  23. package/examples/line-chart.json +21 -63
  24. package/examples/newdata.json +1 -1
  25. package/examples/planet-combo-example-config.json +143 -20
  26. package/examples/planet-deviation-config.json +168 -0
  27. package/examples/planet-deviation-data.json +38 -0
  28. package/examples/planet-example-config.json +139 -20
  29. package/examples/planet-example-data-max-increase.json +56 -0
  30. package/examples/planet-example-data-nonnumeric.json +56 -0
  31. package/examples/planet-example-data.json +9 -9
  32. package/examples/planet-pie-example-config-nonnumeric.json +30 -0
  33. package/examples/scatterplot-continuous.csv +17 -0
  34. package/examples/scatterplot.json +136 -0
  35. package/examples/sparkline-chart-nonnumeric.json +76 -0
  36. package/examples/stacked-vertical-bar-example-negative.json +154 -0
  37. package/examples/stacked-vertical-bar-example-nonnumerics.json +154 -0
  38. package/index.html +91 -0
  39. package/package.json +33 -24
  40. package/src/{CdcChart.tsx → CdcChart.jsx} +196 -124
  41. package/src/components/AreaChart.jsx +198 -0
  42. package/src/components/{BarChart.tsx → BarChart.jsx} +154 -122
  43. package/src/components/BoxPlot.jsx +101 -0
  44. package/src/components/{DataTable.tsx → DataTable.jsx} +109 -28
  45. package/src/components/DeviationBar.jsx +191 -0
  46. package/src/components/{EditorPanel.js → EditorPanel.jsx} +676 -157
  47. package/src/components/{Filters.js → Filters.jsx} +6 -11
  48. package/src/components/Legend.jsx +316 -0
  49. package/src/components/{LineChart.tsx → LineChart.jsx} +22 -26
  50. package/src/components/{LinearChart.tsx → LinearChart.jsx} +214 -91
  51. package/src/components/{PairedBarChart.tsx → PairedBarChart.jsx} +44 -78
  52. package/src/components/{PieChart.tsx → PieChart.jsx} +26 -44
  53. package/src/components/ScatterPlot.jsx +51 -0
  54. package/src/components/SparkLine.jsx +218 -0
  55. package/src/components/{useIntersectionObserver.tsx → useIntersectionObserver.jsx} +2 -2
  56. package/src/data/initial-state.js +51 -5
  57. package/src/hooks/useColorPalette.js +68 -0
  58. package/src/hooks/{useReduceData.ts → useReduceData.js} +26 -16
  59. package/src/hooks/useRightAxis.js +3 -1
  60. package/src/index.jsx +16 -0
  61. package/src/scss/DataTable.scss +22 -0
  62. package/src/scss/editor-panel.scss +5 -0
  63. package/src/scss/main.scss +30 -10
  64. package/src/test/CdcChart.test.jsx +6 -0
  65. package/vite.config.js +4 -0
  66. package/dist/495.js +0 -3
  67. package/dist/703.js +0 -1
  68. package/src/components/BoxPlot.js +0 -92
  69. package/src/components/Legend.js +0 -291
  70. package/src/components/SparkLine.js +0 -185
  71. package/src/hooks/useColorPalette.ts +0 -76
  72. package/src/index.html +0 -67
  73. package/src/index.tsx +0 -18
  74. /package/src/{context.tsx → ConfigContext.jsx} +0 -0
@@ -4,11 +4,15 @@ import { BarGroup, BarStack } from '@visx/shape'
4
4
  import { Text } from '@visx/text'
5
5
  import chroma from 'chroma-js'
6
6
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
7
- import Context from '../context'
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, parseDate } = useContext<any>(Context)
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)
15
+
12
16
  const { orientation, visualizationSubType } = config
13
17
  const isHorizontal = orientation === 'horizontal'
14
18
 
@@ -25,15 +29,20 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
25
29
  const radius = config.roundingStyle === 'standard' ? '8px' : config.roundingStyle === 'shallow' ? '5px' : config.roundingStyle === 'finger' ? '15px' : '0px'
26
30
  const stackCount = config.runtime.seriesKeys.length
27
31
  const barBorderWidth = 1
28
- const fontSize = { small: 14, medium: 16, large: 18 }
32
+ const fontSize = { small: 16, medium: 18, large: 20 }
29
33
  const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
30
34
 
31
- const applyRadius = (index: number) => {
35
+ const applyRadius = (index, isNegative) => {
32
36
  if (index === undefined || index === null || !isRounded) return
33
37
  let style = {}
34
38
 
35
39
  if ((isStacked && index + 1 === stackCount) || !isStacked) {
36
- style = isHorizontal ? { borderRadius: `0 ${radius} ${radius} 0` } : { borderRadius: `${radius} ${radius} 0 0` }
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
46
  }
38
47
  if (tipRounding === 'full' && isStacked && index === 0 && stackCount > 1) {
39
48
  style = isHorizontal ? { borderRadius: `${radius} 0 0 ${radius}` } : { borderRadius: `0 0 ${radius} ${radius}` }
@@ -41,7 +50,6 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
41
50
  if (tipRounding === 'full' && ((isStacked && index === 0 && stackCount === 1) || !isStacked)) {
42
51
  style = { borderRadius: radius }
43
52
  }
44
-
45
53
  return style
46
54
  }
47
55
  // }
@@ -76,7 +84,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
76
84
 
77
85
  // return new updated bars/groupes
78
86
  return barsArr.map((bar, i) => {
79
- // set bars Y dynamycly to handle space between bars
87
+ // set bars Y dynamically to handle space between bars
80
88
  let y = 0
81
89
  bar.index !== 0 && (y = (barHeight + barSpace + labelHeight) * i)
82
90
 
@@ -84,16 +92,6 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
84
92
  })
85
93
  }
86
94
 
87
- function getTextWidth(text, font) {
88
- // function calculates the width of given text and its font-size
89
- const canvas = document.createElement('canvas')
90
- const context = canvas.getContext('2d')
91
-
92
- context.font = font || getComputedStyle(document.body).font
93
-
94
- return Math.ceil(context.measureText(text).width)
95
- }
96
-
97
95
  // Using State
98
96
  const [textWidth, setTextWidth] = useState(null)
99
97
 
@@ -107,13 +105,13 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
107
105
  }
108
106
  })
109
107
  }
110
- }, [config, updateConfig])
108
+ }, [config, updateConfig]) // eslint-disable-line
111
109
 
112
110
  useEffect(() => {
113
111
  if (config.isLollipopChart === false && config.barHeight < 25) {
114
112
  updateConfig({ ...config, barHeight: 25 })
115
113
  }
116
- }, [config.isLollipopChart])
114
+ }, [config.isLollipopChart]) // eslint-disable-line
117
115
 
118
116
  useEffect(() => {
119
117
  if (config.visualizationSubType === 'horizontal') {
@@ -122,7 +120,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
122
120
  orientation: 'horizontal'
123
121
  })
124
122
  }
125
- }, [])
123
+ }, []) // eslint-disable-line
126
124
 
127
125
  useEffect(() => {
128
126
  if (config.barStyle === 'lollipop' && !config.isLollipopChart) {
@@ -131,14 +129,14 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
131
129
  if (isRounded || config.barStyle === 'flat') {
132
130
  updateConfig({ ...config, isLollipopChart: false })
133
131
  }
134
- }, [config.barStyle])
132
+ }, [config.barStyle]) // eslint-disable-line
135
133
 
136
134
  return (
137
135
  <ErrorBoundary component='BarChart'>
138
- <Group left={config.runtime.yAxis.size}>
136
+ <Group left={parseFloat(config.runtime.yAxis.size)}>
139
137
  {/* Stacked Vertical */}
140
138
  {config.visualizationSubType === 'stacked' && !isHorizontal && (
141
- <BarStack data={data} keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys} x={(d: any) => d[config.runtime.xAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale}>
139
+ <BarStack data={cleanedData} keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys} x={d => d[config.runtime.xAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale}>
142
140
  {barStacks =>
143
141
  barStacks.reverse().map(barStack =>
144
142
  barStack.bars.map(bar => {
@@ -147,10 +145,10 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
147
145
  let barThickness = xMax / barStack.bars.length
148
146
  let barThicknessAdjusted = barThickness * (config.barThickness || 0.8)
149
147
  let offset = (barThickness * (1 - (config.barThickness || 0.8))) / 2
150
- const style = applyRadius(barStack.index)
151
148
  // tooltips
152
149
  const xAxisValue = config.runtime.xAxis.type === 'date' ? formatDate(parseDate(data[bar.index][config.runtime.xAxis.dataKey])) : data[bar.index][config.runtime.xAxis.dataKey]
153
- const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0)
150
+ const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0, 'left')
151
+ const style = applyRadius(barStack.index, yAxisValue < 0)
154
152
  let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
155
153
  const xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
156
154
  if (!hasMultipleSeries) {
@@ -162,21 +160,20 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
162
160
  ${yAxisTooltip}<br />
163
161
  ${xAxisTooltip}
164
162
  </div>`
165
-
166
163
  return (
167
- <>
164
+ <Group key={`${barStack.index}--${bar.index}--${orientation}`}>
168
165
  <style>
169
166
  {`
170
167
  #barStack${barStack.index}-${bar.index} rect,
171
168
  #barStack${barStack.index}-${bar.index} foreignObject{
172
- animation-delay: ${barStack.index}.2s;
169
+ animation-delay: ${barStack.index * 0.5}s;
173
170
  transform-origin: ${barThicknessAdjusted / 2}px ${bar.y + bar.height}px
174
171
  }
175
172
  `}
176
173
  </style>
177
174
  <Group key={`bar-stack-${barStack.index}-${bar.index}`} id={`barStack${barStack.index}-${bar.index}`} className='stack vertical'>
178
175
  <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'>
179
- {formatNumber(bar.bar ? bar.bar.data[bar.key] : 0)}
176
+ {yAxisValue}
180
177
  </Text>
181
178
  <foreignObject
182
179
  key={`bar-stack-${barStack.index}-${bar.index}`}
@@ -187,11 +184,11 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
187
184
  style={{ background: bar.color, border: `${config.barHasBorder === 'true' ? barBorderWidth : 0}px solid #333`, ...style }}
188
185
  opacity={transparentBar ? 0.5 : 1}
189
186
  display={displayBar ? 'block' : 'none'}
190
- data-tip={tooltip}
191
- data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
187
+ data-tooltip-html={tooltip}
188
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
192
189
  ></foreignObject>
193
190
  </Group>
194
- </>
191
+ </Group>
195
192
  )
196
193
  })
197
194
  )
@@ -202,18 +199,18 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
202
199
  {/* Stacked Horizontal */}
203
200
  {config.visualizationSubType === 'stacked' && isHorizontal && (
204
201
  <>
205
- <BarStackHorizontal data={data} keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys} height={yMax} y={(d: any) => d[config.runtime.yAxis.dataKey]} xScale={xScale} yScale={yScale} color={colorScale} offset='none'>
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'>
206
203
  {barStacks =>
207
204
  barStacks.map(barStack =>
208
205
  updateBars(barStack.bars).map((bar, index) => {
209
206
  let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
210
207
  let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
211
208
  config.barHeight = Number(config.barHeight)
212
- const style = applyRadius(barStack.index)
213
209
  let labelColor = '#000000'
214
210
  // tooltips
215
- const xAxisValue = formatNumber(data[bar.index][bar.key])
211
+ const xAxisValue = formatNumber(data[bar.index][bar.key], 'left')
216
212
  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)
217
214
  let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
218
215
  let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
219
216
  if (!hasMultipleSeries) {
@@ -230,53 +227,64 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
230
227
  }
231
228
 
232
229
  return (
233
- <Group key={index}>
234
- <foreignObject
235
- key={`barstack-horizontal-${barStack.index}-${bar.index}-${index}`}
236
- className={`animated-chart group ${animatedChart ? 'animated' : ''}`}
237
- x={bar.x}
238
- y={bar.y}
239
- width={bar.width}
240
- height={bar.height}
241
- style={{ background: bar.color, border: `${config.barHasBorder === 'true' ? barBorderWidth : 0}px solid #333`, ...style }}
242
- opacity={transparentBar ? 0.5 : 1}
243
- display={displayBar ? 'block' : 'none'}
244
- data-tip={tooltip}
245
- data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
246
- ></foreignObject>
247
-
248
- {orientation === 'horizontal' && visualizationSubType === 'stacked' && isLabelBelowBar && barStack.index === 0 && !config.yAxis.hideLabel && (
249
- <Text
250
- x={`${bar.x + (config.isLollipopChart ? 15 : 5)}`} // padding
251
- y={bar.y + bar.height * 1.2}
252
- fill={'#000000'}
253
- textAnchor='start'
254
- verticalAnchor='start'
255
- >
256
- {yAxisValue}
257
- </Text>
258
- )}
259
-
260
- {displayNumbersOnBar && textWidth + 50 < bar.width && (
261
- <Text
230
+ <>
231
+ <style>
232
+ {`
233
+ #barStack${barStack.index}-${bar.index} rect,
234
+ #barStack${barStack.index}-${bar.index} foreignObject{
235
+ animation-delay: ${barStack.index * 0.5}s;
236
+ transform-origin: ${bar.x}px
237
+ }
238
+ `}
239
+ </style>
240
+ <Group key={index} id={`barStack${barStack.index}-${bar.index}`} className='stack horizontal'>
241
+ <foreignObject
242
+ key={`barstack-horizontal-${barStack.index}-${bar.index}-${index}`}
243
+ className={`animated-chart group ${animatedChart ? 'animated' : ''}`}
244
+ x={bar.x}
245
+ y={bar.y}
246
+ width={bar.width}
247
+ height={bar.height}
248
+ style={{ background: bar.color, border: `${config.barHasBorder === 'true' ? barBorderWidth : 0}px solid #333`, ...style }}
249
+ opacity={transparentBar ? 0.5 : 1}
262
250
  display={displayBar ? 'block' : 'none'}
263
- x={bar.x + barStack.bars[bar.index].width / 2} // padding
264
- y={bar.y + bar.height / 2}
265
- fill={labelColor}
266
- textAnchor='middle'
267
- verticalAnchor='middle'
268
- innerRef={e => {
269
- if (e) {
270
- // use font sizes and padding to set the bar height
271
- let elem = e.getBBox()
272
- setTextWidth(elem.width)
273
- }
274
- }}
275
- >
276
- {formatNumber(data[bar.index][bar.key])}
277
- </Text>
278
- )}
279
- </Group>
251
+ data-tooltip-html={tooltip}
252
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
253
+ ></foreignObject>
254
+
255
+ {orientation === 'horizontal' && visualizationSubType === 'stacked' && isLabelBelowBar && barStack.index === 0 && !config.yAxis.hideLabel && (
256
+ <Text
257
+ x={`${bar.x + (config.isLollipopChart ? 15 : 5)}`} // padding
258
+ y={bar.y + bar.height * 1.2}
259
+ fill={'#000000'}
260
+ textAnchor='start'
261
+ verticalAnchor='start'
262
+ >
263
+ {yAxisValue}
264
+ </Text>
265
+ )}
266
+
267
+ {displayNumbersOnBar && textWidth < bar.width && (
268
+ <Text
269
+ display={displayBar ? 'block' : 'none'}
270
+ x={bar.x + barStack.bars[bar.index].width / 2} // padding
271
+ y={bar.y + bar.height / 2}
272
+ fill={labelColor}
273
+ textAnchor='middle'
274
+ verticalAnchor='middle'
275
+ innerRef={e => {
276
+ if (e) {
277
+ // use font sizes and padding to set the bar height
278
+ let elem = e.getBBox()
279
+ setTextWidth(elem.width)
280
+ }
281
+ }}
282
+ >
283
+ {xAxisValue}
284
+ </Text>
285
+ )}
286
+ </Group>
287
+ </>
280
288
  )
281
289
  })
282
290
  )
@@ -289,10 +297,10 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
289
297
  {config.visualizationSubType !== 'stacked' && (
290
298
  <Group>
291
299
  <BarGroup
292
- data={data}
300
+ data={cleanedData}
293
301
  keys={config.runtime.barSeriesKeys || config.runtime.seriesKeys}
294
302
  height={yMax}
295
- x0={(d: any) => d[config.runtime.originalXAxis.dataKey]}
303
+ x0={d => d[config.runtime.originalXAxis.dataKey]}
296
304
  x0Scale={config.runtime.horizontal ? yScale : xScale}
297
305
  x1Scale={seriesScale}
298
306
  yScale={config.runtime.horizontal ? xScale : yScale}
@@ -305,14 +313,15 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
305
313
  <Group
306
314
  className={`bar-group-${barGroup.index}-${barGroup.x0}--${index} ${config.orientation}`}
307
315
  key={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`}
316
+ id={`bar-group-${barGroup.index}-${barGroup.x0}--${index}`}
308
317
  top={config.runtime.horizontal ? barGroup.y : 0}
309
318
  left={config.runtime.horizontal ? 0 : (xMax / barGroups.length) * barGroup.index}
310
319
  >
311
320
  {barGroup.bars.map((bar, index) => {
312
321
  let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
313
322
  let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(bar.key) !== -1
314
- let barHeight = orientation === 'horizontal' ? config.barHeight : Math.abs(yScale(bar.value) - yScale(0))
315
- let barY = bar.value >= 0 ? bar.y : yScale(0)
323
+ let barHeight = orientation === 'horizontal' ? config.barHeight : isNumber(Math.abs(yScale(bar.value) - yScale(0))) ? Math.abs(yScale(bar.value) - yScale(0)) : 0
324
+ let barY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
316
325
  let barGroupWidth = ((config.runtime.horizontal ? yMax : xMax) / barGroups.length) * (config.barThickness || 0.8)
317
326
  let offset = (((config.runtime.horizontal ? yMax : xMax) / barGroups.length) * (1 - (config.barThickness || 0.8))) / 2
318
327
 
@@ -333,7 +342,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
333
342
  }
334
343
  if (config.legend.colorCode && config.series.length === 1) barColor = palette[barGroup.index]
335
344
 
336
- let yAxisValue = formatNumber(bar.value)
345
+ let yAxisValue = formatNumber(bar.value, 'left')
337
346
  let xAxisValue = config.runtime[section].type === 'date' ? formatDate(parseDate(data[barGroup.index][config.runtime.originalXAxis.dataKey])) : data[barGroup.index][config.runtime.originalXAxis.dataKey]
338
347
 
339
348
  if (config.runtime.horizontal) {
@@ -342,18 +351,18 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
342
351
  xAxisValue = tempValue
343
352
  barWidth = config.barHeight
344
353
  }
354
+ // check if bar text/value string fits into each bars.
355
+ let textWidth = getTextWidth(xAxisValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
356
+ let textFits = textWidth < bar.y - 5 // minus padding 5
357
+
345
358
  let labelColor = '#000000'
346
359
 
347
360
  // Set label color
348
361
  if (chroma.contrast(labelColor, barColor) < 4.9) {
349
- labelColor = '#FFFFFF'
362
+ if (textFits) labelColor = '#FFFFFF'
350
363
  }
351
364
 
352
- const style = applyRadius(index)
353
-
354
- // check if bar text/value string fits into each bars.
355
- let textWidth = getTextWidth(xAxisValue, config.fontSize)
356
- let doesTextFit = (textWidth / bar.y) * 100 < 48
365
+ const style = applyRadius(index, yAxisValue < 0)
357
366
 
358
367
  let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
359
368
  let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
@@ -371,7 +380,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
371
380
  </div>`
372
381
 
373
382
  return (
374
- <>
383
+ <Group key={`${barGroup.index}--${index}--${orientation}`}>
375
384
  {/* This feels gross but inline transition was not working well*/}
376
385
  <style>
377
386
  {`
@@ -396,18 +405,18 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
396
405
  }}
397
406
  opacity={transparentBar ? 0.5 : 1}
398
407
  display={displayBar ? 'block' : 'none'}
399
- data-tip={tooltip}
400
- data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
408
+ data-tooltip-html={tooltip}
409
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
401
410
  ></foreignObject>
402
411
  {orientation === 'horizontal' && !config.isLollipopChart && displayNumbersOnBar && (
403
- <Text
412
+ <Text // prettier-ignore
404
413
  display={displayBar ? 'block' : 'none'}
405
414
  x={bar.y}
406
415
  y={config.barHeight / 2 + config.barHeight * bar.index}
407
416
  fill={labelColor}
408
- dx={doesTextFit ? -5 : 5} // X padding
417
+ dx={textFits ? -5 : 5}
409
418
  verticalAnchor='middle'
410
- textAnchor={doesTextFit ? 'end' : 'start'}
419
+ textAnchor={textFits ? 'end' : 'start'}
411
420
  >
412
421
  {xAxisValue}
413
422
  </Text>
@@ -438,7 +447,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
438
447
  ;
439
448
  {orientation === 'vertical' && (
440
449
  <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'>
441
- {bar.value}
450
+ {yAxisValue}
442
451
  </Text>
443
452
  )}
444
453
  ;
@@ -449,8 +458,8 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
449
458
  r={lollipopShapeSize / 2}
450
459
  fill={barColor}
451
460
  key={`circle--${bar.index}`}
452
- data-tip={tooltip}
453
- data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
461
+ data-tooltip-html={tooltip}
462
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
454
463
  style={{ filter: 'unset', opacity: 1 }}
455
464
  />
456
465
  )}
@@ -462,15 +471,15 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
462
471
  height={lollipopShapeSize}
463
472
  fill={barColor}
464
473
  key={`circle--${bar.index}`}
465
- data-tip={tooltip}
466
- data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
474
+ data-tooltip-html={tooltip}
475
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
467
476
  style={{ opacity: 1, filter: 'unset' }}
468
477
  >
469
478
  <animate attributeName='height' values={`0, ${lollipopShapeSize}`} dur='2.5s' />
470
479
  </rect>
471
480
  )}
472
481
  </Group>
473
- </>
482
+ </Group>
474
483
  )
475
484
  })}
476
485
  </Group>
@@ -480,25 +489,48 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
480
489
 
481
490
  {Object.keys(config.confidenceKeys).length > 0
482
491
  ? data.map(d => {
483
- let xPos = xScale(getXAxisData(d))
484
- let upperPos = yScale(getYAxisData(d, config.confidenceKeys.lower))
485
- let lowerPos = yScale(getYAxisData(d, config.confidenceKeys.upper))
492
+ let xPos, yPos
493
+ let upperPos
494
+ let lowerPos
486
495
  let tickWidth = 5
487
-
488
- return (
489
- <path
490
- key={`confidence-interval-${d[config.runtime.originalXAxis.dataKey]}`}
491
- stroke='#333'
492
- strokeWidth='2px'
493
- d={`
494
- M${xPos - tickWidth} ${upperPos}
495
- L${xPos + tickWidth} ${upperPos}
496
- M${xPos} ${upperPos}
497
- L${xPos} ${lowerPos}
498
- M${xPos - tickWidth} ${lowerPos}
499
- L${xPos + tickWidth} ${lowerPos}`}
500
- />
501
- )
496
+ // DEV-3264 Make Confidence Intervals work on horizontal bar charts
497
+ if (orientation === 'horizontal') {
498
+ yPos = yScale(getXAxisData(d)) - 0.75 * config.barHeight
499
+ upperPos = xScale(getYAxisData(d, config.confidenceKeys.upper))
500
+ lowerPos = xScale(getYAxisData(d, config.confidenceKeys.lower))
501
+ return (
502
+ <path
503
+ key={`confidence-interval-h-${yPos}-${d[config.runtime.originalXAxis.dataKey]}`}
504
+ stroke='#333'
505
+ strokeWidth='px'
506
+ d={`
507
+ M${lowerPos} ${yPos - tickWidth}
508
+ L${lowerPos} ${yPos + tickWidth}
509
+ M${lowerPos} ${yPos}
510
+ L${upperPos} ${yPos}
511
+ M${upperPos} ${yPos - tickWidth}
512
+ L${upperPos} ${yPos + tickWidth} `}
513
+ />
514
+ )
515
+ } else {
516
+ xPos = xScale(getXAxisData(d))
517
+ upperPos = yScale(getYAxisData(d, config.confidenceKeys.lower))
518
+ lowerPos = yScale(getYAxisData(d, config.confidenceKeys.upper))
519
+ return (
520
+ <path
521
+ key={`confidence-interval-v-${yPos}-${d[config.runtime.originalXAxis.dataKey]}`}
522
+ stroke='#333'
523
+ strokeWidth='px'
524
+ d={`
525
+ M${xPos - tickWidth} ${upperPos}
526
+ L${xPos + tickWidth} ${upperPos}
527
+ M${xPos} ${upperPos}
528
+ L${xPos} ${lowerPos}
529
+ M${xPos - tickWidth} ${lowerPos}
530
+ L${xPos + tickWidth} ${lowerPos}`}
531
+ />
532
+ )
533
+ }
502
534
  })
503
535
  : ''}
504
536
  </Group>
@@ -0,0 +1,101 @@
1
+ import React, { useContext, useEffect } from 'react'
2
+ import { BoxPlot } from '@visx/stats'
3
+ import { Group } from '@visx/group'
4
+ import ConfigContext from '../ConfigContext'
5
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
6
+ import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
7
+
8
+ const CoveBoxPlot = ({ xScale, yScale }) => {
9
+ const { config, setConfig } = useContext(ConfigContext)
10
+ const boxWidth = xScale.bandwidth()
11
+ const constrainedWidth = Math.min(40, boxWidth)
12
+ const color_0 = colorPalettesChart[config?.palette][0] ? colorPalettesChart[config?.palette][0] : '#000'
13
+
14
+ // tooltips
15
+ const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
16
+ const handleTooltip = d => {
17
+ return `
18
+ <strong>${d.columnCategory}</strong></br>
19
+ ${config.boxplot.labels.q1}: ${d.columnFirstQuartile}<br/>
20
+ ${config.boxplot.labels.q3}: ${d.columnThirdQuartile}<br/>
21
+ ${config.boxplot.labels.iqr}: ${d.columnIqr}<br/>
22
+ ${config.boxplot.labels.median}: ${d.columnMedian}
23
+ `
24
+ }
25
+
26
+ useEffect(() => {
27
+ if (config.legend.hide === false) {
28
+ setConfig({
29
+ ...config,
30
+ legend: {
31
+ ...config.legend,
32
+ hide: true
33
+ }
34
+ })
35
+ }
36
+ }, []) // eslint-disable-line
37
+
38
+ return (
39
+ <ErrorBoundary component='BoxPlot'>
40
+ <Group className='boxplot' key={`boxplot-group`}>
41
+ {config.boxplot.plots.map((d, i) => {
42
+ const offset = boxWidth - constrainedWidth
43
+ const radius = 4
44
+ return (
45
+ <Group key={`boxplotplot-${i}`}>
46
+ {config.boxplot.plotNonOutlierValues &&
47
+ d.nonOutlierValues.map((value, index) => {
48
+ return <circle cx={xScale(d.columnCategory) + Number(config.yAxis.size) + boxWidth / 2} cy={yScale(value)} r={radius} fill={'#ccc'} style={{ opacity: 1, fillOpacity: 1, stroke: 'black' }} key={`boxplot-${i}--circle-${index}`} />
49
+ })}
50
+ <BoxPlot
51
+ data-left={xScale(d.columnCategory) + config.yAxis.size + offset / 2 + 0.5}
52
+ key={`box-plot-${i}`}
53
+ min={Number(d.columnMin)}
54
+ max={Number(d.columnMax)}
55
+ left={Number(xScale(d.columnCategory)) + Number(config.yAxis.size) + offset / 2 + 0.5}
56
+ firstQuartile={Number(d.columnFirstQuartile)}
57
+ thirdQuartile={Number(d.columnThirdQuartile)}
58
+ median={Number(d.columnMedian)}
59
+ boxWidth={constrainedWidth}
60
+ fill={color_0}
61
+ fillOpacity={0.5}
62
+ stroke='black'
63
+ valueScale={yScale}
64
+ outliers={config.boxplot.plotOutlierValues ? d.columnOutliers : []}
65
+ outlierProps={{
66
+ style: {
67
+ fill: `${color_0}`,
68
+ opacity: 1
69
+ }
70
+ }}
71
+ medianProps={{
72
+ style: {
73
+ stroke: 'black'
74
+ }
75
+ }}
76
+ boxProps={{
77
+ style: {
78
+ stroke: 'black',
79
+ strokeWidth: config.boxplot.borders === 'true' ? 1 : 0
80
+ }
81
+ }}
82
+ maxProps={{
83
+ style: {
84
+ stroke: 'black'
85
+ }
86
+ }}
87
+ container
88
+ containerProps={{
89
+ 'data-tooltip-html': handleTooltip(d),
90
+ 'data-tooltip-id': tooltip_id
91
+ }}
92
+ />
93
+ </Group>
94
+ )
95
+ })}
96
+ </Group>
97
+ </ErrorBoundary>
98
+ )
99
+ }
100
+
101
+ export default CoveBoxPlot