@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
package/src/components/Legend.js
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import React, { useContext, useEffect } from 'react'
|
|
2
|
-
import Context from '../context'
|
|
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, highlightReset, setSeriesHighlight, dynamicLegendItems, setDynamicLegendItems, transformedData: data, setFilteredData, colorPalettes, rawData, setConfig } = useContext(Context)
|
|
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
|
-
})
|
|
29
|
-
return tmp
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
colsToKeep.map(col => {
|
|
33
|
-
tmpLabels[col] = col
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
if (dynamicLegendItems.length > 0) {
|
|
37
|
-
setConfig({
|
|
38
|
-
...config,
|
|
39
|
-
runtime: {
|
|
40
|
-
...config.runtime,
|
|
41
|
-
seriesKeys: colsToKeep,
|
|
42
|
-
seriesLabels: tmpLabels
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
}
|
|
46
|
-
}, [dynamicLegendItems])
|
|
47
|
-
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
if (dynamicLegendItems.length === 0) {
|
|
50
|
-
// loop through all labels and add keys
|
|
51
|
-
let resetSeriesNames = [...config.runtime.seriesLabelsAll]
|
|
52
|
-
let tmpLabels = []
|
|
53
|
-
config.runtime.seriesLabelsAll.map(item => {
|
|
54
|
-
resetSeriesNames.map(col => {
|
|
55
|
-
tmpLabels[col] = col
|
|
56
|
-
})
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
setConfig({
|
|
60
|
-
...config,
|
|
61
|
-
runtime: {
|
|
62
|
-
...config.runtime,
|
|
63
|
-
seriesKeys: config.runtime.seriesLabelsAll,
|
|
64
|
-
seriesLabels: tmpLabels
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
}, [dynamicLegendItems])
|
|
69
|
-
|
|
70
|
-
const removeDynamicLegendItem = label => {
|
|
71
|
-
let newLegendItems = dynamicLegendItems.filter(item => item.text !== label.text)
|
|
72
|
-
let newLegendItemsText = newLegendItems.map(item => item.text)
|
|
73
|
-
setDynamicLegendItems(newLegendItems)
|
|
74
|
-
setSeriesHighlight(newLegendItemsText)
|
|
75
|
-
}
|
|
76
|
-
const handleDynamicLegendChange = e => {
|
|
77
|
-
setDynamicLegendItems([...dynamicLegendItems, JSON.parse(e.target.value)])
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const createLegendLabels = (data, defaultLabels) => {
|
|
81
|
-
const colorCode = config.legend?.colorCode
|
|
82
|
-
if (config.visualizationType !== 'Bar' || config.visualizationSubType !== 'regular' || !colorCode || config.series?.length > 1) {
|
|
83
|
-
return defaultLabels
|
|
84
|
-
}
|
|
85
|
-
let palette = colorPalettes[config.palette]
|
|
86
|
-
|
|
87
|
-
while (data.length > palette.length) {
|
|
88
|
-
palette = palette.concat(palette)
|
|
89
|
-
}
|
|
90
|
-
palette = palette.slice(0, data.length)
|
|
91
|
-
//store uniq values to Set by colorCode
|
|
92
|
-
const set = new Set()
|
|
93
|
-
|
|
94
|
-
data.forEach(d => set.add(d[colorCode]))
|
|
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
|
-
}
|
|
104
|
-
return newLabel
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
return uniqeLabels
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (!legend) return
|
|
111
|
-
|
|
112
|
-
if (!legend.dynamicLegend)
|
|
113
|
-
return (
|
|
114
|
-
<aside
|
|
115
|
-
style={{ marginTop: config.legend.position === 'bottom' && config.orientation === 'horizontal' ? `${config.runtime.xAxis.size}px` : '0px', marginBottom: config.legend.position === 'bottom' ? '15px' : '0px' }}
|
|
116
|
-
id='legend'
|
|
117
|
-
className={containerClasses.join(' ')}
|
|
118
|
-
role='region'
|
|
119
|
-
aria-label='legend'
|
|
120
|
-
tabIndex={0}
|
|
121
|
-
>
|
|
122
|
-
{legend.label && <h2>{parse(legend.label)}</h2>}
|
|
123
|
-
{legend.description && <p>{parse(legend.description)}</p>}
|
|
124
|
-
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
125
|
-
{labels => (
|
|
126
|
-
<div className={innerClasses.join(' ')}>
|
|
127
|
-
{createLegendLabels(data, labels).map((label, i) => {
|
|
128
|
-
let className = 'legend-item'
|
|
129
|
-
let itemName = label.datum
|
|
130
|
-
|
|
131
|
-
// Filter excluded data keys from legend
|
|
132
|
-
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (config.runtime.seriesLabels) {
|
|
137
|
-
let index = config.runtime.seriesLabelsAll.indexOf(itemName)
|
|
138
|
-
itemName = config.runtime.seriesKeys[index]
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
142
|
-
className += ' inactive'
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return (
|
|
146
|
-
<LegendItem
|
|
147
|
-
className={className}
|
|
148
|
-
tabIndex={0}
|
|
149
|
-
key={`legend-quantile-${i}`}
|
|
150
|
-
onKeyPress={e => {
|
|
151
|
-
if (e.key === 'Enter') {
|
|
152
|
-
highlight(label)
|
|
153
|
-
}
|
|
154
|
-
}}
|
|
155
|
-
onClick={() => {
|
|
156
|
-
highlight(label)
|
|
157
|
-
}}
|
|
158
|
-
>
|
|
159
|
-
<LegendCircle fill={label.value} />
|
|
160
|
-
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
161
|
-
{label.text}
|
|
162
|
-
</LegendLabel>
|
|
163
|
-
</LegendItem>
|
|
164
|
-
)
|
|
165
|
-
})}
|
|
166
|
-
{seriesHighlight.length > 0 && (
|
|
167
|
-
<button className={`legend-reset ${config.theme}`} onClick={labels => highlightReset(labels)} tabIndex={0}>
|
|
168
|
-
Reset
|
|
169
|
-
</button>
|
|
170
|
-
)}
|
|
171
|
-
</div>
|
|
172
|
-
)}
|
|
173
|
-
</LegendOrdinal>
|
|
174
|
-
</aside>
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
return (
|
|
178
|
-
<aside id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
|
|
179
|
-
{legend.label && <h2>{parse(legend.label)}</h2>}
|
|
180
|
-
{legend.description && <p>{parse(legend.description)}</p>}
|
|
181
|
-
|
|
182
|
-
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
183
|
-
{labels => {
|
|
184
|
-
if (
|
|
185
|
-
Number(config.legend.dynamicLegendItemLimit) > dynamicLegendItems.length && // legend items are less than limit
|
|
186
|
-
dynamicLegendItems.length !== config.runtime.seriesLabelsAll.length
|
|
187
|
-
) {
|
|
188
|
-
// legend items are equal to series length
|
|
189
|
-
return (
|
|
190
|
-
<select className='dynamic-legend-dropdown' onChange={e => handleDynamicLegendChange(e)}>
|
|
191
|
-
<option className={'all'} tabIndex={0} value={JSON.stringify({ text: config.legend.dynamicLegendDefaultText })}>
|
|
192
|
-
{config.legend.dynamicLegendDefaultText}
|
|
193
|
-
</option>
|
|
194
|
-
{labels.map((label, i) => {
|
|
195
|
-
let className = 'legend-item'
|
|
196
|
-
let itemName = label.datum
|
|
197
|
-
let inDynamicList = false
|
|
198
|
-
|
|
199
|
-
// Filter excluded data keys from legend
|
|
200
|
-
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
201
|
-
return
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (config.runtime.seriesLabels) {
|
|
205
|
-
let index = config.runtime.seriesLabelsAll.indexOf(itemName)
|
|
206
|
-
itemName = config.runtime.seriesKeys[index]
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
210
|
-
className += ' inactive'
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
dynamicLegendItems.map(listItem => {
|
|
214
|
-
if (listItem.text === label.text) {
|
|
215
|
-
inDynamicList = true
|
|
216
|
-
}
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
if (inDynamicList) return true
|
|
220
|
-
let palette = colorPalettes[config.palette]
|
|
221
|
-
|
|
222
|
-
label.value = palette[dynamicLegendItems.length]
|
|
223
|
-
|
|
224
|
-
return (
|
|
225
|
-
<option className={className} tabIndex={0} value={JSON.stringify(label)}>
|
|
226
|
-
{label.text}
|
|
227
|
-
</option>
|
|
228
|
-
)
|
|
229
|
-
})}
|
|
230
|
-
</select>
|
|
231
|
-
)
|
|
232
|
-
} else {
|
|
233
|
-
return config.legend.dynamicLegendItemLimitMessage
|
|
234
|
-
}
|
|
235
|
-
}}
|
|
236
|
-
</LegendOrdinal>
|
|
237
|
-
|
|
238
|
-
<div className='dynamic-legend-list'>
|
|
239
|
-
{dynamicLegendItems.map((label, i) => {
|
|
240
|
-
let className = ['legend-item']
|
|
241
|
-
let itemName = label.text
|
|
242
|
-
let palette = colorPalettes[config.palette]
|
|
243
|
-
|
|
244
|
-
// Filter excluded data keys from legend
|
|
245
|
-
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
246
|
-
return
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (config.runtime.seriesLabels && !config.legend.dynamicLegend) {
|
|
250
|
-
let index = config.runtime.seriesLabelsAll.indexOf(itemName)
|
|
251
|
-
itemName = config.runtime.seriesKeys[index]
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (seriesHighlight.length > 0 && !seriesHighlight.includes(itemName)) {
|
|
255
|
-
className.push('inactive')
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (seriesHighlight.length === 0 && config.legend.dynamicLegend) {
|
|
259
|
-
className.push('inactive')
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return (
|
|
263
|
-
<>
|
|
264
|
-
<LegendItem className={className.join(' ')} tabIndex={0} key={`dynamic-legend-item-${i}`} alignItems='center'>
|
|
265
|
-
<button
|
|
266
|
-
className='btn-wrapper'
|
|
267
|
-
onClick={() => {
|
|
268
|
-
highlight(label)
|
|
269
|
-
}}
|
|
270
|
-
>
|
|
271
|
-
<LegendCircle fill={palette[i]} config={config} />
|
|
272
|
-
<LegendLabel align='space-between' margin='4px 0 0 4px'>
|
|
273
|
-
{label.text}
|
|
274
|
-
</LegendLabel>
|
|
275
|
-
</button>
|
|
276
|
-
<button onClick={() => removeDynamicLegendItem(label)}>x</button>
|
|
277
|
-
</LegendItem>
|
|
278
|
-
</>
|
|
279
|
-
)
|
|
280
|
-
})}
|
|
281
|
-
</div>
|
|
282
|
-
{seriesHighlight.length < dynamicLegendItems.length && (
|
|
283
|
-
<button className={`legend-reset legend-reset--dynamic ${config.theme}`} onClick={highlightReset} tabIndex={0}>
|
|
284
|
-
Reset
|
|
285
|
-
</button>
|
|
286
|
-
)}
|
|
287
|
-
</aside>
|
|
288
|
-
)
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
export default Legend
|
|
@@ -1,185 +0,0 @@
|
|
|
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
|
-
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
12
|
-
|
|
13
|
-
import ReactTooltip from 'react-tooltip'
|
|
14
|
-
|
|
15
|
-
import useReduceData from '../hooks/useReduceData'
|
|
16
|
-
|
|
17
|
-
import Context from '../context'
|
|
18
|
-
|
|
19
|
-
export default function SparkLine({ width: parentWidth, height: parentHeight }) {
|
|
20
|
-
const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, seriesHighlight, formatNumber, colorScale, handleChartAriaLabels } = useContext(Context)
|
|
21
|
-
let width = parentWidth
|
|
22
|
-
const { minValue, maxValue } = useReduceData(config, data)
|
|
23
|
-
|
|
24
|
-
const margin = { top: 5, right: 10, bottom: 10, left: 10 }
|
|
25
|
-
const height = parentHeight
|
|
26
|
-
|
|
27
|
-
const xMax = width - config.runtime.yAxis.size
|
|
28
|
-
const yMax = height - margin.top - 20
|
|
29
|
-
|
|
30
|
-
const getXAxisData = d => (config.runtime.xAxis.type === 'date' ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
|
|
31
|
-
const getYAxisData = (d, seriesKey) => d[seriesKey]
|
|
32
|
-
|
|
33
|
-
let xScale
|
|
34
|
-
let yScale
|
|
35
|
-
let seriesScale
|
|
36
|
-
const { max: enteredMaxValue, min: enteredMinValue } = config.runtime.yAxis
|
|
37
|
-
const isMaxValid = Number(enteredMaxValue) >= Number(maxValue)
|
|
38
|
-
const isMinValid = Number(enteredMinValue) <= Number(minValue)
|
|
39
|
-
|
|
40
|
-
if (data) {
|
|
41
|
-
let min = enteredMinValue && isMinValid ? enteredMinValue : minValue
|
|
42
|
-
let max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
|
|
43
|
-
|
|
44
|
-
if (max === Number.MIN_VALUE) {
|
|
45
|
-
max = maxValue
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
//Adds Y Axis data padding if applicable
|
|
49
|
-
if (config.runtime.yAxis.paddingPercent) {
|
|
50
|
-
let paddingValue = (max - min) * config.runtime.yAxis.paddingPercent
|
|
51
|
-
min -= paddingValue
|
|
52
|
-
max += paddingValue
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let xAxisDataMapped = data.map(d => getXAxisData(d))
|
|
56
|
-
|
|
57
|
-
if (config.runtime.horizontal) {
|
|
58
|
-
xScale = scaleLinear({
|
|
59
|
-
domain: [min, max],
|
|
60
|
-
range: [0, xMax]
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
yScale = config.runtime.xAxis.type === 'date' ? scaleLinear({ domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)] }) : scalePoint({ domain: xAxisDataMapped, padding: 0.5 })
|
|
64
|
-
|
|
65
|
-
seriesScale = scalePoint({
|
|
66
|
-
domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
|
|
67
|
-
range: [0, yMax]
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
yScale.rangeRound([0, yMax])
|
|
71
|
-
} else {
|
|
72
|
-
min = min < 0 ? min * 1.11 : min
|
|
73
|
-
|
|
74
|
-
yScale = scaleLinear({
|
|
75
|
-
domain: [min, max],
|
|
76
|
-
range: [yMax - margin.bottom, margin.top]
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
xScale = scalePoint({
|
|
80
|
-
domain: xAxisDataMapped,
|
|
81
|
-
range: [margin.left, width - margin.right]
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
seriesScale = scalePoint({
|
|
85
|
-
domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
|
|
86
|
-
range: [0, xMax]
|
|
87
|
-
})
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const handleSparkLineTicks = [xScale.domain()[0], xScale.domain()[xScale.domain().length - 1]]
|
|
92
|
-
|
|
93
|
-
useEffect(() => {
|
|
94
|
-
ReactTooltip.rebuild()
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
return (
|
|
98
|
-
<ErrorBoundary component='SparkLine'>
|
|
99
|
-
<svg role='img' aria-label={handleChartAriaLabels(config)} width={width} height={height} className={'sparkline'} tabIndex={0}>
|
|
100
|
-
{(config.runtime.lineSeriesKeys || config.runtime.seriesKeys).map((seriesKey, index) => (
|
|
101
|
-
<>
|
|
102
|
-
<Group
|
|
103
|
-
className='sparkline-group'
|
|
104
|
-
height={parentHeight}
|
|
105
|
-
style={{ height: parentHeight }}
|
|
106
|
-
top={margin.top}
|
|
107
|
-
key={`series-${seriesKey}`}
|
|
108
|
-
opacity={config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(seriesKey) === -1 ? 0.5 : 1}
|
|
109
|
-
display={config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
|
|
110
|
-
>
|
|
111
|
-
{data.map((d, dataIndex) => {
|
|
112
|
-
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${formatNumber(getYAxisData(d, seriesKey))}` : formatNumber(getYAxisData(d, seriesKey))
|
|
113
|
-
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${d[config.runtime.xAxis.dataKey]}` : d[config.runtime.xAxis.dataKey]
|
|
114
|
-
|
|
115
|
-
const tooltip = `<div>
|
|
116
|
-
${yAxisTooltip}<br />
|
|
117
|
-
${xAxisTooltip}<br />
|
|
118
|
-
${config.seriesLabel ? `${config.seriesLabel}: ${seriesKey}` : ''}
|
|
119
|
-
</div>`
|
|
120
|
-
|
|
121
|
-
let circleRadii = 4.5
|
|
122
|
-
return (
|
|
123
|
-
<Group key={`series-${seriesKey}-point-${dataIndex}`}>
|
|
124
|
-
<Text display={config.labels ? 'block' : 'none'} x={xScale(getXAxisData(d))} y={yScale(getYAxisData(d, seriesKey))} fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'} textAnchor='middle'>
|
|
125
|
-
{formatNumber(d[seriesKey])}
|
|
126
|
-
</Text>
|
|
127
|
-
|
|
128
|
-
{dataIndex + 1 !== data.length && (config.lineDatapointStyle === 'always show' || config.lineDatapointStyle === 'hover') && (
|
|
129
|
-
<circle
|
|
130
|
-
key={`${seriesKey}-${dataIndex}`}
|
|
131
|
-
r={circleRadii}
|
|
132
|
-
cx={xScale(getXAxisData(d))}
|
|
133
|
-
cy={yScale(getYAxisData(d, seriesKey))}
|
|
134
|
-
fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
135
|
-
style={{ fill: colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000' }}
|
|
136
|
-
data-tip={tooltip}
|
|
137
|
-
data-for={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
138
|
-
/>
|
|
139
|
-
)}
|
|
140
|
-
</Group>
|
|
141
|
-
)
|
|
142
|
-
})}
|
|
143
|
-
<LinePath
|
|
144
|
-
curve={allCurves.curveLinear}
|
|
145
|
-
data={data}
|
|
146
|
-
x={d => xScale(getXAxisData(d))}
|
|
147
|
-
y={d => yScale(getYAxisData(d, seriesKey))}
|
|
148
|
-
stroke={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
149
|
-
strokeWidth={2}
|
|
150
|
-
strokeOpacity={1}
|
|
151
|
-
shapeRendering='geometricPrecision'
|
|
152
|
-
marker-end={`url(#${'arrow'}--${index})`}
|
|
153
|
-
/>
|
|
154
|
-
<MarkerArrow
|
|
155
|
-
id={`arrow--${index}`}
|
|
156
|
-
refX={2}
|
|
157
|
-
size={6}
|
|
158
|
-
marker-end={`url(#${'arrow'}--${index})`}
|
|
159
|
-
strokeOpacity={1}
|
|
160
|
-
fillOpacity={1}
|
|
161
|
-
// stroke={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
162
|
-
fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
163
|
-
/>
|
|
164
|
-
</Group>
|
|
165
|
-
<AxisBottom
|
|
166
|
-
top={yMax + margin.top}
|
|
167
|
-
hideAxisLine
|
|
168
|
-
hideTicks
|
|
169
|
-
scale={xScale}
|
|
170
|
-
tickValues={handleSparkLineTicks}
|
|
171
|
-
tickFormat={formatDate}
|
|
172
|
-
stroke={'black'}
|
|
173
|
-
tickStroke={'black'}
|
|
174
|
-
tickLabelProps={() => ({
|
|
175
|
-
fill: 'black',
|
|
176
|
-
fontSize: 11,
|
|
177
|
-
textAnchor: 'middle'
|
|
178
|
-
})}
|
|
179
|
-
/>
|
|
180
|
-
</>
|
|
181
|
-
))}
|
|
182
|
-
</svg>
|
|
183
|
-
</ErrorBoundary>
|
|
184
|
-
)
|
|
185
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
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
|
-
// types & interfaces
|
|
9
|
-
interface State {
|
|
10
|
-
readonly filteredPallets: string[]
|
|
11
|
-
readonly filteredQualitative: string[]
|
|
12
|
-
readonly isPaletteReversed: boolean
|
|
13
|
-
paletteName: string | undefined
|
|
14
|
-
}
|
|
15
|
-
interface Action<Palettes> {
|
|
16
|
-
type: 'SEQUENTIAL' | 'SEQUENTIAL_REVERSE' | 'GET_PALETTE'
|
|
17
|
-
payload: Palettes
|
|
18
|
-
paletteName?: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// create initial state
|
|
22
|
-
const initialState: State = {
|
|
23
|
-
filteredPallets: [],
|
|
24
|
-
isPaletteReversed: false,
|
|
25
|
-
filteredQualitative: [],
|
|
26
|
-
paletteName: undefined
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function reducer<T>(state: State, action: Action<T>): State {
|
|
30
|
-
// <T> refers to generic type
|
|
31
|
-
const palletNamesArr: string[] = Object.keys(action.payload) // action.payload === colorPalettes object
|
|
32
|
-
let paletteName: string = ''
|
|
33
|
-
switch (action.type) {
|
|
34
|
-
case GET_PALETTE:
|
|
35
|
-
return { ...state, paletteName: action.paletteName }
|
|
36
|
-
case SEQUENTIAL:
|
|
37
|
-
paletteName = state.paletteName && state.paletteName.endsWith('reverse') ? String(state.paletteName).substring(0, state.paletteName.length - 7) : String(state.paletteName)
|
|
38
|
-
const qualitative = palletNamesArr.filter((name: string) => String(name).startsWith('qualitative') && !String(name).endsWith('reverse'))
|
|
39
|
-
const sequential = palletNamesArr.filter((name: string) => String(name).startsWith('sequential') && !String(name).endsWith('reverse'))
|
|
40
|
-
return { ...state, filteredPallets: sequential, filteredQualitative: qualitative, paletteName: paletteName, isPaletteReversed: false }
|
|
41
|
-
|
|
42
|
-
case SEQUENTIAL_REVERSE:
|
|
43
|
-
paletteName = palletNamesArr.find((name: string) => name === String(state.paletteName).concat('reverse') || name === String(state.paletteName).concat('-reverse'))
|
|
44
|
-
const qualitativeReverse: string[] = palletNamesArr.filter((name: string) => String(name).startsWith('qualitative') && String(name).endsWith('reverse'))
|
|
45
|
-
const sequentialReverse: string[] = palletNamesArr.filter((name: string) => String(name).startsWith('sequential') && String(name).endsWith('reverse'))
|
|
46
|
-
return { ...state, filteredQualitative: qualitativeReverse, filteredPallets: sequentialReverse, paletteName: paletteName, isPaletteReversed: true }
|
|
47
|
-
default:
|
|
48
|
-
return state
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface Keyable {
|
|
53
|
-
palette: string
|
|
54
|
-
isPaletteReversed: boolean
|
|
55
|
-
}
|
|
56
|
-
function init(initialState) {
|
|
57
|
-
return initialState
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function useColorPalette<T, Y extends Keyable>(colorPalettes: T, configState: Y) {
|
|
61
|
-
const [state, dispatch] = useReducer(reducer, initialState, init)
|
|
62
|
-
const { paletteName, isPaletteReversed, filteredPallets, filteredQualitative } = state
|
|
63
|
-
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
dispatch({ type: SEQUENTIAL, payload: colorPalettes })
|
|
66
|
-
}, [])
|
|
67
|
-
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
if (configState.isPaletteReversed) {
|
|
70
|
-
dispatch({ type: 'SEQUENTIAL_REVERSE', payload: colorPalettes })
|
|
71
|
-
return () => dispatch({ type: 'SEQUENTIAL', payload: colorPalettes })
|
|
72
|
-
}
|
|
73
|
-
}, [configState.isPaletteReversed, dispatch, colorPalettes])
|
|
74
|
-
|
|
75
|
-
return { paletteName, isPaletteReversed, filteredPallets, filteredQualitative, dispatch }
|
|
76
|
-
}
|
package/src/index.html
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
|
6
|
-
<style>
|
|
7
|
-
body {
|
|
8
|
-
/* max-width: 1000px; */
|
|
9
|
-
margin: 0 auto !important;
|
|
10
|
-
display: flex;
|
|
11
|
-
flex-direction: column;
|
|
12
|
-
justify-content: center;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.react-container + .react-container {
|
|
16
|
-
margin-top: 3rem;
|
|
17
|
-
}
|
|
18
|
-
</style>
|
|
19
|
-
</head>
|
|
20
|
-
|
|
21
|
-
<body>
|
|
22
|
-
<!-- <div class="react-container" data-config="/examples/temp-example-config.json"></div> -->
|
|
23
|
-
|
|
24
|
-
<!-- <select id="cove_select">
|
|
25
|
-
<option value=""">Choose An Option</option>
|
|
26
|
-
<option value="Non-Hispanic White">Non-Hispanic White</option>
|
|
27
|
-
<option value="Hispanic or Latino">Test 2</option>
|
|
28
|
-
</select> -->
|
|
29
|
-
|
|
30
|
-
<!-- <div class="react-container" data-config="/examples/private/filters.json"></div> -->
|
|
31
|
-
<!-- <div class="react-container" data-config="/examples/private/yaxis-test.json"></div> -->
|
|
32
|
-
<!-- <div class="react-container" data-config="/examples/dynamic-legends.json"></div> -->
|
|
33
|
-
<!-- <div class="react-container" data-config="/examples/cutoff-example-config.json"></div> -->
|
|
34
|
-
<!-- <div class="react-container" data-config="/examples/covid-confidence-example-config.json"></div> -->
|
|
35
|
-
<!-- <div class="react-container" data-config="/examples/planet-example-config.json"></div> -->
|
|
36
|
-
<!-- <div class="react-container" data-config="/examples/planet-chart-horizontal-example-config.json"></div> -->
|
|
37
|
-
<!-- <div class="react-container" data-config="/examples/planet-combo-example-config.json"></div>-->
|
|
38
|
-
<!-- <div class="react-container" data-config="/examples/planet-pie-example-config.json"></div>-->
|
|
39
|
-
<!-- <div class="react-container" data-config="/examples/date-exclusions-config.json"></div> -->
|
|
40
|
-
<!--<div class="react-container" data-config="/examples/case-rate-example-config.json"></div>-->
|
|
41
|
-
<!-- <div class="react-container" data-config="/examples/private/textelements.json"></div> -->
|
|
42
|
-
<!-- <div class="react-container" data-config="/examples/private/example-bar-chart.json"></div> -->
|
|
43
|
-
<!-- <div class="react-container" data-config="/examples/example-sparkline.json"></div> -->
|
|
44
|
-
<!-- DATA PRESENTATION GALLERY: https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/bar-chart.html#examples -->
|
|
45
|
-
|
|
46
|
-
<!-- HORIZONTAL BAR CHARTS -->
|
|
47
|
-
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-horizontal/horizontal-bar-chart-with-numbers-on-bar.json"></div> -->
|
|
48
|
-
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json"></div> -->
|
|
49
|
-
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-horizontal/horizontal-stacked.json"></div> -->
|
|
50
|
-
|
|
51
|
-
<!-- VERTICAL BAR CHARTS -->
|
|
52
|
-
<div class="react-container" data-config="/examples/gallery/bar-chart-vertical/combo-line-chart.json"></div>
|
|
53
|
-
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json"></div> -->
|
|
54
|
-
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json"></div> -->
|
|
55
|
-
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-vertical/vertical-bar-with-confidence.json"></div> -->
|
|
56
|
-
<!-- <div class="react-container" data-config="/examples/gallery/bar-chart-vertical/vertical-bar-chart.json"></div> -->
|
|
57
|
-
<!-- <div class="react-container" data-config="/examples/box-plot.json"></div> -->
|
|
58
|
-
|
|
59
|
-
<!-- LOLLIPOP CHARTS -->
|
|
60
|
-
<!-- <div class="react-container" data-config="/examples/gallery/lollipop/lollipop-style-horizontal.json"></div> -->
|
|
61
|
-
|
|
62
|
-
<!-- PAIRED BAR CHARTS -->
|
|
63
|
-
<!-- <div class="react-container" data-config="/examples/gallery/paired-bar/paired-bar-chart.json"></div> -->
|
|
64
|
-
|
|
65
|
-
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
66
|
-
</body>
|
|
67
|
-
</html>
|
package/src/index.tsx
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { publish, subscribe } from '@cdc/core/helpers/events'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import { render } from 'react-dom'
|
|
4
|
-
|
|
5
|
-
import CdcChart from './CdcChart'
|
|
6
|
-
|
|
7
|
-
const domContainers = document.querySelectorAll('.react-container')
|
|
8
|
-
|
|
9
|
-
let isEditor = window.location.href.includes('editor=true')
|
|
10
|
-
|
|
11
|
-
domContainers.forEach(domContainer => {
|
|
12
|
-
render(
|
|
13
|
-
<React.StrictMode>
|
|
14
|
-
<CdcChart configUrl={domContainer.attributes['data-config'].value} isEditor={isEditor} />
|
|
15
|
-
</React.StrictMode>,
|
|
16
|
-
domContainer
|
|
17
|
-
)
|
|
18
|
-
})
|
|
File without changes
|