@cdc/chart 4.23.11 → 4.24.1

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 (103) hide show
  1. package/dist/cdcchart.js +30220 -29764
  2. package/examples/feature/bar/additional-column-tooltip.json +446 -0
  3. package/examples/feature/bar/tall-data.json +98 -0
  4. package/examples/feature/forest-plot/forest-plot.json +63 -19
  5. package/examples/feature/forest-plot/linear.json +52 -3
  6. package/examples/feature/forest-plot/log.json +26 -0
  7. package/examples/feature/forest-plot/logarithmic.json +0 -35
  8. package/examples/feature/line/line-chart-preliminary.json +346 -0
  9. package/examples/feature/scatterplot/scatterplot.json +272 -33
  10. package/examples/private/chart-t.json +3740 -0
  11. package/examples/private/combo.json +369 -0
  12. package/examples/private/epi-data.csv +13 -0
  13. package/examples/private/epi-data.json +62 -0
  14. package/examples/private/epi.json +403 -0
  15. package/examples/private/occupancy.json +109283 -0
  16. package/examples/private/prod-line-config.json +401 -0
  17. package/examples/private/region-data.json +822 -0
  18. package/examples/private/region-testing.json +312 -0
  19. package/examples/private/scaling.json +45325 -0
  20. package/examples/private/testing-data.json +1739 -0
  21. package/examples/private/testing.json +816 -0
  22. package/index.html +7 -7
  23. package/package.json +2 -2
  24. package/src/CdcChart.tsx +29 -210
  25. package/src/ConfigContext.tsx +6 -0
  26. package/src/_stories/ChartEditor.stories.tsx +22 -0
  27. package/src/_stories/ChartLine.preliminary.tsx +19 -0
  28. package/src/_stories/_mock/pie_config.json +191 -0
  29. package/src/_stories/_mock/pie_data.json +218 -0
  30. package/src/_stories/_mock/preliminary_mock.json +346 -0
  31. package/src/components/{AreaChart.Stacked.jsx → AreaChart/components/AreaChart.Stacked.jsx} +2 -2
  32. package/src/components/{AreaChart.jsx → AreaChart/components/AreaChart.jsx} +1 -1
  33. package/src/components/AreaChart/index.tsx +4 -0
  34. package/src/components/{BarChart.Horizontal.tsx → BarChart/components/BarChart.Horizontal.tsx} +8 -8
  35. package/src/components/{BarChart.StackedHorizontal.tsx → BarChart/components/BarChart.StackedHorizontal.tsx} +37 -7
  36. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +106 -0
  37. package/src/components/{BarChart.Vertical.tsx → BarChart/components/BarChart.Vertical.tsx} +41 -57
  38. package/src/components/BarChart/components/BarChart.jsx +39 -0
  39. package/src/components/{BarChartType.jsx → BarChart/components/BarChartType.jsx} +0 -2
  40. package/src/components/BarChart/components/context.tsx +13 -0
  41. package/src/components/BarChart/index.tsx +3 -0
  42. package/src/components/{BoxPlot.jsx → BoxPlot/BoxPlot.jsx} +1 -1
  43. package/src/components/BoxPlot/index.tsx +3 -0
  44. package/src/components/{EditorPanel.jsx → EditorPanel/EditorPanel.tsx} +667 -851
  45. package/src/components/EditorPanel/components/Panel.DateHighlighting.tsx +109 -0
  46. package/src/components/{ForestPlotSettings.jsx → EditorPanel/components/Panel.ForestPlotSettings.tsx} +87 -166
  47. package/src/components/EditorPanel/components/Panel.Regions.tsx +168 -0
  48. package/src/components/{Series.jsx → EditorPanel/components/Panel.Series.tsx} +1 -1
  49. package/src/components/EditorPanel/components/PanelProps.ts +3 -0
  50. package/src/components/EditorPanel/components/Panels.tsx +13 -0
  51. package/src/components/EditorPanel/components/panels.scss +72 -0
  52. package/src/components/EditorPanel/editor-panel.scss +751 -0
  53. package/src/components/EditorPanel/index.tsx +3 -0
  54. package/src/{hooks → components/EditorPanel}/useEditorPermissions.js +29 -2
  55. package/src/components/{Forecasting.jsx → Forecasting/Forecasting.jsx} +1 -1
  56. package/src/components/Forecasting/index.tsx +3 -0
  57. package/src/components/ForestPlot/ForestPlot.tsx +254 -0
  58. package/src/components/ForestPlot/ForestPlotProps.ts +7 -0
  59. package/src/components/ForestPlot/index.tsx +1 -209
  60. package/src/components/{Legend.jsx → Legend/Legend.tsx} +150 -113
  61. package/src/components/Legend/index.tsx +3 -0
  62. package/src/components/LineChart/LineChartProps.ts +29 -0
  63. package/src/components/LineChart/{LineChart.Circle.tsx → components/LineChart.Circle.tsx} +12 -3
  64. package/src/components/LineChart/helpers.ts +45 -0
  65. package/src/components/LineChart/index.tsx +20 -8
  66. package/src/components/LinearChart.jsx +52 -69
  67. package/src/components/{PieChart.jsx → PieChart/PieChart.tsx} +16 -7
  68. package/src/components/PieChart/index.tsx +3 -0
  69. package/src/components/Regions/components/Regions.tsx +135 -0
  70. package/src/components/Regions/index.tsx +3 -0
  71. package/src/components/{ScatterPlot.jsx → ScatterPlot/ScatterPlot.jsx} +3 -3
  72. package/src/components/ScatterPlot/index.tsx +3 -0
  73. package/src/components/{SparkLine.jsx → Sparkline/SparkLine.jsx} +2 -2
  74. package/src/components/Sparkline/index.tsx +3 -0
  75. package/src/data/initial-state.js +5 -6
  76. package/src/helpers/abbreviateNumber.ts +17 -0
  77. package/src/helpers/computeMarginBottom.ts +55 -0
  78. package/src/helpers/filterData.ts +18 -0
  79. package/src/helpers/generateColorsArray.ts +8 -0
  80. package/src/helpers/getQuartiles.ts +30 -0
  81. package/src/helpers/handleChartAriaLabels.ts +19 -0
  82. package/src/helpers/handleLineType.ts +18 -0
  83. package/src/helpers/lineOptions.ts +18 -0
  84. package/src/helpers/sort.ts +7 -0
  85. package/src/helpers/tests/computeMarginBottom.test.ts +20 -0
  86. package/src/hooks/useBarChart.js +7 -6
  87. package/src/hooks/useScales.ts +1 -1
  88. package/src/hooks/{useTooltip.jsx → useTooltip.tsx} +23 -21
  89. package/src/scss/main.scss +67 -3
  90. package/src/types/ChartConfig.ts +158 -23
  91. package/src/types/ChartContext.ts +26 -10
  92. package/src/types/ForestPlot.ts +7 -14
  93. package/examples/feature/scatterplot/scatterplot-continuous.csv +0 -17
  94. package/src/ConfigContext.jsx +0 -5
  95. package/src/components/BarChart.StackedVertical.tsx +0 -91
  96. package/src/components/BarChart.jsx +0 -30
  97. package/src/components/ForestPlot/Readme.md +0 -0
  98. package/src/scss/LinearChart.scss +0 -0
  99. package/src/scss/editor-panel.scss +0 -745
  100. package/src/scss/legend.scss +0 -206
  101. package/src/scss/mixins.scss +0 -0
  102. package/src/scss/variables.scss +0 -1
  103. package/src/types/ChartProps.ts +0 -7
@@ -0,0 +1,109 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import { AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
3
+ import { type PanelProps } from './PanelProps'
4
+
5
+ const days = [
6
+ { day: 'Sunday', abbr: 'S', status: 'inactive' },
7
+ { day: 'Monday', abbr: 'M', status: 'inactive' },
8
+ { day: 'Tuesday', abbr: 'T', status: 'inactive' },
9
+ { day: 'Wednesday', abbr: 'W', status: 'inactive' },
10
+ { day: 'Thursday', abbr: 'T', status: 'inactive' },
11
+ { day: 'Friday', abbr: 'F', status: 'inactive' },
12
+ { day: 'Saturday', abbr: 'S', status: 'inactive' }
13
+ ]
14
+
15
+ const DateHighlighting = ({ name }: PanelProps) => {
16
+ const [interval, setInterval] = useState(0)
17
+ const [isPlural, setIsPlural] = useState(false)
18
+ const [recurrance, setRecurrance] = useState('Days')
19
+ const [firstRecurranceUpdate, setFirstRecurranceUpdate] = useState(false)
20
+ const [daySelections, setDaySelections] = useState(days)
21
+
22
+ const handleDaySelections = e => {
23
+ const incomingDay = e.target.value
24
+ const prev = [...daySelections]
25
+
26
+ const updatedDays = prev.map(day => {
27
+ if (day.day === incomingDay) {
28
+ return { ...day, status: day.status === 'active' ? 'inactive' : 'active' }
29
+ }
30
+ return day
31
+ })
32
+
33
+ setDaySelections(updatedDays)
34
+ }
35
+
36
+ useEffect(() => {
37
+ if (interval > 7 && !firstRecurranceUpdate) {
38
+ setRecurrance('Weeks')
39
+ }
40
+ }, [interval])
41
+
42
+ useEffect(() => {
43
+ if (interval > 1) {
44
+ setIsPlural(true)
45
+ } else {
46
+ setIsPlural(false)
47
+ }
48
+ }, [interval])
49
+
50
+ return (
51
+ <AccordionItem>
52
+ <AccordionItemHeading>
53
+ <AccordionItemButton>{name}</AccordionItemButton>
54
+ </AccordionItemHeading>
55
+ <AccordionItemPanel>
56
+ <ul className='date-highlight'>
57
+ <div className='date-highlight__occurance'>
58
+ <label htmlFor=''>Repeat Every</label>
59
+ <input type='number' value={interval} onChange={e => setInterval(Number(e.target.value))} />
60
+ <select value={recurrance} onChange={e => setRecurrance(e.target.value)}>
61
+ <option value='Day'>{isPlural ? 'Days' : 'Day'}</option>
62
+ <option value='Week'>{isPlural ? 'Weeks' : 'Week'}</option>
63
+ <option value='Month'>{isPlural ? 'Months' : 'Month'}</option>
64
+ <option value='Year'>{isPlural ? 'Years' : 'Year'}</option>
65
+ </select>
66
+ </div>
67
+ {recurrance !== 'Days' && (
68
+ <div className='date-highlight__day-selection'>
69
+ <label htmlFor='' style={{ display: 'block', width: '100%' }}>
70
+ Repeat On
71
+ </label>
72
+ {daySelections.map(d => (
73
+ <button className={`week-button week-button--${d.status} ${d.day}`} value={d.day} onClick={handleDaySelections}>
74
+ {d.abbr}
75
+ </button>
76
+ ))}
77
+ </div>
78
+ )}
79
+ <div className='date-highlight__end-date'>
80
+ <label htmlFor=''>Ends</label>
81
+ <div className='radio-group'>
82
+ <div className='group'>
83
+ <input type='radio' name='ending' value='Never' id='Never' />
84
+ <label for='Never'>Never</label>
85
+ </div>
86
+ </div>
87
+ <div className='radio-group'>
88
+ <div className='group'>
89
+ <input type='radio' name='ending' value='After' id='After' />
90
+ <label for='After'>After</label>
91
+ </div>
92
+ <input type='number' className='date-highlight__end-date--on' />
93
+ <p>occurances</p>
94
+ </div>
95
+ <div className='radio-group'>
96
+ <div className='group'>
97
+ <input type='radio' name='ending' value='On' id='On' />
98
+ <label for='On'>On</label>
99
+ </div>
100
+ <input type='date' className='date-highlight__end-date--after' />
101
+ </div>
102
+ </div>
103
+ </ul>
104
+ </AccordionItemPanel>
105
+ </AccordionItem>
106
+ )
107
+ }
108
+
109
+ export default DateHighlighting
@@ -1,119 +1,17 @@
1
1
  import React, { useContext, memo, useState, useEffect } from 'react'
2
- import ConfigContext from '../ConfigContext'
2
+ import ConfigContext from '../../../ConfigContext'
3
3
  import { useDebounce } from 'use-debounce'
4
- import WarningImage from '../images/warning.svg'
4
+ import WarningImage from '../../../images/warning.svg'
5
5
  import Tooltip from '@cdc/core/components/ui/Tooltip'
6
6
  import Icon from '@cdc/core/components/ui/Icon'
7
+ import { type ChartContext } from '../../../types/ChartContext'
8
+ import { Select, CheckBox, TextField } from '@cdc/core/components/EditorPanel/Inputs'
9
+ import { type PanelProps } from './PanelProps'
7
10
 
8
11
  import { AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
9
12
 
10
- const Select = memo(({ label, value, options, fieldName, section = null, subsection = null, required = false, tooltip, updateField, initial: initialValue, ...attributes }) => {
11
- let optionsJsx = options.map((optionName, index) => (
12
- <option value={optionName} key={index}>
13
- {optionName}
14
- </option>
15
- ))
16
-
17
- if (initialValue) {
18
- optionsJsx.unshift(
19
- <option value='' key='initial'>
20
- {initialValue}
21
- </option>
22
- )
23
- }
24
-
25
- return (
26
- <label>
27
- <span className='edit-label'>
28
- {label}
29
- {tooltip}
30
- </span>
31
- <select
32
- className={required && !value ? 'warning' : ''}
33
- name={fieldName}
34
- value={value}
35
- onChange={event => {
36
- updateField(section, subsection, fieldName, event.target.value)
37
- }}
38
- {...attributes}
39
- >
40
- {optionsJsx}
41
- </select>
42
- </label>
43
- )
44
- })
45
-
46
- const CheckBox = memo(({ label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes }) => (
47
- <label className='checkbox column-heading'>
48
- <input
49
- type='checkbox'
50
- name={fieldName}
51
- checked={value}
52
- onChange={e => {
53
- updateField(section, subsection, fieldName, !value)
54
- }}
55
- {...attributes}
56
- />
57
- <span className='edit-label'>
58
- {label}
59
- {tooltip}
60
- </span>
61
- </label>
62
- ))
63
-
64
- /* eslint-disable react-hooks/rules-of-hooks */
65
- const TextField = memo(({ label, tooltip, section = null, subsection = null, fieldName, updateField, value: stateValue, type = 'input', i = null, min = null, ...attributes }) => {
66
- const [value, setValue] = useState(stateValue)
67
-
68
- const [debouncedValue] = useDebounce(value, 500)
69
-
70
- useEffect(() => {
71
- if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
72
- updateField(section, subsection, fieldName, debouncedValue, i)
73
- }
74
- }, [debouncedValue]) // eslint-disable-line
75
-
76
- let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
77
-
78
- const onChange = e => {
79
- if ('number' !== type || min === null) {
80
- setValue(e.target.value)
81
- } else {
82
- if (!e.target.value || min <= parseFloat(e.target.value)) {
83
- setValue(e.target.value)
84
- } else {
85
- setValue(min.toString())
86
- }
87
- }
88
- }
89
-
90
- let formElement = <input type='text' name={name} onChange={onChange} {...attributes} value={value} />
91
-
92
- if ('textarea' === type) {
93
- formElement = <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
94
- }
95
-
96
- if ('number' === type) {
97
- formElement = <input type='number' name={name} onChange={onChange} {...attributes} value={value} />
98
- }
99
-
100
- if ('date' === type) {
101
- formElement = <input type='date' name={name} onChange={onChange} {...attributes} value={value} />
102
- }
103
-
104
- return (
105
- <label>
106
- <span className='edit-label column-heading'>
107
- {label}
108
- {tooltip}
109
- </span>
110
- {formElement}
111
- </label>
112
- )
113
- })
114
-
115
- const ForestPlotSettings = () => {
116
- const { config, rawData: unfilteredData, updateConfig, isDebug } = useContext(ConfigContext)
13
+ const ForestPlotSettings = ({ name }: PanelProps) => {
14
+ const { config, rawData: unfilteredData, updateConfig } = useContext<ChartContext>(ConfigContext)
117
15
 
118
16
  const enforceRestrictions = updatedConfig => {
119
17
  if (updatedConfig.orientation === 'horizontal') {
@@ -243,11 +141,31 @@ const ForestPlotSettings = () => {
243
141
  <AccordionItem>
244
142
  <AccordionItemHeading>
245
143
  <AccordionItemButton>
246
- Forest Plot Settings
144
+ {name}
247
145
  {(!config.forestPlot.estimateField || !config.forestPlot.upper || !config.forestPlot.lower) && <WarningImage width='25' className='warning-icon' />}
248
146
  </AccordionItemButton>
249
147
  </AccordionItemHeading>
250
148
  <AccordionItemPanel>
149
+ <Select
150
+ value={config.xAxis.dataKey || ''}
151
+ section='xAxis'
152
+ fieldName='dataKey'
153
+ label='Study Column'
154
+ initial='Select'
155
+ required={true}
156
+ updateField={updateField}
157
+ options={getColumns(false)}
158
+ tooltip={
159
+ <Tooltip style={{ textTransform: 'none' }}>
160
+ <Tooltip.Target>
161
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
162
+ </Tooltip.Target>
163
+ <Tooltip.Content>
164
+ <p>Select the column or row containing the categories or dates for this axis. </p>
165
+ </Tooltip.Content>
166
+ </Tooltip>
167
+ }
168
+ />
251
169
  <Select
252
170
  value={config.forestPlot.type}
253
171
  label='Forest Plot Type'
@@ -311,7 +229,6 @@ const ForestPlotSettings = () => {
311
229
  <Select
312
230
  value={config.forestPlot.shape}
313
231
  label='Point Estimate Shape'
314
- initial={config.forestPlot.shape || 'Select'}
315
232
  onChange={e => {
316
233
  if (e.target.value !== '' && e.target.value !== 'Select') {
317
234
  updateConfig({
@@ -327,28 +244,6 @@ const ForestPlotSettings = () => {
327
244
  options={['text', 'circle', 'square']}
328
245
  />
329
246
 
330
- <Select
331
- value={config.forestPlot.radius.scalingColumn}
332
- label='Scale Radius Column'
333
- initial={'Select'}
334
- onChange={e => {
335
- if (e.target.value !== '' && e.target.value !== 'Select') {
336
- updateConfig({
337
- ...config,
338
- forestPlot: {
339
- ...config.forestPlot,
340
- radius: {
341
- ...config.forestPlot.radius,
342
- scalingColumn: e.target.value
343
- }
344
- }
345
- })
346
- }
347
- e.target.value = ''
348
- }}
349
- options={getColumns(false)}
350
- />
351
-
352
247
  <Select
353
248
  value={config.forestPlot.lower}
354
249
  label='Lower CI Column'
@@ -389,30 +284,30 @@ const ForestPlotSettings = () => {
389
284
  options={getColumns(false)}
390
285
  />
391
286
 
392
- <Select
393
- value={config.forestPlot.pooledResult.column}
394
- label='Pooled Result Row'
395
- initial={config.forestPlot.pooledResult.column || 'Select'}
396
- required={false}
397
- onChange={e => {
398
- if (e.target.value !== '' && e.target.value !== 'Select') {
399
- updateConfig({
400
- ...config,
401
- forestPlot: {
402
- ...config.forestPlot,
403
- pooledResult: {
404
- ...config.forestPlot.pooledResult,
405
- column: e.target.value
287
+ <label>
288
+ <span className='edit-label column-heading'>
289
+ Pooled Result Column
290
+ <input
291
+ type='text'
292
+ value={config.forestPlot.pooledResult.column || ''}
293
+ label='Pooled Result Row'
294
+ onChange={e => {
295
+ updateConfig({
296
+ ...config,
297
+ forestPlot: {
298
+ ...config.forestPlot,
299
+ pooledResult: {
300
+ ...config.forestPlot.pooledResult,
301
+ column: e.target.value
302
+ }
406
303
  }
407
- }
408
- })
409
- }
410
- e.target.value = ''
411
- }}
412
- options={['None', ...config.data.map(d => d[config.xAxis.dataKey])]}
413
- />
304
+ })
305
+ e.target.value = ''
306
+ }}
307
+ />
308
+ </span>
309
+ </label>
414
310
 
415
- <CheckBox value={config.forestPlot?.hideDateCategoryCol || false} section='forestPlot' fieldName='hideDateCategoryCol' label='Hide Date Category Column' updateField={updateField} />
416
311
  <CheckBox value={config.forestPlot?.lineOfNoEffect?.show || false} section='forestPlot' subsection='lineOfNoEffect' fieldName='show' label='Show Line of No Effect' updateField={updateField} />
417
312
 
418
313
  <br />
@@ -496,12 +391,45 @@ const ForestPlotSettings = () => {
496
391
  />
497
392
  </label>
498
393
 
394
+ <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' />
395
+ <br />
396
+ <hr />
397
+ <br />
398
+ <h4>Labels Settings</h4>
399
+ <TextField type='text' value={config.forestPlot?.leftLabel || ''} updateField={updateField} section='forestPlot' fieldName='leftLabel' label='Left Label' />
400
+ <TextField type='text' value={config.forestPlot?.rightLabel || ''} updateField={updateField} section='forestPlot' fieldName='rightLabel' label='Right Label' />
401
+
402
+ <br />
403
+ <hr />
404
+ <br />
405
+ <Select
406
+ value={config.forestPlot.radius.scalingColumn}
407
+ label='Weight Column'
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
+ radius: {
416
+ ...config.forestPlot.radius,
417
+ scalingColumn: e.target.value
418
+ }
419
+ }
420
+ })
421
+ }
422
+ e.target.value = ''
423
+ }}
424
+ options={getColumns(false)}
425
+ />
426
+
499
427
  <label>
500
428
  <span className='edit-label column-heading'>Radius Minimum Size</span>
501
429
  <input
502
- min={1}
503
- max={5}
504
- value={config.forestPlot.radius.min}
430
+ min={3}
431
+ max={6}
432
+ value={config.forestPlot.radius.min || 3}
505
433
  onChange={e => {
506
434
  updateConfig({
507
435
  ...config,
@@ -522,7 +450,7 @@ const ForestPlotSettings = () => {
522
450
  <label>
523
451
  <span className='edit-label column-heading'>Radius Maximum Size</span>
524
452
  <input
525
- min={5}
453
+ min={7}
526
454
  max={10}
527
455
  value={config.forestPlot.radius.max}
528
456
  onChange={e => {
@@ -542,13 +470,6 @@ const ForestPlotSettings = () => {
542
470
  placeholder=' 1'
543
471
  />
544
472
  </label>
545
- <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' />
546
- <br />
547
- <hr />
548
- <br />
549
- <h4>Labels Settings</h4>
550
- <TextField type='text' value={config.forestPlot?.leftLabel || ''} updateField={updateField} section='forestPlot' fieldName='leftLabel' label='Left Label' />
551
- <TextField type='text' value={config.forestPlot?.rightLabel || ''} updateField={updateField} section='forestPlot' fieldName='rightLabel' label='Right Label' />
552
473
  </AccordionItemPanel>
553
474
  </AccordionItem>
554
475
  )
@@ -0,0 +1,168 @@
1
+ import { memo, useContext } from 'react'
2
+ import { useEditorPermissions } from './../useEditorPermissions.js'
3
+ import { AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
4
+ import { type ChartConfig } from './../../../types/ChartConfig.js'
5
+ import { TextField, Select } from '@cdc/core/components/EditorPanel/Inputs'
6
+ import Tooltip from '@cdc/core/components/ui/Tooltip'
7
+ import Icon from '@cdc/core/components/ui/Icon'
8
+ import { type ChartContext } from '../../../types/ChartContext'
9
+ import { type PanelProps } from './PanelProps'
10
+ import ConfigContext from '../../../ConfigContext'
11
+
12
+ const RegionSettings = memo(({ config, updateConfig }: { config: ChartConfig; updateConfig: Function }) => {
13
+ let regionUpdate = (fieldName, value, i) => {
14
+ let regions = []
15
+
16
+ if (config.regions) {
17
+ regions = [...config.regions]
18
+ }
19
+
20
+ regions[i][fieldName] = value
21
+ updateConfig({ ...config, regions })
22
+ }
23
+
24
+ // only for Regions
25
+ let updateField = (section, subsection, fieldName, value, i) => regionUpdate(fieldName, value, i)
26
+
27
+ let removeColumn = i => {
28
+ let regions = []
29
+
30
+ if (config.regions) {
31
+ regions = [...config.regions]
32
+ }
33
+
34
+ regions.splice(i, 1)
35
+
36
+ updateConfig({ ...config, regions })
37
+ }
38
+
39
+ let addColumn = () => {
40
+ let regions = []
41
+
42
+ if (config.regions) {
43
+ regions = [...config.regions]
44
+ }
45
+
46
+ regions.push({})
47
+
48
+ updateConfig({ ...config, regions })
49
+ }
50
+
51
+ const fromOptions = ['Fixed', 'Previous Days']
52
+ const toOptions = ['Last Date', 'Fixed']
53
+
54
+ return (
55
+ <>
56
+ {config.regions &&
57
+ config.regions.map(({ label, color, from, to, background, range = 'Custom' }, i) => (
58
+ <div className='edit-block' key={`region-${i}`}>
59
+ <button
60
+ type='button'
61
+ className='remove-column'
62
+ onClick={event => {
63
+ event.preventDefault()
64
+ removeColumn(i)
65
+ }}
66
+ >
67
+ Remove
68
+ </button>
69
+ <TextField value={label} label='Region Label' fieldName='label' i={i} updateField={updateField} />
70
+ <div className='two-col-inputs'>
71
+ <TextField value={color} label='Text Color' fieldName='color' updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)} />
72
+ <TextField value={background} label='Background' fieldName='background' updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)} />
73
+ </div>
74
+
75
+ <Select
76
+ value={config.regions[i].fromType ?? 'Fixed'}
77
+ label='Minimum Region Type'
78
+ initial={'Select'}
79
+ required={true}
80
+ onChange={e => {
81
+ if (e.target.value !== '' && e.target.value !== 'Select') {
82
+ const newRegions = [...config.regions]
83
+ newRegions[i].fromType = e.target.value
84
+ updateConfig({
85
+ ...config,
86
+ regions: newRegions
87
+ })
88
+ }
89
+ e.target.value = ''
90
+ }}
91
+ options={fromOptions}
92
+ />
93
+
94
+ {(config.regions[i].fromType === 'Fixed' || config.regions[i].fromType === 'Previous Days' || !config.regions[i].fromType) && (
95
+ <>
96
+ <TextField
97
+ value={from}
98
+ label={config.regions[i].fromType === 'Fixed' || !config.regions[i]?.fromType ? 'From Value' : 'Previous Number of Days'}
99
+ fieldName='from'
100
+ updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}
101
+ tooltip={
102
+ <Tooltip style={{ textTransform: 'none' }}>
103
+ <Tooltip.Target>
104
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
105
+ </Tooltip.Target>
106
+ <Tooltip.Content>
107
+ <p>The date needs to be in the original format of the data. Not the displayed format of the data.</p>
108
+ </Tooltip.Content>
109
+ </Tooltip>
110
+ }
111
+ />
112
+ </>
113
+ )}
114
+
115
+ <Select
116
+ value={config.regions[i].toType ?? 'Fixed'}
117
+ label='Maximum Region Type'
118
+ initial={'Select'}
119
+ required={true}
120
+ onChange={e => {
121
+ if (e.target.value !== '' && e.target.value !== 'Select') {
122
+ const newRegions = [...config.regions]
123
+ newRegions[i].toType = e.target.value
124
+ updateConfig({
125
+ ...config,
126
+ regions: newRegions
127
+ })
128
+ }
129
+ e.target.value = ''
130
+ }}
131
+ options={toOptions}
132
+ />
133
+
134
+ {(config.regions[i].toType === 'Fixed' || !config.regions[i].toType) && <TextField value={to} label='To Value' fieldName='to' updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)} />}
135
+ </div>
136
+ ))}
137
+ {!config.regions && <p style={{ textAlign: 'center' }}>There are currently no regions.</p>}
138
+ <button
139
+ type='button'
140
+ className='btn full-width'
141
+ onClick={e => {
142
+ e.preventDefault()
143
+ addColumn()
144
+ }}
145
+ >
146
+ Add Region
147
+ </button>
148
+ </>
149
+ )
150
+ })
151
+
152
+ const RegionsPanel = ({ name }: PanelProps) => {
153
+ const { visSupportsRegions } = useEditorPermissions()
154
+ const { config, updateConfig } = useContext<ChartContext>(ConfigContext)
155
+
156
+ return visSupportsRegions() ? (
157
+ <AccordionItem>
158
+ <AccordionItemHeading>
159
+ <AccordionItemButton>{name}</AccordionItemButton>
160
+ </AccordionItemHeading>
161
+ <AccordionItemPanel>
162
+ <RegionSettings config={config} updateConfig={updateConfig} />
163
+ </AccordionItemPanel>
164
+ </AccordionItem>
165
+ ) : null
166
+ }
167
+
168
+ export default RegionsPanel
@@ -1,5 +1,5 @@
1
1
  import React, { useContext } from 'react'
2
- import ConfigContext from '../ConfigContext'
2
+ import ConfigContext from '../../../ConfigContext'
3
3
 
4
4
  // Core
5
5
  import InputSelect from '@cdc/core/components/inputs/InputSelect'
@@ -0,0 +1,3 @@
1
+ export type PanelProps = {
2
+ name: string
3
+ }
@@ -0,0 +1,13 @@
1
+ import ForestPlotSettings from './Panel.ForestPlotSettings'
2
+ import Series from './Panel.Series.jsx'
3
+ import DateHighlighting from './Panel.DateHighlighting'
4
+ import Regions from './Panel.Regions'
5
+
6
+ const Panels = {
7
+ ForestPlot: ForestPlotSettings,
8
+ Series: Series,
9
+ DateHighlighting,
10
+ Regions
11
+ }
12
+
13
+ export default Panels