@cdc/chart 4.23.3 → 4.23.5

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 (98) hide show
  1. package/dist/cdcchart.js +52543 -50830
  2. package/examples/feature/__data__/area-chart.json +56 -0
  3. package/examples/{planet-example-data.json → feature/__data__/planet-example-data.json} +3 -8
  4. package/examples/feature/__data__/planet-logaritmic-data.json +56 -0
  5. package/examples/feature/area/area-chart-category.json +240 -0
  6. package/examples/{area-chart.json → feature/area/area-chart-date.json} +70 -13
  7. package/examples/feature/bar/example-bar-chart.json +558 -0
  8. package/examples/{horizontal-chart-max-increase.json → feature/bar/horizontal-chart-max-increase.json} +10 -4
  9. package/examples/{horizontal-chart.json → feature/bar/horizontal-chart.json} +10 -4
  10. package/examples/{horizontal-stacked-bar-chart.json → feature/bar/horizontal-stacked-bar-chart.json} +7 -3
  11. package/examples/{planet-chart-horizontal-example-config.json → feature/bar/planet-chart-horizontal-example-config.json} +8 -3
  12. package/examples/feature/bar/planet-chart-logaritmic-config.json +170 -0
  13. package/examples/{planet-example-config.json → feature/bar/planet-example-config.json} +2 -2
  14. package/examples/{box-plot.json → feature/boxplot/boxplot.json} +7 -7
  15. package/examples/feature/boxplot/testing.csv +38 -0
  16. package/examples/feature/boxplot/valid-boxplot.csv +17 -0
  17. package/examples/feature/combo/combochart-categories_are_numbers .json +18 -0
  18. package/examples/{planet-combo-example-config.json → feature/combo/planet-combo-example-config.json} +1 -1
  19. package/examples/{planet-deviation-config.json → feature/deviation/planet-deviation-config.json} +2 -2
  20. package/examples/{planet-deviation-data.json → feature/deviation/planet-deviation-data.json} +9 -9
  21. package/examples/feature/filters/filter-testing.json +212 -0
  22. package/examples/feature/forecasting/case_date_example.csv +130 -0
  23. package/examples/feature/forecasting/effective_reproduction.json +202 -0
  24. package/examples/feature/forecasting/r_data.csv +130 -0
  25. package/examples/feature/forecasting/random_data.csv +366 -0
  26. package/examples/feature/line/line-chart.json +124 -0
  27. package/examples/{paired-bar-example.json → feature/paired-bar/paired-bar-example.json} +10 -4
  28. package/examples/{planet-pie-example-config.json → feature/pie/planet-pie-example-config.json} +2 -2
  29. package/examples/{scatterplot.json → feature/scatterplot/scatterplot.json} +1 -1
  30. package/examples/feature/test-highlight/test-highlight-2.json +789 -0
  31. package/examples/feature/test-highlight/test-highlight-vertical.json +561 -0
  32. package/examples/feature/test-highlight/test-highlight.json +100 -0
  33. package/examples/{case-rate-example-config.json → feature/tests-case-rate/case-rate-example-config.json} +2 -2
  34. package/examples/{covid-confidence-example-config.json → feature/tests-covid/covid-confidence-example-config.json} +8 -3
  35. package/examples/{covid-example-config.json → feature/tests-covid/covid-example-config.json} +7 -3
  36. package/examples/{cutoff-example-config.json → feature/tests-cutoff/cutoff-example-config.json} +7 -3
  37. package/examples/{date-exclusions-config.json → feature/tests-date-exclusions/date-exclusions-config.json} +2 -2
  38. package/examples/{example-bar-chart-nonnumeric.json → feature/tests-non-numerics/example-bar-chart-nonnumeric.json} +1 -1
  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} +1 -1
  41. package/examples/{stacked-vertical-bar-example-nonnumerics.json → feature/tests-non-numerics/stacked-vertical-bar-example-nonnumerics.json} +1 -2
  42. package/examples/gallery/bar-chart-horizontal/horizontal-highlight.json +345 -0
  43. package/examples/gallery/bar-chart-vertical/combo-line-chart.json +145 -7
  44. package/examples/gallery/paired-bar/paired-bar-chart.json +1 -0
  45. package/index.html +73 -49
  46. package/package.json +2 -2
  47. package/src/CdcChart.jsx +405 -40
  48. package/src/components/AreaChart.jsx +122 -80
  49. package/src/components/BarChart.jsx +126 -49
  50. package/src/components/BoxPlot.jsx +28 -20
  51. package/src/components/DataTable.jsx +7 -6
  52. package/src/components/DeviationBar.jsx +34 -34
  53. package/src/components/EditorPanel.jsx +1332 -352
  54. package/src/components/Legend.jsx +40 -4
  55. package/src/components/LineChart.jsx +10 -23
  56. package/src/components/LinearChart.jsx +133 -286
  57. package/src/components/PairedBarChart.jsx +6 -6
  58. package/src/components/PieChart.jsx +2 -4
  59. package/src/components/SparkLine.jsx +6 -42
  60. package/src/data/initial-state.js +23 -4
  61. package/src/hooks/useHighlightedBars.js +154 -0
  62. package/src/hooks/useMinMax.js +92 -0
  63. package/src/hooks/useReduceData.js +31 -57
  64. package/src/hooks/useScales.js +202 -0
  65. package/src/index.jsx +2 -1
  66. package/src/scss/editor-panel.scss +15 -0
  67. package/src/scss/main.scss +8 -6
  68. package/examples/box-plot.csv +0 -5
  69. package/examples/dynamic-legends.json +0 -125
  70. package/examples/example-bar-chart.json +0 -36
  71. package/examples/line-chart.json +0 -34
  72. package/examples/temp-example-config.json +0 -64
  73. package/examples/temp-example-data.json +0 -130
  74. package/src/components/Filters.jsx +0 -126
  75. /package/examples/{age-adjusted-rates.json → feature/__data__/age-adjusted-rates.json} +0 -0
  76. /package/examples/{new-data.csv → feature/__data__/new-data.csv} +0 -0
  77. /package/examples/{planet-example-data-max-increase.json → feature/__data__/planet-example-data-max-increase.json} +0 -0
  78. /package/examples/{Barchart_with_negative.json → feature/bar/Barchart_with_negative.json} +0 -0
  79. /package/examples/{stacked-vertical-bar-example-negative.json → feature/bar/stacked-vertical-bar-example-negative.json} +0 -0
  80. /package/examples/{stacked-vertical-bar-example.json → feature/bar/stacked-vertical-bar-example.json} +0 -0
  81. /package/examples/{box-plot-data.json → feature/boxplot/box-plot-data.json} +0 -0
  82. /package/examples/{newdata.json → feature/boxplot/boxplot-data.json} +0 -0
  83. /package/examples/{line-chart-max-increase.json → feature/line/line-chart-max-increase.json} +0 -0
  84. /package/examples/{paired-bar-data.json → feature/paired-bar/paired-bar-data.json} +0 -0
  85. /package/examples/{paired-bar-formatted.json → feature/paired-bar/paired-bar-formatted.json} +0 -0
  86. /package/examples/{scatterplot-continuous.csv → feature/scatterplot/scatterplot-continuous.csv} +0 -0
  87. /package/examples/{example-sparkline.json → feature/sparkline/example-sparkline.json} +0 -0
  88. /package/examples/{big-small-test-bar.json → feature/tests-big-small/big-small-test-bar.json} +0 -0
  89. /package/examples/{big-small-test-line.json → feature/tests-big-small/big-small-test-line.json} +0 -0
  90. /package/examples/{big-small-test-negative.json → feature/tests-big-small/big-small-test-negative.json} +0 -0
  91. /package/examples/{case-rate-example-data.json → feature/tests-case-rate/case-rate-example-data.json} +0 -0
  92. /package/examples/{covid-example-data-confidence.json → feature/tests-covid/covid-example-data-confidence.json} +0 -0
  93. /package/examples/{covid-example-data.json → feature/tests-covid/covid-example-data.json} +0 -0
  94. /package/examples/{cutoff-example-data.json → feature/tests-cutoff/cutoff-example-data.json} +0 -0
  95. /package/examples/{date-exclusions-data.json → feature/tests-date-exclusions/date-exclusions-data.json} +0 -0
  96. /package/examples/{example-combo-bar-nonnumeric.json → feature/tests-non-numerics/example-combo-bar-nonnumeric.json} +0 -0
  97. /package/examples/{line-chart-nonnumeric.json → feature/tests-non-numerics/line-chart-nonnumeric.json} +0 -0
  98. /package/examples/{planet-example-data-nonnumeric.json → feature/tests-non-numerics/planet-example-data-nonnumeric.json} +0 -0
@@ -4,314 +4,85 @@ 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, scaleTime } from '@visx/scale'
8
7
  import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
9
8
 
10
- import CoveScatterPlot from './ScatterPlot'
11
9
  import BarChart from './BarChart'
12
- import LineChart from './LineChart'
13
10
  import ConfigContext from '../ConfigContext'
11
+ import CoveAreaChart from './AreaChart'
12
+ import CoveBoxPlot from './BoxPlot'
13
+ import CoveScatterPlot from './ScatterPlot'
14
+ import DeviationBar from './DeviationBar'
15
+ import LineChart from './LineChart'
14
16
  import PairedBarChart from './PairedBarChart'
15
17
  import useIntersectionObserver from './useIntersectionObserver'
16
- import CoveBoxPlot from './BoxPlot'
17
- import CoveAreaChart from './AreaChart'
18
18
 
19
19
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
20
20
  import '../scss/LinearChart.scss'
21
21
  import useReduceData from '../hooks/useReduceData'
22
+ import useScales from '../hooks/useScales'
23
+ import useMinMax from '../hooks/useMinMax'
22
24
  import useRightAxis from '../hooks/useRightAxis'
23
25
  import useTopAxis from '../hooks/useTopAxis'
24
- import { DeviationBar } from './DeviationBar'
25
26
 
26
- // TODO: Move scaling functions into hooks to manage complexity
27
27
  export default function LinearChart() {
28
- const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, stringFormattingOptions } = useContext(ConfigContext)
28
+ const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType } = useContext(ConfigContext)
29
29
 
30
- let [width] = dimensions
31
- const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
32
- const [animatedChart, setAnimatedChart] = useState(false)
33
-
34
- const triggerRef = useRef()
35
- const dataRef = useIntersectionObserver(triggerRef, {
36
- freezeOnceVisible: false
37
- })
38
-
39
- // Make sure the chart is visible if in the editor
40
- /* eslint-disable react-hooks/exhaustive-deps */
41
- useEffect(() => {
42
- const element = document.querySelector('.isEditor')
43
- if (element) {
44
- // parent element is visible
45
- setAnimatedChart(prevState => true)
46
- }
47
- }) /* eslint-disable-line */
48
-
49
- // If the chart is in view, set to animate if it has not already played
50
- useEffect(() => {
51
- if (dataRef?.isIntersecting === true && config.animate) {
52
- setTimeout(() => {
53
- setAnimatedChart(prevState => true)
54
- }, 500)
55
- }
56
- }, [dataRef?.isIntersecting, config.animate])
30
+ // getters & functions
31
+ const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
32
+ const getYAxisData = (d, seriesKey) => d[seriesKey]
33
+ const xAxisDataMapped = data.map(d => getXAxisData(d))
57
34
 
58
- if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && (currentViewport === 'lg' || currentViewport === 'md')) {
35
+ // configure width
36
+ let [width] = dimensions
37
+ if (config && config.legend && !config.legend.hide && config.legend.position !== 'bottom' && ['lg', 'md'].includes(currentViewport)) {
59
38
  width = width * 0.73
60
39
  }
40
+ // configure height , yMax, xMAx
61
41
  const { horizontal: heightHorizontal } = config.heights
42
+ const isHorizontal = config.orientation === 'horizontal'
43
+ const shouldAbbreviate = true
62
44
  const height = config.aspectRatio ? width * config.aspectRatio : config.heights[config.orientation]
63
45
  const xMax = width - config.runtime.yAxis.size - (config.visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
64
46
  const yMax = height - (config.orientation === 'horizontal' ? 0 : config.runtime.xAxis.size)
65
47
 
48
+ // hooks % states
49
+ const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
66
50
  const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
67
51
  const { hasTopAxis } = useTopAxis(config)
52
+ const [animatedChart, setAnimatedChart] = useState(false)
53
+ const properties = { data, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
54
+ const { min, max } = useMinMax(properties)
55
+ const { xScale, yScale, seriesScale, g1xScale, g2xScale } = useScales({ ...properties, min, max })
68
56
 
69
- const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
70
- const getYAxisData = (d, seriesKey) => d[seriesKey]
71
-
72
- let xScale
73
- let yScale
74
- let seriesScale
75
-
76
- const { max: enteredMaxValue, min: enteredMinValue } = config.runtime.yAxis
77
- const isMaxValid = existPositiveValue ? enteredMaxValue >= maxValue : enteredMaxValue >= 0
78
- const isMinValid = (enteredMinValue <= 0 && minValue >= 0) || (enteredMinValue <= minValue && minValue < 0)
79
-
80
- let max = 0 // need outside the if statement
81
- let min = 0
82
- if (data) {
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
- }
109
-
110
- if ((config.visualizationType === 'Bar' || config.visualizationType === 'Deviation Bar' || (config.visualizationType === 'Combo' && !isAllLine)) && min > 0) {
111
- min = 0
112
- }
113
- if (config.visualizationType === 'Combo' && isAllLine) {
114
- if ((enteredMinValue === undefined || enteredMinValue === null || enteredMinValue === '') && min > 0) {
115
- min = 0
116
- }
117
- if (enteredMinValue) {
118
- const isMinValid = +enteredMinValue < minValue
119
- min = +enteredMinValue && isMinValid ? enteredMinValue : minValue
120
- }
121
- }
122
-
123
- if (config.visualizationType === 'Line') {
124
- const isMinValid = enteredMinValue < minValue
125
- min = enteredMinValue && isMinValid ? enteredMinValue : minValue
126
- }
127
- //If data value max wasn't provided, calculate it
128
- if (max === Number.MIN_VALUE) {
129
- // if all values in data are negative set max = 0
130
- max = existPositiveValue ? maxValue : 0
131
- }
132
-
133
- //Adds Y Axis data padding if applicable
134
- if (config.runtime.yAxis.paddingPercent) {
135
- let paddingValue = (max - min) * config.runtime.yAxis.paddingPercent
136
- min -= paddingValue
137
- max += paddingValue
138
- }
139
-
140
- let xAxisDataMapped = data.map(d => getXAxisData(d))
141
-
142
- if (config.isLollipopChart && config.yAxis.displayNumbersOnBar) {
143
- const dataKey = data.map(item => item[config.series[0].dataKey])
144
- const maxDataVal = Math.max(...dataKey).toString().length
145
-
146
- switch (true) {
147
- case maxDataVal > 8 && maxDataVal <= 12:
148
- max = max * 1.3
149
- break
150
- case maxDataVal > 4 && maxDataVal <= 7:
151
- max = max * 1.1
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
166
- }
167
- }
168
-
169
- if (config.runtime.horizontal) {
170
- xScale = scaleLinear({
171
- domain: [min, max],
172
- range: [0, xMax]
173
- })
174
-
175
- yScale =
176
- config.runtime.xAxis.type === 'date'
177
- ? scaleLinear({
178
- domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)]
179
- })
180
- : scalePoint({ domain: xAxisDataMapped, padding: 0.5 })
181
-
182
- seriesScale = scalePoint({
183
- domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
184
- range: [0, yMax]
185
- })
186
-
187
- yScale.rangeRound([0, yMax])
188
- } else {
189
- min = min < 0 ? min * 1.11 : min
190
-
191
- yScale = scaleLinear({
192
- domain: [min, max],
193
- range: [yMax, 0]
194
- })
195
-
196
- xScale = scalePoint({
197
- domain: xAxisDataMapped,
198
- range: [0, xMax],
199
- padding: 0.5
200
- })
201
-
202
- seriesScale = scalePoint({
203
- domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
204
- range: [0, xMax]
205
- })
206
- }
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
-
215
- if (config.visualizationType === 'Paired Bar') {
216
- const offset = 1.02 // Offset of the ticks/values from the Axis
217
- let groupOneMax = Math.max.apply(
218
- Math,
219
- data.map(d => d[config.series[0].dataKey])
220
- )
221
- let groupTwoMax = Math.max.apply(
222
- Math,
223
- data.map(d => d[config.series[1].dataKey])
224
- )
225
-
226
- // group one
227
- var g1xScale = scaleLinear({
228
- domain: [0, Math.max(groupOneMax, groupTwoMax) * offset],
229
- range: [xMax / 2, 0]
230
- })
231
-
232
- // group 2
233
- var g2xScale = scaleLinear({
234
- domain: g1xScale.domain(),
235
- range: [xMax / 2, xMax],
236
- nice: true
237
- })
238
- }
239
-
240
- if (config.visualizationType === 'Scatter Plot') {
241
- if (config.xAxis.type === 'continuous') {
242
- xScale = scaleLinear({
243
- domain: [0, Math.max.apply(null, xScale.domain())],
244
- range: [0, xMax]
245
- })
246
- }
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
- }
297
- }
57
+ // refs
58
+ const triggerRef = useRef()
59
+ const svgRef = useRef()
60
+ const dataRef = useIntersectionObserver(triggerRef, {
61
+ freezeOnceVisible: false
62
+ })
298
63
 
299
64
  const handleLeftTickFormatting = tick => {
65
+ if (config.useLogScale && tick === 0.1) {
66
+ //when logaritmic scale applyed change value of FIRST tick
67
+ tick = 0
68
+ }
300
69
  if (config.runtime.yAxis.type === 'date') return formatDate(parseDate(tick))
301
- if (config.orientation === 'vertical') return formatNumber(tick, 'left')
70
+ if (config.orientation === 'vertical') return formatNumber(tick, 'left', shouldAbbreviate)
302
71
  return tick
303
72
  }
304
73
 
305
74
  const handleBottomTickFormatting = tick => {
75
+ if (config.useLogScale && tick === 0.1) {
76
+ // when logaritmic scale applyed change value FIRST of tick
77
+ tick = 0
78
+ }
306
79
  if (config.runtime.xAxis.type === 'date') return formatDate(tick)
307
- if (config.orientation === 'horizontal') return formatNumber(tick, 'left')
308
- if (config.xAxis.type === 'continuous') return formatNumber(tick, 'bottom')
80
+ if (config.orientation === 'horizontal') return formatNumber(tick, 'left', shouldAbbreviate)
81
+ if (config.xAxis.type === 'continuous') return formatNumber(tick, 'bottom', shouldAbbreviate)
309
82
  return tick
310
83
  }
311
84
 
312
85
  const countNumOfTicks = axis => {
313
- // function get number of ticks based on bar type & users value
314
- const isHorizontal = config.orientation === 'horizontal'
315
86
  const { numTicks } = config.runtime[axis]
316
87
  let tickCount = undefined
317
88
 
@@ -347,11 +118,32 @@ export default function LinearChart() {
347
118
  return tickCount
348
119
  }
349
120
 
121
+ // Make sure the chart is visible if in the editor
122
+ /* eslint-disable react-hooks/exhaustive-deps */
123
+ useEffect(() => {
124
+ const element = document.querySelector('.isEditor')
125
+ if (element) {
126
+ // parent element is visible
127
+ setAnimatedChart(prevState => true)
128
+ }
129
+ }) /* eslint-disable-line */
130
+
131
+ // If the chart is in view, set to animate if it has not already played
132
+ useEffect(() => {
133
+ if (dataRef?.isIntersecting === true && config.animate) {
134
+ setTimeout(() => {
135
+ setAnimatedChart(prevState => true)
136
+ }, 500)
137
+ }
138
+ }, [dataRef?.isIntersecting, config.animate])
139
+
140
+ const { orientation, xAxis, yAxis } = config
141
+
350
142
  return isNaN(width) ? (
351
143
  <></>
352
144
  ) : (
353
145
  <ErrorBoundary component='LinearChart'>
354
- <svg width={width} height={height} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''}`} role='img' aria-label={handleChartAriaLabels(config)} tabIndex={0}>
146
+ <svg width={width} height={height} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''}`} role='img' aria-label={handleChartAriaLabels(config)} tabIndex={0} ref={svgRef}>
355
147
  {/* Higlighted regions */}
356
148
  {config.regions
357
149
  ? config.regions.map(region => {
@@ -398,7 +190,7 @@ export default function LinearChart() {
398
190
 
399
191
  {/* Y axis */}
400
192
  {config.visualizationType !== 'Spark Line' && (
401
- <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')}>
193
+ <AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(config.runtime.yAxis.size) - config.yAxis.axisPadding} label={config.runtime.yAxis.label} stroke='#333' tickFormat={tick => handleLeftTickFormatting(tick)} numTicks={countNumOfTicks('yAxis')}>
402
194
  {props => {
403
195
  const axisCenter = config.runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
404
196
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
@@ -407,12 +199,15 @@ export default function LinearChart() {
407
199
  {props.ticks.map((tick, i) => {
408
200
  const minY = props.ticks[0].to.y
409
201
  const barMinHeight = 15 // 15 is the min height for bars by default
202
+ const showTicks = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
203
+ const tickLength = showTicks === 'block' ? 7 : 0
204
+ const to = { x: tick.to.x - tickLength, y: tick.to.y }
410
205
 
411
206
  return (
412
207
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
413
- {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke={config.yAxis.tickColor} display={config.runtime.horizontal ? 'none' : 'block'} />}
208
+ {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={config.useLogScale ? to : tick.to} stroke={config.yAxis.tickColor} display={config.runtime.horizontal ? 'none' : 'block'} />}
414
209
 
415
- {config.runtime.yAxis.gridLines ? <Line from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
210
+ {config.runtime.yAxis.gridLines ? <Line display={config.useLogScale && showTicks} from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
416
211
 
417
212
  {config.orientation === 'horizontal' && config.visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
418
213
  <Text
@@ -441,8 +236,10 @@ export default function LinearChart() {
441
236
  </Text>
442
237
  )}
443
238
 
444
- {config.orientation !== 'horizontal' && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
239
+ {config.orientation === 'vertical' && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
445
240
  <Text
241
+ display={config.useLogScale ? showTicks : 'block'}
242
+ dx={config.useLogScale ? -6 : 0}
446
243
  x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
447
244
  y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
448
245
  verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
@@ -457,6 +254,7 @@ export default function LinearChart() {
457
254
  })}
458
255
  {!config.yAxis.hideAxis && <Line from={props.axisFromPoint} to={config.runtime.horizontal ? { x: 0, y: Number(heightHorizontal) } : props.axisToPoint} stroke='#000' />}
459
256
  {yScale.domain()[0] < 0 && <Line from={{ x: props.axisFromPoint.x, y: yScale(0) }} to={{ x: xMax, y: yScale(0) }} stroke='#333' />}
257
+ {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} />}
460
258
  <Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${-1 * config.runtime.yAxis.size}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.labelColor}>
461
259
  {props.label}
462
260
  </Text>
@@ -529,12 +327,19 @@ export default function LinearChart() {
529
327
  return (
530
328
  <Group className='bottom-axis'>
531
329
  {props.ticks.map((tick, i) => {
330
+ // when using LogScale show major ticks values only
331
+ const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
532
332
  const tickWidth = xMax / props.ticks.length
333
+ const tickLength = showTick === 'block' ? 16 : 8
334
+ const to = { x: tick.to.x, y: tickLength }
335
+
533
336
  return (
534
337
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
535
- {!config.xAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke={config.xAxis.tickColor} />}
338
+ {!config.xAxis.hideTicks && <Line from={tick.from} to={config.orientation === 'horizontal' && config.useLogScale ? to : tick.to} stroke={config.xAxis.tickColor} strokeWidth={showTick === 'block' ? 1.3 : 1} />}
536
339
  {!config.xAxis.hideLabel && (
537
340
  <Text
341
+ dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
342
+ display={config.orientation === 'horizontal' && config.useLogScale ? showTick : 'block'}
538
343
  transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${!config.runtime.horizontal ? config.runtime.xAxis.tickRotation : 0})`}
539
344
  verticalAnchor='start'
540
345
  textAnchor={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? 'end' : 'middle'}
@@ -571,7 +376,7 @@ export default function LinearChart() {
571
376
  {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
572
377
  {!config.runtime.yAxis.hideLabel && (
573
378
  <Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
574
- {formatNumber(tick.formattedValue)}
379
+ {formatNumber(tick.value, 'left')}
575
380
  </Text>
576
381
  )}
577
382
  </Group>
@@ -604,7 +409,7 @@ export default function LinearChart() {
604
409
  {!config.runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
605
410
  {!config.runtime.yAxis.hideLabel && (
606
411
  <Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
607
- {tick.formattedValue}
412
+ {formatNumber(tick.value, 'left')}
608
413
  </Text>
609
414
  )}
610
415
  </Group>
@@ -625,11 +430,13 @@ export default function LinearChart() {
625
430
  )}
626
431
 
627
432
  {config.visualizationType === 'Deviation Bar' && <DeviationBar xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
628
-
629
- {/* Paired Bar chart */}
630
433
  {config.visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
434
+ {config.visualizationType === 'Scatter Plot' && <CoveScatterPlot xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />}
435
+ {config.visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
436
+ {(config.visualizationType === 'Area Chart' || config.visualizationType === 'Combo') && <CoveAreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} />}
631
437
 
632
438
  {/* Bar chart */}
439
+ {/* TODO: Make this just bar or combo? */}
633
440
  {config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Area Chart' && config.visualizationType !== 'Scatter Plot' && config.visualizationType !== 'Deviation Bar' && (
634
441
  <>
635
442
  <BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} animatedChart={animatedChart} visible={animatedChart} />
@@ -637,18 +444,58 @@ export default function LinearChart() {
637
444
  )}
638
445
 
639
446
  {/* Line chart */}
447
+ {/* TODO: Make this just line or combo? */}
640
448
  {config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Area Chart' && config.visualizationType !== 'Scatter Plot' && config.visualizationType !== 'Deviation Bar' && (
641
449
  <>
642
450
  <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
643
451
  </>
644
452
  )}
645
453
 
646
- {/* Scatter Plot chart */}
647
- {config.visualizationType === 'Scatter Plot' && <CoveScatterPlot xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />}
648
-
649
- {/* Box Plot chart */}
650
- {config.visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
651
- {config.visualizationType === 'Area Chart' && <CoveAreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} />}
454
+ {/* y anchors */}
455
+ {config.yAxis.anchors &&
456
+ config.yAxis.anchors.map(anchor => {
457
+ let anchorPosition = yScale(anchor.value)
458
+ const padding = config.orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
459
+ const middleOffset = config.orientation === 'horizontal' && config.visualizationType === 'Bar' ? config.barHeight / 4 : 0
460
+
461
+ return (
462
+ // prettier-ignore
463
+ <Line
464
+ key={anchor.value}
465
+ strokeDasharray={handleLineType(anchor.lineStyle)}
466
+ stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
467
+ className='anchor-y'
468
+ from={{ x: 0 + padding, y: anchorPosition - middleOffset}}
469
+ to={{ x: width, y: anchorPosition - middleOffset }}
470
+ />
471
+ )
472
+ })}
473
+
474
+ {/* x anchors */}
475
+ {config.xAxis.anchors &&
476
+ config.xAxis.anchors.map(anchor => {
477
+ let newX = xAxis
478
+ if (orientation === 'horizontal') {
479
+ newX = yAxis
480
+ }
481
+
482
+ let anchorPosition = newX.type === 'date' ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
483
+
484
+ const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
485
+
486
+ return (
487
+ // prettier-ignore
488
+ <Line
489
+ key={anchor.value}
490
+ strokeDasharray={handleLineType(anchor.lineStyle)}
491
+ stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
492
+ fill={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
493
+ className='anchor-x'
494
+ from={{ x: Number(anchorPosition) + Number(padding), y: 0 }}
495
+ to={{ x: Number(anchorPosition) + Number(padding), y: yMax }}
496
+ />
497
+ )
498
+ })}
652
499
  </svg>
653
500
  <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' />
654
501
  <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,7 +69,7 @@ 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
75
 
@@ -99,7 +99,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
99
99
  const totalheight = (Number(config.barSpace) + barHeight + borderWidth) * data.length
100
100
  config.heights.horizontal = totalheight
101
101
  // check if text fits inside of the bar including suffix/prefix,comma,fontSize ..etc
102
- 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`)
103
103
  const textFits = textWidth < barWidth - 5 // minus padding dx(5)
104
104
 
105
105
  return (
@@ -123,7 +123,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
123
123
  />
124
124
  {config.yAxis.displayNumbersOnBar && displayBar && (
125
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'}>
126
- {formatNumber(d[groupOne.dataKey])}
126
+ {formatNumber(d[groupOne.dataKey], 'left')}
127
127
  </Text>
128
128
  )}
129
129
  </Group>
@@ -143,7 +143,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
143
143
  const totalheight = (Number(config.barSpace) + barHeight + borderWidth) * data.length
144
144
  config.heights.horizontal = totalheight
145
145
  // check if text fits inside of the bar including suffix/prefix,comma,fontSize ..etc
146
- 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`)
147
147
  const isTextFits = textWidth < barWidth - 5 // minus padding dx(5)
148
148
 
149
149
  return (
@@ -174,7 +174,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
174
174
  />
175
175
  {config.yAxis.displayNumbersOnBar && displayBar && (
176
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'}>
177
- {formatNumber(d[groupTwo.dataKey])}
177
+ {formatNumber(d[groupTwo.dataKey], 'left')}
178
178
  </Text>
179
179
  )}
180
180
  </Group>
@@ -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)
@@ -140,7 +138,7 @@ export default function PieChart() {
140
138
  <ErrorBoundary component='PieChart'>
141
139
  <svg width={width} height={height} className={`animated-pie group ${config.animate === false || animatedPie ? 'animated' : ''}`} role='img' aria-label={handleChartAriaLabels(config)}>
142
140
  <Group top={centerY} left={centerX}>
143
- <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}>
144
142
  {pie => <AnimatedPie {...pie} getKey={d => d.data[config.runtime.xAxis.dataKey]} />}
145
143
  </Pie>
146
144
  </Group>