@cdc/chart 4.23.11 → 4.24.2

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 (104) hide show
  1. package/dist/cdcchart.js +35740 -35027
  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 +393 -0
  9. package/examples/feature/regions/index.json +9 -5
  10. package/examples/feature/scatterplot/scatterplot.json +272 -33
  11. package/index.html +10 -8
  12. package/package.json +2 -2
  13. package/src/CdcChart.tsx +70 -234
  14. package/src/ConfigContext.tsx +6 -0
  15. package/src/_stories/ChartEditor.stories.tsx +22 -0
  16. package/src/_stories/ChartLine.preliminary.tsx +19 -0
  17. package/src/_stories/_mock/pie_config.json +192 -0
  18. package/src/_stories/_mock/pie_data.json +218 -0
  19. package/src/_stories/_mock/preliminary_mock.json +346 -0
  20. package/src/components/{AreaChart.Stacked.jsx → AreaChart/components/AreaChart.Stacked.jsx} +2 -2
  21. package/src/components/{AreaChart.jsx → AreaChart/components/AreaChart.jsx} +2 -26
  22. package/src/components/AreaChart/index.tsx +4 -0
  23. package/src/components/{BarChart.Horizontal.tsx → BarChart/components/BarChart.Horizontal.tsx} +8 -8
  24. package/src/components/{BarChart.StackedHorizontal.tsx → BarChart/components/BarChart.StackedHorizontal.tsx} +37 -7
  25. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +108 -0
  26. package/src/components/{BarChart.Vertical.tsx → BarChart/components/BarChart.Vertical.tsx} +53 -70
  27. package/src/components/BarChart/components/BarChart.jsx +39 -0
  28. package/src/components/{BarChartType.jsx → BarChart/components/BarChartType.jsx} +0 -2
  29. package/src/components/BarChart/components/context.tsx +13 -0
  30. package/src/components/BarChart/index.tsx +3 -0
  31. package/src/components/{BoxPlot.jsx → BoxPlot/BoxPlot.jsx} +10 -9
  32. package/src/components/BoxPlot/index.tsx +3 -0
  33. package/src/components/EditorPanel/EditorPanel.tsx +2776 -0
  34. package/src/components/EditorPanel/EditorPanelContext.ts +40 -0
  35. package/src/components/EditorPanel/components/PanelProps.ts +3 -0
  36. package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +148 -0
  37. package/src/components/{ForestPlotSettings.jsx → EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx} +97 -167
  38. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +160 -0
  39. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +168 -0
  40. package/src/components/{Series.jsx → EditorPanel/components/Panels/Panel.Series.tsx} +4 -4
  41. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +297 -0
  42. package/src/components/EditorPanel/components/Panels/index.tsx +17 -0
  43. package/src/components/EditorPanel/components/panels.scss +72 -0
  44. package/src/components/EditorPanel/editor-panel.scss +739 -0
  45. package/src/components/EditorPanel/index.tsx +3 -0
  46. package/src/{hooks → components/EditorPanel}/useEditorPermissions.js +34 -2
  47. package/src/components/{Forecasting.jsx → Forecasting/Forecasting.jsx} +1 -1
  48. package/src/components/Forecasting/index.tsx +3 -0
  49. package/src/components/ForestPlot/ForestPlot.tsx +254 -0
  50. package/src/components/ForestPlot/ForestPlotProps.ts +7 -0
  51. package/src/components/ForestPlot/index.tsx +1 -209
  52. package/src/components/Legend/Legend.Component.tsx +199 -0
  53. package/src/components/Legend/Legend.tsx +28 -0
  54. package/src/components/Legend/helpers/createFormatLabels.tsx +140 -0
  55. package/src/components/Legend/index.tsx +3 -0
  56. package/src/components/LineChart/LineChartProps.ts +29 -0
  57. package/src/components/LineChart/components/LineChart.Circle.tsx +147 -0
  58. package/src/components/LineChart/helpers.ts +45 -0
  59. package/src/components/LineChart/index.tsx +111 -23
  60. package/src/components/LinearChart.jsx +55 -72
  61. package/src/components/PairedBarChart.jsx +4 -2
  62. package/src/components/{PieChart.jsx → PieChart/PieChart.tsx} +93 -31
  63. package/src/components/PieChart/index.tsx +3 -0
  64. package/src/components/Regions/components/Regions.tsx +144 -0
  65. package/src/components/Regions/index.tsx +3 -0
  66. package/src/components/{ScatterPlot.jsx → ScatterPlot/ScatterPlot.jsx} +3 -3
  67. package/src/components/ScatterPlot/index.tsx +3 -0
  68. package/src/components/{SparkLine.jsx → Sparkline/SparkLine.jsx} +2 -2
  69. package/src/components/Sparkline/index.tsx +3 -0
  70. package/src/data/initial-state.js +10 -8
  71. package/src/helpers/abbreviateNumber.ts +17 -0
  72. package/src/helpers/computeMarginBottom.ts +55 -0
  73. package/src/helpers/filterData.ts +18 -0
  74. package/src/helpers/generateColorsArray.ts +8 -0
  75. package/src/helpers/getQuartiles.ts +30 -0
  76. package/src/helpers/handleChartAriaLabels.ts +19 -0
  77. package/src/helpers/handleLineType.ts +18 -0
  78. package/src/helpers/lineOptions.ts +18 -0
  79. package/src/helpers/sort.ts +7 -0
  80. package/src/helpers/tests/computeMarginBottom.test.ts +20 -0
  81. package/src/hooks/useBarChart.js +7 -6
  82. package/src/hooks/useHighlightedBars.js +1 -1
  83. package/src/hooks/useMinMax.ts +3 -3
  84. package/src/hooks/useScales.ts +19 -6
  85. package/src/hooks/{useTooltip.jsx → useTooltip.tsx} +31 -25
  86. package/src/scss/main.scss +0 -3
  87. package/src/types/ChartConfig.ts +167 -23
  88. package/src/types/ChartContext.ts +34 -12
  89. package/src/types/ForestPlot.ts +7 -14
  90. package/src/types/Label.ts +7 -0
  91. package/examples/feature/scatterplot/scatterplot-continuous.csv +0 -17
  92. package/src/ConfigContext.jsx +0 -5
  93. package/src/components/BarChart.StackedVertical.tsx +0 -91
  94. package/src/components/BarChart.jsx +0 -30
  95. package/src/components/EditorPanel.jsx +0 -3356
  96. package/src/components/ForestPlot/Readme.md +0 -0
  97. package/src/components/Legend.jsx +0 -310
  98. package/src/components/LineChart/LineChart.Circle.tsx +0 -105
  99. package/src/scss/LinearChart.scss +0 -0
  100. package/src/scss/editor-panel.scss +0 -745
  101. package/src/scss/legend.scss +0 -206
  102. package/src/scss/mixins.scss +0 -0
  103. package/src/scss/variables.scss +0 -1
  104. package/src/types/ChartProps.ts +0 -7
@@ -0,0 +1,2776 @@
1
+ import { useState, useEffect, useCallback, memo, useContext } from 'react'
2
+ import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
3
+
4
+ import { Accordion, AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
5
+
6
+ // @cdc/core
7
+ import AdvancedEditor from '@cdc/core/components/AdvancedEditor'
8
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
9
+ import Icon from '@cdc/core/components/ui/Icon'
10
+ import InputToggle from '@cdc/core/components/inputs/InputToggle'
11
+ import Tooltip from '@cdc/core/components/ui/Tooltip'
12
+ import { Select, TextField, CheckBox } from '@cdc/core/components/EditorPanel/Inputs'
13
+
14
+ // chart components
15
+ import Panels from './components/Panels'
16
+ import Series from './components/Panel.Series.jsx'
17
+
18
+ // cdc additional
19
+ import { useColorPalette } from '../../hooks/useColorPalette'
20
+ import { useEditorPermissions } from './useEditorPermissions'
21
+ import { useFilters } from '@cdc/core/components/Filters'
22
+ import { useHighlightedBars } from '../../hooks/useHighlightedBars'
23
+ import ConfigContext from '../../ConfigContext'
24
+ import useReduceData from '../../hooks/useReduceData'
25
+ import useRightAxis from '../../hooks/useRightAxis'
26
+ import WarningImage from '../../images/warning.svg'
27
+ import useMinMax from '../../hooks/useMinMax'
28
+
29
+ import { type ChartConfig } from '../../types/ChartConfig'
30
+ import { type ChartContext } from '../../types/ChartContext'
31
+
32
+ import './editor-panel.scss'
33
+ import { Anchor } from '@cdc/core/types/Axis'
34
+ import DataTableEditor from '@cdc/core/components/EditorPanel/DataTableEditor'
35
+ import EditorPanelContext from './EditorPanelContext'
36
+
37
+ const DataSuppression = memo(({ config, updateConfig, data }: any) => {
38
+ const getColumnOptions = () => {
39
+ const keys = new Set()
40
+ data.forEach(d => {
41
+ Object.keys(d).forEach(key => {
42
+ keys.add(key)
43
+ })
44
+ })
45
+ return [...keys]
46
+ }
47
+
48
+ const getIconOptions = () => {
49
+ return ['star']
50
+ }
51
+
52
+ let removeColumn = i => {
53
+ let suppressedData = []
54
+
55
+ if (config.suppressedData) {
56
+ suppressedData = [...config.suppressedData]
57
+ }
58
+
59
+ suppressedData.splice(i, 1)
60
+
61
+ updateConfig({ ...config, suppressedData })
62
+ }
63
+
64
+ let addColumn = () => {
65
+ let suppressedData = config.suppressedData ? [...config.suppressedData] : []
66
+ suppressedData.push({ label: '', column: '', value: '', icon: '' })
67
+ updateConfig({ ...config, suppressedData })
68
+ }
69
+
70
+ let update = (fieldName, value, i) => {
71
+ let suppressedData = []
72
+
73
+ if (config.suppressedData) {
74
+ suppressedData = [...config.suppressedData]
75
+ }
76
+
77
+ suppressedData[i][fieldName] = value
78
+ updateConfig({ ...config, suppressedData })
79
+ }
80
+
81
+ return (
82
+ <>
83
+ {config.suppressedData &&
84
+ config.suppressedData.map(({ label, column, value, icon }, i) => {
85
+ return (
86
+ <div key={`suppressed-${i}`} className='edit-block'>
87
+ <button
88
+ type='button'
89
+ className='remove-column'
90
+ onClick={event => {
91
+ event.preventDefault()
92
+ removeColumn(i)
93
+ }}
94
+ >
95
+ Remove
96
+ </button>
97
+ <Select value={column} initial='Select' fieldName='column' label='Column' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getColumnOptions()} />
98
+ <TextField value={value} fieldName='value' label='Value' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} />
99
+ <Select value={icon} initial='Select' fieldName='icon' label='Icon' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getIconOptions()} />
100
+ <TextField value={label} fieldName='label' label='Label' placeholder='suppressed' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} />
101
+ </div>
102
+ )
103
+ })}
104
+
105
+ <button type='button' onClick={addColumn} className='btn full-width'>
106
+ Add Suppression Class
107
+ </button>
108
+ </>
109
+ )
110
+ })
111
+ const PreliminaryData = memo(({ config, updateConfig, data }) => {
112
+ const getColumnOptions = () => {
113
+ const keys = new Set()
114
+ data.forEach(d => {
115
+ Object.keys(d).forEach(key => {
116
+ keys.add(key)
117
+ })
118
+ })
119
+ return [...keys]
120
+ }
121
+
122
+ const getTypeOptions = () => {
123
+ if (config.visualizationType === 'Line' || config.visualizationType === 'Combo') {
124
+ return ['effect']
125
+ } else {
126
+ return ['suppression']
127
+ }
128
+ }
129
+
130
+ const getStyleOptions = () => {
131
+ if (config.visualizationType === 'Line' || config.visualizationType === 'Combo') {
132
+ return ['Dashed Small', 'Dashed Medium', 'Dashed Large', 'Open Circles']
133
+ }
134
+ if (config.visualizationType === 'Bar') {
135
+ return ['star']
136
+ }
137
+ }
138
+
139
+ let removeColumn = i => {
140
+ let preliminaryData = []
141
+
142
+ if (config.preliminaryData) {
143
+ preliminaryData = [...config.preliminaryData]
144
+ }
145
+
146
+ preliminaryData.splice(i, 1)
147
+
148
+ updateConfig({ ...config, preliminaryData })
149
+ }
150
+
151
+ let addColumn = () => {
152
+ let preliminaryData = config.preliminaryData ? [...config.preliminaryData] : []
153
+ preliminaryData.push({ type: '', label: '', column: '', value: '', style: '' })
154
+ updateConfig({ ...config, preliminaryData })
155
+ }
156
+
157
+ let update = (fieldName, value, i) => {
158
+ let preliminaryData = []
159
+
160
+ if (config.preliminaryData) {
161
+ preliminaryData = [...config.preliminaryData]
162
+ }
163
+
164
+ preliminaryData[i][fieldName] = value
165
+ updateConfig({ ...config, preliminaryData })
166
+ }
167
+
168
+ return (
169
+ <>
170
+ {config.preliminaryData &&
171
+ config.preliminaryData.map(({ seriesKey, type, label, column, value, style }, i) => {
172
+ return (
173
+ <div key={`preliminaryData-${i}`} className='edit-block'>
174
+ <button
175
+ type='button'
176
+ className='remove-column'
177
+ onClick={event => {
178
+ event.preventDefault()
179
+ removeColumn(i)
180
+ }}
181
+ >
182
+ Remove
183
+ </button>
184
+ <Select value={type} initial='Select' fieldName='type' label='Type' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getTypeOptions()} />
185
+ <Select value={seriesKey} initial='Select' fieldName='seriesKey' label='ASSOCIATE TO SERIES' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={config.runtime?.seriesKeys} />
186
+ <Select value={column} initial='Select' fieldName='column' label='COLUMN WITH CONFIGURATION VALUE' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getColumnOptions()} />
187
+ <TextField value={value} fieldName='value' label='VALUE TO TRIGGER' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} />
188
+ <Select value={style} initial='Select' fieldName='style' label='Style' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} options={getStyleOptions()} />
189
+
190
+ <TextField value={label} fieldName='label' label='Label' placeholder='' updateField={(section, subsection, fieldName, value) => update(fieldName, value, i)} />
191
+ </div>
192
+ )
193
+ })}
194
+
195
+ <button type='button' onClick={addColumn} className='btn full-width'>
196
+ {config.visualizationType === 'Line' ? 'Add Special Line' : config.visualizationType === 'Bar' ? ' Add Special Bar' : 'Add Special Line/Bar'}
197
+ </button>
198
+ </>
199
+ )
200
+ })
201
+
202
+ const EditorPanel = () => {
203
+ const { config, updateConfig, transformedData: data, loading, colorPalettes, twoColorPalette, unfilteredData, excludedData, isDashboard, setParentConfig, missingRequiredSections, isDebug, setFilteredData, lineOptions, rawData } = useContext<ChartContext>(ConfigContext)
204
+
205
+ const { minValue, maxValue, existPositiveValue, isAllLine } = useReduceData(config, unfilteredData)
206
+
207
+ const { twoColorPalettes, sequential, nonSequential } = useColorPalette(config, updateConfig)
208
+
209
+ const properties = { data, config }
210
+ const { leftMax, rightMax } = useMinMax(properties)
211
+
212
+ const {
213
+ headerColors,
214
+ visSupportsTooltipLines,
215
+ visSupportsNonSequentialPallete,
216
+ visSupportsSequentialPallete,
217
+ visSupportsReverseColorPalette,
218
+ visHasLabelOnData,
219
+ visHasNumbersOnBars,
220
+ visHasAnchors,
221
+ visHasBarBorders,
222
+ visHasDataCutoff,
223
+ visCanAnimate,
224
+ visHasLegend,
225
+ visHasBrushChart,
226
+ visSupportsDateCategoryAxis,
227
+ visSupportsValueAxisMin,
228
+ visSupportsValueAxisMax,
229
+ visSupportsDateCategoryAxisLabel,
230
+ visSupportsDateCategoryAxisLine,
231
+ visSupportsDateCategoryAxisTicks,
232
+ visSupportsDateCategoryTickRotation,
233
+ visSupportsDateCategoryNumTicks,
234
+ visSupportsDateCategoryAxisPadding,
235
+ visSupportsRegions,
236
+ visSupportsFilters,
237
+ visSupportsValueAxisGridLines,
238
+ visSupportsValueAxisLine,
239
+ visSupportsValueAxisTicks,
240
+ visSupportsValueAxisLabels,
241
+ visSupportsBarSpace,
242
+ visSupportsBarThickness,
243
+ visSupportsFootnotes,
244
+ visSupportsSuperTitle,
245
+ visSupportsDataCutoff,
246
+ visSupportsChartHeight,
247
+ visSupportsLeftValueAxis,
248
+ visSupportsTooltipOpacity,
249
+ visSupportsRankByValue,
250
+ visSupportsResponsiveTicks,
251
+ visSupportsDateCategoryHeight,
252
+ visHasDataSuppression
253
+ } = useEditorPermissions()
254
+
255
+ // argument acts as props
256
+ const { handleFilterOrder, filterOrderOptions, filterStyleOptions } = useFilters({ config, setConfig: updateConfig, filteredData: data, setFilteredData })
257
+
258
+ // when the visualization type changes we
259
+ // have to update the individual series type & axis details
260
+ // dataKey is unchanged here.
261
+ // ie. { dataKey: 'series_name', type: 'Bar', axis: 'Left'}
262
+ useEffect(() => {
263
+ let newSeries = []
264
+ if (config.series) {
265
+ newSeries = config.series.map(series => {
266
+ return {
267
+ ...series,
268
+ type: config.visualizationType === 'Combo' ? 'Bar' : config.visualizationType ? config.visualizationType : 'Bar',
269
+ axis: 'Left'
270
+ }
271
+ })
272
+ }
273
+
274
+ updateConfig({
275
+ ...config,
276
+ series: newSeries
277
+ })
278
+ }, [config.visualizationType]) // eslint-disable-line
279
+
280
+ // Scatter Plots default date/category axis is 'continuous'
281
+ useEffect(() => {
282
+ if (config.visualizationType === 'Scatter Plot') {
283
+ updateConfig({
284
+ ...config,
285
+ xAxis: {
286
+ ...config.xAxis,
287
+ type: 'continuous'
288
+ }
289
+ })
290
+ }
291
+ }, [])
292
+
293
+ useEffect(() => {
294
+ if (config.visualizationType !== 'Bar') {
295
+ updateConfig({ ...config, tooltips: { ...config.tooltips, singleSeries: false } })
296
+ }
297
+ }, [config.visualizationType])
298
+
299
+ const { hasRightAxis } = useRightAxis({ config: config, yMax: config.yAxis.size, data: config.data, updateConfig })
300
+
301
+ const getItemStyle = (isDragging, draggableStyle) => ({
302
+ ...draggableStyle
303
+ })
304
+
305
+ const sortableItemStyles = {
306
+ animate: false,
307
+ animateReplay: true,
308
+ display: 'block',
309
+ boxSizing: 'border-box',
310
+ border: '1px solid #D1D1D1',
311
+ borderRadius: '2px',
312
+ background: '#F1F1F1',
313
+ padding: '.4em .6em',
314
+ fontSize: '.8em',
315
+ marginRight: '.3em',
316
+ marginBottom: '.3em',
317
+ cursor: 'move',
318
+ zIndex: '999'
319
+ }
320
+
321
+ const enforceRestrictions = updatedConfig => {
322
+ if (updatedConfig.orientation === 'horizontal') {
323
+ updatedConfig.labels = false
324
+ }
325
+ if (updatedConfig.table.show === undefined) {
326
+ updatedConfig.table.show = !isDashboard
327
+ }
328
+ // DEV-3293 - Force combo to always be vertical
329
+ if (updatedConfig.visualizationType === 'Combo') {
330
+ updatedConfig.orientation = 'vertical'
331
+ }
332
+ if (updatedConfig.xAxis.sortDates && !updatedConfig.xAxis.padding) {
333
+ updatedConfig.xAxis.padding = 6
334
+ }
335
+ }
336
+
337
+ const updateField = (section, subsection, fieldName, newValue) => {
338
+ if (isDebug) console.log('#COVE: CHART: EditorPanel: section, subsection, fieldName, newValue', section, subsection, fieldName, newValue) // eslint-disable-line
339
+
340
+ if (section === 'boxplot' && subsection === 'legend') {
341
+ updateConfig({
342
+ ...config,
343
+ [section]: {
344
+ ...config[section],
345
+ [subsection]: {
346
+ ...config.boxplot[subsection],
347
+ [fieldName]: newValue
348
+ }
349
+ }
350
+ })
351
+ return
352
+ }
353
+
354
+ if (section === 'boxplot' && subsection === 'labels') {
355
+ updateConfig({
356
+ ...config,
357
+ [section]: {
358
+ ...config[section],
359
+ [subsection]: {
360
+ ...config.boxplot[subsection],
361
+ [fieldName]: newValue
362
+ }
363
+ }
364
+ })
365
+ return
366
+ }
367
+
368
+ if (section === 'columns' && subsection !== '' && fieldName !== '') {
369
+ updateConfig({
370
+ ...config,
371
+ [section]: {
372
+ ...config[section],
373
+ [subsection]: {
374
+ ...config[section][subsection],
375
+ [fieldName]: newValue
376
+ }
377
+ }
378
+ })
379
+ return
380
+ }
381
+ if (null === section && null === subsection) {
382
+ let updatedConfig = { ...config, [fieldName]: newValue }
383
+ enforceRestrictions(updatedConfig)
384
+ updateConfig(updatedConfig)
385
+ return
386
+ }
387
+
388
+ const isArray = Array.isArray(config[section])
389
+
390
+ let sectionValue = isArray ? [...config[section], newValue] : { ...config[section], [fieldName]: newValue }
391
+
392
+ if (null !== subsection) {
393
+ if (isArray) {
394
+ sectionValue = [...config[section]]
395
+ sectionValue[subsection] = { ...sectionValue[subsection], [fieldName]: newValue }
396
+ } else if (typeof newValue === 'string') {
397
+ sectionValue[subsection] = newValue
398
+ } else {
399
+ sectionValue = { ...config[section], [subsection]: { ...config[section][subsection], [fieldName]: newValue } }
400
+ }
401
+ }
402
+
403
+ let updatedConfig = { ...config, [section]: sectionValue }
404
+
405
+ enforceRestrictions(updatedConfig)
406
+
407
+ updateConfig(updatedConfig)
408
+ }
409
+
410
+ const [displayPanel, setDisplayPanel] = useState(true)
411
+
412
+ if (loading) {
413
+ return null
414
+ }
415
+
416
+ useEffect(() => {
417
+ if (!config.general?.boxplot) return
418
+ if (!config.general.boxplot.firstQuartilePercentage) {
419
+ updateConfig({
420
+ ...config,
421
+ boxplot: {
422
+ ...config.boxplot,
423
+ firstQuartilePercentage: 25
424
+ }
425
+ })
426
+ }
427
+ }, [config])
428
+
429
+ const setLollipopShape = shape => {
430
+ updateConfig({
431
+ ...config,
432
+ lollipopShape: shape
433
+ })
434
+ }
435
+
436
+ const removeFilter = index => {
437
+ let filters = [...config.filters]
438
+
439
+ filters.splice(index, 1)
440
+
441
+ updateConfig({ ...config, filters })
442
+ }
443
+
444
+ const updateFilterProp = (name, index, value) => {
445
+ let filters = [...config.filters]
446
+
447
+ filters[index][name] = value
448
+
449
+ updateConfig({ ...config, filters })
450
+ }
451
+
452
+ const addNewFilter = () => {
453
+ let filters = config.filters ? [...config.filters] : []
454
+
455
+ filters.push({ values: [] })
456
+
457
+ updateConfig({ ...config, filters })
458
+ }
459
+
460
+ const addNewSeries = seriesKey => {
461
+ let newSeries = config.series ? [...config.series] : []
462
+ let forecastingStages = Array.from(new Set(data.map(item => item[seriesKey])))
463
+ let forecastingStageArr = []
464
+
465
+ forecastingStages.forEach(stage => {
466
+ forecastingStageArr.push({ key: stage })
467
+ })
468
+
469
+ if (config.visualizationType === 'Forecasting') {
470
+ newSeries.push({ dataKey: seriesKey, type: config.visualizationType, stages: forecastingStageArr, stageColumn: seriesKey, axis: 'Left', tooltip: true })
471
+ } else {
472
+ newSeries.push({ dataKey: seriesKey, type: config.visualizationType, axis: 'Left', tooltip: true })
473
+ }
474
+ updateConfig({ ...config, series: newSeries }) // left axis series keys
475
+ }
476
+
477
+ const sortSeries = e => {
478
+ const series = config.series[0].dataKey
479
+ const sorted = data.sort((a, b) => a[series] - b[series])
480
+ const newData = e === 'asc' ? sorted : sorted.reverse()
481
+ updateConfig({ ...config }, newData)
482
+ }
483
+
484
+ const removeSeries = seriesKey => {
485
+ let series = [...config.series]
486
+ let seriesIndex = -1
487
+
488
+ for (let i = 0; i < series.length; i++) {
489
+ if (series[i].dataKey === seriesKey) {
490
+ seriesIndex = i
491
+ break
492
+ }
493
+ }
494
+
495
+ if (seriesIndex !== -1) {
496
+ series.splice(seriesIndex, 1)
497
+
498
+ let newConfig = { ...config, series }
499
+
500
+ if (series.length === 0) {
501
+ delete newConfig.series
502
+ }
503
+
504
+ updateConfig(newConfig)
505
+ }
506
+
507
+ if (config.visualizationType === 'Paired Bar') {
508
+ updateConfig({
509
+ ...config,
510
+ series: []
511
+ })
512
+ }
513
+ }
514
+
515
+ const addNewExclusion = exclusionKey => {
516
+ let newExclusion = [...config.exclusions.keys]
517
+ newExclusion.push(exclusionKey)
518
+
519
+ let payload = { ...config.exclusions, keys: newExclusion }
520
+ updateConfig({ ...config, exclusions: payload })
521
+ }
522
+
523
+ const removeExclusion = excludeValue => {
524
+ let exclusionsIndex = -1
525
+ let exclusions = [...config.exclusions.keys]
526
+
527
+ for (let i = 0; i < exclusions.length; i++) {
528
+ if (exclusions[i] === excludeValue) {
529
+ exclusionsIndex = i
530
+ break
531
+ }
532
+ }
533
+
534
+ if (exclusionsIndex !== -1) {
535
+ exclusions.splice(exclusionsIndex, 1)
536
+
537
+ let newExclusions = { ...config.exclusions, keys: exclusions }
538
+ let newExclusionsPayload = { ...config, exclusions: newExclusions }
539
+
540
+ if (exclusions.length === 0) {
541
+ delete newExclusionsPayload.exclusions.keys
542
+ }
543
+
544
+ updateConfig(newExclusionsPayload)
545
+ }
546
+ }
547
+
548
+ const getFilters = () => {
549
+ let columns = {}
550
+
551
+ unfilteredData.forEach(row => {
552
+ Object.keys(row).forEach(columnName => (columns[columnName] = true))
553
+ })
554
+
555
+ return Object.keys(columns)
556
+ }
557
+
558
+ const getColumns = (filter = true) => {
559
+ let columns = {}
560
+ unfilteredData.forEach(row => {
561
+ Object.keys(row).forEach(columnName => (columns[columnName] = true))
562
+ })
563
+
564
+ if (filter) {
565
+ const { lower, upper } = config.confidenceKeys || {}
566
+ Object.keys(columns).forEach(key => {
567
+ if ((config.series && config.series.filter(series => series.dataKey === key).length > 0) || (config.confidenceKeys && Object.keys(config.confidenceKeys).includes(key) && ((lower && upper) || lower || upper) && key !== lower && key !== upper)) {
568
+ delete columns[key]
569
+ }
570
+ })
571
+ }
572
+
573
+ return Object.keys(columns)
574
+ }
575
+
576
+ const getDataValueOptions = data => {
577
+ if (!data) return []
578
+ const set = new Set()
579
+ for (let i = 0; i < data.length; i++) {
580
+ for (const [key] of Object.entries(data[i])) {
581
+ set.add(key)
582
+ }
583
+ }
584
+ return Array.from(set)
585
+ }
586
+
587
+ const getDataValues = (dataKey, unique = false) => {
588
+ let values = []
589
+ excludedData.forEach(e => {
590
+ values.push(e[dataKey])
591
+ })
592
+ return unique ? [...new Set(values)] : values
593
+ }
594
+
595
+ const onBackClick = () => {
596
+ setDisplayPanel(!displayPanel)
597
+ }
598
+
599
+ const Error = () => {
600
+ return (
601
+ <section className='waiting'>
602
+ <section className='waiting-container'>
603
+ <h3>Error With Configuration</h3>
604
+ <p>{config.runtime.editorErrorMessage}</p>
605
+ </section>
606
+ </section>
607
+ )
608
+ }
609
+
610
+ const Confirm = () => {
611
+ const confirmDone = e => {
612
+ e.preventDefault()
613
+
614
+ let newConfig = { ...config }
615
+ delete newConfig.newViz
616
+
617
+ updateConfig(newConfig)
618
+ }
619
+
620
+ return (
621
+ <section className='waiting'>
622
+ <section className='waiting-container'>
623
+ <h3>Finish Configuring</h3>
624
+ <p>Set all required options to the left and confirm below to display a preview of the chart.</p>
625
+ <button className='btn' style={{ margin: '1em auto' }} disabled={missingRequiredSections()} onClick={confirmDone}>
626
+ I'm Done
627
+ </button>
628
+ </section>
629
+ </section>
630
+ )
631
+ }
632
+
633
+ const convertStateToConfig = () => {
634
+ let strippedState = JSON.parse(JSON.stringify(config))
635
+ if (false === missingRequiredSections()) {
636
+ delete strippedState.newViz
637
+ }
638
+ delete strippedState.runtime
639
+
640
+ return strippedState
641
+ }
642
+
643
+ useEffect(() => {
644
+ // Pass up to Editor if needed
645
+ if (setParentConfig) {
646
+ const newConfig = convertStateToConfig()
647
+ setParentConfig(newConfig)
648
+ }
649
+
650
+ // eslint-disable-next-line react-hooks/exhaustive-deps
651
+ }, [config])
652
+
653
+ // when the orientation changes, swap x and y axis anchors
654
+ useEffect(() => {
655
+ const prevXAnchors = config.xAxis.anchors.length > 0 ? config.xAxis.anchors : []
656
+ const prevYAnchors = config.yAxis.anchors.length > 0 ? config.yAxis.anchors : []
657
+
658
+ updateConfig({
659
+ ...config,
660
+ xAxis: {
661
+ ...config.xAxis,
662
+ anchors: prevYAnchors
663
+ },
664
+ yAxis: {
665
+ ...config.yAxis,
666
+ anchors: prevXAnchors
667
+ }
668
+ })
669
+ }, [config.orientation])
670
+
671
+ // Set paired bars to be horizontal, even though that option doesn't display
672
+ useEffect(() => {
673
+ if (config.visualizationType === 'Paired Bar') {
674
+ updateConfig({
675
+ ...config,
676
+ orientation: 'horizontal'
677
+ })
678
+ }
679
+ }, []) // eslint-disable-line
680
+
681
+ useEffect(() => {
682
+ if (config.orientation === 'horizontal') {
683
+ updateConfig({
684
+ ...config,
685
+ lollipopShape: config.lollipopShape
686
+ })
687
+ }
688
+ }, [config.isLollipopChart, config.lollipopShape]) // eslint-disable-line
689
+
690
+ /// temporary force orientation untill we support Vartical deviaton bar
691
+ useEffect(() => {
692
+ if (config.visualizationType === 'Deviation Bar') {
693
+ updateConfig({ ...config, orientation: 'horizontal' })
694
+ }
695
+ }, [config.visualizationType])
696
+
697
+ const ExclusionsList = useCallback(() => {
698
+ const exclusions = [...config.exclusions.keys]
699
+ return (
700
+ <ul className='series-list'>
701
+ {exclusions.map((exclusion, index) => {
702
+ return (
703
+ <li key={exclusion}>
704
+ <div className='series-list__name' data-title={exclusion}>
705
+ <div className='series-list__name--text'>{exclusion}</div>
706
+ </div>
707
+ <button className='series-list__remove' onClick={() => removeExclusion(exclusion)}>
708
+ &#215;
709
+ </button>
710
+ </li>
711
+ )
712
+ })}
713
+ </ul>
714
+ )
715
+ }, [config]) // eslint-disable-line
716
+
717
+ const handleSeriesChange = (idx1, idx2) => {
718
+ let seriesOrder = config.series
719
+ let [movedItem] = seriesOrder.splice(idx1, 1)
720
+ seriesOrder.splice(idx2, 0, movedItem)
721
+ updateConfig({ ...config, series: seriesOrder })
722
+ }
723
+
724
+ if (config.isLollipopChart && config?.series?.length > 1) {
725
+ config.runtime.editorErrorMessage = 'Lollipop charts must use only one data series'
726
+ }
727
+ if (config.visualizationType === 'Paired Bar' && config?.series?.length !== 2) {
728
+ config.runtime.editorErrorMessage = 'Paired Bar charts must use exactly two data series'
729
+ }
730
+
731
+ if (config.visualizationType === 'Deviation Bar' && config?.series?.length !== 1) {
732
+ config.runtime.editorErrorMessage = 'Deviation Bar charts must use exactly one data series'
733
+ }
734
+ if (config.isLollipopChart && config?.series?.length === 0) {
735
+ config.runtime.editorErrorMessage = 'Add a data series'
736
+ }
737
+
738
+ const section = config.orientation === 'horizontal' ? 'xAxis' : 'yAxis'
739
+ const [warningMsg, setWarningMsg] = useState({ maxMsg: '', minMsg: '', rightMaxMessage: '', minMsgRight: '' })
740
+
741
+ const validateMaxValue = () => {
742
+ const enteredValue = config[section].max
743
+ const enteredRightMax = config[section].rightMax
744
+
745
+ let message = ''
746
+ let rightMaxMessage = ''
747
+
748
+ if (config.visualizationType !== 'Combo') {
749
+ switch (true) {
750
+ case enteredValue && parseFloat(enteredValue) < parseFloat(maxValue) && existPositiveValue:
751
+ message = 'Max value must be more than ' + maxValue
752
+ break
753
+ case enteredValue && parseFloat(enteredValue) < 0 && !existPositiveValue:
754
+ message = 'Value must be more than or equal to 0'
755
+ break
756
+ default:
757
+ message = ''
758
+ }
759
+ }
760
+
761
+ if (config.visualizationType === 'Combo') {
762
+ switch (true) {
763
+ case enteredValue && parseFloat(enteredValue) < leftMax:
764
+ message = 'Max value must be more than ' + leftMax
765
+ break
766
+ case enteredRightMax && parseFloat(enteredRightMax) < rightMax:
767
+ rightMaxMessage = 'Max value must be more than ' + rightMax
768
+ break
769
+ case enteredValue && parseFloat(enteredValue) < 0 && !existPositiveValue:
770
+ message = 'Value must be more than or equal to 0'
771
+ break
772
+ default:
773
+ message = ''
774
+ }
775
+ }
776
+
777
+ setWarningMsg(prevMsg => ({ ...prevMsg, maxMsg: message, rightMaxMessage: rightMaxMessage }))
778
+ }
779
+
780
+ const validateMinValue = () => {
781
+ const enteredValue = parseFloat(config[section].min)
782
+ let minVal = Number(minValue)
783
+ let message = ''
784
+
785
+ switch (true) {
786
+ case config.useLogScale && ['Line', 'Combo', 'Bar'].includes(config.visualizationType) && enteredValue < 0:
787
+ message = 'Negative numbers are not supported in logarithmic scale'
788
+ break
789
+ case (config.visualizationType === 'Line' || config.visualizationType === 'Spark Line') && enteredValue > minVal:
790
+ message = 'Value should not exceed ' + minValue
791
+ break
792
+ case config.visualizationType === 'Combo' && isAllLine && enteredValue > minVal:
793
+ message = 'Value should not exceed ' + minValue
794
+ break
795
+ case (config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && !isAllLine)) && minVal > 0 && enteredValue > 0:
796
+ message = config.useLogScale ? 'Value must be equal to 0' : 'Value must be less than or equal to 0'
797
+ break
798
+ case config.visualizationType === 'Deviation Bar' && enteredValue >= Math.min(minVal, config.xAxis.target):
799
+ message = 'Value must be less than ' + Math.min(minVal, config.xAxis.target)
800
+ break
801
+ case config.visualizationType !== 'Deviation Bar' && enteredValue && minVal < 0 && enteredValue > minVal:
802
+ message = 'Value should not exceed ' + minValue
803
+ break
804
+ default:
805
+ message = ''
806
+ }
807
+ setWarningMsg(prevMsg => ({ ...prevMsg, minMsg: message }))
808
+ }
809
+ useEffect(() => {
810
+ validateMinValue()
811
+ validateMaxValue()
812
+ }, [minValue, maxValue, config]) // eslint-disable-line
813
+
814
+ const isLoadedFromUrl = config?.dataKey?.includes('http://') || config?.dataKey?.includes('https://')
815
+
816
+ // if isDebug = true, then try to set the category and data col to reduce clicking
817
+ const setCategoryAxis = () => {
818
+ // only for debug mode
819
+ if (undefined !== isDebug && isDebug && !config?.xAxis?.dataKey) {
820
+ // then try to set the x axis to appropriate value so we dont have to manually do it
821
+ let datakeys = getColumns(false)
822
+ if (datakeys.includes('Date')) return 'Date'
823
+ if (datakeys.includes('Race')) return 'Race'
824
+ if (datakeys.includes('Month')) return 'Month'
825
+ // add other known Category cols here to extend debug
826
+ }
827
+ return config?.xAxis?.dataKey || ''
828
+ }
829
+ const setDataColumn = () => {
830
+ // only for debug mode
831
+ if (undefined !== isDebug && isDebug && getColumns(false).length > 0) {
832
+ // then try to set the x axis to appropriate value so we dont have to manually do it
833
+ let datacols = getColumns(false).filter(x => x !== setCategoryAxis())
834
+ if (datacols.length > 0) {
835
+ return datacols[0]
836
+ }
837
+ }
838
+ return ''
839
+ }
840
+ if (isDebug && !config.xAxis.dataKey) config.xAxis.dataKey = setCategoryAxis()
841
+ if (isDebug && config?.series?.length === 0) {
842
+ let setdatacol = setDataColumn()
843
+ if (setdatacol !== '') addNewSeries(setdatacol)
844
+ if (isDebug) console.log('### COVE DEBUG: Chart: Setting default datacol=', setdatacol) // eslint-disable-line
845
+ }
846
+
847
+ const chartsWithOptions = ['Area Chart', 'Combo', 'Line', 'Bar', 'Forecasting', 'Scatter Plot', 'Paired Bar', 'Deviation Bar']
848
+
849
+ const columnsOptions = [
850
+ <option value='' key={'Select Option'}>
851
+ - Select Option -
852
+ </option>
853
+ ]
854
+
855
+ if (config.data && config.series) {
856
+ Object.keys(config.data?.[0] || []).map(colName => {
857
+ // OMIT ANY COLUMNS THAT ARE IN DATA SERIES!
858
+ const found = config?.series.some(series => series.dataKey === colName)
859
+ if (colName !== config.xAxis.dataKey && !found) {
860
+ // if not the index then add it
861
+ return columnsOptions.push(
862
+ <option value={colName} key={colName}>
863
+ {colName}
864
+ </option>
865
+ )
866
+ }
867
+ })
868
+
869
+ let columnsByKey = {}
870
+ config.data.forEach(datum => {
871
+ Object.keys(datum).forEach(key => {
872
+ columnsByKey[key] = columnsByKey[key] || []
873
+ const value = typeof datum[key] === 'number' ? datum[key].toString() : datum[key]
874
+
875
+ if (columnsByKey[key].indexOf(value) === -1) {
876
+ columnsByKey[key].push(value)
877
+ }
878
+ })
879
+ })
880
+ }
881
+
882
+ // for pie charts
883
+ if (!config.data && data) {
884
+ if (!data[0]) return
885
+ Object.keys(data[0]).map(colName => {
886
+ // OMIT ANY COLUMNS THAT ARE IN DATA SERIES!
887
+ const found = data.some(el => el.dataKey === colName)
888
+ if (colName !== config.xAxis.dataKey && !found) {
889
+ // if not the index then add it
890
+ return columnsOptions.push(
891
+ <option value={colName} key={colName}>
892
+ {colName}
893
+ </option>
894
+ )
895
+ }
896
+ })
897
+
898
+ let columnsByKey = {}
899
+ data.forEach(datum => {
900
+ Object.keys(datum).forEach(key => {
901
+ columnsByKey[key] = columnsByKey[key] || []
902
+ const value = typeof datum[key] === 'number' ? datum[key].toString() : datum[key]
903
+
904
+ if (columnsByKey[key].indexOf(value) === -1) {
905
+ columnsByKey[key].push(value)
906
+ }
907
+ })
908
+ })
909
+ }
910
+
911
+ // prevents adding duplicates
912
+ const additionalColumns = Object.keys(config.columns).filter(value => {
913
+ const defaultCols = [config.xAxis.dataKey] // ['geo', 'navigate', 'primary', 'latitude', 'longitude']
914
+
915
+ if (true === defaultCols.includes(value)) {
916
+ return false
917
+ }
918
+ return true
919
+ })
920
+
921
+ // just adds a new column but not set to any data yet
922
+ const addAdditionalColumn = number => {
923
+ const columnKey = `additionalColumn${number}`
924
+
925
+ updateConfig({
926
+ ...config,
927
+ columns: {
928
+ ...config.columns,
929
+ [columnKey]: {
930
+ label: 'New Column',
931
+ dataTable: false,
932
+ tooltips: false,
933
+ prefix: '',
934
+ suffix: '',
935
+ forestPlot: false,
936
+ startingPoint: '0',
937
+ forestPlotAlignRight: false
938
+ }
939
+ }
940
+ })
941
+ }
942
+
943
+ const removeAdditionalColumn = columnName => {
944
+ const newColumns = config.columns
945
+
946
+ delete newColumns[columnName]
947
+
948
+ updateConfig({
949
+ ...config,
950
+ columns: newColumns
951
+ })
952
+ }
953
+
954
+ const editColumn = async (addCol, columnName, setval) => {
955
+ // not using special classes like in map editorpanel so removed those cases
956
+ switch (columnName) {
957
+ case 'name':
958
+ updateConfig({
959
+ ...config,
960
+ columns: {
961
+ ...config.columns,
962
+ [addCol]: {
963
+ ...config.columns[addCol],
964
+ [columnName]: setval
965
+ }
966
+ }
967
+ })
968
+ break
969
+ default:
970
+ updateConfig({
971
+ ...config,
972
+ columns: {
973
+ ...config.columns,
974
+ [addCol]: {
975
+ ...config.columns[addCol],
976
+ [columnName]: setval
977
+ }
978
+ }
979
+ })
980
+ break
981
+ }
982
+ }
983
+
984
+ // prettier-ignore
985
+ const {
986
+ highlightedBarValues,
987
+ highlightedSeriesValues,
988
+ handleUpdateHighlightedBar,
989
+ handleAddNewHighlightedBar,
990
+ handleRemoveHighlightedBar,
991
+ handleUpdateHighlightedBarColor,
992
+ handleHighlightedBarLegendLabel,
993
+ handleUpdateHighlightedBorderWidth
994
+ } = useHighlightedBars(config, updateConfig)
995
+
996
+ const updateSeriesTooltip = (column, event) => {
997
+ let updatedColumns = config.columns
998
+
999
+ updatedColumns[column].tooltips = event
1000
+
1001
+ updateConfig({
1002
+ ...config,
1003
+ columns: updatedColumns
1004
+ })
1005
+ }
1006
+ const editorContextValues = {
1007
+ addNewExclusion,
1008
+ data,
1009
+ editColumn,
1010
+ getColumns,
1011
+ getDataValueOptions,
1012
+ getDataValues,
1013
+ getItemStyle,
1014
+ handleSeriesChange,
1015
+ handleAddNewHighlightedBar,
1016
+ setCategoryAxis,
1017
+ sortSeries,
1018
+ updateField,
1019
+ warningMsg,
1020
+ highlightedBarValues,
1021
+ handleHighlightedBarLegendLabel,
1022
+ handleUpdateHighlightedBar,
1023
+ handleRemoveHighlightedBar,
1024
+ isPaletteReversed: config.isPaletteReversed,
1025
+ highlightedSeriesValues,
1026
+ handleUpdateHighlightedBorderWidth,
1027
+ handleUpdateHighlightedBarColor,
1028
+ setLollipopShape
1029
+ }
1030
+
1031
+ return (
1032
+ <EditorPanelContext.Provider value={editorContextValues}>
1033
+ <ErrorBoundary component='EditorPanel'>
1034
+ {config.newViz && <Confirm />}
1035
+ {undefined === config.newViz && config.runtime && config.runtime?.editorErrorMessage && <Error />}
1036
+ <button className={displayPanel ? `editor-toggle` : `editor-toggle collapsed`} title={displayPanel ? `Collapse Editor` : `Expand Editor`} onClick={onBackClick}></button>
1037
+ <section className={`${displayPanel ? 'editor-panel cove' : 'hidden editor-panel cove'}${isDashboard ? ' dashboard' : ''}`}>
1038
+ <div aria-level={2} role='heading' className='heading-2'>
1039
+ Configure Chart
1040
+ </div>
1041
+ <section className='form-container'>
1042
+ <Accordion allowZeroExpanded={true}>
1043
+ <Panels.General name='General' />
1044
+ <Panels.ForestPlot name='Forest Plot Settings' />
1045
+ {config.visualizationType !== 'Pie' && config.visualizationType !== 'Forest Plot' && (
1046
+ <AccordionItem>
1047
+ <AccordionItemHeading>
1048
+ <AccordionItemButton>Data Series {(!config.series || config.series.length === 0 || (config.visualizationType === 'Paired Bar' && config.series.length < 2)) && <WarningImage width='25' className='warning-icon' />}</AccordionItemButton>
1049
+ </AccordionItemHeading>
1050
+ <AccordionItemPanel>
1051
+ {(!config.series || config.series.length === 0) && config.visualizationType !== 'Paired Bar' && <p className='warning'>At least one series is required</p>}
1052
+ {(!config.series || config.series.length === 0 || config.series.length < 2) && config.visualizationType === 'Paired Bar' && <p className='warning'>Select two data series for paired bar chart (e.g., Male and Female).</p>}
1053
+ <>
1054
+ <Select
1055
+ fieldName='visualizationType'
1056
+ label='Add Data Series'
1057
+ initial='Select'
1058
+ onChange={e => {
1059
+ if (e.target.value !== '' && e.target.value !== 'Select') {
1060
+ addNewSeries(e.target.value)
1061
+ }
1062
+ e.target.value = ''
1063
+ }}
1064
+ options={getColumns()}
1065
+ />
1066
+ {config.series && config.series.length !== 0 && (
1067
+ <Panels.Series.Wrapper getColumns={getColumns}>
1068
+ <fieldset>
1069
+ <legend className='edit-label float-left'>Displaying</legend>
1070
+ <Tooltip style={{ textTransform: 'none' }}>
1071
+ <Tooltip.Target>
1072
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1073
+ </Tooltip.Target>
1074
+ <Tooltip.Content>
1075
+ <p>A data series is a set of related data points plotted in a chart and typically represented in the chart legend.</p>
1076
+ </Tooltip.Content>
1077
+ </Tooltip>
1078
+ </fieldset>
1079
+
1080
+ <DragDropContext onDragEnd={({ source, destination }) => handleSeriesChange(source.index, destination.index)}>
1081
+ <Droppable droppableId='filter_order'>
1082
+ {/* prettier-ignore */}
1083
+ {provided => {
1084
+ return (
1085
+ <ul {...provided.droppableProps} className='series-list' ref={provided.innerRef}>
1086
+ <Panels.Series.List series={config.series} getItemStyle={getItemStyle} sortableItemStyles={sortableItemStyles} chartsWithOptions={chartsWithOptions} />
1087
+ {provided.placeholder}
1088
+ </ul>
1089
+ )
1090
+ }}
1091
+ </Droppable>
1092
+ </DragDropContext>
1093
+ </Panels.Series.Wrapper>
1094
+ )}
1095
+ </>
1096
+ {config.series && config.series.length <= 1 && config.visualizationType === 'Bar' && (
1097
+ <>
1098
+ <span className='divider-heading'>Confidence Keys</span>
1099
+ <Select value={config.confidenceKeys.upper || ''} section='confidenceKeys' fieldName='upper' label='Upper' updateField={updateField} initial='Select' options={getColumns()} />
1100
+ <Select value={config.confidenceKeys.lower || ''} section='confidenceKeys' fieldName='lower' label='Lower' updateField={updateField} initial='Select' options={getColumns()} />
1101
+ </>
1102
+ )}
1103
+ {visSupportsRankByValue() && config.series && config.series.length === 1 && <Select fieldName='visualizationType' label='Rank by Value' initial='Select' onChange={e => sortSeries(e.target.value)} options={['asc', 'desc']} />}
1104
+ {/* {visHasDataSuppression() && <DataSuppression config={config} updateConfig={updateConfig} data={data} />} */}
1105
+ {config.visualizationType === 'Line' && <PreliminaryData config={config} updateConfig={updateConfig} data={data} />}
1106
+ </AccordionItemPanel>
1107
+ </AccordionItem>
1108
+ )}
1109
+ <Panels.BoxPlot name='Measures' />
1110
+ {/* Left Value Axis */}
1111
+ {visSupportsLeftValueAxis() && (
1112
+ <AccordionItem>
1113
+ <AccordionItemHeading>
1114
+ <AccordionItemButton>
1115
+ {config.visualizationType === 'Pie' ? 'Data Format' : config.orientation === 'vertical' ? 'Left Value Axis' : 'Value Axis'}
1116
+ {config.visualizationType === 'Pie' && !config.yAxis.dataKey && <WarningImage width='25' className='warning-icon' />}
1117
+ </AccordionItemButton>
1118
+ </AccordionItemHeading>
1119
+ <AccordionItemPanel>
1120
+ {config.visualizationType === 'Pie' && (
1121
+ <Select
1122
+ value={config.yAxis.dataKey || ''}
1123
+ section='yAxis'
1124
+ fieldName='dataKey'
1125
+ label='Data Column'
1126
+ initial='Select'
1127
+ required={true}
1128
+ updateField={updateField}
1129
+ options={getColumns(false)}
1130
+ tooltip={
1131
+ <Tooltip style={{ textTransform: 'none' }}>
1132
+ <Tooltip.Target>
1133
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1134
+ </Tooltip.Target>
1135
+ <Tooltip.Content>
1136
+ <p>Select the source data to be visually represented.</p>
1137
+ </Tooltip.Content>
1138
+ </Tooltip>
1139
+ }
1140
+ />
1141
+ )}
1142
+ {config.visualizationType !== 'Pie' && (
1143
+ <>
1144
+ <TextField value={config.yAxis.label} section='yAxis' fieldName='label' label='Label ' updateField={updateField} />
1145
+ {config.runtime.seriesKeys && config.runtime.seriesKeys.length === 1 && !['Box Plot', 'Deviation Bar', 'Forest Plot'].includes(config.visualizationType) && (
1146
+ <CheckBox value={config.isLegendValue} fieldName='isLegendValue' label='Use Legend Value in Hover' updateField={updateField} />
1147
+ )}
1148
+ <TextField value={config.yAxis.numTicks} placeholder='Auto' type='number' section='yAxis' fieldName='numTicks' label='Number of ticks' className='number-narrow' updateField={updateField} />
1149
+ {config.visualizationType === 'Paired Bar' && <TextField value={config.yAxis.tickRotation || 0} type='number' min={0} section='yAxis' fieldName='tickRotation' label='Tick rotation (Degrees)' className='number-narrow' updateField={updateField} />}
1150
+ <TextField
1151
+ value={config.yAxis.size}
1152
+ type='number'
1153
+ section='yAxis'
1154
+ fieldName='size'
1155
+ label={config.orientation === 'horizontal' ? 'Size (Height)' : 'Size (Width)'}
1156
+ className='number-narrow'
1157
+ updateField={updateField}
1158
+ tooltip={
1159
+ <Tooltip style={{ textTransform: 'none' }}>
1160
+ <Tooltip.Target>
1161
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
1162
+ </Tooltip.Target>
1163
+ <Tooltip.Content>
1164
+ <p>{`Increase the size if elements in the ${config.orientation} axis are being crowded or hidden behind other elements. Decrease if less space is required for the value axis.`}</p>
1165
+ </Tooltip.Content>
1166
+ </Tooltip>
1167
+ }
1168
+ />
1169
+ {config.orientation === 'horizontal' && config.visualizationType !== 'Paired Bar' && <CheckBox value={config.isResponsiveTicks} fieldName='isResponsiveTicks' label='Use Responsive Ticks' updateField={updateField} />}
1170
+ {(config.orientation === 'vertical' || !config.isResponsiveTicks) && <TextField value={config.yAxis.tickRotation} type='number' min={0} section='yAxis' fieldName='tickRotation' label='Tick rotation (Degrees)' className='number-narrow' updateField={updateField} />}
1171
+ {config.isResponsiveTicks && config.orientation === 'horizontal' && config.visualizationType !== 'Paired Bar' && (
1172
+ <TextField
1173
+ value={config.xAxis.maxTickRotation}
1174
+ type='number'
1175
+ min={0}
1176
+ section='xAxis'
1177
+ fieldName='maxTickRotation'
1178
+ label='Max Tick Rotation'
1179
+ className='number-narrow'
1180
+ updateField={updateField}
1181
+ tooltip={
1182
+ <Tooltip style={{ textTransform: 'none' }}>
1183
+ <Tooltip.Target>
1184
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
1185
+ </Tooltip.Target>
1186
+ <Tooltip.Content>
1187
+ <p>Degrees ticks will be rotated if values overlap, especially in smaller viewports.</p>
1188
+ </Tooltip.Content>
1189
+ </Tooltip>
1190
+ }
1191
+ />
1192
+ )}
1193
+
1194
+ {/* Hiding this for now, not interested in moving the axis lines away from chart comp. right now. */}
1195
+ {/* <TextField value={config.yAxis.axisPadding} type='number' max={10} min={0} section='yAxis' fieldName='axisPadding' label={'Axis Padding'} className='number-narrow' updateField={updateField} /> */}
1196
+ {config.orientation === 'horizontal' && <TextField value={config.xAxis.labelOffset} section='xAxis' fieldName='labelOffset' label='Label offset' type='number' className='number-narrow' updateField={updateField} />}
1197
+ {visSupportsValueAxisGridLines() && <CheckBox value={config.yAxis.gridLines} section='yAxis' fieldName='gridLines' label='Show Gridlines' updateField={updateField} />}
1198
+ <CheckBox value={config.yAxis.enablePadding} section='yAxis' fieldName='enablePadding' label='Add Padding to Value Axis Scale' updateField={updateField} />
1199
+ {config.yAxis.enablePadding && <TextField type='number' section='yAxis' fieldName='scalePadding' label='Padding Percentage' className='number-narrow' updateField={updateField} value={config.yAxis.scalePadding} />}
1200
+ {config.visualizationSubType === 'regular' && config.visualizationType !== 'Forest Plot' && <CheckBox value={config.useLogScale} fieldName='useLogScale' label='use logarithmic scale' updateField={updateField} />}
1201
+ </>
1202
+ )}
1203
+ <span className='divider-heading'>Number Formatting</span>
1204
+ <CheckBox value={config.dataFormat.commas} section='dataFormat' fieldName='commas' label='Add commas' updateField={updateField} />
1205
+ <CheckBox
1206
+ value={config.dataFormat.abbreviated}
1207
+ section='dataFormat'
1208
+ fieldName='abbreviated'
1209
+ label='Abbreviate Axis Values'
1210
+ updateField={updateField}
1211
+ tooltip={
1212
+ <Tooltip style={{ textTransform: 'none' }}>
1213
+ <Tooltip.Target>
1214
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
1215
+ </Tooltip.Target>
1216
+ <Tooltip.Content>
1217
+ <p>{`This option abbreviates very large or very small numbers on the value axis`}</p>
1218
+ </Tooltip.Content>
1219
+ </Tooltip>
1220
+ }
1221
+ />
1222
+ <TextField value={config.dataFormat.roundTo ? config.dataFormat.roundTo : 0} type='number' section='dataFormat' fieldName='roundTo' label='Round to decimal point' className='number-narrow' updateField={updateField} min={0} />
1223
+ <div className='two-col-inputs'>
1224
+ <TextField
1225
+ value={config.dataFormat.prefix}
1226
+ section='dataFormat'
1227
+ fieldName='prefix'
1228
+ label='Prefix'
1229
+ updateField={updateField}
1230
+ tooltip={
1231
+ <Tooltip style={{ textTransform: 'none' }}>
1232
+ <Tooltip.Target>
1233
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1234
+ </Tooltip.Target>
1235
+ <Tooltip.Content>
1236
+ {config.visualizationType === 'Pie' && <p>Enter a data prefix to display in the data table and chart tooltips, if applicable.</p>}
1237
+ {config.visualizationType !== 'Pie' && <p>Enter a data prefix (such as "$"), if applicable.</p>}
1238
+ </Tooltip.Content>
1239
+ </Tooltip>
1240
+ }
1241
+ />
1242
+ <TextField
1243
+ value={config.dataFormat.suffix}
1244
+ section='dataFormat'
1245
+ fieldName='suffix'
1246
+ label='Suffix'
1247
+ updateField={updateField}
1248
+ tooltip={
1249
+ <Tooltip style={{ textTransform: 'none' }}>
1250
+ <Tooltip.Target>
1251
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1252
+ </Tooltip.Target>
1253
+ <Tooltip.Content>
1254
+ {config.visualizationType === 'Pie' && <p>Enter a data suffix to display in the data table and tooltips, if applicable.</p>}
1255
+ {config.visualizationType !== 'Pie' && <p>Enter a data suffix (such as "%"), if applicable.</p>}
1256
+ </Tooltip.Content>
1257
+ </Tooltip>
1258
+ }
1259
+ />
1260
+ </div>
1261
+
1262
+ {config.orientation === 'horizontal' ? ( // horizontal - x is vertical y is horizontal
1263
+ <>
1264
+ {visSupportsValueAxisLine() && <CheckBox value={config.xAxis.hideAxis} section='xAxis' fieldName='hideAxis' label='Hide Axis' updateField={updateField} />}
1265
+ {visSupportsValueAxisLabels() && <CheckBox value={config.xAxis.hideLabel} section='xAxis' fieldName='hideLabel' label='Hide Label' updateField={updateField} />}
1266
+ {visSupportsValueAxisTicks() && <CheckBox value={config.xAxis.hideTicks} section='xAxis' fieldName='hideTicks' label='Hide Ticks' updateField={updateField} />}
1267
+ {visSupportsValueAxisMax() && <TextField value={config.xAxis.max} section='xAxis' fieldName='max' label='max value' type='number' placeholder='Auto' updateField={updateField} />}
1268
+ <span style={{ color: 'red', display: 'block' }}>{warningMsg.maxMsg}</span>
1269
+ {visSupportsValueAxisMin() && <TextField value={config.xAxis.min} section='xAxis' fieldName='min' type='number' label='min value' placeholder='Auto' updateField={updateField} />}
1270
+ <span style={{ color: 'red', display: 'block' }}>{warningMsg.minMsg}</span>
1271
+ {config.visualizationType === 'Deviation Bar' && (
1272
+ <>
1273
+ <TextField value={config.xAxis.target} section='xAxis' fieldName='target' type='number' label='Deviation point' placeholder='Auto' updateField={updateField} />
1274
+ <TextField value={config.xAxis.targetLabel || 'Target'} section='xAxis' fieldName='targetLabel' type='text' label='Deviation point Label' updateField={updateField} />
1275
+ <CheckBox value={config.xAxis.showTargetLabel} section='xAxis' fieldName='showTargetLabel' label='Show Deviation point label' updateField={updateField} />
1276
+ </>
1277
+ )}
1278
+ </>
1279
+ ) : (
1280
+ config.visualizationType !== 'Pie' && (
1281
+ <>
1282
+ <CheckBox value={config.yAxis.hideAxis} section='yAxis' fieldName='hideAxis' label='Hide Axis' updateField={updateField} />
1283
+ <CheckBox value={config.yAxis.hideLabel} section='yAxis' fieldName='hideLabel' label='Hide Label' updateField={updateField} />
1284
+ <CheckBox value={config.yAxis.hideTicks} section='yAxis' fieldName='hideTicks' label='Hide Ticks' updateField={updateField} />
1285
+
1286
+ <TextField value={config.yAxis.max} section='yAxis' fieldName='max' type='number' label='left axis max value' placeholder='Auto' updateField={updateField} />
1287
+ <span style={{ color: 'red', display: 'block' }}>{warningMsg.maxMsg}</span>
1288
+ <TextField value={config.yAxis.min} section='yAxis' fieldName='min' type='number' label='left axis min value' placeholder='Auto' updateField={updateField} />
1289
+ <span style={{ color: 'red', display: 'block' }}>{warningMsg.minMsg}</span>
1290
+ </>
1291
+ )
1292
+ )}
1293
+
1294
+ {/* start: anchors */}
1295
+ {visHasAnchors() && config.orientation !== 'horizontal' && (
1296
+ <div className='edit-block'>
1297
+ <span className='edit-label column-heading'>Anchors</span>
1298
+ <Accordion allowZeroExpanded>
1299
+ {config.yAxis?.anchors?.map((anchor, index) => (
1300
+ <AccordionItem className='series-item series-item--chart' key={`yaxis-anchors-2-${index}`}>
1301
+ <AccordionItemHeading className='series-item__title'>
1302
+ <>
1303
+ <AccordionItemButton className={'accordion__button accordion__button'}>
1304
+ Anchor {index + 1}
1305
+ <button
1306
+ className='series-list__remove'
1307
+ onClick={e => {
1308
+ e.preventDefault()
1309
+ const copiedAnchorGroups = [...config.yAxis.anchors]
1310
+ copiedAnchorGroups.splice(index, 1)
1311
+ updateConfig({
1312
+ ...config,
1313
+ yAxis: {
1314
+ ...config.yAxis,
1315
+ anchors: copiedAnchorGroups
1316
+ }
1317
+ })
1318
+ }}
1319
+ >
1320
+ Remove
1321
+ </button>
1322
+ </AccordionItemButton>
1323
+ </>
1324
+ </AccordionItemHeading>
1325
+ <AccordionItemPanel>
1326
+ <label>
1327
+ <span>Anchor Value</span>
1328
+ <Tooltip style={{ textTransform: 'none' }}>
1329
+ <Tooltip.Target>
1330
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1331
+ </Tooltip.Target>
1332
+ <Tooltip.Content>
1333
+ <p>Enter the value as its shown in the data column</p>
1334
+ </Tooltip.Content>
1335
+ </Tooltip>
1336
+ <input
1337
+ type='text'
1338
+ value={config.yAxis.anchors[index].value ? config.yAxis.anchors[index].value : ''}
1339
+ onChange={e => {
1340
+ e.preventDefault()
1341
+ const copiedAnchors = [...config.yAxis.anchors]
1342
+ copiedAnchors[index].value = e.target.value
1343
+ updateConfig({
1344
+ ...config,
1345
+ yAxis: {
1346
+ ...config.yAxis,
1347
+ anchors: copiedAnchors
1348
+ }
1349
+ })
1350
+ }}
1351
+ />
1352
+ </label>
1353
+
1354
+ <label>
1355
+ <span>Anchor Color</span>
1356
+ <input
1357
+ type='text'
1358
+ value={config.yAxis.anchors[index].color ? config.yAxis.anchors[index].color : ''}
1359
+ onChange={e => {
1360
+ e.preventDefault()
1361
+ const copiedAnchors = [...config.yAxis.anchors]
1362
+ copiedAnchors[index].color = e.target.value
1363
+ updateConfig({
1364
+ ...config,
1365
+ yAxis: {
1366
+ ...config.yAxis,
1367
+ anchors: copiedAnchors
1368
+ }
1369
+ })
1370
+ }}
1371
+ />
1372
+ </label>
1373
+
1374
+ <label>
1375
+ Anchor Line Style
1376
+ <select
1377
+ value={config.yAxis.anchors[index].lineStyle || ''}
1378
+ onChange={e => {
1379
+ const copiedAnchors = [...config.yAxis.anchors]
1380
+ copiedAnchors[index].lineStyle = e.target.value
1381
+ updateConfig({
1382
+ ...config,
1383
+ yAxis: {
1384
+ ...config.yAxis,
1385
+ anchors: copiedAnchors
1386
+ }
1387
+ })
1388
+ }}
1389
+ >
1390
+ <option>Select</option>
1391
+ {lineOptions.map(line => (
1392
+ <option key={line.key}>{line.value}</option>
1393
+ ))}
1394
+ </select>
1395
+ </label>
1396
+ </AccordionItemPanel>
1397
+ </AccordionItem>
1398
+ ))}
1399
+ </Accordion>
1400
+
1401
+ <button
1402
+ className='btn full-width'
1403
+ onClick={e => {
1404
+ e.preventDefault()
1405
+ const anchors = [...config.yAxis.anchors]
1406
+ anchors.push({} as Anchor)
1407
+ updateConfig({
1408
+ ...config,
1409
+ yAxis: {
1410
+ ...config.yAxis,
1411
+ anchors
1412
+ }
1413
+ })
1414
+ }}
1415
+ >
1416
+ Add Anchor
1417
+ </button>
1418
+ </div>
1419
+ )}
1420
+
1421
+ {visHasAnchors() && config.orientation === 'horizontal' && (
1422
+ <div className='edit-block'>
1423
+ <span className='edit-label column-heading'>Anchors</span>
1424
+ <Accordion allowZeroExpanded>
1425
+ {config.xAxis?.anchors?.map((anchor, index) => (
1426
+ <AccordionItem className='series-item series-item--chart' key={`xaxis-anchors-${index}`}>
1427
+ <AccordionItemHeading className='series-item__title'>
1428
+ <>
1429
+ <AccordionItemButton className={'accordion__button accordion__button'}>
1430
+ Anchor {index + 1}
1431
+ <button
1432
+ className='series-list__remove'
1433
+ onClick={e => {
1434
+ e.preventDefault()
1435
+ const copiedAnchorGroups = [...config.xAxis.anchors]
1436
+ copiedAnchorGroups.splice(index, 1)
1437
+ updateConfig({
1438
+ ...config,
1439
+ xAxis: {
1440
+ ...config.xAxis,
1441
+ anchors: copiedAnchorGroups
1442
+ }
1443
+ })
1444
+ }}
1445
+ >
1446
+ Remove
1447
+ </button>
1448
+ </AccordionItemButton>
1449
+ </>
1450
+ </AccordionItemHeading>
1451
+ <AccordionItemPanel>
1452
+ <label>
1453
+ <span>Anchor Value</span>
1454
+ <Tooltip style={{ textTransform: 'none' }}>
1455
+ <Tooltip.Target>
1456
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1457
+ </Tooltip.Target>
1458
+ <Tooltip.Content>
1459
+ <p>Enter the value as its shown in the data column</p>
1460
+ </Tooltip.Content>
1461
+ </Tooltip>
1462
+ <input
1463
+ type='text'
1464
+ value={config.xAxis.anchors[index].value ? config.xAxis.anchors[index].value : ''}
1465
+ onChange={e => {
1466
+ e.preventDefault()
1467
+ const copiedAnchors = [...config.xAxis.anchors]
1468
+ copiedAnchors[index].value = e.target.value
1469
+ updateConfig({
1470
+ ...config,
1471
+ xAxis: {
1472
+ ...config.xAxis,
1473
+ anchors: copiedAnchors
1474
+ }
1475
+ })
1476
+ }}
1477
+ />
1478
+ </label>
1479
+
1480
+ <label>
1481
+ <span>Anchor Color</span>
1482
+ <input
1483
+ type='text'
1484
+ value={config.xAxis.anchors[index].color ? config.xAxis.anchors[index].color : ''}
1485
+ onChange={e => {
1486
+ e.preventDefault()
1487
+ const copiedAnchors = [...config.xAxis.anchors]
1488
+ copiedAnchors[index].color = e.target.value
1489
+ updateConfig({
1490
+ ...config,
1491
+ xAxis: {
1492
+ ...config.xAxis,
1493
+ anchors: copiedAnchors
1494
+ }
1495
+ })
1496
+ }}
1497
+ />
1498
+ </label>
1499
+
1500
+ <label>
1501
+ Anchor Line Style
1502
+ <select
1503
+ value={config.xAxis.anchors[index].lineStyle || ''}
1504
+ onChange={e => {
1505
+ const copiedAnchors = [...config.xAxis.anchors]
1506
+ copiedAnchors[index].lineStyle = e.target.value
1507
+ updateConfig({
1508
+ ...config,
1509
+ xAxis: {
1510
+ ...config.xAxis,
1511
+ anchors: copiedAnchors
1512
+ }
1513
+ })
1514
+ }}
1515
+ >
1516
+ <option>Select</option>
1517
+ {lineOptions.map(line => (
1518
+ <option key={line.key}>{line.value}</option>
1519
+ ))}
1520
+ </select>
1521
+ </label>
1522
+ </AccordionItemPanel>
1523
+ </AccordionItem>
1524
+ ))}
1525
+ </Accordion>
1526
+
1527
+ <button
1528
+ className='btn full-width'
1529
+ onClick={e => {
1530
+ e.preventDefault()
1531
+ const anchors = [...config.xAxis.anchors]
1532
+ anchors.push({} as Anchor)
1533
+ updateConfig({
1534
+ ...config,
1535
+ xAxis: {
1536
+ ...config.xAxis,
1537
+ anchors
1538
+ }
1539
+ })
1540
+ }}
1541
+ >
1542
+ Add Anchor
1543
+ </button>
1544
+ </div>
1545
+ )}
1546
+ {/* end: anchors */}
1547
+ </AccordionItemPanel>
1548
+ </AccordionItem>
1549
+ )}
1550
+ {/* Right Value Axis Settings */}
1551
+ {hasRightAxis && (
1552
+ <AccordionItem>
1553
+ <AccordionItemHeading>
1554
+ <AccordionItemButton>Right Value Axis</AccordionItemButton>
1555
+ </AccordionItemHeading>
1556
+ <AccordionItemPanel>
1557
+ <TextField value={config.yAxis.rightLabel} section='yAxis' fieldName='rightLabel' label='Label' updateField={updateField} />
1558
+ <TextField value={config.yAxis.rightNumTicks} placeholder='Auto' type='number' section='yAxis' fieldName='rightNumTicks' label='Number of ticks' className='number-narrow' updateField={updateField} />
1559
+ <TextField value={config.yAxis.rightAxisSize} type='number' section='yAxis' fieldName='rightAxisSize' label='Size (Width)' className='number-narrow' updateField={updateField} />
1560
+ <TextField value={config.yAxis.rightLabelOffsetSize} type='number' section='yAxis' fieldName='rightLabelOffsetSize' label='Label Offset' className='number-narrow' updateField={updateField} />
1561
+
1562
+ <span className='divider-heading'>Number Formatting</span>
1563
+ <CheckBox value={config.dataFormat.rightCommas} section='dataFormat' fieldName='rightCommas' label='Add commas' updateField={updateField} />
1564
+ <TextField value={config.dataFormat.rightRoundTo} type='number' section='dataFormat' fieldName='rightRoundTo' label='Round to decimal point' className='number-narrow' updateField={updateField} min={0} />
1565
+ <div className='two-col-inputs'>
1566
+ <TextField
1567
+ value={config.dataFormat.rightPrefix}
1568
+ section='dataFormat'
1569
+ fieldName='rightPrefix'
1570
+ label='Prefix'
1571
+ updateField={updateField}
1572
+ tooltip={
1573
+ <Tooltip style={{ textTransform: 'none' }}>
1574
+ <Tooltip.Target>
1575
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1576
+ </Tooltip.Target>
1577
+ <Tooltip.Content>
1578
+ {config.visualizationType === 'Pie' && <p>Enter a data prefix to display in the data table and chart tooltips, if applicable.</p>}
1579
+ {config.visualizationType !== 'Pie' && <p>Enter a data prefix (such as "$"), if applicable.</p>}
1580
+ </Tooltip.Content>
1581
+ </Tooltip>
1582
+ }
1583
+ />
1584
+ <TextField
1585
+ value={config.dataFormat.rightSuffix}
1586
+ section='dataFormat'
1587
+ fieldName='rightSuffix'
1588
+ label='Suffix'
1589
+ updateField={updateField}
1590
+ tooltip={
1591
+ <Tooltip style={{ textTransform: 'none' }}>
1592
+ <Tooltip.Target>
1593
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1594
+ </Tooltip.Target>
1595
+ <Tooltip.Content>
1596
+ {config.visualizationType === 'Pie' && <p>Enter a data suffix to display in the data table and tooltips, if applicable.</p>}
1597
+ {config.visualizationType !== 'Pie' && <p>Enter a data suffix (such as "%"), if applicable.</p>}
1598
+ </Tooltip.Content>
1599
+ </Tooltip>
1600
+ }
1601
+ />
1602
+ </div>
1603
+
1604
+ <CheckBox value={config.yAxis.rightHideAxis} section='yAxis' fieldName='rightHideAxis' label='Hide Axis' updateField={updateField} />
1605
+ <CheckBox value={config.yAxis.rightHideLabel} section='yAxis' fieldName='rightHideLabel' label='Hide Label' updateField={updateField} />
1606
+ <CheckBox value={config.yAxis.rightHideTicks} section='yAxis' fieldName='rightHideTicks' label='Hide Ticks' updateField={updateField} />
1607
+
1608
+ <TextField value={config.yAxis.max} section='yAxis' fieldName='rightMax' type='number' label='right axis max value' placeholder='Auto' updateField={updateField} />
1609
+ <span style={{ color: 'red', display: 'block' }}>{warningMsg.rightMaxMessage}</span>
1610
+ <TextField value={config.yAxis.min} section='yAxis' fieldName='rightMin' type='number' label='right axis min value' placeholder='Auto' updateField={updateField} />
1611
+ <span style={{ color: 'red', display: 'block' }}>{warningMsg.minMsg}</span>
1612
+ </AccordionItemPanel>
1613
+ </AccordionItem>
1614
+ )}
1615
+ {visSupportsDateCategoryAxis() && (
1616
+ <AccordionItem>
1617
+ <AccordionItemHeading>
1618
+ <AccordionItemButton>
1619
+ {config.visualizationType === 'Pie' ? 'Segments' : 'Date/Category Axis'}
1620
+ {!config.xAxis.dataKey && <WarningImage width='25' className='warning-icon' />}
1621
+ </AccordionItemButton>
1622
+ </AccordionItemHeading>
1623
+ <AccordionItemPanel>
1624
+ {config.visualizationType !== 'Pie' && (
1625
+ <>
1626
+ {config.visualizationType !== 'Forest Plot' && (
1627
+ <>
1628
+ <Select value={config.xAxis.type} section='xAxis' fieldName='type' label='Data Type' updateField={updateField} options={config.visualizationType !== 'Scatter Plot' ? ['categorical', 'date'] : ['categorical', 'continuous', 'date']} />
1629
+ {(config.visualizationType === 'Bar' || config.visualizationType === 'Line' || config.visualizationType === 'Combo' || config.visualizationType === 'Area Chart') && config.xAxis.type === 'date' && config.orientation !== 'horizontal' && (
1630
+ <CheckBox value={config.xAxis.sortDates} section='xAxis' fieldName='sortDates' label='Force Date Scale (Sort Dates)' updateField={updateField} />
1631
+ )}{' '}
1632
+ {visSupportsDateCategoryAxisPadding() && (
1633
+ <TextField
1634
+ value={config.xAxis.padding}
1635
+ type='number'
1636
+ min={0}
1637
+ section='xAxis'
1638
+ fieldName='padding'
1639
+ label={'Padding (Percent)'}
1640
+ className='number-narrow'
1641
+ updateField={updateField}
1642
+ tooltip={
1643
+ <Tooltip style={{ textTransform: 'none' }}>
1644
+ <Tooltip.Target>
1645
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1646
+ </Tooltip.Target>
1647
+ <Tooltip.Content>
1648
+ <p>For use with date scale. Extends the earliest and latest dates represented on the scale by the percentage specified.</p>
1649
+ </Tooltip.Content>
1650
+ </Tooltip>
1651
+ }
1652
+ />
1653
+ )}
1654
+ </>
1655
+ )}
1656
+ <Select
1657
+ value={config.xAxis.dataKey || setCategoryAxis() || ''}
1658
+ section='xAxis'
1659
+ fieldName='dataKey'
1660
+ label='Data Key'
1661
+ initial='Select'
1662
+ required={true}
1663
+ updateField={updateField}
1664
+ options={getColumns(false)}
1665
+ tooltip={
1666
+ <Tooltip style={{ textTransform: 'none' }}>
1667
+ <Tooltip.Target>
1668
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1669
+ </Tooltip.Target>
1670
+ <Tooltip.Content>
1671
+ <p>Select the column or row containing the categories or dates for this axis. </p>
1672
+ </Tooltip.Content>
1673
+ </Tooltip>
1674
+ }
1675
+ />
1676
+ </>
1677
+ )}
1678
+
1679
+ {config.visualizationType === 'Pie' && (
1680
+ <Select
1681
+ value={config.xAxis.dataKey || ''}
1682
+ section='xAxis'
1683
+ fieldName='dataKey'
1684
+ label='Segment Labels'
1685
+ initial='Select'
1686
+ required={true}
1687
+ updateField={updateField}
1688
+ options={getColumns(false)}
1689
+ tooltip={
1690
+ <Tooltip style={{ textTransform: 'none' }}>
1691
+ <Tooltip.Target>
1692
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
1693
+ </Tooltip.Target>
1694
+ <Tooltip.Content>
1695
+ <p>Select the source row or column that contains the segment labels. Depending on the data structure, it may be listed as "Key."</p>
1696
+ </Tooltip.Content>
1697
+ </Tooltip>
1698
+ }
1699
+ />
1700
+ )}
1701
+
1702
+ {config.visualizationType !== 'Pie' && (
1703
+ <>
1704
+ <TextField value={config.xAxis.label} section='xAxis' fieldName='label' label='Label' updateField={updateField} />
1705
+
1706
+ {config.xAxis.type === 'continuous' && (
1707
+ <>
1708
+ <TextField
1709
+ value={config.dataFormat.bottomPrefix}
1710
+ section='dataFormat'
1711
+ fieldName='bottomPrefix'
1712
+ label='Prefix'
1713
+ updateField={updateField}
1714
+ tooltip={
1715
+ <Tooltip style={{ textTransform: 'none' }}>
1716
+ <Tooltip.Target>
1717
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
1718
+ </Tooltip.Target>
1719
+ <Tooltip.Content>
1720
+ <p>Enter a data suffix (such as "%"), if applicable.</p>
1721
+ </Tooltip.Content>
1722
+ </Tooltip>
1723
+ }
1724
+ />
1725
+
1726
+ <TextField
1727
+ value={config.dataFormat.bottomSuffix}
1728
+ section='dataFormat'
1729
+ fieldName='bottomSuffix'
1730
+ label='Suffix'
1731
+ updateField={updateField}
1732
+ tooltip={
1733
+ <Tooltip style={{ textTransform: 'none' }}>
1734
+ <Tooltip.Target>
1735
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
1736
+ </Tooltip.Target>
1737
+ <Tooltip.Content>
1738
+ <p>Enter a data suffix (such as "%"), if applicable.</p>
1739
+ </Tooltip.Content>
1740
+ </Tooltip>
1741
+ }
1742
+ />
1743
+
1744
+ <CheckBox
1745
+ value={config.dataFormat.bottomAbbreviated}
1746
+ section='dataFormat'
1747
+ fieldName='bottomAbbreviated'
1748
+ label='Abbreviate Axis Values'
1749
+ updateField={updateField}
1750
+ tooltip={
1751
+ <Tooltip style={{ textTransform: 'none' }}>
1752
+ <Tooltip.Target>
1753
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
1754
+ </Tooltip.Target>
1755
+ <Tooltip.Content>
1756
+ <p>{`This option abbreviates very large or very small numbers on the value axis`}</p>
1757
+ </Tooltip.Content>
1758
+ </Tooltip>
1759
+ }
1760
+ />
1761
+ </>
1762
+ )}
1763
+
1764
+ {config.xAxis.type === 'date' && (
1765
+ <>
1766
+ <p style={{ padding: '1.5em 0 0.5em', fontSize: '.9rem', lineHeight: '1rem' }}>
1767
+ Format how charts should parse and display your dates using{' '}
1768
+ <a href='https://github.com/d3/d3-time-format#locale_format' target='_blank' rel='noreferrer'>
1769
+ these guidelines
1770
+ </a>
1771
+ .
1772
+ </p>
1773
+ <TextField
1774
+ tooltip={
1775
+ <Tooltip style={{ textTransform: 'none' }}>
1776
+ <Tooltip.Target>
1777
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
1778
+ </Tooltip.Target>
1779
+ <Tooltip.Content>
1780
+ <p>This field specifies the pattern used to read and interpret dates in your dataset, ensuring the dates are correctly understood and processed. </p>
1781
+ </Tooltip.Content>
1782
+ </Tooltip>
1783
+ }
1784
+ value={config.xAxis.dateParseFormat}
1785
+ section='xAxis'
1786
+ fieldName='dateParseFormat'
1787
+ placeholder='Ex. %Y-%m-%d'
1788
+ label='Date Parse Format'
1789
+ updateField={updateField}
1790
+ />
1791
+ <TextField
1792
+ tooltip={
1793
+ <Tooltip style={{ textTransform: 'none' }}>
1794
+ <Tooltip.Target>
1795
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
1796
+ </Tooltip.Target>
1797
+ <Tooltip.Content>
1798
+ <p> Adjusts the date display format on the axis for clear, visual date representation.</p>
1799
+ </Tooltip.Content>
1800
+ </Tooltip>
1801
+ }
1802
+ value={config.xAxis.dateDisplayFormat}
1803
+ section='xAxis'
1804
+ fieldName='dateDisplayFormat'
1805
+ placeholder='Ex. %Y-%m-%d'
1806
+ label='AXIS DATE DISPLAY FORMAT'
1807
+ updateField={updateField}
1808
+ />
1809
+ <TextField
1810
+ tooltip={
1811
+ <Tooltip style={{ textTransform: 'none' }}>
1812
+ <Tooltip.Target>
1813
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
1814
+ </Tooltip.Target>
1815
+ <Tooltip.Content>
1816
+ <p>Specify a custom format for displaying dates in data table. If left empty, dates will adopt the Axis Date Display format. </p>
1817
+ </Tooltip.Content>
1818
+ </Tooltip>
1819
+ }
1820
+ value={config.table.dateDisplayFormat}
1821
+ section='table'
1822
+ fieldName='dateDisplayFormat'
1823
+ placeholder='Ex. %Y-%m-%d'
1824
+ label='DATA TABLE DATE DISPLAY FORMAT'
1825
+ updateField={updateField}
1826
+ />
1827
+ <TextField
1828
+ tooltip={
1829
+ <Tooltip style={{ textTransform: 'none' }}>
1830
+ <Tooltip.Target>
1831
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
1832
+ </Tooltip.Target>
1833
+ <Tooltip.Content>
1834
+ <p>Specify a custom format for displaying dates on hovers. If left empty, dates will adopt the Axis Date Display format. </p>
1835
+ </Tooltip.Content>
1836
+ </Tooltip>
1837
+ }
1838
+ value={config.tooltips.dateDisplayFormat}
1839
+ section='tooltips'
1840
+ fieldName='dateDisplayFormat'
1841
+ placeholder='Ex. %Y-%m-%d'
1842
+ label='HOVER DATE DISPLAY FORMAT'
1843
+ updateField={updateField}
1844
+ />
1845
+ </>
1846
+ )}
1847
+
1848
+ <CheckBox
1849
+ value={config.exclusions.active}
1850
+ section='exclusions'
1851
+ fieldName='active'
1852
+ label={config.xAxis.type === 'date' ? 'Limit by start and/or end dates' : 'Exclude one or more values'}
1853
+ tooltip={
1854
+ <Tooltip style={{ textTransform: 'none' }}>
1855
+ <Tooltip.Target>
1856
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
1857
+ </Tooltip.Target>
1858
+ <Tooltip.Content>
1859
+ <p>When this option is checked, you can select source-file values for exclusion from the date/category axis. </p>
1860
+ </Tooltip.Content>
1861
+ </Tooltip>
1862
+ }
1863
+ updateField={updateField}
1864
+ />
1865
+ {/* {visHasBrushChart && <CheckBox value={config.brush.active} section='brush' fieldName='active' label='Brush Slider ' updateField={updateField} />} */}
1866
+
1867
+ {config.exclusions.active && (
1868
+ <>
1869
+ {config.xAxis.type === 'categorical' && (
1870
+ <>
1871
+ {config.exclusions.keys.length > 0 && (
1872
+ <>
1873
+ <fieldset>
1874
+ <legend className='edit-label'>Excluded Keys</legend>
1875
+ </fieldset>
1876
+ <ExclusionsList />
1877
+ </>
1878
+ )}
1879
+
1880
+ <Select
1881
+ fieldName='visualizationType'
1882
+ label='Add Exclusion'
1883
+ initial='Select'
1884
+ onChange={e => {
1885
+ if (e.target.value !== '' && e.target.value !== 'Select') {
1886
+ addNewExclusion(e.target.value)
1887
+ }
1888
+ e.target.value = ''
1889
+ }}
1890
+ options={getDataValues(config.xAxis.dataKey, true)}
1891
+ />
1892
+ </>
1893
+ )}
1894
+
1895
+ {config.xAxis.type === 'date' && (
1896
+ <>
1897
+ <TextField type='date' section='exclusions' fieldName='dateStart' label='Start Date' updateField={updateField} value={config.exclusions.dateStart || ''} />
1898
+ <TextField type='date' section='exclusions' fieldName='dateEnd' label='End Date' updateField={updateField} value={config.exclusions.dateEnd || ''} />
1899
+ </>
1900
+ )}
1901
+ </>
1902
+ )}
1903
+
1904
+ {visSupportsDateCategoryNumTicks() && <TextField value={config.xAxis.numTicks} placeholder='Auto' type='number' min={1} section='xAxis' fieldName='numTicks' label='Number of ticks' className='number-narrow' updateField={updateField} />}
1905
+ {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} />}
1906
+
1907
+ {visSupportsDateCategoryAxisPadding() && <TextField value={config.xAxis.padding} type='number' min={0} section='xAxis' fieldName='padding' label={'Padding (Percent)'} className='number-narrow' updateField={updateField} />}
1908
+
1909
+ {/* Hiding this for now, not interested in moving the axis lines away from chart comp. right now. */}
1910
+ {/* <TextField value={config.xAxis.axisPadding} type='number' max={10} min={0} section='xAxis' fieldName='axisPadding' label={'Axis Padding'} className='number-narrow' updateField={updateField} /> */}
1911
+ {(config.xAxis.type === 'continuous' || config.forestPlot.type === 'Logarithmic') && (
1912
+ <>
1913
+ <CheckBox value={config.dataFormat.bottomCommas} section='dataFormat' fieldName='bottomCommas' label='Add commas' updateField={updateField} />
1914
+ <TextField value={config.dataFormat.bottomRoundTo} type='number' section='dataFormat' fieldName='bottomRoundTo' label='Round to decimal point' className='number-narrow' updateField={updateField} min={0} />
1915
+ </>
1916
+ )}
1917
+ {visSupportsResponsiveTicks() && config.orientation === 'vertical' && config.visualizationType !== 'Paired Bar' && <CheckBox value={config.isResponsiveTicks} fieldName='isResponsiveTicks' label='Use Responsive Ticks' updateField={updateField} />}
1918
+ {(config.orientation === 'horizontal' || !config.isResponsiveTicks) && visSupportsDateCategoryTickRotation() && (
1919
+ <TextField value={config.xAxis.tickRotation} type='number' min={0} section='xAxis' fieldName='tickRotation' label='Tick rotation (Degrees)' className='number-narrow' updateField={updateField} />
1920
+ )}
1921
+ {config.orientation === 'vertical' && config.isResponsiveTicks && config.visualizationType !== 'Paired Bar' && (
1922
+ <TextField
1923
+ value={config.xAxis.maxTickRotation}
1924
+ type='number'
1925
+ min={0}
1926
+ section='xAxis'
1927
+ fieldName='maxTickRotation'
1928
+ label='Max Tick Rotation'
1929
+ className='number-narrow'
1930
+ updateField={updateField}
1931
+ tooltip={
1932
+ <Tooltip style={{ textTransform: 'none' }}>
1933
+ <Tooltip.Target>
1934
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
1935
+ </Tooltip.Target>
1936
+ <Tooltip.Content>
1937
+ <p>Degrees ticks will be rotated if values overlap, especially in smaller viewports.</p>
1938
+ </Tooltip.Content>
1939
+ </Tooltip>
1940
+ }
1941
+ />
1942
+ )}
1943
+
1944
+ {config.orientation === 'horizontal' ? (
1945
+ <>
1946
+ {visSupportsDateCategoryAxisLine() && <CheckBox value={config.yAxis.hideAxis} section='yAxis' fieldName='hideAxis' label='Hide Axis' updateField={updateField} />}
1947
+ {visSupportsDateCategoryAxisLabel() && <CheckBox value={config.yAxis.hideLabel} section='yAxis' fieldName='hideLabel' label='Hide Label' updateField={updateField} />}
1948
+ </>
1949
+ ) : (
1950
+ <>
1951
+ {visSupportsDateCategoryAxisLine() && <CheckBox value={config.xAxis.hideAxis} section='xAxis' fieldName='hideAxis' label='Hide Axis' updateField={updateField} />}
1952
+ {visSupportsDateCategoryAxisLabel() && <CheckBox value={config.xAxis.hideLabel} section='xAxis' fieldName='hideLabel' label='Hide Label' updateField={updateField} />}
1953
+ {visSupportsDateCategoryAxisTicks() && <CheckBox value={config.xAxis.hideTicks} section='xAxis' fieldName='hideTicks' label='Hide Ticks' updateField={updateField} />}
1954
+ </>
1955
+ )}
1956
+
1957
+ {config.series?.length === 1 && config.visualizationType === 'Bar' && (
1958
+ <>
1959
+ {/* HIGHLIGHTED BARS */}
1960
+ <label htmlFor='barHighlight'>Bar Highlighting</label>
1961
+ {config.series.length === 1 &&
1962
+ highlightedBarValues.map((highlightedBarValue, i) => (
1963
+ <fieldset>
1964
+ <div className='edit-block' key={`highlighted-bar-${i}`}>
1965
+ <button className='remove-column' onClick={e => handleRemoveHighlightedBar(e, i)}>
1966
+ Remove
1967
+ </button>
1968
+ <p>Highlighted Bar {i + 1}</p>
1969
+ <label>
1970
+ <span className='edit-label column-heading'>Value</span>
1971
+ <select value={config.highlightedBarValues[i].value} onChange={e => handleUpdateHighlightedBar(e, i)}>
1972
+ <option value=''>- Select Value -</option>
1973
+ {highlightedSeriesValues && [...new Set(highlightedSeriesValues)].sort().map(option => <option key={`special-class-value-option-${i}-${option}`}>{option}</option>)}
1974
+ </select>
1975
+ </label>
1976
+ <label>
1977
+ <span className='edit-label column-heading'>Color</span>
1978
+ <input type='text' value={config.highlightedBarValues[i].color ? config.highlightedBarValues[i].color : ''} onChange={e => handleUpdateHighlightedBarColor(e, i)} />
1979
+ </label>
1980
+ <label>
1981
+ <span className='edit-label column-heading'>Border Width</span>
1982
+ <input max='5' min='0' type='number' value={config.highlightedBarValues[i].borderWidth ? config.highlightedBarValues[i].borderWidth : ''} onChange={e => handleUpdateHighlightedBorderWidth(e, i)} />
1983
+ </label>
1984
+ <label>
1985
+ <span className='edit-label column-heading'>Legend Label</span>
1986
+ <input type='text' value={config.highlightedBarValues[i].legendLabel ? config.highlightedBarValues[i].legendLabel : ''} onChange={e => handleHighlightedBarLegendLabel(e, i)} />
1987
+ </label>
1988
+ </div>
1989
+ </fieldset>
1990
+ ))}
1991
+ <button className='btn full-width' onClick={e => handleAddNewHighlightedBar(e)}>
1992
+ Add Highlighted Bar
1993
+ </button>
1994
+ </>
1995
+ )}
1996
+ </>
1997
+ )}
1998
+
1999
+ {config.visualizationType === 'Pie' && (
2000
+ <>
2001
+ <CheckBox
2002
+ value={config.exclusions.active}
2003
+ section='exclusions'
2004
+ fieldName='active'
2005
+ label={'Exclude one or more values'}
2006
+ updateField={updateField}
2007
+ tooltip={
2008
+ <Tooltip style={{ textTransform: 'none' }}>
2009
+ <Tooltip.Target>
2010
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
2011
+ </Tooltip.Target>
2012
+ <Tooltip.Content>
2013
+ <p>When this option is checked, you can select values for exclusion from the pie segments.</p>
2014
+ </Tooltip.Content>
2015
+ </Tooltip>
2016
+ }
2017
+ />
2018
+ {config.exclusions.active && (
2019
+ <>
2020
+ {config.exclusions.keys.length > 0 && (
2021
+ <>
2022
+ <fieldset>
2023
+ <legend className='edit-label'>Excluded Keys</legend>
2024
+ </fieldset>
2025
+ <ExclusionsList />
2026
+ </>
2027
+ )}
2028
+
2029
+ <Select
2030
+ fieldName='visualizationType'
2031
+ label='Add Exclusion'
2032
+ initial='Select'
2033
+ onChange={e => {
2034
+ if (e.target.value !== '' && e.target.value !== 'Select') {
2035
+ addNewExclusion(e.target.value)
2036
+ }
2037
+ e.target.value = ''
2038
+ }}
2039
+ options={getDataValues(config.xAxis.dataKey, true)}
2040
+ />
2041
+ </>
2042
+ )}
2043
+ </>
2044
+ )}
2045
+
2046
+ {/* anchors */}
2047
+ {visHasAnchors() && config.orientation !== 'horizontal' && (
2048
+ <div className='edit-block'>
2049
+ <span className='edit-label column-heading'>Anchors</span>
2050
+ <Accordion allowZeroExpanded>
2051
+ {config.xAxis?.anchors?.map((anchor, index) => (
2052
+ <AccordionItem className='series-item series-item--chart' key={`xaxis-anchors-2-${index}`}>
2053
+ <AccordionItemHeading className='series-item__title'>
2054
+ <>
2055
+ <AccordionItemButton className={'accordion__button accordion__button'}>
2056
+ Anchor {index + 1}
2057
+ <button
2058
+ className='series-list__remove'
2059
+ onClick={e => {
2060
+ e.preventDefault()
2061
+ const copiedAnchorGroups = [...config.xAxis.anchors]
2062
+ copiedAnchorGroups.splice(index, 1)
2063
+ updateConfig({
2064
+ ...config,
2065
+ xAxis: {
2066
+ ...config.xAxis,
2067
+ anchors: copiedAnchorGroups
2068
+ }
2069
+ })
2070
+ }}
2071
+ >
2072
+ Remove
2073
+ </button>
2074
+ </AccordionItemButton>
2075
+ </>
2076
+ </AccordionItemHeading>
2077
+ <AccordionItemPanel>
2078
+ <label>
2079
+ <span>Anchor Value</span>
2080
+ <Tooltip style={{ textTransform: 'none' }}>
2081
+ <Tooltip.Target>
2082
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2083
+ </Tooltip.Target>
2084
+ <Tooltip.Content>
2085
+ <p>Enter the value as its shown in the data column</p>
2086
+ </Tooltip.Content>
2087
+ </Tooltip>
2088
+ <input
2089
+ type='text'
2090
+ value={config.xAxis.anchors[index].value ? config.xAxis.anchors[index].value : ''}
2091
+ onChange={e => {
2092
+ e.preventDefault()
2093
+ const copiedAnchors = [...config.xAxis.anchors]
2094
+ copiedAnchors[index].value = e.target.value
2095
+ updateConfig({
2096
+ ...config,
2097
+ xAxis: {
2098
+ ...config.xAxis,
2099
+ anchors: copiedAnchors
2100
+ }
2101
+ })
2102
+ }}
2103
+ />
2104
+ </label>
2105
+
2106
+ <label>
2107
+ <span>Anchor Color</span>
2108
+ <input
2109
+ type='text'
2110
+ value={config.xAxis.anchors[index].color ? config.xAxis.anchors[index].color : ''}
2111
+ onChange={e => {
2112
+ e.preventDefault()
2113
+ const copiedAnchors = [...config.xAxis.anchors]
2114
+ copiedAnchors[index].color = e.target.value
2115
+ updateConfig({
2116
+ ...config,
2117
+ xAxis: {
2118
+ ...config.xAxis,
2119
+ anchors: copiedAnchors
2120
+ }
2121
+ })
2122
+ }}
2123
+ />
2124
+ </label>
2125
+
2126
+ <label>
2127
+ Anchor Line Style
2128
+ <select
2129
+ value={config.xAxis.anchors[index].lineStyle || ''}
2130
+ onChange={e => {
2131
+ const copiedAnchors = [...config.xAxis.anchors]
2132
+ copiedAnchors[index].lineStyle = e.target.value
2133
+ updateConfig({
2134
+ ...config,
2135
+ xAxis: {
2136
+ ...config.xAxis,
2137
+ anchors: copiedAnchors
2138
+ }
2139
+ })
2140
+ }}
2141
+ >
2142
+ <option>Select</option>
2143
+ {lineOptions.map(line => (
2144
+ <option key={line.key}>{line.value}</option>
2145
+ ))}
2146
+ </select>
2147
+ </label>
2148
+ </AccordionItemPanel>
2149
+ </AccordionItem>
2150
+ ))}
2151
+ </Accordion>
2152
+
2153
+ <button
2154
+ className='btn full-width'
2155
+ onClick={e => {
2156
+ e.preventDefault()
2157
+ const anchors = [...config.xAxis.anchors]
2158
+ anchors.push({} as Anchor)
2159
+ updateConfig({
2160
+ ...config,
2161
+ xAxis: {
2162
+ ...config.xAxis,
2163
+ anchors
2164
+ }
2165
+ })
2166
+ }}
2167
+ >
2168
+ Add Anchor
2169
+ </button>
2170
+ </div>
2171
+ )}
2172
+
2173
+ {visHasAnchors() && config.orientation === 'horizontal' && (
2174
+ <div className='edit-block'>
2175
+ <span className='edit-label column-heading'>Anchors</span>
2176
+ <Accordion allowZeroExpanded>
2177
+ {config.yAxis?.anchors?.map((anchor, index) => (
2178
+ <AccordionItem className='series-item series-item--chart' key={`accordion-yaxis-anchors-${index}`}>
2179
+ <AccordionItemHeading className='series-item__title'>
2180
+ <>
2181
+ <AccordionItemButton className={'accordion__button accordion__button'}>
2182
+ Anchor {index + 1}
2183
+ <button
2184
+ className='series-list__remove'
2185
+ onClick={e => {
2186
+ e.preventDefault()
2187
+ const copiedAnchorGroups = [...config.yAxis.anchors]
2188
+ copiedAnchorGroups.splice(index, 1)
2189
+ updateConfig({
2190
+ ...config,
2191
+ yAxis: {
2192
+ ...config.yAxis,
2193
+ anchors: copiedAnchorGroups
2194
+ }
2195
+ })
2196
+ }}
2197
+ >
2198
+ Remove
2199
+ </button>
2200
+ </AccordionItemButton>
2201
+ </>
2202
+ </AccordionItemHeading>
2203
+ <AccordionItemPanel>
2204
+ <label>
2205
+ <span>Anchor Value</span>
2206
+ <Tooltip style={{ textTransform: 'none' }}>
2207
+ <Tooltip.Target>
2208
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2209
+ </Tooltip.Target>
2210
+ <Tooltip.Content>
2211
+ <p>Enter the value as its shown in the data column</p>
2212
+ </Tooltip.Content>
2213
+ </Tooltip>
2214
+ <input
2215
+ type='text'
2216
+ value={config.yAxis.anchors[index].value ? config.yAxis.anchors[index].value : ''}
2217
+ onChange={e => {
2218
+ e.preventDefault()
2219
+ const copiedAnchors = [...config.yAxis.anchors]
2220
+ copiedAnchors[index].value = e.target.value
2221
+ updateConfig({
2222
+ ...config,
2223
+ yAxis: {
2224
+ ...config.yAxis,
2225
+ anchors: copiedAnchors
2226
+ }
2227
+ })
2228
+ }}
2229
+ />
2230
+ </label>
2231
+
2232
+ <label>
2233
+ <span>Anchor Color</span>
2234
+ <input
2235
+ type='text'
2236
+ value={config.yAxis.anchors[index].color ? config.yAxis.anchors[index].color : ''}
2237
+ onChange={e => {
2238
+ e.preventDefault()
2239
+ const copiedAnchors = [...config.yAxis.anchors]
2240
+ copiedAnchors[index].color = e.target.value
2241
+ updateConfig({
2242
+ ...config,
2243
+ yAxis: {
2244
+ ...config.yAxis,
2245
+ anchors: copiedAnchors
2246
+ }
2247
+ })
2248
+ }}
2249
+ />
2250
+ </label>
2251
+
2252
+ <label>
2253
+ Anchor Line Style
2254
+ <select
2255
+ value={config.yAxis.anchors[index].lineStyle || ''}
2256
+ onChange={e => {
2257
+ const copiedAnchors = [...config.yAxis.anchors]
2258
+ copiedAnchors[index].lineStyle = e.target.value
2259
+ updateConfig({
2260
+ ...config,
2261
+ yAxis: {
2262
+ ...config.yAxis,
2263
+ anchors: copiedAnchors
2264
+ }
2265
+ })
2266
+ }}
2267
+ >
2268
+ <option>Select</option>
2269
+ {lineOptions.map(line => (
2270
+ <option key={line.key}>{line.value}</option>
2271
+ ))}
2272
+ </select>
2273
+ </label>
2274
+ </AccordionItemPanel>
2275
+ </AccordionItem>
2276
+ ))}
2277
+ </Accordion>
2278
+
2279
+ <button
2280
+ className='btn full-width'
2281
+ onClick={e => {
2282
+ e.preventDefault()
2283
+ const anchors = [...config.yAxis.anchors]
2284
+ anchors.push({} as Anchor)
2285
+ updateConfig({
2286
+ ...config,
2287
+ yAxis: {
2288
+ ...config.yAxis,
2289
+ anchors
2290
+ }
2291
+ })
2292
+ }}
2293
+ >
2294
+ Add Anchor
2295
+ </button>
2296
+ </div>
2297
+ )}
2298
+ </AccordionItemPanel>
2299
+ </AccordionItem>
2300
+ )}
2301
+ <Panels.Regions name='Regions' />
2302
+ {/* Columns */}
2303
+ {config.visualizationType !== 'Box Plot' && (
2304
+ <AccordionItem>
2305
+ <AccordionItemHeading>
2306
+ <AccordionItemButton>Columns</AccordionItemButton>
2307
+ </AccordionItemHeading>
2308
+ <AccordionItemPanel>
2309
+ {'navigation' !== config.type && (
2310
+ <fieldset className='primary-fieldset edit-block'>
2311
+ <label>
2312
+ <span className='edit-label'>
2313
+ Additional Columns
2314
+ <Tooltip style={{ textTransform: 'none' }}>
2315
+ <Tooltip.Target>
2316
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2317
+ </Tooltip.Target>
2318
+ <Tooltip.Content>
2319
+ <p>You can specify additional columns to display in tooltips and / or the supporting data table.</p>
2320
+ </Tooltip.Content>
2321
+ </Tooltip>
2322
+ </span>
2323
+ </label>
2324
+ {additionalColumns.map(val => (
2325
+ <fieldset className='edit-block' key={val}>
2326
+ <button
2327
+ className='remove-column'
2328
+ onClick={event => {
2329
+ event.preventDefault()
2330
+ removeAdditionalColumn(val)
2331
+ }}
2332
+ >
2333
+ Remove
2334
+ </button>
2335
+ <label>
2336
+ <span className='edit-label column-heading'>Column</span>
2337
+ <select
2338
+ value={config.columns[val] ? config.columns[val].name : getColumns()[0]}
2339
+ onChange={event => {
2340
+ editColumn(val, 'name', event.target.value)
2341
+ }}
2342
+ >
2343
+ {getColumns().map(option => (
2344
+ <option>{option}</option>
2345
+ ))}
2346
+ </select>
2347
+ </label>
2348
+ <label>
2349
+ <span className='edit-label column-heading'>Associate to Series</span>
2350
+ <select
2351
+ value={config.columns[val] ? config.columns[val].series : ''}
2352
+ onChange={event => {
2353
+ editColumn(val, 'series', event.target.value)
2354
+ }}
2355
+ >
2356
+ <option value=''>Select series</option>
2357
+ {config.series.map(series => (
2358
+ <option>{series.dataKey}</option>
2359
+ ))}
2360
+ </select>
2361
+ </label>
2362
+ <TextField value={config.columns[val].label} section='columns' subsection={val} fieldName='label' label='Label' updateField={updateField} />
2363
+ <ul className='column-edit'>
2364
+ <li className='three-col'>
2365
+ <TextField value={config.columns[val].prefix} section='columns' subsection={val} fieldName='prefix' label='Prefix' updateField={updateField} />
2366
+ <TextField value={config.columns[val].suffix} section='columns' subsection={val} fieldName='suffix' label='Suffix' updateField={updateField} />
2367
+ <TextField type='number' value={config.columns[val].roundToPlace} section='columns' subsection={val} fieldName='roundToPlace' label='Round' updateField={updateField} />
2368
+ </li>
2369
+ <li>
2370
+ <label className='checkbox'>
2371
+ <input
2372
+ type='checkbox'
2373
+ checked={config.columns[val].commas}
2374
+ onChange={event => {
2375
+ editColumn(val, 'commas', event.target.checked)
2376
+ }}
2377
+ />
2378
+ <span className='edit-label'>Add Commas to Numbers</span>
2379
+ </label>
2380
+ </li>
2381
+ <li>
2382
+ {config.table.showVertical && (
2383
+ <label className='checkbox'>
2384
+ <input
2385
+ type='checkbox'
2386
+ checked={config.columns[val].dataTable}
2387
+ onChange={event => {
2388
+ editColumn(val, 'dataTable', event.target.checked)
2389
+ }}
2390
+ />
2391
+ <span className='edit-label'>Show in Data Table</span>
2392
+ </label>
2393
+ )}
2394
+ </li>
2395
+ {config.visualizationType === 'Pie' && (
2396
+ <li>
2397
+ <label className='checkbox'>
2398
+ <input
2399
+ type='checkbox'
2400
+ checked={config.columns[val].showInViz}
2401
+ onChange={event => {
2402
+ editColumn(val, 'showInViz', event.target.checked)
2403
+ }}
2404
+ />
2405
+ <span className='edit-label'>Show in Visualization</span>
2406
+ </label>
2407
+ </li>
2408
+ )}
2409
+
2410
+ {/* disable for now */}
2411
+
2412
+ <li>
2413
+ <label className='checkbox'>
2414
+ <input
2415
+ type='checkbox'
2416
+ checked={config.columns[val].tooltips || false}
2417
+ onChange={event => {
2418
+ updateSeriesTooltip(val, event.target.checked)
2419
+ }}
2420
+ />
2421
+ <span className='edit-label'>Show in tooltip</span>
2422
+ </label>
2423
+ </li>
2424
+
2425
+ {config.visualizationType === 'Forest Plot' && (
2426
+ <>
2427
+ <li>
2428
+ <label className='checkbox'>
2429
+ <input
2430
+ type='checkbox'
2431
+ checked={config.columns[val].forestPlot || false}
2432
+ onChange={event => {
2433
+ editColumn(val, 'forestPlot', event.target.checked)
2434
+ }}
2435
+ />
2436
+ <span className='edit-label'>Show in Forest Plot</span>
2437
+ </label>
2438
+ </li>
2439
+ <li>
2440
+ <label className='checkbox'>
2441
+ <input
2442
+ type='checkbox'
2443
+ checked={config.columns[val].forestPlotAlignRight || false}
2444
+ onChange={event => {
2445
+ editColumn(val, 'forestPlotAlignRight', event.target.checked)
2446
+ }}
2447
+ />
2448
+ <span className='edit-label'>Align Right</span>
2449
+ </label>
2450
+ </li>
2451
+
2452
+ {!config.columns[val].forestPlotAlignRight && (
2453
+ <li>
2454
+ <label className='text'>
2455
+ <span className='edit-label'>Forest Plot Starting Point</span>
2456
+ <input
2457
+ type='number'
2458
+ value={config.columns[val].forestPlotStartingPoint || 0}
2459
+ onChange={event => {
2460
+ editColumn(val, 'forestPlotStartingPoint', event.target.value)
2461
+ }}
2462
+ />
2463
+ </label>
2464
+ </li>
2465
+ )}
2466
+ </>
2467
+ )}
2468
+ </ul>
2469
+ </fieldset>
2470
+ ))}
2471
+ <button
2472
+ className={'btn full-width'}
2473
+ onClick={event => {
2474
+ event.preventDefault()
2475
+ addAdditionalColumn(additionalColumns.length + 1)
2476
+ }}
2477
+ >
2478
+ Add Column
2479
+ </button>
2480
+ </fieldset>
2481
+ )}
2482
+ {'category' === config.legend.type && (
2483
+ <fieldset className='primary-fieldset edit-block'>
2484
+ <label>
2485
+ <span className='edit-label'>
2486
+ Additional Category
2487
+ <Tooltip style={{ textTransform: 'none' }}>
2488
+ <Tooltip.Target>
2489
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2490
+ </Tooltip.Target>
2491
+ <Tooltip.Content>
2492
+ <p>You can provide additional categories to ensure they appear in the legend</p>
2493
+ </Tooltip.Content>
2494
+ </Tooltip>
2495
+ </span>
2496
+ </label>
2497
+ {config.legend.additionalCategories &&
2498
+ config.legend.additionalCategories.map((val, i) => (
2499
+ <fieldset className='edit-block' key={val}>
2500
+ <button
2501
+ className='remove-column'
2502
+ onClick={event => {
2503
+ event.preventDefault()
2504
+ const updatedAdditionaCategories = [...config.legend.additionalCategories]
2505
+ updatedAdditionaCategories.splice(i, 1)
2506
+ updateField('legend', null, 'additionalCategories', updatedAdditionaCategories)
2507
+ }}
2508
+ >
2509
+ Remove
2510
+ </button>
2511
+ <TextField
2512
+ value={val}
2513
+ label='Category'
2514
+ section='legend'
2515
+ subsection={null}
2516
+ fieldName='additionalCategories'
2517
+ updateField={(section, subsection, fieldName, value) => {
2518
+ const updatedAdditionaCategories = [...config.legend.additionalCategories]
2519
+ updatedAdditionaCategories[i] = value
2520
+ updateField(section, subsection, fieldName, updatedAdditionaCategories)
2521
+ }}
2522
+ />
2523
+ </fieldset>
2524
+ ))}
2525
+ <button
2526
+ className={'btn full-width'}
2527
+ onClick={event => {
2528
+ event.preventDefault()
2529
+ const updatedAdditionaCategories = [...(config.legend.additionalCategories || [])]
2530
+ updatedAdditionaCategories.push('')
2531
+ updateField('legend', null, 'additionalCategories', updatedAdditionaCategories)
2532
+ }}
2533
+ >
2534
+ Add Category
2535
+ </button>
2536
+ </fieldset>
2537
+ )}
2538
+ </AccordionItemPanel>
2539
+ </AccordionItem>
2540
+ )}
2541
+ {/* End Columns */}
2542
+ {visHasLegend() && (
2543
+ <AccordionItem>
2544
+ <AccordionItemHeading>
2545
+ <AccordionItemButton>Legend</AccordionItemButton>
2546
+ </AccordionItemHeading>
2547
+ <AccordionItemPanel>
2548
+ <CheckBox value={config.legend.reverseLabelOrder} section='legend' fieldName='reverseLabelOrder' label='Reverse Labels' updateField={updateField} />
2549
+ {/* <fieldset className="checkbox-group">
2550
+ <CheckBox value={config.legend.dynamicLegend} section="legend" fieldName="dynamicLegend" label="Dynamic Legend" updateField={updateField}/>
2551
+ {config.legend.dynamicLegend && (
2552
+ <>
2553
+ <TextField value={config.legend.dynamicLegendDefaultText} section="legend" fieldName="dynamicLegendDefaultText" label="Dynamic Legend Default Text" updateField={updateField} />
2554
+ <TextField value={config.legend.dynamicLegendItemLimit} type="number" min="0" section="legend" fieldName="dynamicLegendItemLimit" label={'Dynamic Legend Limit'} className="number-narrow" updateField={updateField}/>
2555
+ <TextField value={config.legend.dynamicLegendItemLimitMessage} section="legend" fieldName="dynamicLegendItemLimitMessage" label="Dynamic Legend Item Limit Message" updateField={updateField} />
2556
+ <TextField value={config.legend.dynamicLegendChartMessage} section="legend" fieldName="dynamicLegendChartMessage" label="Dynamic Legend Chart Message" updateField={updateField} />
2557
+ </>
2558
+ )}
2559
+ </fieldset> */}
2560
+ <CheckBox
2561
+ value={config.legend.hide ? true : false}
2562
+ section='legend'
2563
+ fieldName='hide'
2564
+ label='Hide Legend'
2565
+ updateField={updateField}
2566
+ tooltip={
2567
+ <Tooltip style={{ textTransform: 'none' }}>
2568
+ <Tooltip.Target>
2569
+ <Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
2570
+ </Tooltip.Target>
2571
+ <Tooltip.Content>
2572
+ <p>With a single-series chart, consider hiding the legend to reduce visual clutter.</p>
2573
+ </Tooltip.Content>
2574
+ </Tooltip>
2575
+ }
2576
+ />
2577
+ {/* {config.visualizationType === 'Box Plot' &&
2578
+ <>
2579
+ <CheckBox value={config.boxplot.legend.displayHowToReadText} fieldName='displayHowToReadText' section='boxplot' subsection='legend' label='Display How To Read Text' updateField={updateField} />
2580
+ <TextField type='textarea' value={config.boxplot.legend.howToReadText} updateField={updateField} fieldName='howToReadText' section='boxplot' subsection='legend' label='How to read text' />
2581
+ </>
2582
+ } */}
2583
+ {config.visualizationType === 'Line' && <CheckBox value={config.legend.lineMode} section='legend' fieldName='lineMode' label='Show Lined Style Legend' updateField={updateField} />}
2584
+ {config.visualizationType === 'Bar' && config.visualizationSubType === 'regular' && config.runtime.seriesKeys.length === 1 && (
2585
+ <Select value={config.legend.colorCode} section='legend' fieldName='colorCode' label='Color code by category' initial='Select' updateField={updateField} options={getDataValueOptions(data)} />
2586
+ )}
2587
+ <Select value={config.legend.behavior} section='legend' fieldName='behavior' label='Legend Behavior (When clicked)' updateField={updateField} options={['highlight', 'isolate']} />
2588
+ {config.legend.behavior === 'highlight' && config.tooltips.singleSeries && <CheckBox value={config.legend.highlightOnHover} section='legend' fieldName='highlightOnHover' label='HIGHLIGHT DATA SERIES ON HOVER' updateField={updateField} />}
2589
+ <TextField value={config.legend.label} section='legend' fieldName='label' label='Title' updateField={updateField} />
2590
+ <Select value={config.legend.position} section='legend' fieldName='position' label='Position' updateField={updateField} options={['right', 'left', 'bottom']} />
2591
+ {config.legend.position === 'bottom' && (
2592
+ <>
2593
+ <CheckBox value={config.legend.singleRow} section='legend' fieldName='singleRow' label='Single Row Legend' updateField={updateField} />
2594
+ <CheckBox value={config.legend.verticalSorted} section='legend' fieldName='verticalSorted' label='Vertical sorted Legend' updateField={updateField} />
2595
+ </>
2596
+ )}
2597
+ <TextField type='textarea' value={config.legend.description} updateField={updateField} section='legend' fieldName='description' label='Legend Description' />
2598
+ </AccordionItemPanel>
2599
+ </AccordionItem>
2600
+ )}
2601
+ {visSupportsFilters() && (
2602
+ <AccordionItem>
2603
+ <AccordionItemHeading>
2604
+ <AccordionItemButton>Filters</AccordionItemButton>
2605
+ </AccordionItemHeading>
2606
+ <AccordionItemPanel>
2607
+ {config.filters && (
2608
+ <>
2609
+ {/* prettier-ignore */}
2610
+ <Select
2611
+ value={config.filterBehavior}
2612
+ fieldName='filterBehavior'
2613
+ label='Filter Behavior'
2614
+ updateField={updateField}
2615
+ options={['Apply Button', 'Filter Change']}
2616
+ tooltip={
2617
+ <Tooltip style={{ textTransform: 'none' }}>
2618
+ <Tooltip.Target>
2619
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2620
+ </Tooltip.Target>
2621
+ <Tooltip.Content>
2622
+ <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>
2623
+ </Tooltip.Content>
2624
+ </Tooltip>
2625
+ }
2626
+ />
2627
+ <br />
2628
+ </>
2629
+ )}
2630
+ {config.filters && (
2631
+ <ul className='filters-list'>
2632
+ {/* Whether filters should apply onChange or Apply Button */}
2633
+
2634
+ {config.filters.map((filter, index) => {
2635
+ if (filter.type === 'url') return <></>
2636
+
2637
+ return (
2638
+ <fieldset className='edit-block' key={index}>
2639
+ <button
2640
+ type='button'
2641
+ className='remove-column'
2642
+ onClick={() => {
2643
+ removeFilter(index)
2644
+ }}
2645
+ >
2646
+ Remove
2647
+ </button>
2648
+ <label>
2649
+ <span className='edit-label column-heading'>Filter</span>
2650
+ <select
2651
+ value={filter.columnName}
2652
+ onChange={e => {
2653
+ updateFilterProp('columnName', index, e.target.value)
2654
+ }}
2655
+ >
2656
+ <option value=''>- Select Option -</option>
2657
+ {getFilters().map((dataKey, index) => (
2658
+ <option value={dataKey} key={index}>
2659
+ {dataKey}
2660
+ </option>
2661
+ ))}
2662
+ </select>
2663
+ </label>
2664
+
2665
+ <label>
2666
+ <span className='edit-showDropdown column-heading'>Show Filter Input</span>
2667
+ <input
2668
+ type='checkbox'
2669
+ checked={filter.showDropdown === undefined ? true : filter.showDropdown}
2670
+ onChange={e => {
2671
+ updateFilterProp('showDropdown', index, e.target.checked)
2672
+ }}
2673
+ />
2674
+ </label>
2675
+
2676
+ <label>
2677
+ <span className='edit-label column-heading'>Filter Style</span>
2678
+
2679
+ <select
2680
+ value={filter.filterStyle}
2681
+ onChange={e => {
2682
+ updateFilterProp('filterStyle', index, e.target.value)
2683
+ }}
2684
+ >
2685
+ {filterStyleOptions.map((item, index) => {
2686
+ return (
2687
+ <option key={`filter-style-${index}`} value={item}>
2688
+ {item}
2689
+ </option>
2690
+ )
2691
+ })}
2692
+ </select>
2693
+ </label>
2694
+ <label>
2695
+ <span className='edit-label column-heading'>Label</span>
2696
+ <input
2697
+ type='text'
2698
+ value={filter.label}
2699
+ onChange={e => {
2700
+ updateFilterProp('label', index, e.target.value)
2701
+ }}
2702
+ />
2703
+ </label>
2704
+
2705
+ <label>
2706
+ <span className='edit-filterOrder column-heading'>Filter Order</span>
2707
+ <select value={filter.order ? filter.order : 'asc'} onChange={e => updateFilterProp('order', index, e.target.value)}>
2708
+ {filterOrderOptions.map((option, index) => {
2709
+ return (
2710
+ <option value={option.value} key={`filter-${index}`}>
2711
+ {option.label}
2712
+ </option>
2713
+ )
2714
+ })}
2715
+ </select>
2716
+
2717
+ {filter.order === 'cust' && (
2718
+ <DragDropContext onDragEnd={({ source, destination }) => handleFilterOrder(source.index, destination.index, index, config.filters[index])}>
2719
+ <Droppable droppableId='filter_order'>
2720
+ {provided => (
2721
+ <ul {...provided.droppableProps} className='sort-list' ref={provided.innerRef} style={{ marginTop: '1em' }}>
2722
+ {config.filters[index]?.values.map((value, index) => {
2723
+ return (
2724
+ <Draggable key={value} draggableId={`draggableFilter-${value}`} index={index}>
2725
+ {(provided, snapshot) => (
2726
+ <li>
2727
+ <div className={snapshot.isDragging ? 'currently-dragging' : ''} style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)} ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
2728
+ {value}
2729
+ </div>
2730
+ </li>
2731
+ )}
2732
+ </Draggable>
2733
+ )
2734
+ })}
2735
+ {provided.placeholder}
2736
+ </ul>
2737
+ )}
2738
+ </Droppable>
2739
+ </DragDropContext>
2740
+ )}
2741
+ </label>
2742
+ </fieldset>
2743
+ )
2744
+ })}
2745
+ </ul>
2746
+ )}
2747
+ {!config.filters && <p style={{ textAlign: 'center' }}>There are currently no filters.</p>}
2748
+ <button type='button' onClick={addNewFilter} className='btn full-width'>
2749
+ Add Filter
2750
+ </button>
2751
+ </AccordionItemPanel>
2752
+ </AccordionItem>
2753
+ )}
2754
+ <Panels.Visual name='Visual' />
2755
+ {/* Spark Line has no data table */}
2756
+ {config.visualizationType !== 'Spark Line' && (
2757
+ <AccordionItem>
2758
+ <AccordionItemHeading>
2759
+ <AccordionItemButton>Data Table</AccordionItemButton>
2760
+ </AccordionItemHeading>
2761
+ <AccordionItemPanel>
2762
+ <DataTableEditor config={config} columns={Object.keys(data[0] || {})} updateField={updateField} isDashboard={isDashboard} isLoadedFromUrl={isLoadedFromUrl} />{' '}
2763
+ </AccordionItemPanel>
2764
+ </AccordionItem>
2765
+ )}
2766
+ {/* {(config.visualizationType === 'Bar' || config.visualizationType === 'Line') && <Panels.DateHighlighting name='Date Highlighting' />} */}
2767
+ </Accordion>
2768
+ {config.type !== 'Spark Line' && <AdvancedEditor loadConfig={updateConfig} state={config} convertStateToConfig={convertStateToConfig} />}
2769
+ </section>
2770
+ </section>
2771
+ </ErrorBoundary>
2772
+ </EditorPanelContext.Provider>
2773
+ )
2774
+ }
2775
+
2776
+ export default EditorPanel