@cdc/chart 1.3.1 → 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.
Files changed (56) hide show
  1. package/dist/cdcchart.js +85 -0
  2. package/examples/age-adjusted-rates.json +1218 -0
  3. package/examples/case-rate-example-config.json +36 -0
  4. package/examples/case-rate-example-data.json +33602 -0
  5. package/examples/covid-confidence-example-config.json +35 -0
  6. package/examples/covid-example-config.json +36 -0
  7. package/examples/covid-example-data-confidence.json +32 -0
  8. package/examples/covid-example-data.json +22 -0
  9. package/examples/cutoff-example-config.json +36 -0
  10. package/examples/cutoff-example-data.json +38 -0
  11. package/examples/date-exclusions-config.json +62 -0
  12. package/examples/date-exclusions-data.json +162 -0
  13. package/examples/horizontal-chart.json +35 -0
  14. package/examples/horizontal-stacked-bar-chart.json +36 -0
  15. package/examples/line-chart.json +76 -0
  16. package/examples/paired-bar-data.json +14 -0
  17. package/examples/paired-bar-example.json +48 -0
  18. package/examples/paired-bar-formatted.json +37 -0
  19. package/examples/planet-chart-horizontal-example-config.json +35 -0
  20. package/examples/planet-combo-example-config.json +31 -0
  21. package/examples/planet-example-config.json +35 -0
  22. package/examples/planet-example-data.json +56 -0
  23. package/examples/planet-pie-example-config.json +28 -0
  24. package/examples/private/newtest.csv +101 -0
  25. package/examples/private/test.json +10124 -0
  26. package/examples/temp-example-config.json +57 -0
  27. package/examples/temp-example-data.json +130 -0
  28. package/package.json +9 -8
  29. package/src/CdcChart.tsx +836 -0
  30. package/src/components/BarChart.tsx +571 -0
  31. package/src/components/BarStackVertical.js +0 -0
  32. package/src/components/DataTable.tsx +229 -0
  33. package/src/components/EditorPanel.js +1319 -0
  34. package/src/components/LineChart.tsx +76 -0
  35. package/src/components/LinearChart.tsx +459 -0
  36. package/src/components/PairedBarChart.tsx +144 -0
  37. package/src/components/PieChart.tsx +189 -0
  38. package/src/components/SparkLine.js +206 -0
  39. package/src/context.tsx +5 -0
  40. package/src/data/initial-state.js +61 -0
  41. package/src/hooks/useActiveElement.js +19 -0
  42. package/src/hooks/useColorPalette.ts +83 -0
  43. package/src/hooks/useReduceData.ts +43 -0
  44. package/src/images/active-checkmark.svg +1 -0
  45. package/src/images/asc.svg +1 -0
  46. package/src/images/desc.svg +1 -0
  47. package/src/images/inactive-checkmark.svg +1 -0
  48. package/src/images/warning.svg +1 -0
  49. package/src/index.html +68 -0
  50. package/src/index.tsx +21 -0
  51. package/src/scss/DataTable.scss +23 -0
  52. package/src/scss/LinearChart.scss +0 -0
  53. package/src/scss/editor-panel.scss +693 -0
  54. package/src/scss/main.scss +426 -0
  55. package/src/scss/mixins.scss +0 -0
  56. package/src/scss/variables.scss +1 -0
@@ -0,0 +1,76 @@
1
+ import React, { useContext } from 'react';
2
+
3
+ import * as allCurves from '@visx/curve';
4
+ import { Group } from '@visx/group';
5
+ import { LinePath } from '@visx/shape';
6
+ import { Text } from '@visx/text';
7
+
8
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
9
+
10
+ import Context from '../context';
11
+
12
+ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData }) {
13
+ const { transformedData: data, colorScale, seriesHighlight, config, formatNumber,formatDate,parseDate } = useContext<any>(Context);
14
+
15
+ return (
16
+ <ErrorBoundary component="LineChart">
17
+ <Group left={config.runtime.yAxis.size}>
18
+ { (config.runtime.lineSeriesKeys || config.runtime.seriesKeys).map((seriesKey, index) => (
19
+ <Group
20
+ key={`series-${seriesKey}`}
21
+ opacity={config.legend.behavior === "highlight" && seriesHighlight.length > 0 && seriesHighlight.indexOf(seriesKey) === -1 ? 0.5 : 1}
22
+ display={config.legend.behavior === "highlight" || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
23
+ >
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];
26
+ let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${formatNumber(getYAxisData(d, seriesKey))}` : formatNumber(getYAxisData(d, seriesKey))
27
+ let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` :xAxisValue;
28
+
29
+ const tooltip = `<div>
30
+ ${yAxisTooltip}<br />
31
+ ${xAxisTooltip}<br />
32
+ ${config.seriesLabel ? `${config.seriesLabel}: ${seriesKey}` : ''}
33
+ </div>`
34
+
35
+ let circleRadii = 4.5
36
+
37
+ return (
38
+ <Group key={`series-${seriesKey}-point-${dataIndex}`}>
39
+ <Text
40
+ display={config.labels ? 'block' : 'none'}
41
+ x={xScale(getXAxisData(d))}
42
+ y={yScale(getYAxisData(d, seriesKey))}
43
+ fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
44
+ textAnchor="middle">
45
+ {formatNumber(d[seriesKey])}
46
+ </Text>
47
+ <circle
48
+ key={`${seriesKey}-${dataIndex}`}
49
+ r={circleRadii}
50
+ cx={xScale(getXAxisData(d))}
51
+ cy={yScale(getYAxisData(d, seriesKey))}
52
+ fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
53
+ style={{fill: colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}}
54
+ data-tip={tooltip}
55
+ data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
56
+ />
57
+ </Group>
58
+ )
59
+ })}
60
+ <LinePath
61
+ curve={allCurves.curveLinear}
62
+ data={data}
63
+ x={(d) => xScale(getXAxisData(d))}
64
+ y={(d) => yScale(getYAxisData(d, seriesKey))}
65
+ stroke={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
66
+ strokeWidth={2}
67
+ strokeOpacity={1}
68
+ shapeRendering="geometricPrecision"
69
+ />
70
+ </Group>
71
+ ))
72
+ }
73
+ </Group>
74
+ </ErrorBoundary>
75
+ );
76
+ }
@@ -0,0 +1,459 @@
1
+ import React, { Fragment, useContext, useEffect,useState } from 'react';
2
+ import ReactTooltip from 'react-tooltip';
3
+
4
+ import { Group } from '@visx/group';
5
+ import { Line } from '@visx/shape';
6
+ import { Text } from '@visx/text';
7
+ import { scaleLinear, scalePoint } from '@visx/scale';
8
+ import { AxisLeft, AxisBottom } from '@visx/axis';
9
+
10
+ import BarChart from './BarChart';
11
+ import LineChart from './LineChart';
12
+ import Context from '../context';
13
+ import PairedBarChart from './PairedBarChart';
14
+ import SparkLine from './SparkLine';
15
+
16
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
17
+
18
+ import '../scss/LinearChart.scss';
19
+ import useReduceData from '../hooks/useReduceData';
20
+
21
+ export default function LinearChart() {
22
+ const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport,formatNumber } = useContext<any>(Context);
23
+ let [ width ] = dimensions;
24
+ const {minValue,maxValue} = useReduceData(config,data)
25
+ if(config && config.legend && !config.legend.hide && (currentViewport === 'lg' || currentViewport === 'md')) {
26
+ width = width * 0.73;
27
+ }
28
+
29
+ const height = config.aspectRatio ? (width * config.aspectRatio) : config.height;
30
+
31
+ const xMax = width - config.runtime.yAxis.size;
32
+ const yMax = height - config.runtime.xAxis.size;
33
+
34
+ const getXAxisData = (d: any) => config.runtime.xAxis.type === 'date' ? (parseDate(d[config.runtime.originalXAxis.dataKey])).getTime() : d[config.runtime.originalXAxis.dataKey];
35
+ const getYAxisData = (d: any, seriesKey: string) => d[seriesKey];
36
+
37
+ let xScale;
38
+ let yScale;
39
+ let seriesScale;
40
+
41
+ if (data) {
42
+ let min = config.runtime.yAxis.min !== undefined ? config.runtime.yAxis.min : minValue
43
+ let max = config.runtime.yAxis.max !== undefined ? config.runtime.yAxis.max : Number.MIN_VALUE;
44
+
45
+ if((config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && min > 0) {
46
+ min = 0;
47
+ }
48
+ //If data value max wasn't provided, calculate it
49
+ if(max === Number.MIN_VALUE){
50
+ max = maxValue
51
+ }
52
+
53
+ //Adds Y Axis data padding if applicable
54
+ if(config.runtime.yAxis.paddingPercent) {
55
+ let paddingValue = (max - min) * config.runtime.yAxis.paddingPercent;
56
+ min -= paddingValue;
57
+ max += paddingValue;
58
+ }
59
+
60
+ let xAxisDataMapped = data.map(d => getXAxisData(d));
61
+
62
+ if(config.runtime.horizontal){
63
+ xScale = scaleLinear<number>({
64
+ domain: [min, max],
65
+ range: [0, xMax]
66
+ });
67
+
68
+ yScale = config.runtime.xAxis.type === 'date' ?
69
+ scaleLinear<number>({domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)]}) :
70
+ scalePoint<string>({domain: xAxisDataMapped, padding: 0.5});
71
+
72
+ seriesScale = scalePoint<string>({
73
+ domain: (config.runtime.barSeriesKeys || config.runtime.seriesKeys),
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
+ });
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
+ }
114
+ }
115
+
116
+
117
+
118
+ useEffect(() => {
119
+ ReactTooltip.rebuild();
120
+ });
121
+
122
+
123
+ return (
124
+ <ErrorBoundary component="LinearChart">
125
+ <svg width={width} height={height} className="linear">
126
+ {/* Higlighted regions */}
127
+ { config.regions ? config.regions.map((region) => {
128
+ if(!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
129
+
130
+ const from = xScale((parseDate(region.from)).getTime());
131
+ const to = xScale((parseDate(region.to)).getTime());
132
+ const width = to - from;
133
+
134
+ return (
135
+ <Group className="regions" left={config.runtime.yAxis.size} key={region.label}>
136
+ <path stroke="#333" d={`M${from} -5
137
+ L${from} 5
138
+ M${from} 0
139
+ L${to} 0
140
+ M${to} -5
141
+ L${to} 5`} />
142
+ <rect
143
+ x={from}
144
+ y={0}
145
+ width={width}
146
+ height={yMax}
147
+ fill={region.background}
148
+ opacity={0.3} />
149
+ <Text
150
+ x={from + (width / 2)}
151
+ y={5}
152
+ fill={region.color}
153
+ verticalAnchor="start"
154
+ textAnchor="middle">
155
+ {region.label}
156
+ </Text>
157
+ </Group>
158
+ )
159
+ }) : '' }
160
+
161
+ {/* Y axis */}
162
+ {config.visualizationType !== "Spark Line" &&
163
+ <AxisLeft
164
+ scale={yScale}
165
+ left={config.runtime.yAxis.size}
166
+ label={config.runtime.yAxis.label}
167
+ stroke="#333"
168
+ tickFormat={(tick)=> config.runtime.yAxis.type ==='date' ? formatDate(parseDate(tick)) : config.orientation==='vertical' ? formatNumber(tick) : tick }
169
+ numTicks={config.runtime.yAxis.numTicks || undefined}
170
+ >
171
+ {props => {
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;
174
+ const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length * (1 - config.barThickness)) + 5;
175
+ const belowBarPaddingFromTop = 9;
176
+ return (
177
+ <Group className="left-axis">
178
+ {props.ticks.map((tick, i) => {
179
+ return (
180
+ <Group
181
+ key={`vx-tick-${tick.value}-${i}`}
182
+ className={'vx-axis-tick'}
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 && (
298
+ <Line
299
+ from={tick.from}
300
+ to={tick.to}
301
+ stroke="#333"
302
+ />
303
+ )}
304
+ {!config.xAxis.hideLabel && (
305
+ <Text
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
+
315
+ </Group>
316
+ );
317
+ })}
318
+ {!config.xAxis.hideAxis && (
319
+ <Line
320
+ from={props.axisFromPoint}
321
+ to={props.axisToPoint}
322
+ stroke="#333"
323
+ />
324
+ )}
325
+ <Text
326
+ x={axisCenter}
327
+ y={config.runtime.xAxis.size}
328
+ textAnchor="middle"
329
+ verticalAnchor="end"
330
+ fontWeight="bold"
331
+ >
332
+ {props.label}
333
+ </Text>
334
+ </Group>
335
+ );
336
+ }}
337
+ </AxisBottom>
338
+ )}
339
+
340
+ {config.visualizationType === 'Paired Bar' &&
341
+ <>
342
+ <AxisBottom
343
+ top={yMax}
344
+ left={config.runtime.yAxis.size}
345
+ label={config.runtime.xAxis.label}
346
+ tickFormat={config.runtime.xAxis.type === 'date' ? formatDate : (tick) => tick}
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}
400
+ stroke="#333"
401
+ tickStroke="#333"
402
+ numTicks={config.runtime.xAxis.numTicks || undefined}
403
+ >
404
+ {props => {
405
+ const axisCenter = (props.axisToPoint.x - props.axisFromPoint.x) / 2;
406
+ return (
407
+ <Group className="bottom-axis">
408
+ {props.ticks.map((tick, i) => {
409
+ const tickWidth = xMax / props.ticks.length;
410
+ return (
411
+ <Group
412
+ key={`vx-tick-${tick.value}-${i}`}
413
+ className={'vx-axis-tick'}
414
+ >
415
+ <Line
416
+ from={tick.from}
417
+ to={tick.to}
418
+ stroke="#333"
419
+ />
420
+ <Text
421
+ transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${60})`}
422
+ verticalAnchor="start"
423
+ textAnchor={'end'}
424
+ width={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? undefined : tickWidth}
425
+ >
426
+ {tick.formattedValue}
427
+ </Text>
428
+ </Group>
429
+ );
430
+ })}
431
+ <Line
432
+ from={props.axisFromPoint}
433
+ to={props.axisToPoint}
434
+ stroke="#333"
435
+ />
436
+ </Group>
437
+ );
438
+ }}
439
+ </AxisBottom>
440
+ </>
441
+ }
442
+ { config.visualizationType === 'Paired Bar' && (
443
+ <PairedBarChart width={xMax} height={yMax} />
444
+ ) }
445
+
446
+ {/* Bar chart */}
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') && (
453
+ <LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />
454
+ )}
455
+ </svg>
456
+ <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} html={true} type="light" arrowColor="rgba(0,0,0,0)" className="tooltip"/>
457
+ </ErrorBoundary>
458
+ )
459
+ }
@@ -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;