@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.
Files changed (35) hide show
  1. package/dist/cdcchart.js +77 -4
  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/date-exclusions-config.json +62 -0
  6. package/examples/date-exclusions-data.json +162 -0
  7. package/examples/horizontal-chart.json +35 -0
  8. package/examples/horizontal-stacked-bar-chart.json +36 -0
  9. package/examples/line-chart.json +76 -0
  10. package/examples/paired-bar-data.json +14 -0
  11. package/examples/paired-bar-example.json +48 -0
  12. package/examples/paired-bar-formatted.json +37 -0
  13. package/examples/planet-chart-horizontal-example-config.json +35 -0
  14. package/examples/planet-example-config.json +1 -0
  15. package/examples/private/newtest.csv +101 -0
  16. package/examples/private/test.json +10124 -0
  17. package/package.json +9 -5
  18. package/src/CdcChart.tsx +417 -149
  19. package/src/components/BarChart.tsx +431 -24
  20. package/src/components/BarStackVertical.js +0 -0
  21. package/src/components/DataTable.tsx +55 -28
  22. package/src/components/EditorPanel.js +914 -260
  23. package/src/components/LineChart.tsx +4 -3
  24. package/src/components/LinearChart.tsx +258 -88
  25. package/src/components/PairedBarChart.tsx +144 -0
  26. package/src/components/PieChart.tsx +30 -16
  27. package/src/components/SparkLine.js +206 -0
  28. package/src/data/initial-state.js +59 -32
  29. package/src/hooks/useActiveElement.js +19 -0
  30. package/src/hooks/useColorPalette.ts +83 -0
  31. package/src/hooks/useReduceData.ts +43 -0
  32. package/src/index.html +49 -13
  33. package/src/index.tsx +6 -2
  34. package/src/scss/editor-panel.scss +12 -4
  35. 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 { filteredData:data, colorScale, seriesHighlight, config, formatNumber } = useContext<any>(Context);
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}: ${d[config.runtime.xAxis.dataKey]}` : d[config.runtime.xAxis.dataKey]
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 { filteredData:data, dimensions, config, parseDate, formatDate, currentViewport } = useContext<any>(Context);
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 : Math.min(...data.map((d) => Math.min(...config.runtime.seriesKeys.map((key) => Number(d[key])))));
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
- //If stacked bar, add together y values to get max, otherwise map data to find max
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
- yScale = scaleLinear<number>({
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
- <AxisLeft
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
- const axisCenter = config.runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2;
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
- return (
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
- { config.runtime.yAxis.gridLines ? (
178
- <Line
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
- x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
187
- y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
188
- verticalAnchor={config.runtime.horizontal ? "start" : "middle"}
189
- textAnchor={config.runtime.horizontal ? 'start' : 'end'}
190
- >{tick.formattedValue}</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
+
191
315
  </Group>
192
316
  );
193
317
  })}
194
- <Line
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="start"
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
- </AxisLeft>
337
+ </AxisBottom>
338
+ )}
218
339
 
219
- {/* X axis */}
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={xScale}
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(-${!config.runtime.horizontal ? config.runtime.xAxis.tickRotation : 0})`}
421
+ transform={`translate(${tick.to.x}, ${tick.to.y}) rotate(-${60})`}
248
422
  verticalAnchor="start"
249
- textAnchor={config.runtime.xAxis.tickRotation && config.runtime.xAxis.tickRotation !== '0' ? 'end' : 'middle'}
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
- {/* Line chart */}
277
- { config.visualizationType !== 'Line' && (
278
- <BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} />
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;