@cdc/chart 4.23.1 → 4.23.2

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 (52) hide show
  1. package/dist/cdcchart.js +54532 -696
  2. package/examples/Barchart_with_negative.json +34 -0
  3. package/examples/box-plot.json +2 -2
  4. package/examples/dynamic-legends.json +1 -1
  5. package/examples/example-bar-chart-nonnumeric.json +36 -0
  6. package/examples/example-bar-chart.json +33 -0
  7. package/examples/example-combo-bar-nonnumeric.json +105 -0
  8. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +1 -1
  9. package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +2 -2
  10. package/examples/gallery/paired-bar/paired-bar-chart.json +65 -13
  11. package/examples/line-chart-nonnumeric.json +32 -0
  12. package/examples/line-chart.json +21 -63
  13. package/examples/newdata.json +1 -1
  14. package/examples/planet-combo-example-config.json +143 -20
  15. package/examples/planet-example-data-nonnumeric.json +56 -0
  16. package/examples/planet-example-data.json +2 -2
  17. package/examples/planet-pie-example-config-nonnumeric.json +30 -0
  18. package/examples/scatterplot-continuous.csv +17 -0
  19. package/examples/scatterplot.json +136 -0
  20. package/examples/sparkline-chart-nonnumeric.json +76 -0
  21. package/examples/stacked-vertical-bar-example-negative.json +154 -0
  22. package/examples/stacked-vertical-bar-example-nonnumerics.json +154 -0
  23. package/index.html +74 -0
  24. package/package.json +29 -23
  25. package/src/{CdcChart.tsx → CdcChart.jsx} +74 -56
  26. package/src/components/{BarChart.tsx → BarChart.jsx} +99 -91
  27. package/src/components/BoxPlot.jsx +88 -0
  28. package/src/components/{DataTable.tsx → DataTable.jsx} +102 -25
  29. package/src/components/{EditorPanel.js → EditorPanel.jsx} +228 -14
  30. package/src/components/{Filters.js → Filters.jsx} +6 -12
  31. package/src/components/{Legend.js → Legend.jsx} +120 -108
  32. package/src/components/{LineChart.tsx → LineChart.jsx} +26 -12
  33. package/src/components/{LinearChart.tsx → LinearChart.jsx} +67 -47
  34. package/src/components/{PairedBarChart.tsx → PairedBarChart.jsx} +45 -78
  35. package/src/components/{PieChart.tsx → PieChart.jsx} +17 -32
  36. package/src/components/ScatterPlot.jsx +48 -0
  37. package/src/components/{SparkLine.js → SparkLine.jsx} +49 -18
  38. package/src/components/{useIntersectionObserver.tsx → useIntersectionObserver.jsx} +1 -1
  39. package/src/data/initial-state.js +33 -3
  40. package/src/hooks/{useColorPalette.ts → useColorPalette.js} +10 -28
  41. package/src/hooks/{useReduceData.ts → useReduceData.js} +25 -14
  42. package/src/hooks/useRightAxis.js +3 -1
  43. package/src/index.jsx +16 -0
  44. package/src/scss/DataTable.scss +22 -0
  45. package/src/scss/main.scss +30 -10
  46. package/vite.config.js +4 -0
  47. package/dist/495.js +0 -3
  48. package/dist/703.js +0 -1
  49. package/src/components/BoxPlot.js +0 -92
  50. package/src/index.html +0 -67
  51. package/src/index.tsx +0 -18
  52. /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
  // }
@@ -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
 
@@ -135,10 +133,10 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
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
150
  const yAxisValue = formatNumber(bar.bar ? bar.bar.data[bar.key] : 0)
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,14 +160,13 @@ 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
  <>
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
  `}
@@ -187,8 +184,8 @@ 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
  </>
@@ -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
211
  const xAxisValue = formatNumber(data[bar.index][bar.key])
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 + 50 < 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
+ {formatNumber(data[bar.index][bar.key])}
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}
@@ -311,8 +319,8 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
311
319
  {barGroup.bars.map((bar, index) => {
312
320
  let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(bar.key) === -1
313
321
  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)
322
+ let barHeight = orientation === 'horizontal' ? config.barHeight : isNumber(Math.abs(yScale(bar.value) - yScale(0))) ? Math.abs(yScale(bar.value) - yScale(0)) : 0
323
+ let barY = bar.value >= 0 && isNumber(bar.value) ? bar.y : yScale(0)
316
324
  let barGroupWidth = ((config.runtime.horizontal ? yMax : xMax) / barGroups.length) * (config.barThickness || 0.8)
317
325
  let offset = (((config.runtime.horizontal ? yMax : xMax) / barGroups.length) * (1 - (config.barThickness || 0.8))) / 2
318
326
 
@@ -342,18 +350,18 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
342
350
  xAxisValue = tempValue
343
351
  barWidth = config.barHeight
344
352
  }
353
+ // check if bar text/value string fits into each bars.
354
+ let textWidth = getTextWidth(xAxisValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
355
+ let textFits = textWidth < bar.y - 5 // minus padding 5
356
+
345
357
  let labelColor = '#000000'
346
358
 
347
359
  // Set label color
348
360
  if (chroma.contrast(labelColor, barColor) < 4.9) {
349
- labelColor = '#FFFFFF'
361
+ if (textFits) labelColor = '#FFFFFF'
350
362
  }
351
363
 
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
364
+ const style = applyRadius(index, yAxisValue < 0)
357
365
 
358
366
  let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
359
367
  let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
@@ -396,18 +404,18 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
396
404
  }}
397
405
  opacity={transparentBar ? 0.5 : 1}
398
406
  display={displayBar ? 'block' : 'none'}
399
- data-tip={tooltip}
400
- data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
407
+ data-tooltip-html={tooltip}
408
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
401
409
  ></foreignObject>
402
410
  {orientation === 'horizontal' && !config.isLollipopChart && displayNumbersOnBar && (
403
- <Text
411
+ <Text // prettier-ignore
404
412
  display={displayBar ? 'block' : 'none'}
405
413
  x={bar.y}
406
414
  y={config.barHeight / 2 + config.barHeight * bar.index}
407
415
  fill={labelColor}
408
- dx={doesTextFit ? -5 : 5} // X padding
416
+ dx={textFits ? -5 : 5}
409
417
  verticalAnchor='middle'
410
- textAnchor={doesTextFit ? 'end' : 'start'}
418
+ textAnchor={textFits ? 'end' : 'start'}
411
419
  >
412
420
  {xAxisValue}
413
421
  </Text>
@@ -438,7 +446,7 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
438
446
  ;
439
447
  {orientation === 'vertical' && (
440
448
  <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}
449
+ {formatNumber(bar.value)}
442
450
  </Text>
443
451
  )}
444
452
  ;
@@ -449,8 +457,8 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
449
457
  r={lollipopShapeSize / 2}
450
458
  fill={barColor}
451
459
  key={`circle--${bar.index}`}
452
- data-tip={tooltip}
453
- data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
460
+ data-tooltip-html={tooltip}
461
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
454
462
  style={{ filter: 'unset', opacity: 1 }}
455
463
  />
456
464
  )}
@@ -462,8 +470,8 @@ export default function BarChart({ xScale, yScale, seriesScale, xMax, yMax, getX
462
470
  height={lollipopShapeSize}
463
471
  fill={barColor}
464
472
  key={`circle--${bar.index}`}
465
- data-tip={tooltip}
466
- data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
473
+ data-tooltip-html={tooltip}
474
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
467
475
  style={{ opacity: 1, filter: 'unset' }}
468
476
  >
469
477
  <animate attributeName='height' values={`0, ${lollipopShapeSize}`} dur='2.5s' />
@@ -0,0 +1,88 @@
1
+ import React, { useContext } from 'react'
2
+ import { BoxPlot } from '@visx/stats'
3
+ import { Group } from '@visx/group'
4
+ import { scaleBand, scaleLinear } from '@visx/scale'
5
+ import ConfigContext from '../ConfigContext'
6
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
7
+ import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
8
+
9
+ const CoveBoxPlot = ({ xScale, yScale }) => {
10
+ const { transformedData: data, config } = useContext(ConfigContext)
11
+
12
+ const boxWidth = xScale.bandwidth()
13
+ const constrainedWidth = Math.min(40, boxWidth)
14
+ const color_0 = colorPalettesChart[config?.palette][0] ? colorPalettesChart[config?.palette][0] : '#000'
15
+
16
+ // tooltips
17
+ const tooltip_id = `cdc-open-viz-tooltip-${config.runtime.uniqueId}`
18
+ const handleTooltip = d => {
19
+ return `
20
+ <strong>${d.columnCategory}</strong></br>
21
+ ${config.boxplot.labels.q1}: ${d.columnFirstQuartile}<br/>
22
+ ${config.boxplot.labels.q3}: ${d.columnThirdQuartile}<br/>
23
+ ${config.boxplot.labels.iqr}: ${d.columnIqr}<br/>
24
+ ${config.boxplot.labels.median}: ${d.columnMedian}
25
+ `
26
+ }
27
+ return (
28
+ <ErrorBoundary component='BoxPlot'>
29
+ <Group className='boxplot' key={`boxplot-group`}>
30
+ {config.boxplot.plots.map((d, i) => {
31
+ const offset = boxWidth - constrainedWidth
32
+ const radius = 4
33
+ return (
34
+ <>
35
+ {config.boxplot.plotNonOutlierValues && d.nonOutlierValues.map(value => <circle cx={xScale(d.columnCategory) + config.yAxis.size + boxWidth / 2} cy={yScale(value)} r={radius} fill={'#ccc'} style={{ opacity: 1, fillOpacity: 1, stroke: 'black' }} />)}
36
+ <BoxPlot
37
+ data-left={xScale(d.columnCategory) + config.yAxis.size + offset / 2 + 0.5}
38
+ key={`box-plot-${i}`}
39
+ min={d.columnMin}
40
+ max={d.columnMax}
41
+ left={xScale(d.columnCategory) + config.yAxis.size + offset / 2 + 0.5}
42
+ firstQuartile={d.columnFirstQuartile}
43
+ thirdQuartile={d.columnThirdQuartile}
44
+ median={d.columnMedian}
45
+ boxWidth={constrainedWidth}
46
+ fill={color_0}
47
+ fillOpacity={0.5}
48
+ stroke='black'
49
+ valueScale={yScale}
50
+ outliers={config.boxplot.plotOutlierValues ? d.columnOutliers : []}
51
+ outlierProps={{
52
+ style: {
53
+ fill: `${color_0}`,
54
+ opacity: 1
55
+ }
56
+ }}
57
+ medianProps={{
58
+ style: {
59
+ stroke: 'black'
60
+ }
61
+ }}
62
+ boxProps={{
63
+ style: {
64
+ stroke: 'black',
65
+ strokeWidth: config.boxplot.borders === 'true' ? 1 : 0
66
+ },
67
+ 'data-tooltip-html': 'cool'
68
+ }}
69
+ maxProps={{
70
+ style: {
71
+ stroke: 'black'
72
+ }
73
+ }}
74
+ container
75
+ containerProps={{
76
+ 'data-tooltip-html': handleTooltip(d),
77
+ 'data-tooltip-id': tooltip_id
78
+ }}
79
+ />
80
+ </>
81
+ )
82
+ })}
83
+ </Group>
84
+ </ErrorBoundary>
85
+ )
86
+ }
87
+
88
+ export default CoveBoxPlot
@@ -1,26 +1,27 @@
1
- import React, { useContext, useEffect, useState, useMemo, memo, Fragment } from 'react'
1
+ import React, { useContext, useEffect, useState, useMemo } from 'react'
2
2
  import { useTable, useSortBy, useResizeColumns, useBlockLayout } from 'react-table'
3
3
  import Papa from 'papaparse'
4
4
  import { Base64 } from 'js-base64'
5
5
 
6
6
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
7
7
  import LegendCircle from '@cdc/core/components/LegendCircle'
8
+ import Icon from '@cdc/core/components/ui/Icon'
8
9
 
9
- import Context from '../context'
10
+ import ConfigContext from '../ConfigContext'
10
11
 
11
- import CoveMediaControls from '@cdc/core/helpers/CoveMediaControls'
12
+ import CoveMediaControls from '@cdc/core/components/CoveMediaControls'
12
13
 
13
14
  export default function DataTable() {
14
- const { rawData, transformedData: data, config, colorScale, parseDate, formatDate, formatNumber: numberFormatter, colorPalettes, imageId } = useContext<any>(Context)
15
+ const { rawData, transformedData: data, config, colorScale, parseDate, formatDate, formatNumber: numberFormatter, colorPalettes, imageId } = useContext(ConfigContext)
15
16
 
16
17
  // Debugging.
17
18
  // if (config.visualizationType === 'Box Plot') return null
18
19
 
19
20
  const section = config.orientation === 'horizontal' ? 'yAxis' : 'xAxis'
20
- const [tableExpanded, setTableExpanded] = useState<boolean>(config.table.expanded)
21
+ const [tableExpanded, setTableExpanded] = useState(config.table.expanded)
21
22
  const [accessibilityLabel, setAccessibilityLabel] = useState('')
22
23
 
23
- const DownloadButton = ({ data }: any, type) => {
24
+ const DownloadButton = ({ data }, type) => {
24
25
  const fileName = `${config.title.substring(0, 50)}.csv`
25
26
 
26
27
  const csvData = Papa.unparse(data)
@@ -55,52 +56,127 @@ export default function DataTable() {
55
56
  const newTableColumns =
56
57
  config.visualizationType === 'Pie'
57
58
  ? []
58
- : [
59
+ : config.visualizationType === 'Box Plot'
60
+ ? [
61
+ {
62
+ Header: 'Measures',
63
+ Cell: props => {
64
+ const resolveName = () => {
65
+ let {
66
+ boxplot: { labels }
67
+ } = config
68
+ const columnLookup = {
69
+ columnMean: labels.mean,
70
+ columnMax: labels.maximum,
71
+ columnMin: labels.minimum,
72
+ columnIqr: labels.iqr,
73
+ columnCategory: 'Category',
74
+ columnMedian: labels.median,
75
+ columnFirstQuartile: labels.q1,
76
+ columnThirdQuartile: labels.q3,
77
+ columnOutliers: labels.outliers,
78
+ values: labels.values,
79
+ columnCount: labels.count,
80
+ columnSd: 'Standard Deviation',
81
+ nonOutlierValues: 'Non Outliers'
82
+ }
83
+
84
+ let resolvedName = columnLookup[props.row.original[0]]
85
+
86
+ return resolvedName
87
+ }
88
+
89
+ return resolveName()
90
+ }
91
+ }
92
+ ]
93
+ : [
59
94
  {
60
95
  Header: '',
61
96
  Cell: ({ row }) => {
62
97
  const seriesLabel = config.runtime.seriesLabels ? config.runtime.seriesLabels[row.original] : row.original
63
98
  return (
64
- <Fragment>
99
+ <>
65
100
  {config.visualizationType !== 'Pie' && (
66
101
  <LegendCircle
67
102
  fill={
68
- // non dynamic leged
103
+ // non-dynamic leged
69
104
  !config.legend.dynamicLegend
70
105
  ? colorScale(seriesLabel)
71
106
  : // dynamic legend
72
107
  config.legend.dynamicLegend
73
- ? colorPalettes[config.palette][row.index]
74
- : // fallback
108
+ ? colorPalettes[config.palette][row.index]
109
+ : // fallback
75
110
  '#000'
76
111
  }
77
112
  />
78
113
  )}
79
114
  <span>{seriesLabel}</span>
80
- </Fragment>
115
+ </>
81
116
  )
82
117
  },
83
118
  id: 'series-label'
84
119
  }
85
120
  ]
121
+ if (config.visualizationType !== 'Box Plot') {
122
+ data.forEach((d, index) => {
123
+ const resolveTableHeader = () => {
124
+ if (config.runtime[section].type === 'date') return formatDate(parseDate(d[config.runtime.originalXAxis.dataKey]))
125
+ return d[config.runtime.originalXAxis.dataKey]
126
+ }
127
+ const newCol = {
128
+ Header: resolveTableHeader(),
129
+ Cell: ({ row }) => {
130
+ return <>{numberFormatter(d[row.original])}</>
131
+ },
132
+ id: `${d[config.runtime.originalXAxis.dataKey]}--${index}`,
133
+ canSort: true
134
+ }
86
135
 
87
- data.forEach((d, index) => {
88
- const newCol = {
89
- Header: config.runtime[section].type === 'date' ? formatDate(parseDate(d[config.runtime.originalXAxis.dataKey])) : d[config.runtime.originalXAxis.dataKey],
90
- Cell: ({ row }) => {
91
- return <>{numberFormatter(d[row.original])}</>
92
- },
93
- id: `${d[config.runtime.originalXAxis.dataKey]}--${index}`,
94
- canSort: true
95
- }
136
+ newTableColumns.push(newCol)
137
+ })
138
+ }
96
139
 
97
- newTableColumns.push(newCol)
98
- })
140
+ if (config.visualizationType === 'Box Plot') {
141
+ config.boxplot.tableData.map((plot, index) => {
142
+ const newCol = {
143
+ Header: plot.columnCategory,
144
+ Cell: props => {
145
+ let resolveCell = () => {
146
+ if (Number(props.row.id) === 0) return true
147
+ if (Number(props.row.id) === 1) return plot.columnMax
148
+ if (Number(props.row.id) === 2) return plot.columnThirdQuartile
149
+ if (Number(props.row.id) === 3) return plot.columnMedian
150
+ if (Number(props.row.id) === 4) return plot.columnFirstQuartile
151
+ if (Number(props.row.id) === 5) return plot.columnMin
152
+ if (Number(props.row.id) === 6) return plot.columnCount
153
+ if (Number(props.row.id) === 7) return plot.columnSd
154
+ if (Number(props.row.id) === 8) return plot.columnMean
155
+ if (Number(props.row.id) === 9) return plot.columnOutliers.length > 0 ? plot.columnOutliers.toString() : '-'
156
+ if (Number(props.row.id) === 10) return plot.values.length > 0 ? plot.values.toString() : '-'
157
+ return <p>-</p>
158
+ }
159
+ return resolveCell()
160
+ },
161
+ id: `${index}`,
162
+ canSort: false
163
+ }
164
+
165
+ newTableColumns.push(newCol)
166
+ })
167
+ }
99
168
 
100
169
  return newTableColumns
101
170
  }, [config, colorScale])
102
171
 
103
- const tableData = useMemo(() => (config.visualizationType === 'Pie' ? [config.yAxis.dataKey] : config.runtime.seriesKeys), [config.runtime.seriesKeys])
172
+ // prettier-ignore
173
+ const tableData = useMemo(() => (
174
+ config.visualizationType === 'Pie'
175
+ ? [config.yAxis.dataKey]
176
+ : config.visualizationType === 'Box Plot'
177
+ ? Object.entries(config.boxplot.tableData[0])
178
+ : config.runtime.seriesKeys),
179
+ [config.runtime.seriesKeys])
104
180
 
105
181
  // Change accessibility label depending on expanded status
106
182
  useEffect(() => {
@@ -148,6 +224,7 @@ export default function DataTable() {
148
224
  }
149
225
  }}
150
226
  >
227
+ <Icon display={tableExpanded ? 'minus' : 'plus'} base/>
151
228
  {config.table.label}
152
229
  </div>
153
230
  <div className='table-container' hidden={!tableExpanded} style={{ maxHeight: config.table.limitHeight && `${config.table.height}px`, overflowY: 'scroll' }}>
@@ -182,7 +259,7 @@ export default function DataTable() {
182
259
  {rows.map((row, index) => {
183
260
  prepareRow(row)
184
261
  return (
185
- <tr {...row.getRowProps()} key={`tbody__tr-${index}`}>
262
+ <tr {...row.getRowProps()} key={`tbody__tr-${index}`} className={`row-${String(config.visualizationType).replace(' ', '-')}--${index}`}>
186
263
  {row.cells.map((cell, index) => {
187
264
  return (
188
265
  <td tabIndex='0' {...cell.getCellProps()} key={`tbody__tr__td-${index}`} role='gridcell'>