@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.
- package/dist/cdcchart.js +85 -0
- 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/covid-confidence-example-config.json +35 -0
- package/examples/covid-example-config.json +36 -0
- package/examples/covid-example-data-confidence.json +32 -0
- package/examples/covid-example-data.json +22 -0
- package/examples/cutoff-example-config.json +36 -0
- package/examples/cutoff-example-data.json +38 -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-combo-example-config.json +31 -0
- package/examples/planet-example-config.json +35 -0
- package/examples/planet-example-data.json +56 -0
- package/examples/planet-pie-example-config.json +28 -0
- package/examples/private/newtest.csv +101 -0
- package/examples/private/test.json +10124 -0
- package/examples/temp-example-config.json +57 -0
- package/examples/temp-example-data.json +130 -0
- package/package.json +9 -8
- package/src/CdcChart.tsx +836 -0
- package/src/components/BarChart.tsx +571 -0
- package/src/components/BarStackVertical.js +0 -0
- package/src/components/DataTable.tsx +229 -0
- package/src/components/EditorPanel.js +1319 -0
- package/src/components/LineChart.tsx +76 -0
- package/src/components/LinearChart.tsx +459 -0
- package/src/components/PairedBarChart.tsx +144 -0
- package/src/components/PieChart.tsx +189 -0
- package/src/components/SparkLine.js +206 -0
- package/src/context.tsx +5 -0
- package/src/data/initial-state.js +61 -0
- package/src/hooks/useActiveElement.js +19 -0
- package/src/hooks/useColorPalette.ts +83 -0
- package/src/hooks/useReduceData.ts +43 -0
- package/src/images/active-checkmark.svg +1 -0
- package/src/images/asc.svg +1 -0
- package/src/images/desc.svg +1 -0
- package/src/images/inactive-checkmark.svg +1 -0
- package/src/images/warning.svg +1 -0
- package/src/index.html +68 -0
- package/src/index.tsx +21 -0
- package/src/scss/DataTable.scss +23 -0
- package/src/scss/LinearChart.scss +0 -0
- package/src/scss/editor-panel.scss +693 -0
- package/src/scss/main.scss +426 -0
- package/src/scss/mixins.scss +0 -0
- package/src/scss/variables.scss +1 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import React, { useContext, useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { animated, useTransition, interpolate } from 'react-spring';
|
|
3
|
+
import ReactTooltip from 'react-tooltip';
|
|
4
|
+
|
|
5
|
+
import Pie, { ProvidedProps, PieArcDatum } from '@visx/shape/lib/shapes/Pie';
|
|
6
|
+
import chroma from "chroma-js";
|
|
7
|
+
import { Group } from '@visx/group';
|
|
8
|
+
import { Text } from '@visx/text';
|
|
9
|
+
|
|
10
|
+
import Context from '../context';
|
|
11
|
+
|
|
12
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
|
|
13
|
+
|
|
14
|
+
// react-spring transition definitions
|
|
15
|
+
type PieStyles = { startAngle: number; endAngle: number };
|
|
16
|
+
|
|
17
|
+
const enterUpdateTransition = ({ startAngle, endAngle }: PieArcDatum<any>) => ({
|
|
18
|
+
startAngle,
|
|
19
|
+
endAngle,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export default function PieChart() {
|
|
23
|
+
const { transformedData: data, config, dimensions, seriesHighlight, colorScale, formatNumber, currentViewport } = useContext<any>(Context);
|
|
24
|
+
|
|
25
|
+
const [filteredData, setFilteredData] = useState<any>(undefined);
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
type AnimatedPieProps<Datum> = ProvidedProps<Datum> & {
|
|
30
|
+
animate?: boolean;
|
|
31
|
+
getKey: (d: PieArcDatum<Datum>) => string;
|
|
32
|
+
delay?: number;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function AnimatedPie<Datum>({
|
|
36
|
+
arcs,
|
|
37
|
+
path,
|
|
38
|
+
getKey,
|
|
39
|
+
}: AnimatedPieProps<Datum>) {
|
|
40
|
+
const transitions = useTransition<PieArcDatum<Datum>, PieStyles>(
|
|
41
|
+
arcs,
|
|
42
|
+
getKey,
|
|
43
|
+
// @ts-ignore react-spring doesn't like this overload
|
|
44
|
+
{
|
|
45
|
+
from: enterUpdateTransition,
|
|
46
|
+
enter: enterUpdateTransition,
|
|
47
|
+
update: enterUpdateTransition,
|
|
48
|
+
leave: enterUpdateTransition,
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<>
|
|
54
|
+
{transitions.map(
|
|
55
|
+
({
|
|
56
|
+
item: arc,
|
|
57
|
+
props,
|
|
58
|
+
key,
|
|
59
|
+
}: {
|
|
60
|
+
item: PieArcDatum<Datum>;
|
|
61
|
+
props: PieStyles;
|
|
62
|
+
key: string;
|
|
63
|
+
}) => {
|
|
64
|
+
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${formatNumber(arc.data[config.runtime.yAxis.dataKey])}` : formatNumber(arc.data[config.runtime.yAxis.dataKey])
|
|
65
|
+
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${arc.data[config.runtime.xAxis.dataKey]}` : arc.data[config.runtime.xAxis.dataKey]
|
|
66
|
+
|
|
67
|
+
const tooltip = `<div>
|
|
68
|
+
${yAxisTooltip}<br />
|
|
69
|
+
${xAxisTooltip}<br />`
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Group key={key} style={{ opacity: (config.legend.behavior === "highlight" && seriesHighlight.length > 0 && seriesHighlight.indexOf(arc.data[config.runtime.xAxis.dataKey]) === -1) ? 0.5 : 1 }}>
|
|
73
|
+
<animated.path
|
|
74
|
+
// compute interpolated path d attribute from intermediate angle values
|
|
75
|
+
d={interpolate([props.startAngle, props.endAngle], (startAngle, endAngle) => path({
|
|
76
|
+
...arc,
|
|
77
|
+
startAngle,
|
|
78
|
+
endAngle,
|
|
79
|
+
}))}
|
|
80
|
+
fill={colorScale(arc.data[config.runtime.xAxis.dataKey])}
|
|
81
|
+
data-tip={tooltip}
|
|
82
|
+
data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
83
|
+
/>
|
|
84
|
+
</Group>
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
)}
|
|
88
|
+
{transitions.map(
|
|
89
|
+
({
|
|
90
|
+
item: arc,
|
|
91
|
+
key,
|
|
92
|
+
}: {
|
|
93
|
+
item: PieArcDatum<Datum>;
|
|
94
|
+
props: PieStyles;
|
|
95
|
+
key: string;
|
|
96
|
+
}) => {
|
|
97
|
+
|
|
98
|
+
const [centroidX, centroidY] = path.centroid(arc);
|
|
99
|
+
const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1;
|
|
100
|
+
|
|
101
|
+
let textColor = "#FFF";
|
|
102
|
+
if (chroma.contrast(textColor, colorScale(arc.data[config.runtime.xAxis.dataKey])) < 3.5) {
|
|
103
|
+
textColor = "000";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<animated.g key={key}>
|
|
108
|
+
{hasSpaceForLabel && (
|
|
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>
|
|
124
|
+
)}
|
|
125
|
+
</animated.g>
|
|
126
|
+
);
|
|
127
|
+
},
|
|
128
|
+
)}
|
|
129
|
+
</>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let [ width ] = dimensions;
|
|
134
|
+
|
|
135
|
+
if(config && config.legend && !config.legend.hide && currentViewport === 'lg') {
|
|
136
|
+
width = width * 0.73;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const height = config.aspectRatio ? (width * config.aspectRatio) : config.height;
|
|
140
|
+
|
|
141
|
+
const radius = Math.min(width, height) / 2;
|
|
142
|
+
const centerY = height / 2;
|
|
143
|
+
const centerX = width / 2;
|
|
144
|
+
const donutThickness = (config.pieType === "Donut") ? 75 : radius;
|
|
145
|
+
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if(seriesHighlight.length > 0 && config.legend.behavior !== "highlight"){
|
|
148
|
+
let newFilteredData = [];
|
|
149
|
+
|
|
150
|
+
data.forEach((d) => {
|
|
151
|
+
if(seriesHighlight.indexOf(d[config.runtime.xAxis.dataKey]) !== -1) {
|
|
152
|
+
newFilteredData.push(d);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
setFilteredData(newFilteredData);
|
|
157
|
+
} else {
|
|
158
|
+
setFilteredData(undefined);
|
|
159
|
+
}
|
|
160
|
+
}, [seriesHighlight]);
|
|
161
|
+
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
ReactTooltip.rebuild();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<ErrorBoundary component="PieChart">
|
|
168
|
+
<svg width={width} height={height}>
|
|
169
|
+
<Group top={centerY} left={centerX}>
|
|
170
|
+
<Pie
|
|
171
|
+
data={filteredData || data}
|
|
172
|
+
pieValue={d => d[config.runtime.yAxis.dataKey]}
|
|
173
|
+
pieSortValues={() => -1}
|
|
174
|
+
innerRadius={radius - donutThickness}
|
|
175
|
+
outerRadius={radius}
|
|
176
|
+
>
|
|
177
|
+
{pie => (
|
|
178
|
+
<AnimatedPie<any>
|
|
179
|
+
{...pie}
|
|
180
|
+
getKey={d => d.data[config.runtime.xAxis.dataKey]}
|
|
181
|
+
/>
|
|
182
|
+
)}
|
|
183
|
+
</Pie>
|
|
184
|
+
</Group>
|
|
185
|
+
</svg>
|
|
186
|
+
<ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} html={true} type="light" arrowColor="rgba(0,0,0,0)" className="tooltip"/>
|
|
187
|
+
</ErrorBoundary>
|
|
188
|
+
)
|
|
189
|
+
}
|
|
@@ -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
|
+
}
|
package/src/context.tsx
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export default {
|
|
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
|
+
}
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-2 -2)" fill="none" fill-rule="evenodd"><path d="M0 0h24v24H0z"/><circle fill="#FFF" cx="12" cy="12" r="8"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" fill="#4C4C4C" fill-rule="nonzero"/></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg viewBox="0 0 10 5" xmlns="http://www.w3.org/2000/svg"><path d="M0 5l5-5 5 5z" fill="#FFF" fill-rule="nonzero"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg viewBox="0 0 10 5" xmlns="http://www.w3.org/2000/svg"><path d="M0 0l5 5 5-5z" fill="#FFF" fill-rule="nonzero"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M-2-2h24v24H-2z"/><path d="M10 1.5A8.504 8.504 0 001.5 10c0 4.692 3.808 8.5 8.5 8.5s8.5-3.808 8.5-8.5-3.808-8.5-8.5-8.5z" stroke="#4C4C4C" stroke-width="3"/></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/></svg>
|