@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
@@ -1,5 +1,5 @@
1
- import React, { Fragment, useContext, useEffect, useRef, useState } from 'react'
2
- import ReactTooltip from 'react-tooltip'
1
+ import React, { useContext, useEffect, useRef, useState } from 'react'
2
+ import { Tooltip as ReactTooltip } from 'react-tooltip'
3
3
 
4
4
  import { Group } from '@visx/group'
5
5
  import { Line } from '@visx/shape'
@@ -7,9 +7,10 @@ import { Text } from '@visx/text'
7
7
  import { scaleLinear, scalePoint, scaleBand } from '@visx/scale'
8
8
  import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
9
9
 
10
+ import CoveScatterPlot from './ScatterPlot'
10
11
  import BarChart from './BarChart'
11
12
  import LineChart from './LineChart'
12
- import Context from '../context'
13
+ import ConfigContext from '../ConfigContext'
13
14
  import PairedBarChart from './PairedBarChart'
14
15
  import useIntersectionObserver from './useIntersectionObserver'
15
16
  import CoveBoxPlot from './BoxPlot'
@@ -22,10 +23,10 @@ import useTopAxis from '../hooks/useTopAxis'
22
23
 
23
24
  // TODO: Move scaling functions into hooks to manage complexity
24
25
  export default function LinearChart() {
25
- const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig } = useContext<any>(Context)
26
+ const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig } = useContext(ConfigContext)
26
27
  let [width] = dimensions
27
28
  const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
28
- const [animatedChart, setAnimatedChart] = useState<boolean>(false)
29
+ const [animatedChart, setAnimatedChart] = useState(false)
29
30
 
30
31
  const triggerRef = useRef()
31
32
  const dataRef = useIntersectionObserver(triggerRef, {
@@ -40,7 +41,7 @@ export default function LinearChart() {
40
41
  }
41
42
  })
42
43
 
43
- // If the chart is in view and set to animate and it has not already played
44
+ // If the chart is in view, set to animate if it has not already played
44
45
  useEffect(() => {
45
46
  if (dataRef?.isIntersecting === true && config.animate) {
46
47
  setTimeout(() => {
@@ -54,14 +55,14 @@ export default function LinearChart() {
54
55
  }
55
56
  const { horizontal: heightHorizontal } = config.heights
56
57
  const height = config.aspectRatio ? width * config.aspectRatio : config.heights[config.orientation]
57
- const xMax = width - config.runtime.yAxis.size - config.yAxis.rightAxisSize
58
+ const xMax = width - config.runtime.yAxis.size - (config.visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
58
59
  const yMax = height - (config.orientation === 'horizontal' ? 0 : config.runtime.xAxis.size)
59
60
 
60
61
  const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
61
62
  const { hasTopAxis } = useTopAxis(config)
62
63
 
63
- const getXAxisData = (d: any) => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
64
- const getYAxisData = (d: any, seriesKey: string) => d[seriesKey]
64
+ const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
65
+ const getYAxisData = (d, seriesKey) => d[seriesKey]
65
66
 
66
67
  let xScale
67
68
  let yScale
@@ -122,19 +123,19 @@ export default function LinearChart() {
122
123
  }
123
124
 
124
125
  if (config.runtime.horizontal) {
125
- xScale = scaleLinear<number>({
126
+ xScale = scaleLinear({
126
127
  domain: [min, max],
127
128
  range: [0, xMax]
128
129
  })
129
130
 
130
131
  yScale =
131
132
  config.runtime.xAxis.type === 'date'
132
- ? scaleLinear<number>({
133
+ ? scaleLinear({
133
134
  domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)]
134
135
  })
135
- : scalePoint<string>({ domain: xAxisDataMapped, padding: 0.5 })
136
+ : scalePoint({ domain: xAxisDataMapped, padding: 0.5 })
136
137
 
137
- seriesScale = scalePoint<string>({
138
+ seriesScale = scalePoint({
138
139
  domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
139
140
  range: [0, yMax]
140
141
  })
@@ -143,24 +144,25 @@ export default function LinearChart() {
143
144
  } else {
144
145
  min = min < 0 ? min * 1.11 : min
145
146
 
146
- yScale = scaleLinear<number>({
147
+ yScale = scaleLinear({
147
148
  domain: [min, max],
148
149
  range: [yMax, 0]
149
150
  })
150
151
 
151
- xScale = scalePoint<string>({
152
+ xScale = scalePoint({
152
153
  domain: xAxisDataMapped,
153
154
  range: [0, xMax],
154
155
  padding: 0.5
155
156
  })
156
157
 
157
- seriesScale = scalePoint<string>({
158
+ seriesScale = scalePoint({
158
159
  domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
159
160
  range: [0, xMax]
160
161
  })
161
162
  }
162
163
 
163
164
  if (config.visualizationType === 'Paired Bar') {
165
+ const offset = 1.02 // Offset of the ticks/values from the Axis
164
166
  let groupOneMax = Math.max.apply(
165
167
  Math,
166
168
  data.map(d => d[config.series[0].dataKey])
@@ -171,17 +173,27 @@ export default function LinearChart() {
171
173
  )
172
174
 
173
175
  // group one
174
- var g1xScale = scaleLinear<number>({
175
- domain: [0, Math.max(groupOneMax, groupTwoMax)],
176
+ var g1xScale = scaleLinear({
177
+ domain: [0, Math.max(groupOneMax, groupTwoMax) * offset],
176
178
  range: [xMax / 2, 0]
177
179
  })
178
180
 
179
181
  // group 2
180
- var g2xScale = scaleLinear<number>({
182
+ var g2xScale = scaleLinear({
181
183
  domain: g1xScale.domain(),
182
- range: [xMax / 2, xMax]
184
+ range: [xMax / 2, xMax],
185
+ nice: true
183
186
  })
184
187
  }
188
+
189
+ if (config.visualizationType === 'Scatter Plot') {
190
+ if (config.xAxis.type === 'continuous') {
191
+ xScale = scaleLinear({
192
+ domain: [0, Math.max.apply(null, xScale.domain())],
193
+ range: [0, xMax]
194
+ })
195
+ }
196
+ }
185
197
  }
186
198
 
187
199
  const handleLeftTickFormatting = tick => {
@@ -217,13 +229,13 @@ export default function LinearChart() {
217
229
  let minYValue
218
230
  let maxYValue
219
231
  let allOutliers = []
220
- let allLowerBounds = config.boxplot.map(plot => plot.columnMin)
221
- let allUpperBounds = config.boxplot.map(plot => plot.columnMax)
232
+ let allLowerBounds = config.boxplot.plots.map(plot => plot.columnMin)
233
+ let allUpperBounds = config.boxplot.plots.map(plot => plot.columnMax)
222
234
 
223
235
  minYValue = Math.min(...allLowerBounds)
224
236
  maxYValue = Math.max(...allUpperBounds)
225
237
 
226
- const hasOutliers = config.boxplot.map(b => b.columnOutliers.map(outlier => allOutliers.push(outlier)))
238
+ const hasOutliers = config.boxplot.plots.map(b => b.columnOutliers.map(outlier => allOutliers.push(outlier))) && !config.boxplot.hideOutliers
227
239
 
228
240
  if (hasOutliers) {
229
241
  let outlierMin = Math.min(...allOutliers)
@@ -251,9 +263,9 @@ export default function LinearChart() {
251
263
  })
252
264
  }
253
265
 
254
- useEffect(() => {
255
- ReactTooltip.rebuild()
256
- })
266
+ const handleTick = tick => {
267
+ return config.runtime.xAxis.type === 'date' ? formatDate(tick) : config.orientation === 'horizontal' ? formatNumber(tick) : tick
268
+ }
257
269
 
258
270
  return isNaN(width) ? (
259
271
  <></>
@@ -291,7 +303,7 @@ export default function LinearChart() {
291
303
 
292
304
  {/* Y axis */}
293
305
  {config.visualizationType !== 'Spark Line' && (
294
- <AxisLeft scale={yScale} left={Number(config.runtime.yAxis.size)} label={config.runtime.yAxis.label} stroke='#333' tickFormat={tick => handleLeftTickFormatting(tick)} numTicks={countNumOfTicks('yAxis')}>
306
+ <AxisLeft scale={yScale} left={Number(config.runtime.yAxis.size) - config.yAxis.axisPadding} label={config.runtime.yAxis.label} stroke='#333' tickFormat={tick => handleLeftTickFormatting(tick)} numTicks={countNumOfTicks('yAxis')}>
295
307
  {props => {
296
308
  const axisCenter = config.runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
297
309
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
@@ -324,18 +336,11 @@ export default function LinearChart() {
324
336
  )}
325
337
 
326
338
  {config.orientation === 'horizontal' && config.visualizationType === 'Paired Bar' && !config.yAxis.hideLabel && (
327
- <Text transform={`translate(${-15}, ${tick.from.y}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`} verticalAnchor={config.isLollipopChart ? 'middle' : 'middle'} textAnchor={'end'}>
339
+ <Text transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + Number(config.barHeight) / 2}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`} textAnchor={'end'} verticalAnchor='middle'>
328
340
  {tick.formattedValue}
329
341
  </Text>
330
342
  )}
331
343
 
332
- {config.orientation === 'horizontal' && config.visualizationType === 'Paired Bar' && !config.yAxis.hideLabel && (
333
- // 17 is a magic number from the offset in barchart.
334
- <Text transform={`translate(${-15}, ${tick.from.y}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`} verticalAnchor={config.isLollipopChart ? 'middle' : 'middle'} textAnchor={'end'}>
335
- {formatNumber(tick.formattedValue)}
336
- </Text>
337
- )}
338
-
339
344
  {config.orientation !== 'horizontal' && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
340
345
  <Text
341
346
  x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
@@ -409,7 +414,16 @@ export default function LinearChart() {
409
414
 
410
415
  {/* X axis */}
411
416
  {config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Spark Line' && (
412
- <AxisBottom top={yMax} left={Number(config.runtime.yAxis.size)} label={config.runtime.xAxis.label} tickFormat={handleBottomTickFormatting} scale={xScale} stroke='#333' tickStroke='#333' numTicks={countNumOfTicks('xAxis')}>
417
+ <AxisBottom
418
+ top={config.runtime.horizontal ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : yMax + Number(config.xAxis.axisPadding)}
419
+ left={Number(config.runtime.yAxis.size)}
420
+ label={config.runtime.xAxis.label}
421
+ tickFormat={handleBottomTickFormatting}
422
+ scale={xScale}
423
+ stroke='#333'
424
+ tickStroke='#333'
425
+ numTicks={countNumOfTicks('xAxis')}
426
+ >
413
427
  {props => {
414
428
  const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2
415
429
  return (
@@ -447,16 +461,16 @@ export default function LinearChart() {
447
461
  <>
448
462
  <AxisBottom top={yMax} left={Number(config.runtime.yAxis.size)} label={config.runtime.xAxis.label} tickFormat={config.runtime.xAxis.type === 'date' ? formatDate : formatNumber} scale={g1xScale} stroke='#333' tickStroke='#333' numTicks={config.runtime.xAxis.numTicks || undefined}>
449
463
  {props => {
450
- const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2
451
464
  return (
452
465
  <Group className='bottom-axis'>
453
466
  {props.ticks.map((tick, i) => {
454
- const tickWidth = xMax / props.ticks.length
467
+ const angle = tick.index !== 0 ? config.yAxis.tickRotation : 0
468
+ const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
455
469
  return (
456
470
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
457
471
  {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
458
472
  {!config.runtime.yAxis.hideLabel && (
459
- <Text transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${60})`} verticalAnchor='start' textAnchor={'end'} width={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? undefined : tickWidth}>
473
+ <Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
460
474
  {formatNumber(tick.formattedValue)}
461
475
  </Text>
462
476
  )}
@@ -479,17 +493,17 @@ export default function LinearChart() {
479
493
  numTicks={config.runtime.xAxis.numTicks || undefined}
480
494
  >
481
495
  {props => {
482
- const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2
483
496
  return (
484
497
  <>
485
498
  <Group className='bottom-axis'>
486
499
  {props.ticks.map((tick, i) => {
487
- const tickWidth = xMax / props.ticks.length
500
+ const angle = tick.index !== 0 ? config.yAxis.tickRotation : 0
501
+ const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
488
502
  return (
489
503
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
490
504
  {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
491
505
  {!config.runtime.yAxis.hideLabel && (
492
- <Text transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${60})`} verticalAnchor='start' textAnchor={'end'} width={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? undefined : tickWidth}>
506
+ <Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
493
507
  {tick.formattedValue}
494
508
  </Text>
495
509
  )}
@@ -499,7 +513,7 @@ export default function LinearChart() {
499
513
  {!config.runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
500
514
  </Group>
501
515
  <Group>
502
- <Text transform={`translate(${xMax / 2}, ${yMax + 20}) rotate(-${0})`} verticalAnchor='start' textAnchor={'middle'} stroke='#333'>
516
+ <Text x={xMax / 2} y={config.xAxis.labelOffset} stroke='#333' textAnchor={'middle'} verticalAnchor='start'>
503
517
  {config.runtime.xAxis.label}
504
518
  </Text>
505
519
  </Group>
@@ -509,25 +523,31 @@ export default function LinearChart() {
509
523
  </AxisBottom>
510
524
  </>
511
525
  )}
512
- {config.visualizationType === 'Paired Bar' && <PairedBarChart width={xMax} height={yMax} />}
526
+
527
+ {/* Paired Bar chart */}
528
+ {config.visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
513
529
 
514
530
  {/* Bar chart */}
515
- {config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && (
531
+ {config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Scatter Plot' && (
516
532
  <>
517
533
  <BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} animatedChart={animatedChart} visible={animatedChart} />
518
534
  </>
519
535
  )}
520
536
 
521
537
  {/* Line chart */}
522
- {config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && (
538
+ {config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Scatter Plot' && (
523
539
  <>
524
540
  <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
525
541
  </>
526
542
  )}
527
543
 
544
+ {/* Scatter Plot chart */}
545
+ {config.visualizationType === 'Scatter Plot' && <CoveScatterPlot xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />}
546
+
547
+ {/* Box Plot chart */}
528
548
  {config.visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
529
549
  </svg>
530
- <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} html={true} type='light' arrowColor='rgba(0,0,0,0)' className='tooltip' />
550
+ <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' />
531
551
  <div className='animation-trigger' ref={triggerRef} />
532
552
  </ErrorBoundary>
533
553
  )
@@ -1,26 +1,21 @@
1
1
  import React, { useContext } from 'react'
2
2
  import { Group } from '@visx/group'
3
3
  import { Bar } from '@visx/shape'
4
- import { scaleLinear, scaleBand } from '@visx/scale'
4
+ import { scaleLinear } from '@visx/scale'
5
5
  import { Text } from '@visx/text'
6
6
 
7
- import Context from '../context'
7
+ import ConfigContext from '../ConfigContext'
8
8
  import chroma from 'chroma-js'
9
9
 
10
- interface PairedBarChartProps {
11
- width: number
12
- height: number
13
- }
14
-
15
- const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
16
- const { config, colorScale, transformedData, formatNumber, seriesHighlight } = useContext<any>(Context)
10
+ const PairedBarChart = ({ width, height, originalWidth }) => {
11
+ const { config, colorScale, transformedData: data, formatNumber, seriesHighlight, getTextWidth } = useContext(ConfigContext)
17
12
 
18
13
  if (!config || config?.series?.length < 2) return
19
14
 
20
- const data = transformedData
21
- const adjustedWidth = width
22
- const adjustedHeight = height
23
- const halfWidth = adjustedWidth / 2
15
+ const borderWidth = config.barHasBorder === 'true' ? 1 : 0
16
+ const halfWidth = width / 2
17
+ const fontSize = { small: 16, medium: 18, large: 20 }
18
+ const offset = 1.02 // Offset of the left bar from the Axis
24
19
 
25
20
  const groupOne = {
26
21
  parentKey: config.dataDescription.seriesKey,
@@ -45,15 +40,10 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
45
40
  }
46
41
 
47
42
  const xScale = scaleLinear({
48
- domain: [0, Math.max(groupOne.max, groupTwo.max)],
43
+ domain: [0, Math.max(groupOne.max * offset, groupTwo.max * 1.1)],
49
44
  range: [0, halfWidth]
50
45
  })
51
46
 
52
- const yScale = scaleBand({
53
- range: [0, adjustedHeight],
54
- domain: data.map(d => d[config.dataDescription.xKey])
55
- })
56
-
57
47
  // Set label color
58
48
  let labelColor = '#000000'
59
49
 
@@ -65,11 +55,13 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
65
55
  groupTwo.labelColor = '#FFFFFF'
66
56
  }
67
57
 
58
+ const label = config.yAxis.label ? `${config.yAxis.label}: ` : ''
59
+
68
60
  const dataTipOne = d => {
69
61
  return `<p>
70
62
  ${config.dataDescription.seriesKey}: ${groupOne.dataKey}<br/>
71
63
  ${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
72
- ${config.dataDescription.valueKey}: ${formatNumber(d[groupOne.dataKey])}
64
+ ${label}${formatNumber(d[groupOne.dataKey])}
73
65
  </p>`
74
66
  }
75
67
 
@@ -77,13 +69,10 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
77
69
  return `<p>
78
70
  ${config.dataDescription.seriesKey}: ${groupTwo.dataKey}<br/>
79
71
  ${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
80
- ${config.dataDescription.valueKey}: ${formatNumber(d[groupTwo.dataKey])}
72
+ ${label}${formatNumber(d[groupTwo.dataKey])}
81
73
  </p>`
82
74
  }
83
-
84
- const isLabelBelowBar = config.yAxis.labelPlacement === 'Below Bar'
85
- const isLabelOnYAxis = config.yAxis.labelPlacement === 'On Date/Category Axis'
86
- const isLabelMissing = !config.yAxis.labelPlacement
75
+ console.log(config)
87
76
 
88
77
  return (
89
78
  width > 0 && (
@@ -96,7 +85,7 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
96
85
  }
97
86
  `}
98
87
  </style>
99
- <svg id='cdc-visualization__paired-bar-chart' width={width} height={height} viewBox={`0 0 ${width} ${height}`} role='img' tabIndex={0}>
88
+ <svg id='cdc-visualization__paired-bar-chart' width={originalWidth} height={height} viewBox={`0 0 ${width + Number(config.runtime.yAxis.size)} ${height}`} role='img' tabIndex={0}>
100
89
  <Group top={0} left={Number(config.xAxis.size)}>
101
90
  {data
102
91
  .filter(item => config.series[0].dataKey === groupOne.dataKey)
@@ -105,26 +94,14 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
105
94
  let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(config.series[0].dataKey) !== -1
106
95
  let barWidth = xScale(d[config.series[0].dataKey])
107
96
  let barHeight = Number(config.barHeight) ? Number(config.barHeight) : 25
108
- let barPadding = barHeight
109
- config.barHeight = Number(config.barHeight) ? Number(config.barHeight) : 25
110
- config.barPadding = config.barHeight
111
-
112
- if (config.orientation === 'horizontal') {
113
- if (isLabelBelowBar || isLabelMissing || isLabelOnYAxis) {
114
- if (barHeight < 40) {
115
- config.barPadding = 40
116
- } else {
117
- config.barPadding = barPadding
118
- }
119
- } else {
120
- config.barPadding = barPadding / 2
121
- }
122
- }
123
-
124
- config.height = Number(barHeight) * data.length + config.barPadding * data.length
125
-
126
- let y = yScale([d[config.dataDescription.xKey]]) + config.barHeight / 1.5
127
- y = Number(config.barPadding) > 20 ? (y += Number(config.barPadding / 3.5) - config.barHeight / 2) : (y += 0)
97
+ // update bar Y to give dynamic Y when user applyes BarSpace
98
+ let y = 0
99
+ y = index !== 0 ? (Number(config.barSpace) + barHeight + borderWidth) * index : y
100
+ const totalheight = (Number(config.barSpace) + barHeight + borderWidth) * data.length
101
+ config.heights.horizontal = totalheight
102
+ // check if text fits inside of the bar including suffix/prefix,comma,fontSize ..etc
103
+ const textWidth = getTextWidth(formatNumber(d[groupOne.dataKey]), `normal ${fontSize[config.fontSize]}px sans-serif`)
104
+ const textFits = textWidth < barWidth - 5 // minus padding dx(5)
128
105
 
129
106
  return (
130
107
  <>
@@ -138,15 +115,15 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
138
115
  width={xScale(d[config.series[0].dataKey])}
139
116
  height={barHeight}
140
117
  fill={groupOne.color}
141
- data-tip={dataTipOne(d)}
142
- data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
118
+ data-tooltip-html={dataTipOne(d)}
119
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
143
120
  stroke='#333'
144
- strokeWidth={config.barBorderThickness || 1}
121
+ strokeWidth={borderWidth}
145
122
  opacity={transparentBar ? 0.5 : 1}
146
123
  display={displayBar ? 'block' : 'none'}
147
124
  />
148
125
  {config.yAxis.displayNumbersOnBar && displayBar && (
149
- <Text textAnchor={barWidth < 100 ? 'end' : 'start'} verticalAnchor='middle' x={halfWidth - (barWidth < 100 ? barWidth + 10 : barWidth - 5)} y={y + config.barHeight / 2} fill={barWidth > 100 ? groupOne.labelColor : '#000'}>
126
+ <Text textAnchor={textFits ? 'start' : 'end'} dx={textFits ? 5 : -5} verticalAnchor='middle' x={halfWidth - barWidth} y={y + config.barHeight / 2} fill={textFits ? groupOne.labelColor : '#000'}>
150
127
  {formatNumber(d[groupOne.dataKey])}
151
128
  </Text>
152
129
  )}
@@ -156,38 +133,28 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
156
133
  })}
157
134
  {data
158
135
  .filter(item => config.series[1].dataKey === groupTwo.dataKey)
159
- .map(d => {
136
+ .map((d, index) => {
160
137
  let barWidth = xScale(d[config.series[1].dataKey])
161
138
  let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(config.series[1].dataKey) === -1
162
139
  let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(config.series[1].dataKey) !== -1
163
-
164
- let barHeight = config.barHeight ? config.barHeight : 25
165
- let barPadding = barHeight
166
- config.barHeight = Number(config.barHeight)
167
-
168
- let y = yScale([d[config.dataDescription.xKey]]) + config.barHeight / 1.5
169
- y = Number(config.barPadding) > 20 ? (y += Number(config.barPadding / 3.5) - config.barHeight / 2) : (y += 0)
170
-
171
- if (config.orientation === 'horizontal') {
172
- if (isLabelBelowBar || isLabelMissing || isLabelOnYAxis) {
173
- if (barHeight < 40) {
174
- config.barPadding = 40
175
- } else {
176
- config.barPadding = barPadding
177
- }
178
- } else {
179
- config.barPadding = barPadding / 2
180
- }
181
- }
140
+ let barHeight = config.barHeight ? Number(config.barHeight) : 25
141
+ // update bar Y to give dynamic Y when user applyes BarSpace
142
+ let y = 0
143
+ y = index !== 0 ? (Number(config.barSpace) + barHeight + borderWidth) * index : y
144
+ const totalheight = (Number(config.barSpace) + barHeight + borderWidth) * data.length
145
+ config.heights.horizontal = totalheight
146
+ // check if text fits inside of the bar including suffix/prefix,comma,fontSize ..etc
147
+ const textWidth = getTextWidth(formatNumber(d[groupTwo.dataKey]), `normal ${fontSize[config.fontSize]}px sans-serif`)
148
+ const isTextFits = textWidth < barWidth - 5 // minus padding dx(5)
182
149
 
183
150
  return (
184
151
  <>
185
152
  <style>
186
153
  {`
187
- .bar-${groupTwo.dataKey}-${d[config.xAxis.dataKey]} {
188
- transform-origin: ${halfWidth}px ${y}px
189
- }
190
- `}
154
+ .bar-${groupTwo.dataKey}-${d[config.xAxis.dataKey]} {
155
+ transform-origin: ${halfWidth}px ${y}px
156
+ }
157
+ `}
191
158
  </style>
192
159
  <Group key={`group-${groupTwo.dataKey}-${d[config.dataDescription.xKey]}`} className='horizontal'>
193
160
  <Bar
@@ -199,15 +166,15 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
199
166
  width={xScale(d[config.series[1].dataKey])}
200
167
  height={barHeight}
201
168
  fill={groupTwo.color}
202
- data-tip={dataTipTwo(d)}
203
- data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
204
- strokeWidth={config.barBorderThickness || 1}
169
+ data-tooltip-html={dataTipTwo(d)}
170
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
171
+ strokeWidth={borderWidth}
205
172
  stroke='#333'
206
173
  opacity={transparentBar ? 0.5 : 1}
207
174
  display={displayBar ? 'block' : 'none'}
208
175
  />
209
176
  {config.yAxis.displayNumbersOnBar && displayBar && (
210
- <Text textAnchor={barWidth < 100 ? 'start' : 'end'} verticalAnchor='middle' x={halfWidth + (barWidth < 100 ? barWidth + 10 : barWidth - 10)} y={y + config.barHeight / 2} fill={barWidth > 100 ? groupTwo.labelColor : '#000'}>
177
+ <Text textAnchor={isTextFits ? 'end' : 'start'} dx={isTextFits ? -5 : 5} verticalAnchor='middle' x={halfWidth + barWidth} y={y + config.barHeight / 2} fill={isTextFits ? groupTwo.labelColor : '#000'}>
211
178
  {formatNumber(d[groupTwo.dataKey])}
212
179
  </Text>
213
180
  )}
@@ -1,6 +1,6 @@
1
1
  import React, { useContext, useState, useEffect, useRef } from 'react'
2
2
  import { animated, useTransition, interpolate } from 'react-spring'
3
- import ReactTooltip from 'react-tooltip'
3
+ import { Tooltip as ReactTooltip } from 'react-tooltip'
4
4
 
5
5
  import Pie, { ProvidedProps, PieArcDatum } from '@visx/shape/lib/shapes/Pie'
6
6
  import chroma from 'chroma-js'
@@ -8,23 +8,22 @@ import { Group } from '@visx/group'
8
8
  import { Text } from '@visx/text'
9
9
  import useIntersectionObserver from './useIntersectionObserver'
10
10
 
11
- import Context from '../context'
11
+ import ConfigContext from '../ConfigContext'
12
12
 
13
13
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
14
14
 
15
- // react-spring transition definitions
16
- type PieStyles = { startAngle: number; endAngle: number }
17
-
18
- const enterUpdateTransition = ({ startAngle, endAngle }: PieArcDatum<any>) => ({
15
+ const enterUpdateTransition = ({ startAngle, endAngle }) => ({
19
16
  startAngle,
20
17
  endAngle
21
18
  })
22
19
 
23
20
  export default function PieChart() {
24
- const { transformedData: data, config, dimensions, seriesHighlight, colorScale, formatNumber, currentViewport, handleChartAriaLabels } = useContext<any>(Context)
21
+ const { transformedData: data, config, dimensions, seriesHighlight, colorScale, formatNumber, currentViewport, handleChartAriaLabels, cleanData } = useContext(ConfigContext)
22
+
23
+ const cleanedData = cleanData(data, config.xAxis.dataKey);
25
24
 
26
- const [filteredData, setFilteredData] = useState<any>(undefined)
27
- const [animatedPie, setAnimatePie] = useState<boolean>(false)
25
+ const [filteredData, setFilteredData] = useState(undefined)
26
+ const [animatedPie, setAnimatePie] = useState(false)
28
27
 
29
28
  const triggerRef = useRef()
30
29
  const dataRef = useIntersectionObserver(triggerRef, {
@@ -49,17 +48,9 @@ export default function PieChart() {
49
48
  }
50
49
  }, [dataRef?.isIntersecting, config.animate])
51
50
 
52
- type AnimatedPieProps<Datum> = ProvidedProps<Datum> & {
53
- animate?: boolean
54
- getKey: (d: PieArcDatum<Datum>) => string
55
- delay?: number
56
- }
57
51
 
58
- function AnimatedPie<Datum>({ arcs, path, getKey }: AnimatedPieProps<Datum>) {
59
- const transitions = useTransition<PieArcDatum<Datum>, PieStyles>(
60
- arcs,
61
- getKey,
62
- // @ts-ignore react-spring doesn't like this overload
52
+ function AnimatedPie({ arcs, path, getKey }) {
53
+ const transitions = useTransition( arcs, getKey,
63
54
  {
64
55
  from: enterUpdateTransition,
65
56
  enter: enterUpdateTransition,
@@ -70,7 +61,7 @@ export default function PieChart() {
70
61
 
71
62
  return (
72
63
  <>
73
- {transitions.map(({ item: arc, props, key }: { item: PieArcDatum<Datum>; props: PieStyles; key: string }) => {
64
+ {transitions.map(({ item: arc, props, key }) => {
74
65
  let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${formatNumber(arc.data[config.runtime.yAxis.dataKey])}` : formatNumber(arc.data[config.runtime.yAxis.dataKey])
75
66
  let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${arc.data[config.runtime.xAxis.dataKey]}` : arc.data[config.runtime.xAxis.dataKey]
76
67
 
@@ -79,11 +70,9 @@ export default function PieChart() {
79
70
  ${xAxisTooltip}<br />
80
71
  Percent: ${Math.round((((arc.endAngle - arc.startAngle) * 180) / Math.PI / 360) * 100) + '%'}
81
72
  `
82
-
83
73
  return (
84
74
  <Group key={key} style={{ opacity: config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(arc.data[config.runtime.xAxis.dataKey]) === -1 ? 0.5 : 1 }}>
85
75
  <animated.path
86
- // compute interpolated path d attribute from intermediate angle values
87
76
  d={interpolate([props.startAngle, props.endAngle], (startAngle, endAngle) =>
88
77
  path({
89
78
  ...arc,
@@ -92,13 +81,13 @@ export default function PieChart() {
92
81
  })
93
82
  )}
94
83
  fill={colorScale(arc.data[config.runtime.xAxis.dataKey])}
95
- data-tip={tooltip}
96
- data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
84
+ data-tooltip-html={tooltip}
85
+ data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
97
86
  />
98
87
  </Group>
99
88
  )
100
89
  })}
101
- {transitions.map(({ item: arc, key }: { item: PieArcDatum<Datum>; props: PieStyles; key: string }) => {
90
+ {transitions.map(({ item: arc, key }) => {
102
91
  const [centroidX, centroidY] = path.centroid(arc)
103
92
  const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1
104
93
 
@@ -150,21 +139,17 @@ export default function PieChart() {
150
139
  }
151
140
  }, [seriesHighlight])
152
141
 
153
- useEffect(() => {
154
- ReactTooltip.rebuild()
155
- })
156
-
157
142
  return (
158
143
  <ErrorBoundary component='PieChart'>
159
144
  <svg width={width} height={height} className={`animated-pie group ${config.animate === false || animatedPie ? 'animated' : ''}`} role='img' aria-label={handleChartAriaLabels(config)}>
160
145
  <Group top={centerY} left={centerX}>
161
- <Pie data={filteredData || data} pieValue={d => d[config.runtime.yAxis.dataKey]} pieSortValues={() => -1} innerRadius={radius - donutThickness} outerRadius={radius}>
162
- {pie => <AnimatedPie<any> {...pie} getKey={d => d.data[config.runtime.xAxis.dataKey]} />}
146
+ <Pie data={filteredData || cleanedData} pieValue={d => d[config.runtime.yAxis.dataKey]} pieSortValues={() => -1} innerRadius={radius - donutThickness} outerRadius={radius}>
147
+ {pie => <AnimatedPie {...pie} getKey={d => d.data[config.runtime.xAxis.dataKey]} />}
163
148
  </Pie>
164
149
  </Group>
165
150
  </svg>
166
151
  <div ref={triggerRef} />
167
- <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} html={true} type='light' arrowColor='rgba(0,0,0,0)' className='tooltip' />
152
+ <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' />
168
153
  </ErrorBoundary>
169
154
  )
170
155
  }