@cdc/chart 4.24.4 → 4.24.5

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 (33) hide show
  1. package/dist/cdcchart.js +32130 -31726
  2. package/index.html +7 -7
  3. package/package.json +2 -2
  4. package/src/CdcChart.tsx +17 -13
  5. package/src/_stories/Chart.stories.tsx +8 -0
  6. package/src/_stories/_mock/bar-chart-suppressed.json +474 -0
  7. package/src/components/AreaChart/components/AreaChart.jsx +2 -2
  8. package/src/components/BarChart/components/BarChart.Horizontal.tsx +52 -47
  9. package/src/components/BarChart/components/BarChart.Vertical.tsx +77 -92
  10. package/src/components/DeviationBar.jsx +4 -2
  11. package/src/components/EditorPanel/EditorPanel.tsx +289 -601
  12. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -2
  13. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -5
  14. package/src/components/EditorPanel/useEditorPermissions.js +4 -1
  15. package/src/components/Legend/Legend.Component.tsx +62 -10
  16. package/src/components/LineChart/LineChartProps.ts +13 -6
  17. package/src/components/LineChart/components/LineChart.Circle.tsx +22 -11
  18. package/src/components/LineChart/helpers.ts +134 -10
  19. package/src/components/LineChart/index.tsx +69 -42
  20. package/src/components/LinearChart.jsx +155 -139
  21. package/src/components/ZoomBrush.tsx +40 -21
  22. package/src/data/initial-state.js +4 -4
  23. package/src/hooks/useBarChart.js +47 -22
  24. package/src/hooks/useMinMax.ts +21 -2
  25. package/src/hooks/useScales.ts +23 -23
  26. package/src/hooks/useTooltip.tsx +11 -11
  27. package/src/scss/main.scss +56 -6
  28. package/src/types/ChartConfig.ts +3 -13
  29. package/src/types/ChartContext.ts +4 -0
  30. package/src/_stories/ChartLine.preliminary.tsx +0 -19
  31. package/src/_stories/ChartSuppress.stories.tsx +0 -19
  32. package/src/_stories/_mock/suppress_mock.json +0 -911
  33. package/src/helpers/computeMarginBottom.ts +0 -56
@@ -1,5 +1,5 @@
1
1
  import { useState, useEffect, useCallback, memo, useContext } from 'react'
2
- import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
2
+ import { DragDropContext, Droppable } from '@hello-pangea/dnd'
3
3
  import { isDateScale } from '@cdc/core/helpers/cove/date'
4
4
  import { Accordion, AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
5
5
  import Layout from '@cdc/core/components/Layout'
@@ -8,18 +8,17 @@ import Layout from '@cdc/core/components/Layout'
8
8
  import AdvancedEditor from '@cdc/core/components/AdvancedEditor'
9
9
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
10
10
  import Icon from '@cdc/core/components/ui/Icon'
11
- import InputToggle from '@cdc/core/components/inputs/InputToggle'
11
+ import ColumnsEditor from '@cdc/core/components/EditorPanel/ColumnsEditor'
12
+ import DataTableEditor from '@cdc/core/components/EditorPanel/DataTableEditor'
13
+ import VizFilterEditor from '@cdc/core/components/EditorPanel/VizFilterEditor'
12
14
  import Tooltip from '@cdc/core/components/ui/Tooltip'
13
15
  import { Select, TextField, CheckBox } from '@cdc/core/components/EditorPanel/Inputs'
14
16
 
15
17
  // chart components
16
18
  import Panels from './components/Panels'
17
- import Series from './components/Panel.Series.jsx'
18
19
 
19
20
  // cdc additional
20
- import { useColorPalette } from '../../hooks/useColorPalette'
21
21
  import { useEditorPermissions } from './useEditorPermissions'
22
- import { useFilters } from '@cdc/core/components/Filters'
23
22
  import { useHighlightedBars } from '../../hooks/useHighlightedBars'
24
23
  import ConfigContext from '../../ConfigContext'
25
24
  import useReduceData from '../../hooks/useReduceData'
@@ -27,113 +26,57 @@ import useRightAxis from '../../hooks/useRightAxis'
27
26
  import WarningImage from '../../images/warning.svg'
28
27
  import useMinMax from '../../hooks/useMinMax'
29
28
 
30
- import { type ChartConfig } from '../../types/ChartConfig'
31
29
  import { type ChartContext } from '../../types/ChartContext'
30
+ import { type ChartConfig } from '../../types/ChartConfig'
32
31
 
33
32
  import './editor-panel.scss'
34
33
  import { Anchor } from '@cdc/core/types/Axis'
35
- import DataTableEditor from '@cdc/core/components/EditorPanel/DataTableEditor'
36
34
  import EditorPanelContext from './EditorPanelContext'
35
+ import _ from 'lodash'
36
+ import { adjustedSymbols as symbolCodes } from '@cdc/core/helpers/footnoteSymbols'
37
37
 
38
- const DataSuppression = memo(({ config, updateConfig, data }: any) => {
39
- const getColumnOptions = () => {
40
- const keys = new Set()
41
- data.forEach(d => {
42
- Object.keys(d).forEach(key => {
43
- keys.add(key)
44
- })
45
- })
46
- return [...keys]
47
- }
48
-
49
- const getIconOptions = () => {
50
- return ['star']
51
- }
52
-
53
- let removeColumn = i => {
54
- let suppressedData = []
55
-
56
- if (config.suppressedData) {
57
- suppressedData = [...config.suppressedData]
58
- }
59
-
60
- suppressedData.splice(i, 1)
38
+ interface PreliminaryProps {
39
+ config: ChartConfig
40
+ updateConfig: Function
41
+ data: Record<string, any>[]
42
+ }
61
43
 
62
- updateConfig({ ...config, suppressedData })
63
- }
44
+ const PreliminaryData: React.FC<PreliminaryProps> = ({ config, updateConfig, data }) => {
45
+ const isCombo = config.visualizationType === 'Combo'
46
+ const lineSeriesExists = config.runtime.lineSeriesKeys?.length > 0
47
+ const barSeriesExists = config.runtime.barSeriesKeys?.length > 0
48
+ const hasComboLineSeries = isCombo && lineSeriesExists
49
+ const hasComboBarSeries = isCombo && barSeriesExists
64
50
 
65
- let addColumn = () => {
66
- let suppressedData = config.suppressedData ? [...config.suppressedData] : []
67
- suppressedData.push({ label: '', column: '', value: '', icon: '' })
68
- updateConfig({ ...config, suppressedData })
51
+ const getColumnOptions = () => {
52
+ return _.uniq(_.flatMap(data, _.keys))
69
53
  }
70
54
 
71
- let update = (fieldName, value, i) => {
72
- let suppressedData = []
73
-
74
- if (config.suppressedData) {
75
- suppressedData = [...config.suppressedData]
76
- }
77
-
78
- suppressedData[i][fieldName] = value
79
- updateConfig({ ...config, suppressedData })
55
+ const getTypeOptions = () => {
56
+ return config.visualizationType === 'Line' || hasComboLineSeries ? ['effect', 'suppression'] : ['suppression']
80
57
  }
81
58
 
82
- return (
83
- <>
84
- {config.suppressedData &&
85
- config.suppressedData.map(({ label, column, value, icon }, i) => {
86
- return (
87
- <div key={`suppressed-${i}`} className='edit-block'>
88
- <button
89
- type='button'
90
- className='remove-column'
91
- onClick={event => {
92
- event.preventDefault()
93
- removeColumn(i)
94
- }}
95
- >
96
- Remove
97
- </button>
98
- <Select value={column} initial='Select' fieldName='column' label='Column' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getColumnOptions()} />
99
- <TextField value={value} fieldName='value' label='Value' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} />
100
- <Select value={icon} initial='Select' fieldName='icon' label='Icon' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getIconOptions()} />
101
- <TextField value={label} fieldName='label' label='Label' placeholder='suppressed' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} />
102
- </div>
103
- )
104
- })}
105
-
106
- <button type='button' onClick={addColumn} className='btn full-width'>
107
- Add Suppression Class
108
- </button>
109
- </>
110
- )
111
- })
112
- const PreliminaryData = memo(({ config, updateConfig, data }) => {
113
- const getColumnOptions = () => {
114
- const keys = new Set()
115
- data.forEach(d => {
116
- Object.keys(d).forEach(key => {
117
- keys.add(key)
118
- })
119
- })
120
- return [...keys]
59
+ const lineCodes = {
60
+ 'Dashed Small': '\u002D \u002D \u002D',
61
+ 'Dashed Medium': '\u2013 \u2013',
62
+ 'Dashed Large': '\u2014 \u2013',
63
+ 'Open Circles': '\u25EF'
121
64
  }
122
65
 
123
- const getTypeOptions = () => {
124
- if (config.visualizationType === 'Line' || config.visualizationType === 'Combo') {
125
- return ['effect']
126
- } else {
127
- return ['suppression']
66
+ const getStyleOptions = type => {
67
+ if (config.visualizationType === 'Line' || isCombo) {
68
+ const options = Object.keys(lineCodes)
69
+ if (type === 'suppression') {
70
+ return options.slice(0, -1)
71
+ } else {
72
+ return options
73
+ }
128
74
  }
129
75
  }
130
76
 
131
- const getStyleOptions = () => {
132
- if (config.visualizationType === 'Line' || config.visualizationType === 'Combo') {
133
- return ['Dashed Small', 'Dashed Medium', 'Dashed Large', 'Open Circles']
134
- }
135
- if (config.visualizationType === 'Bar') {
136
- return ['star']
77
+ const getSymbolOptions = () => {
78
+ if (config.visualizationType === 'Bar' || hasComboBarSeries) {
79
+ return Object.keys(symbolCodes)
137
80
  }
138
81
  }
139
82
 
@@ -150,8 +93,23 @@ const PreliminaryData = memo(({ config, updateConfig, data }) => {
150
93
  }
151
94
 
152
95
  let addColumn = () => {
96
+ const defaultType = config.visualizationType === 'Line' ? 'effect' : 'suppression'
153
97
  let preliminaryData = config.preliminaryData ? [...config.preliminaryData] : []
154
- preliminaryData.push({ type: '', label: '', column: '', value: '', style: '' })
98
+ const defaultValues = {
99
+ type: defaultType,
100
+ seriesKey: '',
101
+ label: 'Suppressed',
102
+ column: '',
103
+ value: '',
104
+ style: '',
105
+ displayTooltip: true,
106
+ displayLegend: true,
107
+ displayTable: true,
108
+ symbol: '',
109
+ iconCode: '',
110
+ lineCode: ''
111
+ }
112
+ preliminaryData.push(defaultValues)
155
113
  updateConfig({ ...config, preliminaryData })
156
114
  }
157
115
 
@@ -163,15 +121,22 @@ const PreliminaryData = memo(({ config, updateConfig, data }) => {
163
121
  }
164
122
 
165
123
  preliminaryData[i][fieldName] = value
124
+ if (fieldName === 'symbol') {
125
+ preliminaryData[i]['iconCode'] = symbolCodes[value]
126
+ }
127
+ if (fieldName === 'style') {
128
+ preliminaryData[i]['lineCode'] = lineCodes[value]
129
+ }
166
130
  updateConfig({ ...config, preliminaryData })
167
131
  }
168
132
 
169
133
  return (
170
134
  <>
171
135
  {config.preliminaryData &&
172
- config.preliminaryData.map(({ seriesKey, type, label, column, value, style }, i) => {
136
+ config.preliminaryData?.map(({ column, displayLegend, displayTable, displayTooltip, label, seriesKey, style, symbol, type, value }, i) => {
173
137
  return (
174
138
  <div key={`preliminaryData-${i}`} className='edit-block'>
139
+ <p> {type === 'suppression' ? 'Suppressed' : 'Effect'} Data</p>
175
140
  <button
176
141
  type='button'
177
142
  className='remove-column'
@@ -182,23 +147,156 @@ const PreliminaryData = memo(({ config, updateConfig, data }) => {
182
147
  >
183
148
  Remove
184
149
  </button>
185
- <Select value={type} initial='Select' fieldName='type' label='Type' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getTypeOptions()} />
186
- <Select value={seriesKey} initial='Select' fieldName='seriesKey' label='ASSOCIATE TO SERIES' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={config.runtime.lineSeriesKeys ?? config.runtime?.seriesKeys} />
187
- <Select value={column} initial='Select' fieldName='column' label='COLUMN WITH CONFIGURATION VALUE' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getColumnOptions()} />
188
- <TextField value={value} fieldName='value' label='VALUE TO TRIGGER' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} />
189
- <Select value={style} initial='Select' fieldName='style' label='Style' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getStyleOptions()} />
190
150
 
191
- <TextField value={label} fieldName='label' label='Label' placeholder='' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} />
151
+ <Select value={type} initial={config.visualizationType == 'Bar' ? '' : 'Select'} fieldName='type' label='Type' updateField={(_, __, fieldName, value) => update(fieldName, value, i)} options={getTypeOptions()} />
152
+ {type === 'suppression' ? (
153
+ <>
154
+ <Select
155
+ tooltip={
156
+ <Tooltip style={{ textTransform: 'none' }}>
157
+ <Tooltip.Target>
158
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
159
+ </Tooltip.Target>
160
+ <Tooltip.Content>
161
+ <p>If no “Data Series" is selected, the symbol will be applied to "all" suppressed values indicated in the dataset.</p>
162
+ </Tooltip.Content>
163
+ </Tooltip>
164
+ }
165
+ value={column}
166
+ initial='Select'
167
+ fieldName='column'
168
+ label='Add Data Series'
169
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
170
+ options={config.runtime?.seriesKeys}
171
+ />
172
+ <TextField value={value} fieldName='value' label='Suppressed Data Value' updateField={(_, __, fieldName, value) => update(fieldName, value, i)} />
173
+ {(hasComboLineSeries || config.visualizationType === 'Line') && (
174
+ <Select
175
+ tooltip={
176
+ <Tooltip style={{ textTransform: 'none' }}>
177
+ <Tooltip.Target>
178
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
179
+ </Tooltip.Target>
180
+ <Tooltip.Content>
181
+ <p>The recommended approach for presenting data is to include a footnote indicating any data suppression.</p>
182
+ </Tooltip.Content>
183
+ </Tooltip>
184
+ }
185
+ value={style}
186
+ initial='Select'
187
+ fieldName='style'
188
+ label={'suppression line style'}
189
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
190
+ options={getStyleOptions(type)}
191
+ />
192
+ )}
193
+
194
+ {(hasComboBarSeries || config.visualizationType === 'Bar') && (
195
+ <Select
196
+ tooltip={
197
+ <Tooltip style={{ textTransform: 'none' }}>
198
+ <Tooltip.Target>
199
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
200
+ </Tooltip.Target>
201
+ <Tooltip.Content>
202
+ <p>The suggested method for presenting suppressed data is to use "double asterisks". If "double asterisks" are already used elsewhere (e.g., footnotes), please select an alternative symbol from the menu to denote data suppression.</p>
203
+ </Tooltip.Content>
204
+ </Tooltip>
205
+ }
206
+ value={symbol}
207
+ initial='Select'
208
+ fieldName='symbol'
209
+ label={config.visualizationType === 'Combo' ? 'suppression bar symbol' : 'suppression symbol'}
210
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
211
+ options={getSymbolOptions()}
212
+ />
213
+ )}
214
+
215
+ <TextField
216
+ tooltip={
217
+ <Tooltip style={{ textTransform: 'none' }}>
218
+ <Tooltip.Target>
219
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
220
+ </Tooltip.Target>
221
+ <Tooltip.Content>
222
+ <p>This label will display in the tooltip and legend.</p>
223
+ </Tooltip.Content>
224
+ </Tooltip>
225
+ }
226
+ value={label ? label : 'Suppressed'}
227
+ fieldName='label'
228
+ label='Suppressed Data Label'
229
+ placeholder=''
230
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
231
+ />
232
+ <CheckBox
233
+ tooltip={
234
+ <Tooltip style={{ textTransform: 'none' }}>
235
+ <Tooltip.Target>
236
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
237
+ </Tooltip.Target>
238
+ <Tooltip.Content>
239
+ <p>Enabling this tooltip will provide a clearer indication of 'suppressed' or 'zero data' values, whichever is applicable. Deselecting 'Display In Tooltip' indicates that you do not want to display 'suppressed' or 'zero data' values in tooltips when hovering over them.</p>
240
+ </Tooltip.Content>
241
+ </Tooltip>
242
+ }
243
+ value={displayTooltip}
244
+ fieldName='displayTooltip'
245
+ label='Display in tooltips'
246
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
247
+ />
248
+ <CheckBox
249
+ tooltip={
250
+ <Tooltip style={{ textTransform: 'none' }}>
251
+ <Tooltip.Target>
252
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
253
+ </Tooltip.Target>
254
+ <Tooltip.Content>
255
+ <p>Deselecting "Display in Legend" indicates that you do not want to display suppressed data in the legend.</p>
256
+ </Tooltip.Content>
257
+ </Tooltip>
258
+ }
259
+ value={displayLegend}
260
+ fieldName='displayLegend'
261
+ label='Display in legend'
262
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
263
+ />
264
+ <CheckBox
265
+ tooltip={
266
+ <Tooltip style={{ textTransform: 'none' }}>
267
+ <Tooltip.Target>
268
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
269
+ </Tooltip.Target>
270
+ <Tooltip.Content>
271
+ <p>Deselecting "Display In Data Table" indicates that you do not want to display suppressed data in the data table.</p>
272
+ </Tooltip.Content>
273
+ </Tooltip>
274
+ }
275
+ value={displayTable}
276
+ fieldName='displayTable'
277
+ label='Display in table'
278
+ updateField={(_, __, fieldName, value) => update(fieldName, value, i)}
279
+ />
280
+ </>
281
+ ) : (
282
+ <>
283
+ <Select value={seriesKey} initial='Select' fieldName='seriesKey' label='ASSOCIATE TO SERIES' updateField={(_, __, fieldName, value) => update(fieldName, value, i)} options={config.runtime.lineSeriesKeys ?? config.runtime?.seriesKeys} />
284
+ <Select value={column} initial='Select' fieldName='column' label='COLUMN WITH CONFIGURATION VALUE' updateField={(_, __, fieldName, value) => update(fieldName, value, i)} options={getColumnOptions()} />
285
+ <TextField value={value} fieldName='value' label='VALUE TO TRIGGER' updateField={(_, __, fieldName, value) => update(fieldName, value, i)} />
286
+ <Select value={style} initial='Select' fieldName='style' label='Style' updateField={(_, __, fieldName, value) => update(fieldName, value, i)} options={getStyleOptions(type)} />
287
+ <TextField value={label} fieldName='label' label='Label' placeholder='' updateField={(_, __, fieldName, value) => update(fieldName, value, i)} />
288
+ </>
289
+ )}
192
290
  </div>
193
291
  )
194
292
  })}
195
293
 
196
294
  <button type='button' onClick={addColumn} className='btn full-width'>
197
- {config.visualizationType === 'Line' || config.visualizationType === 'Combo' ? 'Add Special Line' : config.visualizationType === 'Bar' ? ' Add Special Bar' : 'Add Special Line/Bar'}
295
+ {config.visualizationType === 'Line' ? 'Add Special Line' : config.visualizationType === 'Bar' ? ' Add Special Bar' : 'Add Special Bar/Line'}
198
296
  </button>
199
297
  </>
200
298
  )
201
- })
299
+ }
202
300
 
203
301
  const EditorPanel = () => {
204
302
  const {
@@ -224,9 +322,6 @@ const EditorPanel = () => {
224
322
  } = useContext<ChartContext>(ConfigContext)
225
323
 
226
324
  const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, unfilteredData)
227
-
228
- const { twoColorPalettes, sequential, nonSequential } = useColorPalette(config, updateConfig)
229
-
230
325
  const properties = { data, config }
231
326
  const { leftMax, rightMax } = useMinMax(properties)
232
327
 
@@ -276,9 +371,6 @@ const EditorPanel = () => {
276
371
  visHasDataSuppression
277
372
  } = useEditorPermissions()
278
373
 
279
- // argument acts as props
280
- const { handleFilterOrder, filterOrderOptions, filterStyleOptions } = useFilters({ config, setConfig: updateConfig, filteredData: data, setFilteredData })
281
-
282
374
  // when the visualization type changes we
283
375
  // have to update the individual series type & axis details
284
376
  // dataKey is unchanged here.
@@ -389,13 +481,18 @@ const EditorPanel = () => {
389
481
  return
390
482
  }
391
483
 
392
- if (section === 'columns' && subsection !== '' && fieldName !== '') {
484
+ const truthy = val => {
485
+ if (val === 0) return true // indexes can be used as keys
486
+ return !!val
487
+ }
488
+
489
+ if (section === 'columns' && truthy(subsection) && truthy(fieldName)) {
393
490
  updateConfig({
394
491
  ...config,
395
- [section]: {
396
- ...config[section],
492
+ columns: {
493
+ ...config.columns,
397
494
  [subsection]: {
398
- ...config[section][subsection],
495
+ ...config.columns[subsection],
399
496
  [fieldName]: newValue
400
497
  }
401
498
  }
@@ -403,6 +500,8 @@ const EditorPanel = () => {
403
500
  return
404
501
  }
405
502
  if (null === section && null === subsection) {
503
+ // special case that allows for updating the config object directly
504
+ if (!truthy(fieldName)) console.error('fieldName is required')
406
505
  let updatedConfig = { ...config, [fieldName]: newValue }
407
506
  enforceRestrictions(updatedConfig)
408
507
  updateConfig(updatedConfig)
@@ -413,13 +512,13 @@ const EditorPanel = () => {
413
512
 
414
513
  let sectionValue = isArray ? [...config[section], newValue] : { ...config[section], [fieldName]: newValue }
415
514
 
416
- if (null !== subsection) {
515
+ if (truthy(subsection)) {
417
516
  if (isArray) {
418
517
  sectionValue = [...config[section]]
419
518
  sectionValue[subsection] = { ...sectionValue[subsection], [fieldName]: newValue }
420
519
  } else if (typeof newValue === 'string') {
421
520
  sectionValue[subsection] = newValue
422
- } else {
521
+ } else if (truthy(fieldName)) {
423
522
  sectionValue = { ...config[section], [subsection]: { ...config[section][subsection], [fieldName]: newValue } }
424
523
  }
425
524
  }
@@ -457,30 +556,6 @@ const EditorPanel = () => {
457
556
  })
458
557
  }
459
558
 
460
- const removeFilter = index => {
461
- let filters = [...config.filters]
462
-
463
- filters.splice(index, 1)
464
-
465
- updateConfig({ ...config, filters })
466
- }
467
-
468
- const updateFilterProp = (name, index, value) => {
469
- let filters = [...config.filters]
470
-
471
- filters[index][name] = value
472
-
473
- updateConfig({ ...config, filters })
474
- }
475
-
476
- const addNewFilter = () => {
477
- let filters = config.filters ? [...config.filters] : []
478
-
479
- filters.push({ values: [] })
480
-
481
- updateConfig({ ...config, filters })
482
- }
483
-
484
559
  const addNewSeries = seriesKey => {
485
560
  let newSeries = config.series ? [...config.series] : []
486
561
  let forecastingStages = Array.from(new Set(data.map(item => item[seriesKey])))
@@ -505,37 +580,6 @@ const EditorPanel = () => {
505
580
  updateConfig({ ...config }, newData)
506
581
  }
507
582
 
508
- const removeSeries = seriesKey => {
509
- let series = [...config.series]
510
- let seriesIndex = -1
511
-
512
- for (let i = 0; i < series.length; i++) {
513
- if (series[i].dataKey === seriesKey) {
514
- seriesIndex = i
515
- break
516
- }
517
- }
518
-
519
- if (seriesIndex !== -1) {
520
- series.splice(seriesIndex, 1)
521
-
522
- let newConfig = { ...config, series }
523
-
524
- if (series.length === 0) {
525
- delete newConfig.series
526
- }
527
-
528
- updateConfig(newConfig)
529
- }
530
-
531
- if (config.visualizationType === 'Paired Bar') {
532
- updateConfig({
533
- ...config,
534
- series: []
535
- })
536
- }
537
- }
538
-
539
583
  const addNewExclusion = exclusionKey => {
540
584
  let newExclusion = [...config.exclusions.keys]
541
585
  newExclusion.push(exclusionKey)
@@ -569,16 +613,6 @@ const EditorPanel = () => {
569
613
  }
570
614
  }
571
615
 
572
- const getFilters = () => {
573
- let columns = {}
574
-
575
- unfilteredData.forEach(row => {
576
- Object.keys(row).forEach(columnName => (columns[columnName] = true))
577
- })
578
-
579
- return Object.keys(columns)
580
- }
581
-
582
616
  const getColumns = (filter = true) => {
583
617
  let columns = {}
584
618
  unfilteredData.forEach(row => {
@@ -902,40 +936,8 @@ const EditorPanel = () => {
902
936
  })
903
937
  }
904
938
 
905
- // prevents adding duplicates
906
- const additionalColumns = Object.keys(config.columns).filter(value => {
907
- const defaultCols = [config.xAxis.dataKey] // ['geo', 'navigate', 'primary', 'latitude', 'longitude']
908
-
909
- if (true === defaultCols.includes(value)) {
910
- return false
911
- }
912
- return true
913
- })
914
-
915
- // just adds a new column but not set to any data yet
916
- const addAdditionalColumn = number => {
917
- const columnKey = `additionalColumn${number}`
918
-
919
- updateConfig({
920
- ...config,
921
- columns: {
922
- ...config.columns,
923
- [columnKey]: {
924
- label: 'New Column',
925
- dataTable: false,
926
- tooltips: false,
927
- prefix: '',
928
- suffix: '',
929
- forestPlot: false,
930
- startingPoint: '0',
931
- forestPlotAlignRight: false
932
- }
933
- }
934
- })
935
- }
936
-
937
939
  const removeAdditionalColumn = columnName => {
938
- const newColumns = config.columns
940
+ const newColumns = _.cloneDeep(config.columns)
939
941
 
940
942
  delete newColumns[columnName]
941
943
 
@@ -1210,7 +1212,23 @@ const EditorPanel = () => {
1210
1212
  </>
1211
1213
  )}
1212
1214
  <span className='divider-heading'>Number Formatting</span>
1213
- <CheckBox value={config.dataFormat.commas} section='dataFormat' fieldName='commas' label='Add commas' updateField={updateField} />
1215
+ <CheckBox
1216
+ value={config.dataFormat.commas}
1217
+ section='dataFormat'
1218
+ fieldName='commas'
1219
+ label='Add commas'
1220
+ updateField={updateField}
1221
+ tooltip={
1222
+ <Tooltip style={{ textTransform: 'none' }}>
1223
+ <Tooltip.Target>
1224
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
1225
+ </Tooltip.Target>
1226
+ <Tooltip.Content>
1227
+ <p>{`Selecting this option will add commas to the left value axis, tooltip hover, and data table.`}</p>
1228
+ </Tooltip.Content>
1229
+ </Tooltip>
1230
+ }
1231
+ />
1214
1232
  <CheckBox
1215
1233
  value={config.dataFormat.abbreviated}
1216
1234
  section='dataFormat'
@@ -1943,7 +1961,7 @@ const EditorPanel = () => {
1943
1961
  {visSupportsDateCategoryNumTicks() && (config.xAxis.type === 'date-time' || !config.xAxis.manual) && (
1944
1962
  <TextField value={config.xAxis.numTicks} placeholder='Auto' type='number' min={1} section='xAxis' fieldName='numTicks' label='Number of ticks' className='number-narrow' updateField={updateField} />
1945
1963
  )}
1946
- {visSupportsDateCategoryHeight() && <TextField value={config.xAxis.size} type='number' min={0} section='xAxis' fieldName='size' label={config.orientation === 'horizontal' ? 'Size (Width)' : 'Size (Height)'} className='number-narrow' updateField={updateField} />}
1964
+ {visSupportsDateCategoryHeight() && <TextField value={config.xAxis.padding} type='number' min={0} section='xAxis' fieldName='padding' label={config.orientation === 'horizontal' ? 'Size (Width)' : 'Size (Height)'} className='number-narrow' updateField={updateField} />}
1947
1965
 
1948
1966
  {/* Hiding this for now, not interested in moving the axis lines away from chart comp. right now. */}
1949
1967
  {/* <TextField value={config.xAxis.axisPadding} type='number' max={10} min={0} section='xAxis' fieldName='axisPadding' label={'Axis Padding'} className='number-narrow' updateField={updateField} /> */}
@@ -1992,6 +2010,40 @@ const EditorPanel = () => {
1992
2010
  {visSupportsDateCategoryAxisTicks() && <CheckBox value={config.xAxis.hideTicks} section='xAxis' fieldName='hideTicks' label='Hide Ticks' updateField={updateField} />}
1993
2011
  </>
1994
2012
  )}
2013
+ <CheckBox
2014
+ tooltip={
2015
+ <Tooltip style={{ textTransform: 'none' }}>
2016
+ <Tooltip.Target>
2017
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2018
+ </Tooltip.Target>
2019
+ <Tooltip.Content>
2020
+ <p>Selecting this option will display a "thin line" slightly above the Date/Category Axis to indicate "suppressed data" where "suppressed data" values are indicated in the Data Series.</p>
2021
+ </Tooltip.Content>
2022
+ </Tooltip>
2023
+ }
2024
+ value={config.xAxis.showSuppressedLine}
2025
+ section='xAxis'
2026
+ fieldName='showSuppressedLine'
2027
+ label='Display suppressed data line'
2028
+ updateField={updateField}
2029
+ />
2030
+ <CheckBox
2031
+ tooltip={
2032
+ <Tooltip style={{ textTransform: 'none' }}>
2033
+ <Tooltip.Target>
2034
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2035
+ </Tooltip.Target>
2036
+ <Tooltip.Content>
2037
+ <p>Selecting this option will display "suppressed data symbol" on the Date/Category Axis where suppressed data values are indicated in the Data Series, unless a different symbol was chosen from the data series (e.g., suppression symbol) menu.</p>
2038
+ </Tooltip.Content>
2039
+ </Tooltip>
2040
+ }
2041
+ value={config.xAxis.showSuppressedSymbol}
2042
+ section='xAxis'
2043
+ fieldName='showSuppressedSymbol'
2044
+ label='Display suppressed data symbol'
2045
+ updateField={updateField}
2046
+ />
1995
2047
 
1996
2048
  {config.series?.length === 1 && config.visualizationType === 'Bar' && (
1997
2049
  <>
@@ -2338,6 +2390,7 @@ const EditorPanel = () => {
2338
2390
  </AccordionItem>
2339
2391
  )}
2340
2392
  <Panels.Regions name='Regions' />
2393
+
2341
2394
  {/* Columns */}
2342
2395
  {config.visualizationType !== 'Box Plot' && (
2343
2396
  <AccordionItem>
@@ -2345,235 +2398,7 @@ const EditorPanel = () => {
2345
2398
  <AccordionItemButton>Columns</AccordionItemButton>
2346
2399
  </AccordionItemHeading>
2347
2400
  <AccordionItemPanel>
2348
- {'navigation' !== config.type && (
2349
- <fieldset className='primary-fieldset edit-block'>
2350
- <label>
2351
- <span className='edit-label'>
2352
- Additional Columns
2353
- <Tooltip style={{ textTransform: 'none' }}>
2354
- <Tooltip.Target>
2355
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2356
- </Tooltip.Target>
2357
- <Tooltip.Content>
2358
- <p>You can specify additional columns to display in tooltips and / or the supporting data table.</p>
2359
- </Tooltip.Content>
2360
- </Tooltip>
2361
- </span>
2362
- </label>
2363
- {additionalColumns.map(val => (
2364
- <fieldset className='edit-block' key={val}>
2365
- <button
2366
- className='remove-column'
2367
- onClick={event => {
2368
- event.preventDefault()
2369
- removeAdditionalColumn(val)
2370
- }}
2371
- >
2372
- Remove
2373
- </button>
2374
- <label>
2375
- <span className='edit-label column-heading'>Column</span>
2376
- <select
2377
- value={config.columns[val] ? config.columns[val].name : getColumns()[0]}
2378
- onChange={event => {
2379
- editColumn(val, 'name', event.target.value)
2380
- }}
2381
- >
2382
- {getColumns().map(option => (
2383
- <option>{option}</option>
2384
- ))}
2385
- </select>
2386
- </label>
2387
- <label>
2388
- <span className='edit-label column-heading'>Associate to Series</span>
2389
- <select
2390
- value={config.columns[val] ? config.columns[val].series : ''}
2391
- onChange={event => {
2392
- editColumn(val, 'series', event.target.value)
2393
- }}
2394
- >
2395
- <option value=''>Select series</option>
2396
- {config.series.map(series => (
2397
- <option>{series.dataKey}</option>
2398
- ))}
2399
- </select>
2400
- </label>
2401
- <TextField value={config.columns[val].label} section='columns' subsection={val} fieldName='label' label='Label' updateField={updateField} />
2402
- <ul className='column-edit'>
2403
- <li className='three-col'>
2404
- <TextField value={config.columns[val].prefix} section='columns' subsection={val} fieldName='prefix' label='Prefix' updateField={updateField} />
2405
- <TextField value={config.columns[val].suffix} section='columns' subsection={val} fieldName='suffix' label='Suffix' updateField={updateField} />
2406
- <TextField type='number' value={config.columns[val].roundToPlace} section='columns' subsection={val} fieldName='roundToPlace' label='Round' updateField={updateField} />
2407
- </li>
2408
- <li>
2409
- <label className='checkbox'>
2410
- <input
2411
- type='checkbox'
2412
- checked={config.columns[val].commas}
2413
- onChange={event => {
2414
- editColumn(val, 'commas', event.target.checked)
2415
- }}
2416
- />
2417
- <span className='edit-label'>Add Commas to Numbers</span>
2418
- </label>
2419
- </li>
2420
- <li>
2421
- {config.table.showVertical && (
2422
- <label className='checkbox'>
2423
- <input
2424
- type='checkbox'
2425
- checked={config.columns[val].dataTable}
2426
- onChange={event => {
2427
- editColumn(val, 'dataTable', event.target.checked)
2428
- }}
2429
- />
2430
- <span className='edit-label'>Show in Data Table</span>
2431
- </label>
2432
- )}
2433
- </li>
2434
- {config.visualizationType === 'Pie' && (
2435
- <li>
2436
- <label className='checkbox'>
2437
- <input
2438
- type='checkbox'
2439
- checked={config.columns[val].showInViz}
2440
- onChange={event => {
2441
- editColumn(val, 'showInViz', event.target.checked)
2442
- }}
2443
- />
2444
- <span className='edit-label'>Show in Visualization</span>
2445
- </label>
2446
- </li>
2447
- )}
2448
-
2449
- {/* disable for now */}
2450
-
2451
- <li>
2452
- <label className='checkbox'>
2453
- <input
2454
- type='checkbox'
2455
- checked={config.columns[val].tooltips || false}
2456
- onChange={event => {
2457
- updateSeriesTooltip(val, event.target.checked)
2458
- }}
2459
- />
2460
- <span className='edit-label'>Show in tooltip</span>
2461
- </label>
2462
- </li>
2463
-
2464
- {config.visualizationType === 'Forest Plot' && (
2465
- <>
2466
- <li>
2467
- <label className='checkbox'>
2468
- <input
2469
- type='checkbox'
2470
- checked={config.columns[val].forestPlot || false}
2471
- onChange={event => {
2472
- editColumn(val, 'forestPlot', event.target.checked)
2473
- }}
2474
- />
2475
- <span className='edit-label'>Show in Forest Plot</span>
2476
- </label>
2477
- </li>
2478
- <li>
2479
- <label className='checkbox'>
2480
- <input
2481
- type='checkbox'
2482
- checked={config.columns[val].forestPlotAlignRight || false}
2483
- onChange={event => {
2484
- editColumn(val, 'forestPlotAlignRight', event.target.checked)
2485
- }}
2486
- />
2487
- <span className='edit-label'>Align Right</span>
2488
- </label>
2489
- </li>
2490
-
2491
- {!config.columns[val].forestPlotAlignRight && (
2492
- <li>
2493
- <label className='text'>
2494
- <span className='edit-label'>Forest Plot Starting Point</span>
2495
- <input
2496
- type='number'
2497
- value={config.columns[val].forestPlotStartingPoint || 0}
2498
- onChange={event => {
2499
- editColumn(val, 'forestPlotStartingPoint', event.target.value)
2500
- }}
2501
- />
2502
- </label>
2503
- </li>
2504
- )}
2505
- </>
2506
- )}
2507
- </ul>
2508
- </fieldset>
2509
- ))}
2510
- <button
2511
- className={'btn full-width'}
2512
- onClick={event => {
2513
- event.preventDefault()
2514
- addAdditionalColumn(additionalColumns.length + 1)
2515
- }}
2516
- >
2517
- Add Column
2518
- </button>
2519
- </fieldset>
2520
- )}
2521
- {'category' === config.legend.type && (
2522
- <fieldset className='primary-fieldset edit-block'>
2523
- <label>
2524
- <span className='edit-label'>
2525
- Additional Category
2526
- <Tooltip style={{ textTransform: 'none' }}>
2527
- <Tooltip.Target>
2528
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2529
- </Tooltip.Target>
2530
- <Tooltip.Content>
2531
- <p>You can provide additional categories to ensure they appear in the legend</p>
2532
- </Tooltip.Content>
2533
- </Tooltip>
2534
- </span>
2535
- </label>
2536
- {config.legend.additionalCategories &&
2537
- config.legend.additionalCategories.map((val, i) => (
2538
- <fieldset className='edit-block' key={val}>
2539
- <button
2540
- className='remove-column'
2541
- onClick={event => {
2542
- event.preventDefault()
2543
- const updatedAdditionaCategories = [...config.legend.additionalCategories]
2544
- updatedAdditionaCategories.splice(i, 1)
2545
- updateField('legend', null, 'additionalCategories', updatedAdditionaCategories)
2546
- }}
2547
- >
2548
- Remove
2549
- </button>
2550
- <TextField
2551
- value={val}
2552
- label='Category'
2553
- section='legend'
2554
- subsection={null}
2555
- fieldName='additionalCategories'
2556
- updateField={(section, subsection, fieldName, value) => {
2557
- const updatedAdditionaCategories = [...config.legend.additionalCategories]
2558
- updatedAdditionaCategories[i] = value
2559
- updateField(section, subsection, fieldName, updatedAdditionaCategories)
2560
- }}
2561
- />
2562
- </fieldset>
2563
- ))}
2564
- <button
2565
- className={'btn full-width'}
2566
- onClick={event => {
2567
- event.preventDefault()
2568
- const updatedAdditionaCategories = [...(config.legend.additionalCategories || [])]
2569
- updatedAdditionaCategories.push('')
2570
- updateField('legend', null, 'additionalCategories', updatedAdditionaCategories)
2571
- }}
2572
- >
2573
- Add Category
2574
- </button>
2575
- </fieldset>
2576
- )}
2401
+ <ColumnsEditor config={config} updateField={updateField} deleteColumn={removeAdditionalColumn} />{' '}
2577
2402
  </AccordionItemPanel>
2578
2403
  </AccordionItem>
2579
2404
  )}
@@ -2613,6 +2438,23 @@ const EditorPanel = () => {
2613
2438
  </Tooltip>
2614
2439
  }
2615
2440
  />
2441
+ <CheckBox
2442
+ value={config.legend.hideSuppressedLabels}
2443
+ section='legend'
2444
+ fieldName='hideSuppressedLabels'
2445
+ label='Hide Suppressed Labels'
2446
+ updateField={updateField}
2447
+ tooltip={
2448
+ <Tooltip style={{ textTransform: 'none' }}>
2449
+ <Tooltip.Target>
2450
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
2451
+ </Tooltip.Target>
2452
+ <Tooltip.Content>
2453
+ <p>Hiding suppressed labels will not override the 'Special Class' assigned to line chart indicating "suppressed" data in the Data Series Panel.</p>
2454
+ </Tooltip.Content>
2455
+ </Tooltip>
2456
+ }
2457
+ />
2616
2458
  {/* {config.visualizationType === 'Box Plot' &&
2617
2459
  <>
2618
2460
  <CheckBox value={config.boxplot.legend.displayHowToReadText} fieldName='displayHowToReadText' section='boxplot' subsection='legend' label='Display How To Read Text' updateField={updateField} />
@@ -2714,161 +2556,7 @@ const EditorPanel = () => {
2714
2556
  <AccordionItemButton>Filters</AccordionItemButton>
2715
2557
  </AccordionItemHeading>
2716
2558
  <AccordionItemPanel>
2717
- {config.filters && (
2718
- <>
2719
- {/* prettier-ignore */}
2720
- <Select
2721
- value={config.filterBehavior}
2722
- fieldName='filterBehavior'
2723
- label='Filter Behavior'
2724
- updateField={updateField}
2725
- options={['Apply Button', 'Filter Change']}
2726
- tooltip={
2727
- <Tooltip style={{ textTransform: 'none' }}>
2728
- <Tooltip.Target>
2729
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2730
- </Tooltip.Target>
2731
- <Tooltip.Content>
2732
- <p>The Apply Button option changes the visualization when the user clicks "apply". The Filter Change option immediately changes the visualization when the selection is changed.</p>
2733
- </Tooltip.Content>
2734
- </Tooltip>
2735
- }
2736
- />
2737
- <br />
2738
- </>
2739
- )}
2740
- {config.filters && (
2741
- <ul className='filters-list'>
2742
- {/* Whether filters should apply onChange or Apply Button */}
2743
-
2744
- {config.filters.map((filter, index) => {
2745
- if (filter.type === 'url') return <></>
2746
-
2747
- return (
2748
- <fieldset className='edit-block' key={index}>
2749
- <button
2750
- type='button'
2751
- className='remove-column'
2752
- onClick={() => {
2753
- removeFilter(index)
2754
- }}
2755
- >
2756
- Remove
2757
- </button>
2758
- <label>
2759
- <span className='edit-label column-heading'>Filter</span>
2760
- <select
2761
- value={filter.columnName}
2762
- onChange={e => {
2763
- updateFilterProp('columnName', index, e.target.value)
2764
- }}
2765
- >
2766
- <option value=''>- Select Option -</option>
2767
- {getFilters().map((dataKey, index) => (
2768
- <option value={dataKey} key={index}>
2769
- {dataKey}
2770
- </option>
2771
- ))}
2772
- </select>
2773
- </label>
2774
-
2775
- <label>
2776
- <span className='edit-showDropdown column-heading'>Show Filter Input</span>
2777
- <input
2778
- type='checkbox'
2779
- checked={filter.showDropdown === undefined ? true : filter.showDropdown}
2780
- onChange={e => {
2781
- updateFilterProp('showDropdown', index, e.target.checked)
2782
- }}
2783
- />
2784
- </label>
2785
-
2786
- <label>
2787
- <span className='edit-label column-heading'>Filter Style</span>
2788
-
2789
- <select
2790
- value={filter.filterStyle}
2791
- onChange={e => {
2792
- updateFilterProp('filterStyle', index, e.target.value)
2793
- }}
2794
- >
2795
- {filterStyleOptions.map((item, index) => {
2796
- return (
2797
- <option key={`filter-style-${index}`} value={item}>
2798
- {item}
2799
- </option>
2800
- )
2801
- })}
2802
- </select>
2803
- </label>
2804
- <label>
2805
- <span className='edit-label column-heading'>Label</span>
2806
- <input
2807
- type='text'
2808
- value={filter.label}
2809
- onChange={e => {
2810
- updateFilterProp('label', index, e.target.value)
2811
- }}
2812
- />
2813
- </label>
2814
-
2815
- <label>
2816
- <span className='edit-label column-heading'>Default Value Set By Query String Parameter</span>
2817
- <input
2818
- type='text'
2819
- value={filter.setByQueryParameter}
2820
- onChange={e => {
2821
- updateFilterProp('setByQueryParameter', index, e.target.value)
2822
- }}
2823
- />
2824
- </label>
2825
-
2826
- <label>
2827
- <span className='edit-filterOrder column-heading'>Filter Order</span>
2828
- <select value={filter.order ? filter.order : 'asc'} onChange={e => updateFilterProp('order', index, e.target.value)}>
2829
- {filterOrderOptions.map((option, index) => {
2830
- return (
2831
- <option value={option.value} key={`filter-${index}`}>
2832
- {option.label}
2833
- </option>
2834
- )
2835
- })}
2836
- </select>
2837
-
2838
- {filter.order === 'cust' && (
2839
- <DragDropContext onDragEnd={({ source, destination }) => handleFilterOrder(source.index, destination.index, index, config.filters[index])}>
2840
- <Droppable droppableId='filter_order'>
2841
- {provided => (
2842
- <ul {...provided.droppableProps} className='sort-list' ref={provided.innerRef} style={{ marginTop: '1em' }}>
2843
- {config.filters[index]?.values.map((value, index) => {
2844
- return (
2845
- <Draggable key={value} draggableId={`draggableFilter-${value}`} index={index}>
2846
- {(provided, snapshot) => (
2847
- <li>
2848
- <div className={snapshot.isDragging ? 'currently-dragging' : ''} style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)} ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
2849
- {value}
2850
- </div>
2851
- </li>
2852
- )}
2853
- </Draggable>
2854
- )
2855
- })}
2856
- {provided.placeholder}
2857
- </ul>
2858
- )}
2859
- </Droppable>
2860
- </DragDropContext>
2861
- )}
2862
- </label>
2863
- </fieldset>
2864
- )
2865
- })}
2866
- </ul>
2867
- )}
2868
- {!config.filters && <p style={{ textAlign: 'center' }}>There are currently no filters.</p>}
2869
- <button type='button' onClick={addNewFilter} className='btn full-width'>
2870
- Add Filter
2871
- </button>
2559
+ <VizFilterEditor config={config} updateField={updateField} rawData={rawData} />
2872
2560
  </AccordionItemPanel>
2873
2561
  </AccordionItem>
2874
2562
  )}