@cdc/chart 4.23.1 → 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 +56289 -702
- package/examples/Barchart_with_negative.json +34 -0
- 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 +1 -2
- package/examples/dynamic-legends.json +1 -1
- package/examples/example-bar-chart-nonnumeric.json +36 -0
- package/examples/example-bar-chart.json +36 -0
- package/examples/example-combo-bar-nonnumeric.json +105 -0
- 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-categorical.json +1 -1
- 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/bar-chart-vertical/vertical-bar-chart.json +2 -2
- package/examples/gallery/line/line.json +1 -0
- package/examples/gallery/paired-bar/paired-bar-chart.json +65 -13
- package/examples/horizontal-chart-max-increase.json +38 -0
- package/examples/line-chart-max-increase.json +32 -0
- package/examples/line-chart-nonnumeric.json +32 -0
- package/examples/line-chart.json +21 -63
- package/examples/newdata.json +1 -1
- package/examples/planet-combo-example-config.json +143 -20
- 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-nonnumeric.json +56 -0
- package/examples/planet-example-data.json +9 -9
- package/examples/planet-pie-example-config-nonnumeric.json +30 -0
- package/examples/scatterplot-continuous.csv +17 -0
- package/examples/scatterplot.json +136 -0
- 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/index.html +91 -0
- package/package.json +33 -24
- package/src/{CdcChart.tsx → CdcChart.jsx} +196 -124
- package/src/components/AreaChart.jsx +198 -0
- package/src/components/{BarChart.tsx → BarChart.jsx} +154 -122
- package/src/components/BoxPlot.jsx +101 -0
- package/src/components/{DataTable.tsx → DataTable.jsx} +109 -28
- package/src/components/DeviationBar.jsx +191 -0
- package/src/components/{EditorPanel.js → EditorPanel.jsx} +676 -157
- package/src/components/{Filters.js → Filters.jsx} +6 -11
- package/src/components/Legend.jsx +316 -0
- package/src/components/{LineChart.tsx → LineChart.jsx} +22 -26
- package/src/components/{LinearChart.tsx → LinearChart.jsx} +214 -91
- package/src/components/{PairedBarChart.tsx → PairedBarChart.jsx} +44 -78
- package/src/components/{PieChart.tsx → PieChart.jsx} +26 -44
- package/src/components/ScatterPlot.jsx +51 -0
- package/src/components/SparkLine.jsx +218 -0
- package/src/components/{useIntersectionObserver.tsx → useIntersectionObserver.jsx} +2 -2
- package/src/data/initial-state.js +51 -5
- package/src/hooks/useColorPalette.js +68 -0
- package/src/hooks/{useReduceData.ts → useReduceData.js} +26 -16
- package/src/hooks/useRightAxis.js +3 -1
- package/src/index.jsx +16 -0
- package/src/scss/DataTable.scss +22 -0
- package/src/scss/editor-panel.scss +5 -0
- package/src/scss/main.scss +30 -10
- package/src/test/CdcChart.test.jsx +6 -0
- package/vite.config.js +4 -0
- package/dist/495.js +0 -3
- package/dist/703.js +0 -1
- package/src/components/BoxPlot.js +0 -92
- package/src/components/Legend.js +0 -291
- package/src/components/SparkLine.js +0 -185
- package/src/hooks/useColorPalette.ts +0 -76
- package/src/index.html +0 -67
- package/src/index.tsx +0 -18
- /package/src/{context.tsx → ConfigContext.jsx} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import
|
|
1
|
+
import React, { useState, useContext } from 'react'
|
|
2
|
+
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) => {
|
|
@@ -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))
|
|
@@ -48,7 +49,7 @@ const useFilters = () => {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
const Filters = () => {
|
|
51
|
-
const { config } = useContext(
|
|
52
|
+
const { config } = useContext(ConfigContext)
|
|
52
53
|
const { handleApplyButton, changeFilterActive, announceChange, sortAsc, sortDesc, showApplyButton, handleReset } = useFilters()
|
|
53
54
|
const { filters } = config
|
|
54
55
|
const buttonText = 'Apply Filters'
|
|
@@ -105,13 +106,7 @@ const Filters = () => {
|
|
|
105
106
|
|
|
106
107
|
return (
|
|
107
108
|
<section className={`filters-section`} style={{ display: 'block', width: '100%' }}>
|
|
108
|
-
|
|
109
|
-
<>
|
|
110
|
-
<h3 className='filters-section__title'>Filters</h3>
|
|
111
|
-
<hr />
|
|
112
|
-
</>
|
|
113
|
-
)}
|
|
114
|
-
<div className='filters-section__wrapper' style={{ flexWrap: 'wrap', display: 'flex', gap: '7px 15px' }}>
|
|
109
|
+
<div className='filters-section__wrapper' style={{ flexWrap: 'wrap', display: 'flex', gap: '7px 15px', marginTop: '15px' }}>
|
|
115
110
|
<FilterList />
|
|
116
111
|
{config.filters.length > 0 && (
|
|
117
112
|
<div className='filter-section__buttons' style={{ width: '100%' }}>
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import React, { useContext, useEffect } from 'react'
|
|
2
|
+
import ConfigContext from '../ConfigContext'
|
|
3
|
+
import parse from 'html-react-parser'
|
|
4
|
+
import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
|
|
5
|
+
import LegendCircle from '@cdc/core/components/LegendCircle'
|
|
6
|
+
|
|
7
|
+
import useLegendClasses from './../hooks/useLegendClasses'
|
|
8
|
+
|
|
9
|
+
const Legend = () => {
|
|
10
|
+
const { config, legend, colorScale, seriesHighlight, highlight, twoColorPalette, highlightReset, setSeriesHighlight, dynamicLegendItems, setDynamicLegendItems, transformedData: data, colorPalettes, rawData, setConfig, currentViewport } = useContext(ConfigContext)
|
|
11
|
+
|
|
12
|
+
const { innerClasses, containerClasses } = useLegendClasses(config)
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (dynamicLegendItems.length === 0) return
|
|
16
|
+
|
|
17
|
+
let itemsToHighlight = dynamicLegendItems.map(item => item.text)
|
|
18
|
+
|
|
19
|
+
setSeriesHighlight(itemsToHighlight)
|
|
20
|
+
|
|
21
|
+
let colsToKeep = [...itemsToHighlight]
|
|
22
|
+
let tmpLabels = []
|
|
23
|
+
|
|
24
|
+
rawData.map(dataItem => {
|
|
25
|
+
let tmp = {}
|
|
26
|
+
colsToKeep.map(col => {
|
|
27
|
+
tmp[col] = isNaN(dataItem[col]) ? dataItem[col] : dataItem[col]
|
|
28
|
+
return null
|
|
29
|
+
})
|
|
30
|
+
return tmp
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
colsToKeep.map(col => {
|
|
34
|
+
tmpLabels[col] = col
|
|
35
|
+
return null
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
if (dynamicLegendItems.length > 0) {
|
|
39
|
+
setConfig({
|
|
40
|
+
...config,
|
|
41
|
+
runtime: {
|
|
42
|
+
...config.runtime,
|
|
43
|
+
seriesKeys: colsToKeep,
|
|
44
|
+
seriesLabels: tmpLabels
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}, [dynamicLegendItems]) // eslint-disable-line
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (dynamicLegendItems.length === 0) {
|
|
52
|
+
// loop through all labels and add keys
|
|
53
|
+
let resetSeriesNames = [...config.runtime.seriesLabelsAll]
|
|
54
|
+
let tmpLabels = []
|
|
55
|
+
config.runtime.seriesLabelsAll.map(item => {
|
|
56
|
+
resetSeriesNames.map(col => {
|
|
57
|
+
tmpLabels[col] = col
|
|
58
|
+
return null
|
|
59
|
+
})
|
|
60
|
+
return null
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
setConfig({
|
|
64
|
+
...config,
|
|
65
|
+
runtime: {
|
|
66
|
+
...config.runtime,
|
|
67
|
+
seriesKeys: config.runtime.seriesLabelsAll,
|
|
68
|
+
seriesLabels: tmpLabels
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
}, [dynamicLegendItems]) // eslint-disable-line
|
|
73
|
+
|
|
74
|
+
const removeDynamicLegendItem = label => {
|
|
75
|
+
let newLegendItems = dynamicLegendItems.filter(item => item.text !== label.text)
|
|
76
|
+
let newLegendItemsText = newLegendItems.map(item => item.text)
|
|
77
|
+
setDynamicLegendItems(newLegendItems)
|
|
78
|
+
setSeriesHighlight(newLegendItemsText)
|
|
79
|
+
}
|
|
80
|
+
const handleDynamicLegendChange = e => {
|
|
81
|
+
setDynamicLegendItems([...dynamicLegendItems, JSON.parse(e.target.value)])
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const createLegendLabels = defaultLabels => {
|
|
85
|
+
const colorCode = config.legend?.colorCode
|
|
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
|
+
}
|
|
100
|
+
|
|
101
|
+
return [labelBelow, labelAbove]
|
|
102
|
+
}
|
|
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)
|
|
108
|
+
}
|
|
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
|
+
})
|
|
125
|
+
|
|
126
|
+
return uniqeLabels
|
|
127
|
+
}
|
|
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'
|
|
135
|
+
|
|
136
|
+
if (!legend) return null
|
|
137
|
+
|
|
138
|
+
if (!legend.dynamicLegend)
|
|
139
|
+
return config.visualizationType !== 'Box Plot' ? (
|
|
140
|
+
<aside style={{ marginTop, marginBottom }} id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
|
|
141
|
+
{legend.label && <h2>{parse(legend.label)}</h2>}
|
|
142
|
+
{legend.description && <p>{parse(legend.description)}</p>}
|
|
143
|
+
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
144
|
+
{labels => (
|
|
145
|
+
<div className={innerClasses.join(' ')}>
|
|
146
|
+
{createLegendLabels(labels).map((label, i) => {
|
|
147
|
+
let className = 'legend-item'
|
|
148
|
+
let itemName = label.datum
|
|
149
|
+
|
|
150
|
+
// Filter excluded data keys from legend
|
|
151
|
+
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
152
|
+
return null
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (config.runtime.seriesLabels) {
|
|
156
|
+
let index = config.runtime.seriesLabelsAll.indexOf(itemName)
|
|
157
|
+
itemName = config.runtime.seriesKeys[index]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
161
|
+
className += ' inactive'
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<LegendItem
|
|
166
|
+
className={className}
|
|
167
|
+
tabIndex={0}
|
|
168
|
+
key={`legend-quantile-${i}`}
|
|
169
|
+
onKeyPress={e => {
|
|
170
|
+
if (e.key === 'Enter') {
|
|
171
|
+
highlight(label)
|
|
172
|
+
}
|
|
173
|
+
}}
|
|
174
|
+
onClick={() => {
|
|
175
|
+
highlight(label)
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
<LegendCircle fill={label.value} />
|
|
179
|
+
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
180
|
+
{label.text}
|
|
181
|
+
</LegendLabel>
|
|
182
|
+
</LegendItem>
|
|
183
|
+
)
|
|
184
|
+
})}
|
|
185
|
+
{seriesHighlight.length > 0 && (
|
|
186
|
+
<button className={`legend-reset ${config.theme}`} onClick={labels => highlightReset(labels)} tabIndex={0}>
|
|
187
|
+
Reset
|
|
188
|
+
</button>
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
</LegendOrdinal>
|
|
193
|
+
</aside>
|
|
194
|
+
) : (
|
|
195
|
+
<aside id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
|
|
196
|
+
{config.boxplot.legend.displayHowToReadText && <h3>{config.boxplot.legend.howToReadText}</h3>}
|
|
197
|
+
</aside>
|
|
198
|
+
)
|
|
199
|
+
return (
|
|
200
|
+
config.visualizationType !== 'Box Plot' && (
|
|
201
|
+
<aside id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
|
|
202
|
+
{legend.label && <h2>{parse(legend.label)}</h2>}
|
|
203
|
+
{legend.description && <p>{parse(legend.description)}</p>}
|
|
204
|
+
|
|
205
|
+
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
206
|
+
{labels => {
|
|
207
|
+
if (
|
|
208
|
+
Number(config.legend.dynamicLegendItemLimit) > dynamicLegendItems.length && // legend items are less than limit
|
|
209
|
+
dynamicLegendItems.length !== config.runtime.seriesLabelsAll.length
|
|
210
|
+
) {
|
|
211
|
+
// legend items are equal to series length
|
|
212
|
+
return (
|
|
213
|
+
<select className='dynamic-legend-dropdown' onChange={e => handleDynamicLegendChange(e)}>
|
|
214
|
+
<option className={'all'} tabIndex={0} value={JSON.stringify({ text: config.legend.dynamicLegendDefaultText })}>
|
|
215
|
+
{config.legend.dynamicLegendDefaultText}
|
|
216
|
+
</option>
|
|
217
|
+
{labels.map((label, i) => {
|
|
218
|
+
let className = 'legend-item'
|
|
219
|
+
let itemName = label.datum
|
|
220
|
+
let inDynamicList = false
|
|
221
|
+
|
|
222
|
+
// Filter excluded data keys from legend
|
|
223
|
+
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
224
|
+
return null
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (config.runtime.seriesLabels) {
|
|
228
|
+
let index = config.runtime.seriesLabelsAll.indexOf(itemName)
|
|
229
|
+
itemName = config.runtime.seriesKeys[index]
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
233
|
+
className += ' inactive'
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
dynamicLegendItems.map(listItem => {
|
|
237
|
+
if (listItem.text === label.text) {
|
|
238
|
+
inDynamicList = true
|
|
239
|
+
}
|
|
240
|
+
return null
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
if (inDynamicList) return true
|
|
244
|
+
let palette = colorPalettes[config.palette]
|
|
245
|
+
|
|
246
|
+
label.value = palette[dynamicLegendItems.length]
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<option className={className} tabIndex={0} value={JSON.stringify(label)}>
|
|
250
|
+
{label.text}
|
|
251
|
+
</option>
|
|
252
|
+
)
|
|
253
|
+
})}
|
|
254
|
+
</select>
|
|
255
|
+
)
|
|
256
|
+
} else {
|
|
257
|
+
return config.legend.dynamicLegendItemLimitMessage
|
|
258
|
+
}
|
|
259
|
+
}}
|
|
260
|
+
</LegendOrdinal>
|
|
261
|
+
|
|
262
|
+
<div className='dynamic-legend-list'>
|
|
263
|
+
{dynamicLegendItems.map((label, i) => {
|
|
264
|
+
let className = ['legend-item']
|
|
265
|
+
let itemName = label.text
|
|
266
|
+
let palette = colorPalettes[config.palette]
|
|
267
|
+
|
|
268
|
+
// Filter excluded data keys from legend
|
|
269
|
+
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
270
|
+
return null
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (config.runtime.seriesLabels && !config.legend.dynamicLegend) {
|
|
274
|
+
let index = config.runtime.seriesLabelsAll.indexOf(itemName)
|
|
275
|
+
itemName = config.runtime.seriesKeys[index]
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (seriesHighlight.length > 0 && !seriesHighlight.includes(itemName)) {
|
|
279
|
+
className.push('inactive')
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (seriesHighlight.length === 0 && config.legend.dynamicLegend) {
|
|
283
|
+
className.push('inactive')
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<>
|
|
288
|
+
<LegendItem className={className.join(' ')} tabIndex={0} key={`dynamic-legend-item-${i}`} alignItems='center'>
|
|
289
|
+
<button
|
|
290
|
+
className='btn-wrapper'
|
|
291
|
+
onClick={() => {
|
|
292
|
+
highlight(label)
|
|
293
|
+
}}
|
|
294
|
+
>
|
|
295
|
+
<LegendCircle fill={palette[i]} config={config} />
|
|
296
|
+
<LegendLabel align='space-between' margin='4px 0 0 4px'>
|
|
297
|
+
{label.text}
|
|
298
|
+
</LegendLabel>
|
|
299
|
+
</button>
|
|
300
|
+
<button onClick={() => removeDynamicLegendItem(label)}>x</button>
|
|
301
|
+
</LegendItem>
|
|
302
|
+
</>
|
|
303
|
+
)
|
|
304
|
+
})}
|
|
305
|
+
</div>
|
|
306
|
+
{seriesHighlight.length < dynamicLegendItems.length && (
|
|
307
|
+
<button className={`legend-reset legend-reset--dynamic ${config.theme}`} onClick={highlightReset} tabIndex={0}>
|
|
308
|
+
Reset
|
|
309
|
+
</button>
|
|
310
|
+
)}
|
|
311
|
+
</aside>
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export default Legend
|
|
@@ -7,28 +7,21 @@ import { Text } from '@visx/text'
|
|
|
7
7
|
|
|
8
8
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import ConfigContext from '../ConfigContext'
|
|
11
11
|
|
|
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, updateConfig } = useContext
|
|
15
|
+
const { colorPalettes, transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, isNumber, cleanData, updateConfig, handleLineType } = useContext(ConfigContext)
|
|
16
|
+
// Just do this once up front otherwise we end up
|
|
17
|
+
// calling clean several times on same set of data (TT)
|
|
18
|
+
const cleanedData = cleanData(data, config.xAxis.dataKey)
|
|
16
19
|
const { yScaleRight } = useRightAxis({ config, yMax, data, updateConfig })
|
|
17
20
|
|
|
18
|
-
const handleLineType = lineType => {
|
|
19
|
-
switch (lineType) {
|
|
20
|
-
case 'dashed-sm':
|
|
21
|
-
return '5 5'
|
|
22
|
-
case 'dashed-md':
|
|
23
|
-
return '10 5'
|
|
24
|
-
case 'dashed-lg':
|
|
25
|
-
return '15 5'
|
|
26
|
-
default:
|
|
27
|
-
return 0
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
21
|
const handleAxisFormating = (axis = 'left', label, value) => {
|
|
22
|
+
// if this is an x axis category/date value return without doing any formatting.
|
|
23
|
+
// if (label === config.runtime.xAxis.label) return value
|
|
24
|
+
|
|
32
25
|
axis = String(axis).toLocaleLowerCase()
|
|
33
26
|
if (label) {
|
|
34
27
|
return `${label}: ${formatNumber(value, axis)}`
|
|
@@ -38,7 +31,9 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
|
|
|
38
31
|
|
|
39
32
|
return (
|
|
40
33
|
<ErrorBoundary component='LineChart'>
|
|
41
|
-
<Group left={config.runtime.yAxis.size}>
|
|
34
|
+
<Group left={config.runtime.yAxis.size ? parseInt(config.runtime.yAxis.size) : 66}>
|
|
35
|
+
{' '}
|
|
36
|
+
{/* left - expects a number not a string */}
|
|
42
37
|
{(config.runtime.lineSeriesKeys || config.runtime.seriesKeys).map((seriesKey, index) => {
|
|
43
38
|
let lineType = config.series.filter(item => item.dataKey === seriesKey)[0].type
|
|
44
39
|
const seriesData = config.series.filter(item => item.dataKey === seriesKey)
|
|
@@ -50,7 +45,7 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
|
|
|
50
45
|
opacity={config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(seriesKey) === -1 ? 0.5 : 1}
|
|
51
46
|
display={config.legend.behavior === 'highlight' || (seriesHighlight.length === 0 && !config.legend.dynamicLegend) || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
|
|
52
47
|
>
|
|
53
|
-
{
|
|
48
|
+
{cleanedData.map((d, dataIndex) => {
|
|
54
49
|
// Find the series object from the config.series array that has a dataKey matching the seriesKey variable.
|
|
55
50
|
const series = config.series.find(({ dataKey }) => dataKey === seriesKey)
|
|
56
51
|
const { axis } = series
|
|
@@ -73,11 +68,13 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
|
|
|
73
68
|
${xAxisTooltip}
|
|
74
69
|
</div>`
|
|
75
70
|
let circleRadii = 4.5
|
|
71
|
+
|
|
76
72
|
return (
|
|
77
73
|
d[seriesKey] !== undefined &&
|
|
78
74
|
d[seriesKey] !== '' &&
|
|
79
75
|
d[seriesKey] !== null && (
|
|
80
76
|
<Group key={`series-${seriesKey}-point-${dataIndex}`}>
|
|
77
|
+
{/* Render legend */}
|
|
81
78
|
<Text
|
|
82
79
|
display={config.labels ? 'block' : 'none'}
|
|
83
80
|
x={xScale(getXAxisData(d))}
|
|
@@ -85,18 +82,18 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
|
|
|
85
82
|
fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
86
83
|
textAnchor='middle'
|
|
87
84
|
>
|
|
88
|
-
{formatNumber(d[seriesKey])}
|
|
85
|
+
{formatNumber(d[seriesKey], 'left')}
|
|
89
86
|
</Text>
|
|
90
87
|
|
|
91
88
|
<circle
|
|
92
89
|
key={`${seriesKey}-${dataIndex}`}
|
|
93
90
|
r={circleRadii}
|
|
94
|
-
cx={xScale(getXAxisData(d))}
|
|
91
|
+
cx={Number(xScale(getXAxisData(d)))}
|
|
95
92
|
cy={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey))}
|
|
96
93
|
fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
97
94
|
style={{ fill: colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000' }}
|
|
98
|
-
data-
|
|
99
|
-
data-
|
|
95
|
+
data-tooltip-html={tooltip}
|
|
96
|
+
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
100
97
|
/>
|
|
101
98
|
</Group>
|
|
102
99
|
)
|
|
@@ -105,7 +102,7 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
|
|
|
105
102
|
|
|
106
103
|
<LinePath
|
|
107
104
|
curve={allCurves.curveLinear}
|
|
108
|
-
data={
|
|
105
|
+
data={cleanedData}
|
|
109
106
|
x={d => xScale(getXAxisData(d))}
|
|
110
107
|
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey)))}
|
|
111
108
|
stroke={
|
|
@@ -122,14 +119,14 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
|
|
|
122
119
|
shapeRendering='geometricPrecision'
|
|
123
120
|
strokeDasharray={lineType ? handleLineType(lineType) : 0}
|
|
124
121
|
defined={(item, i) => {
|
|
125
|
-
return item[config.runtime.seriesLabels[seriesKey]] !== '' && item[config.runtime.seriesLabels[seriesKey]] !== null
|
|
122
|
+
return item[config.runtime.seriesLabels[seriesKey]] !== '' && item[config.runtime.seriesLabels[seriesKey]] !== null && item[config.runtime.seriesLabels[seriesKey]] !== undefined
|
|
126
123
|
}}
|
|
127
124
|
/>
|
|
128
125
|
{config.animate && (
|
|
129
126
|
<LinePath
|
|
130
127
|
className='animation'
|
|
131
128
|
curve={allCurves.curveLinear}
|
|
132
|
-
data={
|
|
129
|
+
data={cleanedData}
|
|
133
130
|
x={d => xScale(getXAxisData(d))}
|
|
134
131
|
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey)))}
|
|
135
132
|
stroke='#fff'
|
|
@@ -138,7 +135,7 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
|
|
|
138
135
|
shapeRendering='geometricPrecision'
|
|
139
136
|
strokeDasharray={lineType ? handleLineType(lineType) : 0}
|
|
140
137
|
defined={(item, i) => {
|
|
141
|
-
return item[config.runtime.seriesLabels[seriesKey]]
|
|
138
|
+
return isNumber(item[config.runtime.seriesLabels[seriesKey]])
|
|
142
139
|
}}
|
|
143
140
|
/>
|
|
144
141
|
)}
|
|
@@ -165,7 +162,6 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
|
|
|
165
162
|
</Group>
|
|
166
163
|
)
|
|
167
164
|
})}
|
|
168
|
-
|
|
169
165
|
{/* Message when dynamic legend and nothing has been picked */}
|
|
170
166
|
{config.legend.dynamicLegend && seriesHighlight.length === 0 && (
|
|
171
167
|
<Text x={xMax / 2} y={yMax / 2} fill='black' textAnchor='middle' color='black'>
|