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