@cdc/chart 4.23.5 → 4.23.7
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 +29320 -28775
- package/examples/feature/__data__/planet-example-data.json +15 -16
- package/examples/feature/bar/new.json +561 -0
- package/examples/feature/combo/right-issues.json +190 -0
- package/examples/feature/forecasting/combo-forecasting.json +271 -0
- package/examples/feature/forecasting/effective_reproduction.json +57 -8
- package/examples/feature/forecasting/forecasting.json +5334 -0
- package/examples/feature/forecasting/index.json +203 -0
- package/examples/feature/line/line-chart.json +12 -12
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +167 -20
- package/examples/gallery/line/line.json +173 -1
- package/index.html +14 -8
- package/package.json +2 -2
- package/src/CdcChart.jsx +104 -26
- package/src/components/AreaChart.jsx +23 -149
- package/src/components/BarChart.jsx +87 -15
- package/src/components/DataTable.jsx +35 -14
- package/src/components/EditorPanel.jsx +1829 -1954
- package/src/components/Forecasting.jsx +84 -0
- package/src/components/Legend.jsx +191 -275
- package/src/components/LineChart.jsx +34 -7
- package/src/components/LinearChart.jsx +510 -101
- package/src/components/Series.jsx +554 -0
- package/src/components/SparkLine.jsx +3 -3
- package/src/data/initial-state.js +13 -5
- package/src/hooks/useMinMax.js +37 -0
- package/src/hooks/useRightAxis.js +9 -2
- package/src/hooks/useScales.js +7 -13
- package/src/scss/main.scss +4 -17
- package/LICENSE +0 -201
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
import ConfigContext from '../ConfigContext'
|
|
3
|
+
|
|
4
|
+
// Core
|
|
5
|
+
import InputSelect from '@cdc/core/components/inputs/InputSelect'
|
|
6
|
+
import Check from '@cdc/core/assets/icon-check.svg'
|
|
7
|
+
|
|
8
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
9
|
+
|
|
10
|
+
// Third Party
|
|
11
|
+
import { Accordion, AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
|
|
12
|
+
import { Draggable } from '@hello-pangea/dnd'
|
|
13
|
+
import { colorPalettesChart } from '@cdc/core/data/colorPalettes'
|
|
14
|
+
|
|
15
|
+
const SeriesContext = React.createContext()
|
|
16
|
+
|
|
17
|
+
const SeriesWrapper = props => {
|
|
18
|
+
const { updateConfig, config } = useContext(ConfigContext)
|
|
19
|
+
|
|
20
|
+
const { getColumns, selectComponent } = props
|
|
21
|
+
|
|
22
|
+
const supportedRightAxisTypes = ['Line', 'dashed-sm', 'dashed-md', 'dashed-lg']
|
|
23
|
+
|
|
24
|
+
const updateSeries = (index, value, property) => {
|
|
25
|
+
let series = [...config.series]
|
|
26
|
+
series[index][property] = value
|
|
27
|
+
|
|
28
|
+
updateConfig({ ...config, series })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return <SeriesContext.Provider value={{ updateSeries, supportedRightAxisTypes, getColumns, selectComponent }}>{props.children}</SeriesContext.Provider>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const SeriesDropdownLineType = props => {
|
|
35
|
+
const { config, updateConfig } = useContext(ConfigContext)
|
|
36
|
+
const { series, index } = props
|
|
37
|
+
|
|
38
|
+
// Run a quick test to determine if we should even show this.
|
|
39
|
+
const supportsLineType = ['Line', 'dashed-sm', 'dashed-md', 'dashed-lg', 'Area Chart'].some(item => item.includes(series.type))
|
|
40
|
+
|
|
41
|
+
if (!supportsLineType) return
|
|
42
|
+
|
|
43
|
+
const changeLineType = (i, value) => {
|
|
44
|
+
let series = [...config.series]
|
|
45
|
+
series[i].lineType = value
|
|
46
|
+
updateConfig({ ...config, series })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const approvedCurveTypes = {
|
|
50
|
+
Linear: 'curveLinear',
|
|
51
|
+
Cardinal: 'curveCardinal',
|
|
52
|
+
Natural: 'curveNatural',
|
|
53
|
+
'Monotone X': 'curveMonotoneX',
|
|
54
|
+
Step: 'curveStep'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let options = []
|
|
58
|
+
|
|
59
|
+
Object.keys(approvedCurveTypes).map(curveName => {
|
|
60
|
+
return options.push(approvedCurveTypes[curveName])
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<InputSelect
|
|
65
|
+
initial='Select an option'
|
|
66
|
+
value={series.lineType ? series.lineType : 'curveLinear'}
|
|
67
|
+
label='Series Line Type'
|
|
68
|
+
onChange={event => {
|
|
69
|
+
changeLineType(index, event.target.value)
|
|
70
|
+
}}
|
|
71
|
+
options={options}
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const SeriesDropdownSeriesType = props => {
|
|
77
|
+
const { config } = useContext(ConfigContext)
|
|
78
|
+
const { updateSeries } = useContext(SeriesContext)
|
|
79
|
+
|
|
80
|
+
const { index, series } = props
|
|
81
|
+
|
|
82
|
+
const getOptions = () => {
|
|
83
|
+
if (config.visualizationType === 'Combo') {
|
|
84
|
+
return {
|
|
85
|
+
Bar: 'Bar',
|
|
86
|
+
Line: 'Line',
|
|
87
|
+
'dashed-sm': 'Small Dashed',
|
|
88
|
+
'dashed-md': 'Medium Dashed',
|
|
89
|
+
'dashed-lg': 'Large Dashed',
|
|
90
|
+
'Area Chart': 'Area Chart',
|
|
91
|
+
Forecasting: 'Forecasting'
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (config.visualizationType === 'Line') {
|
|
95
|
+
return {
|
|
96
|
+
Line: 'Line',
|
|
97
|
+
'dashed-sm': 'Small Dashed',
|
|
98
|
+
'dashed-md': 'Medium Dashed',
|
|
99
|
+
'dashed-lg': 'Large Dashed'
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Allowable changes
|
|
105
|
+
if (!['Line', 'Combo'].includes(config.visualizationType)) return
|
|
106
|
+
return (
|
|
107
|
+
<InputSelect
|
|
108
|
+
initial='Select an option'
|
|
109
|
+
value={series.type}
|
|
110
|
+
label='Series Type'
|
|
111
|
+
onChange={event => {
|
|
112
|
+
updateSeries(index, event.target.value, 'type')
|
|
113
|
+
}}
|
|
114
|
+
options={getOptions()}
|
|
115
|
+
/>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const SeriesDropdownForecastingStage = props => {
|
|
120
|
+
const { config, updateConfig, rawData } = useContext(ConfigContext)
|
|
121
|
+
const { updateSeries, getColumns } = useContext(SeriesContext)
|
|
122
|
+
|
|
123
|
+
const { index, series } = props
|
|
124
|
+
|
|
125
|
+
// Only combo charts are allowed to have different options
|
|
126
|
+
if (series.type !== 'Forecasting') return
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<InputSelect
|
|
130
|
+
initial='Select an option'
|
|
131
|
+
value={series.stageColumn}
|
|
132
|
+
label='Add Forecasting Stages'
|
|
133
|
+
onChange={e => {
|
|
134
|
+
let stageObjects = []
|
|
135
|
+
let tempGroups = new Set(rawData?.map(item => item[e.target.value])) // [estimate, forecast, etc.]
|
|
136
|
+
tempGroups = Array.from(tempGroups) // convert set to array
|
|
137
|
+
|
|
138
|
+
tempGroups = tempGroups.filter(group => group !== undefined) // removes undefined
|
|
139
|
+
|
|
140
|
+
tempGroups.forEach(group => stageObjects.push({ key: group }))
|
|
141
|
+
|
|
142
|
+
const copyOfSeries = [...config.series] // copy the entire series array
|
|
143
|
+
copyOfSeries[index] = { ...copyOfSeries[index], stages: stageObjects, stageColumn: e.target.value }
|
|
144
|
+
|
|
145
|
+
updateConfig({
|
|
146
|
+
...config,
|
|
147
|
+
series: copyOfSeries
|
|
148
|
+
})
|
|
149
|
+
}}
|
|
150
|
+
options={getColumns(false)}
|
|
151
|
+
/>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const SeriesDropdownForecastingColumn = props => {
|
|
156
|
+
const { config, rawData } = useContext(ConfigContext)
|
|
157
|
+
const { updateSeries } = useContext(SeriesContext)
|
|
158
|
+
|
|
159
|
+
const { index, series } = props
|
|
160
|
+
|
|
161
|
+
// Only combo charts are allowed to have different options
|
|
162
|
+
if (series.type !== 'Forecasting') return
|
|
163
|
+
if (!rawData) return
|
|
164
|
+
if (!series.stageColumn) return
|
|
165
|
+
|
|
166
|
+
let tempGroups = new Set(rawData.map(item => item[series.stageColumn])) // [estimate, forecast, etc.]
|
|
167
|
+
tempGroups = Array.from(tempGroups) // convert set to array
|
|
168
|
+
|
|
169
|
+
tempGroups = tempGroups.filter(group => group !== undefined) // removes undefined
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<InputSelect
|
|
173
|
+
initial='Select an option'
|
|
174
|
+
value={series.stageItem}
|
|
175
|
+
label='Forecasting Item Column'
|
|
176
|
+
onChange={event => {
|
|
177
|
+
updateSeries(index, event.target.value, 'stageItem')
|
|
178
|
+
}}
|
|
179
|
+
options={tempGroups}
|
|
180
|
+
/>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const SeriesDropdownAxisPosition = props => {
|
|
185
|
+
const { config } = useContext(ConfigContext)
|
|
186
|
+
const { updateSeries, supportedRightAxisTypes } = useContext(SeriesContext)
|
|
187
|
+
|
|
188
|
+
const { index, series } = props
|
|
189
|
+
|
|
190
|
+
// Hide AxisPositionDropdown in certain cases.
|
|
191
|
+
if (config.visualizationType !== 'Combo' || !series) return
|
|
192
|
+
if (!supportedRightAxisTypes.includes(series.type)) {
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
return (
|
|
196
|
+
<InputSelect
|
|
197
|
+
initial='Select an option'
|
|
198
|
+
value={series.axis ? series.axis : 'Left'}
|
|
199
|
+
label='Series Axis'
|
|
200
|
+
onChange={event => {
|
|
201
|
+
updateSeries(index, event.target.value, 'axis')
|
|
202
|
+
}}
|
|
203
|
+
options={{
|
|
204
|
+
['Left']: 'Left',
|
|
205
|
+
['Right']: 'Right'
|
|
206
|
+
}}
|
|
207
|
+
/>
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const SeriesDropdownForecastColor = props => {
|
|
212
|
+
const { config, updateConfig } = useContext(ConfigContext)
|
|
213
|
+
|
|
214
|
+
const { index, series } = props
|
|
215
|
+
|
|
216
|
+
if (series.type !== 'Forecasting') return
|
|
217
|
+
|
|
218
|
+
// Hide AxisPositionDropdown in certain cases.
|
|
219
|
+
if (!series) return
|
|
220
|
+
|
|
221
|
+
return series?.stages?.map((stage, stageIndex) => (
|
|
222
|
+
<InputSelect
|
|
223
|
+
key={`${stage}--${stageIndex}`}
|
|
224
|
+
initial='Select an option'
|
|
225
|
+
value={config.series?.[index].stages?.[stageIndex].color ? config.series?.[index].stages?.[stageIndex].color : 'Select'}
|
|
226
|
+
label={`${stage.key} Series Color`}
|
|
227
|
+
onChange={event => {
|
|
228
|
+
const copyOfSeries = [...config.series] // copy the entire series array
|
|
229
|
+
const copyOfStages = copyOfSeries[index].stages
|
|
230
|
+
copyOfStages[stageIndex].color = event.target.value
|
|
231
|
+
copyOfSeries[index] = { ...copyOfSeries[index], stages: copyOfStages }
|
|
232
|
+
|
|
233
|
+
updateConfig({
|
|
234
|
+
...config,
|
|
235
|
+
series: copyOfSeries
|
|
236
|
+
})
|
|
237
|
+
}}
|
|
238
|
+
options={Object.keys(colorPalettesChart)}
|
|
239
|
+
/>
|
|
240
|
+
))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const SeriesDropdownConfidenceInterval = props => {
|
|
244
|
+
const { config, updateConfig } = useContext(ConfigContext)
|
|
245
|
+
const { series, index } = props
|
|
246
|
+
const { getColumns } = useContext(SeriesContext)
|
|
247
|
+
if (series.type !== 'Forecasting') return
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<div className='edit-block'>
|
|
251
|
+
<span className='edit-label column-heading'>Confidence Interval Groups</span>
|
|
252
|
+
<fieldset>
|
|
253
|
+
<Accordion allowZeroExpanded>
|
|
254
|
+
{series?.confidenceIntervals?.map((ciGroup, ciIndex) => {
|
|
255
|
+
const showInTooltip = ciGroup.showInTooltip ? ciGroup.showInTooltip : false
|
|
256
|
+
|
|
257
|
+
const updateShowInTooltip = (e, seriesIndex, ciIndex) => {
|
|
258
|
+
e.preventDefault()
|
|
259
|
+
let copiedSeries = [...config.series]
|
|
260
|
+
copiedSeries[seriesIndex].confidenceIntervals[ciIndex].showInTooltip = !showInTooltip
|
|
261
|
+
updateConfig({
|
|
262
|
+
...config,
|
|
263
|
+
series: copiedSeries
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<AccordionItem className='series-item series-item--chart' key={`${ciIndex}`}>
|
|
269
|
+
<AccordionItemHeading className='series-item__title'>
|
|
270
|
+
<>
|
|
271
|
+
<AccordionItemButton className={'accordion__button accordion__button'}>
|
|
272
|
+
Group {ciIndex + 1}
|
|
273
|
+
<button
|
|
274
|
+
className='series-list__remove'
|
|
275
|
+
onClick={e => {
|
|
276
|
+
e.preventDefault()
|
|
277
|
+
const copiedIndex = [...config.series[index].confidenceIntervals]
|
|
278
|
+
copiedIndex.splice(ciIndex, 1)
|
|
279
|
+
const copyOfSeries = [...config.series] // copy the entire series array
|
|
280
|
+
copyOfSeries[index] = { ...copyOfSeries[index], confidenceIntervals: [...copiedIndex] }
|
|
281
|
+
updateConfig({
|
|
282
|
+
...config,
|
|
283
|
+
series: copyOfSeries
|
|
284
|
+
})
|
|
285
|
+
}}
|
|
286
|
+
>
|
|
287
|
+
Remove
|
|
288
|
+
</button>
|
|
289
|
+
</AccordionItemButton>
|
|
290
|
+
</>
|
|
291
|
+
</AccordionItemHeading>
|
|
292
|
+
<AccordionItemPanel>
|
|
293
|
+
{/* <div className='input-group'>
|
|
294
|
+
<label htmlFor='showInTooltip'>Show In Tooltip</label>
|
|
295
|
+
<div className={'cove-input__checkbox--small'} onClick={e => updateShowInTooltip(e, index, ciIndex)}>
|
|
296
|
+
<div className={`cove-input__checkbox-box${'blue' ? ' custom-color' : ''}`} style={{ backgroundColor: '' }}>
|
|
297
|
+
{showInTooltip && <Check className='' style={{ fill: '#025eaa' }} />}
|
|
298
|
+
</div>
|
|
299
|
+
<input className='cove-input--hidden' type='checkbox' name={'showInTooltip'} checked={showInTooltip ? showInTooltip : false} readOnly />
|
|
300
|
+
</div>
|
|
301
|
+
</div> */}
|
|
302
|
+
|
|
303
|
+
<InputSelect
|
|
304
|
+
initial='Select an option'
|
|
305
|
+
value={config.series[index].confidenceIntervals[ciIndex].low ? config.series[index].confidenceIntervals[ciIndex].low : 'Select'}
|
|
306
|
+
label='Low Confidence Interval'
|
|
307
|
+
onChange={e => {
|
|
308
|
+
const copiedConfidenceArray = [...config.series[index].confidenceIntervals]
|
|
309
|
+
copiedConfidenceArray[ciIndex].low = e.target.value
|
|
310
|
+
const copyOfSeries = [...config.series] // copy the entire series array
|
|
311
|
+
copyOfSeries[index] = { ...copyOfSeries[index], confidenceIntervals: copiedConfidenceArray }
|
|
312
|
+
updateConfig({
|
|
313
|
+
...config,
|
|
314
|
+
series: copyOfSeries
|
|
315
|
+
})
|
|
316
|
+
}}
|
|
317
|
+
options={getColumns()}
|
|
318
|
+
/>
|
|
319
|
+
<InputSelect
|
|
320
|
+
initial='Select an option'
|
|
321
|
+
value={config.series[index].confidenceIntervals[ciIndex].high ? config.series[index].confidenceIntervals[ciIndex].high : 'Select'}
|
|
322
|
+
label='High Confidence Interval'
|
|
323
|
+
onChange={e => {
|
|
324
|
+
const copiedConfidenceArray = [...config.series[index].confidenceIntervals]
|
|
325
|
+
copiedConfidenceArray[ciIndex].high = e.target.value
|
|
326
|
+
const copyOfSeries = [...config.series] // copy the entire series array
|
|
327
|
+
copyOfSeries[index] = { ...copyOfSeries[index], confidenceIntervals: copiedConfidenceArray }
|
|
328
|
+
updateConfig({
|
|
329
|
+
...config,
|
|
330
|
+
series: copyOfSeries
|
|
331
|
+
})
|
|
332
|
+
}}
|
|
333
|
+
options={getColumns()}
|
|
334
|
+
/>
|
|
335
|
+
</AccordionItemPanel>
|
|
336
|
+
</AccordionItem>
|
|
337
|
+
)
|
|
338
|
+
})}
|
|
339
|
+
</Accordion>
|
|
340
|
+
<button
|
|
341
|
+
className='btn full-width'
|
|
342
|
+
onClick={e => {
|
|
343
|
+
e.preventDefault()
|
|
344
|
+
let copiedIndex = null
|
|
345
|
+
if (config.series[index].confidenceIntervals) {
|
|
346
|
+
copiedIndex = [...config.series[index].confidenceIntervals]
|
|
347
|
+
} else {
|
|
348
|
+
copiedIndex = []
|
|
349
|
+
}
|
|
350
|
+
const copyOfSeries = [...config.series] // copy the entire series array
|
|
351
|
+
copyOfSeries[index] = { ...copyOfSeries[index], confidenceIntervals: [...copiedIndex, { high: '', low: '' }] } // update the nested array
|
|
352
|
+
updateConfig({
|
|
353
|
+
...config,
|
|
354
|
+
series: copyOfSeries
|
|
355
|
+
})
|
|
356
|
+
}}
|
|
357
|
+
>
|
|
358
|
+
Add Confidence Interval Group
|
|
359
|
+
</button>
|
|
360
|
+
</fieldset>
|
|
361
|
+
</div>
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const SeriesInputName = props => {
|
|
366
|
+
const { series, index: i } = props
|
|
367
|
+
const { config, updateConfig } = useContext(ConfigContext)
|
|
368
|
+
const adjustableNameSeriesTypes = ['Bar', 'Line', 'Area Chart']
|
|
369
|
+
|
|
370
|
+
if (config.visualizationType === 'Combo') return
|
|
371
|
+
|
|
372
|
+
if (!adjustableNameSeriesTypes.includes(series.type)) return
|
|
373
|
+
|
|
374
|
+
let changeSeriesName = (i, value) => {
|
|
375
|
+
let series = [...config.series]
|
|
376
|
+
let seriesLabelsCopy = { ...config.runtime.seriesLabels }
|
|
377
|
+
series[i].name = value
|
|
378
|
+
seriesLabelsCopy[series[i].dataKey] = series[i].name
|
|
379
|
+
|
|
380
|
+
let newConfig = {
|
|
381
|
+
...config,
|
|
382
|
+
series
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
updateConfig(newConfig)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return (
|
|
389
|
+
<>
|
|
390
|
+
<label htmlFor='series-name'>Series Name</label>
|
|
391
|
+
<input
|
|
392
|
+
type='text'
|
|
393
|
+
key={`series-name-${i}`}
|
|
394
|
+
value={series.name ? series.name : ''}
|
|
395
|
+
onChange={event => {
|
|
396
|
+
changeSeriesName(i, event.target.value)
|
|
397
|
+
}}
|
|
398
|
+
/>
|
|
399
|
+
</>
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const SeriesDisplayInTooltip = props => {
|
|
404
|
+
const { series, index } = props
|
|
405
|
+
const { config, updateConfig } = useContext(ConfigContext)
|
|
406
|
+
|
|
407
|
+
const toggleTooltip = seriesIndex => {
|
|
408
|
+
let copiedSeries = [...config.series]
|
|
409
|
+
|
|
410
|
+
const showInTooltip = copiedSeries[seriesIndex].tooltip ? copiedSeries[seriesIndex].tooltip : false
|
|
411
|
+
|
|
412
|
+
copiedSeries[seriesIndex].tooltip = !copiedSeries[seriesIndex].tooltip
|
|
413
|
+
|
|
414
|
+
updateConfig({
|
|
415
|
+
...config,
|
|
416
|
+
series: copiedSeries
|
|
417
|
+
})
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return (
|
|
421
|
+
<>
|
|
422
|
+
<div className='input-group'>
|
|
423
|
+
<label htmlFor={`series-tooltip--${index}`}>Show In Tooltip</label>
|
|
424
|
+
<div className={'cove-input__checkbox--small'} onClick={e => toggleTooltip(index)}>
|
|
425
|
+
<div className={`cove-input__checkbox-box${'blue' ? ' custom-color' : ''}`} style={{ backgroundColor: '' }}>
|
|
426
|
+
{series.tooltip && <Check className='' style={{ fill: '#025eaa' }} />}
|
|
427
|
+
</div>
|
|
428
|
+
<input className='cove-input--hidden' type='checkbox' name={`series-tooltip--${index}`} checked={series.tooltip ? series.tooltip : false} readOnly />
|
|
429
|
+
</div>
|
|
430
|
+
</div>
|
|
431
|
+
</>
|
|
432
|
+
)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const SeriesButtonRemove = props => {
|
|
436
|
+
const { config, updateConfig } = useContext(ConfigContext)
|
|
437
|
+
const { series, index } = props
|
|
438
|
+
|
|
439
|
+
const removeSeries = seriesKey => {
|
|
440
|
+
let series = [...config.series]
|
|
441
|
+
let seriesIndex = -1
|
|
442
|
+
|
|
443
|
+
for (let i = 0; i < series.length; i++) {
|
|
444
|
+
if (series[i].dataKey === seriesKey) {
|
|
445
|
+
seriesIndex = i
|
|
446
|
+
break
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (seriesIndex !== -1) {
|
|
451
|
+
series.splice(seriesIndex, 1)
|
|
452
|
+
|
|
453
|
+
let newConfig = { ...config, series }
|
|
454
|
+
|
|
455
|
+
if (series.length === 0) {
|
|
456
|
+
delete newConfig.series
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
updateConfig(newConfig)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (config.visualizationType === 'Paired Bar') {
|
|
463
|
+
updateConfig({
|
|
464
|
+
...config,
|
|
465
|
+
series: []
|
|
466
|
+
})
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const handleRemoveSeries = (e, series, index) => {
|
|
471
|
+
e.preventDefault()
|
|
472
|
+
removeSeries(series.dataKey)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return (
|
|
476
|
+
config.series &&
|
|
477
|
+
config.series.length > 1 && (
|
|
478
|
+
<button className='series-list__remove' onClick={e => handleRemoveSeries(e, series, index)}>
|
|
479
|
+
Remove
|
|
480
|
+
</button>
|
|
481
|
+
)
|
|
482
|
+
)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const SeriesItem = props => {
|
|
486
|
+
const { config } = useContext(ConfigContext)
|
|
487
|
+
|
|
488
|
+
const { series, getItemStyle, sortableItemStyles, chartsWithOptions, index: i } = props
|
|
489
|
+
|
|
490
|
+
return (
|
|
491
|
+
<Draggable key={series.dataKey} draggableId={`draggableFilter-${series.dataKey}`} index={i}>
|
|
492
|
+
{(provided, snapshot) => (
|
|
493
|
+
<div key={i} className={snapshot.isDragging ? 'currently-dragging' : ''} style={getItemStyle(snapshot.isDragging, provided.draggableProps.style, sortableItemStyles)} ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
|
|
494
|
+
<Accordion allowZeroExpanded>
|
|
495
|
+
<AccordionItem className='series-item series-item--chart'>
|
|
496
|
+
<AccordionItemHeading className='series-item__title'>
|
|
497
|
+
<AccordionItemButton className={chartsWithOptions.includes(config.visualizationType) ? 'accordion__button' : 'accordion__button hide-arrow'}>
|
|
498
|
+
<Icon display='move' size={15} style={{ cursor: 'default' }} />
|
|
499
|
+
{series.dataKey}
|
|
500
|
+
<Series.Button.Remove series={series} index={i} />
|
|
501
|
+
</AccordionItemButton>
|
|
502
|
+
</AccordionItemHeading>
|
|
503
|
+
{chartsWithOptions.includes(config.visualizationType) && (
|
|
504
|
+
<AccordionItemPanel>
|
|
505
|
+
<Series.Input.Name series={series} index={i} />
|
|
506
|
+
<Series.Dropdown.SeriesType series={series} index={i} />
|
|
507
|
+
<Series.Dropdown.AxisPosition series={series} index={i} />
|
|
508
|
+
<Series.Dropdown.LineType series={series} index={i} />
|
|
509
|
+
{/* <Series.Dropdown.ForecastingStage series={series} index={i} /> */}
|
|
510
|
+
<Series.Dropdown.ForecastingColor series={series} index={i} />
|
|
511
|
+
<Series.Dropdown.ConfidenceInterval series={series} index={i} />
|
|
512
|
+
<Series.Checkbox.DisplayInTooltip series={series} index={i} />
|
|
513
|
+
</AccordionItemPanel>
|
|
514
|
+
)}
|
|
515
|
+
</AccordionItem>
|
|
516
|
+
</Accordion>
|
|
517
|
+
</div>
|
|
518
|
+
)}
|
|
519
|
+
</Draggable>
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const SeriesList = props => {
|
|
524
|
+
const { series, getItemStyle, sortableItemStyles, chartsWithOptions } = props
|
|
525
|
+
return series.map((series, i) => {
|
|
526
|
+
return <SeriesItem getItemStyle={getItemStyle} sortableItemStyles={sortableItemStyles} chartsWithOptions={chartsWithOptions} series={series} index={i} key={`series-list-${i}`} />
|
|
527
|
+
})
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const Series = {
|
|
531
|
+
Wrapper: SeriesWrapper,
|
|
532
|
+
Dropdown: {
|
|
533
|
+
SeriesType: SeriesDropdownSeriesType,
|
|
534
|
+
AxisPosition: SeriesDropdownAxisPosition,
|
|
535
|
+
ConfidenceInterval: SeriesDropdownConfidenceInterval,
|
|
536
|
+
LineType: SeriesDropdownLineType,
|
|
537
|
+
ForecastingStage: SeriesDropdownForecastingStage,
|
|
538
|
+
ForecastingColumn: SeriesDropdownForecastingColumn,
|
|
539
|
+
ForecastingColor: SeriesDropdownForecastColor
|
|
540
|
+
},
|
|
541
|
+
Input: {
|
|
542
|
+
Name: SeriesInputName
|
|
543
|
+
},
|
|
544
|
+
Checkbox: {
|
|
545
|
+
DisplayInTooltip: SeriesDisplayInTooltip
|
|
546
|
+
},
|
|
547
|
+
Button: {
|
|
548
|
+
Remove: SeriesButtonRemove
|
|
549
|
+
},
|
|
550
|
+
Item: SeriesItem,
|
|
551
|
+
List: SeriesList
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export default Series
|
|
@@ -121,8 +121,8 @@ export default function SparkLine({ width: parentWidth, height: parentHeight })
|
|
|
121
121
|
<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'>
|
|
122
122
|
{formatNumber(d[seriesKey])}
|
|
123
123
|
</Text>
|
|
124
|
-
|
|
125
|
-
{dataIndex + 1 !== data.length && (config.lineDatapointStyle === 'always show' || config.lineDatapointStyle === 'hover') && (
|
|
124
|
+
{/* hide data point circles */}
|
|
125
|
+
{/* dataIndex + 1 !== data.length && (config.lineDatapointStyle === 'always show' || config.lineDatapointStyle === 'hover') && (
|
|
126
126
|
<circle
|
|
127
127
|
key={`${seriesKey}-${dataIndex}`}
|
|
128
128
|
r={circleRadii}
|
|
@@ -133,7 +133,7 @@ export default function SparkLine({ width: parentWidth, height: parentHeight })
|
|
|
133
133
|
data-tooltip-html={tooltip}
|
|
134
134
|
data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
|
|
135
135
|
/>
|
|
136
|
-
)}
|
|
136
|
+
)*/}
|
|
137
137
|
</Group>
|
|
138
138
|
)
|
|
139
139
|
})}
|
|
@@ -15,6 +15,7 @@ export default {
|
|
|
15
15
|
barStyle: '',
|
|
16
16
|
roundingStyle: 'standard',
|
|
17
17
|
tipRounding: 'top',
|
|
18
|
+
isResponsiveTicks: false,
|
|
18
19
|
general: {
|
|
19
20
|
showDownloadButton: false
|
|
20
21
|
},
|
|
@@ -36,7 +37,7 @@ export default {
|
|
|
36
37
|
tickLabelColor: '#333',
|
|
37
38
|
tickColor: '#333',
|
|
38
39
|
rightHideAxis: true,
|
|
39
|
-
rightAxisSize:
|
|
40
|
+
rightAxisSize: 0,
|
|
40
41
|
rightLabel: '',
|
|
41
42
|
rightLabelOffsetSize: 0,
|
|
42
43
|
rightAxisLabelColor: '#333',
|
|
@@ -106,7 +107,8 @@ export default {
|
|
|
106
107
|
numTicks: '',
|
|
107
108
|
labelOffset: 65,
|
|
108
109
|
axisPadding: 0,
|
|
109
|
-
target: 0
|
|
110
|
+
target: 0,
|
|
111
|
+
maxTickRotation: 0
|
|
110
112
|
},
|
|
111
113
|
table: {
|
|
112
114
|
label: 'Data Table',
|
|
@@ -122,7 +124,8 @@ export default {
|
|
|
122
124
|
},
|
|
123
125
|
orientation: 'vertical',
|
|
124
126
|
color: 'pinkpurple',
|
|
125
|
-
columns: {
|
|
127
|
+
columns: {
|
|
128
|
+
// start with a blank list
|
|
126
129
|
},
|
|
127
130
|
legend: {
|
|
128
131
|
behavior: 'isolate',
|
|
@@ -160,10 +163,15 @@ export default {
|
|
|
160
163
|
visual: {
|
|
161
164
|
border: true,
|
|
162
165
|
accent: true,
|
|
163
|
-
background: true
|
|
166
|
+
background: true,
|
|
167
|
+
verticalHoverLine: false,
|
|
168
|
+
horizontalHoverLine: false
|
|
164
169
|
},
|
|
165
170
|
useLogScale: false,
|
|
166
171
|
filterBehavior: 'Filter Change',
|
|
167
172
|
highlightedBarValues: [],
|
|
168
|
-
series: []
|
|
173
|
+
series: [],
|
|
174
|
+
tooltips: {
|
|
175
|
+
opacity: 90
|
|
176
|
+
}
|
|
169
177
|
}
|
package/src/hooks/useMinMax.js
CHANGED
|
@@ -26,6 +26,43 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
if (config.visualizationType === 'Forecasting') {
|
|
30
|
+
const {
|
|
31
|
+
runtime: { forecastingSeriesKeys }
|
|
32
|
+
} = config
|
|
33
|
+
if (forecastingSeriesKeys.length > 0) {
|
|
34
|
+
// push all keys into an array
|
|
35
|
+
let columnNames = []
|
|
36
|
+
|
|
37
|
+
forecastingSeriesKeys.forEach(f => {
|
|
38
|
+
f.confidenceIntervals?.map(ciGroup => {
|
|
39
|
+
columnNames.push(ciGroup.high)
|
|
40
|
+
columnNames.push(ciGroup.low)
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// Using the columnNames or "keys" get the returned result
|
|
45
|
+
const result = data.map(obj => columnNames.map(key => obj[key]))
|
|
46
|
+
|
|
47
|
+
const highCIGroup = Math.max.apply(
|
|
48
|
+
null,
|
|
49
|
+
result.map(item => item[0])
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const lowCIGroup = Math.min.apply(
|
|
53
|
+
null,
|
|
54
|
+
result.map(item => item[1])
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if (highCIGroup > max) {
|
|
58
|
+
max = highCIGroup
|
|
59
|
+
}
|
|
60
|
+
if (lowCIGroup < min) {
|
|
61
|
+
min = lowCIGroup
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
29
66
|
if ((config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && !isAllLine)) && min > 0) {
|
|
30
67
|
min = 0
|
|
31
68
|
}
|
|
@@ -4,7 +4,7 @@ import useReduceData from '../hooks/useReduceData'
|
|
|
4
4
|
export default function useRightAxis({ config, yMax = 0, data = [], updateConfig }) {
|
|
5
5
|
const hasRightAxis = config.visualizationType === 'Combo' && config.orientation === 'vertical'
|
|
6
6
|
const rightSeriesKeys = config.series && config.series.filter(series => series.axis === 'Right').map(key => key.dataKey)
|
|
7
|
-
|
|
7
|
+
let { minValue } = useReduceData(config, data)
|
|
8
8
|
|
|
9
9
|
const allRightAxisData = rightSeriesKeys => {
|
|
10
10
|
if (!rightSeriesKeys) return [0]
|
|
@@ -15,9 +15,16 @@ export default function useRightAxis({ config, yMax = 0, data = [], updateConfig
|
|
|
15
15
|
return rightAxisData
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const min = Math.min.apply(null, allRightAxisData(rightSeriesKeys))
|
|
19
18
|
const max = Math.max.apply(null, allRightAxisData(rightSeriesKeys))
|
|
20
19
|
|
|
20
|
+
// if there is a bar series & the right axis doesn't include a negative number, default to zero
|
|
21
|
+
const hasBarSeries = config.runtime?.barSeriesKeys?.length > 0
|
|
22
|
+
const hasLineSeries = config.runtime?.lineSeriesKeys?.length > 0
|
|
23
|
+
|
|
24
|
+
if ((hasBarSeries || hasLineSeries) && minValue > 0) {
|
|
25
|
+
minValue = 0
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
const yScaleRight = scaleLinear({
|
|
22
29
|
domain: [minValue, max],
|
|
23
30
|
range: [yMax, 0]
|