@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
package/src/components/Legend.js
DELETED
|
@@ -1,284 +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 id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
|
|
115
|
-
{legend.label && <h2>{parse(legend.label)}</h2>}
|
|
116
|
-
{legend.description && <p>{parse(legend.description)}</p>}
|
|
117
|
-
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
118
|
-
{labels => (
|
|
119
|
-
<div className={innerClasses.join(' ')}>
|
|
120
|
-
{createLegendLabels(data, labels).map((label, i) => {
|
|
121
|
-
let className = 'legend-item'
|
|
122
|
-
let itemName = label.datum
|
|
123
|
-
|
|
124
|
-
// Filter excluded data keys from legend
|
|
125
|
-
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
126
|
-
return
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (config.runtime.seriesLabels) {
|
|
130
|
-
let index = config.runtime.seriesLabelsAll.indexOf(itemName)
|
|
131
|
-
itemName = config.runtime.seriesKeys[index]
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
135
|
-
className += ' inactive'
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return (
|
|
139
|
-
<LegendItem
|
|
140
|
-
className={className}
|
|
141
|
-
tabIndex={0}
|
|
142
|
-
key={`legend-quantile-${i}`}
|
|
143
|
-
onKeyPress={e => {
|
|
144
|
-
if (e.key === 'Enter') {
|
|
145
|
-
highlight(label)
|
|
146
|
-
}
|
|
147
|
-
}}
|
|
148
|
-
onClick={() => {
|
|
149
|
-
highlight(label)
|
|
150
|
-
}}
|
|
151
|
-
>
|
|
152
|
-
<LegendCircle fill={label.value} />
|
|
153
|
-
<LegendLabel align='left' margin='0 0 0 4px'>
|
|
154
|
-
{label.text}
|
|
155
|
-
</LegendLabel>
|
|
156
|
-
</LegendItem>
|
|
157
|
-
)
|
|
158
|
-
})}
|
|
159
|
-
{seriesHighlight.length > 0 && (
|
|
160
|
-
<button className={`legend-reset ${config.theme}`} onClick={labels => highlightReset(labels)} tabIndex={0}>
|
|
161
|
-
Reset
|
|
162
|
-
</button>
|
|
163
|
-
)}
|
|
164
|
-
</div>
|
|
165
|
-
)}
|
|
166
|
-
</LegendOrdinal>
|
|
167
|
-
</aside>
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
return (
|
|
171
|
-
<aside id='legend' className={containerClasses.join(' ')} role='region' aria-label='legend' tabIndex={0}>
|
|
172
|
-
{legend.label && <h2>{parse(legend.label)}</h2>}
|
|
173
|
-
{legend.description && <p>{parse(legend.description)}</p>}
|
|
174
|
-
|
|
175
|
-
<LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
|
|
176
|
-
{labels => {
|
|
177
|
-
if (
|
|
178
|
-
Number(config.legend.dynamicLegendItemLimit) > dynamicLegendItems.length && // legend items are less than limit
|
|
179
|
-
dynamicLegendItems.length !== config.runtime.seriesLabelsAll.length
|
|
180
|
-
) {
|
|
181
|
-
// legend items are equal to series length
|
|
182
|
-
return (
|
|
183
|
-
<select className='dynamic-legend-dropdown' onChange={e => handleDynamicLegendChange(e)}>
|
|
184
|
-
<option className={'all'} tabIndex={0} value={JSON.stringify({ text: config.legend.dynamicLegendDefaultText })}>
|
|
185
|
-
{config.legend.dynamicLegendDefaultText}
|
|
186
|
-
</option>
|
|
187
|
-
{labels.map((label, i) => {
|
|
188
|
-
let className = 'legend-item'
|
|
189
|
-
let itemName = label.datum
|
|
190
|
-
let inDynamicList = false
|
|
191
|
-
|
|
192
|
-
// Filter excluded data keys from legend
|
|
193
|
-
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
194
|
-
return
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (config.runtime.seriesLabels) {
|
|
198
|
-
let index = config.runtime.seriesLabelsAll.indexOf(itemName)
|
|
199
|
-
itemName = config.runtime.seriesKeys[index]
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (seriesHighlight.length > 0 && false === seriesHighlight.includes(itemName)) {
|
|
203
|
-
className += ' inactive'
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
dynamicLegendItems.map(listItem => {
|
|
207
|
-
if (listItem.text === label.text) {
|
|
208
|
-
inDynamicList = true
|
|
209
|
-
}
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
if (inDynamicList) return true
|
|
213
|
-
let palette = colorPalettes[config.palette]
|
|
214
|
-
|
|
215
|
-
label.value = palette[dynamicLegendItems.length]
|
|
216
|
-
|
|
217
|
-
return (
|
|
218
|
-
<option className={className} tabIndex={0} value={JSON.stringify(label)}>
|
|
219
|
-
{label.text}
|
|
220
|
-
</option>
|
|
221
|
-
)
|
|
222
|
-
})}
|
|
223
|
-
</select>
|
|
224
|
-
)
|
|
225
|
-
} else {
|
|
226
|
-
return config.legend.dynamicLegendItemLimitMessage
|
|
227
|
-
}
|
|
228
|
-
}}
|
|
229
|
-
</LegendOrdinal>
|
|
230
|
-
|
|
231
|
-
<div className='dynamic-legend-list'>
|
|
232
|
-
{dynamicLegendItems.map((label, i) => {
|
|
233
|
-
let className = ['legend-item']
|
|
234
|
-
let itemName = label.text
|
|
235
|
-
let palette = colorPalettes[config.palette]
|
|
236
|
-
|
|
237
|
-
// Filter excluded data keys from legend
|
|
238
|
-
if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
|
|
239
|
-
return
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (config.runtime.seriesLabels && !config.legend.dynamicLegend) {
|
|
243
|
-
let index = config.runtime.seriesLabelsAll.indexOf(itemName)
|
|
244
|
-
itemName = config.runtime.seriesKeys[index]
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (seriesHighlight.length > 0 && !seriesHighlight.includes(itemName)) {
|
|
248
|
-
className.push('inactive')
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (seriesHighlight.length === 0 && config.legend.dynamicLegend) {
|
|
252
|
-
className.push('inactive')
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return (
|
|
256
|
-
<>
|
|
257
|
-
<LegendItem className={className.join(' ')} tabIndex={0} key={`dynamic-legend-item-${i}`} alignItems='center'>
|
|
258
|
-
<button
|
|
259
|
-
className='btn-wrapper'
|
|
260
|
-
onClick={() => {
|
|
261
|
-
highlight(label)
|
|
262
|
-
}}
|
|
263
|
-
>
|
|
264
|
-
<LegendCircle fill={palette[i]} config={config} />
|
|
265
|
-
<LegendLabel align='space-between' margin='4px 0 0 4px'>
|
|
266
|
-
{label.text}
|
|
267
|
-
</LegendLabel>
|
|
268
|
-
</button>
|
|
269
|
-
<button onClick={() => removeDynamicLegendItem(label)}>x</button>
|
|
270
|
-
</LegendItem>
|
|
271
|
-
</>
|
|
272
|
-
)
|
|
273
|
-
})}
|
|
274
|
-
</div>
|
|
275
|
-
{seriesHighlight.length < dynamicLegendItems.length && (
|
|
276
|
-
<button className={`legend-reset legend-reset--dynamic ${config.theme}`} onClick={highlightReset} tabIndex={0}>
|
|
277
|
-
Reset
|
|
278
|
-
</button>
|
|
279
|
-
)}
|
|
280
|
-
</aside>
|
|
281
|
-
)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export default Legend
|
|
@@ -1,27 +0,0 @@
|
|
|
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<any>()
|
|
5
|
-
|
|
6
|
-
const frozen = entry?.isIntersecting && freezeOnceVisible
|
|
7
|
-
|
|
8
|
-
const updateEntry = ([entry]) => {
|
|
9
|
-
setEntry(entry)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
const node = elementRef?.current
|
|
14
|
-
const hasIOSupport = !!window.IntersectionObserver
|
|
15
|
-
|
|
16
|
-
if (!hasIOSupport || frozen || !node) return
|
|
17
|
-
|
|
18
|
-
const observerParams = { threshold, root, rootMargin }
|
|
19
|
-
const observer = new IntersectionObserver(updateEntry, observerParams)
|
|
20
|
-
|
|
21
|
-
observer.observe(node)
|
|
22
|
-
|
|
23
|
-
return () => observer.disconnect()
|
|
24
|
-
}, [elementRef, threshold, root, rootMargin, frozen])
|
|
25
|
-
|
|
26
|
-
return entry
|
|
27
|
-
}
|
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
|