@cdc/chart 1.3.1 → 1.3.3
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.
- package/dist/cdcchart.js +85 -0
- package/examples/age-adjusted-rates.json +1218 -0
- package/examples/case-rate-example-config.json +36 -0
- package/examples/case-rate-example-data.json +33602 -0
- package/examples/covid-confidence-example-config.json +35 -0
- package/examples/covid-example-config.json +36 -0
- package/examples/covid-example-data-confidence.json +32 -0
- package/examples/covid-example-data.json +22 -0
- package/examples/cutoff-example-config.json +36 -0
- package/examples/cutoff-example-data.json +38 -0
- package/examples/date-exclusions-config.json +62 -0
- package/examples/date-exclusions-data.json +162 -0
- package/examples/horizontal-chart.json +35 -0
- package/examples/horizontal-stacked-bar-chart.json +36 -0
- package/examples/line-chart.json +76 -0
- package/examples/paired-bar-data.json +14 -0
- package/examples/paired-bar-example.json +48 -0
- package/examples/paired-bar-formatted.json +37 -0
- package/examples/planet-chart-horizontal-example-config.json +35 -0
- package/examples/planet-combo-example-config.json +31 -0
- package/examples/planet-example-config.json +35 -0
- package/examples/planet-example-data.json +56 -0
- package/examples/planet-pie-example-config.json +28 -0
- package/examples/private/newtest.csv +101 -0
- package/examples/private/test.json +10124 -0
- package/examples/temp-example-config.json +57 -0
- package/examples/temp-example-data.json +130 -0
- package/package.json +9 -8
- package/src/CdcChart.tsx +836 -0
- package/src/components/BarChart.tsx +571 -0
- package/src/components/BarStackVertical.js +0 -0
- package/src/components/DataTable.tsx +229 -0
- package/src/components/EditorPanel.js +1319 -0
- package/src/components/LineChart.tsx +76 -0
- package/src/components/LinearChart.tsx +459 -0
- package/src/components/PairedBarChart.tsx +144 -0
- package/src/components/PieChart.tsx +189 -0
- package/src/components/SparkLine.js +206 -0
- package/src/context.tsx +5 -0
- package/src/data/initial-state.js +61 -0
- package/src/hooks/useActiveElement.js +19 -0
- package/src/hooks/useColorPalette.ts +83 -0
- package/src/hooks/useReduceData.ts +43 -0
- package/src/images/active-checkmark.svg +1 -0
- package/src/images/asc.svg +1 -0
- package/src/images/desc.svg +1 -0
- package/src/images/inactive-checkmark.svg +1 -0
- package/src/images/warning.svg +1 -0
- package/src/index.html +68 -0
- package/src/index.tsx +21 -0
- package/src/scss/DataTable.scss +23 -0
- package/src/scss/LinearChart.scss +0 -0
- package/src/scss/editor-panel.scss +693 -0
- package/src/scss/main.scss +426 -0
- package/src/scss/mixins.scss +0 -0
- package/src/scss/variables.scss +1 -0
|
@@ -0,0 +1,1319 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback, memo, useContext } from 'react'
|
|
2
|
+
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
Accordion,
|
|
6
|
+
AccordionItem,
|
|
7
|
+
AccordionItemHeading,
|
|
8
|
+
AccordionItemPanel,
|
|
9
|
+
AccordionItemButton,
|
|
10
|
+
} from 'react-accessible-accordion'
|
|
11
|
+
|
|
12
|
+
import { timeParse, timeFormat } from 'd3-time-format'
|
|
13
|
+
import { useDebounce, useDebouncedCallback } from 'use-debounce'
|
|
14
|
+
|
|
15
|
+
import Context from '../context'
|
|
16
|
+
import WarningImage from '../images/warning.svg'
|
|
17
|
+
import AdvancedEditor from '@cdc/core/components/AdvancedEditor';
|
|
18
|
+
|
|
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
|
+
|
|
28
|
+
const TextField = memo(({label, tooltip, section = null, subsection = null, fieldName, updateField, value: stateValue, type = "input", i = null, min = null, ...attributes}) => {
|
|
29
|
+
const [ value, setValue ] = useState(stateValue);
|
|
30
|
+
|
|
31
|
+
const [ debouncedValue ] = useDebounce(value, 500);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
|
|
35
|
+
updateField(section, subsection, fieldName, debouncedValue, i)
|
|
36
|
+
}
|
|
37
|
+
}, [ debouncedValue ])
|
|
38
|
+
|
|
39
|
+
let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
|
|
40
|
+
|
|
41
|
+
const onChange = (e) => {
|
|
42
|
+
|
|
43
|
+
if ('number' !== type || min === null) {
|
|
44
|
+
setValue(e.target.value)
|
|
45
|
+
} else {
|
|
46
|
+
if (!e.target.value || min <= parseFloat(e.target.value)) {
|
|
47
|
+
setValue(e.target.value)
|
|
48
|
+
} else {
|
|
49
|
+
setValue(min.toString())
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let formElement = <input type="text" name={name} onChange={onChange} {...attributes} value={value}/>
|
|
55
|
+
|
|
56
|
+
if ('textarea' === type) {
|
|
57
|
+
formElement = (
|
|
58
|
+
<textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
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}/>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<label>
|
|
72
|
+
<span className="edit-label column-heading">{label}{tooltip}</span>
|
|
73
|
+
{formElement}
|
|
74
|
+
</label>
|
|
75
|
+
)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const CheckBox = memo(({ label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes }) => (
|
|
79
|
+
<label className="checkbox">
|
|
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>
|
|
84
|
+
</label>
|
|
85
|
+
))
|
|
86
|
+
|
|
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>)
|
|
89
|
+
|
|
90
|
+
if (initialValue) {
|
|
91
|
+
optionsJsx.unshift(<option value="" key="initial">{initialValue}</option>)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<label>
|
|
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}>
|
|
100
|
+
{optionsJsx}
|
|
101
|
+
</select>
|
|
102
|
+
</label>
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const Regions = memo(({ config, updateConfig }) => {
|
|
107
|
+
let regionUpdate = (fieldName, value, i) => {
|
|
108
|
+
let regions = []
|
|
109
|
+
|
|
110
|
+
if (config.regions) {
|
|
111
|
+
regions = [ ...config.regions ]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
regions[i][fieldName] = value
|
|
115
|
+
updateConfig({ ...config, regions })
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let updateField = (section, subsection, fieldName, value, i) => regionUpdate(fieldName, value, i)
|
|
119
|
+
|
|
120
|
+
let removeColumn = (i) => {
|
|
121
|
+
let regions = []
|
|
122
|
+
|
|
123
|
+
if (config.regions) {
|
|
124
|
+
regions = [ ...config.regions ]
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
regions.splice(i, 1)
|
|
128
|
+
|
|
129
|
+
updateConfig({ ...config, regions })
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let addColumn = () => {
|
|
133
|
+
|
|
134
|
+
let regions = []
|
|
135
|
+
|
|
136
|
+
if (config.regions) {
|
|
137
|
+
regions = [ ...config.regions ]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
regions.push({})
|
|
141
|
+
|
|
142
|
+
updateConfig({ ...config, regions })
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<>
|
|
147
|
+
{config.regions && config.regions.map(({ label, color, from, to, background }, i) => (
|
|
148
|
+
<div className="edit-block" key={`region-${i}`}>
|
|
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}/>
|
|
155
|
+
<div className="two-col-inputs">
|
|
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)}/>
|
|
158
|
+
</div>
|
|
159
|
+
<div className="two-col-inputs">
|
|
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)}/>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
))}
|
|
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>
|
|
171
|
+
</>
|
|
172
|
+
)
|
|
173
|
+
})
|
|
174
|
+
|
|
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' ]
|
|
176
|
+
|
|
177
|
+
const EditorPanel = () => {
|
|
178
|
+
const {
|
|
179
|
+
config,
|
|
180
|
+
updateConfig,
|
|
181
|
+
transformedData: data,
|
|
182
|
+
loading,
|
|
183
|
+
colorPalettes,
|
|
184
|
+
unfilteredData,
|
|
185
|
+
excludedData,
|
|
186
|
+
transformedData,
|
|
187
|
+
isDashboard,
|
|
188
|
+
setParentConfig,
|
|
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
|
+
}
|
|
240
|
+
|
|
241
|
+
let hasLineChart = false
|
|
242
|
+
|
|
243
|
+
const enforceRestrictions = (updatedConfig) => {
|
|
244
|
+
if (updatedConfig.orientation === 'horizontal') {
|
|
245
|
+
updatedConfig.labels = false
|
|
246
|
+
}
|
|
247
|
+
if (updatedConfig.table.show === undefined) {
|
|
248
|
+
updatedConfig.table.show = !isDashboard
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const updateField = (section, subsection, fieldName, newValue) => {
|
|
253
|
+
// Top level
|
|
254
|
+
if (null === section && null === subsection) {
|
|
255
|
+
let updatedConfig = { ...config, [fieldName]: newValue }
|
|
256
|
+
|
|
257
|
+
enforceRestrictions(updatedConfig)
|
|
258
|
+
|
|
259
|
+
updateConfig(updatedConfig)
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const isArray = Array.isArray(config[section])
|
|
264
|
+
|
|
265
|
+
let sectionValue = isArray ? [ ...config[section], newValue ] : { ...config[section], [fieldName]: newValue }
|
|
266
|
+
|
|
267
|
+
if (null !== subsection) {
|
|
268
|
+
if (isArray) {
|
|
269
|
+
sectionValue = [ ...config[section] ]
|
|
270
|
+
sectionValue[subsection] = { ...sectionValue[subsection], [fieldName]: newValue }
|
|
271
|
+
} else if (typeof newValue === 'string') {
|
|
272
|
+
sectionValue[subsection] = newValue
|
|
273
|
+
} else {
|
|
274
|
+
sectionValue = { ...config[section], [subsection]: { ...config[section][subsection], [fieldName]: newValue } }
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let updatedConfig = { ...config, [section]: sectionValue }
|
|
279
|
+
|
|
280
|
+
enforceRestrictions(updatedConfig)
|
|
281
|
+
|
|
282
|
+
updateConfig(updatedConfig)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const [ displayPanel, setDisplayPanel ] = useState(true)
|
|
286
|
+
const [ lollipopColorStyle, setLollipopColorStyle ] = useState('two-tone')
|
|
287
|
+
|
|
288
|
+
if (loading) {
|
|
289
|
+
return null
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const setLollipopShape = (shape) => {
|
|
293
|
+
updateConfig({
|
|
294
|
+
...config,
|
|
295
|
+
lollipopShape: shape
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const removeFilter = (index) => {
|
|
300
|
+
let filters = [ ...config.filters ]
|
|
301
|
+
|
|
302
|
+
filters.splice(index, 1)
|
|
303
|
+
|
|
304
|
+
updateConfig({ ...config, filters })
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const updateFilterProp = (name, index, value) => {
|
|
308
|
+
let filters = [ ...config.filters ]
|
|
309
|
+
|
|
310
|
+
filters[index][name] = value
|
|
311
|
+
|
|
312
|
+
updateConfig({ ...config, filters })
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const addNewFilter = () => {
|
|
316
|
+
let filters = config.filters ? [ ...config.filters ] : []
|
|
317
|
+
|
|
318
|
+
filters.push({ values: [] })
|
|
319
|
+
|
|
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 })
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const removeSeries = (seriesKey) => {
|
|
330
|
+
|
|
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
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (seriesIndex !== -1) {
|
|
343
|
+
series.splice(seriesIndex, 1)
|
|
344
|
+
|
|
345
|
+
let newConfig = { ...config, series }
|
|
346
|
+
|
|
347
|
+
if (series.length === 0) {
|
|
348
|
+
delete newConfig.series
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
updateConfig(newConfig)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (config.visualizationType === 'Paired Bar') {
|
|
355
|
+
updateConfig({
|
|
356
|
+
...config,
|
|
357
|
+
series: []
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const addNewExclusion = (exclusionKey) => {
|
|
363
|
+
let newExclusion = [ ...config.exclusions.keys ]
|
|
364
|
+
newExclusion.push(exclusionKey)
|
|
365
|
+
|
|
366
|
+
let payload = { ...config.exclusions, keys: newExclusion }
|
|
367
|
+
updateConfig({ ...config, exclusions: payload })
|
|
368
|
+
}
|
|
369
|
+
|
|
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
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const getColumns = (filter = true) => {
|
|
396
|
+
let columns = {}
|
|
397
|
+
|
|
398
|
+
unfilteredData.map(row => {
|
|
399
|
+
Object.keys(row).forEach(columnName => columns[columnName] = true)
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
if (filter) {
|
|
403
|
+
let confidenceUpper = config.confidenceKeys?.upper && config.confidenceKeys?.upper !== ''
|
|
404
|
+
let confidenceLower = config.confidenceKeys?.lower && config.confidenceKeys?.lower !== ''
|
|
405
|
+
|
|
406
|
+
Object.keys(columns).forEach(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
|
+
) {
|
|
415
|
+
delete columns[key]
|
|
416
|
+
}
|
|
417
|
+
})
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return Object.keys(columns)
|
|
421
|
+
}
|
|
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
|
+
|
|
441
|
+
const onBackClick = () => {
|
|
442
|
+
setDisplayPanel(!displayPanel)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const Error = () => {
|
|
446
|
+
return (
|
|
447
|
+
<section className="waiting">
|
|
448
|
+
<section className="waiting-container">
|
|
449
|
+
<h3>Error With Configuration</h3>
|
|
450
|
+
<p>{config.runtime.editorErrorMessage}</p>
|
|
451
|
+
</section>
|
|
452
|
+
</section>
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const Confirm = () => {
|
|
458
|
+
const confirmDone = (e) => {
|
|
459
|
+
e.preventDefault()
|
|
460
|
+
|
|
461
|
+
let newConfig = { ...config }
|
|
462
|
+
delete newConfig.newViz
|
|
463
|
+
|
|
464
|
+
updateConfig(newConfig)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return (
|
|
468
|
+
<section className="waiting">
|
|
469
|
+
<section className="waiting-container">
|
|
470
|
+
<h3>Finish Configuring</h3>
|
|
471
|
+
<p>Set all required options to the left and confirm below to display a preview of the chart.</p>
|
|
472
|
+
<button className="btn" style={{ margin: '1em auto' }} disabled={missingRequiredSections()} onClick={confirmDone}>I'm Done</button>
|
|
473
|
+
</section>
|
|
474
|
+
</section>
|
|
475
|
+
)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const convertStateToConfig = () => {
|
|
479
|
+
let strippedState = JSON.parse(JSON.stringify(config))
|
|
480
|
+
if (false === missingRequiredSections()) {
|
|
481
|
+
delete strippedState.newViz
|
|
482
|
+
}
|
|
483
|
+
delete strippedState.runtime
|
|
484
|
+
|
|
485
|
+
return strippedState
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
useEffect(() => {
|
|
489
|
+
// Pass up to Editor if needed
|
|
490
|
+
if (setParentConfig) {
|
|
491
|
+
const newConfig = convertStateToConfig()
|
|
492
|
+
setParentConfig(newConfig)
|
|
493
|
+
}
|
|
494
|
+
|
|
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)}>×</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
|
+
|
|
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
|
+
|
|
647
|
+
return (
|
|
648
|
+
<ErrorBoundary component="EditorPanel">
|
|
649
|
+
{config.newViz && <Confirm/>}
|
|
650
|
+
{undefined === config.newViz && config.runtime && config.runtime.editorErrorMessage && <Error/>}
|
|
651
|
+
<button className={displayPanel ? `editor-toggle` : `editor-toggle collapsed`} title={displayPanel ? `Collapse Editor` : `Expand Editor`} onClick={onBackClick}></button>
|
|
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>
|
|
654
|
+
<section className="form-container">
|
|
655
|
+
<form>
|
|
656
|
+
<Accordion allowZeroExpanded={true}>
|
|
657
|
+
<AccordionItem> {/* General */}
|
|
658
|
+
<AccordionItemHeading>
|
|
659
|
+
<AccordionItemButton>
|
|
660
|
+
General
|
|
661
|
+
</AccordionItemButton>
|
|
662
|
+
</AccordionItemHeading>
|
|
663
|
+
<AccordionItemPanel>
|
|
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
|
+
}
|
|
698
|
+
</AccordionItemPanel>
|
|
699
|
+
</AccordionItem>
|
|
700
|
+
|
|
701
|
+
|
|
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)}>×</span>
|
|
754
|
+
}
|
|
755
|
+
</span>
|
|
756
|
+
</li>
|
|
757
|
+
)
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return (
|
|
761
|
+
<li key={series.dataKey}>
|
|
762
|
+
<div className="series-list__name" data-title={series.dataKey}>
|
|
763
|
+
<div className="series-list__name--text">
|
|
764
|
+
{series.dataKey}
|
|
765
|
+
</div>
|
|
766
|
+
</div>
|
|
767
|
+
{config.series.length > 1 &&
|
|
768
|
+
<span className="series-list__remove" onClick={() => removeSeries(series.dataKey)}>×</span>
|
|
769
|
+
}
|
|
770
|
+
</li>
|
|
771
|
+
)
|
|
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' && (
|
|
783
|
+
<>
|
|
784
|
+
<span className="divider-heading">Confidence Keys</span>
|
|
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()}/>
|
|
787
|
+
</>
|
|
788
|
+
)}
|
|
789
|
+
</AccordionItemPanel>
|
|
790
|
+
</AccordionItem>
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
<AccordionItem>
|
|
794
|
+
<AccordionItemHeading>
|
|
795
|
+
<AccordionItemButton>
|
|
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"/>}
|
|
801
|
+
</AccordionItemButton>
|
|
802
|
+
</AccordionItemHeading>
|
|
803
|
+
<AccordionItemPanel>
|
|
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
|
+
}
|
|
814
|
+
{config.visualizationType !== 'Pie' && (
|
|
815
|
+
<>
|
|
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}/>}
|
|
820
|
+
</>
|
|
821
|
+
)}
|
|
822
|
+
<span className="divider-heading">Number Formatting</span>
|
|
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}/>
|
|
825
|
+
<div className="two-col-inputs">
|
|
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
|
+
}/>
|
|
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
|
+
}
|
|
865
|
+
</AccordionItemPanel>
|
|
866
|
+
</AccordionItem>
|
|
867
|
+
|
|
868
|
+
<AccordionItem>
|
|
869
|
+
<AccordionItemHeading>
|
|
870
|
+
<AccordionItemButton>
|
|
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"/>}
|
|
876
|
+
</AccordionItemButton>
|
|
877
|
+
</AccordionItemHeading>
|
|
878
|
+
<AccordionItemPanel>
|
|
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
|
+
|
|
902
|
+
{config.visualizationType !== 'Pie' && (
|
|
903
|
+
<>
|
|
904
|
+
<TextField value={config.xAxis.label} section="xAxis" fieldName="label" label="Label" updateField={updateField}/>
|
|
905
|
+
|
|
906
|
+
{config.xAxis.type === 'date' && (
|
|
907
|
+
<>
|
|
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}/>
|
|
911
|
+
</>
|
|
912
|
+
)}
|
|
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
|
+
}
|
|
975
|
+
</>
|
|
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
|
+
}
|
|
1007
|
+
</AccordionItemPanel>
|
|
1008
|
+
</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
|
+
|
|
1023
|
+
<AccordionItem>
|
|
1024
|
+
<AccordionItemHeading>
|
|
1025
|
+
<AccordionItemButton>
|
|
1026
|
+
Legend
|
|
1027
|
+
</AccordionItemButton>
|
|
1028
|
+
</AccordionItemHeading>
|
|
1029
|
+
<AccordionItemPanel>
|
|
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' ]}/>
|
|
1042
|
+
</AccordionItemPanel>
|
|
1043
|
+
</AccordionItem>
|
|
1044
|
+
|
|
1045
|
+
<AccordionItem>
|
|
1046
|
+
<AccordionItemHeading>
|
|
1047
|
+
<AccordionItemButton>
|
|
1048
|
+
Filters
|
|
1049
|
+
</AccordionItemButton>
|
|
1050
|
+
</AccordionItemHeading>
|
|
1051
|
+
<AccordionItemPanel>
|
|
1052
|
+
{config.filters && <ul className="filters-list">
|
|
1053
|
+
{config.filters.map((filter, index) => (
|
|
1054
|
+
<fieldset className="edit-block" key={index}>
|
|
1055
|
+
<button type="button" className="remove-column" onClick={() => {
|
|
1056
|
+
removeFilter(index)
|
|
1057
|
+
}}>Remove
|
|
1058
|
+
</button>
|
|
1059
|
+
<label>
|
|
1060
|
+
<span className="edit-label column-heading">Filter</span>
|
|
1061
|
+
<select value={filter.columnName} onChange={(e) => {
|
|
1062
|
+
updateFilterProp('columnName', index, e.target.value)
|
|
1063
|
+
}}>
|
|
1064
|
+
<option value="">- Select Option -</option>
|
|
1065
|
+
{getColumns().map((dataKey, index) => (
|
|
1066
|
+
<option value={dataKey} key={index}>{dataKey}</option>
|
|
1067
|
+
))}
|
|
1068
|
+
</select>
|
|
1069
|
+
</label>
|
|
1070
|
+
<label>
|
|
1071
|
+
<span className="edit-label column-heading">Label</span>
|
|
1072
|
+
<input type="text" value={filter.label} onChange={(e) => {
|
|
1073
|
+
updateFilterProp('label', index, e.target.value)
|
|
1074
|
+
}}/>
|
|
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
|
+
|
|
1123
|
+
</fieldset>
|
|
1124
|
+
)
|
|
1125
|
+
)}
|
|
1126
|
+
</ul>}
|
|
1127
|
+
{!config.filters && <p style={{ textAlign: 'center' }}>There are currently no filters.</p>}
|
|
1128
|
+
<button type="button" onClick={addNewFilter} className="btn full-width">Add Filter</button>
|
|
1129
|
+
</AccordionItemPanel>
|
|
1130
|
+
</AccordionItem>
|
|
1131
|
+
|
|
1132
|
+
<AccordionItem>
|
|
1133
|
+
<AccordionItemHeading>
|
|
1134
|
+
<AccordionItemButton>
|
|
1135
|
+
Visual
|
|
1136
|
+
</AccordionItemButton>
|
|
1137
|
+
</AccordionItemHeading>
|
|
1138
|
+
<AccordionItemPanel>
|
|
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
|
+
|
|
1175
|
+
{config.series?.some(series => series.type === 'Bar') &&
|
|
1176
|
+
<Select value={config.barHasBorder} fieldName="barHasBorder" label="Bar Borders" updateField={updateField} options={[ 'true', 'false' ]}/>
|
|
1177
|
+
}
|
|
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' ]}/>
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
<label className="header">
|
|
1184
|
+
<span className="edit-label">Header Theme</span>
|
|
1185
|
+
<ul className="color-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}>
|
|
1190
|
+
</li>
|
|
1191
|
+
))}
|
|
1192
|
+
</ul>
|
|
1193
|
+
</label>
|
|
1194
|
+
<label>
|
|
1195
|
+
<span className="edit-label">Chart Color Palette</span>
|
|
1196
|
+
</label>
|
|
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>
|
|
1200
|
+
<ul className="color-palette">
|
|
1201
|
+
{filteredPallets.map((palette) => {
|
|
1202
|
+
|
|
1203
|
+
const colorOne = {
|
|
1204
|
+
backgroundColor: colorPalettes[palette][2]
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
const colorTwo = {
|
|
1208
|
+
backgroundColor: colorPalettes[palette][3]
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
const colorThree = {
|
|
1212
|
+
backgroundColor: colorPalettes[palette][5]
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
return (
|
|
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>
|
|
1223
|
+
)
|
|
1224
|
+
})}
|
|
1225
|
+
</ul>
|
|
1226
|
+
<span>Non-Sequential</span>
|
|
1227
|
+
<ul className="color-palette">
|
|
1228
|
+
{filteredQualitative.map((palette) => {
|
|
1229
|
+
|
|
1230
|
+
const colorOne = {
|
|
1231
|
+
backgroundColor: colorPalettes[palette][2]
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
const colorTwo = {
|
|
1235
|
+
backgroundColor: colorPalettes[palette][4]
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
const colorThree = {
|
|
1239
|
+
backgroundColor: colorPalettes[palette][6]
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
return (
|
|
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>
|
|
1251
|
+
)
|
|
1252
|
+
})}
|
|
1253
|
+
</ul>
|
|
1254
|
+
|
|
1255
|
+
{config.visualizationType !== 'Pie' && (
|
|
1256
|
+
<>
|
|
1257
|
+
{config.orientation !== 'horizontal' &&
|
|
1258
|
+
<CheckBox value={config.labels} fieldName="labels" label="Display label on data" updateField={updateField}/>
|
|
1259
|
+
}
|
|
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
|
+
}/>
|
|
1268
|
+
</>
|
|
1269
|
+
)}
|
|
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> */}
|
|
1284
|
+
</AccordionItemPanel>
|
|
1285
|
+
</AccordionItem>
|
|
1286
|
+
|
|
1287
|
+
<AccordionItem>
|
|
1288
|
+
<AccordionItemHeading>
|
|
1289
|
+
<AccordionItemButton>
|
|
1290
|
+
Data Table
|
|
1291
|
+
</AccordionItemButton>
|
|
1292
|
+
</AccordionItemHeading>
|
|
1293
|
+
<AccordionItemPanel>
|
|
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}/>
|
|
1306
|
+
</AccordionItemPanel>
|
|
1307
|
+
</AccordionItem>
|
|
1308
|
+
</Accordion>
|
|
1309
|
+
</form>
|
|
1310
|
+
{config.type !== 'Spark Line' &&
|
|
1311
|
+
<AdvancedEditor loadConfig={updateConfig} state={config} convertStateToConfig={convertStateToConfig} />
|
|
1312
|
+
}
|
|
1313
|
+
</section>
|
|
1314
|
+
</section>
|
|
1315
|
+
</ErrorBoundary>
|
|
1316
|
+
)
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
export default EditorPanel
|