@cdc/chart 1.3.4 → 9.22.9
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 +6 -6
- package/examples/cutoff-example-config.json +2 -0
- package/examples/cutoff-example-data.json +1 -1
- package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart-with-numbers-on-bar.json +198 -0
- package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +241 -0
- package/examples/gallery/bar-chart-horizontal/horizontal-stacked.json +248 -0
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +137 -0
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +80 -0
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +81 -0
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +68 -0
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +111 -0
- package/examples/gallery/lollipop/lollipop-style-horizontal.json +220 -0
- package/examples/gallery/paired-bar/paired-bar-chart.json +196 -0
- package/examples/horizontal-chart.json +3 -0
- package/examples/paired-bar-data.json +1 -1
- package/examples/paired-bar-example.json +2 -0
- package/examples/planet-combo-example-config.json +2 -0
- package/examples/planet-example-config.json +2 -2
- package/examples/planet-example-data.json +1 -1
- package/examples/planet-pie-example-config.json +2 -0
- package/examples/private/line-test-data.json +22 -0
- package/examples/private/line-test-two.json +216 -0
- package/examples/private/line-test.json +102 -0
- package/examples/stacked-vertical-bar-example.json +228 -0
- package/package.json +3 -3
- package/src/CdcChart.tsx +79 -47
- package/src/components/BarChart.tsx +82 -39
- package/src/components/DataTable.tsx +17 -10
- package/src/components/EditorPanel.js +233 -169
- package/src/components/LineChart.tsx +3 -0
- package/src/components/LinearChart.tsx +171 -77
- package/src/components/PairedBarChart.tsx +139 -42
- package/src/components/PieChart.tsx +31 -6
- package/src/components/SparkLine.js +4 -1
- package/src/components/useIntersectionObserver.tsx +32 -0
- package/src/data/initial-state.js +17 -7
- package/src/hooks/useReduceData.ts +50 -23
- package/src/index.html +5 -9
- package/src/scss/editor-panel.scss +34 -4
- package/src/scss/main.scss +165 -5
- package/src/components/BarStackVertical.js +0 -0
|
@@ -66,6 +66,9 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData }
|
|
|
66
66
|
strokeWidth={2}
|
|
67
67
|
strokeOpacity={1}
|
|
68
68
|
shapeRendering="geometricPrecision"
|
|
69
|
+
defined={(item,i) => {
|
|
70
|
+
return item[config.runtime.seriesLabels[seriesKey]] !== "";
|
|
71
|
+
}}
|
|
69
72
|
/>
|
|
70
73
|
</Group>
|
|
71
74
|
))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { Fragment, useContext, useEffect,useState } from 'react';
|
|
1
|
+
import React, { Fragment, useContext, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import ReactTooltip from 'react-tooltip';
|
|
3
3
|
|
|
4
4
|
import { Group } from '@visx/group';
|
|
@@ -11,17 +11,37 @@ import BarChart from './BarChart';
|
|
|
11
11
|
import LineChart from './LineChart';
|
|
12
12
|
import Context from '../context';
|
|
13
13
|
import PairedBarChart from './PairedBarChart';
|
|
14
|
+
import useIntersectionObserver from "./useIntersectionObserver";
|
|
14
15
|
import SparkLine from './SparkLine';
|
|
15
16
|
|
|
16
17
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
|
|
17
|
-
|
|
18
|
+
import numberFromString from '@cdc/core/helpers/numberFromString'
|
|
18
19
|
import '../scss/LinearChart.scss';
|
|
19
20
|
import useReduceData from '../hooks/useReduceData';
|
|
20
21
|
|
|
21
22
|
export default function LinearChart() {
|
|
22
|
-
const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport,formatNumber } = useContext<any>(Context);
|
|
23
|
+
const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels } = useContext<any>(Context);
|
|
23
24
|
let [ width ] = dimensions;
|
|
24
|
-
const {minValue,maxValue} = useReduceData(config,data)
|
|
25
|
+
const {minValue,maxValue,existPositiveValue} = useReduceData(config,data)
|
|
26
|
+
const [animatedChart, setAnimatedChart] = useState<boolean>((!config.animate));
|
|
27
|
+
const [animatedChartPlayed, setAnimatedChartPlayed] = useState<boolean>(false);
|
|
28
|
+
|
|
29
|
+
const triggerRef = useRef();
|
|
30
|
+
const dataRef = useIntersectionObserver(triggerRef, {
|
|
31
|
+
freezeOnceVisible: false
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// If the chart is in view and set to animate and it has not already played
|
|
35
|
+
if( dataRef?.isIntersecting && config.animate && ! animatedChartPlayed ) {
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
setAnimatedChart(true);
|
|
38
|
+
}, 500);
|
|
39
|
+
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
setAnimatedChartPlayed(!animatedChartPlayed);
|
|
42
|
+
}, 600);
|
|
43
|
+
}
|
|
44
|
+
|
|
25
45
|
if(config && config.legend && !config.legend.hide && (currentViewport === 'lg' || currentViewport === 'md')) {
|
|
26
46
|
width = width * 0.73;
|
|
27
47
|
}
|
|
@@ -38,16 +58,23 @@ export default function LinearChart() {
|
|
|
38
58
|
let yScale;
|
|
39
59
|
let seriesScale;
|
|
40
60
|
|
|
61
|
+
// desctructure users enetered value from initial state config.
|
|
62
|
+
const {max:enteredMaxValue,min:enteredMinValue} = config.runtime.yAxis;
|
|
63
|
+
// validation for for min/max that user entered;
|
|
64
|
+
const isMaxValid = existPositiveValue ? numberFromString(enteredMaxValue) >= numberFromString(maxValue) : numberFromString(enteredMaxValue) >= 0;
|
|
65
|
+
const isMinValid = ((numberFromString(enteredMinValue) <= 0 && numberFromString(minValue) >=0) || (numberFromString(enteredMinValue) <= minValue && minValue < 0));
|
|
66
|
+
|
|
41
67
|
if (data) {
|
|
42
|
-
let min =
|
|
43
|
-
let max =
|
|
68
|
+
let min = enteredMinValue && isMinValid ? enteredMinValue : minValue;
|
|
69
|
+
let max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE;
|
|
44
70
|
|
|
45
71
|
if((config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && min > 0) {
|
|
46
72
|
min = 0;
|
|
47
73
|
}
|
|
48
74
|
//If data value max wasn't provided, calculate it
|
|
49
75
|
if(max === Number.MIN_VALUE){
|
|
50
|
-
|
|
76
|
+
// if all values in data are negative set max = 0
|
|
77
|
+
max = existPositiveValue ? maxValue : 0;
|
|
51
78
|
}
|
|
52
79
|
|
|
53
80
|
//Adds Y Axis data padding if applicable
|
|
@@ -59,41 +86,65 @@ export default function LinearChart() {
|
|
|
59
86
|
|
|
60
87
|
let xAxisDataMapped = data.map(d => getXAxisData(d));
|
|
61
88
|
|
|
62
|
-
if(config.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
range: [0, yMax]
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
yScale.rangeRound([0, yMax]);
|
|
78
|
-
} else {
|
|
79
|
-
min = min < 0 ? min * 1.11 : min
|
|
80
|
-
|
|
81
|
-
yScale = scaleLinear<number>({
|
|
82
|
-
domain: [min, max],
|
|
83
|
-
range: [yMax, 0]
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
xScale = scalePoint<string>({domain: xAxisDataMapped, range: [0, xMax], padding: 0.5});
|
|
87
|
-
|
|
88
|
-
seriesScale = scalePoint<string>({
|
|
89
|
-
domain: (config.runtime.barSeriesKeys || config.runtime.seriesKeys),
|
|
90
|
-
range: [0, xMax]
|
|
91
|
-
});
|
|
89
|
+
if (config.isLollipopChart && config.yAxis.displayNumbersOnBar) {
|
|
90
|
+
const dataKey = data.map((item) => item[config.series[0].dataKey]);
|
|
91
|
+
const maxDataVal = Math.max(...dataKey).toString().length;
|
|
92
|
+
|
|
93
|
+
switch (true) {
|
|
94
|
+
case maxDataVal > 8 && maxDataVal <= 12:
|
|
95
|
+
max = max * 1.3;
|
|
96
|
+
break;
|
|
97
|
+
case maxDataVal > 4 && maxDataVal <= 7:
|
|
98
|
+
max = max * 1.1;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
92
101
|
}
|
|
93
102
|
|
|
103
|
+
if (config.runtime.horizontal) {
|
|
104
|
+
xScale = scaleLinear<number>({
|
|
105
|
+
domain: [min, max],
|
|
106
|
+
range: [0, xMax],
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
yScale =
|
|
110
|
+
config.runtime.xAxis.type === "date"
|
|
111
|
+
? scaleLinear<number>({
|
|
112
|
+
domain: [
|
|
113
|
+
Math.min(...xAxisDataMapped),
|
|
114
|
+
Math.max(...xAxisDataMapped),
|
|
115
|
+
],
|
|
116
|
+
})
|
|
117
|
+
: scalePoint<string>({ domain: xAxisDataMapped, padding: 0.5 });
|
|
118
|
+
|
|
119
|
+
seriesScale = scalePoint<string>({
|
|
120
|
+
domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
|
|
121
|
+
range: [0, yMax],
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
yScale.rangeRound([0, yMax]);
|
|
125
|
+
} else {
|
|
126
|
+
min = min < 0 ? min * 1.11 : min;
|
|
127
|
+
|
|
128
|
+
yScale = scaleLinear<number>({
|
|
129
|
+
domain: [min, max],
|
|
130
|
+
range: [yMax, 0],
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
xScale = scalePoint<string>({
|
|
134
|
+
domain: xAxisDataMapped,
|
|
135
|
+
range: [0, xMax],
|
|
136
|
+
padding: 0.5,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
seriesScale = scalePoint<string>({
|
|
140
|
+
domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
|
|
141
|
+
range: [0, xMax],
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
94
145
|
|
|
95
146
|
if(config.visualizationType === 'Paired Bar') {
|
|
96
|
-
|
|
147
|
+
|
|
97
148
|
|
|
98
149
|
let groupOneMax = Math.max.apply(Math, data.map(d => d[config.series[0].dataKey]))
|
|
99
150
|
let groupTwoMax = Math.max.apply(Math, data.map(d => d[config.series[1].dataKey]))
|
|
@@ -103,7 +154,7 @@ export default function LinearChart() {
|
|
|
103
154
|
domain: [0, Math.max(groupOneMax,groupTwoMax) ],
|
|
104
155
|
range: [xMax/2, 0]
|
|
105
156
|
})
|
|
106
|
-
|
|
157
|
+
|
|
107
158
|
// group 2
|
|
108
159
|
var g2xScale = scaleLinear<number>({
|
|
109
160
|
domain: g1xScale.domain(),
|
|
@@ -119,10 +170,18 @@ export default function LinearChart() {
|
|
|
119
170
|
ReactTooltip.rebuild();
|
|
120
171
|
});
|
|
121
172
|
|
|
122
|
-
|
|
123
|
-
return (
|
|
173
|
+
return isNaN(width) ? <></> : (
|
|
124
174
|
<ErrorBoundary component="LinearChart">
|
|
125
|
-
<svg
|
|
175
|
+
<svg
|
|
176
|
+
width={width}
|
|
177
|
+
height={height}
|
|
178
|
+
// If the chart is set to not replay the filter and has already animated, don't add the animated class
|
|
179
|
+
// className={`linear ${(config.animate) || (config.animateReplay && animatedChartPlayed) ? 'animated' : ''} ${animatedChart ? 'animate' : ''}`}
|
|
180
|
+
className={`linear ${(config.animate) ? 'animated' : ''} ${animatedChart ? 'animate' : ''}`}
|
|
181
|
+
role="img"
|
|
182
|
+
aria-label={handleChartAriaLabels(config)}
|
|
183
|
+
tabIndex={0}
|
|
184
|
+
>
|
|
126
185
|
{/* Higlighted regions */}
|
|
127
186
|
{ config.regions ? config.regions.map((region) => {
|
|
128
187
|
if(!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
|
|
@@ -173,7 +232,7 @@ export default function LinearChart() {
|
|
|
173
232
|
const axisCenter = config.runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2;
|
|
174
233
|
const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length * (1 - config.barThickness)) + 5;
|
|
175
234
|
const belowBarPaddingFromTop = 9;
|
|
176
|
-
return (
|
|
235
|
+
return (
|
|
177
236
|
<Group className="left-axis">
|
|
178
237
|
{props.ticks.map((tick, i) => {
|
|
179
238
|
return (
|
|
@@ -188,15 +247,16 @@ export default function LinearChart() {
|
|
|
188
247
|
stroke="#333"
|
|
189
248
|
display={config.runtime.horizontal ? 'none' : 'block'}
|
|
190
249
|
/>
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
250
|
+
)}
|
|
251
|
+
|
|
252
|
+
{ config.runtime.yAxis.gridLines ? (
|
|
253
|
+
<Line
|
|
254
|
+
from={{x: tick.from.x + xMax, y: tick.from.y}}
|
|
255
|
+
to={tick.from}
|
|
256
|
+
stroke="rgba(0,0,0,0.3)"
|
|
257
|
+
/>
|
|
258
|
+
) : ''
|
|
259
|
+
}
|
|
200
260
|
|
|
201
261
|
{( config.orientation === "horizontal" && config.visualizationSubType !== 'stacked') && (config.yAxis.labelPlacement === 'On Date/Category Axis' ) && !config.yAxis.hideLabel &&
|
|
202
262
|
// 17 is a magic number from the offset in barchart.
|
|
@@ -205,7 +265,7 @@ export default function LinearChart() {
|
|
|
205
265
|
transform={`translate(${tick.to.x - 5}, ${ config.isLollipopChart ? tick.from.y : tick.from.y - 17 }) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`}
|
|
206
266
|
verticalAnchor={ config.isLollipopChart ? "middle" : "middle"}
|
|
207
267
|
textAnchor={"end"}
|
|
208
|
-
>{
|
|
268
|
+
>{tick.formattedValue}</Text>
|
|
209
269
|
</Fragment>
|
|
210
270
|
}
|
|
211
271
|
|
|
@@ -215,7 +275,16 @@ export default function LinearChart() {
|
|
|
215
275
|
transform={`translate(${tick.to.x - 5}, ${ tick.from.y - config.barHeight / 2 - 3 }) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`}
|
|
216
276
|
verticalAnchor={ config.isLollipopChart ? "middle" : "middle"}
|
|
217
277
|
textAnchor={"end"}
|
|
218
|
-
>{
|
|
278
|
+
>{tick.formattedValue}</Text>
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
{ (config.orientation === "horizontal" && config.visualizationType === 'Paired Bar') && !config.yAxis.hideLabel &&
|
|
282
|
+
// 17 is a magic number from the offset in barchart.
|
|
283
|
+
<Text
|
|
284
|
+
transform={`translate(${-15}, ${ tick.from.y }) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`}
|
|
285
|
+
verticalAnchor={ config.isLollipopChart ? "middle" : "middle"}
|
|
286
|
+
textAnchor={"end"}
|
|
287
|
+
>{tick.formattedValue}</Text>
|
|
219
288
|
}
|
|
220
289
|
|
|
221
290
|
{ (config.orientation === "horizontal" && config.visualizationType === 'Paired Bar') && !config.yAxis.hideLabel &&
|
|
@@ -235,7 +304,7 @@ export default function LinearChart() {
|
|
|
235
304
|
verticalAnchor={config.runtime.horizontal ? "start" : "middle"}
|
|
236
305
|
textAnchor={config.runtime.horizontal ? 'start' : 'end'}
|
|
237
306
|
>
|
|
238
|
-
{
|
|
307
|
+
{tick.formattedValue}
|
|
239
308
|
</Text>
|
|
240
309
|
}
|
|
241
310
|
|
|
@@ -343,7 +412,7 @@ export default function LinearChart() {
|
|
|
343
412
|
top={yMax}
|
|
344
413
|
left={config.runtime.yAxis.size}
|
|
345
414
|
label={config.runtime.xAxis.label}
|
|
346
|
-
tickFormat={config.runtime.xAxis.type === 'date' ? formatDate :
|
|
415
|
+
tickFormat={config.runtime.xAxis.type === 'date' ? formatDate :formatNumber}
|
|
347
416
|
scale={g1xScale}
|
|
348
417
|
stroke="#333"
|
|
349
418
|
tickStroke="#333"
|
|
@@ -395,7 +464,7 @@ export default function LinearChart() {
|
|
|
395
464
|
top={yMax}
|
|
396
465
|
left={config.runtime.yAxis.size}
|
|
397
466
|
label={config.runtime.xAxis.label}
|
|
398
|
-
tickFormat={config.runtime.xAxis.type === 'date' ? formatDate : (tick)
|
|
467
|
+
tickFormat={config.runtime.xAxis.type === 'date' ? formatDate : config.runtime.xAxis.dataKey !=='Year'? formatNumber : (tick)=>tick}
|
|
399
468
|
scale={g2xScale}
|
|
400
469
|
stroke="#333"
|
|
401
470
|
tickStroke="#333"
|
|
@@ -404,6 +473,7 @@ export default function LinearChart() {
|
|
|
404
473
|
{props => {
|
|
405
474
|
const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2;
|
|
406
475
|
return (
|
|
476
|
+
<>
|
|
407
477
|
<Group className="bottom-axis">
|
|
408
478
|
{props.ticks.map((tick, i) => {
|
|
409
479
|
const tickWidth = xMax / props.ticks.length;
|
|
@@ -412,28 +482,45 @@ export default function LinearChart() {
|
|
|
412
482
|
key={`vx-tick-${tick.value}-${i}`}
|
|
413
483
|
className={'vx-axis-tick'}
|
|
414
484
|
>
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
485
|
+
{!config.runtime.yAxis.hideTicks &&
|
|
486
|
+
<Line
|
|
487
|
+
from={tick.from}
|
|
488
|
+
to={tick.to}
|
|
489
|
+
stroke="#333"
|
|
490
|
+
/>
|
|
491
|
+
}
|
|
492
|
+
{!config.runtime.yAxis.hideLabel &&
|
|
493
|
+
<Text
|
|
494
|
+
transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${60})`}
|
|
495
|
+
verticalAnchor="start"
|
|
496
|
+
textAnchor={'end'}
|
|
497
|
+
width={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? undefined : tickWidth}
|
|
498
|
+
>
|
|
499
|
+
{tick.formattedValue}
|
|
500
|
+
</Text>
|
|
501
|
+
}
|
|
428
502
|
</Group>
|
|
429
503
|
);
|
|
430
504
|
})}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
505
|
+
{!config.runtime.yAxis.hideAxis &&
|
|
506
|
+
<Line
|
|
507
|
+
from={props.axisFromPoint}
|
|
508
|
+
to={props.axisToPoint}
|
|
509
|
+
stroke="#333"
|
|
510
|
+
/>
|
|
511
|
+
}
|
|
436
512
|
</Group>
|
|
513
|
+
<Group>
|
|
514
|
+
<Text
|
|
515
|
+
transform={`translate(${xMax/2}, ${config.height - yMax + 20}) rotate(-${0})`}
|
|
516
|
+
verticalAnchor="start"
|
|
517
|
+
textAnchor={'middle'}
|
|
518
|
+
stroke="#333"
|
|
519
|
+
>
|
|
520
|
+
{config.runtime.xAxis.label}
|
|
521
|
+
</Text>
|
|
522
|
+
</Group>
|
|
523
|
+
</>
|
|
437
524
|
);
|
|
438
525
|
}}
|
|
439
526
|
</AxisBottom>
|
|
@@ -445,15 +532,22 @@ export default function LinearChart() {
|
|
|
445
532
|
|
|
446
533
|
{/* Bar chart */}
|
|
447
534
|
{ (config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar') && (
|
|
448
|
-
|
|
535
|
+
<>
|
|
536
|
+
<BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} animatedChart={animatedChart} visible={animatedChart} />
|
|
537
|
+
</>
|
|
538
|
+
|
|
449
539
|
)}
|
|
450
540
|
|
|
451
541
|
{/* Line chart */}
|
|
452
542
|
{ (config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar') && (
|
|
453
|
-
|
|
543
|
+
<>
|
|
544
|
+
<LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />
|
|
545
|
+
</>
|
|
546
|
+
|
|
454
547
|
)}
|
|
455
548
|
</svg>
|
|
456
549
|
<ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} html={true} type="light" arrowColor="rgba(0,0,0,0)" className="tooltip"/>
|
|
550
|
+
<div ref={triggerRef} />
|
|
457
551
|
</ErrorBoundary>
|
|
458
552
|
)
|
|
459
553
|
}
|
|
@@ -15,7 +15,7 @@ interface PairedBarChartProps {
|
|
|
15
15
|
|
|
16
16
|
const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
|
|
17
17
|
|
|
18
|
-
const { config, colorScale, transformedData } = useContext<any>(Context);
|
|
18
|
+
const { config, colorScale, transformedData, formatNumber, seriesHighlight } = useContext<any>(Context);
|
|
19
19
|
|
|
20
20
|
if(!config || config?.series?.length < 2) return;
|
|
21
21
|
|
|
@@ -49,7 +49,6 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
|
|
|
49
49
|
const yScale = scaleBand({
|
|
50
50
|
range: [0, adjustedHeight],
|
|
51
51
|
domain: data.map(d => d[config.dataDescription.xKey]),
|
|
52
|
-
padding: 0.2
|
|
53
52
|
});
|
|
54
53
|
|
|
55
54
|
// Set label color
|
|
@@ -63,75 +62,173 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
|
|
|
63
62
|
groupTwo.labelColor = '#FFFFFF';
|
|
64
63
|
}
|
|
65
64
|
|
|
65
|
+
const dataTipOne = (d) => {
|
|
66
|
+
return (
|
|
67
|
+
`<p>
|
|
68
|
+
${config.dataDescription.seriesKey}: ${groupOne.dataKey}<br/>
|
|
69
|
+
${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
|
|
70
|
+
${config.dataDescription.valueKey}: ${formatNumber(d[groupOne.dataKey])}
|
|
71
|
+
</p>`
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const dataTipTwo = (d) => {
|
|
76
|
+
return (
|
|
77
|
+
`<p>
|
|
78
|
+
${config.dataDescription.seriesKey}: ${groupTwo.dataKey}<br/>
|
|
79
|
+
${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
|
|
80
|
+
${config.dataDescription.valueKey}: ${formatNumber(d[groupTwo.dataKey])}
|
|
81
|
+
</p>`
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const isLabelBelowBar = config.yAxis.labelPlacement === "Below Bar";
|
|
86
|
+
const isLabelOnYAxis = config.yAxis.labelPlacement === "On Date/Category Axis";
|
|
87
|
+
const isLabelMissing = !config.yAxis.labelPlacement;
|
|
88
|
+
|
|
66
89
|
return (width > 0) && (
|
|
67
90
|
<>
|
|
91
|
+
<style>
|
|
92
|
+
{`
|
|
93
|
+
#cdc-visualization__paired-bar-chart,
|
|
94
|
+
#cdc-visualization__paired-bar-chart > .visx-group {
|
|
95
|
+
transform-origin: center
|
|
96
|
+
}
|
|
97
|
+
`}
|
|
98
|
+
</style>
|
|
68
99
|
<svg
|
|
69
100
|
id="cdc-visualization__paired-bar-chart"
|
|
70
101
|
width={width}
|
|
71
|
-
height={height}
|
|
72
|
-
|
|
73
|
-
|
|
102
|
+
height={height}
|
|
103
|
+
viewBox={`0 0 ${width} ${height}`}
|
|
104
|
+
role="img"
|
|
105
|
+
tabIndex={0}
|
|
106
|
+
>
|
|
107
|
+
<Group top={0} left={config.xAxis.size} >
|
|
108
|
+
{data.filter(item => config.series[0].dataKey === groupOne.dataKey).map( (d,index) => {
|
|
109
|
+
|
|
110
|
+
let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(config.series[0].dataKey) === -1;
|
|
111
|
+
let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(config.series[0].dataKey) !== -1;
|
|
74
112
|
let barWidth = (xScale(d[config.series[0].dataKey]))
|
|
113
|
+
let barHeight = Number(config.barHeight) ? Number(config.barHeight) : 25;
|
|
114
|
+
let barPadding = barHeight;
|
|
115
|
+
config.barHeight = Number(config.barHeight) ? Number(config.barHeight) : 25;
|
|
116
|
+
config.barPadding = config.barHeight;
|
|
117
|
+
|
|
118
|
+
if (config.orientation=== "horizontal") {
|
|
119
|
+
|
|
120
|
+
if(isLabelBelowBar || isLabelMissing || isLabelOnYAxis) {
|
|
121
|
+
if(barHeight < 40) {
|
|
122
|
+
config.barPadding = 40;
|
|
123
|
+
} else {
|
|
124
|
+
config.barPadding = barPadding;
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
config.barPadding = barPadding / 2;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
config.height = (Number(barHeight) ) * data.length + (config.barPadding * data.length);
|
|
132
|
+
|
|
133
|
+
let y = yScale([d[config.dataDescription.xKey]]) + config.barHeight/1.5;
|
|
134
|
+
y = Number(config.barPadding) > 20 ? y += Number(config.barPadding/3.5) - config.barHeight/2 : y += 0
|
|
135
|
+
|
|
75
136
|
return (
|
|
76
|
-
|
|
137
|
+
<>
|
|
138
|
+
<Group key={`group-${groupOne.dataKey}-${d[config.xAxis.dataKey]}`} className='horizontal'>
|
|
77
139
|
<Bar
|
|
78
|
-
|
|
140
|
+
id={`bar-${groupOne.dataKey}-${d[config.dataDescription.xKey]}`}
|
|
141
|
+
className='bar group-1'
|
|
79
142
|
key={`bar-${groupOne.dataKey}-${d[config.dataDescription.xKey]}`}
|
|
80
143
|
x={halfWidth - barWidth}
|
|
81
|
-
y={
|
|
144
|
+
y={ y }
|
|
82
145
|
width={xScale(d[config.series[0].dataKey])}
|
|
83
|
-
height={
|
|
146
|
+
height={barHeight}
|
|
84
147
|
fill={groupOne.color}
|
|
85
|
-
data-tip={
|
|
86
|
-
`<p>
|
|
87
|
-
${config.dataDescription.seriesKey}: ${groupOne.dataKey}<br/>
|
|
88
|
-
${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
|
|
89
|
-
${config.dataDescription.valueKey}: ${d[groupOne.dataKey]}
|
|
90
|
-
</p>`
|
|
91
|
-
}
|
|
148
|
+
data-tip={ dataTipOne(d) }
|
|
92
149
|
data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
150
|
+
stroke="#333"
|
|
151
|
+
strokeWidth={config.barBorderThickness || 1}
|
|
152
|
+
opacity={transparentBar ? 0.5 : 1}
|
|
153
|
+
display={displayBar ? 'block' : 'none'}
|
|
93
154
|
/>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
155
|
+
{config.yAxis.displayNumbersOnBar && displayBar &&
|
|
156
|
+
<Text
|
|
157
|
+
textAnchor={barWidth < 100 ? 'end' : 'start' }
|
|
158
|
+
verticalAnchor="middle"
|
|
159
|
+
x={halfWidth - (barWidth < 100 ? barWidth + 10 : barWidth - 5)}
|
|
160
|
+
y={ y + config.barHeight/2}
|
|
161
|
+
fill={barWidth > 100 ? groupOne.labelColor : '#000' }>
|
|
162
|
+
{formatNumber(d[groupOne.dataKey])}
|
|
163
|
+
</Text>
|
|
164
|
+
}
|
|
101
165
|
</Group>
|
|
166
|
+
</>
|
|
102
167
|
)}
|
|
103
168
|
)}
|
|
104
169
|
{data.filter(item => config.series[1].dataKey === groupTwo.dataKey).map(d => {
|
|
105
170
|
let barWidth = (xScale(d[config.series[1].dataKey]))
|
|
171
|
+
let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(config.series[1].dataKey) === -1;
|
|
172
|
+
let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(config.series[1].dataKey) !== -1;
|
|
173
|
+
|
|
174
|
+
let barHeight = config.barHeight ? config.barHeight : 25;
|
|
175
|
+
let barPadding = barHeight;
|
|
176
|
+
config.barHeight = Number(config.barHeight)
|
|
177
|
+
|
|
178
|
+
let y = yScale([d[config.dataDescription.xKey]]) + config.barHeight/1.5;
|
|
179
|
+
y = Number(config.barPadding) > 20 ? y += Number(config.barPadding/3.5) - config.barHeight/2 : y += 0
|
|
180
|
+
|
|
181
|
+
if (config.orientation=== "horizontal") {
|
|
182
|
+
|
|
183
|
+
if(isLabelBelowBar || isLabelMissing || isLabelOnYAxis) {
|
|
184
|
+
if(barHeight < 40) {
|
|
185
|
+
config.barPadding = 40;
|
|
186
|
+
} else {
|
|
187
|
+
config.barPadding = barPadding;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
config.barPadding = barPadding / 2;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
106
193
|
|
|
107
194
|
return(
|
|
108
|
-
|
|
195
|
+
<>
|
|
196
|
+
<style>
|
|
197
|
+
{`
|
|
198
|
+
.bar-${groupTwo.dataKey}-${d[config.xAxis.dataKey]} {
|
|
199
|
+
transform-origin: ${halfWidth}px ${y}px
|
|
200
|
+
}
|
|
201
|
+
`}
|
|
202
|
+
</style>
|
|
203
|
+
<Group key={`group-${groupTwo.dataKey}-${d[config.dataDescription.xKey]}`} className='horizontal'>
|
|
109
204
|
<Bar
|
|
110
|
-
|
|
205
|
+
id={`bar-${groupTwo.dataKey}-${d[config.dataDescription.xKey]}`}
|
|
206
|
+
className="bar group-2"
|
|
111
207
|
key={`bar-${groupTwo.dataKey}-${d[config.dataDescription.xKey]}`}
|
|
112
208
|
x={halfWidth}
|
|
113
|
-
y={
|
|
209
|
+
y={ y }
|
|
114
210
|
width={xScale(d[config.series[1].dataKey])}
|
|
115
|
-
height={
|
|
211
|
+
height={barHeight}
|
|
116
212
|
fill={groupTwo.color}
|
|
117
|
-
data-tip={
|
|
118
|
-
`<p>
|
|
119
|
-
${config.dataDescription.seriesKey}: ${groupTwo.dataKey}<br/>
|
|
120
|
-
${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
|
|
121
|
-
${config.dataDescription.valueKey}: ${d[groupTwo.dataKey]}
|
|
122
|
-
</p>`
|
|
123
|
-
}
|
|
213
|
+
data-tip={ dataTipTwo(d) }
|
|
124
214
|
data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
125
|
-
|
|
215
|
+
strokeWidth={config.barBorderThickness || 1}
|
|
216
|
+
stroke="#333"
|
|
217
|
+
opacity={transparentBar ? 0.5 : 1}
|
|
218
|
+
display={displayBar ? 'block' : 'none'}
|
|
126
219
|
/>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
220
|
+
{config.yAxis.displayNumbersOnBar && displayBar &&
|
|
221
|
+
<Text
|
|
222
|
+
textAnchor={barWidth < 100 ? 'start' : 'end' }
|
|
223
|
+
verticalAnchor="middle"
|
|
224
|
+
x={halfWidth + (barWidth < 100 ? barWidth + 10 : barWidth - 10 )}
|
|
225
|
+
y={ y + config.barHeight/2}
|
|
226
|
+
fill={barWidth > 100 ? groupTwo.labelColor : '#000' }>
|
|
227
|
+
{formatNumber(d[groupTwo.dataKey])}
|
|
228
|
+
</Text>
|
|
229
|
+
}
|
|
134
230
|
</Group>
|
|
231
|
+
</>
|
|
135
232
|
)
|
|
136
233
|
}
|
|
137
234
|
)}
|