@cdc/chart 4.23.2 → 4.23.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/cdcchart.js +42292 -40337
  2. package/examples/feature/__data__/area-chart.json +56 -0
  3. package/examples/{planet-example-data.json → feature/__data__/planet-example-data-max-increase.json} +4 -4
  4. package/examples/feature/__data__/planet-example-data.json +68 -0
  5. package/examples/feature/area/area-chart.json +244 -0
  6. package/examples/{example-bar-chart.json → feature/bar/example-bar-chart.json} +4 -1
  7. package/examples/feature/bar/horizontal-chart-max-increase.json +44 -0
  8. package/examples/{horizontal-chart.json → feature/bar/horizontal-chart.json} +10 -4
  9. package/examples/{horizontal-stacked-bar-chart.json → feature/bar/horizontal-stacked-bar-chart.json} +7 -3
  10. package/examples/{planet-chart-horizontal-example-config.json → feature/bar/planet-chart-horizontal-example-config.json} +8 -3
  11. package/examples/feature/bar/planet-example-config.json +156 -0
  12. package/examples/{box-plot.json → feature/boxplot/boxplot.json} +7 -8
  13. package/examples/feature/boxplot/testing.csv +38 -0
  14. package/examples/feature/combo/combochart-categories_are_numbers .json +18 -0
  15. package/examples/{planet-combo-example-config.json → feature/combo/planet-combo-example-config.json} +1 -1
  16. package/examples/feature/deviation/planet-deviation-config.json +168 -0
  17. package/examples/feature/deviation/planet-deviation-data.json +38 -0
  18. package/examples/feature/filters/filter-testing.json +178 -0
  19. package/examples/feature/forecasting/case_date_example.csv +130 -0
  20. package/examples/feature/forecasting/effective_reproduction.json +202 -0
  21. package/examples/feature/forecasting/r_data.csv +130 -0
  22. package/examples/feature/line/line-chart-max-increase.json +32 -0
  23. package/examples/feature/line/line-chart.json +124 -0
  24. package/examples/{paired-bar-example.json → feature/paired-bar/paired-bar-example.json} +10 -4
  25. package/examples/{planet-pie-example-config.json → feature/pie/planet-pie-example-config.json} +2 -2
  26. package/examples/{scatterplot-continuous.csv → feature/scatterplot/scatterplot-continuous.csv} +3 -3
  27. package/examples/{scatterplot.json → feature/scatterplot/scatterplot.json} +3 -3
  28. package/examples/feature/sparkline/example-sparkline.json +76 -0
  29. package/examples/feature/tests-big-small/big-small-test-bar.json +328 -0
  30. package/examples/feature/tests-big-small/big-small-test-line.json +328 -0
  31. package/examples/feature/tests-big-small/big-small-test-negative.json +328 -0
  32. package/examples/{case-rate-example-config.json → feature/tests-case-rate/case-rate-example-config.json} +2 -2
  33. package/examples/{covid-confidence-example-config.json → feature/tests-covid/covid-confidence-example-config.json} +8 -3
  34. package/examples/{covid-example-config.json → feature/tests-covid/covid-example-config.json} +7 -3
  35. package/examples/{cutoff-example-config.json → feature/tests-cutoff/cutoff-example-config.json} +7 -3
  36. package/examples/{date-exclusions-config.json → feature/tests-date-exclusions/date-exclusions-config.json} +2 -2
  37. package/examples/{example-bar-chart-nonnumeric.json → feature/tests-non-numerics/example-bar-chart-nonnumeric.json} +1 -1
  38. package/examples/{line-chart-nonnumeric.json → feature/tests-non-numerics/line-chart-nonnumeric.json} +5 -5
  39. package/examples/{planet-pie-example-config-nonnumeric.json → feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json} +2 -2
  40. package/examples/{sparkline-chart-nonnumeric.json → feature/tests-non-numerics/sparkline-chart-nonnumeric.json} +2 -2
  41. package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +31 -172
  42. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +145 -7
  43. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-confidence.json +1 -0
  44. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +96 -14
  45. package/examples/gallery/line/line.json +1 -0
  46. package/examples/gallery/paired-bar/paired-bar-chart.json +1 -0
  47. package/index.html +76 -35
  48. package/package.json +6 -3
  49. package/src/CdcChart.jsx +245 -106
  50. package/src/components/AreaChart.jsx +233 -0
  51. package/src/components/BarChart.jsx +103 -62
  52. package/src/components/BoxPlot.jsx +39 -18
  53. package/src/components/DataTable.jsx +26 -21
  54. package/src/components/DeviationBar.jsx +191 -0
  55. package/src/components/EditorPanel.jsx +662 -298
  56. package/src/components/Legend.jsx +59 -46
  57. package/src/components/LineChart.jsx +12 -36
  58. package/src/components/LinearChart.jsx +163 -64
  59. package/src/components/PairedBarChart.jsx +6 -7
  60. package/src/components/PieChart.jsx +12 -17
  61. package/src/components/ScatterPlot.jsx +19 -16
  62. package/src/components/SparkLine.jsx +84 -118
  63. package/src/components/useIntersectionObserver.jsx +1 -1
  64. package/src/data/initial-state.js +27 -7
  65. package/src/hooks/useColorPalette.js +58 -48
  66. package/src/hooks/useReduceData.js +3 -4
  67. package/src/index.jsx +3 -2
  68. package/src/scss/editor-panel.scss +20 -0
  69. package/src/scss/main.scss +8 -6
  70. package/src/test/CdcChart.test.jsx +6 -0
  71. package/examples/box-plot.csv +0 -5
  72. package/examples/dynamic-legends.json +0 -125
  73. package/examples/line-chart.json +0 -34
  74. package/examples/planet-example-config.json +0 -37
  75. package/examples/temp-example-config.json +0 -64
  76. package/examples/temp-example-data.json +0 -130
  77. package/src/components/Filters.jsx +0 -125
  78. /package/examples/{age-adjusted-rates.json → feature/__data__/age-adjusted-rates.json} +0 -0
  79. /package/examples/{new-data.csv → feature/__data__/new-data.csv} +0 -0
  80. /package/examples/{Barchart_with_negative.json → feature/bar/Barchart_with_negative.json} +0 -0
  81. /package/examples/{stacked-vertical-bar-example-negative.json → feature/bar/stacked-vertical-bar-example-negative.json} +0 -0
  82. /package/examples/{stacked-vertical-bar-example.json → feature/bar/stacked-vertical-bar-example.json} +0 -0
  83. /package/examples/{box-plot-data.json → feature/boxplot/box-plot-data.json} +0 -0
  84. /package/examples/{newdata.json → feature/boxplot/boxplot-data.json} +0 -0
  85. /package/examples/{paired-bar-data.json → feature/paired-bar/paired-bar-data.json} +0 -0
  86. /package/examples/{paired-bar-formatted.json → feature/paired-bar/paired-bar-formatted.json} +0 -0
  87. /package/examples/{case-rate-example-data.json → feature/tests-case-rate/case-rate-example-data.json} +0 -0
  88. /package/examples/{covid-example-data-confidence.json → feature/tests-covid/covid-example-data-confidence.json} +0 -0
  89. /package/examples/{covid-example-data.json → feature/tests-covid/covid-example-data.json} +0 -0
  90. /package/examples/{cutoff-example-data.json → feature/tests-cutoff/cutoff-example-data.json} +0 -0
  91. /package/examples/{date-exclusions-data.json → feature/tests-date-exclusions/date-exclusions-data.json} +0 -0
  92. /package/examples/{example-combo-bar-nonnumeric.json → feature/tests-non-numerics/example-combo-bar-nonnumeric.json} +0 -0
  93. /package/examples/{planet-example-data-nonnumeric.json → feature/tests-non-numerics/planet-example-data-nonnumeric.json} +0 -0
  94. /package/examples/{stacked-vertical-bar-example-nonnumerics.json → feature/tests-non-numerics/stacked-vertical-bar-example-nonnumerics.json} +0 -0
@@ -4,7 +4,7 @@ import { Tooltip as ReactTooltip } from 'react-tooltip'
4
4
  import { Group } from '@visx/group'
5
5
  import { Line } from '@visx/shape'
6
6
  import { Text } from '@visx/text'
7
- import { scaleLinear, scalePoint, scaleBand } from '@visx/scale'
7
+ import { scaleLinear, scalePoint, scaleBand, scaleTime } from '@visx/scale'
8
8
  import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
9
9
 
10
10
  import CoveScatterPlot from './ScatterPlot'
@@ -14,16 +14,19 @@ import ConfigContext from '../ConfigContext'
14
14
  import PairedBarChart from './PairedBarChart'
15
15
  import useIntersectionObserver from './useIntersectionObserver'
16
16
  import CoveBoxPlot from './BoxPlot'
17
+ import CoveAreaChart from './AreaChart'
17
18
 
18
19
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
19
20
  import '../scss/LinearChart.scss'
20
21
  import useReduceData from '../hooks/useReduceData'
21
22
  import useRightAxis from '../hooks/useRightAxis'
22
23
  import useTopAxis from '../hooks/useTopAxis'
24
+ import { DeviationBar } from './DeviationBar'
23
25
 
24
26
  // TODO: Move scaling functions into hooks to manage complexity
25
27
  export default function LinearChart() {
26
28
  const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig } = useContext(ConfigContext)
29
+
27
30
  let [width] = dimensions
28
31
  const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
29
32
  const [animatedChart, setAnimatedChart] = useState(false)
@@ -32,14 +35,16 @@ export default function LinearChart() {
32
35
  const dataRef = useIntersectionObserver(triggerRef, {
33
36
  freezeOnceVisible: false
34
37
  })
38
+
35
39
  // Make sure the chart is visible if in the editor
40
+ /* eslint-disable react-hooks/exhaustive-deps */
36
41
  useEffect(() => {
37
42
  const element = document.querySelector('.isEditor')
38
43
  if (element) {
39
44
  // parent element is visible
40
45
  setAnimatedChart(prevState => true)
41
46
  }
42
- })
47
+ }) /* eslint-disable-line */
43
48
 
44
49
  // If the chart is in view, set to animate if it has not already played
45
50
  useEffect(() => {
@@ -72,9 +77,23 @@ export default function LinearChart() {
72
77
  const isMaxValid = existPositiveValue ? enteredMaxValue >= maxValue : enteredMaxValue >= 0
73
78
  const isMinValid = (enteredMinValue <= 0 && minValue >= 0) || (enteredMinValue <= minValue && minValue < 0)
74
79
 
80
+ let max = 0 // need outside the if statement
81
+ let min = 0
75
82
  if (data) {
76
- let min = enteredMinValue && isMinValid ? enteredMinValue : minValue
77
- let max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
83
+ min = enteredMinValue && isMinValid ? enteredMinValue : minValue
84
+ max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
85
+
86
+ // If Confidence Intervals in data, then need to account for increased height in max for YScale
87
+ if (config.visualizationType === 'Bar' || config.visualizationType === 'Combo' || config.visualizationType === 'Deviation Bar') {
88
+ let ciYMax = 0
89
+ if (config.hasOwnProperty('confidenceKeys')) {
90
+ let upperCIValues = data.map(function (d) {
91
+ return d[config.confidenceKeys.upper]
92
+ })
93
+ ciYMax = Math.max.apply(Math, upperCIValues)
94
+ if (ciYMax > max) max = ciYMax // bump up the max
95
+ }
96
+ }
78
97
 
79
98
  if ((config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && !isAllLine)) && min > 0) {
80
99
  min = 0
@@ -89,6 +108,11 @@ export default function LinearChart() {
89
108
  }
90
109
  }
91
110
 
111
+ if (config.visualizationType === 'Deviation Bar' && min > 0) {
112
+ const isMinValid = Number(enteredMinValue) < Math.min(minValue, Number(config.xAxis.target))
113
+ min = enteredMinValue && isMinValid ? enteredMinValue : 0
114
+ }
115
+
92
116
  if (config.visualizationType === 'Line') {
93
117
  const isMinValid = enteredMinValue < minValue
94
118
  min = enteredMinValue && isMinValid ? enteredMinValue : minValue
@@ -119,12 +143,26 @@ export default function LinearChart() {
119
143
  case maxDataVal > 4 && maxDataVal <= 7:
120
144
  max = max * 1.1
121
145
  break
146
+ default:
147
+ break
148
+ }
149
+ }
150
+
151
+ // DEV-3219 - bc some values are going above YScale - adding 10% or 20% factor onto Max
152
+ // - put the statement up here and it works for both vert and horiz charts of all types
153
+ if (config.yAxis.enablePadding) {
154
+ if (min < 0) {
155
+ // sets with negative data need more padding on the max
156
+ max *= 1.2
157
+ min *= 1.2
158
+ } else {
159
+ max *= 1.1
122
160
  }
123
161
  }
124
162
 
125
163
  if (config.runtime.horizontal) {
126
164
  xScale = scaleLinear({
127
- domain: [min, max],
165
+ domain: [min * 1.03, max],
128
166
  range: [0, xMax]
129
167
  })
130
168
 
@@ -161,6 +199,13 @@ export default function LinearChart() {
161
199
  })
162
200
  }
163
201
 
202
+ if (config.visualizationType === 'Area Chart' && config.xAxis.type === 'date') {
203
+ xScale = scaleTime({
204
+ domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)],
205
+ range: [0, xMax]
206
+ })
207
+ }
208
+
164
209
  if (config.visualizationType === 'Paired Bar') {
165
210
  const offset = 1.02 // Offset of the ticks/values from the Axis
166
211
  let groupOneMax = Math.max.apply(
@@ -194,17 +239,70 @@ export default function LinearChart() {
194
239
  })
195
240
  }
196
241
  }
242
+
243
+ if (config.visualizationType === 'Deviation Bar') {
244
+ const leftOffset = config.isLollipopChart ? 1.05 : 1.03
245
+ yScale = scaleBand({
246
+ domain: xAxisDataMapped,
247
+ range: [0, yMax]
248
+ })
249
+ xScale = scaleLinear({
250
+ domain: [min * leftOffset, Math.max(Number(config.xAxis.target), max)],
251
+ range: [0, xMax],
252
+ round: true,
253
+ nice: true
254
+ })
255
+ }
256
+ // Handle Box Plots
257
+ if (config.visualizationType === 'Box Plot') {
258
+ const allOutliers = []
259
+ const hasOutliers = config.boxplot.plots.map(b => b.columnOutliers.map(outlier => allOutliers.push(outlier))) && !config.boxplot.hideOutliers
260
+
261
+ // check if outliers are lower
262
+ if (hasOutliers) {
263
+ let outlierMin = Math.min(...allOutliers)
264
+ let outlierMax = Math.max(...allOutliers)
265
+
266
+ // check if outliers exceed standard bounds
267
+ if (outlierMin < min) min = outlierMin
268
+ if (outlierMax > max) max = outlierMax
269
+ }
270
+
271
+ // check fences for max/min
272
+ let lowestFence = Math.min(...config.boxplot.plots.map(item => item.columnLowerBounds))
273
+ let highestFence = Math.max(...config.boxplot.plots.map(item => item.columnUpperBounds))
274
+
275
+ if (lowestFence < min) min = lowestFence
276
+ if (highestFence > max) max = highestFence
277
+
278
+ // Set Scales
279
+ yScale = scaleLinear({
280
+ range: [yMax, 0],
281
+ round: true,
282
+ domain: [min, max]
283
+ })
284
+
285
+ xScale = scaleBand({
286
+ range: [0, xMax],
287
+ round: true,
288
+ domain: config.boxplot.categories,
289
+ padding: 0.4
290
+ })
291
+ }
197
292
  }
198
293
 
294
+ const shouldAbbreviate = true
295
+
199
296
  const handleLeftTickFormatting = tick => {
200
297
  if (config.runtime.yAxis.type === 'date') return formatDate(parseDate(tick))
201
- if (config.orientation === 'vertical') return formatNumber(tick, 'left')
298
+ if (config.orientation === 'vertical') return formatNumber(tick, 'left', shouldAbbreviate)
202
299
  return tick
203
300
  }
204
301
 
205
302
  const handleBottomTickFormatting = tick => {
206
303
  if (config.runtime.xAxis.type === 'date') return formatDate(tick)
207
- if (config.orientation === 'horizontal') return formatNumber(tick, 'bottom')
304
+ if (config.orientation === 'horizontal') return formatNumber(tick, 'left', shouldAbbreviate)
305
+ if (config.xAxis.type === 'continuous') return formatNumber(tick, 'bottom', shouldAbbreviate)
208
306
  return tick
209
307
  }
210
308
 
@@ -216,70 +314,66 @@ export default function LinearChart() {
216
314
 
217
315
  if (axis === 'yAxis') {
218
316
  tickCount = isHorizontal && !numTicks ? data.length : isHorizontal && numTicks ? numTicks : !isHorizontal && !numTicks ? undefined : !isHorizontal && numTicks && numTicks
317
+ // to fix edge case of small numbers with decimals
318
+ if (tickCount === undefined && !config.dataFormat.roundTo) {
319
+ // then it is set to Auto
320
+ if (Number(max) <= 3) {
321
+ tickCount = 2
322
+ } else {
323
+ tickCount = 4 // same default as standalone components
324
+ }
325
+ }
326
+ if (Number(tickCount) > Number(max)) {
327
+ // cap it and round it so its an integer
328
+ tickCount = Number(min) < 0 ? Math.round(max) * 2 : Math.round(max)
329
+ }
219
330
  }
220
331
 
221
332
  if (axis === 'xAxis') {
222
333
  tickCount = isHorizontal && !numTicks ? undefined : isHorizontal && numTicks ? numTicks : !isHorizontal && !numTicks ? undefined : !isHorizontal && numTicks && numTicks
334
+ if (isHorizontal && tickCount === undefined && !config.dataFormat.roundTo) {
335
+ // then it is set to Auto
336
+ // - check for small numbers situation
337
+ if (max <= 3) {
338
+ tickCount = 2
339
+ } else {
340
+ tickCount = 4 // same default as standalone components
341
+ }
342
+ }
223
343
  }
224
344
  return tickCount
225
345
  }
226
346
 
227
- // Handle Box Plots
228
- if (config.visualizationType === 'Box Plot') {
229
- let minYValue
230
- let maxYValue
231
- let allOutliers = []
232
- let allLowerBounds = config.boxplot.plots.map(plot => plot.columnMin)
233
- let allUpperBounds = config.boxplot.plots.map(plot => plot.columnMax)
234
-
235
- minYValue = Math.min(...allLowerBounds)
236
- maxYValue = Math.max(...allUpperBounds)
237
-
238
- const hasOutliers = config.boxplot.plots.map(b => b.columnOutliers.map(outlier => allOutliers.push(outlier))) && !config.boxplot.hideOutliers
239
-
240
- if (hasOutliers) {
241
- let outlierMin = Math.min(...allOutliers)
242
- let outlierMax = Math.max(...allOutliers)
243
-
244
- // check if outliers exceed standard bounds
245
- if (outlierMin < minYValue) minYValue = outlierMin
246
- if (outlierMax > maxYValue) maxYValue = outlierMax
247
- }
248
-
249
- const seriesNames = data.map(d => d[config.xAxis.dataKey])
250
-
251
- // Set Scales
252
- yScale = scaleLinear({
253
- range: [yMax, 0],
254
- round: true,
255
- domain: [minYValue, maxYValue]
256
- })
257
-
258
- xScale = scaleBand({
259
- range: [0, xMax],
260
- round: true,
261
- domain: config.boxplot.categories,
262
- padding: 0.4
263
- })
264
- }
265
-
266
- const handleTick = tick => {
267
- return config.runtime.xAxis.type === 'date' ? formatDate(tick) : config.orientation === 'horizontal' ? formatNumber(tick) : tick
268
- }
347
+ const svgRef = useRef()
269
348
 
270
349
  return isNaN(width) ? (
271
350
  <></>
272
351
  ) : (
273
352
  <ErrorBoundary component='LinearChart'>
274
- <svg width={width} height={height} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''}`} role='img' aria-label={handleChartAriaLabels(config)} tabIndex={0}>
353
+ <svg width={width} height={height} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''}`} role='img' aria-label={handleChartAriaLabels(config)} tabIndex={0} ref={svgRef}>
275
354
  {/* Higlighted regions */}
276
355
  {config.regions
277
356
  ? config.regions.map(region => {
278
357
  if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
279
358
 
280
- const from = xScale(parseDate(region.from).getTime())
281
- const to = xScale(parseDate(region.to).getTime())
282
- const width = to - from
359
+ let from
360
+ let to
361
+ let width
362
+
363
+ if (config.xAxis.type === 'date') {
364
+ from = xScale(parseDate(region.from).getTime())
365
+ to = xScale(parseDate(region.to).getTime())
366
+ width = to - from
367
+ }
368
+
369
+ if (config.xAxis.type === 'categorical') {
370
+ from = xScale(region.from)
371
+ to = xScale(region.to)
372
+ width = to - from
373
+ }
374
+
375
+ if (!from) return null
376
+ if (!to) return null
283
377
 
284
378
  return (
285
379
  <Group className='regions' left={Number(config.runtime.yAxis.size)} key={region.label}>
@@ -340,6 +434,11 @@ export default function LinearChart() {
340
434
  {tick.formattedValue}
341
435
  </Text>
342
436
  )}
437
+ {config.orientation === 'horizontal' && config.visualizationType === 'Deviation Bar' && !config.yAxis.hideLabel && (
438
+ <Text transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart ? tick.to.y - minY + 2 : tick.to.y - minY + Number(config.barHeight) / 2}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`} textAnchor={'end'} verticalAnchor='middle'>
439
+ {tick.formattedValue}
440
+ </Text>
441
+ )}
343
442
 
344
443
  {config.orientation !== 'horizontal' && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
345
444
  <Text
@@ -357,6 +456,7 @@ export default function LinearChart() {
357
456
  })}
358
457
  {!config.yAxis.hideAxis && <Line from={props.axisFromPoint} to={config.runtime.horizontal ? { x: 0, y: Number(heightHorizontal) } : props.axisToPoint} stroke='#000' />}
359
458
  {yScale.domain()[0] < 0 && <Line from={{ x: props.axisFromPoint.x, y: yScale(0) }} to={{ x: xMax, y: yScale(0) }} stroke='#333' />}
459
+ {config.visualizationType === 'Bar' && config.orientation === 'horizontal' && xScale.domain()[0] < 0 && <Line from={{ x: xScale(0), y: 0 }} to={{ x: xScale(0), y: yMax }} stroke='#333' strokeWidth={2} />}
360
460
  <Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${-1 * config.runtime.yAxis.size}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.labelColor}>
361
461
  {props.label}
362
462
  </Text>
@@ -471,7 +571,7 @@ export default function LinearChart() {
471
571
  {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
472
572
  {!config.runtime.yAxis.hideLabel && (
473
573
  <Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
474
- {formatNumber(tick.formattedValue)}
574
+ {formatNumber(tick.value, 'left')}
475
575
  </Text>
476
576
  )}
477
577
  </Group>
@@ -504,7 +604,7 @@ export default function LinearChart() {
504
604
  {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
505
605
  {!config.runtime.yAxis.hideLabel && (
506
606
  <Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
507
- {tick.formattedValue}
607
+ {formatNumber(tick.value, 'left')}
508
608
  </Text>
509
609
  )}
510
610
  </Group>
@@ -524,28 +624,27 @@ export default function LinearChart() {
524
624
  </>
525
625
  )}
526
626
 
527
- {/* Paired Bar chart */}
627
+ {config.visualizationType === 'Deviation Bar' && <DeviationBar xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
528
628
  {config.visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
629
+ {config.visualizationType === 'Scatter Plot' && <CoveScatterPlot xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />}
630
+ {config.visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
631
+ {(config.visualizationType === 'Area Chart' || config.visualizationType === 'Combo') && <CoveAreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} />}
529
632
 
530
633
  {/* Bar chart */}
531
- {config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Scatter Plot' && (
634
+ {/* TODO: Make this just bar or combo? */}
635
+ {config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Area Chart' && config.visualizationType !== 'Scatter Plot' && config.visualizationType !== 'Deviation Bar' && (
532
636
  <>
533
637
  <BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} animatedChart={animatedChart} visible={animatedChart} />
534
638
  </>
535
639
  )}
536
640
 
537
641
  {/* Line chart */}
538
- {config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Scatter Plot' && (
642
+ {/* TODO: Make this just line or combo? */}
643
+ {config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Area Chart' && config.visualizationType !== 'Scatter Plot' && config.visualizationType !== 'Deviation Bar' && (
539
644
  <>
540
645
  <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
541
646
  </>
542
647
  )}
543
-
544
- {/* Scatter Plot chart */}
545
- {config.visualizationType === 'Scatter Plot' && <CoveScatterPlot xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />}
546
-
547
- {/* Box Plot chart */}
548
- {config.visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
549
648
  </svg>
550
649
  <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' />
551
650
  <div className='animation-trigger' ref={triggerRef} />
@@ -61,7 +61,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
61
61
  return `<p>
62
62
  ${config.dataDescription.seriesKey}: ${groupOne.dataKey}<br/>
63
63
  ${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
64
- ${label}${formatNumber(d[groupOne.dataKey])}
64
+ ${label}${formatNumber(d[groupOne.dataKey], 'left')}
65
65
  </p>`
66
66
  }
67
67
 
@@ -69,10 +69,9 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
69
69
  return `<p>
70
70
  ${config.dataDescription.seriesKey}: ${groupTwo.dataKey}<br/>
71
71
  ${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
72
- ${label}${formatNumber(d[groupTwo.dataKey])}
72
+ ${label}${formatNumber(d[groupTwo.dataKey], 'left')}
73
73
  </p>`
74
74
  }
75
- console.log(config)
76
75
 
77
76
  return (
78
77
  width > 0 && (
@@ -100,7 +99,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
100
99
  const totalheight = (Number(config.barSpace) + barHeight + borderWidth) * data.length
101
100
  config.heights.horizontal = totalheight
102
101
  // 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`)
102
+ const textWidth = getTextWidth(formatNumber(d[groupOne.dataKey], 'left'), `normal ${fontSize[config.fontSize]}px sans-serif`)
104
103
  const textFits = textWidth < barWidth - 5 // minus padding dx(5)
105
104
 
106
105
  return (
@@ -124,7 +123,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
124
123
  />
125
124
  {config.yAxis.displayNumbersOnBar && displayBar && (
126
125
  <Text textAnchor={textFits ? 'start' : 'end'} dx={textFits ? 5 : -5} verticalAnchor='middle' x={halfWidth - barWidth} y={y + config.barHeight / 2} fill={textFits ? groupOne.labelColor : '#000'}>
127
- {formatNumber(d[groupOne.dataKey])}
126
+ {formatNumber(d[groupOne.dataKey], 'left')}
128
127
  </Text>
129
128
  )}
130
129
  </Group>
@@ -144,7 +143,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
144
143
  const totalheight = (Number(config.barSpace) + barHeight + borderWidth) * data.length
145
144
  config.heights.horizontal = totalheight
146
145
  // 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`)
146
+ const textWidth = getTextWidth(formatNumber(d[groupTwo.dataKey], 'left'), `normal ${fontSize[config.fontSize]}px sans-serif`)
148
147
  const isTextFits = textWidth < barWidth - 5 // minus padding dx(5)
149
148
 
150
149
  return (
@@ -175,7 +174,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
175
174
  />
176
175
  {config.yAxis.displayNumbersOnBar && displayBar && (
177
176
  <Text textAnchor={isTextFits ? 'end' : 'start'} dx={isTextFits ? -5 : 5} verticalAnchor='middle' x={halfWidth + barWidth} y={y + config.barHeight / 2} fill={isTextFits ? groupTwo.labelColor : '#000'}>
178
- {formatNumber(d[groupTwo.dataKey])}
177
+ {formatNumber(d[groupTwo.dataKey], 'left')}
179
178
  </Text>
180
179
  )}
181
180
  </Group>
@@ -2,7 +2,7 @@ import React, { useContext, useState, useEffect, useRef } from 'react'
2
2
  import { animated, useTransition, interpolate } from 'react-spring'
3
3
  import { Tooltip as ReactTooltip } from 'react-tooltip'
4
4
 
5
- import Pie, { ProvidedProps, PieArcDatum } from '@visx/shape/lib/shapes/Pie'
5
+ import Pie from '@visx/shape/lib/shapes/Pie'
6
6
  import chroma from 'chroma-js'
7
7
  import { Group } from '@visx/group'
8
8
  import { Text } from '@visx/text'
@@ -18,9 +18,7 @@ const enterUpdateTransition = ({ startAngle, endAngle }) => ({
18
18
  })
19
19
 
20
20
  export default function PieChart() {
21
- const { transformedData: data, config, dimensions, seriesHighlight, colorScale, formatNumber, currentViewport, handleChartAriaLabels, cleanData } = useContext(ConfigContext)
22
-
23
- const cleanedData = cleanData(data, config.xAxis.dataKey);
21
+ const { transformedData: data, config, dimensions, seriesHighlight, colorScale, formatNumber, currentViewport, handleChartAriaLabels } = useContext(ConfigContext)
24
22
 
25
23
  const [filteredData, setFilteredData] = useState(undefined)
26
24
  const [animatedPie, setAnimatePie] = useState(false)
@@ -31,11 +29,11 @@ export default function PieChart() {
31
29
  })
32
30
 
33
31
  // Make sure the chart is visible if in the editor
32
+ /* eslint-disable react-hooks/exhaustive-deps */
34
33
  useEffect(() => {
35
34
  const element = document.querySelector('.isEditor')
36
35
  if (element) {
37
36
  // parent element is visible
38
- console.log('setAnimation')
39
37
  setAnimatePie(prevState => true)
40
38
  }
41
39
  })
@@ -46,18 +44,15 @@ export default function PieChart() {
46
44
  setAnimatePie(true)
47
45
  }, 500)
48
46
  }
49
- }, [dataRef?.isIntersecting, config.animate])
50
-
47
+ }, [dataRef?.isIntersecting, config.animate]) // eslint-disable-line
51
48
 
52
49
  function AnimatedPie({ arcs, path, getKey }) {
53
- const transitions = useTransition( arcs, getKey,
54
- {
55
- from: enterUpdateTransition,
56
- enter: enterUpdateTransition,
57
- update: enterUpdateTransition,
58
- leave: enterUpdateTransition
59
- }
60
- )
50
+ const transitions = useTransition(arcs, getKey, {
51
+ from: enterUpdateTransition,
52
+ enter: enterUpdateTransition,
53
+ update: enterUpdateTransition,
54
+ leave: enterUpdateTransition
55
+ })
61
56
 
62
57
  return (
63
58
  <>
@@ -137,13 +132,13 @@ export default function PieChart() {
137
132
  } else {
138
133
  setFilteredData(undefined)
139
134
  }
140
- }, [seriesHighlight])
135
+ }, [seriesHighlight]) // eslint-disable-line
141
136
 
142
137
  return (
143
138
  <ErrorBoundary component='PieChart'>
144
139
  <svg width={width} height={height} className={`animated-pie group ${config.animate === false || animatedPie ? 'animated' : ''}`} role='img' aria-label={handleChartAriaLabels(config)}>
145
140
  <Group top={centerY} left={centerX}>
146
- <Pie data={filteredData || cleanedData} pieValue={d => d[config.runtime.yAxis.dataKey]} pieSortValues={() => -1} innerRadius={radius - donutThickness} outerRadius={radius}>
141
+ <Pie data={filteredData || data} pieValue={d => d[config.runtime.yAxis.dataKey]} pieSortValues={() => -1} innerRadius={radius - donutThickness} outerRadius={radius}>
147
142
  {pie => <AnimatedPie {...pie} getKey={d => d.data[config.runtime.xAxis.dataKey]} />}
148
143
  </Pie>
149
144
  </Group>
@@ -3,39 +3,42 @@ import ConfigContext from '../ConfigContext'
3
3
  import { Group } from '@visx/group'
4
4
 
5
5
  const CoveScatterPlot = ({ xScale, yScale, getXAxisData, getYAxisData }) => {
6
- const { colorScale, transformedData: data, config } = useContext(ConfigContext)
6
+ const { colorScale, transformedData: data, config, formatNumber, seriesHighlight, colorPalettes } = useContext(ConfigContext)
7
7
 
8
- // copied from line chart
9
- // should probably be a constant somewhere.
8
+ // TODO: copied from line chart should probably be a constant somewhere.
10
9
  let circleRadii = 4.5
11
-
12
- let pointStyles = {
13
- filter: 'unset',
14
- opacity: 1,
15
- stroke: 'black'
16
- }
10
+ const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
17
11
 
18
12
  const handleTooltip = (item, s) => `<div>
19
- ${config.xAxis.label}: ${item[config.xAxis.dataKey]} <br/>
20
- ${config.yAxis.label}: ${item[s]}
13
+ ${config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && hasMultipleSeries ? `${config.runtime.seriesLabels[s] || ''}<br/>` : ''}
14
+ ${config.xAxis.label}: ${formatNumber(item[config.xAxis.dataKey], 'bottom')} <br/>
15
+ ${config.yAxis.label}: ${formatNumber(item[s], 'left')}
21
16
  </div>`
22
17
 
23
18
  return (
24
19
  <Group className='scatter-plot' left={config.yAxis.size}>
25
20
  {data.map((item, dataIndex) => {
26
21
  // prettier-ignore
27
- return config.runtime.seriesKeys.map(s => {
22
+ return config.runtime.seriesKeys.map((s, index) => {
23
+ const transparentArea = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(s) === -1
24
+ const displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(s) !== -1
25
+ const seriesColor = config.palette ? colorPalettes[config.palette][index] : '#000'
26
+
27
+ let pointStyles = {
28
+ filter: 'unset',
29
+ opacity: 1,
30
+ stroke: displayArea ? 'black' : ''
31
+ }
28
32
 
29
33
  return (
30
34
  <circle
31
- key={`${dataIndex}`}
35
+ key={`${dataIndex}-${index}`}
32
36
  r={circleRadii}
33
37
  cx={xScale(item[config.xAxis.dataKey])}
34
38
  cy={yScale(item[s])}
35
- fillOpacity={1}
36
- opacity={1}
39
+ fill={displayArea ? seriesColor : 'transparent'}
40
+ fillOpacity={transparentArea ? .25 : 1}
37
41
  style={pointStyles}
38
- fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s] : s) : '#000'}
39
42
  data-tooltip-html={handleTooltip(item, s)}
40
43
  data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
41
44
  />