@cdc/chart 4.23.2 → 4.23.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/cdcchart.js +41198 -39447
  2. package/examples/area-chart.json +187 -0
  3. package/examples/big-small-test-bar.json +328 -0
  4. package/examples/big-small-test-line.json +328 -0
  5. package/examples/big-small-test-negative.json +328 -0
  6. package/examples/box-plot.json +0 -1
  7. package/examples/example-bar-chart.json +4 -1
  8. package/examples/example-sparkline.json +76 -0
  9. package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +31 -172
  10. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-confidence.json +1 -0
  11. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +96 -14
  12. package/examples/gallery/line/line.json +1 -0
  13. package/examples/horizontal-chart-max-increase.json +38 -0
  14. package/examples/line-chart-max-increase.json +32 -0
  15. package/examples/line-chart-nonnumeric.json +5 -5
  16. package/examples/line-chart.json +6 -6
  17. package/examples/planet-deviation-config.json +168 -0
  18. package/examples/planet-deviation-data.json +38 -0
  19. package/examples/planet-example-config.json +139 -20
  20. package/examples/planet-example-data-max-increase.json +56 -0
  21. package/examples/planet-example-data.json +10 -10
  22. package/examples/scatterplot-continuous.csv +3 -3
  23. package/examples/scatterplot.json +2 -2
  24. package/examples/sparkline-chart-nonnumeric.json +3 -3
  25. package/index.html +26 -9
  26. package/package.json +6 -3
  27. package/src/CdcChart.jsx +146 -92
  28. package/src/components/AreaChart.jsx +198 -0
  29. package/src/components/BarChart.jsx +58 -34
  30. package/src/components/BoxPlot.jsx +28 -15
  31. package/src/components/DataTable.jsx +21 -17
  32. package/src/components/DeviationBar.jsx +191 -0
  33. package/src/components/EditorPanel.jsx +473 -168
  34. package/src/components/Filters.jsx +3 -2
  35. package/src/components/Legend.jsx +59 -46
  36. package/src/components/LineChart.jsx +3 -21
  37. package/src/components/LinearChart.jsx +158 -55
  38. package/src/components/PairedBarChart.jsx +0 -1
  39. package/src/components/PieChart.jsx +11 -14
  40. package/src/components/ScatterPlot.jsx +19 -16
  41. package/src/components/SparkLine.jsx +87 -85
  42. package/src/components/useIntersectionObserver.jsx +1 -1
  43. package/src/data/initial-state.js +20 -4
  44. package/src/hooks/useColorPalette.js +58 -48
  45. package/src/hooks/useReduceData.js +3 -4
  46. package/src/index.jsx +1 -1
  47. package/src/scss/editor-panel.scss +5 -0
  48. package/src/test/CdcChart.test.jsx +6 -0
@@ -3,7 +3,7 @@ import ConfigContext from './../ConfigContext'
3
3
  import Button from '@cdc/core/components/elements/Button'
4
4
 
5
5
  const useFilters = () => {
6
- const { config, setConfig, filteredData, setFilteredData, excludedData, filterData, runtimeFilters } = useContext(ConfigContext)
6
+ const { config, setConfig, setFilteredData, excludedData, filterData } = useContext(ConfigContext)
7
7
  const [showApplyButton, setShowApplyButton] = useState(false)
8
8
 
9
9
  const sortAsc = (a, b) => {
@@ -14,7 +14,7 @@ const useFilters = () => {
14
14
  return b.toString().localeCompare(a.toString(), 'en', { numeric: true })
15
15
  }
16
16
 
17
- const announceChange = text => { }
17
+ const announceChange = text => {}
18
18
 
19
19
  const changeFilterActive = (index, value) => {
20
20
  let newFilters = config.filters
@@ -38,6 +38,7 @@ const useFilters = () => {
38
38
  // reset to first item in values array.
39
39
  newFilters.map(filter => {
40
40
  filter.active = filter.values[0]
41
+ return null
41
42
  })
42
43
 
43
44
  setFilteredData(filterData(newFilters, excludedData))
@@ -7,7 +7,7 @@ import LegendCircle from '@cdc/core/components/LegendCircle'
7
7
  import useLegendClasses from './../hooks/useLegendClasses'
8
8
 
9
9
  const Legend = () => {
10
- const { config, legend, colorScale, seriesHighlight, highlight, highlightReset, setSeriesHighlight, dynamicLegendItems, setDynamicLegendItems, transformedData: data, colorPalettes, rawData, setConfig, currentViewport } = useContext(ConfigContext)
10
+ const { config, legend, colorScale, seriesHighlight, highlight, twoColorPalette, highlightReset, setSeriesHighlight, dynamicLegendItems, setDynamicLegendItems, transformedData: data, colorPalettes, rawData, setConfig, currentViewport } = useContext(ConfigContext)
11
11
 
12
12
  const { innerClasses, containerClasses } = useLegendClasses(config)
13
13
 
@@ -25,12 +25,14 @@ const Legend = () => {
25
25
  let tmp = {}
26
26
  colsToKeep.map(col => {
27
27
  tmp[col] = isNaN(dataItem[col]) ? dataItem[col] : dataItem[col]
28
+ return null
28
29
  })
29
30
  return tmp
30
31
  })
31
32
 
32
33
  colsToKeep.map(col => {
33
34
  tmpLabels[col] = col
35
+ return null
34
36
  })
35
37
 
36
38
  if (dynamicLegendItems.length > 0) {
@@ -43,7 +45,7 @@ const Legend = () => {
43
45
  }
44
46
  })
45
47
  }
46
- }, [dynamicLegendItems])
48
+ }, [dynamicLegendItems]) // eslint-disable-line
47
49
 
48
50
  useEffect(() => {
49
51
  if (dynamicLegendItems.length === 0) {
@@ -53,7 +55,9 @@ const Legend = () => {
53
55
  config.runtime.seriesLabelsAll.map(item => {
54
56
  resetSeriesNames.map(col => {
55
57
  tmpLabels[col] = col
58
+ return null
56
59
  })
60
+ return null
57
61
  })
58
62
 
59
63
  setConfig({
@@ -65,7 +69,7 @@ const Legend = () => {
65
69
  }
66
70
  })
67
71
  }
68
- }, [dynamicLegendItems])
72
+ }, [dynamicLegendItems]) // eslint-disable-line
69
73
 
70
74
  const removeDynamicLegendItem = label => {
71
75
  let newLegendItems = dynamicLegendItems.filter(item => item.text !== label.text)
@@ -77,67 +81,75 @@ const Legend = () => {
77
81
  setDynamicLegendItems([...dynamicLegendItems, JSON.parse(e.target.value)])
78
82
  }
79
83
 
80
- const createLegendLabels = (data, defaultLabels) => {
84
+ const createLegendLabels = defaultLabels => {
81
85
  const colorCode = config.legend?.colorCode
82
- if (config.visualizationType !== 'Bar' || config.visualizationSubType !== 'regular' || !colorCode || config.series?.length > 1) {
83
- return defaultLabels
84
- }
85
- let palette = colorPalettes[config.palette]
86
+ if (config.visualizationType === 'Deviation Bar') {
87
+ const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette]
88
+ const labelBelow = {
89
+ datum: 'X',
90
+ index: 0,
91
+ text: `Below ${config.xAxis.targetLabel}`,
92
+ value: belowColor
93
+ }
94
+ const labelAbove = {
95
+ datum: 'X',
96
+ index: 1,
97
+ text: `Above ${config.xAxis.targetLabel}`,
98
+ value: aboveColor
99
+ }
86
100
 
87
- while (data.length > palette.length) {
88
- palette = palette.concat(palette)
101
+ return [labelBelow, labelAbove]
89
102
  }
90
- palette = palette.slice(0, data.length)
91
- //store uniq values to Set by colorCode
92
- const set = new Set()
93
-
94
- data.forEach(d => set.add(d[colorCode]))
95
-
96
- // create labels with uniq values
97
- const uniqeLabels = Array.from(set).map((val, i) => {
98
- const newLabel = {
99
- datum: val,
100
- index: i,
101
- text: val,
102
- value: palette[i]
103
+ if (config.visualizationType === 'Bar' && config.visualizationSubType === 'regular' && colorCode && config.series?.length === 1) {
104
+ let palette = colorPalettes[config.palette]
105
+
106
+ while (data.length > palette.length) {
107
+ palette = palette.concat(palette)
103
108
  }
104
- return newLabel
105
- })
109
+ palette = palette.slice(0, data.length)
110
+ //store uniq values to Set by colorCode
111
+ const set = new Set()
112
+
113
+ data.forEach(d => set.add(d[colorCode]))
114
+
115
+ // create labels with uniq values
116
+ const uniqeLabels = Array.from(set).map((val, i) => {
117
+ const newLabel = {
118
+ datum: val,
119
+ index: i,
120
+ text: val,
121
+ value: palette[i]
122
+ }
123
+ return newLabel
124
+ })
106
125
 
107
- return uniqeLabels
108
- }
109
- // in small screens update config legend position.
110
- useEffect(() => {
111
- if (currentViewport === 'sm' || currentViewport === 'xs' || config.legend.position === 'left') {
112
- setConfig({ ...config, legend: { ...config.legend, position: 'bottom' } })
126
+ return uniqeLabels
113
127
  }
114
- setConfig({ ...config, legend: { ...config.legend, position: 'right' } })
115
- }, [currentViewport])
128
+ return defaultLabels
129
+ }
130
+
131
+ const isBottomOrSmallViewport = config.legend.position === 'bottom' || currentViewport === 'sm' || currentViewport === 'xs'
132
+ const isHorizontal = config.orientation === 'horizontal'
133
+ const marginTop = isBottomOrSmallViewport && isHorizontal ? `${config.runtime.xAxis.size}px` : '0px'
134
+ const marginBottom = isBottomOrSmallViewport ? '15px' : '0px'
116
135
 
117
- if (!legend) return
136
+ if (!legend) return null
118
137
 
119
138
  if (!legend.dynamicLegend)
120
139
  return config.visualizationType !== 'Box Plot' ? (
121
- <aside
122
- style={{ marginTop: config.legend.position === 'bottom' && config.orientation === 'horizontal' ? `${config.runtime.xAxis.size}px` : '0px', marginBottom: config.legend.position === 'bottom' ? '15px' : '0px' }}
123
- id='legend'
124
- className={containerClasses.join(' ')}
125
- role='region'
126
- aria-label='legend'
127
- tabIndex={0}
128
- >
140
+ <aside style={{ marginTop, marginBottom }} id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
129
141
  {legend.label && <h2>{parse(legend.label)}</h2>}
130
142
  {legend.description && <p>{parse(legend.description)}</p>}
131
143
  <LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
132
144
  {labels => (
133
145
  <div className={innerClasses.join(' ')}>
134
- {createLegendLabels(data, labels).map((label, i) => {
146
+ {createLegendLabels(labels).map((label, i) => {
135
147
  let className = 'legend-item'
136
148
  let itemName = label.datum
137
149
 
138
150
  // Filter excluded data keys from legend
139
151
  if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
140
- return
152
+ return null
141
153
  }
142
154
 
143
155
  if (config.runtime.seriesLabels) {
@@ -209,7 +221,7 @@ const Legend = () => {
209
221
 
210
222
  // Filter excluded data keys from legend
211
223
  if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
212
- return
224
+ return null
213
225
  }
214
226
 
215
227
  if (config.runtime.seriesLabels) {
@@ -225,6 +237,7 @@ const Legend = () => {
225
237
  if (listItem.text === label.text) {
226
238
  inDynamicList = true
227
239
  }
240
+ return null
228
241
  })
229
242
 
230
243
  if (inDynamicList) return true
@@ -254,7 +267,7 @@ const Legend = () => {
254
267
 
255
268
  // Filter excluded data keys from legend
256
269
  if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
257
- return
270
+ return null
258
271
  }
259
272
 
260
273
  if (config.runtime.seriesLabels && !config.legend.dynamicLegend) {
@@ -12,28 +12,15 @@ import ConfigContext from '../ConfigContext'
12
12
  import useRightAxis from '../hooks/useRightAxis'
13
13
 
14
14
  export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, seriesStyle = 'Line' }) {
15
- const { colorPalettes, transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, isNumber, cleanData, updateConfig } = useContext(ConfigContext)
15
+ const { colorPalettes, transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, isNumber, cleanData, updateConfig, handleLineType } = useContext(ConfigContext)
16
16
  // Just do this once up front otherwise we end up
17
17
  // calling clean several times on same set of data (TT)
18
18
  const cleanedData = cleanData(data, config.xAxis.dataKey)
19
19
  const { yScaleRight } = useRightAxis({ config, yMax, data, updateConfig })
20
20
 
21
- const handleLineType = lineType => {
22
- switch (lineType) {
23
- case 'dashed-sm':
24
- return '5 5'
25
- case 'dashed-md':
26
- return '10 5'
27
- case 'dashed-lg':
28
- return '15 5'
29
- default:
30
- return 0
31
- }
32
- }
33
-
34
21
  const handleAxisFormating = (axis = 'left', label, value) => {
35
22
  // if this is an x axis category/date value return without doing any formatting.
36
- if (label === config.runtime.xAxis.label) return value
23
+ // if (label === config.runtime.xAxis.label) return value
37
24
 
38
25
  axis = String(axis).toLocaleLowerCase()
39
26
  if (label) {
@@ -86,11 +73,6 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
86
73
  d[seriesKey] !== undefined &&
87
74
  d[seriesKey] !== '' &&
88
75
  d[seriesKey] !== null && (
89
- // isNumber(d[seriesKey]) &&
90
- // isNumber(getYAxisData(d, seriesKey)) &&
91
- // isNumber(getXAxisData(d)) &&
92
- // isNumber(yScaleRight(getXAxisData(d))) &&
93
- // isNumber(yScale(getXAxisData(d))) &&
94
76
  <Group key={`series-${seriesKey}-point-${dataIndex}`}>
95
77
  {/* Render legend */}
96
78
  <Text
@@ -100,7 +82,7 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
100
82
  fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
101
83
  textAnchor='middle'
102
84
  >
103
- {formatNumber(d[seriesKey])}
85
+ {formatNumber(d[seriesKey], 'left')}
104
86
  </Text>
105
87
 
106
88
  <circle
@@ -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
- const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig } = useContext(ConfigContext)
28
+ const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, stringFormattingOptions } = 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,11 +77,37 @@ 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
+ // DEV-3263 - 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
+ }
97
+
98
+ // DEV-3263 - If Confidence Intervals in data, then need to account for increased height in max for YScale
99
+ if (config.visualizationType === 'Bar' || config.visualizationType === 'Combo') {
100
+ let ciYMax = 0
101
+ if (config.hasOwnProperty('confidenceKeys')) {
102
+ let upperCIValues = data.map(function (d) {
103
+ return d[config.confidenceKeys.upper]
104
+ })
105
+ ciYMax = Math.max.apply(Math, upperCIValues)
106
+ if (ciYMax > max) max = ciYMax // bump up the max
107
+ }
108
+ }
78
109
 
79
- if ((config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && !isAllLine)) && min > 0) {
110
+ if ((config.visualizationType === 'Bar' || config.visualizationType === 'Deviation Bar' || (config.visualizationType === 'Combo' && !isAllLine)) && min > 0) {
80
111
  min = 0
81
112
  }
82
113
  if (config.visualizationType === 'Combo' && isAllLine) {
@@ -119,6 +150,19 @@ export default function LinearChart() {
119
150
  case maxDataVal > 4 && maxDataVal <= 7:
120
151
  max = max * 1.1
121
152
  break
153
+ default:
154
+ break
155
+ }
156
+ }
157
+
158
+ // DEV-3219 - bc some values are going above YScale - adding 10% or 20% factor onto Max
159
+ // - put the statement up here and it works for both vert and horiz charts of all types
160
+ if (config.yAxis.enablePadding) {
161
+ if (min < 0) {
162
+ // sets with negative data need more padding on the max
163
+ max *= 1.2
164
+ } else {
165
+ max *= 1.1
122
166
  }
123
167
  }
124
168
 
@@ -161,6 +205,13 @@ export default function LinearChart() {
161
205
  })
162
206
  }
163
207
 
208
+ if (config.visualizationType === 'Area Chart' && config.xAxis.type === 'date') {
209
+ xScale = scaleTime({
210
+ domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)],
211
+ range: [0, xMax]
212
+ })
213
+ }
214
+
164
215
  if (config.visualizationType === 'Paired Bar') {
165
216
  const offset = 1.02 // Offset of the ticks/values from the Axis
166
217
  let groupOneMax = Math.max.apply(
@@ -194,6 +245,55 @@ export default function LinearChart() {
194
245
  })
195
246
  }
196
247
  }
248
+
249
+ if (config.visualizationType === 'Deviation Bar') {
250
+ const leftOffset = config.isLollipopChart ? 1.05 : 1.03
251
+ yScale = scaleBand({
252
+ domain: xAxisDataMapped,
253
+ range: [0, yMax]
254
+ })
255
+ xScale = scaleLinear({
256
+ domain: [min * leftOffset, Math.max(Number(config.xAxis.target), max)],
257
+ range: [0, xMax],
258
+ round: true
259
+ })
260
+ }
261
+ // Handle Box Plots
262
+ if (config.visualizationType === 'Box Plot') {
263
+ const allOutliers = []
264
+ const hasOutliers = config.boxplot.plots.map(b => b.columnOutliers.map(outlier => allOutliers.push(outlier))) && !config.boxplot.hideOutliers
265
+
266
+ // check if outliers are lower
267
+ if (hasOutliers) {
268
+ let outlierMin = Math.min(...allOutliers)
269
+ let outlierMax = Math.max(...allOutliers)
270
+
271
+ // check if outliers exceed standard bounds
272
+ if (outlierMin < min) min = outlierMin
273
+ if (outlierMax > max) max = outlierMax
274
+ }
275
+
276
+ // check fences for max/min
277
+ let lowestFence = Math.min(...config.boxplot.plots.map(item => item.columnMin))
278
+ let highestFence = Math.max(...config.boxplot.plots.map(item => item.columnMax))
279
+
280
+ if (lowestFence < min) min = lowestFence
281
+ if (highestFence > max) max = highestFence
282
+
283
+ // Set Scales
284
+ yScale = scaleLinear({
285
+ range: [yMax, 0],
286
+ round: true,
287
+ domain: [min, max]
288
+ })
289
+
290
+ xScale = scaleBand({
291
+ range: [0, xMax],
292
+ round: true,
293
+ domain: config.boxplot.categories,
294
+ padding: 0.4
295
+ })
296
+ }
197
297
  }
198
298
 
199
299
  const handleLeftTickFormatting = tick => {
@@ -204,7 +304,8 @@ export default function LinearChart() {
204
304
 
205
305
  const handleBottomTickFormatting = tick => {
206
306
  if (config.runtime.xAxis.type === 'date') return formatDate(tick)
207
- if (config.orientation === 'horizontal') return formatNumber(tick, 'bottom')
307
+ if (config.orientation === 'horizontal') return formatNumber(tick, 'left')
308
+ if (config.xAxis.type === 'continuous') return formatNumber(tick, 'bottom')
208
309
  return tick
209
310
  }
210
311
 
@@ -216,57 +317,36 @@ export default function LinearChart() {
216
317
 
217
318
  if (axis === 'yAxis') {
218
319
  tickCount = isHorizontal && !numTicks ? data.length : isHorizontal && numTicks ? numTicks : !isHorizontal && !numTicks ? undefined : !isHorizontal && numTicks && numTicks
320
+ // to fix edge case of small numbers with decimals
321
+ if (tickCount === undefined && !config.dataFormat.roundTo) {
322
+ // then it is set to Auto
323
+ if (Number(max) <= 3) {
324
+ tickCount = 2
325
+ } else {
326
+ tickCount = 4 // same default as standalone components
327
+ }
328
+ }
329
+ if (Number(tickCount) > Number(max)) {
330
+ // cap it and round it so its an integer
331
+ tickCount = Number(min) < 0 ? Math.round(max) * 2 : Math.round(max)
332
+ }
219
333
  }
220
334
 
221
335
  if (axis === 'xAxis') {
222
336
  tickCount = isHorizontal && !numTicks ? undefined : isHorizontal && numTicks ? numTicks : !isHorizontal && !numTicks ? undefined : !isHorizontal && numTicks && numTicks
337
+ if (isHorizontal && tickCount === undefined && !config.dataFormat.roundTo) {
338
+ // then it is set to Auto
339
+ // - check for small numbers situation
340
+ if (max <= 3) {
341
+ tickCount = 2
342
+ } else {
343
+ tickCount = 4 // same default as standalone components
344
+ }
345
+ }
223
346
  }
224
347
  return tickCount
225
348
  }
226
349
 
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
- }
269
-
270
350
  return isNaN(width) ? (
271
351
  <></>
272
352
  ) : (
@@ -277,9 +357,24 @@ export default function LinearChart() {
277
357
  ? config.regions.map(region => {
278
358
  if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
279
359
 
280
- const from = xScale(parseDate(region.from).getTime())
281
- const to = xScale(parseDate(region.to).getTime())
282
- const width = to - from
360
+ let from
361
+ let to
362
+ let width
363
+
364
+ if (config.xAxis.type === 'date') {
365
+ from = xScale(parseDate(region.from).getTime())
366
+ to = xScale(parseDate(region.to).getTime())
367
+ width = to - from
368
+ }
369
+
370
+ if (config.xAxis.type === 'categorical') {
371
+ from = xScale(region.from)
372
+ to = xScale(region.to)
373
+ width = to - from
374
+ }
375
+
376
+ if (!from) return null
377
+ if (!to) return null
283
378
 
284
379
  return (
285
380
  <Group className='regions' left={Number(config.runtime.yAxis.size)} key={region.label}>
@@ -340,6 +435,11 @@ export default function LinearChart() {
340
435
  {tick.formattedValue}
341
436
  </Text>
342
437
  )}
438
+ {config.orientation === 'horizontal' && config.visualizationType === 'Deviation Bar' && !config.yAxis.hideLabel && (
439
+ <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'>
440
+ {tick.formattedValue}
441
+ </Text>
442
+ )}
343
443
 
344
444
  {config.orientation !== 'horizontal' && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
345
445
  <Text
@@ -524,18 +624,20 @@ export default function LinearChart() {
524
624
  </>
525
625
  )}
526
626
 
627
+ {config.visualizationType === 'Deviation Bar' && <DeviationBar xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
628
+
527
629
  {/* Paired Bar chart */}
528
630
  {config.visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
529
631
 
530
632
  {/* Bar chart */}
531
- {config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Scatter Plot' && (
633
+ {config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Area Chart' && config.visualizationType !== 'Scatter Plot' && config.visualizationType !== 'Deviation Bar' && (
532
634
  <>
533
635
  <BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} animatedChart={animatedChart} visible={animatedChart} />
534
636
  </>
535
637
  )}
536
638
 
537
639
  {/* Line chart */}
538
- {config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Scatter Plot' && (
640
+ {config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Area Chart' && config.visualizationType !== 'Scatter Plot' && config.visualizationType !== 'Deviation Bar' && (
539
641
  <>
540
642
  <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
541
643
  </>
@@ -546,6 +648,7 @@ export default function LinearChart() {
546
648
 
547
649
  {/* Box Plot chart */}
548
650
  {config.visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
651
+ {config.visualizationType === 'Area Chart' && <CoveAreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} />}
549
652
  </svg>
550
653
  <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' />
551
654
  <div className='animation-trigger' ref={triggerRef} />
@@ -72,7 +72,6 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
72
72
  ${label}${formatNumber(d[groupTwo.dataKey])}
73
73
  </p>`
74
74
  }
75
- console.log(config)
76
75
 
77
76
  return (
78
77
  width > 0 && (
@@ -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'
@@ -20,7 +20,7 @@ const enterUpdateTransition = ({ startAngle, endAngle }) => ({
20
20
  export default function PieChart() {
21
21
  const { transformedData: data, config, dimensions, seriesHighlight, colorScale, formatNumber, currentViewport, handleChartAriaLabels, cleanData } = useContext(ConfigContext)
22
22
 
23
- const cleanedData = cleanData(data, config.xAxis.dataKey);
23
+ const cleanedData = cleanData(data, config.xAxis.dataKey)
24
24
 
25
25
  const [filteredData, setFilteredData] = useState(undefined)
26
26
  const [animatedPie, setAnimatePie] = useState(false)
@@ -31,11 +31,11 @@ export default function PieChart() {
31
31
  })
32
32
 
33
33
  // Make sure the chart is visible if in the editor
34
+ /* eslint-disable react-hooks/exhaustive-deps */
34
35
  useEffect(() => {
35
36
  const element = document.querySelector('.isEditor')
36
37
  if (element) {
37
38
  // parent element is visible
38
- console.log('setAnimation')
39
39
  setAnimatePie(prevState => true)
40
40
  }
41
41
  })
@@ -46,18 +46,15 @@ export default function PieChart() {
46
46
  setAnimatePie(true)
47
47
  }, 500)
48
48
  }
49
- }, [dataRef?.isIntersecting, config.animate])
50
-
49
+ }, [dataRef?.isIntersecting, config.animate]) // eslint-disable-line
51
50
 
52
51
  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
- )
52
+ const transitions = useTransition(arcs, getKey, {
53
+ from: enterUpdateTransition,
54
+ enter: enterUpdateTransition,
55
+ update: enterUpdateTransition,
56
+ leave: enterUpdateTransition
57
+ })
61
58
 
62
59
  return (
63
60
  <>
@@ -137,7 +134,7 @@ export default function PieChart() {
137
134
  } else {
138
135
  setFilteredData(undefined)
139
136
  }
140
- }, [seriesHighlight])
137
+ }, [seriesHighlight]) // eslint-disable-line
141
138
 
142
139
  return (
143
140
  <ErrorBoundary component='PieChart'>