@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.
- package/dist/cdcchart.js +77 -4
- 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/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-example-config.json +1 -0
- package/examples/private/newtest.csv +101 -0
- package/examples/private/test.json +10124 -0
- package/package.json +9 -5
- package/src/CdcChart.tsx +417 -149
- package/src/components/BarChart.tsx +431 -24
- package/src/components/BarStackVertical.js +0 -0
- package/src/components/DataTable.tsx +55 -28
- package/src/components/EditorPanel.js +914 -260
- package/src/components/LineChart.tsx +4 -3
- package/src/components/LinearChart.tsx +258 -88
- package/src/components/PairedBarChart.tsx +144 -0
- package/src/components/PieChart.tsx +30 -16
- package/src/components/SparkLine.js +206 -0
- package/src/data/initial-state.js +59 -32
- package/src/hooks/useActiveElement.js +19 -0
- package/src/hooks/useColorPalette.ts +83 -0
- package/src/hooks/useReduceData.ts +43 -0
- package/src/index.html +49 -13
- package/src/index.tsx +6 -2
- package/src/scss/editor-panel.scss +12 -4
- package/src/scss/main.scss +112 -3
- package/LICENSE +0 -201
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect, useCallback, memo, useContext } from 'react'
|
|
2
|
-
import
|
|
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
|
|
14
|
-
import
|
|
12
|
+
import { timeParse, timeFormat } from 'd3-time-format'
|
|
13
|
+
import { useDebounce, useDebouncedCallback } from 'use-debounce'
|
|
15
14
|
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
15
|
+
import Context from '../context'
|
|
16
|
+
import WarningImage from '../images/warning.svg'
|
|
17
|
+
import AdvancedEditor from '@cdc/core/components/AdvancedEditor';
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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={
|
|
76
|
-
|
|
77
|
-
{
|
|
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={
|
|
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) => {
|
|
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) => {
|
|
141
|
-
|
|
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:
|
|
153
|
-
<button className="btn full-width" onClick={(e) => {
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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 ===
|
|
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 [
|
|
217
|
-
const [
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
|
272
|
-
let
|
|
362
|
+
const addNewExclusion = (exclusionKey) => {
|
|
363
|
+
let newExclusion = [ ...config.exclusions.keys ]
|
|
364
|
+
newExclusion.push(exclusionKey)
|
|
273
365
|
|
|
274
|
-
|
|
366
|
+
let payload = { ...config.exclusions, keys: newExclusion }
|
|
367
|
+
updateConfig({ ...config, exclusions: payload })
|
|
368
|
+
}
|
|
275
369
|
|
|
276
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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)}>×</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 ===
|
|
373
|
-
<
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
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
|
+
)
|
|
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=
|
|
411
|
-
<div className="series-list__name
|
|
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
|
-
|
|
414
|
-
<span className="series-list__dropdown">{typeDropdown}</span>
|
|
767
|
+
{config.series.length > 1 &&
|
|
415
768
|
<span className="series-list__remove" onClick={() => removeSeries(series.dataKey)}>×</span>
|
|
416
|
-
|
|
769
|
+
}
|
|
417
770
|
</li>
|
|
418
771
|
)
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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 ||
|
|
439
|
-
<Select value={config.confidenceKeys.lower ||
|
|
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
|
-
|
|
443
|
-
|
|
789
|
+
</AccordionItemPanel>
|
|
790
|
+
</AccordionItem>
|
|
791
|
+
}
|
|
792
|
+
|
|
444
793
|
<AccordionItem>
|
|
445
794
|
<AccordionItemHeading>
|
|
446
795
|
<AccordionItemButton>
|
|
447
|
-
{config.
|
|
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' &&
|
|
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=
|
|
457
|
-
{config.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
481
|
-
{config.xAxis.type ===
|
|
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
|
-
|
|
490
|
-
<
|
|
491
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
|
|
514
|
-
<
|
|
515
|
-
|
|
516
|
-
|
|
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={() => {
|
|
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) => {
|
|
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) => {
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1176
|
+
<Select value={config.barHasBorder} fieldName="barHasBorder" label="Bar Borders" updateField={updateField} options={[ 'true', 'false' ]}/>
|
|
561
1177
|
}
|
|
562
|
-
|
|
563
|
-
|
|
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(
|
|
569
|
-
<li title={
|
|
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
|
-
<
|
|
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
|
-
{
|
|
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][
|
|
1208
|
+
backgroundColor: colorPalettes[palette][3]
|
|
587
1209
|
}
|
|
588
1210
|
|
|
589
1211
|
const colorThree = {
|
|
590
|
-
backgroundColor: colorPalettes[palette][
|
|
1212
|
+
backgroundColor: colorPalettes[palette][5]
|
|
591
1213
|
}
|
|
592
1214
|
|
|
593
1215
|
return (
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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
|
|
1226
|
+
<span>Non-Sequential</span>
|
|
603
1227
|
<ul className="color-palette">
|
|
604
|
-
{
|
|
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][
|
|
1235
|
+
backgroundColor: colorPalettes[palette][4]
|
|
612
1236
|
}
|
|
613
1237
|
|
|
614
1238
|
const colorThree = {
|
|
615
|
-
backgroundColor: colorPalettes[palette][
|
|
1239
|
+
backgroundColor: colorPalettes[palette][6]
|
|
616
1240
|
}
|
|
617
1241
|
|
|
1242
|
+
|
|
618
1243
|
return (
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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.
|
|
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
|
-
{(
|
|
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
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
-
|
|
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
|