@cdc/chart 1.3.2 → 1.3.4

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 (36) hide show
  1. package/dist/cdcchart.js +77 -4
  2. package/examples/age-adjusted-rates.json +1218 -0
  3. package/examples/case-rate-example-config.json +36 -0
  4. package/examples/case-rate-example-data.json +33602 -0
  5. package/examples/date-exclusions-config.json +62 -0
  6. package/examples/date-exclusions-data.json +162 -0
  7. package/examples/horizontal-chart.json +35 -0
  8. package/examples/horizontal-stacked-bar-chart.json +36 -0
  9. package/examples/line-chart.json +76 -0
  10. package/examples/paired-bar-data.json +14 -0
  11. package/examples/paired-bar-example.json +48 -0
  12. package/examples/paired-bar-formatted.json +37 -0
  13. package/examples/planet-chart-horizontal-example-config.json +35 -0
  14. package/examples/planet-example-config.json +1 -0
  15. package/examples/private/newtest.csv +101 -0
  16. package/examples/private/test.json +10124 -0
  17. package/package.json +9 -5
  18. package/src/CdcChart.tsx +417 -149
  19. package/src/components/BarChart.tsx +431 -24
  20. package/src/components/BarStackVertical.js +0 -0
  21. package/src/components/DataTable.tsx +55 -28
  22. package/src/components/EditorPanel.js +914 -260
  23. package/src/components/LineChart.tsx +4 -3
  24. package/src/components/LinearChart.tsx +258 -88
  25. package/src/components/PairedBarChart.tsx +144 -0
  26. package/src/components/PieChart.tsx +30 -16
  27. package/src/components/SparkLine.js +206 -0
  28. package/src/data/initial-state.js +59 -32
  29. package/src/hooks/useActiveElement.js +19 -0
  30. package/src/hooks/useColorPalette.ts +83 -0
  31. package/src/hooks/useReduceData.ts +43 -0
  32. package/src/index.html +49 -13
  33. package/src/index.tsx +6 -2
  34. package/src/scss/editor-panel.scss +12 -4
  35. package/src/scss/main.scss +112 -3
  36. package/LICENSE +0 -201
@@ -1,5 +1,5 @@
1
1
  import React, { useState, useEffect, useCallback, memo, useContext } from 'react'
2
- import ReactTooltip from 'react-tooltip'
2
+ import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'
3
3
 
4
4
  import {
5
5
  Accordion,
@@ -7,104 +7,112 @@ import {
7
7
  AccordionItemHeading,
8
8
  AccordionItemPanel,
9
9
  AccordionItemButton,
10
- } from 'react-accessible-accordion';
11
- import { useDebounce } from 'use-debounce';
10
+ } from 'react-accessible-accordion'
12
11
 
13
- import Context from '../context';
14
- import WarningImage from '../images/warning.svg';
12
+ import { timeParse, timeFormat } from 'd3-time-format'
13
+ import { useDebounce, useDebouncedCallback } from 'use-debounce'
15
14
 
16
- import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
17
- import Waiting from '@cdc/core/components/Waiting';
18
- import QuestionIcon from '@cdc/core/assets/question-circle.svg';
15
+ import Context from '../context'
16
+ import WarningImage from '../images/warning.svg'
17
+ import AdvancedEditor from '@cdc/core/components/AdvancedEditor';
19
18
 
20
- const Helper = ({text}) => {
21
- return (
22
- <span className='tooltip helper' data-tip={text}>
23
- <QuestionIcon />
24
- </span>
25
- )
26
- }
19
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
20
+ import { useColorPalette } from '../hooks/useColorPalette'
21
+
22
+ import InputCheckbox from '@cdc/core/components/inputs/InputCheckbox';
23
+ import InputToggle from '@cdc/core/components/inputs/InputToggle';
24
+ import Tooltip from '@cdc/core/components/ui/Tooltip'
25
+ import Icon from '@cdc/core/components/ui/Icon'
26
+ import useReduceData from '../hooks/useReduceData';
27
27
 
28
- const TextField = memo(({label, section = null, subsection = null, fieldName, updateField, value: stateValue, type = "input", i = null, min = null, ...attributes}) => {
28
+ const TextField = memo(({label, tooltip, section = null, subsection = null, fieldName, updateField, value: stateValue, type = "input", i = null, min = null, ...attributes}) => {
29
29
  const [ value, setValue ] = useState(stateValue);
30
30
 
31
31
  const [ debouncedValue ] = useDebounce(value, 500);
32
32
 
33
33
  useEffect(() => {
34
- if('string' === typeof debouncedValue && stateValue !== debouncedValue ) {
34
+ if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
35
35
  updateField(section, subsection, fieldName, debouncedValue, i)
36
36
  }
37
- }, [debouncedValue])
37
+ }, [ debouncedValue ])
38
38
 
39
- let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`;
39
+ let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
40
40
 
41
41
  const onChange = (e) => {
42
- if('number' !== type || min === null){
43
- setValue(e.target.value);
42
+
43
+ if ('number' !== type || min === null) {
44
+ setValue(e.target.value)
44
45
  } else {
45
- if(!e.target.value || min <= parseFloat(e.target.value)){
46
- setValue(e.target.value);
46
+ if (!e.target.value || min <= parseFloat(e.target.value)) {
47
+ setValue(e.target.value)
47
48
  } else {
48
- setValue(min.toString());
49
+ setValue(min.toString())
49
50
  }
50
51
  }
51
- };
52
+ }
52
53
 
53
- let formElement = <input type="text" name={name} onChange={onChange} {...attributes} value={value} />
54
+ let formElement = <input type="text" name={name} onChange={onChange} {...attributes} value={value}/>
54
55
 
55
- if('textarea' === type) {
56
+ if ('textarea' === type) {
56
57
  formElement = (
57
58
  <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
58
59
  )
59
60
  }
60
61
 
61
- if('number' === type) {
62
- formElement = <input type="number" name={name} onChange={onChange} {...attributes} value={value} />
62
+ if ('number' === type) {
63
+ formElement = <input type="number" name={name} onChange={onChange} {...attributes} value={value}/>
64
+ }
65
+
66
+ if ('date' === type) {
67
+ formElement = <input type="date" name={name} onChange={onChange} {...attributes} value={value}/>
63
68
  }
64
69
 
65
70
  return (
66
71
  <label>
67
- <span className="edit-label column-heading">{label}</span>
72
+ <span className="edit-label column-heading">{label}{tooltip}</span>
68
73
  {formElement}
69
74
  </label>
70
75
  )
71
76
  })
72
77
 
73
- const CheckBox = memo(({label, value, fieldName, section = null, subsection = null, updateField, ...attributes}) => (
78
+ const CheckBox = memo(({ label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes }) => (
74
79
  <label className="checkbox">
75
- <input type="checkbox" name={fieldName} checked={ value } onChange={() => { updateField(section, subsection, fieldName, !value) }} {...attributes}/>
76
- <span className="edit-label">{label}</span>
77
- {section === 'table' && fieldName === 'show' && <Helper text=" Hiding the data table may affect accessibility. An alternate form of accessing visualization data is a 508 requirement." />}
80
+ <input type="checkbox" name={fieldName} checked={value} onChange={() => {
81
+ updateField(section, subsection, fieldName, !value)
82
+ }} {...attributes}/>
83
+ <span className="edit-label">{label}{tooltip}</span>
78
84
  </label>
79
85
  ))
80
86
 
81
- const Select = memo(({label, value, options, fieldName, section = null, subsection = null, required = false, updateField, initial: initialValue, ...attributes}) => {
82
- let optionsJsx = options.map(optionName => <option value={optionName} key={optionName}>{optionName}</option>)
87
+ const Select = memo(({ label, value, options, fieldName, section = null, subsection = null, required = false, tooltip, updateField, initial: initialValue, ...attributes }) => {
88
+ let optionsJsx = options.map((optionName, index) => <option value={optionName} key={index}>{optionName}</option>)
83
89
 
84
- if(initialValue) {
90
+ if (initialValue) {
85
91
  optionsJsx.unshift(<option value="" key="initial">{initialValue}</option>)
86
92
  }
87
93
 
88
94
  return (
89
95
  <label>
90
- <span className="edit-label">{label}</span>
91
- <select className={required && !value ? 'warning' : ''} name={fieldName} value={value} onChange={(event) => { updateField(section, subsection, fieldName, event.target.value) }} {...attributes}>
96
+ <span className="edit-label">{label}{tooltip}</span>
97
+ <select className={required && !value ? 'warning' : ''} name={fieldName} value={value} onChange={(event) => {
98
+ updateField(section, subsection, fieldName, event.target.value)
99
+ }} {...attributes}>
92
100
  {optionsJsx}
93
101
  </select>
94
102
  </label>
95
103
  )
96
104
  })
97
105
 
98
- const Regions = memo(({config, updateConfig}) => {
106
+ const Regions = memo(({ config, updateConfig }) => {
99
107
  let regionUpdate = (fieldName, value, i) => {
100
108
  let regions = []
101
109
 
102
- if(config.regions) {
103
- regions = [...config.regions]
110
+ if (config.regions) {
111
+ regions = [ ...config.regions ]
104
112
  }
105
113
 
106
114
  regions[i][fieldName] = value
107
- updateConfig({...config, regions})
115
+ updateConfig({ ...config, regions })
108
116
  }
109
117
 
110
118
  let updateField = (section, subsection, fieldName, value, i) => regionUpdate(fieldName, value, i)
@@ -112,168 +120,276 @@ const Regions = memo(({config, updateConfig}) => {
112
120
  let removeColumn = (i) => {
113
121
  let regions = []
114
122
 
115
- if(config.regions) {
116
- regions = [...config.regions]
123
+ if (config.regions) {
124
+ regions = [ ...config.regions ]
117
125
  }
118
126
 
119
127
  regions.splice(i, 1)
120
128
 
121
- updateConfig({...config, regions})
129
+ updateConfig({ ...config, regions })
122
130
  }
123
131
 
124
132
  let addColumn = () => {
133
+
125
134
  let regions = []
126
135
 
127
- if(config.regions) {
128
- regions = [...config.regions]
136
+ if (config.regions) {
137
+ regions = [ ...config.regions ]
129
138
  }
130
139
 
131
140
  regions.push({})
132
141
 
133
- updateConfig({...config, regions})
142
+ updateConfig({ ...config, regions })
134
143
  }
135
144
 
136
145
  return (
137
146
  <>
138
- {config.regions && config.regions.map(({label, color, from, to, background}, i) => (
147
+ {config.regions && config.regions.map(({ label, color, from, to, background }, i) => (
139
148
  <div className="edit-block" key={`region-${i}`}>
140
- <button className="remove-column" onClick={(event) => { event.preventDefault(); removeColumn(i)}}>Remove</button>
141
- <TextField value={label} label="Region Label" fieldName="label" i={i} updateField={updateField} />
149
+ <button type="button" className="remove-column" onClick={(event) => {
150
+ event.preventDefault()
151
+ removeColumn(i)
152
+ }}>Remove
153
+ </button>
154
+ <TextField value={label} label="Region Label" fieldName="label" i={i} updateField={updateField}/>
142
155
  <div className="two-col-inputs">
143
- <TextField value={color} label="Text Color" fieldName="color" updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)} />
144
- <TextField value={background} label="Background" fieldName="background" updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)} />
156
+ <TextField value={color} label="Text Color" fieldName="color" updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}/>
157
+ <TextField value={background} label="Background" fieldName="background" updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}/>
145
158
  </div>
146
159
  <div className="two-col-inputs">
147
- <TextField value={from} label="From Value" fieldName="from" updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)} />
148
- <TextField value={to} label="To Value" fieldName="to" updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)} />
160
+ <TextField value={from} label="From Value" fieldName="from" updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}/>
161
+ <TextField value={to} label="To Value" fieldName="to" updateField={(section, subsection, fieldName, value) => regionUpdate(fieldName, value, i)}/>
149
162
  </div>
150
163
  </div>
151
164
  ))}
152
- {!config.regions && <p style={{textAlign: "center"}}>There are currently no regions.</p>}
153
- <button className="btn full-width" onClick={(e) => {e.preventDefault(); addColumn()}}>Add Region</button>
165
+ {!config.regions && <p style={{ textAlign: 'center' }}>There are currently no regions.</p>}
166
+ <button type="button" className="btn full-width" onClick={(e) => {
167
+ e.preventDefault()
168
+ addColumn()
169
+ }}>Add Region
170
+ </button>
154
171
  </>
155
172
  )
156
173
  })
157
174
 
158
- const headerColors = ['theme-blue','theme-purple','theme-brown','theme-teal','theme-pink','theme-orange','theme-slate','theme-indigo','theme-cyan','theme-green','theme-amber']
175
+ const headerColors = [ 'theme-blue', 'theme-purple', 'theme-brown', 'theme-teal', 'theme-pink', 'theme-orange', 'theme-slate', 'theme-indigo', 'theme-cyan', 'theme-green', 'theme-amber' ]
159
176
 
160
177
  const EditorPanel = () => {
161
178
  const {
162
179
  config,
163
180
  updateConfig,
181
+ transformedData: data,
164
182
  loading,
165
183
  colorPalettes,
166
184
  unfilteredData,
185
+ excludedData,
186
+ transformedData,
167
187
  isDashboard,
168
188
  setParentConfig,
169
- missingRequiredSections
170
- } = useContext(Context);
189
+ missingRequiredSections,
190
+ setFilteredData
191
+ } = useContext(Context)
192
+
193
+ const {minValue,maxValue} = useReduceData(config,data)
194
+ const {paletteName,isPaletteReversed,filteredPallets,filteredQualitative,dispatch} = useColorPalette(colorPalettes,config);
195
+ useEffect(()=>{
196
+ if(paletteName) updateConfig({...config, palette:paletteName})
197
+ },[paletteName])
198
+
199
+ useEffect(()=>{
200
+ dispatch({type:"GET_PALETTE",payload:colorPalettes,paletteName:config.palette})
201
+ },[dispatch,config.palette]);
202
+
203
+ useEffect(() => {
204
+ dispatch({ type: 'GET_PALETTE', payload: colorPalettes, paletteName: config.palette })
205
+ }, [ dispatch, config.palette ])
206
+
207
+
208
+ const filterOptions = [
209
+ {
210
+ label: 'Ascending Alphanumeric',
211
+ value: 'asc'
212
+ },
213
+ {
214
+ label: 'Descending Alphanumeric',
215
+ value: 'desc'
216
+ },
217
+ {
218
+ label: 'Custom',
219
+ value: 'cust'
220
+ }
221
+ ]
222
+
223
+ const getItemStyle = (isDragging, draggableStyle) => ({
224
+ ...draggableStyle,
225
+ })
226
+
227
+ const sortableItemStyles = {
228
+ display: 'block',
229
+ boxSizing: 'border-box',
230
+ border: '1px solid #D1D1D1',
231
+ borderRadius: '2px',
232
+ background: '#F1F1F1',
233
+ padding: '.4em .6em',
234
+ fontSize: '.8em',
235
+ marginRight: '.3em',
236
+ marginBottom: '.3em',
237
+ cursor: 'move',
238
+ zIndex: '999',
239
+ }
171
240
 
172
241
  let hasLineChart = false
173
242
 
174
243
  const enforceRestrictions = (updatedConfig) => {
175
- if(updatedConfig.visualizationSubType === 'horizontal'){
176
- updatedConfig.labels = false;
244
+ if (updatedConfig.orientation === 'horizontal') {
245
+ updatedConfig.labels = false
177
246
  }
178
- if(updatedConfig.table.show === undefined){
179
- updatedConfig.table.show = !isDashboard;
247
+ if (updatedConfig.table.show === undefined) {
248
+ updatedConfig.table.show = !isDashboard
180
249
  }
181
- };
250
+ }
182
251
 
183
252
  const updateField = (section, subsection, fieldName, newValue) => {
184
253
  // Top level
185
- if( null === section && null === subsection) {
186
- let updatedConfig = {...config, [fieldName]: newValue};
254
+ if (null === section && null === subsection) {
255
+ let updatedConfig = { ...config, [fieldName]: newValue }
187
256
 
188
- enforceRestrictions(updatedConfig);
257
+ enforceRestrictions(updatedConfig)
189
258
 
190
- updateConfig(updatedConfig);
259
+ updateConfig(updatedConfig)
191
260
  return
192
261
  }
193
262
 
194
- const isArray = Array.isArray(config[section]);
263
+ const isArray = Array.isArray(config[section])
195
264
 
196
- let sectionValue = isArray ? [...config[section], newValue] : {...config[section], [fieldName]: newValue};
265
+ let sectionValue = isArray ? [ ...config[section], newValue ] : { ...config[section], [fieldName]: newValue }
197
266
 
198
- if(null !== subsection) {
199
- if(isArray) {
200
- sectionValue = [...config[section]]
201
- sectionValue[subsection] = {...sectionValue[subsection], [fieldName]: newValue}
202
- } else if(typeof newValue === "string") {
267
+ if (null !== subsection) {
268
+ if (isArray) {
269
+ sectionValue = [ ...config[section] ]
270
+ sectionValue[subsection] = { ...sectionValue[subsection], [fieldName]: newValue }
271
+ } else if (typeof newValue === 'string') {
203
272
  sectionValue[subsection] = newValue
204
273
  } else {
205
- sectionValue = {...config[section], [subsection]: { ...config[section][subsection], [fieldName]: newValue}}
274
+ sectionValue = { ...config[section], [subsection]: { ...config[section][subsection], [fieldName]: newValue } }
206
275
  }
207
276
  }
208
277
 
209
- let updatedConfig = {...config, [section]: sectionValue};
278
+ let updatedConfig = { ...config, [section]: sectionValue }
210
279
 
211
- enforceRestrictions(updatedConfig);
280
+ enforceRestrictions(updatedConfig)
212
281
 
213
282
  updateConfig(updatedConfig)
214
283
  }
215
284
 
216
- const [ addSeries, setAddSeries ] = useState('');
217
- const [ displayPanel, setDisplayPanel ] = useState(true);
285
+ const [ displayPanel, setDisplayPanel ] = useState(true)
286
+ const [ lollipopColorStyle, setLollipopColorStyle ] = useState('two-tone')
218
287
 
219
- if(loading) {
288
+ if (loading) {
220
289
  return null
221
290
  }
222
291
 
292
+ const setLollipopShape = (shape) => {
293
+ updateConfig({
294
+ ...config,
295
+ lollipopShape: shape
296
+ })
297
+ }
298
+
223
299
  const removeFilter = (index) => {
224
- let filters = [...config.filters];
300
+ let filters = [ ...config.filters ]
225
301
 
226
- filters.splice(index, 1);
302
+ filters.splice(index, 1)
227
303
 
228
- updateConfig({...config, filters})
304
+ updateConfig({ ...config, filters })
229
305
  }
230
306
 
231
307
  const updateFilterProp = (name, index, value) => {
232
- let filters = [...config.filters];
308
+ let filters = [ ...config.filters ]
233
309
 
234
- filters[index][name] = value;
310
+ filters[index][name] = value
235
311
 
236
- updateConfig({...config, filters});
312
+ updateConfig({ ...config, filters })
237
313
  }
238
314
 
239
315
  const addNewFilter = () => {
240
- let filters = config.filters ? [...config.filters] : [];
316
+ let filters = config.filters ? [ ...config.filters ] : []
241
317
 
242
- filters.push({values: []});
318
+ filters.push({ values: [] })
243
319
 
244
- updateConfig({...config, filters});
320
+ updateConfig({ ...config, filters })
321
+ }
322
+
323
+ const addNewSeries = (seriesKey) => {
324
+ let newSeries = config.series ? [ ...config.series ] : []
325
+ newSeries.push({ dataKey: seriesKey, type: 'Bar' })
326
+ updateConfig({ ...config, series: newSeries })
245
327
  }
246
328
 
247
329
  const removeSeries = (seriesKey) => {
248
- let series = [...config.series]
249
- let seriesIndex = -1;
250
330
 
251
- for(let i = 0; i < series.length; i++){
252
- if(series[i].dataKey === seriesKey){
253
- seriesIndex = i;
254
- break;
331
+
332
+ let series = [ ...config.series ]
333
+ let seriesIndex = -1
334
+
335
+ for (let i = 0; i < series.length; i++) {
336
+ if (series[i].dataKey === seriesKey) {
337
+ seriesIndex = i
338
+ break
255
339
  }
256
340
  }
257
341
 
258
- if(seriesIndex !== -1){
342
+ if (seriesIndex !== -1) {
259
343
  series.splice(seriesIndex, 1)
260
344
 
261
- let newConfig = {...config, series}
345
+ let newConfig = { ...config, series }
262
346
 
263
- if(series.length === 0) {
347
+ if (series.length === 0) {
264
348
  delete newConfig.series
265
349
  }
266
350
 
267
351
  updateConfig(newConfig)
268
352
  }
353
+
354
+ if (config.visualizationType === 'Paired Bar') {
355
+ updateConfig({
356
+ ...config,
357
+ series: []
358
+ })
359
+ }
269
360
  }
270
361
 
271
- const addNewSeries = (seriesKey) => {
272
- let newSeries = config.series ? [...config.series] : []
362
+ const addNewExclusion = (exclusionKey) => {
363
+ let newExclusion = [ ...config.exclusions.keys ]
364
+ newExclusion.push(exclusionKey)
273
365
 
274
- newSeries.push({dataKey: seriesKey, type: 'Bar'})
366
+ let payload = { ...config.exclusions, keys: newExclusion }
367
+ updateConfig({ ...config, exclusions: payload })
368
+ }
275
369
 
276
- updateConfig({...config, series: newSeries})
370
+ const removeExclusion = (excludeValue) => {
371
+ let exclusionsIndex = -1
372
+ let exclusions = [ ...config.exclusions.keys ]
373
+
374
+ for (let i = 0; i < exclusions.length; i++) {
375
+ if (exclusions[i] === excludeValue) {
376
+ exclusionsIndex = i
377
+ break
378
+ }
379
+ }
380
+
381
+ if (exclusionsIndex !== -1) {
382
+ exclusions.splice(exclusionsIndex, 1)
383
+
384
+ let newExclusions = { ...config.exclusions, keys: exclusions }
385
+ let newExclusionsPayload = { ...config, exclusions: newExclusions }
386
+
387
+ if (exclusions.length === 0) {
388
+ delete newExclusionsPayload.exclusions.keys
389
+ }
390
+
391
+ updateConfig(newExclusionsPayload)
392
+ }
277
393
  }
278
394
 
279
395
  const getColumns = (filter = true) => {
@@ -283,9 +399,19 @@ const EditorPanel = () => {
283
399
  Object.keys(row).forEach(columnName => columns[columnName] = true)
284
400
  })
285
401
 
286
- if(filter) {
402
+ if (filter) {
403
+ let confidenceUpper = config.confidenceKeys?.upper && config.confidenceKeys?.upper !== ''
404
+ let confidenceLower = config.confidenceKeys?.lower && config.confidenceKeys?.lower !== ''
405
+
287
406
  Object.keys(columns).forEach(key => {
288
- if((config.series && config.series.filter(series => series.dataKey === key).length > 0) || (config.confidenceKeys && Object.keys(config.confidenceKeys).includes(key)) ) {
407
+ if (
408
+ (config.series && config.series.filter(series => series.dataKey === key).length > 0) ||
409
+ (config.confidenceKeys && Object.keys(config.confidenceKeys).includes(key))
410
+ /*
411
+ TODO: Resolve errors when config keys exist, but have no value
412
+ Proposal: (((confidenceUpper && confidenceLower) || confidenceUpper || confidenceLower) && Object.keys(config.confidenceKeys).includes(key))
413
+ */
414
+ ) {
289
415
  delete columns[key]
290
416
  }
291
417
  })
@@ -294,8 +420,26 @@ const EditorPanel = () => {
294
420
  return Object.keys(columns)
295
421
  }
296
422
 
423
+ const getDataValues = (dataKey, unique = false) => {
424
+ let values = []
425
+ excludedData.map(e => {
426
+ values.push(e[dataKey])
427
+ })
428
+ return unique ? [ ...new Set(values) ] : values
429
+ }
430
+
431
+ // when to show lollipop checkbox.
432
+ // update as the need grows (ie. vertical bars, divergeing, etc.)
433
+ const showLollipopCheckbox = () => {
434
+ if (config.visualizationType === 'Bar' && (config.orientation === 'horizontal' || config.orientation === 'regular') && config.visualizationSubType !== 'stacked') {
435
+ return true
436
+ } else {
437
+ return false
438
+ }
439
+ }
440
+
297
441
  const onBackClick = () => {
298
- setDisplayPanel(!displayPanel);
442
+ setDisplayPanel(!displayPanel)
299
443
  }
300
444
 
301
445
  const Error = () => {
@@ -306,7 +450,7 @@ const EditorPanel = () => {
306
450
  <p>{config.runtime.editorErrorMessage}</p>
307
451
  </section>
308
452
  </section>
309
- );
453
+ )
310
454
 
311
455
  }
312
456
 
@@ -314,7 +458,7 @@ const EditorPanel = () => {
314
458
  const confirmDone = (e) => {
315
459
  e.preventDefault()
316
460
 
317
- let newConfig = {...config}
461
+ let newConfig = { ...config }
318
462
  delete newConfig.newViz
319
463
 
320
464
  updateConfig(newConfig)
@@ -325,15 +469,15 @@ const EditorPanel = () => {
325
469
  <section className="waiting-container">
326
470
  <h3>Finish Configuring</h3>
327
471
  <p>Set all required options to the left and confirm below to display a preview of the chart.</p>
328
- <button className="btn" style={{margin: '1em auto'}} disabled={missingRequiredSections()} onClick={confirmDone}>I'm Done</button>
472
+ <button className="btn" style={{ margin: '1em auto' }} disabled={missingRequiredSections()} onClick={confirmDone}>I'm Done</button>
329
473
  </section>
330
474
  </section>
331
- );
475
+ )
332
476
  }
333
477
 
334
478
  const convertStateToConfig = () => {
335
479
  let strippedState = JSON.parse(JSON.stringify(config))
336
- if(false === missingRequiredSections()) {
480
+ if (false === missingRequiredSections()) {
337
481
  delete strippedState.newViz
338
482
  }
339
483
  delete strippedState.runtime
@@ -343,21 +487,170 @@ const EditorPanel = () => {
343
487
 
344
488
  useEffect(() => {
345
489
  // Pass up to Editor if needed
346
- if(setParentConfig) {
490
+ if (setParentConfig) {
347
491
  const newConfig = convertStateToConfig()
348
492
  setParentConfig(newConfig)
349
493
  }
350
494
 
351
- // eslint-disable-next-line react-hooks/exhaustive-deps
352
- }, [config])
495
+ // eslint-disable-next-line react-hooks/exhaustive-deps
496
+ }, [ config ])
497
+
498
+ useEffect(() => {
499
+ if (config.orientation === 'horizontal') {
500
+ updateConfig({
501
+ ...config,
502
+ lollipopShape: config.lollipopShape
503
+ })
504
+ }
505
+ }, [ config.isLollipopChart, config.lollipopShape ])
506
+
507
+ const ExclusionsList = useCallback(() => {
508
+ const exclusions = [ ...config.exclusions.keys ]
509
+ return (
510
+ <ul className="series-list">
511
+ {exclusions.map((exclusion, index) => {
512
+ return (
513
+ <li key={exclusion}>
514
+ <div className="series-list__name" data-title={exclusion}>
515
+ <div className="series-list__name--text">
516
+ {exclusion}
517
+ </div>
518
+ </div>
519
+ <span className="series-list__remove" onClick={() => removeExclusion(exclusion)}>&#215;</span>
520
+ </li>
521
+ )
522
+ })}
523
+ </ul>
524
+ )
525
+ }, [ config ])
526
+
527
+ const ErrorWithLolliopChart = ({ message }) => {
528
+ return (
529
+ <section className="waiting">
530
+ <section className="waiting-container">
531
+ <h3>Error With Configuration</h3>
532
+ <p>{message}</p>
533
+ </section>
534
+ </section>
535
+ )
536
+ }
537
+ const handleFilterChange = (idx1, idx2, filterIndex, filter) => {
538
+
539
+ let filterOrder = filter.values
540
+ let [ movedItem ] = filterOrder.splice(idx1, 1)
541
+ filterOrder.splice(idx2, 0, movedItem)
542
+ let filters = [ ...config.filters ]
543
+ let filterItem = { ...config.filters[filterIndex] }
544
+ filterItem.active = filter.values[0]
545
+ filterItem.values = filterOrder
546
+ filterItem.order = 'cust'
547
+ filters[filterIndex] = filterItem
548
+ setFilteredData(filters)
549
+ }
550
+
551
+ if (config.isLollipopChart && config?.series?.length > 1) {
552
+ config.runtime.editorErrorMessage = 'Lollipop charts must use only one data series'
553
+ }
554
+
555
+ if (config.isLollipopChart && config?.series?.length === 0) {
556
+ config.runtime.editorErrorMessage = 'Add a data series'
557
+ }
558
+
559
+ const section = config.orientation === 'horizontal' ? 'xAxis' : 'yAxis'
560
+ const [warningMsg,updateWarningMsg] = useState({maxMsg:'',minMsg:''})
561
+
562
+ const onMaxChangeHandler = (e) => {
563
+ const enteredValue = e.target.value;
564
+
565
+ var existPositiveValue;
566
+ let value;
567
+
568
+ // loop through series keys
569
+ if (config.runtime.seriesKeys) {
570
+ for(let i = 0; i < config.runtime.seriesKeys.length; i++) {
571
+ existPositiveValue = data.some(d => d[config.runtime.seriesKeys[i]] >= 0);
572
+ }
573
+ }
574
+
575
+ // input >= max
576
+ if (Number(enteredValue) >= maxValue) {
577
+ value = enteredValue
578
+ updateWarningMsg(function(prevMsg){return{...prevMsg,maxMsg:''}})
579
+ }
580
+
581
+ // input < max && a positive number exists
582
+ if (Number(enteredValue)< maxValue && existPositiveValue) {
583
+ updateWarningMsg(function(presMsg){return{...presMsg,maxMsg:'Max value must be more than '+ maxValue}})
584
+ }
585
+
586
+ // input < max && all numbers negatice
587
+ if (Number(enteredValue) < maxValue && !existPositiveValue) {
588
+ updateWarningMsg(function(presMsg){return{...presMsg,maxMsg:'Value must be more than or equal to 0'}})
589
+ }
590
+ updateField(section, null, 'max', value)
591
+
592
+ if (!enteredValue.length) {
593
+ updateWarningMsg(function(prevMsg){return{...prevMsg,maxMsg:''}})
594
+ }
595
+ }
596
+
597
+ const onMinChangeHandler = (e) => {
598
+ const enteredValue = e.target.value;
599
+ let value;
600
+ if (config.visualizationType === 'Line') {
601
+ if (Number(enteredValue) > minValue) {
602
+ updateWarningMsg(function (presMsg) { return { ...presMsg, minMsg: 'Value must be less than ' + minValue}})
603
+ } else {
604
+ value = enteredValue
605
+ updateWarningMsg(function (presMsg) { return { ...presMsg, minMsg: '' } })
606
+ }
607
+ } else {
608
+ if (Number(enteredValue) > minValue) {
609
+ updateWarningMsg(function (presMsg) { return { ...presMsg, minMsg: 'Value must be less than '+ minValue }})
610
+ } else if (Number(enteredValue) > 0 ) {
611
+ updateWarningMsg(function (presMsg) { return { ...presMsg, minMsg: 'Value must be less than or equal to 0' }})
612
+ } else {
613
+ value = enteredValue
614
+ updateWarningMsg(function (presMsg) { return { ...presMsg, minMsg: '' }})
615
+ }
616
+
617
+ }
618
+ updateField(section, null, 'min', value)
619
+
620
+ if (!enteredValue.length) {
621
+ updateWarningMsg(function (presMsg) { return {...presMsg, minMsg: ''}})
622
+ }
623
+ }
624
+
353
625
 
626
+ useEffect(() => {
627
+ if (config[section].max && config[section].max < maxValue) {
628
+ updateField(section,null,'max',maxValue)
629
+ updateWarningMsg(function (presMsg) {return {...presMsg, maxMsg: `Entered value ${config[section].max} is not valid `}})
630
+ }
631
+ }, [data,maxValue])
632
+
633
+ useEffect(() => {
634
+ if (config.visualizationType === 'Line') {
635
+ if (config[section].min && config[section].min > minValue) {
636
+ updateWarningMsg(function (presMsg) { return { ...presMsg, minMsg: `Entered value ${config[section].min} is not valid`}})
637
+ updateField(section,null,'min',minValue)
638
+ }
639
+ } else {
640
+ if (config[section].min && config[section].min < minValue) {
641
+ updateWarningMsg(function (presMsg) { return { ...presMsg, minMsg: `Entered value ${config[section].min} is not valid`}})
642
+ updateField(section,null,'min',minValue)
643
+ }
644
+ }
645
+ }, [data,minValue])
646
+
354
647
  return (
355
648
  <ErrorBoundary component="EditorPanel">
356
- {config.newViz && <Confirm />}
357
- {undefined === config.newViz && config.runtime && config.runtime.editorErrorMessage && <Error /> }
649
+ {config.newViz && <Confirm/>}
650
+ {undefined === config.newViz && config.runtime && config.runtime.editorErrorMessage && <Error/>}
358
651
  <button className={displayPanel ? `editor-toggle` : `editor-toggle collapsed`} title={displayPanel ? `Collapse Editor` : `Expand Editor`} onClick={onBackClick}></button>
359
- <section className={`${displayPanel ? 'editor-panel' : 'hidden editor-panel'}${isDashboard ? ' dashboard': ''}`}>
360
- <div className="heading-2">Configure Chart</div>
652
+ <section className={`${displayPanel ? 'editor-panel cove' : 'hidden editor-panel cove'}${isDashboard ? ' dashboard' : ''}`}>
653
+ <div aria-level="2" role="heading" className="heading-2">Configure Chart</div>
361
654
  <section className="form-container">
362
655
  <form>
363
656
  <Accordion allowZeroExpanded={true}>
@@ -368,141 +661,365 @@ const EditorPanel = () => {
368
661
  </AccordionItemButton>
369
662
  </AccordionItemHeading>
370
663
  <AccordionItemPanel>
371
- <Select value={config.visualizationType} fieldName="visualizationType" label="Chart Type" updateField={updateField} options={['Pie', 'Line', 'Bar', 'Combo']} />
372
- {config.visualizationType === "Bar" && <Select value={config.visualizationSubType || "Regular"} fieldName="visualizationSubType" label="Chart Subtype" updateField={updateField} options={['regular', 'stacked', 'horizontal']} />}
373
- <TextField value={config.title} fieldName="title" label="Title" updateField={updateField} />
374
- <TextField type="textarea" value={config.description} fieldName="description" label="Subtext" updateField={updateField} />
375
- <TextField type="number" value={config.height} fieldName="height" label="Chart Height" updateField={updateField} />
664
+ <Select value={config.visualizationType} fieldName="visualizationType" label="Chart Type" updateField={updateField} options={[ 'Pie', 'Line', 'Bar', 'Combo', 'Paired Bar']}/>
665
+ {config.visualizationType === 'Bar' && <Select value={config.visualizationSubType || 'Regular'} fieldName="visualizationSubType" label="Chart Subtype" updateField={updateField} options={[ 'regular', 'stacked' ]}/>}
666
+ {config.visualizationType === 'Bar' && <Select value={config.orientation || 'vertical'} fieldName="orientation" label="Orientation" updateField={updateField} options={[ 'vertical', 'horizontal' ]}/>}
667
+ {(config.visualizationType === 'Bar' && config.orientation === 'horizontal') &&
668
+ <Select value={config.yAxis.labelPlacement || 'Below Bar'} section="yAxis" fieldName="labelPlacement" label="Label Placement" updateField={updateField} options={[ 'Below Bar', 'On Date/Category Axis' ]}/>
669
+ }
670
+ {(showLollipopCheckbox()) &&
671
+ <CheckBox value={config.isLollipopChart} fieldName="isLollipopChart" label="Use lollipop styling" updateField={updateField} tooltip={
672
+ <Tooltip style={{ textTransform: 'none' }}>
673
+ <Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }}/></Tooltip.Target>
674
+ <Tooltip.Content>
675
+ <p>Select this option to replace each bar with a line and a dot at the end.</p>
676
+ </Tooltip.Content>
677
+ </Tooltip>
678
+ }/>
679
+ }
680
+ {config.orientation === 'horizontal' && (config.yAxis.labelPlacement === 'Below Bar' || config.yAxis.labelPlacement === 'On Date/Category Axis') &&
681
+ <CheckBox value={config.yAxis.displayNumbersOnBar} section="yAxis" fieldName="displayNumbersOnBar" label={config.isLollipopChart ? 'Display Numbers after Bar' : 'Display Numbers on Bar'} updateField={updateField}/>
682
+ }
683
+ {config.visualizationType === 'Pie' && <Select fieldName="pieType" label="Pie Chart Type" updateField={updateField} options={[ 'Regular', 'Donut' ]}/>}
684
+ <TextField value={config.title} fieldName="title" label="Title" updateField={updateField}/>
685
+
686
+ <TextField type="textarea" value={config.description} fieldName="description" label="Subtext" updateField={updateField} tooltip={
687
+ <Tooltip style={{ textTransform: 'none' }}>
688
+ <Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }}/></Tooltip.Target>
689
+ <Tooltip.Content>
690
+ <p>Enter supporting text to display below the data visualization, if applicable. The following HTML tags are supported: strong, em, sup, and sub.</p>
691
+ </Tooltip.Content>
692
+ </Tooltip>
693
+ }/>
694
+
695
+ {config.visualizationSubType !== 'horizontal' &&
696
+ <TextField type="number" value={config.height} fieldName="height" label="Chart Height" updateField={updateField}/>
697
+ }
376
698
  </AccordionItemPanel>
377
699
  </AccordionItem>
378
- {config.visualizationType !== "Pie" && <AccordionItem>
379
- <AccordionItemHeading>
380
- <AccordionItemButton>
381
- Data Series {(!config.series || config.series.length === 0) && <WarningImage width="25" className="warning-icon" />}
382
- </AccordionItemButton>
383
- </AccordionItemHeading>
384
- <AccordionItemPanel>
385
- {(!config.series || config.series.length === 0) && <p className="warning">At least one series is required</p>}
386
- {config.series && config.series.length !== 0 && (
387
- <>
388
- <label><span className="edit-label">Displaying</span></label>
389
- <ul className="series-list">
390
- {config.series.map((series, i) => {
391
- if(config.visualizationType === "Combo") {
392
- let changeType = (i, value) => {
393
- let series = [...config.series];
394
700
 
395
- series[i].type = value;
396
701
 
397
- updateConfig({...config, series})
702
+ {config.visualizationType !== 'Pie' &&
703
+ <AccordionItem>
704
+ <AccordionItemHeading>
705
+ <AccordionItemButton>
706
+ Data Series {((!config.series || config.series.length === 0) || (config.visualizationType === 'Paired Bar' && config.series.length < 2)) && <WarningImage width="25" className="warning-icon"/>}
707
+ </AccordionItemButton>
708
+ </AccordionItemHeading>
709
+ <AccordionItemPanel>
710
+ {((!config.series || config.series.length === 0) && (config.visualizationType !== 'Paired Bar')) && <p className="warning">At least one series is required</p>}
711
+ {((!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>}
712
+ {config.series && config.series.length !== 0 && (
713
+ <>
714
+ <label>
715
+ <span className="edit-label">
716
+ Displaying
717
+ <Tooltip style={{ textTransform: 'none' }}>
718
+ <Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }}/></Tooltip.Target>
719
+ <Tooltip.Content>
720
+ <p>A data series is a set of related data points plotted in a chart and typically represented in the chart legend.</p>
721
+ </Tooltip.Content>
722
+ </Tooltip>
723
+ </span>
724
+ </label>
725
+ <ul className="series-list">
726
+ {config.series.map((series, i) => {
727
+
728
+ if (config.visualizationType === 'Combo') {
729
+ let changeType = (i, value) => {
730
+ let series = [ ...config.series ]
731
+ series[i].type = value
732
+ updateConfig({ ...config, series })
733
+ }
734
+
735
+ let typeDropdown = (
736
+ <select value={series.type} onChange={(event) => {
737
+ changeType(i, event.target.value)
738
+ }} style={{ width: '100px', marginRight: '10px' }}>
739
+ <option value="" default>Select</option>
740
+ <option value="Bar">Bar</option>
741
+ <option value="Line">Line</option>
742
+ </select>
743
+ )
744
+
745
+ return (
746
+ <li key={series.dataKey}>
747
+ <div className={`series-list__name${series.dataKey.length > 15 ? ' series-list__name--truncate' : ''}`} data-title={series.dataKey}>
748
+ <div className="series-list__name-text">{series.dataKey}</div>
749
+ </div>
750
+ <span>
751
+ <span className="series-list__dropdown">{typeDropdown}</span>
752
+ {config.series.length > 1 &&
753
+ <span className="series-list__remove" onClick={() => removeSeries(series.dataKey)}>&#215;</span>
754
+ }
755
+ </span>
756
+ </li>
757
+ )
398
758
  }
399
759
 
400
- let typeDropdown = (
401
- <select value={series.type} onChange={(event) => { changeType(i, event.target.value) }} style={{width: "100px", marginRight: "10px"}}>
402
- <option value="" default>Select</option>
403
- <option value="Bar">Bar</option>
404
- <option value="Line">Line</option>
405
- </select>
406
- )
407
-
408
760
  return (
409
761
  <li key={series.dataKey}>
410
- <div className={`series-list__name${series.dataKey.length > 15 ? ' series-list__name--truncate' : ''}`} data-title={series.dataKey}>
411
- <div className="series-list__name-text">{series.dataKey}</div>
762
+ <div className="series-list__name" data-title={series.dataKey}>
763
+ <div className="series-list__name--text">
764
+ {series.dataKey}
765
+ </div>
412
766
  </div>
413
- <span>
414
- <span className="series-list__dropdown">{typeDropdown}</span>
767
+ {config.series.length > 1 &&
415
768
  <span className="series-list__remove" onClick={() => removeSeries(series.dataKey)}>&#215;</span>
416
- </span>
769
+ }
417
770
  </li>
418
771
  )
419
- }
420
- return (
421
- <li key={series.dataKey}>
422
- <div className="series-list__name" data-title={series.dataKey}>
423
- <div className="series-list__name--text">
424
- {series.dataKey}
425
- </div>
426
- </div>
427
- <span className="series-list__remove" onClick={() => removeSeries(series.dataKey)}>&#215;</span>
428
- </li>
429
- )
430
- })}
431
- </ul>
432
- </>)}
433
- <Select value={addSeries} fieldName="visualizationType" label="Add Data Series" initial="Select" onChange={(e) => { setAddSeries(e.target.value)}} options={getColumns()} />
434
- <button onClick={(e) => { e.preventDefault(); if(addSeries.length > 0) { addNewSeries(addSeries); } setAddSeries(''); }} className="btn btn-primary">Add Data Series</button>
435
- {config.series && config.series.length <= 1 && config.visualizationType === "Bar" && (
772
+ })}
773
+ </ul>
774
+ </>)}
775
+
776
+ <Select fieldName="visualizationType" label="Add Data Series" initial="Select" onChange={(e) => {
777
+ if (e.target.value !== '' && e.target.value !== 'Select') {
778
+ addNewSeries(e.target.value)
779
+ }
780
+ e.target.value = ''
781
+ }} options={getColumns()}/>
782
+ {config.series && config.series.length <= 1 && config.visualizationType === 'Bar' && (
436
783
  <>
437
784
  <span className="divider-heading">Confidence Keys</span>
438
- <Select value={config.confidenceKeys.upper || ""} section="confidenceKeys" fieldName="upper" label="Upper" updateField={updateField} initial="Select" options={getColumns()} />
439
- <Select value={config.confidenceKeys.lower || ""} section="confidenceKeys" fieldName="lower" label="Lower" updateField={updateField} initial="Select" options={getColumns()} />
785
+ <Select value={config.confidenceKeys.upper || ''} section="confidenceKeys" fieldName="upper" label="Upper" updateField={updateField} initial="Select" options={getColumns()}/>
786
+ <Select value={config.confidenceKeys.lower || ''} section="confidenceKeys" fieldName="lower" label="Lower" updateField={updateField} initial="Select" options={getColumns()}/>
440
787
  </>
441
788
  )}
442
- </AccordionItemPanel>
443
- </AccordionItem>}
789
+ </AccordionItemPanel>
790
+ </AccordionItem>
791
+ }
792
+
444
793
  <AccordionItem>
445
794
  <AccordionItemHeading>
446
795
  <AccordionItemButton>
447
- {config.visualizationSubType === 'horizontal' ? 'X Axis' : 'Y Axis'} {config.visualizationType === 'Pie' && !config.yAxis.dataKey && <WarningImage width="25" className="warning-icon" />}
796
+ {config.visualizationType !== 'Pie'
797
+ ? config.visualizationType === 'Bar' ? 'Value Axis' : 'Value Axis'
798
+ : 'Data Format'
799
+ }
800
+ {config.visualizationType === 'Pie' && !config.yAxis.dataKey && <WarningImage width="25" className="warning-icon"/>}
448
801
  </AccordionItemButton>
449
802
  </AccordionItemHeading>
450
803
  <AccordionItemPanel>
451
- {config.visualizationType === 'Pie' && <Select value={config.yAxis.dataKey || ""} section="yAxis" fieldName="dataKey" label="Data Key" initial="Select" required={true} updateField={updateField} options={getColumns(false)} /> }
804
+ {config.visualizationType === 'Pie' &&
805
+ <Select value={config.yAxis.dataKey || ''} section="yAxis" fieldName="dataKey" label="Data Column" initial="Select" required={true} updateField={updateField} options={getColumns(false)} tooltip={
806
+ <Tooltip style={{ textTransform: 'none' }}>
807
+ <Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }}/></Tooltip.Target>
808
+ <Tooltip.Content>
809
+ <p>Select the source data to be visually represented.</p>
810
+ </Tooltip.Content>
811
+ </Tooltip>
812
+ }/>
813
+ }
452
814
  {config.visualizationType !== 'Pie' && (
453
815
  <>
454
- <TextField value={config.yAxis.label} section="yAxis" fieldName="label" label="Label" updateField={updateField} />
455
- <TextField value={config.yAxis.numTicks} placeholder="Auto" type="number" section="yAxis" fieldName="numTicks" label="Number of ticks" className="number-narrow" updateField={updateField} />
456
- <TextField value={config.yAxis.size} type="number" section="yAxis" fieldName="size" label="Size (width)" className="number-narrow" updateField={updateField} />
457
- {config.visualizationSubType !== 'horizontal' && <CheckBox value={config.yAxis.gridLines} section="yAxis" fieldName="gridLines" label="Display Gridlines" updateField={updateField} />}
816
+ <TextField value={config.yAxis.label} section="yAxis" fieldName="label" label="Label" updateField={updateField}/>
817
+ <TextField value={config.yAxis.numTicks} placeholder="Auto" type="number" section="yAxis" fieldName="numTicks" label="Number of ticks" className="number-narrow" updateField={updateField}/>
818
+ <TextField value={config.yAxis.size} type="number" section="yAxis" fieldName="size" label={config.orientation === 'horizontal' ? 'Size (Height)' : 'Size (Width)'} className="number-narrow" updateField={updateField}/>
819
+ {config.orientation !== 'horizontal' && <CheckBox value={config.yAxis.gridLines} section="yAxis" fieldName="gridLines" label="Display Gridlines" updateField={updateField}/>}
458
820
  </>
459
821
  )}
460
822
  <span className="divider-heading">Number Formatting</span>
461
- <CheckBox value={config.dataFormat.commas} section="dataFormat" fieldName="commas" label="Add commas" updateField={updateField} />
462
- <TextField value={config.dataFormat.roundTo} type="number" section="dataFormat" fieldName="roundTo" label="Round to decimal point" className="number-narrow" updateField={updateField} min={0} />
823
+ <CheckBox value={config.dataFormat.commas} section="dataFormat" fieldName="commas" label="Add commas" updateField={updateField}/>
824
+ <TextField value={config.dataFormat.roundTo} type="number" section="dataFormat" fieldName="roundTo" label="Round to decimal point" className="number-narrow" updateField={updateField} min={0}/>
463
825
  <div className="two-col-inputs">
464
- <TextField value={config.dataFormat.prefix} section="dataFormat" fieldName="prefix" label="Prefix" updateField={updateField} />
465
- <TextField value={config.dataFormat.suffix} section="dataFormat" fieldName="suffix" label="Suffix" updateField={updateField} />
826
+ <TextField value={config.dataFormat.prefix} section="dataFormat" fieldName="prefix" label="Prefix" updateField={updateField} tooltip={
827
+ <Tooltip style={{ textTransform: 'none' }}>
828
+ <Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }}/></Tooltip.Target>
829
+ <Tooltip.Content>
830
+ {config.visualizationType === 'Pie' && <p>Enter a data prefix to display in the data table and chart tooltips, if applicable.</p>}
831
+ {config.visualizationType !== 'Pie' && <p>Enter a data prefix (such as "$"), if applicable.</p>}
832
+ </Tooltip.Content>
833
+ </Tooltip>
834
+ }/>
835
+ <TextField value={config.dataFormat.suffix} section="dataFormat" fieldName="suffix" label="Suffix" updateField={updateField} tooltip={
836
+ <Tooltip style={{ textTransform: 'none' }}>
837
+ <Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }}/></Tooltip.Target>
838
+ <Tooltip.Content>
839
+ {config.visualizationType === 'Pie' && <p>Enter a data suffix to display in the data table and tooltips, if applicable.</p>}
840
+ {config.visualizationType !== 'Pie' && <p>Enter a data suffix (such as "%"), if applicable.</p>}
841
+ </Tooltip.Content>
842
+ </Tooltip>
843
+ }/>
466
844
  </div>
845
+
846
+ {(config.orientation === 'horizontal') ? // horizontal - x is vertical y is horizontal
847
+ <>
848
+ <CheckBox value={config.xAxis.hideAxis} section="xAxis" fieldName="hideAxis" label="Hide Axis" updateField={updateField} />
849
+ <CheckBox value={config.xAxis.hideLabel} section="xAxis" fieldName="hideLabel" label="Hide Label" updateField={updateField} />
850
+ <CheckBox value={config.xAxis.hideTicks} section="xAxis" fieldName="hideTicks" label="Hide Ticks" updateField={updateField} />
851
+ <TextField value={config.xAxis.max} type='number' label='update max value' placeholder='Auto' onChange={(e) => onMaxChangeHandler(e)} />
852
+ <span style={{color:'red',display:'block'}} >{warningMsg.maxMsg}</span>
853
+ </>
854
+ : config.visualizationType !=='Pie' &&
855
+ <>
856
+ <CheckBox value={config.yAxis.hideAxis} section="yAxis" fieldName="hideAxis" label="Hide Axis" updateField={updateField} />
857
+ <CheckBox value={config.yAxis.hideLabel} section="yAxis" fieldName="hideLabel" label="Hide Label" updateField={updateField} />
858
+ <CheckBox value={config.yAxis.hideTicks} section="yAxis" fieldName="hideTicks" label="Hide Ticks" updateField={updateField} />
859
+ <TextField value={config.yAxis.max} type='number' label='update max value' placeholder='Auto' onChange={(e) => onMaxChangeHandler(e)} />
860
+ <span style={{color:'red',display:'block'}} >{warningMsg.maxMsg}</span>
861
+ <TextField value={config.yAxis.min} type='number' label='update min value' placeholder='Auto' onChange={(e)=>onMinChangeHandler(e)} />
862
+ <span style={{color:'red',display:'block'}} >{warningMsg.minMsg}</span>
863
+ </>
864
+ }
467
865
  </AccordionItemPanel>
468
866
  </AccordionItem>
867
+
469
868
  <AccordionItem>
470
869
  <AccordionItemHeading>
471
870
  <AccordionItemButton>
472
- {config.visualizationSubType === 'horizontal' ? 'Y Axis' : 'X Axis'} {!config.xAxis.dataKey && <WarningImage width="25" className="warning-icon" />}
871
+ {config.visualizationType !== 'Pie'
872
+ ? config.visualizationType === 'Bar' ? 'Date/Category Axis' : 'Date/Category Axis'
873
+ : 'Segments'
874
+ }
875
+ {!config.xAxis.dataKey && <WarningImage width="25" className="warning-icon"/>}
473
876
  </AccordionItemButton>
474
877
  </AccordionItemHeading>
475
878
  <AccordionItemPanel>
476
- <Select value={config.xAxis.dataKey || ""} section="xAxis" fieldName="dataKey" label="Data Key" initial="Select" required={true} updateField={updateField} options={getColumns(false)} />
879
+ {config.visualizationType !== 'Pie' && <>
880
+ <Select value={config.xAxis.type} section="xAxis" fieldName="type" label="Data Type" updateField={updateField} options={[ 'categorical', 'date' ]}/>
881
+ <Select value={config.xAxis.dataKey || ''} section="xAxis" fieldName="dataKey" label="Data Key" initial="Select" required={true} updateField={updateField} options={getColumns(false)} tooltip={
882
+ <Tooltip style={{ textTransform: 'none' }}>
883
+ <Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }}/></Tooltip.Target>
884
+ <Tooltip.Content>
885
+ <p>Select the column or row containing the categories or dates for this axis. </p>
886
+ </Tooltip.Content>
887
+ </Tooltip>
888
+ }/>
889
+ </>}
890
+
891
+ {config.visualizationType === 'Pie' &&
892
+ <Select value={config.xAxis.dataKey || ''} section="xAxis" fieldName="dataKey" label="Segment Labels" initial="Select" required={true} updateField={updateField} options={getColumns(false)} tooltip={
893
+ <Tooltip style={{ textTransform: 'none' }}>
894
+ <Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }}/></Tooltip.Target>
895
+ <Tooltip.Content>
896
+ <p>Select the source row or column that contains the segment labels. Depending on the data structure, it may be listed as "Key."</p>
897
+ </Tooltip.Content>
898
+ </Tooltip>
899
+ }/>
900
+ }
901
+
477
902
  {config.visualizationType !== 'Pie' && (
478
903
  <>
479
- <TextField value={config.xAxis.label} section="xAxis" fieldName="label" label="Label" updateField={updateField} />
480
- <Select value={config.xAxis.type} section="xAxis" fieldName="type" label="Data Type" updateField={updateField} options={['categorical', 'date']} />
481
- {config.xAxis.type === "date" && (
904
+ <TextField value={config.xAxis.label} section="xAxis" fieldName="label" label="Label" updateField={updateField}/>
905
+
906
+ {config.xAxis.type === 'date' && (
482
907
  <>
483
- <p style={{padding: '.5em 0', fontSize: '.9rem', lineHeight: '1rem'}}>Format how charts should parse and display your dates using <a href="https://github.com/d3/d3-time-format#locale_format" target="_blank">these guidelines</a>.</p>
484
- <TextField value={config.xAxis.dateParseFormat} section="xAxis" fieldName="dateParseFormat" placeholder="Ex. %Y-%m-%d" label="Date Parse Format" updateField={updateField} />
485
- <TextField value={config.xAxis.dateDisplayFormat} section="xAxis" fieldName="dateDisplayFormat" placeholder="Ex. %Y-%m-%d" label="Date Display Format" updateField={updateField} />
486
- <TextField value={config.xAxis.numTicks} placeholder="Auto" type="number" min="1" section="xAxis" fieldName="numTicks" label="Number of ticks" className="number-narrow" updateField={updateField} />
908
+ <p style={{ padding: '1.5em 0 0.5em', fontSize: '.9rem', lineHeight: '1rem' }}>Format how charts should parse and display your dates using <a href="https://github.com/d3/d3-time-format#locale_format" target="_blank" rel="noreferrer">these guidelines</a>.</p>
909
+ <TextField value={config.xAxis.dateParseFormat} section="xAxis" fieldName="dateParseFormat" placeholder="Ex. %Y-%m-%d" label="Date Parse Format" updateField={updateField}/>
910
+ <TextField value={config.xAxis.dateDisplayFormat} section="xAxis" fieldName="dateDisplayFormat" placeholder="Ex. %Y-%m-%d" label="Date Display Format" updateField={updateField}/>
487
911
  </>
488
912
  )}
489
- {config.xAxis.numTicks = (config.xAxis.type === 'categorical') ? '' : config.xAxis.numTicks /* remove tick setting for categorical */ }
490
- <TextField value={config.xAxis.size} type="number" min="0" section="xAxis" fieldName="size" label="Size (height)" className="number-narrow" updateField={updateField} />
491
- {config.visualizationSubType !== 'horizontal' && <TextField value={config.xAxis.tickRotation} type="number" min="0" section="xAxis" fieldName="tickRotation" label="Tick rotation (Degrees)" className="number-narrow" updateField={updateField} />}
913
+
914
+ <CheckBox value={config.exclusions.active} section="exclusions" fieldName="active" label={config.xAxis.type === 'date' ? 'Limit by start and/or end dates' : 'Exclude one or more values'} tooltip={
915
+ <Tooltip style={{ textTransform: 'none' }}>
916
+ <Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }}/></Tooltip.Target>
917
+ <Tooltip.Content>
918
+ <p>When this option is checked, you can select source-file values for exclusion from the date/category axis. </p>
919
+ </Tooltip.Content>
920
+ </Tooltip>
921
+ } updateField={updateField}/>
922
+
923
+ {config.exclusions.active &&
924
+ <>
925
+ {config.xAxis.type === 'categorical' &&
926
+ <>
927
+ {config.exclusions.keys.length > 0 &&
928
+ <>
929
+ <label><span className="edit-label">Excluded Keys</span></label>
930
+ <ExclusionsList/>
931
+ </>
932
+ }
933
+
934
+ <Select fieldName="visualizationType" label="Add Exclusion" initial="Select" onChange={(e) => {
935
+ if (e.target.value !== '' && e.target.value !== 'Select') {
936
+ addNewExclusion(e.target.value)
937
+ }
938
+ e.target.value = ''
939
+ }} options={getDataValues(config.xAxis.dataKey, true)}/>
940
+ </>
941
+ }
942
+
943
+ {config.xAxis.type === 'date' &&
944
+ <>
945
+ <TextField type="date" section="exclusions" fieldName="dateStart" label="Start Date" updateField={updateField} value={config.exclusions.dateStart || ''}/>
946
+ <TextField type="date" section="exclusions" fieldName="dateEnd" label="End Date" updateField={updateField} value={config.exclusions.dateEnd || ''}/>
947
+ </>
948
+ }
949
+ </>
950
+ }
951
+
952
+ {config.xAxis.type === 'date' &&
953
+ <>
954
+ <TextField value={config.xAxis.numTicks} placeholder="Auto" type="number" min="1" section="xAxis" fieldName="numTicks" label="Number of ticks" className="number-narrow" updateField={updateField}/>
955
+ </>
956
+ }
957
+
958
+ <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}/>
959
+
960
+ {config.yAxis.labelPlacement !== 'Below Bar' &&
961
+ <TextField value={config.xAxis.tickRotation} type="number" min="0" section="xAxis" fieldName="tickRotation" label="Tick rotation (Degrees)" className="number-narrow" updateField={updateField}/>
962
+ }
963
+ {(config.orientation === 'horizontal') ?
964
+ <>
965
+ <CheckBox value={config.yAxis.hideAxis} section="yAxis" fieldName="hideAxis" label="Hide Axis" updateField={updateField}/>
966
+ <CheckBox value={config.yAxis.hideLabel} section="yAxis" fieldName="hideLabel" label="Hide Label" updateField={updateField}/>
967
+ </>
968
+ :
969
+ <>
970
+ <CheckBox value={config.xAxis.hideAxis} section="xAxis" fieldName="hideAxis" label="Hide Axis" updateField={updateField}/>
971
+ <CheckBox value={config.xAxis.hideLabel} section="xAxis" fieldName="hideLabel" label="Hide Label" updateField={updateField}/>
972
+ <CheckBox value={config.xAxis.hideTicks} section="xAxis" fieldName="hideTicks" label="Hide Ticks" updateField={updateField}/>
973
+ </>
974
+ }
492
975
  </>
493
976
  )}
977
+
978
+ {config.visualizationType === 'Pie' &&
979
+ <>
980
+ <CheckBox value={config.exclusions.active} section="exclusions" fieldName="active" label={'Exclude one or more values'} updateField={updateField} tooltip={
981
+ <Tooltip style={{ textTransform: 'none' }}>
982
+ <Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }}/></Tooltip.Target>
983
+ <Tooltip.Content>
984
+ <p>When this option is checked, you can select values for exclusion from the pie segments.</p>
985
+ </Tooltip.Content>
986
+ </Tooltip>
987
+ }/>
988
+ {config.exclusions.active &&
989
+ <>
990
+ {config.exclusions.keys.length > 0 &&
991
+ <>
992
+ <label><span className="edit-label">Excluded Keys</span></label>
993
+ <ExclusionsList/>
994
+ </>
995
+ }
996
+
997
+ <Select fieldName="visualizationType" label="Add Exclusion" initial="Select" onChange={(e) => {
998
+ if (e.target.value !== '' && e.target.value !== 'Select') {
999
+ addNewExclusion(e.target.value)
1000
+ }
1001
+ e.target.value = ''
1002
+ }} options={getDataValues(config.xAxis.dataKey, true)}/>
1003
+ </>
1004
+ }
1005
+ </>
1006
+ }
494
1007
  </AccordionItemPanel>
495
1008
  </AccordionItem>
496
- {config.visualizationType !== 'Pie' && <AccordionItem>
497
- <AccordionItemHeading>
498
- <AccordionItemButton>
499
- Regions
500
- </AccordionItemButton>
501
- </AccordionItemHeading>
502
- <AccordionItemPanel>
503
- <Regions config={config} updateConfig={updateConfig} />
504
- </AccordionItemPanel>
505
- </AccordionItem> }
1009
+
1010
+ {(config.visualizationType !== 'Pie' && config.visualizationType !== 'Paired Bar') &&
1011
+ <AccordionItem>
1012
+ <AccordionItemHeading>
1013
+ <AccordionItemButton>
1014
+ Regions
1015
+ </AccordionItemButton>
1016
+ </AccordionItemHeading>
1017
+ <AccordionItemPanel>
1018
+ <Regions config={config} updateConfig={updateConfig}/>
1019
+ </AccordionItemPanel>
1020
+ </AccordionItem>
1021
+ }
1022
+
506
1023
  <AccordionItem>
507
1024
  <AccordionItemHeading>
508
1025
  <AccordionItemButton>
@@ -510,12 +1027,21 @@ const EditorPanel = () => {
510
1027
  </AccordionItemButton>
511
1028
  </AccordionItemHeading>
512
1029
  <AccordionItemPanel>
513
- <CheckBox value={config.legend.hide} section="legend" fieldName="hide" label="Hide Legend" updateField={updateField} />
514
- <Select value={config.legend.behavior} section="legend" fieldName="behavior" label="Legend Behavior (When clicked)" updateField={updateField} options={['highlight', 'isolate']} />
515
- <TextField value={config.legend.label} section="legend" fieldName="label" label="Title" updateField={updateField} />
516
- <Select value={config.legend.position} section="legend" fieldName="position" label="Position" updateField={updateField} options={['right', 'left']} />
1030
+ <CheckBox value={config.legend.reverseLabelOrder} section="legend" fieldName="reverseLabelOrder" label="Reverse Labels" updateField={updateField}/>
1031
+ <CheckBox value={config.legend.hide} section="legend" fieldName="hide" label="Hide Legend" updateField={updateField} tooltip={
1032
+ <Tooltip style={{ textTransform: 'none' }}>
1033
+ <Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }}/></Tooltip.Target>
1034
+ <Tooltip.Content>
1035
+ <p>With a single-series chart, consider hiding the legend to reduce visual clutter.</p>
1036
+ </Tooltip.Content>
1037
+ </Tooltip>
1038
+ }/>
1039
+ <Select value={config.legend.behavior} section="legend" fieldName="behavior" label="Legend Behavior (When clicked)" updateField={updateField} options={[ 'highlight', 'isolate' ]}/>
1040
+ <TextField value={config.legend.label} section="legend" fieldName="label" label="Title" updateField={updateField}/>
1041
+ <Select value={config.legend.position} section="legend" fieldName="position" label="Position" updateField={updateField} options={[ 'right', 'left' ]}/>
517
1042
  </AccordionItemPanel>
518
1043
  </AccordionItem>
1044
+
519
1045
  <AccordionItem>
520
1046
  <AccordionItemHeading>
521
1047
  <AccordionItemButton>
@@ -525,29 +1051,84 @@ const EditorPanel = () => {
525
1051
  <AccordionItemPanel>
526
1052
  {config.filters && <ul className="filters-list">
527
1053
  {config.filters.map((filter, index) => (
528
- <fieldset className="edit-block">
529
- <button type="button" className="remove-column" onClick={() => {removeFilter(index)}}>Remove</button>
1054
+ <fieldset className="edit-block" key={index}>
1055
+ <button type="button" className="remove-column" onClick={() => {
1056
+ removeFilter(index)
1057
+ }}>Remove
1058
+ </button>
530
1059
  <label>
531
1060
  <span className="edit-label column-heading">Filter</span>
532
- <select value={filter.columnName} onChange={(e) => {updateFilterProp('columnName', index, e.target.value)}}>
1061
+ <select value={filter.columnName} onChange={(e) => {
1062
+ updateFilterProp('columnName', index, e.target.value)
1063
+ }}>
533
1064
  <option value="">- Select Option -</option>
534
- {getColumns().map((dataKey) => (
535
- <option value={dataKey}>{dataKey}</option>
1065
+ {getColumns().map((dataKey, index) => (
1066
+ <option value={dataKey} key={index}>{dataKey}</option>
536
1067
  ))}
537
1068
  </select>
538
1069
  </label>
539
1070
  <label>
540
1071
  <span className="edit-label column-heading">Label</span>
541
- <input type="text" value={filter.label} onChange={(e) => {updateFilterProp('label', index, e.target.value)}}/>
1072
+ <input type="text" value={filter.label} onChange={(e) => {
1073
+ updateFilterProp('label', index, e.target.value)
1074
+ }}/>
542
1075
  </label>
1076
+
1077
+ <label>
1078
+ <span className="edit-filterOrder column-heading">Filter Order</span>
1079
+ <select value={filter.order ? filter.order : 'asc'} onChange={(e) => updateFilterProp('order', index, e.target.value)}>
1080
+ {filterOptions.map((option, index) => {
1081
+ return <option value={option.value} key={`filter-${index}`}>{option.label}</option>
1082
+ })}
1083
+ </select>
1084
+
1085
+ {filter.order === 'cust' &&
1086
+ <DragDropContext
1087
+ onDragEnd={({ source, destination }) =>
1088
+ handleFilterChange(source.index, destination.index, index, config.filters[index])
1089
+ }>
1090
+ <Droppable droppableId="filter_order">
1091
+ {(provided) => (
1092
+ <ul
1093
+ {...provided.droppableProps}
1094
+ className="sort-list"
1095
+ ref={provided.innerRef}
1096
+ style={{ marginTop: '1em' }}
1097
+ >
1098
+ {config.filters[index]?.values.map((value, index) => {
1099
+ return (
1100
+ <Draggable key={value} draggableId={`draggableFilter-${value}`} index={index}>
1101
+ {(provided, snapshot) => (
1102
+ <li>
1103
+ <div className={snapshot.isDragging ? 'currently-dragging' : ''}
1104
+ style={getItemStyle(snapshot.isDragging, provided.draggableProps.style, sortableItemStyles)}
1105
+ ref={provided.innerRef}
1106
+ {...provided.draggableProps}
1107
+ {...provided.dragHandleProps}>
1108
+ {value}
1109
+ </div>
1110
+ </li>
1111
+ )}
1112
+ </Draggable>
1113
+ )
1114
+ })}
1115
+ {provided.placeholder}
1116
+ </ul>
1117
+ )}
1118
+ </Droppable>
1119
+ </DragDropContext>
1120
+ }
1121
+ </label>
1122
+
543
1123
  </fieldset>
544
1124
  )
545
1125
  )}
546
1126
  </ul>}
547
- {!config.filters && <p style={{textAlign: "center"}}>There are currently no filters.</p>}
1127
+ {!config.filters && <p style={{ textAlign: 'center' }}>There are currently no filters.</p>}
548
1128
  <button type="button" onClick={addNewFilter} className="btn full-width">Add Filter</button>
549
1129
  </AccordionItemPanel>
550
1130
  </AccordionItem>
1131
+
551
1132
  <AccordionItem>
552
1133
  <AccordionItemHeading>
553
1134
  <AccordionItemButton>
@@ -555,18 +1136,57 @@ const EditorPanel = () => {
555
1136
  </AccordionItemButton>
556
1137
  </AccordionItemHeading>
557
1138
  <AccordionItemPanel>
558
- <Select value={config.fontSize} fieldName="fontSize" label="Font Size" updateField={updateField} options={['small', 'medium', 'large']} />
1139
+
1140
+ {config.isLollipopChart &&
1141
+ <>
1142
+ <label className="header">
1143
+ <span className="edit-label">Lollipop Shape</span>
1144
+ <div onChange={(e) => {
1145
+ setLollipopShape(e.target.value)
1146
+ }}>
1147
+ <label>
1148
+ <input
1149
+ type="radio"
1150
+ name="lollipopShape"
1151
+ value="circle"
1152
+ checked={config.lollipopShape === 'circle'}
1153
+ />
1154
+ Circle
1155
+ </label>
1156
+ <label>
1157
+ <input
1158
+ type="radio"
1159
+ name="lollipopShape"
1160
+ value="square"
1161
+ checked={config.lollipopShape === 'square'}
1162
+ />
1163
+ Square
1164
+ </label>
1165
+ </div>
1166
+
1167
+ </label>
1168
+ <Select value={config.lollipopColorStyle ? config.lollipopColorStyle : 'two-tone'} fieldName="lollipopColorStyle" label="Lollipop Color Style" updateField={updateField} options={[ 'regular', 'two-tone' ]}/>
1169
+ <Select value={config.lollipopSize ? config.lollipopSize : 'small'} fieldName="lollipopSize" label="Lollipop Size" updateField={updateField} options={[ 'small', 'medium', 'large' ]}/>
1170
+ </>
1171
+ }
1172
+
1173
+ <Select value={config.fontSize} fieldName="fontSize" label="Font Size" updateField={updateField} options={[ 'small', 'medium', 'large' ]}/>
1174
+
559
1175
  {config.series?.some(series => series.type === 'Bar') &&
560
- <Select value={config.barHasBorder} fieldName="barHasBorder" label="Bar Borders" updateField={updateField} options={['true', 'false']} />
1176
+ <Select value={config.barHasBorder} fieldName="barHasBorder" label="Bar Borders" updateField={updateField} options={[ 'true', 'false' ]}/>
561
1177
  }
562
- {config.series?.some(series => series.type === 'Line') &&
563
- <Select value={config.lineDatapointStyle} fieldName="lineDatapointStyle" label="Line Datapoint Style" updateField={updateField} options={['hidden', 'hover', 'always show']} />
1178
+
1179
+ {((config.series?.some(series => series.type === 'Line') && config.visualizationType === 'Combo') || config.visualizationType === 'Line') &&
1180
+ <Select value={config.lineDatapointStyle} fieldName="lineDatapointStyle" label="Line Datapoint Style" updateField={updateField} options={[ 'hidden', 'hover', 'always show' ]}/>
564
1181
  }
1182
+
565
1183
  <label className="header">
566
1184
  <span className="edit-label">Header Theme</span>
567
1185
  <ul className="color-palette">
568
- {headerColors.map( (palette) => (
569
- <li title={ palette } key={ palette } onClick={ () => { updateConfig({...config, theme: palette})}} className={ config.theme === palette ? "selected " + palette : palette}>
1186
+ {headerColors.map((palette) => (
1187
+ <li title={palette} key={palette} onClick={() => {
1188
+ updateConfig({ ...config, theme: palette })
1189
+ }} className={config.theme === palette ? 'selected ' + palette : palette}>
570
1190
  </li>
571
1191
  ))}
572
1192
  </ul>
@@ -574,67 +1194,96 @@ const EditorPanel = () => {
574
1194
  <label>
575
1195
  <span className="edit-label">Chart Color Palette</span>
576
1196
  </label>
577
- <span className="h5">Quantitative</span>
1197
+ {/* <InputCheckbox fieldName='isPaletteReversed' size='small' label='Use selected palette in reverse order' updateField={updateField} value={isPaletteReversed} /> */}
1198
+ <InputToggle fieldName="isPaletteReversed" size="small" label="Use selected palette in reverse order" updateField={updateField} value={isPaletteReversed}/>
1199
+ <span>Sequential</span>
578
1200
  <ul className="color-palette">
579
- {Object.keys(colorPalettes).filter((name) => name.includes('qualitative')).map( (palette) => {
1201
+ {filteredPallets.map((palette) => {
580
1202
 
581
1203
  const colorOne = {
582
1204
  backgroundColor: colorPalettes[palette][2]
583
1205
  }
584
1206
 
585
1207
  const colorTwo = {
586
- backgroundColor: colorPalettes[palette][4]
1208
+ backgroundColor: colorPalettes[palette][3]
587
1209
  }
588
1210
 
589
1211
  const colorThree = {
590
- backgroundColor: colorPalettes[palette][6]
1212
+ backgroundColor: colorPalettes[palette][5]
591
1213
  }
592
1214
 
593
1215
  return (
594
- <li title={ palette } key={ palette } onClick={ () => { updateConfig({...config, palette}) }} className={ config.palette === palette ? "selected" : ""}>
595
- <span style={colorOne}></span>
596
- <span style={colorTwo}></span>
597
- <span style={colorThree}></span>
598
- </li>
1216
+ <li title={palette} key={palette} onClick={() => {
1217
+ updateConfig({ ...config, palette })
1218
+ }} className={config.palette === palette ? 'selected' : ''}>
1219
+ <span style={colorOne}></span>
1220
+ <span style={colorTwo}></span>
1221
+ <span style={colorThree}></span>
1222
+ </li>
599
1223
  )
600
1224
  })}
601
1225
  </ul>
602
- <span className="h5">Sequential</span>
1226
+ <span>Non-Sequential</span>
603
1227
  <ul className="color-palette">
604
- {Object.keys(colorPalettes).filter((name) => name.includes('sequential')).map( (palette) => {
1228
+ {filteredQualitative.map((palette) => {
605
1229
 
606
1230
  const colorOne = {
607
1231
  backgroundColor: colorPalettes[palette][2]
608
1232
  }
609
1233
 
610
1234
  const colorTwo = {
611
- backgroundColor: colorPalettes[palette][3]
1235
+ backgroundColor: colorPalettes[palette][4]
612
1236
  }
613
1237
 
614
1238
  const colorThree = {
615
- backgroundColor: colorPalettes[palette][5]
1239
+ backgroundColor: colorPalettes[palette][6]
616
1240
  }
617
1241
 
1242
+
618
1243
  return (
619
- <li title={ palette } key={ palette } onClick={ () => { updateConfig({...config, palette}) }} className={ config.palette === palette ? "selected" : ""}>
620
- <span style={colorOne}></span>
621
- <span style={colorTwo}></span>
622
- <span style={colorThree}></span>
623
- </li>
1244
+ <li title={palette} key={palette} onClick={() => {
1245
+ updateConfig({ ...config, palette })
1246
+ }} className={config.palette === palette ? 'selected' : ''}>
1247
+ <span style={colorOne}></span>
1248
+ <span style={colorTwo}></span>
1249
+ <span style={colorThree}></span>
1250
+ </li>
624
1251
  )
625
1252
  })}
626
1253
  </ul>
1254
+
627
1255
  {config.visualizationType !== 'Pie' && (
628
1256
  <>
629
- {config.visualizationSubType !== 'horizontal' &&
630
- <CheckBox value={config.labels} fieldName="labels" label="Display label on data" updateField={updateField} />
1257
+ {config.orientation !== 'horizontal' &&
1258
+ <CheckBox value={config.labels} fieldName="labels" label="Display label on data" updateField={updateField}/>
631
1259
  }
632
- <TextField value={config.dataCutoff} type="number" fieldName="dataCutoff" className="number-narrow" label="Data Cutoff" updateField={updateField} />
1260
+ <TextField value={config.dataCutoff} type="number" fieldName="dataCutoff" className="number-narrow" label="Data Cutoff" updateField={updateField} tooltip={
1261
+ <Tooltip style={{ textTransform: 'none' }}>
1262
+ <Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }}/></Tooltip.Target>
1263
+ <Tooltip.Content>
1264
+ <p>Any value below the cut-off value is included in a special "less than" category. This option supports special conditions like suppressed data.</p>
1265
+ </Tooltip.Content>
1266
+ </Tooltip>
1267
+ }/>
633
1268
  </>
634
1269
  )}
635
- {( config.visualizationType === "Bar" || config.visualizationType === "Combo" ) && <TextField value={config.barThickness} type="number" fieldName="barThickness" label="Bar Thickness" updateField={updateField} />}
1270
+ {(config.orientation === 'horizontal' && config.yAxis.labelPlacement !== 'On Bar') &&
1271
+ <TextField type="number" value={config.barHeight || '25'} fieldName="barHeight" label="Bar Thickness" updateField={updateField} min="15"/>
1272
+ }
1273
+ {((config.visualizationType === 'Bar' && config.orientation !== 'horizontal') || config.visualizationType === 'Combo') &&
1274
+ <TextField value={config.barThickness} type="number" fieldName="barThickness" label="Bar Thickness" updateField={updateField}/>
1275
+ }
1276
+
1277
+ {/* <div className="cove-accordion__panel-section">
1278
+ <CheckBox value={config.visual?.border} section="visual" fieldName="border" label="Display Border" updateField={updateField} />
1279
+ <CheckBox value={config.visual?.borderColorTheme} section="visual" fieldName="borderColorTheme" label="Use Border Color Theme" updateField={updateField} />
1280
+ <CheckBox value={config.visual?.accent} section="visual" fieldName="accent" label="Use Accent Style" updateField={updateField} />
1281
+ <CheckBox value={config.visual?.background} section="visual" fieldName="background" label="Use Theme Background Color" updateField={updateField} />
1282
+ <CheckBox value={config.visual?.hideBackgroundColor} section="visual" fieldName="hideBackgroundColor" label="Hide Background Color" updateField={updateField} />
1283
+ </div> */}
636
1284
  </AccordionItemPanel>
637
1285
  </AccordionItem>
1286
+
638
1287
  <AccordionItem>
639
1288
  <AccordionItemHeading>
640
1289
  <AccordionItemButton>
@@ -642,24 +1291,29 @@ const EditorPanel = () => {
642
1291
  </AccordionItemButton>
643
1292
  </AccordionItemHeading>
644
1293
  <AccordionItemPanel>
645
- <CheckBox value={config.table.show} section="table" fieldName="show" label="Show Table" updateField={updateField} />
646
- <CheckBox value={config.table.expanded} section="table" fieldName="expanded" label="Expanded by Default" updateField={updateField} />
647
- <CheckBox value={config.table.download} section="table" fieldName="download" label="Display Download Button" updateField={updateField} />
648
- <TextField value={config.table.label} section="table" fieldName="label" label="Label" updateField={updateField} />
649
- <TextField value={config.table.indexLabel} section="table" fieldName="indexLabel" label="Column Index Label" updateField={updateField} />
1294
+ <CheckBox value={config.table.show} section="table" fieldName="show" label="Show Table" updateField={updateField} tooltip={
1295
+ <Tooltip style={{ textTransform: 'none' }}>
1296
+ <Tooltip.Target><Icon display="question" style={{ marginLeft: '0.5rem' }}/></Tooltip.Target>
1297
+ <Tooltip.Content>
1298
+ <p>Hiding the data table may affect accessibility. An alternate form of accessing visualization data is a 508 requirement.</p>
1299
+ </Tooltip.Content>
1300
+ </Tooltip>
1301
+ }/>
1302
+ <CheckBox value={config.table.expanded} section="table" fieldName="expanded" label="Expanded by Default" updateField={updateField}/>
1303
+ <CheckBox value={config.table.download} section="table" fieldName="download" label="Display Download Button" updateField={updateField}/>
1304
+ <TextField value={config.table.label} section="table" fieldName="label" label="Label" updateField={updateField}/>
1305
+ <TextField value={config.table.indexLabel} section="table" fieldName="indexLabel" label="Index Column Header" updateField={updateField}/>
650
1306
  </AccordionItemPanel>
651
1307
  </AccordionItem>
652
- </Accordion>
1308
+ </Accordion>
653
1309
  </form>
1310
+ {config.type !== 'Spark Line' &&
1311
+ <AdvancedEditor loadConfig={updateConfig} state={config} convertStateToConfig={convertStateToConfig} />
1312
+ }
654
1313
  </section>
655
- <ReactTooltip
656
- html={true}
657
- multiline={true}
658
- className="helper-tooltip"
659
- />
660
1314
  </section>
661
1315
  </ErrorBoundary>
662
1316
  )
663
1317
  }
664
1318
 
665
- export default EditorPanel;
1319
+ export default EditorPanel