@cdc/chart 1.3.2 → 1.3.4
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
- package/LICENSE +0 -201
|
@@ -3,6 +3,7 @@ import { animated, useTransition, interpolate } from 'react-spring';
|
|
|
3
3
|
import ReactTooltip from 'react-tooltip';
|
|
4
4
|
|
|
5
5
|
import Pie, { ProvidedProps, PieArcDatum } from '@visx/shape/lib/shapes/Pie';
|
|
6
|
+
import chroma from "chroma-js";
|
|
6
7
|
import { Group } from '@visx/group';
|
|
7
8
|
import { Text } from '@visx/text';
|
|
8
9
|
|
|
@@ -19,16 +20,18 @@ const enterUpdateTransition = ({ startAngle, endAngle }: PieArcDatum<any>) => ({
|
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
export default function PieChart() {
|
|
22
|
-
const {
|
|
23
|
+
const { transformedData: data, config, dimensions, seriesHighlight, colorScale, formatNumber, currentViewport } = useContext<any>(Context);
|
|
23
24
|
|
|
24
25
|
const [filteredData, setFilteredData] = useState<any>(undefined);
|
|
25
26
|
|
|
27
|
+
|
|
28
|
+
|
|
26
29
|
type AnimatedPieProps<Datum> = ProvidedProps<Datum> & {
|
|
27
30
|
animate?: boolean;
|
|
28
31
|
getKey: (d: PieArcDatum<Datum>) => string;
|
|
29
32
|
delay?: number;
|
|
30
33
|
};
|
|
31
|
-
|
|
34
|
+
|
|
32
35
|
function AnimatedPie<Datum>({
|
|
33
36
|
arcs,
|
|
34
37
|
path,
|
|
@@ -45,6 +48,7 @@ export default function PieChart() {
|
|
|
45
48
|
leave: enterUpdateTransition,
|
|
46
49
|
},
|
|
47
50
|
);
|
|
51
|
+
|
|
48
52
|
return (
|
|
49
53
|
<>
|
|
50
54
|
{transitions.map(
|
|
@@ -89,24 +93,34 @@ export default function PieChart() {
|
|
|
89
93
|
item: PieArcDatum<Datum>;
|
|
90
94
|
props: PieStyles;
|
|
91
95
|
key: string;
|
|
92
|
-
|
|
96
|
+
}) => {
|
|
97
|
+
|
|
93
98
|
const [centroidX, centroidY] = path.centroid(arc);
|
|
94
99
|
const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1;
|
|
95
100
|
|
|
101
|
+
let textColor = "#FFF";
|
|
102
|
+
if (chroma.contrast(textColor, colorScale(arc.data[config.runtime.xAxis.dataKey])) < 3.5) {
|
|
103
|
+
textColor = "000";
|
|
104
|
+
}
|
|
105
|
+
|
|
96
106
|
return (
|
|
97
107
|
<animated.g key={key}>
|
|
98
108
|
{hasSpaceForLabel && (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
<Text
|
|
110
|
+
style={{ fill: textColor }}
|
|
111
|
+
x={centroidX}
|
|
112
|
+
y={centroidY}
|
|
113
|
+
dy=".33em"
|
|
114
|
+
textAnchor="middle"
|
|
115
|
+
pointerEvents="none"
|
|
116
|
+
>
|
|
117
|
+
{Math.round(
|
|
118
|
+
(((arc.endAngle - arc.startAngle) * 180) /
|
|
119
|
+
Math.PI /
|
|
120
|
+
360) *
|
|
121
|
+
100
|
|
122
|
+
) + "%"}
|
|
123
|
+
</Text>
|
|
110
124
|
)}
|
|
111
125
|
</animated.g>
|
|
112
126
|
);
|
|
@@ -121,13 +135,13 @@ export default function PieChart() {
|
|
|
121
135
|
if(config && config.legend && !config.legend.hide && currentViewport === 'lg') {
|
|
122
136
|
width = width * 0.73;
|
|
123
137
|
}
|
|
124
|
-
|
|
138
|
+
|
|
125
139
|
const height = config.aspectRatio ? (width * config.aspectRatio) : config.height;
|
|
126
140
|
|
|
127
141
|
const radius = Math.min(width, height) / 2;
|
|
128
142
|
const centerY = height / 2;
|
|
129
143
|
const centerX = width / 2;
|
|
130
|
-
const donutThickness = radius;
|
|
144
|
+
const donutThickness = (config.pieType === "Donut") ? 75 : radius;
|
|
131
145
|
|
|
132
146
|
useEffect(() => {
|
|
133
147
|
if(seriesHighlight.length > 0 && config.legend.behavior !== "highlight"){
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import React, { useContext, useEffect } 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
|
+
import { scaleLinear, scalePoint } from '@visx/scale';
|
|
8
|
+
import { AxisBottom } from '@visx/axis';
|
|
9
|
+
import { MarkerArrow } from '@visx/marker';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
|
|
13
|
+
|
|
14
|
+
import ReactTooltip from 'react-tooltip';
|
|
15
|
+
|
|
16
|
+
import useReduceData from '../hooks/useReduceData';
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
import Context from '../context';
|
|
20
|
+
|
|
21
|
+
export default function SparkLine({width: parentWidth, height: parentHeight}) {
|
|
22
|
+
|
|
23
|
+
const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, seriesHighlight, formatNumber, colorScale } = useContext(Context);
|
|
24
|
+
let width = parentWidth
|
|
25
|
+
const { minValue, maxValue } = useReduceData(config, data)
|
|
26
|
+
// if (config && config.legend && !config.legend.hide && (currentViewport === 'lg' || currentViewport === 'md')) {
|
|
27
|
+
// width = width * 0.73;
|
|
28
|
+
// }
|
|
29
|
+
const margin = ({ top: 5, right: 10, bottom: 10, left: 10 })
|
|
30
|
+
const height = parentHeight;
|
|
31
|
+
|
|
32
|
+
const xMax = width - config.runtime.yAxis.size;
|
|
33
|
+
const yMax = height - margin.top - 20;
|
|
34
|
+
|
|
35
|
+
const getXAxisData = (d) => config.runtime.xAxis.type === 'date' ? (parseDate(d[config.runtime.originalXAxis.dataKey])).getTime() : d[config.runtime.originalXAxis.dataKey];
|
|
36
|
+
const getYAxisData = (d, seriesKey) => d[seriesKey];
|
|
37
|
+
|
|
38
|
+
let xScale;
|
|
39
|
+
let yScale;
|
|
40
|
+
let seriesScale;
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if (data) {
|
|
44
|
+
let min = config.runtime.yAxis.min !== undefined ? config.runtime.yAxis.min : minValue
|
|
45
|
+
let max = config.runtime.yAxis.max !== undefined ? config.runtime.yAxis.max : Number.MIN_VALUE;
|
|
46
|
+
|
|
47
|
+
if ((config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && min > 0) {
|
|
48
|
+
min = 0;
|
|
49
|
+
}
|
|
50
|
+
//If data value max wasn't provided, calculate it
|
|
51
|
+
if (max === Number.MIN_VALUE) {
|
|
52
|
+
max = maxValue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//Adds Y Axis data padding if applicable
|
|
56
|
+
if (config.runtime.yAxis.paddingPercent) {
|
|
57
|
+
let paddingValue = (max - min) * config.runtime.yAxis.paddingPercent;
|
|
58
|
+
min -= paddingValue;
|
|
59
|
+
max += paddingValue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let xAxisDataMapped = data.map(d => getXAxisData(d));
|
|
63
|
+
|
|
64
|
+
if (config.runtime.horizontal) {
|
|
65
|
+
xScale = scaleLinear({
|
|
66
|
+
domain: [min, max],
|
|
67
|
+
range: [0, xMax]
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
yScale = config.runtime.xAxis.type === 'date' ?
|
|
71
|
+
scaleLinear({ domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)] }) :
|
|
72
|
+
scalePoint({ domain: xAxisDataMapped, padding: 0.5 });
|
|
73
|
+
|
|
74
|
+
seriesScale = scalePoint({
|
|
75
|
+
domain: (config.runtime.barSeriesKeys || config.runtime.seriesKeys),
|
|
76
|
+
range: [0, yMax]
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
yScale.rangeRound([0, yMax]);
|
|
80
|
+
} else {
|
|
81
|
+
min = min < 0 ? min * 1.11 : min
|
|
82
|
+
|
|
83
|
+
yScale = scaleLinear({
|
|
84
|
+
domain: [min, max],
|
|
85
|
+
range: [yMax - margin.bottom, margin.top]
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
xScale = scalePoint({
|
|
89
|
+
domain: xAxisDataMapped,
|
|
90
|
+
range: [margin.left, width - margin.right]
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
seriesScale = scalePoint({
|
|
94
|
+
domain: (config.runtime.barSeriesKeys || config.runtime.seriesKeys),
|
|
95
|
+
range: [0, xMax]
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const handleSparkLineTicks = [xScale.domain()[0], xScale.domain()[xScale.domain().length - 1]];
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
ReactTooltip.rebuild();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<ErrorBoundary component="SparkLine">
|
|
109
|
+
<svg
|
|
110
|
+
width={width}
|
|
111
|
+
height={height}
|
|
112
|
+
className={'sparkline'}
|
|
113
|
+
>
|
|
114
|
+
{(config.runtime.lineSeriesKeys || config.runtime.seriesKeys).map((seriesKey, index) => (
|
|
115
|
+
<>
|
|
116
|
+
<Group
|
|
117
|
+
className='sparkline-group'
|
|
118
|
+
height={parentHeight}
|
|
119
|
+
top={margin.top}
|
|
120
|
+
key={`series-${seriesKey}`}
|
|
121
|
+
opacity={config.legend.behavior === "highlight" && seriesHighlight.length > 0 && seriesHighlight.indexOf(seriesKey) === -1 ? 0.5 : 1}
|
|
122
|
+
display={config.legend.behavior === "highlight" || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
|
|
123
|
+
>
|
|
124
|
+
{data.map((d, dataIndex) => {
|
|
125
|
+
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${formatNumber(getYAxisData(d, seriesKey))}` : formatNumber(getYAxisData(d, seriesKey))
|
|
126
|
+
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${d[config.runtime.xAxis.dataKey]}` : d[config.runtime.xAxis.dataKey]
|
|
127
|
+
|
|
128
|
+
const tooltip = `<div>
|
|
129
|
+
${yAxisTooltip}<br />
|
|
130
|
+
${xAxisTooltip}<br />
|
|
131
|
+
${config.seriesLabel ? `${config.seriesLabel}: ${seriesKey}` : ''}
|
|
132
|
+
</div>`
|
|
133
|
+
|
|
134
|
+
let circleRadii = 4.5
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<Group key={`series-${seriesKey}-point-${dataIndex}`}>
|
|
138
|
+
<Text
|
|
139
|
+
display={config.labels ? 'block' : 'none'}
|
|
140
|
+
x={xScale(getXAxisData(d))}
|
|
141
|
+
y={yScale(getYAxisData(d, seriesKey))}
|
|
142
|
+
fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
143
|
+
textAnchor="middle">
|
|
144
|
+
{formatNumber(d[seriesKey])}
|
|
145
|
+
</Text>
|
|
146
|
+
|
|
147
|
+
{dataIndex + 1 !== data.length &&
|
|
148
|
+
<circle
|
|
149
|
+
key={`${seriesKey}-${dataIndex}`}
|
|
150
|
+
r={circleRadii}
|
|
151
|
+
cx={xScale(getXAxisData(d))}
|
|
152
|
+
cy={yScale(getYAxisData(d, seriesKey))}
|
|
153
|
+
fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
154
|
+
style={{ fill: colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000' }}
|
|
155
|
+
data-tip={tooltip}
|
|
156
|
+
data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
157
|
+
/>
|
|
158
|
+
}
|
|
159
|
+
</Group>
|
|
160
|
+
)
|
|
161
|
+
})}
|
|
162
|
+
<LinePath
|
|
163
|
+
curve={allCurves.curveLinear}
|
|
164
|
+
data={data}
|
|
165
|
+
x={(d) => xScale(getXAxisData(d))}
|
|
166
|
+
y={(d) => yScale(getYAxisData(d, seriesKey))}
|
|
167
|
+
stroke={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
168
|
+
strokeWidth={2}
|
|
169
|
+
strokeOpacity={1}
|
|
170
|
+
shapeRendering="geometricPrecision"
|
|
171
|
+
marker-end="url(#arrow)"
|
|
172
|
+
|
|
173
|
+
/>
|
|
174
|
+
<MarkerArrow
|
|
175
|
+
id="arrow"
|
|
176
|
+
refX={2}
|
|
177
|
+
size={6}
|
|
178
|
+
marker-end="url(#arrow)"
|
|
179
|
+
fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
180
|
+
/>
|
|
181
|
+
|
|
182
|
+
</Group>
|
|
183
|
+
<AxisBottom
|
|
184
|
+
top={yMax + margin.top}
|
|
185
|
+
hideAxisLine
|
|
186
|
+
hideTicks
|
|
187
|
+
scale={xScale}
|
|
188
|
+
tickValues={ handleSparkLineTicks }
|
|
189
|
+
tickFormat={formatDate}
|
|
190
|
+
stroke={'black'}
|
|
191
|
+
tickStroke={'black'}
|
|
192
|
+
tickLabelProps={() => ({
|
|
193
|
+
fill: 'black',
|
|
194
|
+
fontSize: 11,
|
|
195
|
+
textAnchor: 'middle',
|
|
196
|
+
})}
|
|
197
|
+
|
|
198
|
+
/>
|
|
199
|
+
</>
|
|
200
|
+
))
|
|
201
|
+
}
|
|
202
|
+
</svg>
|
|
203
|
+
</ErrorBoundary>
|
|
204
|
+
|
|
205
|
+
);
|
|
206
|
+
}
|
|
@@ -1,34 +1,61 @@
|
|
|
1
1
|
export default {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
2
|
+
type: 'chart',
|
|
3
|
+
title: '',
|
|
4
|
+
theme: 'theme-blue',
|
|
5
|
+
fontSize: 'medium',
|
|
6
|
+
lineDatapointStyle: 'hover',
|
|
7
|
+
barHasBorder: 'false',
|
|
8
|
+
isLollipopChart: false,
|
|
9
|
+
lollipopShape: 'circle',
|
|
10
|
+
lollipopColorStyle: 'two-tone',
|
|
11
|
+
visualizationSubType: 'regular',
|
|
12
|
+
padding: {
|
|
13
|
+
left: 5,
|
|
14
|
+
right: 5
|
|
15
|
+
},
|
|
16
|
+
yAxis: {
|
|
17
|
+
hideAxis: false,
|
|
18
|
+
hideLabel: false,
|
|
19
|
+
hideTicks: false,
|
|
20
|
+
size: 50,
|
|
21
|
+
gridLines: false,
|
|
22
|
+
min: undefined,
|
|
23
|
+
max:undefined
|
|
24
|
+
},
|
|
25
|
+
barThickness: 0.35,
|
|
26
|
+
height: 300,
|
|
27
|
+
xAxis: {
|
|
28
|
+
type: 'categorical',
|
|
29
|
+
hideAxis: false,
|
|
30
|
+
hideLabel: false,
|
|
31
|
+
hideTicks: false,
|
|
32
|
+
size: 75,
|
|
33
|
+
tickRotation: 0,
|
|
34
|
+
min: undefined,
|
|
35
|
+
max:undefined
|
|
36
|
+
},
|
|
37
|
+
table: {
|
|
38
|
+
label: 'Data Table',
|
|
39
|
+
expanded: true
|
|
40
|
+
},
|
|
41
|
+
orientation: 'vertical',
|
|
42
|
+
legend: {
|
|
43
|
+
behavior: 'isolate',
|
|
44
|
+
position: 'right',
|
|
45
|
+
reverseLabelOrder:false
|
|
46
|
+
},
|
|
47
|
+
exclusions: {
|
|
48
|
+
active: false,
|
|
49
|
+
keys: []
|
|
50
|
+
},
|
|
51
|
+
palette: 'qualitative-bold',
|
|
52
|
+
isPaletteReversed: false,
|
|
53
|
+
labels: false,
|
|
54
|
+
dataFormat: {},
|
|
55
|
+
confidenceKeys: {},
|
|
56
|
+
visual: {
|
|
57
|
+
border: true,
|
|
58
|
+
accent: true,
|
|
59
|
+
background: true
|
|
60
|
+
}
|
|
34
61
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {useState, useEffect} from 'react'
|
|
2
|
+
// Use for accessibility testing
|
|
3
|
+
const useActiveElement = () => {
|
|
4
|
+
const [active, setActive] = useState(document.activeElement);
|
|
5
|
+
|
|
6
|
+
const handleFocusIn = (e) => {
|
|
7
|
+
setActive(document.activeElement);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
document.addEventListener('focusin', handleFocusIn)
|
|
12
|
+
return () => {
|
|
13
|
+
document.removeEventListener('focusin', handleFocusIn)
|
|
14
|
+
};
|
|
15
|
+
}, [])
|
|
16
|
+
|
|
17
|
+
return active;
|
|
18
|
+
}
|
|
19
|
+
export default useActiveElement;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useEffect, useReducer } from 'react';
|
|
2
|
+
|
|
3
|
+
// constants
|
|
4
|
+
const SEQUENTIAL = 'SEQUENTIAL';
|
|
5
|
+
const SEQUENTIAL_REVERSE = 'SEQUENTIAL_REVERSE';
|
|
6
|
+
export const GET_PALETTE = 'GET_PALETTE';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
// types & interfaces
|
|
10
|
+
interface State {
|
|
11
|
+
readonly filteredPallets:string[]
|
|
12
|
+
readonly filteredQualitative:string[]
|
|
13
|
+
readonly isPaletteReversed:boolean;
|
|
14
|
+
paletteName:string|undefined
|
|
15
|
+
}
|
|
16
|
+
interface Action<Palettes> {
|
|
17
|
+
type:
|
|
18
|
+
| 'SEQUENTIAL'
|
|
19
|
+
| 'SEQUENTIAL_REVERSE'
|
|
20
|
+
| 'GET_PALETTE'
|
|
21
|
+
payload: Palettes;
|
|
22
|
+
paletteName?:string
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// create initial state
|
|
26
|
+
const initialState:State = {
|
|
27
|
+
filteredPallets: [],
|
|
28
|
+
isPaletteReversed:false,
|
|
29
|
+
filteredQualitative: [],
|
|
30
|
+
paletteName:undefined
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function reducer<T> (state:State,action:Action<T>):State{ // <T> refers to generic type
|
|
34
|
+
const palletNamesArr:string[] = Object.keys(action.payload); // action.payload === colorPalettes object
|
|
35
|
+
let paletteName:string ='';
|
|
36
|
+
switch(action.type){
|
|
37
|
+
case GET_PALETTE:
|
|
38
|
+
|
|
39
|
+
return {...state,paletteName:action.paletteName}
|
|
40
|
+
case SEQUENTIAL:
|
|
41
|
+
paletteName = state.paletteName && state.paletteName.endsWith('reverse') ? String(state.paletteName).substring(0,state.paletteName.length-7) :String(state.paletteName)
|
|
42
|
+
const qualitative = palletNamesArr.filter((name:string)=>String(name).startsWith('qualitative') && !String(name).endsWith('reverse'))
|
|
43
|
+
const sequential = palletNamesArr.filter((name:string)=>String(name).startsWith('sequential') && !String(name).endsWith('reverse'))
|
|
44
|
+
return { ...state, filteredPallets: sequential,filteredQualitative:qualitative, paletteName:paletteName,isPaletteReversed:false};
|
|
45
|
+
|
|
46
|
+
case SEQUENTIAL_REVERSE:
|
|
47
|
+
paletteName = palletNamesArr.find((name:string)=> name === String(state.paletteName).concat('reverse') || name === String(state.paletteName).concat('-reverse') )
|
|
48
|
+
const qualitativeReverse:string[] = palletNamesArr.filter((name:string)=>String(name).startsWith('qualitative') && String(name).endsWith('reverse'));
|
|
49
|
+
const sequentialReverse:string[] = palletNamesArr.filter((name:string)=>String(name).startsWith('sequential') && String(name).endsWith('reverse'));
|
|
50
|
+
return { ...state, filteredQualitative:qualitativeReverse, filteredPallets: sequentialReverse ,paletteName:paletteName,isPaletteReversed:true};
|
|
51
|
+
default : return state;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
interface Keyable {
|
|
56
|
+
palette:string
|
|
57
|
+
isPaletteReversed:boolean
|
|
58
|
+
}
|
|
59
|
+
function init(initialState){
|
|
60
|
+
return initialState
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function useColorPalette<T,Y extends Keyable>(colorPalettes:T,configState:Y){
|
|
64
|
+
const [state, dispatch] = useReducer(reducer, initialState,init);
|
|
65
|
+
const {paletteName,isPaletteReversed,filteredPallets,filteredQualitative} = state
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
dispatch({ type: SEQUENTIAL, payload: colorPalettes });
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
useEffect(()=>{
|
|
76
|
+
if(configState.isPaletteReversed){
|
|
77
|
+
dispatch({ type: "SEQUENTIAL_REVERSE", payload: colorPalettes });
|
|
78
|
+
return ()=> dispatch({ type: "SEQUENTIAL", payload: colorPalettes });
|
|
79
|
+
}
|
|
80
|
+
},[configState.isPaletteReversed,dispatch,colorPalettes])
|
|
81
|
+
|
|
82
|
+
return {paletteName,isPaletteReversed,filteredPallets,filteredQualitative, dispatch}
|
|
83
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
|
|
2
|
+
function useReduceData(config,data) {
|
|
3
|
+
|
|
4
|
+
const getMaxValueFromData = ()=>{
|
|
5
|
+
let max; // will hold max number from data.
|
|
6
|
+
|
|
7
|
+
if (config.visualizationType === 'Bar' && config.visualizationSubType === 'stacked') {
|
|
8
|
+
const yTotals = data.reduce((allTotals, xValue) => {
|
|
9
|
+
const totalYValues = config.runtime.seriesKeys.reduce((yTotal, k) => {
|
|
10
|
+
yTotal += Number(xValue[k]);
|
|
11
|
+
return yTotal;
|
|
12
|
+
}, 0);
|
|
13
|
+
allTotals.push(totalYValues);
|
|
14
|
+
if(totalYValues > max){
|
|
15
|
+
max = totalYValues;
|
|
16
|
+
}
|
|
17
|
+
return allTotals;
|
|
18
|
+
}, [] as number[]);
|
|
19
|
+
|
|
20
|
+
max = Math.max(...yTotals);
|
|
21
|
+
} else if(config.visualizationType === 'Bar' && config.confidenceKeys && config.confidenceKeys.upper) {
|
|
22
|
+
max = Math.max(...data.map((d) => Number(d[config.confidenceKeys.upper])));
|
|
23
|
+
} else {
|
|
24
|
+
max = Math.max(...data.map((d) => Math.max(...config.runtime.seriesKeys.map((key) => Number(d[key])))));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return max;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const getMinValueFromData = ()=> {
|
|
31
|
+
let min;
|
|
32
|
+
const minNumberFromData = Math.min(...data.map((d) => Math.min(...config.runtime.seriesKeys.map((key) => Number(d[key])))));
|
|
33
|
+
min = String(minNumberFromData)
|
|
34
|
+
|
|
35
|
+
return min;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const maxValue = getMaxValueFromData();
|
|
39
|
+
const minValue = getMinValueFromData()
|
|
40
|
+
return {minValue,maxValue}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default useReduceData
|
package/src/index.html
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<meta
|
|
6
|
-
name="viewport"
|
|
7
|
-
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta
|
|
6
|
+
name="viewport"
|
|
7
|
+
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
|
8
8
|
/>
|
|
9
9
|
<style>
|
|
10
10
|
body {
|
|
@@ -18,15 +18,51 @@
|
|
|
18
18
|
margin-top: 3rem;
|
|
19
19
|
}
|
|
20
20
|
</style>
|
|
21
|
-
</head>
|
|
21
|
+
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<!-- <div class="react-container" data-config="/examples/temp-example-config.json"></div> -->
|
|
24
|
+
|
|
25
|
+
<!-- <select id="cove_select">
|
|
26
|
+
<option value=""">Choose An Option</option>
|
|
27
|
+
<option value="Non-Hispanic White">Non-Hispanic White</option>
|
|
28
|
+
<option value="Hispanic or Latino">Test 2</option>
|
|
29
|
+
</select> -->
|
|
30
|
+
|
|
24
31
|
<!-- <div class="react-container" data-config="/examples/covid-example-config.json"></div> -->
|
|
25
32
|
<!-- <div class="react-container" data-config="/examples/cutoff-example-config.json"></div> -->
|
|
26
33
|
<!-- <div class="react-container" data-config="/examples/covid-confidence-example-config.json"></div> -->
|
|
27
|
-
|
|
28
|
-
<div class="react-container" data-config="/examples/planet-
|
|
34
|
+
<div class="react-container" data-config="/examples/planet-example-config.json"></div>
|
|
35
|
+
<!-- <div class="react-container" data-config="/examples/planet-chart-horizontal-example-config.json"></div> -->
|
|
36
|
+
<!-- <div class="react-container" data-config="/examples/planet-combo-example-config.json"></div> -->
|
|
29
37
|
<!-- <div class="react-container" data-config="/examples/planet-pie-example-config.json"></div> -->
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
<!-- <div class="react-container" data-config="/examples/date-exclusions-config.json"></div> -->
|
|
39
|
+
<!--<div class="react-container" data-config="/examples/case-rate-example-config.json"></div>-->
|
|
40
|
+
<!-- <div class="react-container" data-config="/examples/private/example-bar-chart.json"></div> -->
|
|
41
|
+
|
|
42
|
+
<!-- LINE -->
|
|
43
|
+
<!-- <div class="react-container" data-config="/examples/line-chart.json"></div> -->
|
|
44
|
+
|
|
45
|
+
<!-- DATA PRESENTATION GALLERY: https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/bar-chart.html#examples -->
|
|
46
|
+
|
|
47
|
+
<!-- HORIZONTAL BAR CHARTS -->
|
|
48
|
+
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-horizontal/horizontal-bar-chart-with-numbers-on-bar.json"></div> -->
|
|
49
|
+
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json"></div> -->
|
|
50
|
+
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-horizontal/horizontal-stacked.json"></div> -->
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
<!-- VERTICAL BAR CHARTS -->
|
|
54
|
+
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-vertical/combo-line-chart.json"></div> -->
|
|
55
|
+
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json"></div> -->
|
|
56
|
+
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json"></div> -->
|
|
57
|
+
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-vertical/vertical-bar-with-confidence.json"></div> -->
|
|
58
|
+
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-vertical/vertical-bar-chart.json"></div> -->
|
|
59
|
+
|
|
60
|
+
<!-- LOLLIPOP CHARTS -->
|
|
61
|
+
<!-- <div class="react-container" data-config="/examples/gallery/lollipop/lollipop-style-horizontal.json"></div> -->
|
|
62
|
+
|
|
63
|
+
<!-- PAIRED BAR CHARTS -->
|
|
64
|
+
<!-- <div class="react-container" data-config="/examples/gallery/paired-bar/paired-bar-chart.json"></div> -->
|
|
65
|
+
|
|
66
|
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
67
|
+
</body>
|
|
68
|
+
</html>
|
package/src/index.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { publish, subscribe } from '@cdc/core/helpers/events';
|
|
1
2
|
import React from 'react';
|
|
2
3
|
import { render } from 'react-dom';
|
|
3
4
|
|
|
4
|
-
import CdcChart from './CdcChart
|
|
5
|
+
import CdcChart from './CdcChart';
|
|
5
6
|
|
|
6
7
|
const domContainers = document.querySelectorAll('.react-container');
|
|
7
8
|
|
|
@@ -10,7 +11,10 @@ let isEditor = window.location.href.includes('editor=true');
|
|
|
10
11
|
domContainers.forEach((domContainer) => {
|
|
11
12
|
render(
|
|
12
13
|
<React.StrictMode>
|
|
13
|
-
<CdcChart
|
|
14
|
+
<CdcChart
|
|
15
|
+
configUrl={domContainer.attributes['data-config'].value}
|
|
16
|
+
isEditor={isEditor}
|
|
17
|
+
/>
|
|
14
18
|
</React.StrictMode>,
|
|
15
19
|
domContainer,
|
|
16
20
|
);
|