@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.
Files changed (74) hide show
  1. package/dist/cdcchart.js +56289 -702
  2. package/examples/Barchart_with_negative.json +34 -0
  3. package/examples/area-chart.json +187 -0
  4. package/examples/big-small-test-bar.json +328 -0
  5. package/examples/big-small-test-line.json +328 -0
  6. package/examples/big-small-test-negative.json +328 -0
  7. package/examples/box-plot.json +1 -2
  8. package/examples/dynamic-legends.json +1 -1
  9. package/examples/example-bar-chart-nonnumeric.json +36 -0
  10. package/examples/example-bar-chart.json +36 -0
  11. package/examples/example-combo-bar-nonnumeric.json +105 -0
  12. package/examples/example-sparkline.json +76 -0
  13. package/examples/gallery/bar-chart-horizontal/horizontal-bar-chart.json +31 -172
  14. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +1 -1
  15. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-confidence.json +1 -0
  16. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-with-confidence.json +96 -14
  17. package/examples/gallery/bar-chart-vertical/vertical-bar-chart.json +2 -2
  18. package/examples/gallery/line/line.json +1 -0
  19. package/examples/gallery/paired-bar/paired-bar-chart.json +65 -13
  20. package/examples/horizontal-chart-max-increase.json +38 -0
  21. package/examples/line-chart-max-increase.json +32 -0
  22. package/examples/line-chart-nonnumeric.json +32 -0
  23. package/examples/line-chart.json +21 -63
  24. package/examples/newdata.json +1 -1
  25. package/examples/planet-combo-example-config.json +143 -20
  26. package/examples/planet-deviation-config.json +168 -0
  27. package/examples/planet-deviation-data.json +38 -0
  28. package/examples/planet-example-config.json +139 -20
  29. package/examples/planet-example-data-max-increase.json +56 -0
  30. package/examples/planet-example-data-nonnumeric.json +56 -0
  31. package/examples/planet-example-data.json +9 -9
  32. package/examples/planet-pie-example-config-nonnumeric.json +30 -0
  33. package/examples/scatterplot-continuous.csv +17 -0
  34. package/examples/scatterplot.json +136 -0
  35. package/examples/sparkline-chart-nonnumeric.json +76 -0
  36. package/examples/stacked-vertical-bar-example-negative.json +154 -0
  37. package/examples/stacked-vertical-bar-example-nonnumerics.json +154 -0
  38. package/index.html +91 -0
  39. package/package.json +33 -24
  40. package/src/{CdcChart.tsx → CdcChart.jsx} +196 -124
  41. package/src/components/AreaChart.jsx +198 -0
  42. package/src/components/{BarChart.tsx → BarChart.jsx} +154 -122
  43. package/src/components/BoxPlot.jsx +101 -0
  44. package/src/components/{DataTable.tsx → DataTable.jsx} +109 -28
  45. package/src/components/DeviationBar.jsx +191 -0
  46. package/src/components/{EditorPanel.js → EditorPanel.jsx} +676 -157
  47. package/src/components/{Filters.js → Filters.jsx} +6 -11
  48. package/src/components/Legend.jsx +316 -0
  49. package/src/components/{LineChart.tsx → LineChart.jsx} +22 -26
  50. package/src/components/{LinearChart.tsx → LinearChart.jsx} +214 -91
  51. package/src/components/{PairedBarChart.tsx → PairedBarChart.jsx} +44 -78
  52. package/src/components/{PieChart.tsx → PieChart.jsx} +26 -44
  53. package/src/components/ScatterPlot.jsx +51 -0
  54. package/src/components/SparkLine.jsx +218 -0
  55. package/src/components/{useIntersectionObserver.tsx → useIntersectionObserver.jsx} +2 -2
  56. package/src/data/initial-state.js +51 -5
  57. package/src/hooks/useColorPalette.js +68 -0
  58. package/src/hooks/{useReduceData.ts → useReduceData.js} +26 -16
  59. package/src/hooks/useRightAxis.js +3 -1
  60. package/src/index.jsx +16 -0
  61. package/src/scss/DataTable.scss +22 -0
  62. package/src/scss/editor-panel.scss +5 -0
  63. package/src/scss/main.scss +30 -10
  64. package/src/test/CdcChart.test.jsx +6 -0
  65. package/vite.config.js +4 -0
  66. package/dist/495.js +0 -3
  67. package/dist/703.js +0 -1
  68. package/src/components/BoxPlot.js +0 -92
  69. package/src/components/Legend.js +0 -291
  70. package/src/components/SparkLine.js +0 -185
  71. package/src/hooks/useColorPalette.ts +0 -76
  72. package/src/index.html +0 -67
  73. package/src/index.tsx +0 -18
  74. /package/src/{context.tsx → ConfigContext.jsx} +0 -0
@@ -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