@cdc/chart 4.22.11 → 4.23.2
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 +54569 -16
- package/examples/Barchart_with_negative.json +34 -0
- package/examples/box-plot-data.json +71 -0
- package/examples/box-plot.csv +5 -0
- package/examples/box-plot.json +124 -0
- package/examples/dynamic-legends.json +1 -1
- package/examples/example-bar-chart-nonnumeric.json +36 -0
- package/examples/example-bar-chart.json +33 -0
- package/examples/example-combo-bar-nonnumeric.json +105 -0
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +3 -1
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +1 -1
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +86 -17
- package/examples/gallery/paired-bar/paired-bar-chart.json +65 -13
- package/examples/line-chart-nonnumeric.json +32 -0
- package/examples/line-chart.json +21 -63
- package/examples/new-data.csv +17 -0
- package/examples/newdata.json +90 -0
- package/examples/planet-combo-example-config.json +143 -20
- package/examples/planet-example-data-nonnumeric.json +56 -0
- package/examples/planet-example-data.json +2 -2
- package/examples/planet-pie-example-config-nonnumeric.json +30 -0
- package/examples/scatterplot-continuous.csv +17 -0
- package/examples/{private/yaxis-test.json → scatterplot.json} +53 -50
- package/examples/sparkline-chart-nonnumeric.json +76 -0
- package/examples/stacked-vertical-bar-example-negative.json +154 -0
- package/examples/stacked-vertical-bar-example-nonnumerics.json +154 -0
- package/{src/index.html → index.html} +18 -11
- package/package.json +29 -22
- package/src/{CdcChart.tsx → CdcChart.jsx} +193 -119
- package/src/components/BarChart.jsx +517 -0
- package/src/components/BoxPlot.jsx +88 -0
- package/src/components/{DataTable.tsx → DataTable.jsx} +125 -32
- package/src/components/{EditorPanel.js → EditorPanel.jsx} +376 -115
- package/src/components/Filters.jsx +125 -0
- package/src/components/Legend.jsx +303 -0
- package/src/components/{LineChart.tsx → LineChart.jsx} +87 -22
- package/src/components/{LinearChart.tsx → LinearChart.jsx} +172 -113
- package/src/components/{PairedBarChart.tsx → PairedBarChart.jsx} +46 -79
- package/src/components/{PieChart.tsx → PieChart.jsx} +29 -34
- package/src/components/ScatterPlot.jsx +48 -0
- package/src/components/{SparkLine.js → SparkLine.jsx} +49 -18
- package/src/components/useIntersectionObserver.jsx +29 -0
- package/src/data/initial-state.js +44 -8
- package/src/hooks/{useColorPalette.ts → useColorPalette.js} +10 -28
- package/src/hooks/{useReduceData.ts → useReduceData.js} +27 -13
- package/src/hooks/useRightAxis.js +3 -1
- package/src/index.jsx +16 -0
- package/src/scss/DataTable.scss +23 -1
- package/src/scss/main.scss +83 -32
- package/vite.config.js +4 -0
- package/examples/private/filters.json +0 -170
- package/examples/private/line-test-data.json +0 -22
- package/examples/private/line-test-two.json +0 -210
- package/examples/private/line-test.json +0 -102
- package/examples/private/new.json +0 -48800
- package/examples/private/newtest.csv +0 -101
- package/examples/private/shawn.json +0 -1106
- package/examples/private/test.json +0 -10124
- package/examples/private/yaxis-testing.csv +0 -27
- package/examples/private/yaxis.json +0 -28
- package/src/components/BarChart.tsx +0 -579
- package/src/components/Legend.js +0 -284
- package/src/components/useIntersectionObserver.tsx +0 -27
- package/src/index.tsx +0 -18
- /package/src/{context.tsx → ConfigContext.jsx} +0 -0
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
2
|
import { Group } from '@visx/group'
|
|
3
3
|
import { Bar } from '@visx/shape'
|
|
4
|
-
import { scaleLinear
|
|
4
|
+
import { scaleLinear } from '@visx/scale'
|
|
5
5
|
import { Text } from '@visx/text'
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import ConfigContext from '../ConfigContext'
|
|
8
8
|
import chroma from 'chroma-js'
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
height: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
|
|
16
|
-
const { config, colorScale, transformedData, formatNumber, seriesHighlight } = useContext<any>(Context)
|
|
10
|
+
const PairedBarChart = ({ width, height, originalWidth }) => {
|
|
11
|
+
const { config, colorScale, transformedData: data, formatNumber, seriesHighlight, getTextWidth } = useContext(ConfigContext)
|
|
17
12
|
|
|
18
13
|
if (!config || config?.series?.length < 2) return
|
|
19
14
|
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
15
|
+
const borderWidth = config.barHasBorder === 'true' ? 1 : 0
|
|
16
|
+
const halfWidth = width / 2
|
|
17
|
+
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
18
|
+
const offset = 1.02 // Offset of the left bar from the Axis
|
|
24
19
|
|
|
25
20
|
const groupOne = {
|
|
26
21
|
parentKey: config.dataDescription.seriesKey,
|
|
@@ -45,15 +40,10 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
|
|
|
45
40
|
}
|
|
46
41
|
|
|
47
42
|
const xScale = scaleLinear({
|
|
48
|
-
domain: [0, Math.max(groupOne.max, groupTwo.max)],
|
|
43
|
+
domain: [0, Math.max(groupOne.max * offset, groupTwo.max * 1.1)],
|
|
49
44
|
range: [0, halfWidth]
|
|
50
45
|
})
|
|
51
46
|
|
|
52
|
-
const yScale = scaleBand({
|
|
53
|
-
range: [0, adjustedHeight],
|
|
54
|
-
domain: data.map(d => d[config.dataDescription.xKey])
|
|
55
|
-
})
|
|
56
|
-
|
|
57
47
|
// Set label color
|
|
58
48
|
let labelColor = '#000000'
|
|
59
49
|
|
|
@@ -65,11 +55,13 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
|
|
|
65
55
|
groupTwo.labelColor = '#FFFFFF'
|
|
66
56
|
}
|
|
67
57
|
|
|
58
|
+
const label = config.yAxis.label ? `${config.yAxis.label}: ` : ''
|
|
59
|
+
|
|
68
60
|
const dataTipOne = d => {
|
|
69
61
|
return `<p>
|
|
70
62
|
${config.dataDescription.seriesKey}: ${groupOne.dataKey}<br/>
|
|
71
63
|
${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
|
|
72
|
-
${
|
|
64
|
+
${label}${formatNumber(d[groupOne.dataKey])}
|
|
73
65
|
</p>`
|
|
74
66
|
}
|
|
75
67
|
|
|
@@ -77,13 +69,10 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
|
|
|
77
69
|
return `<p>
|
|
78
70
|
${config.dataDescription.seriesKey}: ${groupTwo.dataKey}<br/>
|
|
79
71
|
${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
|
|
80
|
-
${
|
|
72
|
+
${label}${formatNumber(d[groupTwo.dataKey])}
|
|
81
73
|
</p>`
|
|
82
74
|
}
|
|
83
|
-
|
|
84
|
-
const isLabelBelowBar = config.yAxis.labelPlacement === 'Below Bar'
|
|
85
|
-
const isLabelOnYAxis = config.yAxis.labelPlacement === 'On Date/Category Axis'
|
|
86
|
-
const isLabelMissing = !config.yAxis.labelPlacement
|
|
75
|
+
console.log(config)
|
|
87
76
|
|
|
88
77
|
return (
|
|
89
78
|
width > 0 && (
|
|
@@ -96,8 +85,8 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
|
|
|
96
85
|
}
|
|
97
86
|
`}
|
|
98
87
|
</style>
|
|
99
|
-
<svg id='cdc-visualization__paired-bar-chart' width={
|
|
100
|
-
<Group top={0} left={config.xAxis.size}>
|
|
88
|
+
<svg id='cdc-visualization__paired-bar-chart' width={originalWidth} height={height} viewBox={`0 0 ${width + Number(config.runtime.yAxis.size)} ${height}`} role='img' tabIndex={0}>
|
|
89
|
+
<Group top={0} left={Number(config.xAxis.size)}>
|
|
101
90
|
{data
|
|
102
91
|
.filter(item => config.series[0].dataKey === groupOne.dataKey)
|
|
103
92
|
.map((d, index) => {
|
|
@@ -105,26 +94,14 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
|
|
|
105
94
|
let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(config.series[0].dataKey) !== -1
|
|
106
95
|
let barWidth = xScale(d[config.series[0].dataKey])
|
|
107
96
|
let barHeight = Number(config.barHeight) ? Number(config.barHeight) : 25
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
} else {
|
|
117
|
-
config.barPadding = barPadding
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
config.barPadding = barPadding / 2
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
config.height = Number(barHeight) * data.length + config.barPadding * data.length
|
|
125
|
-
|
|
126
|
-
let y = yScale([d[config.dataDescription.xKey]]) + config.barHeight / 1.5
|
|
127
|
-
y = Number(config.barPadding) > 20 ? (y += Number(config.barPadding / 3.5) - config.barHeight / 2) : (y += 0)
|
|
97
|
+
// update bar Y to give dynamic Y when user applyes BarSpace
|
|
98
|
+
let y = 0
|
|
99
|
+
y = index !== 0 ? (Number(config.barSpace) + barHeight + borderWidth) * index : y
|
|
100
|
+
const totalheight = (Number(config.barSpace) + barHeight + borderWidth) * data.length
|
|
101
|
+
config.heights.horizontal = totalheight
|
|
102
|
+
// check if text fits inside of the bar including suffix/prefix,comma,fontSize ..etc
|
|
103
|
+
const textWidth = getTextWidth(formatNumber(d[groupOne.dataKey]), `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
104
|
+
const textFits = textWidth < barWidth - 5 // minus padding dx(5)
|
|
128
105
|
|
|
129
106
|
return (
|
|
130
107
|
<>
|
|
@@ -138,15 +115,15 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
|
|
|
138
115
|
width={xScale(d[config.series[0].dataKey])}
|
|
139
116
|
height={barHeight}
|
|
140
117
|
fill={groupOne.color}
|
|
141
|
-
data-
|
|
142
|
-
data-
|
|
118
|
+
data-tooltip-html={dataTipOne(d)}
|
|
119
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
143
120
|
stroke='#333'
|
|
144
|
-
strokeWidth={
|
|
121
|
+
strokeWidth={borderWidth}
|
|
145
122
|
opacity={transparentBar ? 0.5 : 1}
|
|
146
123
|
display={displayBar ? 'block' : 'none'}
|
|
147
124
|
/>
|
|
148
125
|
{config.yAxis.displayNumbersOnBar && displayBar && (
|
|
149
|
-
<Text textAnchor={
|
|
126
|
+
<Text textAnchor={textFits ? 'start' : 'end'} dx={textFits ? 5 : -5} verticalAnchor='middle' x={halfWidth - barWidth} y={y + config.barHeight / 2} fill={textFits ? groupOne.labelColor : '#000'}>
|
|
150
127
|
{formatNumber(d[groupOne.dataKey])}
|
|
151
128
|
</Text>
|
|
152
129
|
)}
|
|
@@ -156,38 +133,28 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
|
|
|
156
133
|
})}
|
|
157
134
|
{data
|
|
158
135
|
.filter(item => config.series[1].dataKey === groupTwo.dataKey)
|
|
159
|
-
.map(d => {
|
|
136
|
+
.map((d, index) => {
|
|
160
137
|
let barWidth = xScale(d[config.series[1].dataKey])
|
|
161
138
|
let transparentBar = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(config.series[1].dataKey) === -1
|
|
162
139
|
let displayBar = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(config.series[1].dataKey) !== -1
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
let
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (isLabelBelowBar || isLabelMissing || isLabelOnYAxis) {
|
|
173
|
-
if (barHeight < 40) {
|
|
174
|
-
config.barPadding = 40
|
|
175
|
-
} else {
|
|
176
|
-
config.barPadding = barPadding
|
|
177
|
-
}
|
|
178
|
-
} else {
|
|
179
|
-
config.barPadding = barPadding / 2
|
|
180
|
-
}
|
|
181
|
-
}
|
|
140
|
+
let barHeight = config.barHeight ? Number(config.barHeight) : 25
|
|
141
|
+
// update bar Y to give dynamic Y when user applyes BarSpace
|
|
142
|
+
let y = 0
|
|
143
|
+
y = index !== 0 ? (Number(config.barSpace) + barHeight + borderWidth) * index : y
|
|
144
|
+
const totalheight = (Number(config.barSpace) + barHeight + borderWidth) * data.length
|
|
145
|
+
config.heights.horizontal = totalheight
|
|
146
|
+
// check if text fits inside of the bar including suffix/prefix,comma,fontSize ..etc
|
|
147
|
+
const textWidth = getTextWidth(formatNumber(d[groupTwo.dataKey]), `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
148
|
+
const isTextFits = textWidth < barWidth - 5 // minus padding dx(5)
|
|
182
149
|
|
|
183
150
|
return (
|
|
184
151
|
<>
|
|
185
152
|
<style>
|
|
186
153
|
{`
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
154
|
+
.bar-${groupTwo.dataKey}-${d[config.xAxis.dataKey]} {
|
|
155
|
+
transform-origin: ${halfWidth}px ${y}px
|
|
156
|
+
}
|
|
157
|
+
`}
|
|
191
158
|
</style>
|
|
192
159
|
<Group key={`group-${groupTwo.dataKey}-${d[config.dataDescription.xKey]}`} className='horizontal'>
|
|
193
160
|
<Bar
|
|
@@ -199,15 +166,15 @@ const PairedBarChart: React.FC<PairedBarChartProps> = ({ width, height }) => {
|
|
|
199
166
|
width={xScale(d[config.series[1].dataKey])}
|
|
200
167
|
height={barHeight}
|
|
201
168
|
fill={groupTwo.color}
|
|
202
|
-
data-
|
|
203
|
-
data-
|
|
204
|
-
strokeWidth={
|
|
169
|
+
data-tooltip-html={dataTipTwo(d)}
|
|
170
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
171
|
+
strokeWidth={borderWidth}
|
|
205
172
|
stroke='#333'
|
|
206
173
|
opacity={transparentBar ? 0.5 : 1}
|
|
207
174
|
display={displayBar ? 'block' : 'none'}
|
|
208
175
|
/>
|
|
209
176
|
{config.yAxis.displayNumbersOnBar && displayBar && (
|
|
210
|
-
<Text textAnchor={
|
|
177
|
+
<Text textAnchor={isTextFits ? 'end' : 'start'} dx={isTextFits ? -5 : 5} verticalAnchor='middle' x={halfWidth + barWidth} y={y + config.barHeight / 2} fill={isTextFits ? groupTwo.labelColor : '#000'}>
|
|
211
178
|
{formatNumber(d[groupTwo.dataKey])}
|
|
212
179
|
</Text>
|
|
213
180
|
)}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useContext, useState, useEffect, useRef } from 'react'
|
|
2
2
|
import { animated, useTransition, interpolate } from 'react-spring'
|
|
3
|
-
import ReactTooltip from 'react-tooltip'
|
|
3
|
+
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
4
4
|
|
|
5
5
|
import Pie, { ProvidedProps, PieArcDatum } from '@visx/shape/lib/shapes/Pie'
|
|
6
6
|
import chroma from 'chroma-js'
|
|
@@ -8,29 +8,38 @@ import { Group } from '@visx/group'
|
|
|
8
8
|
import { Text } from '@visx/text'
|
|
9
9
|
import useIntersectionObserver from './useIntersectionObserver'
|
|
10
10
|
|
|
11
|
-
import
|
|
11
|
+
import ConfigContext from '../ConfigContext'
|
|
12
12
|
|
|
13
13
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
type PieStyles = { startAngle: number; endAngle: number }
|
|
17
|
-
|
|
18
|
-
const enterUpdateTransition = ({ startAngle, endAngle }: PieArcDatum<any>) => ({
|
|
15
|
+
const enterUpdateTransition = ({ startAngle, endAngle }) => ({
|
|
19
16
|
startAngle,
|
|
20
17
|
endAngle
|
|
21
18
|
})
|
|
22
19
|
|
|
23
20
|
export default function PieChart() {
|
|
24
|
-
const { transformedData: data, config, dimensions, seriesHighlight, colorScale, formatNumber, currentViewport, handleChartAriaLabels } = useContext
|
|
21
|
+
const { transformedData: data, config, dimensions, seriesHighlight, colorScale, formatNumber, currentViewport, handleChartAriaLabels, cleanData } = useContext(ConfigContext)
|
|
22
|
+
|
|
23
|
+
const cleanedData = cleanData(data, config.xAxis.dataKey);
|
|
25
24
|
|
|
26
|
-
const [filteredData, setFilteredData] = useState
|
|
27
|
-
const [animatedPie, setAnimatePie] = useState
|
|
25
|
+
const [filteredData, setFilteredData] = useState(undefined)
|
|
26
|
+
const [animatedPie, setAnimatePie] = useState(false)
|
|
28
27
|
|
|
29
28
|
const triggerRef = useRef()
|
|
30
29
|
const dataRef = useIntersectionObserver(triggerRef, {
|
|
31
30
|
freezeOnceVisible: false
|
|
32
31
|
})
|
|
33
32
|
|
|
33
|
+
// Make sure the chart is visible if in the editor
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const element = document.querySelector('.isEditor')
|
|
36
|
+
if (element) {
|
|
37
|
+
// parent element is visible
|
|
38
|
+
console.log('setAnimation')
|
|
39
|
+
setAnimatePie(prevState => true)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
34
43
|
useEffect(() => {
|
|
35
44
|
if (dataRef?.isIntersecting && config.animate && !animatedPie) {
|
|
36
45
|
setTimeout(() => {
|
|
@@ -39,17 +48,9 @@ export default function PieChart() {
|
|
|
39
48
|
}
|
|
40
49
|
}, [dataRef?.isIntersecting, config.animate])
|
|
41
50
|
|
|
42
|
-
type AnimatedPieProps<Datum> = ProvidedProps<Datum> & {
|
|
43
|
-
animate?: boolean
|
|
44
|
-
getKey: (d: PieArcDatum<Datum>) => string
|
|
45
|
-
delay?: number
|
|
46
|
-
}
|
|
47
51
|
|
|
48
|
-
function AnimatedPie
|
|
49
|
-
const transitions = useTransition
|
|
50
|
-
arcs,
|
|
51
|
-
getKey,
|
|
52
|
-
// @ts-ignore react-spring doesn't like this overload
|
|
52
|
+
function AnimatedPie({ arcs, path, getKey }) {
|
|
53
|
+
const transitions = useTransition( arcs, getKey,
|
|
53
54
|
{
|
|
54
55
|
from: enterUpdateTransition,
|
|
55
56
|
enter: enterUpdateTransition,
|
|
@@ -60,7 +61,7 @@ export default function PieChart() {
|
|
|
60
61
|
|
|
61
62
|
return (
|
|
62
63
|
<>
|
|
63
|
-
{transitions.map(({ item: arc, props, key }
|
|
64
|
+
{transitions.map(({ item: arc, props, key }) => {
|
|
64
65
|
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
66
|
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${arc.data[config.runtime.xAxis.dataKey]}` : arc.data[config.runtime.xAxis.dataKey]
|
|
66
67
|
|
|
@@ -69,11 +70,9 @@ export default function PieChart() {
|
|
|
69
70
|
${xAxisTooltip}<br />
|
|
70
71
|
Percent: ${Math.round((((arc.endAngle - arc.startAngle) * 180) / Math.PI / 360) * 100) + '%'}
|
|
71
72
|
`
|
|
72
|
-
|
|
73
73
|
return (
|
|
74
74
|
<Group key={key} style={{ opacity: config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(arc.data[config.runtime.xAxis.dataKey]) === -1 ? 0.5 : 1 }}>
|
|
75
75
|
<animated.path
|
|
76
|
-
// compute interpolated path d attribute from intermediate angle values
|
|
77
76
|
d={interpolate([props.startAngle, props.endAngle], (startAngle, endAngle) =>
|
|
78
77
|
path({
|
|
79
78
|
...arc,
|
|
@@ -82,13 +81,13 @@ export default function PieChart() {
|
|
|
82
81
|
})
|
|
83
82
|
)}
|
|
84
83
|
fill={colorScale(arc.data[config.runtime.xAxis.dataKey])}
|
|
85
|
-
data-
|
|
86
|
-
data-
|
|
84
|
+
data-tooltip-html={tooltip}
|
|
85
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
87
86
|
/>
|
|
88
87
|
</Group>
|
|
89
88
|
)
|
|
90
89
|
})}
|
|
91
|
-
{transitions.map(({ item: arc, key }
|
|
90
|
+
{transitions.map(({ item: arc, key }) => {
|
|
92
91
|
const [centroidX, centroidY] = path.centroid(arc)
|
|
93
92
|
const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1
|
|
94
93
|
|
|
@@ -117,7 +116,7 @@ export default function PieChart() {
|
|
|
117
116
|
width = width * 0.73
|
|
118
117
|
}
|
|
119
118
|
|
|
120
|
-
const height = config.
|
|
119
|
+
const height = config.heights.vertical
|
|
121
120
|
|
|
122
121
|
const radius = Math.min(width, height) / 2
|
|
123
122
|
const centerY = height / 2
|
|
@@ -140,21 +139,17 @@ export default function PieChart() {
|
|
|
140
139
|
}
|
|
141
140
|
}, [seriesHighlight])
|
|
142
141
|
|
|
143
|
-
useEffect(() => {
|
|
144
|
-
ReactTooltip.rebuild()
|
|
145
|
-
})
|
|
146
|
-
|
|
147
142
|
return (
|
|
148
143
|
<ErrorBoundary component='PieChart'>
|
|
149
|
-
<svg width={width} height={height} className={`group ${animatedPie ? 'animated' : ''}`} role='img' aria-label={handleChartAriaLabels(config)}>
|
|
144
|
+
<svg width={width} height={height} className={`animated-pie group ${config.animate === false || animatedPie ? 'animated' : ''}`} role='img' aria-label={handleChartAriaLabels(config)}>
|
|
150
145
|
<Group top={centerY} left={centerX}>
|
|
151
|
-
<Pie data={filteredData ||
|
|
152
|
-
{pie => <AnimatedPie
|
|
146
|
+
<Pie data={filteredData || cleanedData} pieValue={d => d[config.runtime.yAxis.dataKey]} pieSortValues={() => -1} innerRadius={radius - donutThickness} outerRadius={radius}>
|
|
147
|
+
{pie => <AnimatedPie {...pie} getKey={d => d.data[config.runtime.xAxis.dataKey]} />}
|
|
153
148
|
</Pie>
|
|
154
149
|
</Group>
|
|
155
150
|
</svg>
|
|
156
151
|
<div ref={triggerRef} />
|
|
157
|
-
<ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
152
|
+
<ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' />
|
|
158
153
|
</ErrorBoundary>
|
|
159
154
|
)
|
|
160
155
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
import ConfigContext from '../ConfigContext'
|
|
3
|
+
import { Group } from '@visx/group'
|
|
4
|
+
|
|
5
|
+
const CoveScatterPlot = ({ xScale, yScale, getXAxisData, getYAxisData }) => {
|
|
6
|
+
const { colorScale, transformedData: data, config } = useContext(ConfigContext)
|
|
7
|
+
|
|
8
|
+
// copied from line chart
|
|
9
|
+
// should probably be a constant somewhere.
|
|
10
|
+
let circleRadii = 4.5
|
|
11
|
+
|
|
12
|
+
let pointStyles = {
|
|
13
|
+
filter: 'unset',
|
|
14
|
+
opacity: 1,
|
|
15
|
+
stroke: 'black'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const handleTooltip = (item, s) => `<div>
|
|
19
|
+
${config.xAxis.label}: ${item[config.xAxis.dataKey]} <br/>
|
|
20
|
+
${config.yAxis.label}: ${item[s]}
|
|
21
|
+
</div>`
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Group className='scatter-plot' left={config.yAxis.size}>
|
|
25
|
+
{data.map((item, dataIndex) => {
|
|
26
|
+
// prettier-ignore
|
|
27
|
+
return config.runtime.seriesKeys.map(s => {
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<circle
|
|
31
|
+
key={`${dataIndex}`}
|
|
32
|
+
r={circleRadii}
|
|
33
|
+
cx={xScale(item[config.xAxis.dataKey])}
|
|
34
|
+
cy={yScale(item[s])}
|
|
35
|
+
fillOpacity={1}
|
|
36
|
+
opacity={1}
|
|
37
|
+
style={pointStyles}
|
|
38
|
+
fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[s] : s) : '#000'}
|
|
39
|
+
data-tooltip-html={handleTooltip(item, s)}
|
|
40
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
})}
|
|
45
|
+
</Group>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
export default CoveScatterPlot
|
|
@@ -10,16 +10,14 @@ import { MarkerArrow } from '@visx/marker'
|
|
|
10
10
|
|
|
11
11
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
12
12
|
|
|
13
|
-
import ReactTooltip from 'react-tooltip'
|
|
14
|
-
|
|
15
13
|
import useReduceData from '../hooks/useReduceData'
|
|
16
14
|
|
|
17
|
-
import
|
|
15
|
+
import ConfigContext from '../ConfigContext'
|
|
18
16
|
|
|
19
17
|
export default function SparkLine({ width: parentWidth, height: parentHeight }) {
|
|
20
|
-
const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, seriesHighlight, formatNumber, colorScale, handleChartAriaLabels } = useContext(
|
|
18
|
+
const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, seriesHighlight, formatNumber, colorScale, isNumber, handleChartAriaLabels } = useContext(ConfigContext)
|
|
21
19
|
let width = parentWidth
|
|
22
|
-
const { minValue, maxValue } = useReduceData(config, data)
|
|
20
|
+
const { minValue, maxValue } = useReduceData(config, data, ConfigContext)
|
|
23
21
|
|
|
24
22
|
const margin = { top: 5, right: 10, bottom: 10, left: 10 }
|
|
25
23
|
const height = parentHeight
|
|
@@ -37,7 +35,44 @@ export default function SparkLine({ width: parentWidth, height: parentHeight })
|
|
|
37
35
|
const isMaxValid = Number(enteredMaxValue) >= Number(maxValue)
|
|
38
36
|
const isMinValid = Number(enteredMinValue) <= Number(minValue)
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
|
|
39
|
+
// REMOVE bad data points from the data set
|
|
40
|
+
// Examples: NA, N/A, "1,234", "anystring"
|
|
41
|
+
// - if you dont call this on data into LineGroup below, for example
|
|
42
|
+
// then entire data series are removed because of the defined statement
|
|
43
|
+
// i.e. if a series has any bad data points the entire series wont plot
|
|
44
|
+
const cleanData = (data, testing = false) => {
|
|
45
|
+
let cleanedup = []
|
|
46
|
+
if (testing) console.log('## Data to clean=', data)
|
|
47
|
+
data.forEach(function (d, i) {
|
|
48
|
+
let cleanedSeries = {}
|
|
49
|
+
Object.keys(d).forEach(function (key) {
|
|
50
|
+
if (key === 'Date') {
|
|
51
|
+
// pass thru the dates
|
|
52
|
+
cleanedSeries[key] = d[key]
|
|
53
|
+
} else {
|
|
54
|
+
// remove comma and dollar signs
|
|
55
|
+
let tmp = d[key] != null && d[key] != '' ? d[key].replace(/[,\$]/g, '') : ''
|
|
56
|
+
if (testing) console.log("tmp no comma or $", tmp)
|
|
57
|
+
if ((tmp !== '' && tmp !== null && !isNaN(tmp)) || (tmp !== '' && tmp !== null && /\d+\.?\d*/.test(tmp))) {
|
|
58
|
+
cleanedSeries[key] = tmp
|
|
59
|
+
} else {
|
|
60
|
+
// return nothing to skip bad data point
|
|
61
|
+
cleanedSeries[key] = '' // returning blank fixes broken chart draw
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
cleanedup.push(cleanedSeries)
|
|
66
|
+
})
|
|
67
|
+
if (testing) console.log('## cleanedData =', cleanedup)
|
|
68
|
+
return cleanedup
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Just do this once up front otherwise we end up
|
|
72
|
+
// calling clean several times on same set of data (TT)
|
|
73
|
+
const cleanedData = cleanData(data, config.xAxis.dataKey);
|
|
74
|
+
|
|
75
|
+
if (cleanedData) {
|
|
41
76
|
let min = enteredMinValue && isMinValid ? enteredMinValue : minValue
|
|
42
77
|
let max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
|
|
43
78
|
|
|
@@ -52,7 +87,7 @@ export default function SparkLine({ width: parentWidth, height: parentHeight })
|
|
|
52
87
|
max += paddingValue
|
|
53
88
|
}
|
|
54
89
|
|
|
55
|
-
let xAxisDataMapped =
|
|
90
|
+
let xAxisDataMapped = cleanedData.map(d => getXAxisData(d))
|
|
56
91
|
|
|
57
92
|
if (config.runtime.horizontal) {
|
|
58
93
|
xScale = scaleLinear({
|
|
@@ -90,10 +125,6 @@ export default function SparkLine({ width: parentWidth, height: parentHeight })
|
|
|
90
125
|
|
|
91
126
|
const handleSparkLineTicks = [xScale.domain()[0], xScale.domain()[xScale.domain().length - 1]]
|
|
92
127
|
|
|
93
|
-
useEffect(() => {
|
|
94
|
-
ReactTooltip.rebuild()
|
|
95
|
-
})
|
|
96
|
-
|
|
97
128
|
return (
|
|
98
129
|
<ErrorBoundary component='SparkLine'>
|
|
99
130
|
<svg role='img' aria-label={handleChartAriaLabels(config)} width={width} height={height} className={'sparkline'} tabIndex={0}>
|
|
@@ -108,14 +139,14 @@ export default function SparkLine({ width: parentWidth, height: parentHeight })
|
|
|
108
139
|
opacity={config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(seriesKey) === -1 ? 0.5 : 1}
|
|
109
140
|
display={config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
|
|
110
141
|
>
|
|
111
|
-
{
|
|
142
|
+
{cleanedData.map((d, dataIndex) => {
|
|
112
143
|
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${formatNumber(getYAxisData(d, seriesKey))}` : formatNumber(getYAxisData(d, seriesKey))
|
|
113
144
|
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${d[config.runtime.xAxis.dataKey]}` : d[config.runtime.xAxis.dataKey]
|
|
114
145
|
|
|
115
146
|
const tooltip = `<div>
|
|
116
147
|
${yAxisTooltip}<br />
|
|
117
148
|
${xAxisTooltip}<br />
|
|
118
|
-
${config.seriesLabel ? `${config.seriesLabel}: ${seriesKey}` : ''}
|
|
149
|
+
${config.seriesLabel ? `${config.seriesLabel}: ${seriesKey}` : ''}
|
|
119
150
|
</div>`
|
|
120
151
|
|
|
121
152
|
let circleRadii = 4.5
|
|
@@ -133,8 +164,8 @@ export default function SparkLine({ width: parentWidth, height: parentHeight })
|
|
|
133
164
|
cy={yScale(getYAxisData(d, seriesKey))}
|
|
134
165
|
fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
135
166
|
style={{ fill: colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000' }}
|
|
136
|
-
data-
|
|
137
|
-
data-
|
|
167
|
+
data-tooltip-html={tooltip}
|
|
168
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
138
169
|
/>
|
|
139
170
|
)}
|
|
140
171
|
</Group>
|
|
@@ -142,20 +173,20 @@ export default function SparkLine({ width: parentWidth, height: parentHeight })
|
|
|
142
173
|
})}
|
|
143
174
|
<LinePath
|
|
144
175
|
curve={allCurves.curveLinear}
|
|
145
|
-
data={
|
|
176
|
+
data={cleanedData}
|
|
146
177
|
x={d => xScale(getXAxisData(d))}
|
|
147
178
|
y={d => yScale(getYAxisData(d, seriesKey))}
|
|
148
179
|
stroke={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
149
180
|
strokeWidth={2}
|
|
150
181
|
strokeOpacity={1}
|
|
151
182
|
shapeRendering='geometricPrecision'
|
|
152
|
-
|
|
183
|
+
markerEnd={`url(#${'arrow'}--${index})`}
|
|
153
184
|
/>
|
|
154
185
|
<MarkerArrow
|
|
155
186
|
id={`arrow--${index}`}
|
|
156
187
|
refX={2}
|
|
157
188
|
size={6}
|
|
158
|
-
|
|
189
|
+
markerEnd={`url(#${'arrow'}--${index})`}
|
|
159
190
|
strokeOpacity={1}
|
|
160
191
|
fillOpacity={1}
|
|
161
192
|
// stroke={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
export default function useIntersectionObserver(elementRef, { threshold = 0, root = null, rootMargin = '0%', freezeOnceVisible = false }) {
|
|
4
|
+
const [entry, setEntry] = useState()
|
|
5
|
+
|
|
6
|
+
const frozen = entry?.isIntersecting && freezeOnceVisible
|
|
7
|
+
|
|
8
|
+
const updateEntry = ([entry]) => {
|
|
9
|
+
setEntry(entry)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
setTimeout(() => {
|
|
14
|
+
const node = elementRef?.current
|
|
15
|
+
const hasIOSupport = !!window.IntersectionObserver
|
|
16
|
+
|
|
17
|
+
if (!hasIOSupport || frozen || !node) return
|
|
18
|
+
|
|
19
|
+
const observerParams = { threshold, root, rootMargin }
|
|
20
|
+
const observer = new IntersectionObserver(updateEntry, observerParams)
|
|
21
|
+
|
|
22
|
+
observer.observe(node)
|
|
23
|
+
|
|
24
|
+
return () => observer.disconnect()
|
|
25
|
+
}, 500);
|
|
26
|
+
}, [elementRef, threshold, root, rootMargin, frozen])
|
|
27
|
+
|
|
28
|
+
return entry
|
|
29
|
+
}
|