@cdc/chart 4.23.6 → 4.23.8

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 (48) hide show
  1. package/dist/cdcchart.js +29981 -29995
  2. package/examples/feature/__data__/area-chart-date-apple.json +5122 -0
  3. package/examples/feature/__data__/city-temperature.json +2198 -0
  4. package/examples/feature/__data__/planet-example-data.json +1 -1
  5. package/examples/feature/area/area-chart-category.json +45 -45
  6. package/examples/feature/area/area-chart-date-apple.json +10376 -0
  7. package/examples/feature/area/area-chart-date-city-temperature.json +4528 -0
  8. package/examples/feature/area/area-chart-date.json +111 -3
  9. package/examples/feature/combo/right-issues.json +1 -1
  10. package/examples/feature/forecasting/combo-forecasting.json +72 -46
  11. package/examples/feature/forecasting/effective_reproduction.json +57 -8
  12. package/examples/feature/forecasting/forecasting.json +12 -3
  13. package/examples/feature/forest-plot/broken.json +700 -0
  14. package/examples/feature/forest-plot/data.csv +24 -0
  15. package/examples/feature/forest-plot/forest-plot.json +717 -0
  16. package/examples/feature/line/line-chart.json +11 -11
  17. package/examples/feature/pie/planet-pie-example-config.json +1 -1
  18. package/examples/gallery/bar-chart-vertical/vertical-bar-chart-categorical.json +167 -20
  19. package/examples/private/confidence_interval_test.json +248 -0
  20. package/examples/private/tooltip-issue.json +45275 -0
  21. package/index.html +13 -11
  22. package/package.json +4 -3
  23. package/src/CdcChart.jsx +78 -27
  24. package/src/components/AreaChart.jsx +65 -151
  25. package/src/components/BarChart.Horizontal.jsx +251 -0
  26. package/src/components/BarChart.StackedHorizontal.jsx +118 -0
  27. package/src/components/BarChart.StackedVertical.jsx +93 -0
  28. package/src/components/BarChart.Vertical.jsx +204 -0
  29. package/src/components/BarChart.jsx +17 -667
  30. package/src/components/BarChartType.jsx +15 -0
  31. package/src/components/BrushHandle.jsx +17 -0
  32. package/src/components/DataTable.jsx +67 -22
  33. package/src/components/EditorPanel.jsx +426 -358
  34. package/src/components/Forecasting.jsx +23 -86
  35. package/src/components/ForestPlot.jsx +191 -0
  36. package/src/components/ForestPlotSettings.jsx +508 -0
  37. package/src/components/Legend.jsx +10 -8
  38. package/src/components/LineChart.jsx +31 -6
  39. package/src/components/LinearChart.jsx +317 -230
  40. package/src/components/Series.jsx +40 -4
  41. package/src/data/initial-state.js +50 -3
  42. package/src/hooks/useBarChart.js +186 -0
  43. package/src/hooks/useEditorPermissions.js +218 -0
  44. package/src/hooks/useMinMax.js +18 -5
  45. package/src/hooks/useRightAxis.js +2 -1
  46. package/src/hooks/useScales.js +45 -2
  47. package/src/hooks/useTooltip.jsx +407 -0
  48. package/src/scss/main.scss +11 -17
@@ -0,0 +1,508 @@
1
+ import React, { useContext, memo, useState, useEffect } from 'react'
2
+ import ConfigContext from '../ConfigContext'
3
+ import { useDebounce } from 'use-debounce'
4
+ import WarningImage from '../images/warning.svg'
5
+
6
+ import { AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
7
+
8
+ const Select = memo(({ label, value, options, fieldName, section = null, subsection = null, required = false, tooltip, updateField, initial: initialValue, ...attributes }) => {
9
+ let optionsJsx = options.map((optionName, index) => (
10
+ <option value={optionName} key={index}>
11
+ {optionName}
12
+ </option>
13
+ ))
14
+
15
+ if (initialValue) {
16
+ optionsJsx.unshift(
17
+ <option value='' key='initial'>
18
+ {initialValue}
19
+ </option>
20
+ )
21
+ }
22
+
23
+ return (
24
+ <label>
25
+ <span className='edit-label'>
26
+ {label}
27
+ {tooltip}
28
+ </span>
29
+ <select
30
+ className={required && !value ? 'warning' : ''}
31
+ name={fieldName}
32
+ value={value}
33
+ onChange={event => {
34
+ updateField(section, subsection, fieldName, event.target.value)
35
+ }}
36
+ {...attributes}
37
+ >
38
+ {optionsJsx}
39
+ </select>
40
+ </label>
41
+ )
42
+ })
43
+
44
+ const CheckBox = memo(({ label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes }) => (
45
+ <label className='checkbox column-heading'>
46
+ <input
47
+ type='checkbox'
48
+ name={fieldName}
49
+ checked={value}
50
+ onChange={e => {
51
+ updateField(section, subsection, fieldName, !value)
52
+ }}
53
+ {...attributes}
54
+ />
55
+ <span className='edit-label'>
56
+ {label}
57
+ {tooltip}
58
+ </span>
59
+ </label>
60
+ ))
61
+
62
+ /* eslint-disable react-hooks/rules-of-hooks */
63
+ const TextField = memo(({ label, tooltip, section = null, subsection = null, fieldName, updateField, value: stateValue, type = 'input', i = null, min = null, ...attributes }) => {
64
+ const [value, setValue] = useState(stateValue)
65
+
66
+ const [debouncedValue] = useDebounce(value, 500)
67
+
68
+ useEffect(() => {
69
+ if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
70
+ updateField(section, subsection, fieldName, debouncedValue, i)
71
+ }
72
+ }, [debouncedValue]) // eslint-disable-line
73
+
74
+ let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
75
+
76
+ const onChange = e => {
77
+ if ('number' !== type || min === null) {
78
+ setValue(e.target.value)
79
+ } else {
80
+ if (!e.target.value || min <= parseFloat(e.target.value)) {
81
+ setValue(e.target.value)
82
+ } else {
83
+ setValue(min.toString())
84
+ }
85
+ }
86
+ }
87
+
88
+ let formElement = <input type='text' name={name} onChange={onChange} {...attributes} value={value} />
89
+
90
+ if ('textarea' === type) {
91
+ formElement = <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
92
+ }
93
+
94
+ if ('number' === type) {
95
+ formElement = <input type='number' name={name} onChange={onChange} {...attributes} value={value} />
96
+ }
97
+
98
+ if ('date' === type) {
99
+ formElement = <input type='date' name={name} onChange={onChange} {...attributes} value={value} />
100
+ }
101
+
102
+ return (
103
+ <label>
104
+ <span className='edit-label column-heading'>
105
+ {label}
106
+ {tooltip}
107
+ </span>
108
+ {formElement}
109
+ </label>
110
+ )
111
+ })
112
+
113
+ const ForestPlotSettings = () => {
114
+ const { config, rawData: unfilteredData, updateConfig, isDebug } = useContext(ConfigContext)
115
+
116
+ const enforceRestrictions = updatedConfig => {
117
+ if (updatedConfig.orientation === 'horizontal') {
118
+ updatedConfig.labels = false
119
+ }
120
+ if (updatedConfig.table.show === undefined) {
121
+ updatedConfig.table.show = !isDashboard
122
+ }
123
+ // DEV-3293 - Force combo to always be vertical
124
+ if (updatedConfig.visualizationType === 'Combo') {
125
+ updatedConfig.orientation = 'vertical'
126
+ }
127
+ }
128
+
129
+ const getColumns = (filter = true) => {
130
+ let columns = {}
131
+ unfilteredData.forEach(row => {
132
+ Object.keys(row).forEach(columnName => (columns[columnName] = true))
133
+ })
134
+
135
+ if (filter) {
136
+ Object.keys(columns).forEach(key => {
137
+ if (
138
+ (config.series && config.series.filter(series => series.dataKey === key).length > 0) ||
139
+ (config.confidenceKeys && Object.keys(config.confidenceKeys).includes(key))
140
+ /*
141
+ TODO: Resolve errors when config keys exist, but have no value
142
+ Proposal: (((confidenceUpper && confidenceLower) || confidenceUpper || confidenceLower) && Object.keys(config.confidenceKeys).includes(key))
143
+ */
144
+ ) {
145
+ delete columns[key]
146
+ }
147
+ })
148
+ }
149
+
150
+ return Object.keys(columns)
151
+ }
152
+
153
+ const updateField = (section, subsection, fieldName, newValue) => {
154
+ if (section === 'boxplot' && subsection === 'legend') {
155
+ updateConfig({
156
+ ...config,
157
+ [section]: {
158
+ ...config[section],
159
+ [subsection]: {
160
+ ...config.boxplot[subsection],
161
+ [fieldName]: newValue
162
+ }
163
+ }
164
+ })
165
+ return
166
+ }
167
+
168
+ if (section === 'boxplot' && subsection === 'labels') {
169
+ updateConfig({
170
+ ...config,
171
+ [section]: {
172
+ ...config[section],
173
+ [subsection]: {
174
+ ...config.boxplot[subsection],
175
+ [fieldName]: newValue
176
+ }
177
+ }
178
+ })
179
+ return
180
+ }
181
+
182
+ if (section === 'forestPlot' && subsection) {
183
+ updateConfig({
184
+ ...config,
185
+ [section]: {
186
+ ...config[section],
187
+ [subsection]: {
188
+ ...config.forestPlot[subsection],
189
+ [fieldName]: newValue
190
+ }
191
+ }
192
+ })
193
+ return
194
+ }
195
+
196
+ if (section === 'columns' && subsection !== '' && fieldName !== '') {
197
+ updateConfig({
198
+ ...config,
199
+ [section]: {
200
+ ...config[section],
201
+ [subsection]: {
202
+ ...config[section][subsection],
203
+ [fieldName]: newValue
204
+ }
205
+ }
206
+ })
207
+ return
208
+ }
209
+ if (null === section && null === subsection) {
210
+ let updatedConfig = { ...config, [fieldName]: newValue }
211
+ enforceRestrictions(updatedConfig)
212
+ updateConfig(updatedConfig)
213
+ return
214
+ }
215
+
216
+ const isArray = Array.isArray(config[section])
217
+
218
+ let sectionValue = isArray ? [...config[section], newValue] : { ...config[section], [fieldName]: newValue }
219
+
220
+ if (null !== subsection) {
221
+ if (isArray) {
222
+ sectionValue = [...config[section]]
223
+ sectionValue[subsection] = { ...sectionValue[subsection], [fieldName]: newValue }
224
+ } else if (typeof newValue === 'string') {
225
+ sectionValue[subsection] = newValue
226
+ } else {
227
+ sectionValue = { ...config[section], [subsection]: { ...config[section][subsection], [fieldName]: newValue } }
228
+ }
229
+ }
230
+
231
+ let updatedConfig = { ...config, [section]: sectionValue }
232
+
233
+ enforceRestrictions(updatedConfig)
234
+
235
+ updateConfig(updatedConfig)
236
+ }
237
+
238
+ return (
239
+ <AccordionItem>
240
+ <AccordionItemHeading>
241
+ <AccordionItemButton>
242
+ Forest Plot Settings
243
+ {(!config.forestPlot.estimateField || !config.forestPlot.upper || !config.forestPlot.lower) && <WarningImage width='25' className='warning-icon' />}
244
+ </AccordionItemButton>
245
+ </AccordionItemHeading>
246
+ <AccordionItemPanel>
247
+ <TextField type='text' value={config.forestPlot?.title || ''} updateField={updateField} section='forestPlot' fieldName='title' label='Plot Title' />
248
+
249
+ {/* width in center */}
250
+ {/* <label>
251
+ <span className='edit-label column-heading'>Forest Plot Width (%)</span>
252
+ <input
253
+ type='number'
254
+ min={0}
255
+ max={100}
256
+ value={config.forestPlot.width || ''}
257
+ onChange={e => {
258
+ updateConfig({
259
+ ...config,
260
+ forestPlot: {
261
+ ...config.forestPlot,
262
+ width: e.target.value
263
+ }
264
+ })
265
+ }}
266
+ />
267
+ </label> */}
268
+
269
+ <label>
270
+ <span className='edit-label column-heading'>Chart Offset Left (%)</span>
271
+ <input
272
+ type='number'
273
+ min={0}
274
+ max={100}
275
+ value={config.forestPlot.leftWidthOffset || 0}
276
+ onChange={e => {
277
+ updateConfig({
278
+ ...config,
279
+ forestPlot: {
280
+ ...config.forestPlot,
281
+ leftWidthOffset: e.target.value
282
+ }
283
+ })
284
+ }}
285
+ />
286
+ </label>
287
+
288
+ <label>
289
+ <span className='edit-label column-heading'>Chart Offset Left Mobile(%)</span>
290
+ <input
291
+ type='number'
292
+ min={0}
293
+ max={100}
294
+ value={config.forestPlot.leftWidthOffsetMobile || 0}
295
+ onChange={e => {
296
+ updateConfig({
297
+ ...config,
298
+ forestPlot: {
299
+ ...config.forestPlot,
300
+ leftWidthOffsetMobile: e.target.value
301
+ }
302
+ })
303
+ }}
304
+ />
305
+ </label>
306
+
307
+ <label>
308
+ <span className='edit-label column-heading'>Chart Offset Right (%)</span>
309
+ <input
310
+ type='number'
311
+ min={0}
312
+ max={100}
313
+ value={config.forestPlot.rightWidthOffset || 0}
314
+ onChange={e => {
315
+ updateConfig({
316
+ ...config,
317
+ forestPlot: {
318
+ ...config.forestPlot,
319
+ rightWidthOffset: e.target.value
320
+ }
321
+ })
322
+ }}
323
+ />
324
+ </label>
325
+
326
+ <label>
327
+ <span className='edit-label column-heading'>Chart Offset Right Mobile(%)</span>
328
+ <input
329
+ type='number'
330
+ min={0}
331
+ max={100}
332
+ value={config.forestPlot.rightWidthOffsetMobile || 0}
333
+ onChange={e => {
334
+ updateConfig({
335
+ ...config,
336
+ forestPlot: {
337
+ ...config.forestPlot,
338
+ rightWidthOffsetMobile: e.target.value
339
+ }
340
+ })
341
+ }}
342
+ />
343
+ </label>
344
+
345
+ <Select
346
+ value={config.forestPlot.estimateField}
347
+ label='Point Estimate Column'
348
+ initial={'Select'}
349
+ required={true}
350
+ onChange={e => {
351
+ if (e.target.value !== '' && e.target.value !== 'Select') {
352
+ updateConfig({
353
+ ...config,
354
+ forestPlot: {
355
+ ...config.forestPlot,
356
+ estimateField: e.target.value
357
+ }
358
+ })
359
+ }
360
+ e.target.value = ''
361
+ }}
362
+ options={getColumns(false)}
363
+ />
364
+
365
+ <Select
366
+ value={config.forestPlot.lower}
367
+ label='Lower CI Column'
368
+ required={true}
369
+ initial={'Select'}
370
+ onChange={e => {
371
+ if (e.target.value !== '' && e.target.value !== 'Select') {
372
+ updateConfig({
373
+ ...config,
374
+ forestPlot: {
375
+ ...config.forestPlot,
376
+ lower: e.target.value
377
+ }
378
+ })
379
+ }
380
+ e.target.value = ''
381
+ }}
382
+ options={getColumns(false)}
383
+ />
384
+ <Select
385
+ value={config.forestPlot.upper}
386
+ label='Upper CI Column'
387
+ initial={'Select'}
388
+ required={true}
389
+ onChange={e => {
390
+ if (e.target.value !== '' && e.target.value !== 'Select') {
391
+ updateConfig({
392
+ ...config,
393
+ forestPlot: {
394
+ ...config.forestPlot,
395
+ upper: e.target.value
396
+ }
397
+ })
398
+ }
399
+ e.target.value = ''
400
+ }}
401
+ options={getColumns(false)}
402
+ />
403
+
404
+ <CheckBox value={config.forestPlot.showZeroLine} section='forestPlot' fieldName='showZeroLine' label='Show Line on Zero' updateField={updateField} />
405
+ <Select
406
+ value={config.forestPlot.shape}
407
+ label='Point Estimate Shape'
408
+ initial={'Select'}
409
+ onChange={e => {
410
+ if (e.target.value !== '' && e.target.value !== 'Select') {
411
+ updateConfig({
412
+ ...config,
413
+ forestPlot: {
414
+ ...config.forestPlot,
415
+ shape: e.target.value
416
+ }
417
+ })
418
+ }
419
+ e.target.value = ''
420
+ }}
421
+ options={['text', 'circle', 'square', 'diamond']}
422
+ />
423
+ <Select
424
+ value={config.forestPlot.radius.scalingColumn}
425
+ label='Scale Radius Column'
426
+ initial={'Select'}
427
+ onChange={e => {
428
+ if (e.target.value !== '' && e.target.value !== 'Select') {
429
+ updateConfig({
430
+ ...config,
431
+ forestPlot: {
432
+ ...config.forestPlot,
433
+ radius: {
434
+ ...config.forestPlot.radius,
435
+ scalingColumn: e.target.value
436
+ }
437
+ }
438
+ })
439
+ }
440
+ e.target.value = ''
441
+ }}
442
+ options={getColumns(false)}
443
+ />
444
+ <label>
445
+ <span className='edit-label column-heading'>Radius Minimum Size</span>
446
+ <input
447
+ min={1}
448
+ max={5}
449
+ value={config.forestPlot.radius.min}
450
+ onChange={e => {
451
+ updateConfig({
452
+ ...config,
453
+ forestPlot: {
454
+ ...config.forestPlot,
455
+ radius: {
456
+ ...config.forestPlot.radius,
457
+ min: Number(e.target.value)
458
+ }
459
+ }
460
+ })
461
+ }}
462
+ type='number'
463
+ label='Radius Minimum'
464
+ placeholder=' 1'
465
+ />
466
+ </label>
467
+ <label>
468
+ <span className='edit-label column-heading'>Radius Maximum Size</span>
469
+ <input
470
+ min={5}
471
+ max={10}
472
+ value={config.forestPlot.radius.max}
473
+ onChange={e => {
474
+ updateConfig({
475
+ ...config,
476
+ forestPlot: {
477
+ ...config.forestPlot,
478
+ radius: {
479
+ ...config.forestPlot.radius,
480
+ max: Number(e.target.value)
481
+ }
482
+ }
483
+ })
484
+ }}
485
+ type='number'
486
+ label='Radius Minimum'
487
+ placeholder=' 1'
488
+ />
489
+ </label>
490
+ <TextField type='number' min={20} max={45} value={config.forestPlot.rowHeight ? config.forestPlot.rowHeight : 10} updateField={updateField} section='forestPlot' fieldName='rowHeight' label='Row Height' placeholder='10' />
491
+ <br />
492
+ <hr />
493
+ <br />
494
+ <h4>Add Regression Line</h4>
495
+ <TextField type='number' value={config.forestPlot?.regression?.upper || ''} updateField={updateField} section='forestPlot' subsection='regression' fieldName='upper' label='Upper Value' />
496
+ <TextField type='number' value={config.forestPlot?.regression?.lower || ''} updateField={updateField} section='forestPlot' subsection='regression' fieldName='lower' label='Lower Value' />
497
+ <TextField type='number' value={config.forestPlot?.regression?.estimateField || ''} updateField={updateField} section='forestPlot' subsection='regression' fieldName='estimateField' label='Estimate Value' />
498
+ <TextField type='text' value={config.forestPlot?.regression?.baseLineColor || 'black'} updateField={updateField} section='forestPlot' subsection='regression' fieldName='baseLineColor' label='Base Color' />
499
+ <CheckBox value={config.forestPlot?.regression?.showBaseLine || false} section='forestPlot' subsection='regression' fieldName='showBaseLine' label='Show base line' updateField={updateField} />
500
+ <CheckBox value={config.forestPlot?.regression?.showDiamond || false} section='forestPlot' subsection='regression' fieldName='showDiamond' label='Show Diamond' updateField={updateField} />
501
+ <CheckBox value={config.forestPlot?.hideDateCategoryCol || false} section='forestPlot' fieldName='hideDateCategoryCol' label='Hide Date Category Column' updateField={updateField} />
502
+ <TextField type='text' value={config.forestPlot?.regression?.description || ''} updateField={updateField} section='forestPlot' subsection='regression' fieldName='description' label='Description' />
503
+ </AccordionItemPanel>
504
+ </AccordionItem>
505
+ )
506
+ }
507
+
508
+ export default ForestPlotSettings
@@ -33,6 +33,8 @@ const Legend = () => {
33
33
 
34
34
  const { innerClasses, containerClasses } = useLegendClasses(config)
35
35
  const { visualizationType, visualizationSubType, series, runtime, orientation } = config
36
+ // create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
37
+ const reverseLabels = labels => (config.legend.reverseLabelOrder && config.legend.position === 'bottom' ? labels.reverse() : labels)
36
38
 
37
39
  const createLegendLabels = defaultLabels => {
38
40
  const colorCode = config.legend?.colorCode
@@ -51,7 +53,7 @@ const Legend = () => {
51
53
  value: aboveColor
52
54
  }
53
55
 
54
- return [labelBelow, labelAbove]
56
+ return reverseLabels([labelBelow, labelAbove])
55
57
  }
56
58
  if (visualizationType === 'Bar' && visualizationSubType === 'regular' && colorCode && series?.length === 1) {
57
59
  let palette = colorPalettes[config.palette]
@@ -76,7 +78,7 @@ const Legend = () => {
76
78
  return newLabel
77
79
  })
78
80
 
79
- return uniqueLabels
81
+ return reverseLabels(uniqueLabels)
80
82
  }
81
83
 
82
84
  // get forecasting items inside of combo
@@ -103,7 +105,7 @@ const Legend = () => {
103
105
 
104
106
  // loop through bars for now to meet requirements.
105
107
  config.runtime.barSeriesKeys &&
106
- config.runtime.barSeriesKeys.map((bar, index) => {
108
+ config.runtime.barSeriesKeys.forEach((bar, index) => {
107
109
  let colorValue = colorPalettes[config.palette][index] ? colorPalettes[config.palette][index] : '#ccc'
108
110
 
109
111
  const newLabel = {
@@ -116,7 +118,7 @@ const Legend = () => {
116
118
  seriesLabels.push(newLabel)
117
119
  })
118
120
 
119
- return seriesLabels
121
+ return reverseLabels(seriesLabels)
120
122
  }
121
123
 
122
124
  // DEV-4161: replaceable series name in the legend
@@ -147,17 +149,17 @@ const Legend = () => {
147
149
  return newLabel
148
150
  })
149
151
 
150
- return uniqueLabels
152
+ return reverseLabels(uniqueLabels)
151
153
  }
152
154
 
153
- return defaultLabels
155
+ return reverseLabels(defaultLabels)
154
156
  }
155
157
 
156
- const isBottomOrSmallViewport = legend.position === 'bottom' || currentViewport === 'sm' || currentViewport === 'xs' || currentViewport === 'xxs'
158
+ const isBottomOrSmallViewport = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
157
159
 
158
160
  const legendClasses = {
159
161
  marginBottom: isBottomOrSmallViewport ? '15px' : '0px',
160
- marginTop: isBottomOrSmallViewport && orientation === 'horizontal' ? `${config.runtime.xAxis.size}px` : '0px'
162
+ marginTop: isBottomOrSmallViewport && orientation === 'horizontal' ? `${config.yAxis.label && config.isResponsiveTicks ? config.dynamicMarginTop : config.runtime.xAxis.size}px` : `0px`
161
163
  }
162
164
 
163
165
  const { HighLightedBarUtils } = useHighlightedBars(config)
@@ -2,22 +2,23 @@ import React, { useContext } from 'react'
2
2
 
3
3
  import * as allCurves from '@visx/curve'
4
4
  import { Group } from '@visx/group'
5
- import { LinePath } from '@visx/shape'
5
+ import { LinePath, Bar } from '@visx/shape'
6
6
  import { Text } from '@visx/text'
7
7
 
8
8
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
9
9
  import ConfigContext from '../ConfigContext'
10
10
  import useRightAxis from '../hooks/useRightAxis'
11
11
 
12
- export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, seriesStyle = 'Line' }) {
13
- const { colorPalettes, transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, isNumber, updateConfig, handleLineType } = useContext(ConfigContext)
12
+ const LineChart = ({ xScale, yScale, getXAxisData, getYAxisData, xMax, yMax, handleTooltipMouseOver, handleTooltipMouseOff, showTooltip, seriesStyle = 'Line', svgRef, handleTooltipClick, tooltipData }) => {
13
+ // Not sure why there's a redraw here.
14
14
 
15
+ const { colorPalettes, transformedData: data, colorScale, seriesHighlight, config, formatNumber, formatDate, parseDate, isNumber, updateConfig, handleLineType, dashboardConfig, tableData } = useContext(ConfigContext)
15
16
  const { yScaleRight } = useRightAxis({ config, yMax, data, updateConfig })
16
17
 
18
+ if (!handleTooltipMouseOver) return
17
19
  const handleAxisFormating = (axis = 'left', label, value) => {
18
20
  // if this is an x axis category/date value return without doing any formatting.
19
21
  // if (label === config.runtime.xAxis.label) return value
20
-
21
22
  axis = String(axis).toLocaleLowerCase()
22
23
  if (label) {
23
24
  return `${label}: ${formatNumber(value, axis)}`
@@ -25,6 +26,7 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
25
26
  return `${formatNumber(value, axis)}`
26
27
  }
27
28
 
29
+ const DEBUG = false
28
30
  return (
29
31
  <ErrorBoundary component='LineChart'>
30
32
  <Group left={config.runtime.yAxis.size ? parseInt(config.runtime.yAxis.size) : 66}>
@@ -35,6 +37,8 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
35
37
  const seriesData = config.series.filter(item => item.dataKey === seriesKey)
36
38
  const seriesAxis = seriesData[0].axis ? seriesData[0].axis : 'left'
37
39
 
40
+ let displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
41
+
38
42
  return (
39
43
  <Group
40
44
  key={`series-${seriesKey}`}
@@ -71,7 +75,10 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
71
75
  d[seriesKey] !== '' &&
72
76
  d[seriesKey] !== null &&
73
77
  isNumber(d[seriesKey]) && (
74
- <Group key={`series-${seriesKey}-point-${dataIndex}`}>
78
+ <Group key={`series-${seriesKey}-point-${dataIndex}`} className='checkwidth'>
79
+ {/* tooltips */}
80
+ <Bar key={'bars'} width={Number(xMax)} height={Number(yMax)} fill={DEBUG ? 'red' : 'transparent'} fillOpacity={0.05} onMouseMove={e => handleTooltipMouseOver(e, tableData)} onMouseOut={handleTooltipMouseOff} onClick={e => handleTooltipClick(e, data)} />
81
+
75
82
  {/* Render legend */}
76
83
  <Text display={config.labels ? 'block' : 'none'} x={xScale(getXAxisData(d))} y={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey))} fill={'#000'} textAnchor='middle'>
77
84
  {formatNumber(d[seriesKey], 'left')}
@@ -83,10 +90,26 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
83
90
  cx={Number(xScale(getXAxisData(d)))}
84
91
  cy={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey))}
85
92
  fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
86
- style={{ fill: colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000' }}
93
+ style={{
94
+ fill: colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'
95
+ }}
87
96
  data-tooltip-html={tooltip}
88
97
  data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
89
98
  />
99
+
100
+ {/* circles that appear on hover */}
101
+ {/* todo: circle radii used here should be global with other circle radii */}
102
+ {/* {tooltipData && Object.entries(tooltipData.data).length > 0 && isNumber(tooltipData.data[seriesKey]) && config.lineDatapointStyle === 'hover' && config.series.filter(s => s.type === 'Line') && (
103
+ <circle
104
+ cx={config.xAxis.type === 'categorical' ? xScale(tooltipData.data[config.xAxis.dataKey]) : xScale(parseDate(tooltipData.data[config.xAxis.dataKey]))}
105
+ cy={yScale(tooltipData.data[seriesKey])}
106
+ r={4.5}
107
+ opacity={tooltipData[seriesKey] ? 1 : 0}
108
+ fillOpacity={1}
109
+ fill={displayArea ? (colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000') : 'transparent'}
110
+ style={{ filter: 'unset', opacity: 1 }}
111
+ />
112
+ )} */}
90
113
  </Group>
91
114
  )
92
115
  )
@@ -161,3 +184,5 @@ export default function LineChart({ xScale, yScale, getXAxisData, getYAxisData,
161
184
  </ErrorBoundary>
162
185
  )
163
186
  }
187
+
188
+ export default LineChart