@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.
- package/dist/cdcchart.js +42292 -40337
- package/examples/feature/__data__/area-chart.json +56 -0
- package/examples/{planet-example-data.json → feature/__data__/planet-example-data-max-increase.json} +4 -4
- package/examples/feature/__data__/planet-example-data.json +68 -0
- package/examples/feature/area/area-chart.json +244 -0
- package/examples/{example-bar-chart.json → feature/bar/example-bar-chart.json} +4 -1
- package/examples/feature/bar/horizontal-chart-max-increase.json +44 -0
- package/examples/{horizontal-chart.json → feature/bar/horizontal-chart.json} +10 -4
- package/examples/{horizontal-stacked-bar-chart.json → feature/bar/horizontal-stacked-bar-chart.json} +7 -3
- package/examples/{planet-chart-horizontal-example-config.json → feature/bar/planet-chart-horizontal-example-config.json} +8 -3
- package/examples/feature/bar/planet-example-config.json +156 -0
- package/examples/{box-plot.json → feature/boxplot/boxplot.json} +7 -8
- package/examples/feature/boxplot/testing.csv +38 -0
- package/examples/feature/combo/combochart-categories_are_numbers .json +18 -0
- package/examples/{planet-combo-example-config.json → feature/combo/planet-combo-example-config.json} +1 -1
- package/examples/feature/deviation/planet-deviation-config.json +168 -0
- package/examples/feature/deviation/planet-deviation-data.json +38 -0
- package/examples/feature/filters/filter-testing.json +178 -0
- package/examples/feature/forecasting/case_date_example.csv +130 -0
- package/examples/feature/forecasting/effective_reproduction.json +202 -0
- package/examples/feature/forecasting/r_data.csv +130 -0
- package/examples/feature/line/line-chart-max-increase.json +32 -0
- package/examples/feature/line/line-chart.json +124 -0
- package/examples/{paired-bar-example.json → feature/paired-bar/paired-bar-example.json} +10 -4
- package/examples/{planet-pie-example-config.json → feature/pie/planet-pie-example-config.json} +2 -2
- package/examples/{scatterplot-continuous.csv → feature/scatterplot/scatterplot-continuous.csv} +3 -3
- package/examples/{scatterplot.json → feature/scatterplot/scatterplot.json} +3 -3
- package/examples/feature/sparkline/example-sparkline.json +76 -0
- package/examples/feature/tests-big-small/big-small-test-bar.json +328 -0
- package/examples/feature/tests-big-small/big-small-test-line.json +328 -0
- package/examples/feature/tests-big-small/big-small-test-negative.json +328 -0
- package/examples/{case-rate-example-config.json → feature/tests-case-rate/case-rate-example-config.json} +2 -2
- package/examples/{covid-confidence-example-config.json → feature/tests-covid/covid-confidence-example-config.json} +8 -3
- package/examples/{covid-example-config.json → feature/tests-covid/covid-example-config.json} +7 -3
- package/examples/{cutoff-example-config.json → feature/tests-cutoff/cutoff-example-config.json} +7 -3
- package/examples/{date-exclusions-config.json → feature/tests-date-exclusions/date-exclusions-config.json} +2 -2
- package/examples/{example-bar-chart-nonnumeric.json → feature/tests-non-numerics/example-bar-chart-nonnumeric.json} +1 -1
- package/examples/{line-chart-nonnumeric.json → feature/tests-non-numerics/line-chart-nonnumeric.json} +5 -5
- package/examples/{planet-pie-example-config-nonnumeric.json → feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json} +2 -2
- package/examples/{sparkline-chart-nonnumeric.json → feature/tests-non-numerics/sparkline-chart-nonnumeric.json} +2 -2
- package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +31 -172
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +145 -7
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-confidence.json +1 -0
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +96 -14
- package/examples/gallery/line/line.json +1 -0
- package/examples/gallery/paired-bar/paired-bar-chart.json +1 -0
- package/index.html +76 -35
- package/package.json +6 -3
- package/src/CdcChart.jsx +245 -106
- package/src/components/AreaChart.jsx +233 -0
- package/src/components/BarChart.jsx +103 -62
- package/src/components/BoxPlot.jsx +39 -18
- package/src/components/DataTable.jsx +26 -21
- package/src/components/DeviationBar.jsx +191 -0
- package/src/components/EditorPanel.jsx +662 -298
- package/src/components/Legend.jsx +59 -46
- package/src/components/LineChart.jsx +12 -36
- package/src/components/LinearChart.jsx +163 -64
- package/src/components/PairedBarChart.jsx +6 -7
- package/src/components/PieChart.jsx +12 -17
- package/src/components/ScatterPlot.jsx +19 -16
- package/src/components/SparkLine.jsx +84 -118
- package/src/components/useIntersectionObserver.jsx +1 -1
- package/src/data/initial-state.js +27 -7
- package/src/hooks/useColorPalette.js +58 -48
- package/src/hooks/useReduceData.js +3 -4
- package/src/index.jsx +3 -2
- package/src/scss/editor-panel.scss +20 -0
- package/src/scss/main.scss +8 -6
- package/src/test/CdcChart.test.jsx +6 -0
- package/examples/box-plot.csv +0 -5
- package/examples/dynamic-legends.json +0 -125
- package/examples/line-chart.json +0 -34
- package/examples/planet-example-config.json +0 -37
- package/examples/temp-example-config.json +0 -64
- package/examples/temp-example-data.json +0 -130
- package/src/components/Filters.jsx +0 -125
- /package/examples/{age-adjusted-rates.json → feature/__data__/age-adjusted-rates.json} +0 -0
- /package/examples/{new-data.csv → feature/__data__/new-data.csv} +0 -0
- /package/examples/{Barchart_with_negative.json → feature/bar/Barchart_with_negative.json} +0 -0
- /package/examples/{stacked-vertical-bar-example-negative.json → feature/bar/stacked-vertical-bar-example-negative.json} +0 -0
- /package/examples/{stacked-vertical-bar-example.json → feature/bar/stacked-vertical-bar-example.json} +0 -0
- /package/examples/{box-plot-data.json → feature/boxplot/box-plot-data.json} +0 -0
- /package/examples/{newdata.json → feature/boxplot/boxplot-data.json} +0 -0
- /package/examples/{paired-bar-data.json → feature/paired-bar/paired-bar-data.json} +0 -0
- /package/examples/{paired-bar-formatted.json → feature/paired-bar/paired-bar-formatted.json} +0 -0
- /package/examples/{case-rate-example-data.json → feature/tests-case-rate/case-rate-example-data.json} +0 -0
- /package/examples/{covid-example-data-confidence.json → feature/tests-covid/covid-example-data-confidence.json} +0 -0
- /package/examples/{covid-example-data.json → feature/tests-covid/covid-example-data.json} +0 -0
- /package/examples/{cutoff-example-data.json → feature/tests-cutoff/cutoff-example-data.json} +0 -0
- /package/examples/{date-exclusions-data.json → feature/tests-date-exclusions/date-exclusions-data.json} +0 -0
- /package/examples/{example-combo-bar-nonnumeric.json → feature/tests-non-numerics/example-combo-bar-nonnumeric.json} +0 -0
- /package/examples/{planet-example-data-nonnumeric.json → feature/tests-non-numerics/planet-example-data-nonnumeric.json} +0 -0
- /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
|
-
|
|
77
|
-
|
|
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, '
|
|
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
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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.
|
|
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.
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
|
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
|
|
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(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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 ||
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
/>
|