@cdc/chart 4.23.2 → 4.23.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 +41198 -39447
- package/examples/area-chart.json +187 -0
- package/examples/big-small-test-bar.json +328 -0
- package/examples/big-small-test-line.json +328 -0
- package/examples/big-small-test-negative.json +328 -0
- package/examples/box-plot.json +0 -1
- package/examples/example-bar-chart.json +4 -1
- package/examples/example-sparkline.json +76 -0
- package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +31 -172
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-confidence.json +1 -0
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +96 -14
- package/examples/gallery/line/line.json +1 -0
- package/examples/horizontal-chart-max-increase.json +38 -0
- package/examples/line-chart-max-increase.json +32 -0
- package/examples/line-chart-nonnumeric.json +5 -5
- package/examples/line-chart.json +6 -6
- package/examples/planet-deviation-config.json +168 -0
- package/examples/planet-deviation-data.json +38 -0
- package/examples/planet-example-config.json +139 -20
- package/examples/planet-example-data-max-increase.json +56 -0
- package/examples/planet-example-data.json +10 -10
- package/examples/scatterplot-continuous.csv +3 -3
- package/examples/scatterplot.json +2 -2
- package/examples/sparkline-chart-nonnumeric.json +3 -3
- package/index.html +26 -9
- package/package.json +6 -3
- package/src/CdcChart.jsx +146 -92
- package/src/components/AreaChart.jsx +198 -0
- package/src/components/BarChart.jsx +58 -34
- package/src/components/BoxPlot.jsx +28 -15
- package/src/components/DataTable.jsx +21 -17
- package/src/components/DeviationBar.jsx +191 -0
- package/src/components/EditorPanel.jsx +473 -168
- package/src/components/Filters.jsx +3 -2
- package/src/components/Legend.jsx +59 -46
- package/src/components/LineChart.jsx +3 -21
- package/src/components/LinearChart.jsx +158 -55
- package/src/components/PairedBarChart.jsx +0 -1
- package/src/components/PieChart.jsx +11 -14
- package/src/components/ScatterPlot.jsx +19 -16
- package/src/components/SparkLine.jsx +87 -85
- package/src/components/useIntersectionObserver.jsx +1 -1
- package/src/data/initial-state.js +20 -4
- package/src/hooks/useColorPalette.js +58 -48
- package/src/hooks/useReduceData.js +3 -4
- package/src/index.jsx +1 -1
- package/src/scss/editor-panel.scss +5 -0
- package/src/test/CdcChart.test.jsx +6 -0
|
@@ -3,7 +3,7 @@ import ConfigContext from './../ConfigContext'
|
|
|
3
3
|
import Button from '@cdc/core/components/elements/Button'
|
|
4
4
|
|
|
5
5
|
const useFilters = () => {
|
|
6
|
-
const { config, setConfig,
|
|
6
|
+
const { config, setConfig, setFilteredData, excludedData, filterData } = useContext(ConfigContext)
|
|
7
7
|
const [showApplyButton, setShowApplyButton] = useState(false)
|
|
8
8
|
|
|
9
9
|
const sortAsc = (a, b) => {
|
|
@@ -14,7 +14,7 @@ const useFilters = () => {
|
|
|
14
14
|
return b.toString().localeCompare(a.toString(), 'en', { numeric: true })
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const announceChange = text => {
|
|
17
|
+
const announceChange = text => {}
|
|
18
18
|
|
|
19
19
|
const changeFilterActive = (index, value) => {
|
|
20
20
|
let newFilters = config.filters
|
|
@@ -38,6 +38,7 @@ const useFilters = () => {
|
|
|
38
38
|
// reset to first item in values array.
|
|
39
39
|
newFilters.map(filter => {
|
|
40
40
|
filter.active = filter.values[0]
|
|
41
|
+
return null
|
|
41
42
|
})
|
|
42
43
|
|
|
43
44
|
setFilteredData(filterData(newFilters, excludedData))
|
|
@@ -7,7 +7,7 @@ import LegendCircle from '@cdc/core/components/LegendCircle'
|
|
|
7
7
|
import useLegendClasses from './../hooks/useLegendClasses'
|
|
8
8
|
|
|
9
9
|
const Legend = () => {
|
|
10
|
-
const { config, legend, colorScale, seriesHighlight, highlight, highlightReset, setSeriesHighlight, dynamicLegendItems, setDynamicLegendItems, transformedData: data, colorPalettes, rawData, setConfig, currentViewport } = useContext(ConfigContext)
|
|
10
|
+
const { config, legend, colorScale, seriesHighlight, highlight, twoColorPalette, highlightReset, setSeriesHighlight, dynamicLegendItems, setDynamicLegendItems, transformedData: data, colorPalettes, rawData, setConfig, currentViewport } = useContext(ConfigContext)
|
|
11
11
|
|
|
12
12
|
const { innerClasses, containerClasses } = useLegendClasses(config)
|
|
13
13
|
|
|
@@ -25,12 +25,14 @@ const Legend = () => {
|
|
|
25
25
|
let tmp = {}
|
|
26
26
|
colsToKeep.map(col => {
|
|
27
27
|
tmp[col] = isNaN(dataItem[col]) ? dataItem[col] : dataItem[col]
|
|
28
|
+
return null
|
|
28
29
|
})
|
|
29
30
|
return tmp
|
|
30
31
|
})
|
|
31
32
|
|
|
32
33
|
colsToKeep.map(col => {
|
|
33
34
|
tmpLabels[col] = col
|
|
35
|
+
return null
|
|
34
36
|
})
|
|
35
37
|
|
|
36
38
|
if (dynamicLegendItems.length > 0) {
|
|
@@ -43,7 +45,7 @@ const Legend = () => {
|
|
|
43
45
|
}
|
|
44
46
|
})
|
|
45
47
|
}
|
|
46
|
-
}, [dynamicLegendItems])
|
|
48
|
+
}, [dynamicLegendItems]) // eslint-disable-line
|
|
47
49
|
|
|
48
50
|
useEffect(() => {
|
|
49
51
|
if (dynamicLegendItems.length === 0) {
|
|
@@ -53,7 +55,9 @@ const Legend = () => {
|
|
|
53
55
|
config.runtime.seriesLabelsAll.map(item => {
|
|
54
56
|
resetSeriesNames.map(col => {
|
|
55
57
|
tmpLabels[col] = col
|
|
58
|
+
return null
|
|
56
59
|
})
|
|
60
|
+
return null
|
|
57
61
|
})
|
|
58
62
|
|
|
59
63
|
setConfig({
|
|
@@ -65,7 +69,7 @@ const Legend = () => {
|
|
|
65
69
|
}
|
|
66
70
|
})
|
|
67
71
|
}
|
|
68
|
-
}, [dynamicLegendItems])
|
|
72
|
+
}, [dynamicLegendItems]) // eslint-disable-line
|
|
69
73
|
|
|
70
74
|
const removeDynamicLegendItem = label => {
|
|
71
75
|
let newLegendItems = dynamicLegendItems.filter(item => item.text !== label.text)
|
|
@@ -77,67 +81,75 @@ const Legend = () => {
|
|
|
77
81
|
setDynamicLegendItems([...dynamicLegendItems, JSON.parse(e.target.value)])
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
const createLegendLabels =
|
|
84
|
+
const createLegendLabels = defaultLabels => {
|
|
81
85
|
const colorCode = config.legend?.colorCode
|
|
82
|
-
if (config.visualizationType
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
if (config.visualizationType === 'Deviation Bar') {
|
|
87
|
+
const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette]
|
|
88
|
+
const labelBelow = {
|
|
89
|
+
datum: 'X',
|
|
90
|
+
index: 0,
|
|
91
|
+
text: `Below ${config.xAxis.targetLabel}`,
|
|
92
|
+
value: belowColor
|
|
93
|
+
}
|
|
94
|
+
const labelAbove = {
|
|
95
|
+
datum: 'X',
|
|
96
|
+
index: 1,
|
|
97
|
+
text: `Above ${config.xAxis.targetLabel}`,
|
|
98
|
+
value: aboveColor
|
|
99
|
+
}
|
|
86
100
|
|
|
87
|
-
|
|
88
|
-
palette = palette.concat(palette)
|
|
101
|
+
return [labelBelow, labelAbove]
|
|
89
102
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// create labels with uniq values
|
|
97
|
-
const uniqeLabels = Array.from(set).map((val, i) => {
|
|
98
|
-
const newLabel = {
|
|
99
|
-
datum: val,
|
|
100
|
-
index: i,
|
|
101
|
-
text: val,
|
|
102
|
-
value: palette[i]
|
|
103
|
+
if (config.visualizationType === 'Bar' && config.visualizationSubType === 'regular' && colorCode && config.series?.length === 1) {
|
|
104
|
+
let palette = colorPalettes[config.palette]
|
|
105
|
+
|
|
106
|
+
while (data.length > palette.length) {
|
|
107
|
+
palette = palette.concat(palette)
|
|
103
108
|
}
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
palette = palette.slice(0, data.length)
|
|
110
|
+
//store uniq values to Set by colorCode
|
|
111
|
+
const set = new Set()
|
|
112
|
+
|
|
113
|
+
data.forEach(d => set.add(d[colorCode]))
|
|
114
|
+
|
|
115
|
+
// create labels with uniq values
|
|
116
|
+
const uniqeLabels = Array.from(set).map((val, i) => {
|
|
117
|
+
const newLabel = {
|
|
118
|
+
datum: val,
|
|
119
|
+
index: i,
|
|
120
|
+
text: val,
|
|
121
|
+
value: palette[i]
|
|
122
|
+
}
|
|
123
|
+
return newLabel
|
|
124
|
+
})
|
|
106
125
|
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
// in small screens update config legend position.
|
|
110
|
-
useEffect(() => {
|
|
111
|
-
if (currentViewport === 'sm' || currentViewport === 'xs' || config.legend.position === 'left') {
|
|
112
|
-
setConfig({ ...config, legend: { ...config.legend, position: 'bottom' } })
|
|
126
|
+
return uniqeLabels
|
|
113
127
|
}
|
|
114
|
-
|
|
115
|
-
}
|
|
128
|
+
return defaultLabels
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const isBottomOrSmallViewport = config.legend.position === 'bottom' || currentViewport === 'sm' || currentViewport === 'xs'
|
|
132
|
+
const isHorizontal = config.orientation === 'horizontal'
|
|
133
|
+
const marginTop = isBottomOrSmallViewport && isHorizontal ? `${config.runtime.xAxis.size}px` : '0px'
|
|
134
|
+
const marginBottom = isBottomOrSmallViewport ? '15px' : '0px'
|
|
116
135
|
|
|
117
|
-
if (!legend) return
|
|
136
|
+
if (!legend) return null
|
|
118
137
|
|
|
119
138
|
if (!legend.dynamicLegend)
|
|
120
139
|
return config.visualizationType !== 'Box Plot' ? (
|
|
121
|
-
<aside
|
|
122
|
-
style={{ marginTop: config.legend.position === 'bottom' && config.orientation === 'horizontal' ? `${config.runtime.xAxis.size}px` : '0px', marginBottom: config.legend.position === 'bottom' ? '15px' : '0px' }}
|
|
123
|
-
id='legend'
|
|
124
|
-
className={containerClasses.join(' ')}
|
|
125
|
-
role='region'
|
|
126
|
-
aria-label='legend'
|
|
127
|
-
tabIndex={0}
|
|
128
|
-
>
|
|
140
|
+
<aside style={{ marginTop, marginBottom }} id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
|
|
129
141
|
{legend.label && <h2>{parse(legend.label)}</h2>}
|
|
130
142
|
{legend.description && <p>{parse(legend.description)}</p>}
|
|
131
143
|
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
132
144
|
{labels => (
|
|
133
145
|
<div className={innerClasses.join(' ')}>
|
|
134
|
-
{createLegendLabels(
|
|
146
|
+
{createLegendLabels(labels).map((label, i) => {
|
|
135
147
|
let className = 'legend-item'
|
|
136
148
|
let itemName = label.datum
|
|
137
149
|
|
|
138
150
|
// Filter excluded data keys from legend
|
|
139
151
|
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
140
|
-
return
|
|
152
|
+
return null
|
|
141
153
|
}
|
|
142
154
|
|
|
143
155
|
if (config.runtime.seriesLabels) {
|
|
@@ -209,7 +221,7 @@ const Legend = () => {
|
|
|
209
221
|
|
|
210
222
|
// Filter excluded data keys from legend
|
|
211
223
|
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
212
|
-
return
|
|
224
|
+
return null
|
|
213
225
|
}
|
|
214
226
|
|
|
215
227
|
if (config.runtime.seriesLabels) {
|
|
@@ -225,6 +237,7 @@ const Legend = () => {
|
|
|
225
237
|
if (listItem.text === label.text) {
|
|
226
238
|
inDynamicList = true
|
|
227
239
|
}
|
|
240
|
+
return null
|
|
228
241
|
})
|
|
229
242
|
|
|
230
243
|
if (inDynamicList) return true
|
|
@@ -254,7 +267,7 @@ const Legend = () => {
|
|
|
254
267
|
|
|
255
268
|
// Filter excluded data keys from legend
|
|
256
269
|
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
257
|
-
return
|
|
270
|
+
return null
|
|
258
271
|
}
|
|
259
272
|
|
|
260
273
|
if (config.runtime.seriesLabels && !config.legend.dynamicLegend) {
|
|
@@ -12,28 +12,15 @@ import ConfigContext from '../ConfigContext'
|
|
|
12
12
|
import useRightAxis from '../hooks/useRightAxis'
|
|
13
13
|
|
|
14
14
|
export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, seriesStyle = 'Line' }) {
|
|
15
|
-
const { colorPalettes, transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, isNumber, cleanData, updateConfig } = useContext(ConfigContext)
|
|
15
|
+
const { colorPalettes, transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, isNumber, cleanData, updateConfig, handleLineType } = useContext(ConfigContext)
|
|
16
16
|
// Just do this once up front otherwise we end up
|
|
17
17
|
// calling clean several times on same set of data (TT)
|
|
18
18
|
const cleanedData = cleanData(data, config.xAxis.dataKey)
|
|
19
19
|
const { yScaleRight } = useRightAxis({ config, yMax, data, updateConfig })
|
|
20
20
|
|
|
21
|
-
const handleLineType = lineType => {
|
|
22
|
-
switch (lineType) {
|
|
23
|
-
case 'dashed-sm':
|
|
24
|
-
return '5 5'
|
|
25
|
-
case 'dashed-md':
|
|
26
|
-
return '10 5'
|
|
27
|
-
case 'dashed-lg':
|
|
28
|
-
return '15 5'
|
|
29
|
-
default:
|
|
30
|
-
return 0
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
21
|
const handleAxisFormating = (axis = 'left', label, value) => {
|
|
35
22
|
// if this is an x axis category/date value return without doing any formatting.
|
|
36
|
-
if (label === config.runtime.xAxis.label) return value
|
|
23
|
+
// if (label === config.runtime.xAxis.label) return value
|
|
37
24
|
|
|
38
25
|
axis = String(axis).toLocaleLowerCase()
|
|
39
26
|
if (label) {
|
|
@@ -86,11 +73,6 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
|
|
|
86
73
|
d[seriesKey] !== undefined &&
|
|
87
74
|
d[seriesKey] !== '' &&
|
|
88
75
|
d[seriesKey] !== null && (
|
|
89
|
-
// isNumber(d[seriesKey]) &&
|
|
90
|
-
// isNumber(getYAxisData(d, seriesKey)) &&
|
|
91
|
-
// isNumber(getXAxisData(d)) &&
|
|
92
|
-
// isNumber(yScaleRight(getXAxisData(d))) &&
|
|
93
|
-
// isNumber(yScale(getXAxisData(d))) &&
|
|
94
76
|
<Group key={`series-${seriesKey}-point-${dataIndex}`}>
|
|
95
77
|
{/* Render legend */}
|
|
96
78
|
<Text
|
|
@@ -100,7 +82,7 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
|
|
|
100
82
|
fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
101
83
|
textAnchor='middle'
|
|
102
84
|
>
|
|
103
|
-
{formatNumber(d[seriesKey])}
|
|
85
|
+
{formatNumber(d[seriesKey], 'left')}
|
|
104
86
|
</Text>
|
|
105
87
|
|
|
106
88
|
<circle
|
|
@@ -4,7 +4,7 @@ import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
|
4
4
|
import { Group } from '@visx/group'
|
|
5
5
|
import { Line } from '@visx/shape'
|
|
6
6
|
import { Text } from '@visx/text'
|
|
7
|
-
import { scaleLinear, scalePoint, scaleBand } from '@visx/scale'
|
|
7
|
+
import { scaleLinear, scalePoint, scaleBand, scaleTime } from '@visx/scale'
|
|
8
8
|
import { AxisLeft, AxisBottom, AxisRight, AxisTop } from '@visx/axis'
|
|
9
9
|
|
|
10
10
|
import CoveScatterPlot from './ScatterPlot'
|
|
@@ -14,16 +14,19 @@ import ConfigContext from '../ConfigContext'
|
|
|
14
14
|
import PairedBarChart from './PairedBarChart'
|
|
15
15
|
import useIntersectionObserver from './useIntersectionObserver'
|
|
16
16
|
import CoveBoxPlot from './BoxPlot'
|
|
17
|
+
import CoveAreaChart from './AreaChart'
|
|
17
18
|
|
|
18
19
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
19
20
|
import '../scss/LinearChart.scss'
|
|
20
21
|
import useReduceData from '../hooks/useReduceData'
|
|
21
22
|
import useRightAxis from '../hooks/useRightAxis'
|
|
22
23
|
import useTopAxis from '../hooks/useTopAxis'
|
|
24
|
+
import { DeviationBar } from './DeviationBar'
|
|
23
25
|
|
|
24
26
|
// TODO: Move scaling functions into hooks to manage complexity
|
|
25
27
|
export default function LinearChart() {
|
|
26
|
-
const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig } = useContext(ConfigContext)
|
|
28
|
+
const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, stringFormattingOptions } = useContext(ConfigContext)
|
|
29
|
+
|
|
27
30
|
let [width] = dimensions
|
|
28
31
|
const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, data)
|
|
29
32
|
const [animatedChart, setAnimatedChart] = useState(false)
|
|
@@ -32,14 +35,16 @@ export default function LinearChart() {
|
|
|
32
35
|
const dataRef = useIntersectionObserver(triggerRef, {
|
|
33
36
|
freezeOnceVisible: false
|
|
34
37
|
})
|
|
38
|
+
|
|
35
39
|
// Make sure the chart is visible if in the editor
|
|
40
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
36
41
|
useEffect(() => {
|
|
37
42
|
const element = document.querySelector('.isEditor')
|
|
38
43
|
if (element) {
|
|
39
44
|
// parent element is visible
|
|
40
45
|
setAnimatedChart(prevState => true)
|
|
41
46
|
}
|
|
42
|
-
})
|
|
47
|
+
}) /* eslint-disable-line */
|
|
43
48
|
|
|
44
49
|
// If the chart is in view, set to animate if it has not already played
|
|
45
50
|
useEffect(() => {
|
|
@@ -72,11 +77,37 @@ export default function LinearChart() {
|
|
|
72
77
|
const isMaxValid = existPositiveValue ? enteredMaxValue >= maxValue : enteredMaxValue >= 0
|
|
73
78
|
const isMinValid = (enteredMinValue <= 0 && minValue >= 0) || (enteredMinValue <= minValue && minValue < 0)
|
|
74
79
|
|
|
80
|
+
let max = 0 // need outside the if statement
|
|
81
|
+
let min = 0
|
|
75
82
|
if (data) {
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
min = enteredMinValue && isMinValid ? enteredMinValue : minValue
|
|
84
|
+
max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
|
|
85
|
+
|
|
86
|
+
// DEV-3263 - If Confidence Intervals in data, then need to account for increased height in max for YScale
|
|
87
|
+
if (config.visualizationType === 'Bar' || config.visualizationType === 'Combo' || config.visualizationType === 'Deviation Bar') {
|
|
88
|
+
let ciYMax = 0
|
|
89
|
+
if (config.hasOwnProperty('confidenceKeys')) {
|
|
90
|
+
let upperCIValues = data.map(function (d) {
|
|
91
|
+
return d[config.confidenceKeys.upper]
|
|
92
|
+
})
|
|
93
|
+
ciYMax = Math.max.apply(Math, upperCIValues)
|
|
94
|
+
if (ciYMax > max) max = ciYMax // bump up the max
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// DEV-3263 - If Confidence Intervals in data, then need to account for increased height in max for YScale
|
|
99
|
+
if (config.visualizationType === 'Bar' || config.visualizationType === 'Combo') {
|
|
100
|
+
let ciYMax = 0
|
|
101
|
+
if (config.hasOwnProperty('confidenceKeys')) {
|
|
102
|
+
let upperCIValues = data.map(function (d) {
|
|
103
|
+
return d[config.confidenceKeys.upper]
|
|
104
|
+
})
|
|
105
|
+
ciYMax = Math.max.apply(Math, upperCIValues)
|
|
106
|
+
if (ciYMax > max) max = ciYMax // bump up the max
|
|
107
|
+
}
|
|
108
|
+
}
|
|
78
109
|
|
|
79
|
-
if ((config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && !isAllLine)) && min > 0) {
|
|
110
|
+
if ((config.visualizationType === 'Bar' || config.visualizationType === 'Deviation Bar' || (config.visualizationType === 'Combo' && !isAllLine)) && min > 0) {
|
|
80
111
|
min = 0
|
|
81
112
|
}
|
|
82
113
|
if (config.visualizationType === 'Combo' && isAllLine) {
|
|
@@ -119,6 +150,19 @@ export default function LinearChart() {
|
|
|
119
150
|
case maxDataVal > 4 && maxDataVal <= 7:
|
|
120
151
|
max = max * 1.1
|
|
121
152
|
break
|
|
153
|
+
default:
|
|
154
|
+
break
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// DEV-3219 - bc some values are going above YScale - adding 10% or 20% factor onto Max
|
|
159
|
+
// - put the statement up here and it works for both vert and horiz charts of all types
|
|
160
|
+
if (config.yAxis.enablePadding) {
|
|
161
|
+
if (min < 0) {
|
|
162
|
+
// sets with negative data need more padding on the max
|
|
163
|
+
max *= 1.2
|
|
164
|
+
} else {
|
|
165
|
+
max *= 1.1
|
|
122
166
|
}
|
|
123
167
|
}
|
|
124
168
|
|
|
@@ -161,6 +205,13 @@ export default function LinearChart() {
|
|
|
161
205
|
})
|
|
162
206
|
}
|
|
163
207
|
|
|
208
|
+
if (config.visualizationType === 'Area Chart' && config.xAxis.type === 'date') {
|
|
209
|
+
xScale = scaleTime({
|
|
210
|
+
domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)],
|
|
211
|
+
range: [0, xMax]
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
164
215
|
if (config.visualizationType === 'Paired Bar') {
|
|
165
216
|
const offset = 1.02 // Offset of the ticks/values from the Axis
|
|
166
217
|
let groupOneMax = Math.max.apply(
|
|
@@ -194,6 +245,55 @@ export default function LinearChart() {
|
|
|
194
245
|
})
|
|
195
246
|
}
|
|
196
247
|
}
|
|
248
|
+
|
|
249
|
+
if (config.visualizationType === 'Deviation Bar') {
|
|
250
|
+
const leftOffset = config.isLollipopChart ? 1.05 : 1.03
|
|
251
|
+
yScale = scaleBand({
|
|
252
|
+
domain: xAxisDataMapped,
|
|
253
|
+
range: [0, yMax]
|
|
254
|
+
})
|
|
255
|
+
xScale = scaleLinear({
|
|
256
|
+
domain: [min * leftOffset, Math.max(Number(config.xAxis.target), max)],
|
|
257
|
+
range: [0, xMax],
|
|
258
|
+
round: true
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
// Handle Box Plots
|
|
262
|
+
if (config.visualizationType === 'Box Plot') {
|
|
263
|
+
const allOutliers = []
|
|
264
|
+
const hasOutliers = config.boxplot.plots.map(b => b.columnOutliers.map(outlier => allOutliers.push(outlier))) && !config.boxplot.hideOutliers
|
|
265
|
+
|
|
266
|
+
// check if outliers are lower
|
|
267
|
+
if (hasOutliers) {
|
|
268
|
+
let outlierMin = Math.min(...allOutliers)
|
|
269
|
+
let outlierMax = Math.max(...allOutliers)
|
|
270
|
+
|
|
271
|
+
// check if outliers exceed standard bounds
|
|
272
|
+
if (outlierMin < min) min = outlierMin
|
|
273
|
+
if (outlierMax > max) max = outlierMax
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// check fences for max/min
|
|
277
|
+
let lowestFence = Math.min(...config.boxplot.plots.map(item => item.columnMin))
|
|
278
|
+
let highestFence = Math.max(...config.boxplot.plots.map(item => item.columnMax))
|
|
279
|
+
|
|
280
|
+
if (lowestFence < min) min = lowestFence
|
|
281
|
+
if (highestFence > max) max = highestFence
|
|
282
|
+
|
|
283
|
+
// Set Scales
|
|
284
|
+
yScale = scaleLinear({
|
|
285
|
+
range: [yMax, 0],
|
|
286
|
+
round: true,
|
|
287
|
+
domain: [min, max]
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
xScale = scaleBand({
|
|
291
|
+
range: [0, xMax],
|
|
292
|
+
round: true,
|
|
293
|
+
domain: config.boxplot.categories,
|
|
294
|
+
padding: 0.4
|
|
295
|
+
})
|
|
296
|
+
}
|
|
197
297
|
}
|
|
198
298
|
|
|
199
299
|
const handleLeftTickFormatting = tick => {
|
|
@@ -204,7 +304,8 @@ export default function LinearChart() {
|
|
|
204
304
|
|
|
205
305
|
const handleBottomTickFormatting = tick => {
|
|
206
306
|
if (config.runtime.xAxis.type === 'date') return formatDate(tick)
|
|
207
|
-
if (config.orientation === 'horizontal') return formatNumber(tick, '
|
|
307
|
+
if (config.orientation === 'horizontal') return formatNumber(tick, 'left')
|
|
308
|
+
if (config.xAxis.type === 'continuous') return formatNumber(tick, 'bottom')
|
|
208
309
|
return tick
|
|
209
310
|
}
|
|
210
311
|
|
|
@@ -216,57 +317,36 @@ export default function LinearChart() {
|
|
|
216
317
|
|
|
217
318
|
if (axis === 'yAxis') {
|
|
218
319
|
tickCount = isHorizontal && !numTicks ? data.length : isHorizontal && numTicks ? numTicks : !isHorizontal && !numTicks ? undefined : !isHorizontal && numTicks && numTicks
|
|
320
|
+
// to fix edge case of small numbers with decimals
|
|
321
|
+
if (tickCount === undefined && !config.dataFormat.roundTo) {
|
|
322
|
+
// then it is set to Auto
|
|
323
|
+
if (Number(max) <= 3) {
|
|
324
|
+
tickCount = 2
|
|
325
|
+
} else {
|
|
326
|
+
tickCount = 4 // same default as standalone components
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (Number(tickCount) > Number(max)) {
|
|
330
|
+
// cap it and round it so its an integer
|
|
331
|
+
tickCount = Number(min) < 0 ? Math.round(max) * 2 : Math.round(max)
|
|
332
|
+
}
|
|
219
333
|
}
|
|
220
334
|
|
|
221
335
|
if (axis === 'xAxis') {
|
|
222
336
|
tickCount = isHorizontal && !numTicks ? undefined : isHorizontal && numTicks ? numTicks : !isHorizontal && !numTicks ? undefined : !isHorizontal && numTicks && numTicks
|
|
337
|
+
if (isHorizontal && tickCount === undefined && !config.dataFormat.roundTo) {
|
|
338
|
+
// then it is set to Auto
|
|
339
|
+
// - check for small numbers situation
|
|
340
|
+
if (max <= 3) {
|
|
341
|
+
tickCount = 2
|
|
342
|
+
} else {
|
|
343
|
+
tickCount = 4 // same default as standalone components
|
|
344
|
+
}
|
|
345
|
+
}
|
|
223
346
|
}
|
|
224
347
|
return tickCount
|
|
225
348
|
}
|
|
226
349
|
|
|
227
|
-
// Handle Box Plots
|
|
228
|
-
if (config.visualizationType === 'Box Plot') {
|
|
229
|
-
let minYValue
|
|
230
|
-
let maxYValue
|
|
231
|
-
let allOutliers = []
|
|
232
|
-
let allLowerBounds = config.boxplot.plots.map(plot => plot.columnMin)
|
|
233
|
-
let allUpperBounds = config.boxplot.plots.map(plot => plot.columnMax)
|
|
234
|
-
|
|
235
|
-
minYValue = Math.min(...allLowerBounds)
|
|
236
|
-
maxYValue = Math.max(...allUpperBounds)
|
|
237
|
-
|
|
238
|
-
const hasOutliers = config.boxplot.plots.map(b => b.columnOutliers.map(outlier => allOutliers.push(outlier))) && !config.boxplot.hideOutliers
|
|
239
|
-
|
|
240
|
-
if (hasOutliers) {
|
|
241
|
-
let outlierMin = Math.min(...allOutliers)
|
|
242
|
-
let outlierMax = Math.max(...allOutliers)
|
|
243
|
-
|
|
244
|
-
// check if outliers exceed standard bounds
|
|
245
|
-
if (outlierMin < minYValue) minYValue = outlierMin
|
|
246
|
-
if (outlierMax > maxYValue) maxYValue = outlierMax
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const seriesNames = data.map(d => d[config.xAxis.dataKey])
|
|
250
|
-
|
|
251
|
-
// Set Scales
|
|
252
|
-
yScale = scaleLinear({
|
|
253
|
-
range: [yMax, 0],
|
|
254
|
-
round: true,
|
|
255
|
-
domain: [minYValue, maxYValue]
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
xScale = scaleBand({
|
|
259
|
-
range: [0, xMax],
|
|
260
|
-
round: true,
|
|
261
|
-
domain: config.boxplot.categories,
|
|
262
|
-
padding: 0.4
|
|
263
|
-
})
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const handleTick = tick => {
|
|
267
|
-
return config.runtime.xAxis.type === 'date' ? formatDate(tick) : config.orientation === 'horizontal' ? formatNumber(tick) : tick
|
|
268
|
-
}
|
|
269
|
-
|
|
270
350
|
return isNaN(width) ? (
|
|
271
351
|
<></>
|
|
272
352
|
) : (
|
|
@@ -277,9 +357,24 @@ export default function LinearChart() {
|
|
|
277
357
|
? config.regions.map(region => {
|
|
278
358
|
if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
|
|
279
359
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
360
|
+
let from
|
|
361
|
+
let to
|
|
362
|
+
let width
|
|
363
|
+
|
|
364
|
+
if (config.xAxis.type === 'date') {
|
|
365
|
+
from = xScale(parseDate(region.from).getTime())
|
|
366
|
+
to = xScale(parseDate(region.to).getTime())
|
|
367
|
+
width = to - from
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (config.xAxis.type === 'categorical') {
|
|
371
|
+
from = xScale(region.from)
|
|
372
|
+
to = xScale(region.to)
|
|
373
|
+
width = to - from
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (!from) return null
|
|
377
|
+
if (!to) return null
|
|
283
378
|
|
|
284
379
|
return (
|
|
285
380
|
<Group className='regions' left={Number(config.runtime.yAxis.size)} key={region.label}>
|
|
@@ -340,6 +435,11 @@ export default function LinearChart() {
|
|
|
340
435
|
{tick.formattedValue}
|
|
341
436
|
</Text>
|
|
342
437
|
)}
|
|
438
|
+
{config.orientation === 'horizontal' && config.visualizationType === 'Deviation Bar' && !config.yAxis.hideLabel && (
|
|
439
|
+
<Text transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart ? tick.to.y - minY + 2 : tick.to.y - minY + Number(config.barHeight) / 2}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation : 0})`} textAnchor={'end'} verticalAnchor='middle'>
|
|
440
|
+
{tick.formattedValue}
|
|
441
|
+
</Text>
|
|
442
|
+
)}
|
|
343
443
|
|
|
344
444
|
{config.orientation !== 'horizontal' && config.visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
|
|
345
445
|
<Text
|
|
@@ -524,18 +624,20 @@ export default function LinearChart() {
|
|
|
524
624
|
</>
|
|
525
625
|
)}
|
|
526
626
|
|
|
627
|
+
{config.visualizationType === 'Deviation Bar' && <DeviationBar xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
|
|
628
|
+
|
|
527
629
|
{/* Paired Bar chart */}
|
|
528
630
|
{config.visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
|
|
529
631
|
|
|
530
632
|
{/* Bar chart */}
|
|
531
|
-
{config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Scatter Plot' && (
|
|
633
|
+
{config.visualizationType !== 'Line' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Area Chart' && config.visualizationType !== 'Scatter Plot' && config.visualizationType !== 'Deviation Bar' && (
|
|
532
634
|
<>
|
|
533
635
|
<BarChart xScale={xScale} yScale={yScale} seriesScale={seriesScale} xMax={xMax} yMax={yMax} getXAxisData={getXAxisData} getYAxisData={getYAxisData} animatedChart={animatedChart} visible={animatedChart} />
|
|
534
636
|
</>
|
|
535
637
|
)}
|
|
536
638
|
|
|
537
639
|
{/* Line chart */}
|
|
538
|
-
{config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Scatter Plot' && (
|
|
640
|
+
{config.visualizationType !== 'Bar' && config.visualizationType !== 'Paired Bar' && config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Area Chart' && config.visualizationType !== 'Scatter Plot' && config.visualizationType !== 'Deviation Bar' && (
|
|
539
641
|
<>
|
|
540
642
|
<LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
|
|
541
643
|
</>
|
|
@@ -546,6 +648,7 @@ export default function LinearChart() {
|
|
|
546
648
|
|
|
547
649
|
{/* Box Plot chart */}
|
|
548
650
|
{config.visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
|
|
651
|
+
{config.visualizationType === 'Area Chart' && <CoveAreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} />}
|
|
549
652
|
</svg>
|
|
550
653
|
<ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' />
|
|
551
654
|
<div className='animation-trigger' ref={triggerRef} />
|
|
@@ -2,7 +2,7 @@ import React, { useContext, useState, useEffect, useRef } from 'react'
|
|
|
2
2
|
import { animated, useTransition, interpolate } from 'react-spring'
|
|
3
3
|
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
4
4
|
|
|
5
|
-
import Pie
|
|
5
|
+
import Pie from '@visx/shape/lib/shapes/Pie'
|
|
6
6
|
import chroma from 'chroma-js'
|
|
7
7
|
import { Group } from '@visx/group'
|
|
8
8
|
import { Text } from '@visx/text'
|
|
@@ -20,7 +20,7 @@ const enterUpdateTransition = ({ startAngle, endAngle }) => ({
|
|
|
20
20
|
export default function PieChart() {
|
|
21
21
|
const { transformedData: data, config, dimensions, seriesHighlight, colorScale, formatNumber, currentViewport, handleChartAriaLabels, cleanData } = useContext(ConfigContext)
|
|
22
22
|
|
|
23
|
-
const cleanedData = cleanData(data, config.xAxis.dataKey)
|
|
23
|
+
const cleanedData = cleanData(data, config.xAxis.dataKey)
|
|
24
24
|
|
|
25
25
|
const [filteredData, setFilteredData] = useState(undefined)
|
|
26
26
|
const [animatedPie, setAnimatePie] = useState(false)
|
|
@@ -31,11 +31,11 @@ export default function PieChart() {
|
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
// Make sure the chart is visible if in the editor
|
|
34
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
34
35
|
useEffect(() => {
|
|
35
36
|
const element = document.querySelector('.isEditor')
|
|
36
37
|
if (element) {
|
|
37
38
|
// parent element is visible
|
|
38
|
-
console.log('setAnimation')
|
|
39
39
|
setAnimatePie(prevState => true)
|
|
40
40
|
}
|
|
41
41
|
})
|
|
@@ -46,18 +46,15 @@ export default function PieChart() {
|
|
|
46
46
|
setAnimatePie(true)
|
|
47
47
|
}, 500)
|
|
48
48
|
}
|
|
49
|
-
}, [dataRef?.isIntersecting, config.animate])
|
|
50
|
-
|
|
49
|
+
}, [dataRef?.isIntersecting, config.animate]) // eslint-disable-line
|
|
51
50
|
|
|
52
51
|
function AnimatedPie({ arcs, path, getKey }) {
|
|
53
|
-
const transitions = useTransition(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
)
|
|
52
|
+
const transitions = useTransition(arcs, getKey, {
|
|
53
|
+
from: enterUpdateTransition,
|
|
54
|
+
enter: enterUpdateTransition,
|
|
55
|
+
update: enterUpdateTransition,
|
|
56
|
+
leave: enterUpdateTransition
|
|
57
|
+
})
|
|
61
58
|
|
|
62
59
|
return (
|
|
63
60
|
<>
|
|
@@ -137,7 +134,7 @@ export default function PieChart() {
|
|
|
137
134
|
} else {
|
|
138
135
|
setFilteredData(undefined)
|
|
139
136
|
}
|
|
140
|
-
}, [seriesHighlight])
|
|
137
|
+
}, [seriesHighlight]) // eslint-disable-line
|
|
141
138
|
|
|
142
139
|
return (
|
|
143
140
|
<ErrorBoundary component='PieChart'>
|