@cdc/chart 1.3.2 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcchart.js +77 -4
- package/examples/age-adjusted-rates.json +1218 -0
- package/examples/case-rate-example-config.json +36 -0
- package/examples/case-rate-example-data.json +33602 -0
- package/examples/date-exclusions-config.json +62 -0
- package/examples/date-exclusions-data.json +162 -0
- package/examples/horizontal-chart.json +35 -0
- package/examples/horizontal-stacked-bar-chart.json +36 -0
- package/examples/line-chart.json +76 -0
- package/examples/paired-bar-data.json +14 -0
- package/examples/paired-bar-example.json +48 -0
- package/examples/paired-bar-formatted.json +37 -0
- package/examples/planet-chart-horizontal-example-config.json +35 -0
- package/examples/planet-example-config.json +1 -0
- package/examples/private/newtest.csv +101 -0
- package/examples/private/test.json +10124 -0
- package/package.json +9 -5
- package/src/CdcChart.tsx +417 -149
- package/src/components/BarChart.tsx +431 -24
- package/src/components/BarStackVertical.js +0 -0
- package/src/components/DataTable.tsx +55 -28
- package/src/components/EditorPanel.js +914 -260
- package/src/components/LineChart.tsx +4 -3
- package/src/components/LinearChart.tsx +258 -88
- package/src/components/PairedBarChart.tsx +144 -0
- package/src/components/PieChart.tsx +30 -16
- package/src/components/SparkLine.js +206 -0
- package/src/data/initial-state.js +59 -32
- package/src/hooks/useActiveElement.js +19 -0
- package/src/hooks/useColorPalette.ts +83 -0
- package/src/hooks/useReduceData.ts +43 -0
- package/src/index.html +49 -13
- package/src/index.tsx +6 -2
- package/src/scss/editor-panel.scss +12 -4
- package/src/scss/main.scss +112 -3
|
@@ -10,7 +10,7 @@ import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
|
|
|
10
10
|
import Context from '../context';
|
|
11
11
|
|
|
12
12
|
export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData }) {
|
|
13
|
-
const {
|
|
13
|
+
const { transformedData: data, colorScale, seriesHighlight, config, formatNumber,formatDate,parseDate } = useContext<any>(Context);
|
|
14
14
|
|
|
15
15
|
return (
|
|
16
16
|
<ErrorBoundary component="LineChart">
|
|
@@ -22,8 +22,9 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData }
|
|
|
22
22
|
display={config.legend.behavior === "highlight" || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
|
|
23
23
|
>
|
|
24
24
|
{ data.map((d, dataIndex) => {
|
|
25
|
+
const xAxisValue = config.runtime.xAxis.type==='date' ? formatDate(parseDate(d[config.runtime.xAxis.dataKey])) : d[config.runtime.xAxis.dataKey];
|
|
25
26
|
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${formatNumber(getYAxisData(d, seriesKey))}` : formatNumber(getYAxisData(d, seriesKey))
|
|
26
|
-
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${
|
|
27
|
+
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` :xAxisValue;
|
|
27
28
|
|
|
28
29
|
const tooltip = `<div>
|
|
29
30
|
${yAxisTooltip}<br />
|
|
@@ -35,7 +36,7 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData }
|
|
|
35
36
|
|
|
36
37
|
return (
|
|
37
38
|
<Group key={`series-${seriesKey}-point-${dataIndex}`}>
|
|
38
|
-
<Text
|
|
39
|
+
<Text
|
|
39
40
|
display={config.labels ? 'block' : 'none'}
|
|
40
41
|
x={xScale(getXAxisData(d))}
|
|
41
42
|
y={yScale(getYAxisData(d, seriesKey))}
|
|
@@ -1,24 +1,27 @@
|
|
|
1
|
-
import React, { useContext, useEffect } from 'react';
|
|
1
|
+
import React, { Fragment, useContext, useEffect,useState } from 'react';
|
|
2
2
|
import ReactTooltip from 'react-tooltip';
|
|
3
3
|
|
|
4
4
|
import { Group } from '@visx/group';
|
|
5
5
|
import { Line } from '@visx/shape';
|
|
6
|
-
import { Text } from '@visx/text';
|
|
6
|
+
import { Text } from '@visx/text';
|
|
7
7
|
import { scaleLinear, scalePoint } from '@visx/scale';
|
|
8
8
|
import { AxisLeft, AxisBottom } from '@visx/axis';
|
|
9
9
|
|
|
10
10
|
import BarChart from './BarChart';
|
|
11
11
|
import LineChart from './LineChart';
|
|
12
12
|
import Context from '../context';
|
|
13
|
+
import PairedBarChart from './PairedBarChart';
|
|
14
|
+
import SparkLine from './SparkLine';
|
|
13
15
|
|
|
14
16
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
|
|
15
17
|
|
|
16
18
|
import '../scss/LinearChart.scss';
|
|
19
|
+
import useReduceData from '../hooks/useReduceData';
|
|
17
20
|
|
|
18
21
|
export default function LinearChart() {
|
|
19
|
-
const {
|
|
22
|
+
const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport,formatNumber } = useContext<any>(Context);
|
|
20
23
|
let [ width ] = dimensions;
|
|
21
|
-
|
|
24
|
+
const {minValue,maxValue} = useReduceData(config,data)
|
|
22
25
|
if(config && config.legend && !config.legend.hide && (currentViewport === 'lg' || currentViewport === 'md')) {
|
|
23
26
|
width = width * 0.73;
|
|
24
27
|
}
|
|
@@ -36,39 +39,19 @@ export default function LinearChart() {
|
|
|
36
39
|
let seriesScale;
|
|
37
40
|
|
|
38
41
|
if (data) {
|
|
39
|
-
let min = config.runtime.yAxis.min !== undefined ? config.runtime.yAxis.min :
|
|
42
|
+
let min = config.runtime.yAxis.min !== undefined ? config.runtime.yAxis.min : minValue
|
|
40
43
|
let max = config.runtime.yAxis.max !== undefined ? config.runtime.yAxis.max : Number.MIN_VALUE;
|
|
41
44
|
|
|
42
45
|
if((config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && min > 0) {
|
|
43
46
|
min = 0;
|
|
44
47
|
}
|
|
45
|
-
|
|
46
48
|
//If data value max wasn't provided, calculate it
|
|
47
49
|
if(max === Number.MIN_VALUE){
|
|
48
|
-
|
|
49
|
-
if (config.visualizationType === 'Bar' && config.visualizationSubType === 'stacked') {
|
|
50
|
-
const yTotals = data.reduce((allTotals, xValue) => {
|
|
51
|
-
const totalYValues = config.runtime.seriesKeys.reduce((yTotal, k) => {
|
|
52
|
-
yTotal += Number(xValue[k]);
|
|
53
|
-
return yTotal;
|
|
54
|
-
}, 0);
|
|
55
|
-
allTotals.push(totalYValues);
|
|
56
|
-
if(totalYValues > max){
|
|
57
|
-
max = totalYValues;
|
|
58
|
-
}
|
|
59
|
-
return allTotals;
|
|
60
|
-
}, [] as number[]);
|
|
61
|
-
|
|
62
|
-
max = Math.max(...yTotals);
|
|
63
|
-
} else if(config.visualizationType === 'Bar' && config.confidenceKeys && config.confidenceKeys.upper) {
|
|
64
|
-
max = Math.max(...data.map((d) => Number(d[config.confidenceKeys.upper])));
|
|
65
|
-
} else {
|
|
66
|
-
max = Math.max(...data.map((d) => Math.max(...config.runtime.seriesKeys.map((key) => Number(d[key])))));
|
|
67
|
-
}
|
|
50
|
+
max = maxValue
|
|
68
51
|
}
|
|
69
|
-
|
|
52
|
+
|
|
70
53
|
//Adds Y Axis data padding if applicable
|
|
71
|
-
if(config.runtime.yAxis.paddingPercent) {
|
|
54
|
+
if(config.runtime.yAxis.paddingPercent) {
|
|
72
55
|
let paddingValue = (max - min) * config.runtime.yAxis.paddingPercent;
|
|
73
56
|
min -= paddingValue;
|
|
74
57
|
max += paddingValue;
|
|
@@ -82,18 +65,20 @@ export default function LinearChart() {
|
|
|
82
65
|
range: [0, xMax]
|
|
83
66
|
});
|
|
84
67
|
|
|
85
|
-
yScale = config.runtime.xAxis.type === 'date' ?
|
|
86
|
-
scaleLinear<number>({domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)]}) :
|
|
68
|
+
yScale = config.runtime.xAxis.type === 'date' ?
|
|
69
|
+
scaleLinear<number>({domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)]}) :
|
|
87
70
|
scalePoint<string>({domain: xAxisDataMapped, padding: 0.5});
|
|
88
71
|
|
|
89
72
|
seriesScale = scalePoint<string>({
|
|
90
73
|
domain: (config.runtime.barSeriesKeys || config.runtime.seriesKeys),
|
|
91
74
|
range: [0, yMax]
|
|
92
75
|
});
|
|
93
|
-
|
|
76
|
+
|
|
94
77
|
yScale.rangeRound([0, yMax]);
|
|
95
78
|
} else {
|
|
96
|
-
|
|
79
|
+
min = min < 0 ? min * 1.11 : min
|
|
80
|
+
|
|
81
|
+
yScale = scaleLinear<number>({
|
|
97
82
|
domain: [min, max],
|
|
98
83
|
range: [yMax, 0]
|
|
99
84
|
});
|
|
@@ -105,12 +90,36 @@ export default function LinearChart() {
|
|
|
105
90
|
range: [0, xMax]
|
|
106
91
|
});
|
|
107
92
|
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if(config.visualizationType === 'Paired Bar') {
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
let groupOneMax = Math.max.apply(Math, data.map(d => d[config.series[0].dataKey]))
|
|
99
|
+
let groupTwoMax = Math.max.apply(Math, data.map(d => d[config.series[1].dataKey]))
|
|
100
|
+
|
|
101
|
+
// group one
|
|
102
|
+
var g1xScale = scaleLinear<number>({
|
|
103
|
+
domain: [0, Math.max(groupOneMax,groupTwoMax) ],
|
|
104
|
+
range: [xMax/2, 0]
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// group 2
|
|
108
|
+
var g2xScale = scaleLinear<number>({
|
|
109
|
+
domain: g1xScale.domain(),
|
|
110
|
+
range: [xMax / 2, xMax]
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
}
|
|
108
114
|
}
|
|
109
115
|
|
|
116
|
+
|
|
117
|
+
|
|
110
118
|
useEffect(() => {
|
|
111
119
|
ReactTooltip.rebuild();
|
|
112
120
|
});
|
|
113
121
|
|
|
122
|
+
|
|
114
123
|
return (
|
|
115
124
|
<ErrorBoundary component="LinearChart">
|
|
116
125
|
<svg width={width} height={height} className="linear">
|
|
@@ -130,16 +139,16 @@ export default function LinearChart() {
|
|
|
130
139
|
L${to} 0
|
|
131
140
|
M${to} -5
|
|
132
141
|
L${to} 5`} />
|
|
133
|
-
<rect
|
|
134
|
-
x={from}
|
|
135
|
-
y={0}
|
|
136
|
-
width={width}
|
|
137
|
-
height={yMax}
|
|
138
|
-
fill={region.background}
|
|
142
|
+
<rect
|
|
143
|
+
x={from}
|
|
144
|
+
y={0}
|
|
145
|
+
width={width}
|
|
146
|
+
height={yMax}
|
|
147
|
+
fill={region.background}
|
|
139
148
|
opacity={0.3} />
|
|
140
|
-
<Text
|
|
141
|
-
x={from + (width / 2)}
|
|
142
|
-
y={5}
|
|
149
|
+
<Text
|
|
150
|
+
x={from + (width / 2)}
|
|
151
|
+
y={5}
|
|
143
152
|
fill={region.color}
|
|
144
153
|
verticalAnchor="start"
|
|
145
154
|
textAnchor="middle">
|
|
@@ -150,17 +159,21 @@ export default function LinearChart() {
|
|
|
150
159
|
}) : '' }
|
|
151
160
|
|
|
152
161
|
{/* Y axis */}
|
|
153
|
-
|
|
162
|
+
{config.visualizationType !== "Spark Line" &&
|
|
163
|
+
<AxisLeft
|
|
154
164
|
scale={yScale}
|
|
155
165
|
left={config.runtime.yAxis.size}
|
|
156
166
|
label={config.runtime.yAxis.label}
|
|
157
167
|
stroke="#333"
|
|
168
|
+
tickFormat={(tick)=> config.runtime.yAxis.type ==='date' ? formatDate(parseDate(tick)) : config.orientation==='vertical' ? formatNumber(tick) : tick }
|
|
158
169
|
numTicks={config.runtime.yAxis.numTicks || undefined}
|
|
159
170
|
>
|
|
160
171
|
{props => {
|
|
161
|
-
|
|
172
|
+
const lollipopShapeSize = config.lollipopSize === 'large' ? 14 : config.lollipopSize === 'medium' ? 12 : 10;
|
|
173
|
+
const axisCenter = config.runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2;
|
|
162
174
|
const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length * (1 - config.barThickness)) + 5;
|
|
163
|
-
|
|
175
|
+
const belowBarPaddingFromTop = 9;
|
|
176
|
+
return (
|
|
164
177
|
<Group className="left-axis">
|
|
165
178
|
{props.ticks.map((tick, i) => {
|
|
166
179
|
return (
|
|
@@ -168,45 +181,152 @@ export default function LinearChart() {
|
|
|
168
181
|
key={`vx-tick-${tick.value}-${i}`}
|
|
169
182
|
className={'vx-axis-tick'}
|
|
170
183
|
>
|
|
184
|
+
{!config.runtime.yAxis.hideTicks && (
|
|
185
|
+
<Line
|
|
186
|
+
from={tick.from}
|
|
187
|
+
to={tick.to}
|
|
188
|
+
stroke="#333"
|
|
189
|
+
display={config.runtime.horizontal ? 'none' : 'block'}
|
|
190
|
+
/>
|
|
191
|
+
)}
|
|
192
|
+
{ config.runtime.yAxis.gridLines ? (
|
|
193
|
+
<Line
|
|
194
|
+
from={{x: tick.from.x + xMax, y: tick.from.y}}
|
|
195
|
+
to={tick.from}
|
|
196
|
+
stroke="rgba(0,0,0,0.3)"
|
|
197
|
+
/>
|
|
198
|
+
) : ''
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
{( config.orientation === "horizontal" && config.visualizationSubType !== 'stacked') && (config.yAxis.labelPlacement === 'On Date/Category Axis' ) && !config.yAxis.hideLabel &&
|
|
202
|
+
// 17 is a magic number from the offset in barchart.
|
|
203
|
+
<Fragment>
|
|
204
|
+
<Text
|
|
205
|
+
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
|
+
verticalAnchor={ config.isLollipopChart ? "middle" : "middle"}
|
|
207
|
+
textAnchor={"end"}
|
|
208
|
+
>{formatNumber(tick.formattedValue)}</Text>
|
|
209
|
+
</Fragment>
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
{ (config.orientation === "horizontal" && config.visualizationSubType === 'stacked') && (config.yAxis.labelPlacement === 'On Date/Category Axis' ) && !config.yAxis.hideLabel &&
|
|
213
|
+
// 17 is a magic number from the offset in barchart.
|
|
214
|
+
<Text
|
|
215
|
+
transform={`translate(${tick.to.x - 5}, ${ tick.from.y - config.barHeight / 2 - 3 }) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`}
|
|
216
|
+
verticalAnchor={ config.isLollipopChart ? "middle" : "middle"}
|
|
217
|
+
textAnchor={"end"}
|
|
218
|
+
>{formatNumber(tick.formattedValue)}</Text>
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
{ (config.orientation === "horizontal" && config.visualizationType === 'Paired Bar') && !config.yAxis.hideLabel &&
|
|
222
|
+
// 17 is a magic number from the offset in barchart.
|
|
223
|
+
<Text
|
|
224
|
+
transform={`translate(${-15}, ${ tick.from.y }) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`}
|
|
225
|
+
verticalAnchor={ config.isLollipopChart ? "middle" : "middle"}
|
|
226
|
+
textAnchor={"end"}
|
|
227
|
+
>{formatNumber(tick.formattedValue)}</Text>
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
{ config.orientation !== "horizontal" && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel &&
|
|
232
|
+
<Text
|
|
233
|
+
x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
|
|
234
|
+
y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
|
|
235
|
+
verticalAnchor={config.runtime.horizontal ? "start" : "middle"}
|
|
236
|
+
textAnchor={config.runtime.horizontal ? 'start' : 'end'}
|
|
237
|
+
>
|
|
238
|
+
{formatNumber(tick.value)}
|
|
239
|
+
</Text>
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
</Group>
|
|
243
|
+
);
|
|
244
|
+
})}
|
|
245
|
+
{!config.yAxis.hideAxis && (
|
|
246
|
+
<Line
|
|
247
|
+
from={props.axisFromPoint}
|
|
248
|
+
to={props.axisToPoint}
|
|
249
|
+
stroke="#333"
|
|
250
|
+
/>
|
|
251
|
+
)}
|
|
252
|
+
{ yScale.domain()[0] < 0 && (
|
|
253
|
+
<Line
|
|
254
|
+
from={{x: props.axisFromPoint.x, y: yScale(0)}}
|
|
255
|
+
to={{x: xMax, y: yScale(0)}}
|
|
256
|
+
stroke="#333"
|
|
257
|
+
/>
|
|
258
|
+
)}
|
|
259
|
+
<Text
|
|
260
|
+
className="y-label"
|
|
261
|
+
textAnchor="middle"
|
|
262
|
+
verticalAnchor="start"
|
|
263
|
+
transform={`translate(${-1 * config.runtime.yAxis.size}, ${axisCenter}) rotate(-90)`}
|
|
264
|
+
fontWeight="bold"
|
|
265
|
+
>
|
|
266
|
+
{props.label}
|
|
267
|
+
</Text>
|
|
268
|
+
</Group>
|
|
269
|
+
);
|
|
270
|
+
}}
|
|
271
|
+
</AxisLeft>
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
{/* X axis */}
|
|
275
|
+
{config.visualizationType !== 'Paired Bar' && config.visualizationType !== "Spark Line" && (
|
|
276
|
+
<AxisBottom
|
|
277
|
+
top={yMax}
|
|
278
|
+
left={config.runtime.yAxis.size}
|
|
279
|
+
label={config.runtime.xAxis.label}
|
|
280
|
+
tickFormat={tick=> config.runtime.xAxis.type === 'date' ? formatDate(tick) : config.orientation ==='horizontal' ? formatNumber(tick) : tick}
|
|
281
|
+
scale={xScale}
|
|
282
|
+
stroke="#333"
|
|
283
|
+
tickStroke="#333"
|
|
284
|
+
numTicks={config.runtime.xAxis.numTicks || undefined}
|
|
285
|
+
>
|
|
286
|
+
{props => {
|
|
287
|
+
const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2;
|
|
288
|
+
return (
|
|
289
|
+
<Group className="bottom-axis">
|
|
290
|
+
{props.ticks.map((tick, i) => {
|
|
291
|
+
const tickWidth = xMax / props.ticks.length;
|
|
292
|
+
return (
|
|
293
|
+
<Group
|
|
294
|
+
key={`vx-tick-${tick.value}-${i}`}
|
|
295
|
+
className={'vx-axis-tick'}
|
|
296
|
+
>
|
|
297
|
+
{!config.xAxis.hideTicks && (
|
|
171
298
|
<Line
|
|
172
299
|
from={tick.from}
|
|
173
300
|
to={tick.to}
|
|
174
301
|
stroke="#333"
|
|
175
|
-
display={config.runtime.horizontal ? 'none' : 'block'}
|
|
176
302
|
/>
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
from={{x: tick.from.x + xMax, y: tick.from.y}}
|
|
180
|
-
to={tick.from}
|
|
181
|
-
stroke="rgba(0,0,0,0.3)"
|
|
182
|
-
/>
|
|
183
|
-
) : ''
|
|
184
|
-
}
|
|
303
|
+
)}
|
|
304
|
+
{!config.xAxis.hideLabel && (
|
|
185
305
|
<Text
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
>
|
|
306
|
+
transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${!config.runtime.horizontal ? config.runtime.xAxis.tickRotation : 0})`}
|
|
307
|
+
verticalAnchor="start"
|
|
308
|
+
textAnchor={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? 'end' : 'middle'}
|
|
309
|
+
width={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? undefined : tickWidth}
|
|
310
|
+
>
|
|
311
|
+
{tick.formattedValue}
|
|
312
|
+
</Text>
|
|
313
|
+
)}
|
|
314
|
+
|
|
191
315
|
</Group>
|
|
192
316
|
);
|
|
193
317
|
})}
|
|
194
|
-
|
|
318
|
+
{!config.xAxis.hideAxis && (
|
|
319
|
+
<Line
|
|
195
320
|
from={props.axisFromPoint}
|
|
196
321
|
to={props.axisToPoint}
|
|
197
322
|
stroke="#333"
|
|
198
323
|
/>
|
|
199
|
-
{ yScale.domain()[0] < 0 && (
|
|
200
|
-
<Line
|
|
201
|
-
from={{x: props.axisFromPoint.x, y: yScale(0)}}
|
|
202
|
-
to={{x: xMax, y: yScale(0)}}
|
|
203
|
-
stroke="#333"
|
|
204
|
-
/>
|
|
205
324
|
)}
|
|
206
325
|
<Text
|
|
326
|
+
x={axisCenter}
|
|
327
|
+
y={config.runtime.xAxis.size}
|
|
207
328
|
textAnchor="middle"
|
|
208
|
-
verticalAnchor="
|
|
209
|
-
transform={`translate(${-1 * config.runtime.yAxis.size}, ${axisCenter}) rotate(-90)`}
|
|
329
|
+
verticalAnchor="end"
|
|
210
330
|
fontWeight="bold"
|
|
211
331
|
>
|
|
212
332
|
{props.label}
|
|
@@ -214,15 +334,69 @@ export default function LinearChart() {
|
|
|
214
334
|
</Group>
|
|
215
335
|
);
|
|
216
336
|
}}
|
|
217
|
-
</
|
|
337
|
+
</AxisBottom>
|
|
338
|
+
)}
|
|
218
339
|
|
|
219
|
-
{
|
|
340
|
+
{config.visualizationType === 'Paired Bar' &&
|
|
341
|
+
<>
|
|
220
342
|
<AxisBottom
|
|
221
343
|
top={yMax}
|
|
222
344
|
left={config.runtime.yAxis.size}
|
|
223
345
|
label={config.runtime.xAxis.label}
|
|
224
346
|
tickFormat={config.runtime.xAxis.type === 'date' ? formatDate : (tick) => tick}
|
|
225
|
-
scale={
|
|
347
|
+
scale={g1xScale}
|
|
348
|
+
stroke="#333"
|
|
349
|
+
tickStroke="#333"
|
|
350
|
+
numTicks={config.runtime.xAxis.numTicks || undefined}
|
|
351
|
+
>
|
|
352
|
+
{props => {
|
|
353
|
+
const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2;
|
|
354
|
+
return (
|
|
355
|
+
<Group className="bottom-axis">
|
|
356
|
+
{props.ticks.map((tick, i) => {
|
|
357
|
+
const tickWidth = xMax / props.ticks.length;
|
|
358
|
+
return (
|
|
359
|
+
<Group
|
|
360
|
+
key={`vx-tick-${tick.value}-${i}`}
|
|
361
|
+
className={'vx-axis-tick'}
|
|
362
|
+
>
|
|
363
|
+
{!config.runtime.yAxis.hideTicks &&
|
|
364
|
+
<Line
|
|
365
|
+
from={tick.from}
|
|
366
|
+
to={tick.to}
|
|
367
|
+
stroke="#333"
|
|
368
|
+
/>
|
|
369
|
+
}
|
|
370
|
+
{!config.runtime.yAxis.hideLabel &&
|
|
371
|
+
<Text
|
|
372
|
+
transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${60})`}
|
|
373
|
+
verticalAnchor="start"
|
|
374
|
+
textAnchor={'end'}
|
|
375
|
+
width={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? undefined : tickWidth}
|
|
376
|
+
>
|
|
377
|
+
{formatNumber(tick.formattedValue)}
|
|
378
|
+
</Text>
|
|
379
|
+
}
|
|
380
|
+
</Group>
|
|
381
|
+
);
|
|
382
|
+
})}
|
|
383
|
+
{!config.runtime.yAxis.hideAxis &&
|
|
384
|
+
<Line
|
|
385
|
+
from={props.axisFromPoint}
|
|
386
|
+
to={props.axisToPoint}
|
|
387
|
+
stroke="#333"
|
|
388
|
+
/>
|
|
389
|
+
}
|
|
390
|
+
</Group>
|
|
391
|
+
);
|
|
392
|
+
}}
|
|
393
|
+
</AxisBottom>
|
|
394
|
+
<AxisBottom
|
|
395
|
+
top={yMax}
|
|
396
|
+
left={config.runtime.yAxis.size}
|
|
397
|
+
label={config.runtime.xAxis.label}
|
|
398
|
+
tickFormat={config.runtime.xAxis.type === 'date' ? formatDate : (tick) => tick}
|
|
399
|
+
scale={g2xScale}
|
|
226
400
|
stroke="#333"
|
|
227
401
|
tickStroke="#333"
|
|
228
402
|
numTicks={config.runtime.xAxis.numTicks || undefined}
|
|
@@ -244,9 +418,9 @@ export default function LinearChart() {
|
|
|
244
418
|
stroke="#333"
|
|
245
419
|
/>
|
|
246
420
|
<Text
|
|
247
|
-
transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${
|
|
421
|
+
transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${60})`}
|
|
248
422
|
verticalAnchor="start"
|
|
249
|
-
textAnchor={
|
|
423
|
+
textAnchor={'end'}
|
|
250
424
|
width={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? undefined : tickWidth}
|
|
251
425
|
>
|
|
252
426
|
{tick.formattedValue}
|
|
@@ -254,32 +428,28 @@ export default function LinearChart() {
|
|
|
254
428
|
</Group>
|
|
255
429
|
);
|
|
256
430
|
})}
|
|
257
|
-
<Line
|
|
431
|
+
<Line
|
|
258
432
|
from={props.axisFromPoint}
|
|
259
433
|
to={props.axisToPoint}
|
|
260
434
|
stroke="#333"
|
|
261
435
|
/>
|
|
262
|
-
<Text
|
|
263
|
-
x={axisCenter}
|
|
264
|
-
y={config.runtime.xAxis.size}
|
|
265
|
-
textAnchor="middle"
|
|
266
|
-
verticalAnchor="end"
|
|
267
|
-
fontWeight="bold"
|
|
268
|
-
>
|
|
269
|
-
{props.label}
|
|
270
|
-
</Text>
|
|
271
436
|
</Group>
|
|
272
437
|
);
|
|
273
438
|
}}
|
|
274
439
|
</AxisBottom>
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
{ config.visualizationType
|
|
278
|
-
<
|
|
279
|
-
)}
|
|
440
|
+
</>
|
|
441
|
+
}
|
|
442
|
+
{ config.visualizationType === 'Paired Bar' && (
|
|
443
|
+
<PairedBarChart width={xMax} height={yMax} />
|
|
444
|
+
) }
|
|
280
445
|
|
|
281
446
|
{/* Bar chart */}
|
|
282
|
-
{ config.visualizationType !== 'Bar' && (
|
|
447
|
+
{ (config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar') && (
|
|
448
|
+
<BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />
|
|
449
|
+
)}
|
|
450
|
+
|
|
451
|
+
{/* Line chart */}
|
|
452
|
+
{ (config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar') && (
|
|
283
453
|
<LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />
|
|
284
454
|
)}
|
|
285
455
|
</svg>
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
|
+
import { Group } from '@visx/group';
|
|
3
|
+
import { Bar } from '@visx/shape';
|
|
4
|
+
import { scaleLinear, scaleBand } from '@visx/scale';
|
|
5
|
+
import { Text } from '@visx/text';
|
|
6
|
+
|
|
7
|
+
import Context from '../context';
|
|
8
|
+
import chroma from 'chroma-js';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
interface PairedBarChartProps {
|
|
12
|
+
width: number,
|
|
13
|
+
height: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
|
|
17
|
+
|
|
18
|
+
const { config, colorScale, transformedData } = useContext<any>(Context);
|
|
19
|
+
|
|
20
|
+
if(!config || config?.series?.length < 2) return;
|
|
21
|
+
|
|
22
|
+
const data = transformedData
|
|
23
|
+
const adjustedWidth = width;
|
|
24
|
+
const adjustedHeight = height;
|
|
25
|
+
const halfWidth = adjustedWidth / 2;
|
|
26
|
+
|
|
27
|
+
const groupOne = {
|
|
28
|
+
parentKey: config.dataDescription.seriesKey,
|
|
29
|
+
dataKey: config.series[0].dataKey,
|
|
30
|
+
color: colorScale(config.runtime.seriesLabels[config.series[0].dataKey]),
|
|
31
|
+
max: Math.max.apply(Math, data.map(item => item[config.series[0].dataKey])),
|
|
32
|
+
labelColor: ''
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const groupTwo = {
|
|
36
|
+
parentKey: config.dataDescription.seriesKey,
|
|
37
|
+
dataKey: config.series[1].dataKey,
|
|
38
|
+
color: colorScale(config.runtime.seriesLabels[config.series[1].dataKey]),
|
|
39
|
+
max: Math.max.apply(Math, data.map(item => item[config.series[1].dataKey])),
|
|
40
|
+
labelColor: ''
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const xScale = scaleLinear({
|
|
44
|
+
domain: [0, Math.max(groupOne.max, groupTwo.max)],
|
|
45
|
+
range: [0, halfWidth]
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
const yScale = scaleBand({
|
|
50
|
+
range: [0, adjustedHeight],
|
|
51
|
+
domain: data.map(d => d[config.dataDescription.xKey]),
|
|
52
|
+
padding: 0.2
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Set label color
|
|
56
|
+
let labelColor = '#000000';
|
|
57
|
+
|
|
58
|
+
if (chroma.contrast(labelColor, groupOne.color) < 4.9) {
|
|
59
|
+
groupOne.labelColor = '#FFFFFF';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (chroma.contrast(labelColor, groupTwo.color) < 4.9) {
|
|
63
|
+
groupTwo.labelColor = '#FFFFFF';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (width > 0) && (
|
|
67
|
+
<>
|
|
68
|
+
<svg
|
|
69
|
+
id="cdc-visualization__paired-bar-chart"
|
|
70
|
+
width={width}
|
|
71
|
+
height={height}>
|
|
72
|
+
<Group top={0} left={config.xAxis.size}>
|
|
73
|
+
{data.filter(item => config.series[0].dataKey === groupOne.dataKey).map(d => {
|
|
74
|
+
let barWidth = (xScale(d[config.series[0].dataKey]))
|
|
75
|
+
return (
|
|
76
|
+
<Group key={`group-${groupOne.dataKey}-${d[config.xAxis.dataKey]}`}>
|
|
77
|
+
<Bar
|
|
78
|
+
className="bar"
|
|
79
|
+
key={`bar-${groupOne.dataKey}-${d[config.dataDescription.xKey]}`}
|
|
80
|
+
x={halfWidth - barWidth}
|
|
81
|
+
y={yScale([d[config.dataDescription.xKey]])}
|
|
82
|
+
width={xScale(d[config.series[0].dataKey])}
|
|
83
|
+
height={yScale.bandwidth()}
|
|
84
|
+
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
|
+
}
|
|
92
|
+
data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
93
|
+
/>
|
|
94
|
+
<Text
|
|
95
|
+
textAnchor={barWidth < 100 ? 'end' : 'start' }
|
|
96
|
+
x={halfWidth - (barWidth < 100 ? barWidth + 10 : barWidth - 5)}
|
|
97
|
+
y={yScale([d[config.dataDescription.xKey]]) + yScale.bandwidth() / 1.5}
|
|
98
|
+
fill={barWidth > 100 ? groupOne.labelColor : '#000' }>
|
|
99
|
+
{d[config.dataDescription.xKey]}
|
|
100
|
+
</Text>
|
|
101
|
+
</Group>
|
|
102
|
+
)}
|
|
103
|
+
)}
|
|
104
|
+
{data.filter(item => config.series[1].dataKey === groupTwo.dataKey).map(d => {
|
|
105
|
+
let barWidth = (xScale(d[config.series[1].dataKey]))
|
|
106
|
+
|
|
107
|
+
return(
|
|
108
|
+
<Group key={`group-${groupTwo.dataKey}-${d[config.dataDescription.xKey]}`}>
|
|
109
|
+
<Bar
|
|
110
|
+
className="bar"
|
|
111
|
+
key={`bar-${groupTwo.dataKey}-${d[config.dataDescription.xKey]}`}
|
|
112
|
+
x={halfWidth}
|
|
113
|
+
y={yScale([d[config.dataDescription.xKey]])}
|
|
114
|
+
width={xScale(d[config.series[1].dataKey])}
|
|
115
|
+
height={yScale.bandwidth()}
|
|
116
|
+
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
|
+
}
|
|
124
|
+
data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
125
|
+
|
|
126
|
+
/>
|
|
127
|
+
<Text
|
|
128
|
+
textAnchor={barWidth < 100 ? 'start' : 'end' }
|
|
129
|
+
x={halfWidth + (barWidth < 100 ? barWidth + 10 : barWidth - 10 )}
|
|
130
|
+
y={yScale([d[config.dataDescription.xKey]]) + (yScale.bandwidth() / 1.5)}
|
|
131
|
+
fill={barWidth > 100 ? groupTwo.labelColor : '#000' }>
|
|
132
|
+
{d[config.dataDescription.xKey]}
|
|
133
|
+
</Text>
|
|
134
|
+
</Group>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
)}
|
|
138
|
+
</Group>
|
|
139
|
+
</svg>
|
|
140
|
+
</>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default PairedBarChart;
|