@cdc/map 2.6.2 → 2.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/cdcmap.js +37 -29
  2. package/examples/bubble-us.json +363 -0
  3. package/examples/bubble-world.json +427 -0
  4. package/examples/default-county.json +105 -0
  5. package/examples/default-hex.json +475 -0
  6. package/examples/default-single-state.json +109 -0
  7. package/examples/default-usa-regions.json +118 -0
  8. package/examples/default-usa.json +744 -603
  9. package/examples/default-world-data.json +1450 -0
  10. package/examples/default-world.json +5 -3
  11. package/examples/example-city-state.json +510 -0
  12. package/examples/example-world-map.json +1596 -0
  13. package/examples/gender-rate-map.json +1 -0
  14. package/examples/private/atsdr.json +439 -0
  15. package/examples/private/atsdr_new.json +436 -0
  16. package/examples/private/bubble.json +285 -0
  17. package/examples/private/default-world-data.json +1444 -0
  18. package/examples/private/default.json +968 -0
  19. package/examples/private/map.csv +60 -0
  20. package/examples/private/mdx.json +210 -0
  21. package/examples/private/regions.json +52 -0
  22. package/examples/private/wcmsrd-13881-data.json +2858 -0
  23. package/examples/private/wcmsrd-13881.json +5823 -0
  24. package/examples/private/wcmsrd-test.json +268 -0
  25. package/examples/private/world.json +1580 -0
  26. package/examples/private/worldmap.json +1490 -0
  27. package/package.json +11 -7
  28. package/src/CdcMap.js +726 -202
  29. package/src/components/BubbleList.js +240 -0
  30. package/src/components/CityList.js +22 -3
  31. package/src/components/CountyMap.js +557 -0
  32. package/src/components/DataTable.js +85 -24
  33. package/src/components/EditorPanel.js +2455 -1204
  34. package/src/components/Geo.js +1 -1
  35. package/src/components/Sidebar.js +5 -5
  36. package/src/components/SingleStateMap.js +326 -0
  37. package/src/components/UsaMap.js +41 -9
  38. package/src/components/UsaRegionMap.js +319 -0
  39. package/src/components/WorldMap.js +112 -35
  40. package/src/data/abbreviations.js +57 -0
  41. package/src/data/country-coordinates.js +250 -0
  42. package/src/data/county-map-halfquality.json +58453 -0
  43. package/src/data/county-map-quarterquality.json +1 -0
  44. package/src/data/county-topo.json +1 -0
  45. package/src/data/dfc-map.json +1 -0
  46. package/src/data/initial-state.js +21 -4
  47. package/src/data/newtest.json +1 -0
  48. package/src/data/state-abbreviations.js +60 -0
  49. package/src/data/state-coordinates.js +55 -0
  50. package/src/data/supported-geos.js +3592 -163
  51. package/src/data/test.json +1 -0
  52. package/src/data/us-regions-topo-2.json +360525 -0
  53. package/src/data/us-regions-topo.json +37894 -0
  54. package/src/hooks/useActiveElement.js +19 -0
  55. package/src/hooks/useColorPalette.ts +96 -0
  56. package/src/index.html +35 -20
  57. package/src/index.js +8 -4
  58. package/src/scss/datatable.scss +2 -1
  59. package/src/scss/editor-panel.scss +76 -55
  60. package/src/scss/main.scss +10 -1
  61. package/src/scss/map.scss +257 -121
  62. package/src/scss/sidebar.scss +0 -1
  63. package/uploads/upload-example-city-state.json +392 -0
  64. package/uploads/upload-example-world-data.json +1490 -0
  65. package/LICENSE +0 -201
  66. package/src/data/color-palettes.js +0 -191
  67. package/src/images/map-folded.svg +0 -1
@@ -1,1253 +1,2504 @@
1
- import React, { useState, useEffect, useCallback } from 'react'
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
2
  import {
3
- Accordion,
4
- AccordionItem,
5
- AccordionItemHeading,
6
- AccordionItemPanel,
7
- AccordionItemButton,
3
+ Accordion,
4
+ AccordionItem,
5
+ AccordionItemHeading,
6
+ AccordionItemPanel,
7
+ AccordionItemButton,
8
8
  } from 'react-accessible-accordion';
9
- import ReactTooltip from 'react-tooltip'
9
+ import ReactTooltip from 'react-tooltip';
10
10
  import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
11
11
  import { useDebounce } from 'use-debounce';
12
12
 
13
- import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
14
- import Waiting from '@cdc/core/components/Waiting'
13
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
14
+ import Waiting from '@cdc/core/components/Waiting';
15
15
 
16
- import MapIcon from '../images/map-folded.svg';
17
16
  import UsaGraphic from '@cdc/core/assets/usa-graphic.svg';
18
17
  import WorldGraphic from '@cdc/core/assets/world-graphic.svg';
19
- import colorPalettes from '../data/color-palettes';
18
+ import AlabamaGraphic from '@cdc/core/assets/alabama-graphic.svg';
19
+ import colorPalettes from '../../../core/data/colorPalettes';
20
20
  import worldDefaultConfig from '../../examples/default-world.json';
21
21
  import usaDefaultConfig from '../../examples/default-usa.json';
22
+ import countyDefaultConfig from '../../examples/default-county.json';
22
23
  import QuestionIcon from '@cdc/core/assets/question-circle.svg';
23
24
 
24
- const ReactTags = require('react-tag-autocomplete'); // Future: Lazy
25
-
26
- const Helper = ({text}) => {
27
- return (
28
- <span className='tooltip helper' data-tip={text}>
29
- <QuestionIcon />
30
- </span>
31
- )
32
- }
25
+ import { supportedStatesFipsCodes } from '../data/supported-geos';
26
+ import { GET_PALETTE,useColorPalette } from '../hooks/useColorPalette';
27
+ import InputCheckbox from '@cdc/core/components/inputs/InputCheckbox';
28
+ import InputToggle from '@cdc/core/components/inputs/InputToggle';
29
+ import Tooltip from '@cdc/core/components/ui/Tooltip'
30
+ import Icon from '@cdc/core/components/ui/Icon'
33
31
 
34
- const TextField = ({label, section = null, subsection = null, fieldName, updateField, value: stateValue, type = "input", helper = null, ...attributes}) => {
35
- const [ value, setValue ] = useState(stateValue);
32
+ import AdvancedEditor from '@cdc/core/components/AdvancedEditor';
36
33
 
37
- const [ debouncedValue ] = useDebounce(value, 500);
34
+ const ReactTags = require('react-tag-autocomplete'); // Future: Lazy
38
35
 
39
- useEffect(() => {
40
- if('string' === typeof debouncedValue && stateValue !== debouncedValue ) {
41
- updateField(section, subsection, fieldName, debouncedValue)
42
- }
43
- }, [debouncedValue])
36
+ const TextField = ({
37
+ label,
38
+ section = null,
39
+ subsection = null,
40
+ fieldName,
41
+ updateField,
42
+ value: stateValue,
43
+ type = 'input',
44
+ tooltip,
45
+ ...attributes
46
+ }) => {
47
+ const [value, setValue] = useState(stateValue);
48
+
49
+ const [debouncedValue] = useDebounce(value, 500);
50
+
51
+ useEffect(() => {
52
+ if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
53
+ updateField(section, subsection, fieldName, debouncedValue);
54
+ }
55
+ }, [debouncedValue]);
56
+
57
+ let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`;
58
+
59
+ const onChange = (e) => setValue(e.target.value);
60
+
61
+ let formElement = <input type='text' name={name} onChange={onChange} {...attributes} value={value} />;
62
+
63
+ if ('textarea' === type) {
64
+ formElement = <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>;
65
+ }
66
+
67
+ if ('number' === type) {
68
+ formElement = <input type='number' name={name} onChange={onChange} {...attributes} value={value} />;
69
+ }
70
+
71
+ return (
72
+ <label>
73
+ <span className='edit-label column-heading'>
74
+ {label}{tooltip}
75
+ </span>
76
+ {formElement}
77
+ </label>
78
+ );
79
+ };
44
80
 
45
- let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`;
81
+ const EditorPanel = (props) => {
82
+ const {
83
+ state,
84
+ columnsInData = [],
85
+ loadConfig,
86
+ setState,
87
+ isDashboard,
88
+ setParentConfig,
89
+ setRuntimeFilters,
90
+ runtimeFilters,
91
+ runtimeLegend,
92
+ } = props;
46
93
 
47
- const onChange = (e) => setValue(e.target.value);
94
+ const { general, columns, legend, dataTable, tooltips } = state;
48
95
 
49
- let formElement = <input type="text" name={name} onChange={onChange} {...attributes} value={value} />
96
+ const [requiredColumns, setRequiredColumns] = useState(null); // Simple state so we know if we need more information before parsing the map
50
97
 
51
- if('textarea' === type) {
52
- formElement = (
53
- <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
54
- )
55
- }
98
+ const [configTextboxValue, setConfigTextbox] = useState({});
56
99
 
57
- if('number' === type) {
58
- formElement = <input type="number" name={name} onChange={onChange} {...attributes} value={value} />
59
- }
100
+ const [loadedDefault, setLoadedDefault] = useState(false);
60
101
 
61
- return (
62
- <label>
63
- <span className="edit-label column-heading">{label} {helper && <Helper text={helper} />}</span>
64
- {formElement}
65
- </label>
66
- )
67
- }
102
+ const [displayPanel, setDisplayPanel] = useState(true);
68
103
 
69
- const EditorPanel = (props) => {
70
- const {
71
- state,
72
- columnsInData = [],
73
- loadConfig,
74
- setState,
75
- isDashboard,
76
- setParentConfig,
77
- runtimeFilters,
78
- runtimeLegend,
79
- } = props
80
-
81
- const { general, columns, legend, dataTable, tooltips} = state
82
-
83
- const [ requiredColumns, setRequiredColumns ] = useState(null) // Simple state so we know if we need more information before parsing the map
84
-
85
- const [ configTextboxValue, setConfigTextbox ] = useState({})
86
-
87
- const [ loadedDefault, setLoadedDefault ] = useState(false)
88
-
89
- const [ displayPanel, setDisplayPanel ] = useState(true)
90
-
91
- const [ advancedToggle, setAdvancedToggle ] = useState(false)
92
-
93
- const [ activeFilterValueForDescription, setActiveFilterValueForDescription ] = useState([0,0])
94
-
95
- const [ editorCatOrder, setEditorCatOrder ] = useState(state.legend.categoryValuesOrder || [])
96
-
97
- const headerColors = ['theme-blue','theme-purple','theme-brown','theme-teal','theme-pink','theme-orange','theme-slate','theme-indigo','theme-cyan','theme-green','theme-amber']
98
-
99
- const categoryMove = (idx1, idx2) => {
100
- let categoryValuesOrder = [...editorCatOrder]
101
-
102
- let [movedItem] = categoryValuesOrder.splice(idx1, 1)
103
-
104
- categoryValuesOrder.splice(idx2, 0, movedItem)
105
-
106
- setEditorCatOrder(categoryValuesOrder)
107
-
108
- setState({
109
- ...state,
110
- legend: {
111
- ...state.legend,
112
- categoryValuesOrder
113
- }
114
- })
115
- }
116
-
117
- const DynamicDesc = ({label, fieldName, value: stateValue, type = "input", helper = null, ...attributes}) => {
118
- const [ value, setValue ] = useState(stateValue);
119
-
120
- const [ debouncedValue ] = useDebounce(value, 500);
121
-
122
- useEffect(() => {
123
- if('string' === typeof debouncedValue && stateValue !== debouncedValue ) {
124
- handleEditorChanges("changeLegendDescription", [String(activeFilterValueForDescription), debouncedValue])
125
- }
126
- }, [debouncedValue])
127
-
128
- const onChange = (e) => setValue(e.target.value);
129
-
130
- return (
131
- <textarea onChange={onChange} {...attributes} value={value}></textarea>
132
- )
133
- }
134
-
135
- const handleEditorChanges = async (property, value) => {
136
- switch (property) {
137
- case 'showTitle':
138
- setState({
139
- ...state,
140
- general: {
141
- ...state.general,
142
- showTitle: value
143
- }
144
- })
145
- break;
146
- case 'showSidebar':
147
- setState({
148
- ...state,
149
- general: {
150
- ...state.general,
151
- showSidebar: value
152
- }
153
- })
154
- break;
155
- case 'fullBorder':
156
- setState({
157
- ...state,
158
- general: {
159
- ...state.general,
160
- fullBorder: value
161
- }
162
- })
163
- break;
164
- case 'expandDataTable':
165
- setState({
166
- ...state,
167
- general: {
168
- ...state.general,
169
- expandDataTable: value
170
- }
171
- })
172
- break;
173
- case 'color':
174
- setState({
175
- ...state,
176
- color: value
177
- })
178
- break;
179
- case 'sidebarPosition':
180
- setState({
181
- ...state,
182
- legend: {
183
- ...state.legend,
184
- position: value
185
- }
186
- })
187
- break;
188
- case 'geoBorderColor':
189
- setState({
190
- ...state,
191
- general: {
192
- ...state.general,
193
- geoBorderColor: value
194
- }
195
- })
196
- break;
197
- case 'headerColor':
198
- setState({
199
- ...state,
200
- general: {
201
- ...state.general,
202
- headerColor: value
203
- }
204
- })
205
- break;
206
- case 'navigateColumn':
207
- setState({
208
- ...state,
209
- columns: {
210
- ...state.columns,
211
- navigate: {
212
- ...state.columns.navigate,
213
- name: value
214
- }
215
- }
216
- })
217
- break;
218
- case 'legendDescription':
219
- setState({
220
- ...state,
221
- legend: {
222
- ...state.legend,
223
- description: value
224
- }
225
- })
226
- break;
227
- case 'legendType':
228
- setState({
229
- ...state,
230
- legend: {
231
- ...state.legend,
232
- type: value
233
- }
234
- })
235
- break;
236
- case 'legendNumber':
237
- setState({
238
- ...state,
239
- legend: {
240
- ...state.legend,
241
- numberOfItems: parseInt(value)
242
- }
243
- })
244
- break;
245
- case 'changeActiveFilterValue':
246
- const arrVal = value.split(',')
247
-
248
- setActiveFilterValueForDescription(arrVal)
249
- break;
250
- case 'unifiedLegend':
251
- setState({
252
- ...state,
253
- legend: {
254
- ...state.legend,
255
- unified: value
256
- }
257
- })
258
- break;
259
- case 'separateZero':
260
- setState({
261
- ...state,
262
- legend: {
263
- ...state.legend,
264
- separateZero: value
265
- }
266
- })
267
- break;
268
- case 'toggleDownloadButton':
269
- setState({
270
- ...state,
271
- general: {
272
- ...state.general,
273
- showDownloadButton: !state.general.showDownloadButton
274
- }
275
- })
276
- break;
277
- case 'toggleDownloadMediaButton':
278
- setState({
279
- ...state,
280
- general: {
281
- ...state.general,
282
- showDownloadMediaButton: !state.general.showDownloadMediaButton
283
- }
284
- })
285
- break;
286
- case 'displayAsHex':
287
- setState({
288
- ...state,
289
- general: {
290
- ...state.general,
291
- displayAsHex: value
292
- }
293
- })
294
- break;
295
- case 'editorMapType':
296
- switch(value) {
297
- case 'data':
298
- setState({
299
- ...state,
300
- general: {
301
- ...state.general,
302
- showSidebar: true,
303
- type: "data"
304
- }
305
- })
306
- break;
307
- case 'navigation':
308
- setState({
309
- ...state,
310
- general: {
311
- ...state.general,
312
- showSidebar: false,
313
- type: "navigation"
314
- },
315
- tooltips: {
316
- ...state.tooltips,
317
- appearanceType: "hover"
318
- }
319
- })
320
- break;
321
- default:
322
- console.warn("Map type not set")
323
- break;
324
- }
325
- break;
326
- case 'geoType':
327
- // If we're still working with default data, switch to the world default to show it as an example
328
- if(true === loadedDefault && 'world' === value) {
329
- loadConfig(worldDefaultConfig)
330
- ReactTooltip.rebuild()
331
- break;
332
- }
333
-
334
- if(true === loadedDefault && 'us' === value) {
335
- loadConfig(usaDefaultConfig)
336
- ReactTooltip.rebuild()
337
- break;
338
- }
339
-
340
- switch(value) {
341
- case 'us':
342
- setState({
343
- ...state,
344
- general: {
345
- ...state.general,
346
- geoType: "us"
347
- }
348
- })
349
- break;
350
- case 'world':
351
- setState({
352
- ...state,
353
- general: {
354
- ...state.general,
355
- geoType: "world"
356
- }
357
- })
358
- break;
359
- default:
360
- console.warn("Map type not set.")
361
- break;
362
- }
363
-
364
- ReactTooltip.rebuild()
365
- break;
366
- case 'singleColumnLegend':
367
- setState({
368
- ...state,
369
- legend: {
370
- ...state.legend,
371
- singleColumn: !state.legend.singleColumn
372
- }
373
- })
374
- break;
375
- case 'dynamicDescription':
376
- setState({
377
- ...state,
378
- editor: {
379
- ...state.editor,
380
- activeFilterValueForDescription: value
381
- },
382
- legend: {
383
- ...state.legend,
384
- dynamicDescription: !state.legend.dynamicDescription
385
- }
386
- })
387
- break;
388
- case 'changeLegendDescription':
389
- const [filterValKey, filterValDesc] = value
390
- setState({
391
- ...state,
392
- legend: {
393
- ...state.legend,
394
- descriptions: {
395
- ...state.legend.descriptions,
396
- [filterValKey]: [filterValDesc]
397
- }
398
- }
399
- })
400
- break;
401
- case 'appearanceType':
402
- setState({
403
- ...state,
404
- tooltips: {
405
- ...state.tooltips,
406
- appearanceType: value
407
- }
408
- })
409
- break;
410
- case 'linkLabel':
411
- setState({
412
- ...state,
413
- tooltips: {
414
- ...state.tooltips,
415
- linkLabel: value
416
- }
417
- })
418
- break;
419
- case 'displayStateLabels':
420
- setState({
421
- ...state,
422
- general: {
423
- ...state.general,
424
- displayStateLabels: !state.general.displayStateLabels
425
- }
426
- })
427
- break;
428
- case 'capitalizeLabels':
429
- setState({
430
- ...state,
431
- tooltips: {
432
- ...state.tooltips,
433
- capitalizeLabels: value
434
- }
435
- })
436
- break;
437
- case 'showDataTable':
438
- setState({
439
- ...state,
440
- dataTable: {
441
- ...state.dataTable,
442
- forceDisplay: value
443
- }
444
- })
445
- break;
446
- default:
447
- console.warn(`Did not recognize editor property.`)
448
- break;
449
- }
450
- }
451
-
452
- const columnsRequiredChecker = useCallback(() => {
453
- let columnList = []
454
-
455
- // Geo is always required
456
- if('' === state.columns.geo.name) {
457
- columnList.push('Geography')
458
- }
459
-
460
- // Primary is required if we're on a data map or a point map
461
- if('navigation' !== state.general.type && '' === state.columns.primary.name) {
462
- columnList.push('Primary')
463
- }
464
-
465
- // Navigate is required for navigation maps
466
- if('navigation' === state.general.type && ('' === state.columns.navigate.name || undefined === state.columns.navigate) ) {
467
- columnList.push('Navigation')
468
- }
469
-
470
- if(columnList.length === 0) columnList = null
471
-
472
- setRequiredColumns(columnList)
473
- }, [state.columns, state.general.type])
474
-
475
- const editColumn = async (columnName, editTarget, value) => {
476
- switch (editTarget) {
477
- case 'specialClassDelete':
478
- const updatedSpecialClasses = Array.from(legend.specialClasses)
479
-
480
- updatedSpecialClasses.splice(value, 1)
481
-
482
- setState({
483
- ...state,
484
- legend: {
485
- ...state.legend,
486
- specialClasses: updatedSpecialClasses
487
- }
488
- })
489
- break;
490
- case 'specialClassAdd':
491
- let newSpecialClasses = legend.specialClasses
492
-
493
- newSpecialClasses.push(value.name)
494
-
495
- setState({
496
- ...state,
497
- legend: {
498
- ...state.legend,
499
- specialClasses: newSpecialClasses
500
- }
501
- })
502
- break;
503
- case 'name':
504
- setState({
505
- ...state,
506
- columns: {
507
- ...state.columns,
508
- [columnName]: {
509
- ...state.columns[columnName],
510
- [editTarget]: value
511
- }
512
- }
513
- })
514
-
515
- break;
516
- default:
517
- setState({
518
- ...state,
519
- columns: {
520
- ...state.columns,
521
- [columnName]: {
522
- ...state.columns[columnName],
523
- [editTarget]: value
524
- }
525
- }
526
- })
527
- break;
528
- }
529
- }
530
-
531
- const changeFilter = async (idx, target, value) => {
532
- let newFilters = [...state.filters]
533
-
534
- switch (target) {
535
- case 'addNew':
536
- newFilters.push({
537
- label: '',
538
- values:[]
539
- })
540
- break;
541
- case 'remove':
542
- newFilters.splice(idx, 1)
543
- break;
544
- case 'columnName':
545
- newFilters[idx] = {...newFilters[idx]}
546
- newFilters[idx].columnName = value
547
- break;
548
- default:
549
- newFilters[idx][target] = value
550
- break;
551
- }
552
-
553
- setState({
554
- ...state,
555
- filters: newFilters
556
- })
557
- }
558
-
559
- const addAdditionalColumn = (number) => {
560
- const columnKey = `additionalColumn${number}`
561
-
562
- setState({
563
- ...state,
564
- columns: {
565
- ...state.columns,
566
- [columnKey]: {
567
- label: "New Column",
568
- dataTable: false,
569
- tooltips: false,
570
- prefix: "",
571
- suffix: ""
572
- }
573
- }
574
- })
575
- }
576
-
577
- const removeAdditionalColumn = (columnName) => {
578
- const newColumns = state.columns
579
-
580
- delete newColumns[columnName]
581
-
582
- setState({
583
- ...state,
584
- columns: newColumns
585
- })
586
- }
587
-
588
- const displayFilterLegendValue = (arr) => {
589
- const filterName = state.filters[ arr[0] ].label || `Unlabeled Legend`
590
-
591
- const filterValue = runtimeFilters[ arr[0] ]
592
-
593
- if(filterValue) {
594
- return filterName + ' - ' + filterValue.values[ arr[1] ]
595
- }
596
- }
597
-
598
- const sortableItemStyles = {
599
- display:"block",
600
- boxSizing:"border-box",
601
- border:"1px solid #D1D1D1",
602
- borderRadius:"2px",
603
- background:"#F1F1F1",
604
- padding:".4em .6em",
605
- fontSize:".8em",
606
- marginRight:".3em",
607
- marginBottom:".3em",
608
- cursor:"move",
609
- zIndex:"999"
610
- }
611
-
612
- const convertStateToConfig = () => {
613
- let strippedState = JSON.parse(JSON.stringify(state)) // Deep copy
614
-
615
- // Strip ref
616
- delete strippedState[""]
617
-
618
- delete strippedState.newViz
619
-
620
- // Remove the legend
621
- let strippedLegend = JSON.parse(JSON.stringify(state.legend))
622
-
623
- delete strippedLegend.disabledAmt
624
-
625
- strippedState.legend = strippedLegend
626
-
627
- // Remove default data marker if the user started this map from default data
628
- delete strippedState.defaultData
629
-
630
- // Remove tooltips if they're active in the editor
631
- let strippedGeneral = JSON.parse(JSON.stringify(state.general))
632
-
633
- strippedState.general = strippedGeneral
634
-
635
- // Add columns property back to data if it's there
636
- if(state.data.columns) {
637
- strippedState.data.columns = state.data.columns
638
- }
639
-
640
- return strippedState
641
- }
642
-
643
- useEffect(() => {
644
- setLoadedDefault(state.defaultData)
645
-
646
- columnsRequiredChecker()
647
- }, [state])
648
-
649
- useEffect(() => {
650
- if('category' === state.legend.type && editorCatOrder.length === 0) {
651
- let arr = runtimeLegend.filter(item => !item.special).map(({value}) => value)
652
-
653
- setEditorCatOrder(arr)
654
- }
655
- }, [runtimeLegend])
656
-
657
- const columnsOptions = [<option value="" key={"Select Option"}>- Select Option -</option>]
658
-
659
- columnsInData.map(colName => {
660
- columnsOptions.push(<option value={colName} key={colName}>{colName}</option>)
661
- })
662
-
663
- const specialClasses = []
664
-
665
- if("" !== legend.specialClasses[0]) {
666
- legend.specialClasses.forEach( (specialClass, index) => {
667
- specialClasses.push({id: index, name: specialClass})
668
- })
669
- }
670
-
671
- const additionalColumns = Object.keys(state.columns).filter( (value) => {
672
- const defaultCols = [
673
- 'geo',
674
- 'navigate',
675
- 'primary'
676
- ]
677
-
678
- if( true === defaultCols.includes(value) ) {
679
- return false
680
- }
681
- return true
682
- })
683
-
684
- const updateField = (section, subsection, fieldName, newValue) => {
685
- const isArray = Array.isArray(state[section]);
686
- let sectionValue = isArray ? [...state[section], newValue] : {...state[section], [fieldName]: newValue};
687
-
688
- if(null !== subsection) {
689
- if(isArray) {
690
- sectionValue = [...state[section]]
691
- sectionValue[subsection] = {...sectionValue[subsection], [fieldName]: newValue}
692
- } else {
693
- sectionValue = {...state[section], [subsection]: { ...state[section][subsection], [fieldName]: newValue}}
694
- }
695
- }
696
-
697
- let updatedState = {
698
- ...state,
699
- [section]: sectionValue
700
- }
701
-
702
- setState(updatedState)
703
- }
704
-
705
- const onBackClick = () => {
706
- setDisplayPanel(!displayPanel);
707
- }
708
-
709
- const usedFilterColumns = {}
710
-
711
- const filtersJSX = state.filters.map( (filter, index) => {
712
- if(filter.columnName) {
713
- usedFilterColumns[filter.columnName] = true
714
- }
715
-
716
- return (
717
- <fieldset className="edit-block" key={`filter-${index}`}>
718
- <button className="remove-column" onClick={() => { changeFilter(index, "remove")}}>Remove</button>
719
- <TextField value={state.filters[index].label} section="filters" subsection={index} fieldName="label" label="Label" updateField={updateField} />
720
- <label>
721
- <span className="edit-label column-heading">Filter Column <Helper text="Selecting a column will add a dropdown menu below the map legend and allow users to filter based on the values in this column." /></span>
722
- <select value={filter.columnName} onChange={(event) => { changeFilter(index, "columnName", event.target.value) }}>
723
- {columnsOptions.filter(({key}) => undefined === usedFilterColumns[key] || filter.columnName === key)}
724
- </select>
725
- </label>
726
- </fieldset>
727
- )
728
- })
729
-
730
- const filterValueOptionList = []
731
-
732
- if(runtimeFilters.length > 0) {
733
- runtimeFilters.forEach( (filter, index) => {
734
- runtimeFilters[index].values.forEach( (value, valueNum) => {
735
- filterValueOptionList.push([index, valueNum])
736
- })
737
- })
738
- }
739
-
740
- useEffect(() => {
741
- const parsedData = convertStateToConfig()
742
-
743
- const formattedData = JSON.stringify(parsedData, undefined, 2);
744
-
745
- setConfigTextbox(formattedData)
746
-
747
- // Pass up to Editor if needed
748
- if(setParentConfig) {
749
- const newConfig = convertStateToConfig()
750
- setParentConfig(newConfig)
751
- }
752
-
753
- // eslint-disable-next-line react-hooks/exhaustive-deps
754
- }, [state])
755
-
756
- let numberOfItemsLimit = 8
757
-
758
- const getItemStyle = (isDragging, draggableStyle) => ({
759
- ...draggableStyle
760
- });
761
-
762
- const CategoryList = () => {
763
- return editorCatOrder.map((value, index) => (
764
- <Draggable key={value} draggableId={`item-${value}`} index={index}>
765
- {(provided, snapshot) => (
766
- <li
767
- style={{position: 'relative'}}
768
- >
769
- <div
770
- className={snapshot.isDragging ? 'currently-dragging': ''}
771
- style={getItemStyle(
772
- snapshot.isDragging,
773
- provided.draggableProps.style,
774
- sortableItemStyles
775
- )}
776
- ref={provided.innerRef}
777
- {...provided.draggableProps}
778
- {...provided.dragHandleProps}
779
- >{value}</div>
780
- </li>
781
- )}
782
- </Draggable>
783
- ))
784
- }
785
-
786
- return (
787
- <ErrorBoundary component="EditorPanel">
788
- {requiredColumns && <Waiting requiredColumns={requiredColumns} className={displayPanel ? `waiting` : `waiting collapsed`} />}
789
- <button className={displayPanel ? `editor-toggle` : `editor-toggle collapsed`} title={displayPanel ? `Collapse Editor` : `Expand Editor`} onClick={(onBackClick)} data-html2canvas-ignore></button>
790
- <section className={displayPanel ? 'editor-panel' : 'hidden editor-panel'} data-html2canvas-ignore>
791
- <ReactTooltip
792
- html={true}
793
- multiline={true}
794
- />
795
- <span className="base-label">Configure Map</span>
796
- <section className="form-container">
797
- <form>
798
- <Accordion allowZeroExpanded={true}>
799
- <AccordionItem> {/* Type */}
800
- <AccordionItemHeading>
801
- <AccordionItemButton>
802
- Type
803
- </AccordionItemButton>
804
- </AccordionItemHeading>
805
- <AccordionItemPanel>
806
- {/* Geography */}
807
- <label>
808
- <span className="edit-label column-heading"><span>Geography</span></span>
809
- <ul className="geo-buttons">
810
- <li className={state.general.geoType === 'us' ? 'active' : ''} onClick={() => handleEditorChanges("geoType", "us")}>
811
- <UsaGraphic />
812
- <span>United States</span>
813
- </li>
814
- <li className={state.general.geoType === 'world' ? 'active' : ''} onClick={() => handleEditorChanges("geoType", "world")}>
815
- <WorldGraphic />
816
- <span>World</span>
817
- </li>
818
- </ul>
819
- </label>
820
- <label>
821
- {/* Type */}
822
- <span className="edit-label column-heading">Map Type</span>
823
- <select value={state.general.type} onChange={(event) => { handleEditorChanges("editorMapType", event.target.value) }}>
824
- <option value="data">Data</option>
825
- <option value="navigation">Navigation</option>
826
- </select>
827
- </label>
828
- {/* SubType */}
829
- {'us' === state.general.geoType && 'data' === state.general.type &&
830
- <label className="checkbox mt-4">
831
- <input type="checkbox" checked={ state.general.displayAsHex } onChange={(event) => { handleEditorChanges("displayAsHex", event.target.checked) }} />
832
- <span className="edit-label">Display As Hex Map</span>
833
- </label>
834
- }
835
- {'us' === state.general.geoType && 'data' === state.general.type && false === state.general.displayAsHex &&
836
- <label className="checkbox">
837
- <input type="checkbox" checked={ state.general.displayStateLabels } onChange={(event) => { handleEditorChanges("displayStateLabels", event.target.checked) }} />
838
- <span className="edit-label">Display state labels</span>
839
- </label>
840
- }
841
- </AccordionItemPanel>
842
- </AccordionItem>
843
- <AccordionItem> {/* General */}
844
- <AccordionItemHeading>
845
- <AccordionItemButton>
846
- General
847
- </AccordionItemButton>
848
- </AccordionItemHeading>
849
- <AccordionItemPanel>
850
- <TextField value={state.general.title} updateField={updateField} section="general" fieldName="title" label="Title" placeholder="Map Title" helper="For accessibility reasons, you should enter a title even if you are not planning on displaying it." />
851
- <TextField type="textarea" value={general.subtext} updateField={updateField} section="general" fieldName="subtext" label="Subtext" />
852
- {'us' === state.general.geoType &&
853
- <TextField value={general.territoriesLabel} updateField={updateField} section="general" fieldName="territoriesLabel" label="Territories Label" placeholder="Territories" />
854
- }
855
- {/* <label className="checkbox mt-4">
104
+ const [advancedToggle, setAdvancedToggle] = useState(false);
105
+
106
+ const [activeFilterValueForDescription, setActiveFilterValueForDescription] = useState([0, 0]);
107
+
108
+ const {filteredPallets,filteredQualitative,isPaletteReversed,paletteName} = useColorPalette(colorPalettes,state);
109
+
110
+
111
+ const [editorCatOrder, setEditorCatOrder] = useState(state.legend.categoryValuesOrder || []);
112
+
113
+ const headerColors = [
114
+ 'theme-blue',
115
+ 'theme-purple',
116
+ 'theme-brown',
117
+ 'theme-teal',
118
+ 'theme-pink',
119
+ 'theme-orange',
120
+ 'theme-slate',
121
+ 'theme-indigo',
122
+ 'theme-cyan',
123
+ 'theme-green',
124
+ 'theme-amber',
125
+ ];
126
+
127
+ const categoryMove = (idx1, idx2) => {
128
+ let categoryValuesOrder = [...editorCatOrder];
129
+
130
+ let [movedItem] = categoryValuesOrder.splice(idx1, 1);
131
+
132
+ categoryValuesOrder.splice(idx2, 0, movedItem);
133
+
134
+ setEditorCatOrder(categoryValuesOrder);
135
+
136
+ setState({
137
+ ...state,
138
+ legend: {
139
+ ...state.legend,
140
+ categoryValuesOrder,
141
+ },
142
+ });
143
+ };
144
+
145
+
146
+ const handleFilterOrder = (idx1, idx2, filterIndex, filter) => {
147
+
148
+ let filterOrder = filter.values;
149
+ let [movedItem] = filterOrder.splice(idx1, 1);
150
+ filterOrder.splice(idx2, 0, movedItem);
151
+ let filters = [...runtimeFilters]
152
+ let filterItem= { ...runtimeFilters[filterIndex] };
153
+ filterItem.active = filter.values[0]
154
+ filterItem.values = filterOrder;
155
+ filterItem.order = 'cust'
156
+ filters[filterIndex] = filterItem
157
+
158
+ setState({
159
+ ...state,
160
+ filters
161
+ });
162
+
163
+ };
164
+
165
+
166
+ const DynamicDesc = ({ label, fieldName, value: stateValue, type = 'input', ...attributes }) => {
167
+ const [value, setValue] = useState(stateValue);
168
+
169
+ const [debouncedValue] = useDebounce(value, 500);
170
+
171
+ useEffect(() => {
172
+ if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
173
+ handleEditorChanges('changeLegendDescription', [
174
+ String(activeFilterValueForDescription),
175
+ debouncedValue,
176
+ ]);
177
+ }
178
+ }, [debouncedValue]);
179
+
180
+ const onChange = (e) => setValue(e.target.value);
181
+
182
+ return <textarea onChange={onChange} {...attributes} value={value}></textarea>;
183
+ };
184
+
185
+ const handleEditorChanges = async (property, value) => {
186
+ switch (property) {
187
+
188
+ // change these to be more generic.
189
+ // updateVisualPropertyValue
190
+ // updateGeneralPropertyValue, etc.
191
+ case 'showBubbleZeros':
192
+ setState({
193
+ ...state,
194
+ visual: {
195
+ ...state.visual,
196
+ showBubbleZeros: value
197
+ }
198
+ })
199
+ break;
200
+
201
+ case 'showEqualNumber':
202
+ setState({
203
+ ...state,
204
+ general: {
205
+ ...state.general,
206
+ equalNumberOptIn: value
207
+ }
208
+ })
209
+ break;
210
+
211
+ case 'hideGeoColumnInTooltip':
212
+ setState({
213
+ ...state,
214
+ general: {
215
+ ...state.general,
216
+ [property]: value
217
+ }
218
+ })
219
+ break;
220
+
221
+ case 'toggleExtraBubbleBorder':
222
+ setState({
223
+ ...state,
224
+ visual: {
225
+ ...state.visual,
226
+ extraBubbleBorder: value
227
+ }
228
+ })
229
+ break;
230
+ case 'allowMapZoom':
231
+ setState({
232
+ ...state,
233
+ general: {
234
+ ...state.general,
235
+ [property]: value
236
+ }
237
+ })
238
+ break;
239
+ case 'hidePrimaryColumnInTooltip':
240
+ setState({
241
+ ...state,
242
+ general: {
243
+ ...state.general,
244
+ [property]: value
245
+ }
246
+ })
247
+ break;
248
+ case 'showTitle':
249
+ setState({
250
+ ...state,
251
+ general: {
252
+ ...state.general,
253
+ showTitle: value,
254
+ },
255
+ });
256
+ break;
257
+ case 'showSidebar':
258
+ setState({
259
+ ...state,
260
+ general: {
261
+ ...state.general,
262
+ showSidebar: value,
263
+ },
264
+ });
265
+ break;
266
+ case 'fullBorder':
267
+ setState({
268
+ ...state,
269
+ general: {
270
+ ...state.general,
271
+ fullBorder: value,
272
+ },
273
+ });
274
+ break;
275
+ case 'expandDataTable':
276
+ setState({
277
+ ...state,
278
+ general: {
279
+ ...state.general,
280
+ expandDataTable: value,
281
+ },
282
+ });
283
+ break;
284
+ case 'color':
285
+ setState({
286
+ ...state,
287
+ color: value,
288
+ });
289
+ break;
290
+ case 'sidebarPosition':
291
+ setState({
292
+ ...state,
293
+ legend: {
294
+ ...state.legend,
295
+ position: value,
296
+ },
297
+ });
298
+ break;
299
+ case 'geoBorderColor':
300
+ setState({
301
+ ...state,
302
+ general: {
303
+ ...state.general,
304
+ geoBorderColor: value,
305
+ },
306
+ });
307
+ break;
308
+ case 'headerColor':
309
+ setState({
310
+ ...state,
311
+ general: {
312
+ ...state.general,
313
+ headerColor: value,
314
+ },
315
+ });
316
+ break;
317
+ case 'navigateColumn':
318
+ setState({
319
+ ...state,
320
+ columns: {
321
+ ...state.columns,
322
+ navigate: {
323
+ ...state.columns.navigate,
324
+ name: value,
325
+ },
326
+ },
327
+ });
328
+ break;
329
+ case 'legendDescription':
330
+ setState({
331
+ ...state,
332
+ legend: {
333
+ ...state.legend,
334
+ description: value,
335
+ },
336
+ });
337
+ break;
338
+ case 'legendType':
339
+
340
+ let testForType = typeof state.data[0][state.columns.primary.name];
341
+ let hasValue = state.data[0][state.columns.primary.name];
342
+ let messages = [];
343
+
344
+ if(!hasValue) {
345
+ messages.push(`There appears to be values missing for data in the primary column ${state.columns.primary.name}`);
346
+ }
347
+
348
+ if (testForType === 'string' && value !== 'category') {
349
+ messages.push( 'Error with legend. Primary columns that are text must use a categorical legend type. Try changing the legend type to categorical.' );
350
+ } else {
351
+ messages = []
352
+ }
353
+
354
+ setState({
355
+ ...state,
356
+ legend: {
357
+ ...state.legend,
358
+ type: value,
359
+ },
360
+ runtime: {
361
+ ...state.runtime,
362
+ editorErrorMessage: messages
363
+ }
364
+ });
365
+ break;
366
+ case 'legendNumber':
367
+ setState({
368
+ ...state,
369
+ legend: {
370
+ ...state.legend,
371
+ numberOfItems: parseInt(value),
372
+ },
373
+ });
374
+ break;
375
+ case 'changeActiveFilterValue':
376
+ const arrVal = value.split(',');
377
+
378
+ setActiveFilterValueForDescription(arrVal);
379
+ break;
380
+ case 'unifiedLegend':
381
+ setState({
382
+ ...state,
383
+ legend: {
384
+ ...state.legend,
385
+ unified: value,
386
+ },
387
+ });
388
+ break;
389
+ case 'separateZero':
390
+ setState({
391
+ ...state,
392
+ legend: {
393
+ ...state.legend,
394
+ separateZero: value,
395
+ },
396
+ });
397
+ break;
398
+ case 'toggleDownloadButton':
399
+ setState({
400
+ ...state,
401
+ general: {
402
+ ...state.general,
403
+ showDownloadButton: !state.general.showDownloadButton,
404
+ },
405
+ });
406
+ break;
407
+ case 'toggleDownloadMediaButton':
408
+ setState({
409
+ ...state,
410
+ general: {
411
+ ...state.general,
412
+ showDownloadMediaButton: !state.general.showDownloadMediaButton,
413
+ },
414
+ });
415
+ break;
416
+ case 'displayAsHex':
417
+ setState({
418
+ ...state,
419
+ general: {
420
+ ...state.general,
421
+ displayAsHex: value,
422
+ },
423
+ });
424
+ break;
425
+ case 'editorMapType':
426
+ switch (value) {
427
+ case 'data':
428
+ setState({
429
+ ...state,
430
+ general: {
431
+ ...state.general,
432
+ showSidebar: true,
433
+ type: 'data',
434
+ },
435
+ });
436
+ break;
437
+ case 'navigation':
438
+ setState({
439
+ ...state,
440
+ general: {
441
+ ...state.general,
442
+ showSidebar: false,
443
+ type: 'navigation',
444
+ },
445
+ tooltips: {
446
+ ...state.tooltips,
447
+ appearanceType: 'hover',
448
+ },
449
+ });
450
+ break;
451
+ case 'bubble':
452
+ setState({
453
+ ...state,
454
+ general: {
455
+ ...state.general,
456
+ showSidebar: false,
457
+ type: 'bubble',
458
+ },
459
+ tooltips: {
460
+ ...state.tooltips,
461
+ appearanceType: 'hover',
462
+ },
463
+ });
464
+ break;
465
+ default:
466
+ console.warn('Map type not set');
467
+ break;
468
+ }
469
+ break;
470
+ case 'geoType':
471
+ // If we're still working with default data, switch to the world default to show it as an example
472
+ if (true === loadedDefault && 'world' === value) {
473
+ loadConfig(worldDefaultConfig);
474
+ ReactTooltip.rebuild();
475
+ break;
476
+ }
477
+
478
+ if (true === loadedDefault && 'us' === value) {
479
+ loadConfig(usaDefaultConfig);
480
+ ReactTooltip.rebuild();
481
+ break;
482
+ }
483
+
484
+ if (true === loadedDefault && 'us-county' === value) {
485
+ loadConfig(countyDefaultConfig);
486
+ ReactTooltip.rebuild();
487
+ break;
488
+ }
489
+
490
+ switch (value) {
491
+ case 'us':
492
+ setState({
493
+ ...state,
494
+ general: {
495
+ ...state.general,
496
+ geoType: 'us',
497
+ },
498
+ dataTable: {
499
+ ...state.dataTable,
500
+ forceDisplay: true,
501
+ },
502
+ });
503
+ ReactTooltip.rebuild();
504
+ break;
505
+ case 'world':
506
+ setState({
507
+ ...state,
508
+ general: {
509
+ ...state.general,
510
+ geoType: 'world',
511
+ },
512
+ dataTable: {
513
+ ...state.dataTable,
514
+ forceDisplay: true,
515
+ },
516
+ });
517
+ break;
518
+ case 'us-county':
519
+ setState({
520
+ ...state,
521
+ general: {
522
+ ...state.general,
523
+ geoType: 'us-county',
524
+ expandDataTable: false,
525
+ },
526
+ dataTable: {
527
+ ...state.dataTable,
528
+ forceDisplay: true,
529
+ },
530
+ });
531
+ break;
532
+ case 'single-state':
533
+ setState({
534
+ ...state,
535
+ general: {
536
+ ...state.general,
537
+ geoType: 'single-state',
538
+ expandDataTable: false,
539
+ },
540
+ dataTable: {
541
+ ...state.dataTable,
542
+ forceDisplay: true,
543
+ },
544
+ });
545
+ break;
546
+ default:
547
+ break;
548
+ }
549
+
550
+ ReactTooltip.rebuild();
551
+ break;
552
+ case 'singleColumnLegend':
553
+ setState({
554
+ ...state,
555
+ legend: {
556
+ ...state.legend,
557
+ singleColumn: !state.legend.singleColumn,
558
+ },
559
+ });
560
+ break;
561
+ case 'dynamicDescription':
562
+ setState({
563
+ ...state,
564
+ editor: {
565
+ ...state.editor,
566
+ activeFilterValueForDescription: value,
567
+ },
568
+ legend: {
569
+ ...state.legend,
570
+ dynamicDescription: !state.legend.dynamicDescription,
571
+ },
572
+ });
573
+ break;
574
+ case 'changeLegendDescription':
575
+ const [filterValKey, filterValDesc] = value;
576
+ setState({
577
+ ...state,
578
+ legend: {
579
+ ...state.legend,
580
+ descriptions: {
581
+ ...state.legend.descriptions,
582
+ [filterValKey]: [filterValDesc],
583
+ },
584
+ },
585
+ });
586
+ break;
587
+ case 'appearanceType':
588
+ setState({
589
+ ...state,
590
+ tooltips: {
591
+ ...state.tooltips,
592
+ appearanceType: value,
593
+ },
594
+ });
595
+ break;
596
+ case 'linkLabel':
597
+ setState({
598
+ ...state,
599
+ tooltips: {
600
+ ...state.tooltips,
601
+ linkLabel: value,
602
+ },
603
+ });
604
+ break;
605
+ case 'displayStateLabels':
606
+ setState({
607
+ ...state,
608
+ general: {
609
+ ...state.general,
610
+ displayStateLabels: !state.general.displayStateLabels,
611
+ },
612
+ });
613
+ break;
614
+ case 'capitalizeLabels':
615
+ setState({
616
+ ...state,
617
+ tooltips: {
618
+ ...state.tooltips,
619
+ capitalizeLabels: value,
620
+ },
621
+ });
622
+ break;
623
+ case 'showDataTable':
624
+ setState({
625
+ ...state,
626
+ dataTable: {
627
+ ...state.dataTable,
628
+ forceDisplay: value,
629
+ },
630
+ });
631
+ break;
632
+ case 'limitDataTableHeight':
633
+ setState({
634
+ ...state,
635
+ dataTable: {
636
+ ...state.dataTable,
637
+ limitHeight: value
638
+ }
639
+ });
640
+ break;
641
+ case 'chooseState':
642
+ let fipsCode = Object.keys(supportedStatesFipsCodes).find(
643
+ (key) => supportedStatesFipsCodes[key] === value
644
+ );
645
+ let stateName = value;
646
+ let stateData = { fipsCode, stateName };
647
+
648
+ setState({
649
+ ...state,
650
+ general: {
651
+ ...state.general,
652
+ statePicked: stateData,
653
+ },
654
+ });
655
+ break;
656
+ case "classificationType":
657
+ setState({
658
+ ...state,
659
+ legend: {
660
+ ...state.legend,
661
+ type: value,
662
+ },
663
+ });
664
+ break;
665
+ default:
666
+ console.warn(`Did not recognize editor property.`);
667
+ break;
668
+ }
669
+ };
670
+
671
+ const columnsRequiredChecker = useCallback(() => {
672
+ let columnList = [];
673
+
674
+ // Geo is always required
675
+ if ('' === state.columns.geo.name) {
676
+ columnList.push('Geography');
677
+ }
678
+
679
+ // Primary is required if we're on a data map or a point map
680
+ if ('navigation' !== state.general.type && '' === state.columns.primary.name) {
681
+ columnList.push('Primary');
682
+ }
683
+
684
+ // Navigate is required for navigation maps
685
+ if (
686
+ 'navigation' === state.general.type &&
687
+ ('' === state.columns.navigate.name || undefined === state.columns.navigate)
688
+ ) {
689
+ columnList.push('Navigation');
690
+ }
691
+
692
+ if (columnList.length === 0) columnList = null;
693
+
694
+ setRequiredColumns(columnList);
695
+ }, [state.columns, state.general.type]);
696
+
697
+ const editColumn = async (columnName, editTarget, value) => {
698
+ let newSpecialClasses;
699
+
700
+ switch (editTarget) {
701
+ case 'specialClassEdit':
702
+ newSpecialClasses = Array.from(legend.specialClasses);
703
+
704
+ newSpecialClasses[value.index][value.prop] = value.value;
705
+
706
+ setState({
707
+ ...state,
708
+ legend: {
709
+ ...state.legend,
710
+ specialClasses: newSpecialClasses,
711
+ },
712
+ });
713
+ break;
714
+ case 'specialClassDelete':
715
+ newSpecialClasses = Array.from(legend.specialClasses);
716
+
717
+ newSpecialClasses.splice(value, 1);
718
+
719
+ setState({
720
+ ...state,
721
+ legend: {
722
+ ...state.legend,
723
+ specialClasses: newSpecialClasses,
724
+ },
725
+ });
726
+ break;
727
+ case 'specialClassAdd':
728
+ newSpecialClasses = legend.specialClasses;
729
+
730
+ newSpecialClasses.push(value);
731
+
732
+ setState({
733
+ ...state,
734
+ legend: {
735
+ ...state.legend,
736
+ specialClasses: newSpecialClasses,
737
+ },
738
+ });
739
+ break;
740
+ case 'name':
741
+ setState({
742
+ ...state,
743
+ columns: {
744
+ ...state.columns,
745
+ [columnName]: {
746
+ ...state.columns[columnName],
747
+ [editTarget]: value,
748
+ },
749
+ },
750
+ });
751
+
752
+ break;
753
+ default:
754
+ setState({
755
+ ...state,
756
+ columns: {
757
+ ...state.columns,
758
+ [columnName]: {
759
+ ...state.columns[columnName],
760
+ [editTarget]: value,
761
+ },
762
+ },
763
+ });
764
+ break;
765
+ }
766
+ };
767
+
768
+ const changeFilter = async (idx, target, value) => {
769
+
770
+ let newFilters = [...state.filters];
771
+
772
+ switch (target) {
773
+ case 'addNew':
774
+ newFilters.push({
775
+ label: '',
776
+ values: [],
777
+ });
778
+ break;
779
+ case 'remove':
780
+
781
+ if(newFilters.length === 1) {
782
+ newFilters = []
783
+ } else {
784
+ newFilters.splice(idx, 1);
785
+ }
786
+ break;
787
+ case 'columnName':
788
+ newFilters[idx] = { ...newFilters[idx] };
789
+ newFilters[idx].columnName = value;
790
+ newFilters[idx].values = [] // when a column name changes knock the previous values out
791
+ break;
792
+ case 'filterOrder':
793
+ if(value === 'desc') {
794
+ newFilters[idx] = { ...runtimeFilters[idx]}
795
+ delete newFilters[idx].active;
796
+ newFilters[idx].order = 'desc';
797
+ }
798
+ if(value === 'asc') {
799
+ newFilters[idx] = { ...runtimeFilters[idx] }
800
+ delete newFilters[idx].active;
801
+ newFilters[idx].order = 'asc'
802
+ }
803
+ if(value === 'cust') {
804
+ newFilters[idx] = { ...runtimeFilters[idx] }
805
+ newFilters[idx].order = 'cust'
806
+ }
807
+ break;
808
+ default:
809
+ newFilters[idx][target] = value;
810
+ break;
811
+ }
812
+
813
+ setState({
814
+ ...state,
815
+ filters: newFilters,
816
+ });
817
+
818
+ };
819
+
820
+ const addAdditionalColumn = (number) => {
821
+ const columnKey = `additionalColumn${number}`;
822
+
823
+ setState({
824
+ ...state,
825
+ columns: {
826
+ ...state.columns,
827
+ [columnKey]: {
828
+ label: 'New Column',
829
+ dataTable: false,
830
+ tooltips: false,
831
+ prefix: '',
832
+ suffix: '',
833
+ },
834
+ },
835
+ });
836
+ };
837
+
838
+ const removeAdditionalColumn = (columnName) => {
839
+ const newColumns = state.columns;
840
+
841
+ delete newColumns[columnName];
842
+
843
+ setState({
844
+ ...state,
845
+ columns: newColumns,
846
+ });
847
+ };
848
+
849
+ const displayFilterLegendValue = (arr) => {
850
+ const filterName = state.filters[arr[0]].label || `Unlabeled Legend`;
851
+
852
+ const filterValue = runtimeFilters[arr[0]];
853
+
854
+ if (filterValue) {
855
+ return filterName + ' - ' + filterValue.values[arr[1]];
856
+ }
857
+ };
858
+
859
+ const sortableItemStyles = {
860
+ display: 'block',
861
+ boxSizing: 'border-box',
862
+ border: '1px solid #D1D1D1',
863
+ borderRadius: '2px',
864
+ background: '#F1F1F1',
865
+ padding: '.4em .6em',
866
+ fontSize: '.8em',
867
+ marginRight: '.3em',
868
+ marginBottom: '.3em',
869
+ cursor: 'move',
870
+ zIndex: '999',
871
+ };
872
+
873
+ const convertStateToConfig = () => {
874
+ let strippedState = JSON.parse(JSON.stringify(state)); // Deep copy
875
+
876
+ // Strip ref
877
+ delete strippedState[''];
878
+
879
+ delete strippedState.newViz;
880
+
881
+ // Remove the legend
882
+ let strippedLegend = JSON.parse(JSON.stringify(state.legend));
883
+
884
+ delete strippedLegend.disabledAmt;
885
+
886
+ strippedState.legend = strippedLegend;
887
+
888
+ // Remove default data marker if the user started this map from default data
889
+ delete strippedState.defaultData;
890
+
891
+ // Remove tooltips if they're active in the editor
892
+ let strippedGeneral = JSON.parse(JSON.stringify(state.general));
893
+
894
+ strippedState.general = strippedGeneral;
895
+
896
+ // Add columns property back to data if it's there
897
+ if (state.data.columns) {
898
+ strippedState.data.columns = state.data.columns;
899
+ }
900
+
901
+ return strippedState;
902
+ };
903
+
904
+ useEffect(() => {
905
+ setLoadedDefault(state.defaultData);
906
+
907
+ columnsRequiredChecker();
908
+ }, [state]);
909
+
910
+ useEffect(() => {
911
+ if ('category' === state.legend.type) {
912
+ let arr = runtimeLegend.filter((item) => !item.special).map(({ value }) => value);
913
+
914
+ setEditorCatOrder(arr);
915
+ }
916
+ }, [runtimeLegend]);
917
+
918
+
919
+ // if no state choice by default show alabama
920
+ useEffect(() => {
921
+ if (!state.general.statePicked) {
922
+ setState({
923
+ ...state,
924
+ general: {
925
+ ...general,
926
+ statePicked: {
927
+ fipsCode: '01',
928
+ stateName: 'Alabama',
929
+ },
930
+ },
931
+ });
932
+ }
933
+ }, []);
934
+
935
+ const columnsOptions = [
936
+ <option value='' key={'Select Option'}>
937
+ - Select Option -
938
+ </option>,
939
+ ];
940
+
941
+ columnsInData.map((colName) => {
942
+ columnsOptions.push(
943
+ <option value={colName} key={colName}>
944
+ {colName}
945
+ </option>
946
+ );
947
+ });
948
+
949
+ let columnsByKey = {};
950
+ state.data.forEach(datum => {
951
+ Object.keys(datum).forEach(key => {
952
+ columnsByKey[key] = columnsByKey[key] || [];
953
+ const value = typeof datum[key] === 'number' ? datum[key].toString() : datum[key];
954
+
955
+ if(columnsByKey[key].indexOf(value) === -1){
956
+ columnsByKey[key].push(value);
957
+ }
958
+ });
959
+ });
960
+
961
+ let specialClasses = [];
962
+ if(legend.specialClasses && legend.specialClasses.length && typeof legend.specialClasses[0] === 'string'){
963
+ legend.specialClasses.forEach(specialClass => {
964
+ specialClasses.push({
965
+ key: state.columns.primary && state.columns.primary.name ? state.columns.primary.name : columnsInData[0],
966
+ value: specialClass,
967
+ label: specialClass
968
+ });
969
+ });
970
+ } else {
971
+ specialClasses = legend.specialClasses || [];
972
+ }
973
+
974
+ const additionalColumns = Object.keys(state.columns).filter((value) => {
975
+ const defaultCols = ['geo', 'navigate', 'primary'];
976
+
977
+ if (true === defaultCols.includes(value)) {
978
+ return false;
979
+ }
980
+ return true;
981
+ });
982
+
983
+ const updateField = (section, subsection, fieldName, newValue) => {
984
+ const isArray = Array.isArray(state[section]);
985
+ let sectionValue = isArray ? [...state[section], newValue] : { ...state[section], [fieldName]: newValue };
986
+
987
+ if (null !== subsection) {
988
+ if (isArray) {
989
+ sectionValue = [...state[section]];
990
+ sectionValue[subsection] = { ...sectionValue[subsection], [fieldName]: newValue };
991
+ } else {
992
+ sectionValue = {
993
+ ...state[section],
994
+ [subsection]: { ...state[section][subsection], [fieldName]: newValue },
995
+ };
996
+ }
997
+ }
998
+
999
+ let updatedState = {
1000
+ ...state,
1001
+ [section]: sectionValue,
1002
+ };
1003
+
1004
+ setState(updatedState);
1005
+ };
1006
+
1007
+ const onBackClick = () => {
1008
+ setDisplayPanel(!displayPanel);
1009
+ };
1010
+
1011
+ const usedFilterColumns = {};
1012
+
1013
+ const filtersJSX = state.filters.map((filter, index) => {
1014
+ if (filter.columnName) {
1015
+ usedFilterColumns[filter.columnName] = true;
1016
+ }
1017
+
1018
+ const filterOptions = [
1019
+ {
1020
+ label: 'Ascending Alphanumeric',
1021
+ value: 'asc'
1022
+ },
1023
+ {
1024
+ label: 'Descending Alphanumeric',
1025
+ value: 'desc'
1026
+ },
1027
+ {
1028
+ label: 'Custom',
1029
+ value: 'cust'
1030
+ }
1031
+ ]
1032
+
1033
+ return (
1034
+ <fieldset className='edit-block' key={`filter-${index}`}>
1035
+ <button
1036
+ className='remove-column'
1037
+ onClick={(e) => {
1038
+ e.preventDefault();
1039
+ changeFilter(index, 'remove');
1040
+ }}
1041
+ >
1042
+ Remove
1043
+ </button>
1044
+ <TextField
1045
+ value={state.filters[index].label}
1046
+ section='filters'
1047
+ subsection={index}
1048
+ fieldName='label'
1049
+ label='Label'
1050
+ updateField={updateField}
1051
+ />
1052
+ <label>
1053
+ <span className='edit-label column-heading'>
1054
+ Filter Column{' '}
1055
+ <Tooltip style={{textTransform: 'none'}}>
1056
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
1057
+ <Tooltip.Content>
1058
+ <p>Selecting a column will add a dropdown menu below the map legend and allow users to filter based on the values in this column.</p>
1059
+ </Tooltip.Content>
1060
+ </Tooltip>
1061
+ </span>
1062
+ <select
1063
+ value={filter.columnName}
1064
+ onChange={(event) => {
1065
+ changeFilter(index, 'columnName', event.target.value);
1066
+ }}
1067
+ >
1068
+ {columnsOptions.filter(
1069
+ ({ key }) => undefined === usedFilterColumns[key] || filter.columnName === key
1070
+ )}
1071
+ </select>
1072
+ </label>
1073
+
1074
+ <label>
1075
+ <span className="edit-filterOrder column-heading">Filter Order</span>
1076
+ <select value={filter.order} onChange={ (e) => {
1077
+ changeFilter(index, 'filterOrder', e.target.value)
1078
+ }}>
1079
+ {filterOptions.map( (option, index) => {
1080
+ return <option value={option.value} key={`filter-${index}`}>{option.label}</option>
1081
+ })}
1082
+ </select>
1083
+ </label>
1084
+
1085
+ {filter.order === 'cust' &&
1086
+ <DragDropContext
1087
+ onDragEnd={({ source, destination }) =>
1088
+ handleFilterOrder(source.index, destination.index, index, runtimeFilters[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
+ {runtimeFilters[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
+
1122
+ </fieldset>
1123
+ );
1124
+ });
1125
+
1126
+ const StateOptionList = () => {
1127
+ const arrOfArrays = Object.entries(supportedStatesFipsCodes);
1128
+
1129
+ let sorted = arrOfArrays.sort((a, b) => {
1130
+ return a[0].localeCompare(b[0]);
1131
+ });
1132
+
1133
+ let options = [];
1134
+ sorted.forEach((state) => {
1135
+ options.push(
1136
+ <option key={state[0]} value={state[1]}>
1137
+ {state[1]}
1138
+ </option>
1139
+ );
1140
+ });
1141
+
1142
+ return options;
1143
+ };
1144
+
1145
+ const filterValueOptionList = [];
1146
+
1147
+ if (runtimeFilters.length > 0) {
1148
+ runtimeFilters.forEach((filter, index) => {
1149
+ runtimeFilters[index].values.forEach((value, valueNum) => {
1150
+ filterValueOptionList.push([index, valueNum]);
1151
+ });
1152
+ });
1153
+ }
1154
+
1155
+
1156
+ useEffect(()=>{
1157
+ if(paletteName) handleEditorChanges('color',paletteName)
1158
+ },[paletteName]) // dont add handleEditorChanges as a dependency even if it requires
1159
+
1160
+ useEffect(() => {
1161
+ const parsedData = convertStateToConfig();
1162
+ const formattedData = JSON.stringify(parsedData, undefined, 2);
1163
+
1164
+ setConfigTextbox(formattedData);
1165
+ }, [state]);
1166
+
1167
+ useEffect(() => {
1168
+ // Pass up to Editor if needed
1169
+ if (setParentConfig) {
1170
+ const newConfig = convertStateToConfig();
1171
+ setParentConfig(newConfig);
1172
+ }
1173
+
1174
+ }, [state]);
1175
+
1176
+
1177
+ let numberOfItemsLimit = 8;
1178
+
1179
+ const getItemStyle = (isDragging, draggableStyle) => ({
1180
+ ...draggableStyle,
1181
+ });
1182
+
1183
+ const CategoryList = () => {
1184
+ return editorCatOrder.map((value, index) => (
1185
+ <Draggable key={value} draggableId={`item-${value}`} index={index}>
1186
+ {(provided, snapshot) => (
1187
+ <li style={{ position: 'relative' }}>
1188
+ <div
1189
+ className={snapshot.isDragging ? 'currently-dragging' : ''}
1190
+ style={getItemStyle(snapshot.isDragging, provided.draggableProps.style, sortableItemStyles)}
1191
+ ref={provided.innerRef}
1192
+ {...provided.draggableProps}
1193
+ {...provided.dragHandleProps}
1194
+ >
1195
+ {value}
1196
+ </div>
1197
+ </li>
1198
+ )}
1199
+ </Draggable>
1200
+ ));
1201
+ };
1202
+
1203
+ const Error = () => {
1204
+ return (
1205
+ <section className="waiting">
1206
+ <section className="waiting-container">
1207
+ <h3>Error With Configuration</h3>
1208
+ <p>{state.runtime.editorErrorMessage}</p>
1209
+ </section>
1210
+ </section>
1211
+ );
1212
+ }
1213
+
1214
+ return (
1215
+ <ErrorBoundary component='EditorPanel'>
1216
+ {state?.runtime?.editorErrorMessage.length > 0 && <Error />}
1217
+ {requiredColumns && (
1218
+ <Waiting requiredColumns={requiredColumns} className={displayPanel ? `waiting` : `waiting collapsed`} />
1219
+ )}
1220
+ <button
1221
+ className={displayPanel ? `editor-toggle` : `editor-toggle collapsed`}
1222
+ title={displayPanel ? `Collapse Editor` : `Expand Editor`}
1223
+ onClick={onBackClick}
1224
+ data-html2canvas-ignore
1225
+ ></button>
1226
+
1227
+ <section className={displayPanel ? 'editor-panel cove' : 'hidden editor-panel cove'} data-html2canvas-ignore>
1228
+ <ReactTooltip html={true} multiline={true} />
1229
+ <span className='base-label'>Configure Map</span>
1230
+ <section className='form-container'>
1231
+ <form>
1232
+ <Accordion allowZeroExpanded={true}>
1233
+ <AccordionItem>
1234
+ {' '}
1235
+ {/* Type */}
1236
+ <AccordionItemHeading>
1237
+ <AccordionItemButton>Type</AccordionItemButton>
1238
+ </AccordionItemHeading>
1239
+ <AccordionItemPanel>
1240
+ {/* Geography */}
1241
+ <label>
1242
+ <span className='edit-label column-heading'>
1243
+ <span>Geography</span>
1244
+ </span>
1245
+ <ul className='geo-buttons'>
1246
+ <li
1247
+ className={
1248
+ state.general.geoType === 'us' ||
1249
+ state.general.geoType === 'us-county'
1250
+ ? 'active'
1251
+ : ''
1252
+ }
1253
+ onClick={() => handleEditorChanges('geoType', 'us')}
1254
+ >
1255
+ <UsaGraphic />
1256
+ <span>United States</span>
1257
+ </li>
1258
+ <li
1259
+ className={state.general.geoType === 'world' ? 'active' : ''}
1260
+ onClick={() => handleEditorChanges('geoType', 'world')}
1261
+ >
1262
+ <WorldGraphic />
1263
+ <span>World</span>
1264
+ </li>
1265
+ <li
1266
+ className={state.general.geoType === 'single-state' ? 'active' : ''}
1267
+ onClick={() => handleEditorChanges('geoType', 'single-state')}
1268
+ >
1269
+ <AlabamaGraphic />
1270
+ <span>U.S. State</span>
1271
+ </li>
1272
+ </ul>
1273
+ </label>
1274
+ {/* Select > State or County Map */}
1275
+ {(state.general.geoType === 'us' || state.general.geoType === 'us-county') && (
1276
+ <label>
1277
+ <span className='edit-label column-heading'>
1278
+ Map Type
1279
+ <Tooltip style={{textTransform: 'none'}}>
1280
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
1281
+ <Tooltip.Content>
1282
+ <p>Select "Data" to create a color-coded data map. To create a navigation-only map, select "Navigation."</p>
1283
+ </Tooltip.Content>
1284
+ </Tooltip>
1285
+ </span>
1286
+ <select
1287
+ value={state.general.geoType}
1288
+ onChange={(event) => {
1289
+ handleEditorChanges('geoType', event.target.value);
1290
+ }}
1291
+ >
1292
+ <option value='us'>US State-Level</option>
1293
+ <option value='us-county'>US County-Level</option>
1294
+ </select>
1295
+ </label>
1296
+ )}
1297
+ {/* Type */}
1298
+ {/* Select > Filter a state */}
1299
+ {state.general.geoType === 'single-state' && (
1300
+ <label>
1301
+ <span className='edit-label column-heading'>State Selector</span>
1302
+ <select
1303
+ value={
1304
+ state.general.hasOwnProperty('statePicked')
1305
+ ? state.general.statePicked.stateName
1306
+ : { fipsCode: '04', stateName: 'Alabama' }
1307
+ }
1308
+ onChange={(event) => {
1309
+ handleEditorChanges('chooseState', event.target.value);
1310
+ }}
1311
+ >
1312
+ <StateOptionList />
1313
+ </select>
1314
+ </label>
1315
+ )}
1316
+ {/* Type */}
1317
+ <label>
1318
+ <span className='edit-label column-heading'>Map Type</span>
1319
+ <select
1320
+ value={state.general.type}
1321
+ onChange={(event) => {
1322
+ handleEditorChanges('editorMapType', event.target.value);
1323
+ }}
1324
+ >
1325
+ <option value='data'>Data</option>
1326
+ <option value='navigation'>Navigation</option>
1327
+ { (state.general.geoType === 'world' || state.general.geoType === 'us') && <option value="bubble">Bubble</option>}
1328
+ </select>
1329
+ </label>
1330
+ {/* SubType */}
1331
+ {'us' === state.general.geoType && 'data' === state.general.type && (
1332
+ <label className='checkbox mt-4'>
1333
+ <input
1334
+ type='checkbox'
1335
+ checked={state.general.displayAsHex}
1336
+ onChange={(event) => {
1337
+ handleEditorChanges('displayAsHex', event.target.checked);
1338
+ }}
1339
+ />
1340
+ <span className='edit-label'>Display As Hex Map</span>
1341
+ </label>
1342
+ )}
1343
+ {'us' === state.general.geoType &&
1344
+ 'data' === state.general.type &&
1345
+ false === state.general.displayAsHex && (
1346
+ <label className='checkbox'>
1347
+ <input
1348
+ type='checkbox'
1349
+ checked={state.general.displayStateLabels}
1350
+ onChange={(event) => {
1351
+ handleEditorChanges('displayStateLabels', event.target.checked);
1352
+ }}
1353
+ />
1354
+ <span className='edit-label'>Display state labels</span>
1355
+ </label>
1356
+ )}
1357
+ </AccordionItemPanel>
1358
+ </AccordionItem>
1359
+ <AccordionItem>
1360
+ {' '}
1361
+ {/* General */}
1362
+ <AccordionItemHeading>
1363
+ <AccordionItemButton>General</AccordionItemButton>
1364
+ </AccordionItemHeading>
1365
+ <AccordionItemPanel>
1366
+ <TextField
1367
+ value={state.general.title}
1368
+ updateField={updateField}
1369
+ section='general'
1370
+ fieldName='title'
1371
+ label='Title'
1372
+ placeholder='Map Title'
1373
+ tooltip={
1374
+ <Tooltip style={{textTransform: 'none'}}>
1375
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
1376
+ <Tooltip.Content>
1377
+ <p>For accessibility reasons, you should enter a title even if you are not planning on displaying it.</p>
1378
+ </Tooltip.Content>
1379
+ </Tooltip>
1380
+ }
1381
+ />
1382
+ <TextField
1383
+ type='textarea'
1384
+ value={general.subtext}
1385
+ updateField={updateField}
1386
+ section='general'
1387
+ fieldName='subtext'
1388
+ label='Subtext'
1389
+ tooltip={
1390
+ <Tooltip style={{textTransform: 'none'}}>
1391
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
1392
+ <Tooltip.Content>
1393
+ <p>Enter supporting text to display below the data visualization, if applicable. The following HTML tags are supported: strong, em, sup, and sub.</p>
1394
+ </Tooltip.Content>
1395
+ </Tooltip>
1396
+ }
1397
+ />
1398
+ {'us' === state.general.geoType && (
1399
+ <TextField
1400
+ value={general.territoriesLabel}
1401
+ updateField={updateField}
1402
+ section='general'
1403
+ fieldName='territoriesLabel'
1404
+ label='Territories Label'
1405
+ placeholder='Territories'
1406
+ />
1407
+ )}
1408
+ <label>
1409
+ <span className="edit-label">Data Classification Type</span>
1410
+ <div>
1411
+ <label>
1412
+ <input
1413
+ type="radio"
1414
+ name="equalnumber"
1415
+ value="equalnumber"
1416
+ checked={state.legend.type === "equalnumber"}
1417
+ onChange={(e) =>
1418
+ handleEditorChanges(
1419
+ "classificationType",
1420
+ e.target.value
1421
+ )
1422
+ }
1423
+ />
1424
+ Numeric/Quantitative
1425
+ </label>
1426
+ <label>
1427
+ <input
1428
+ type="radio"
1429
+ name="category"
1430
+ value="category"
1431
+ checked={state.legend.type === "category"}
1432
+ onChange={(e) =>
1433
+ handleEditorChanges(
1434
+ "classificationType",
1435
+ e.target.value
1436
+ )
1437
+ }
1438
+ />
1439
+ Categorical
1440
+ </label>
1441
+ </div>
1442
+ </label>
1443
+ {/* <label className="checkbox mt-4">
856
1444
  <input type="checkbox" checked={ state.general.showDownloadMediaButton } onChange={(event) => { handleEditorChanges("toggleDownloadMediaButton", event.target.checked) }} />
857
1445
  <span className="edit-label">Enable Media Download</span>
858
1446
  </label> */}
859
- </AccordionItemPanel>
860
- </AccordionItem>
861
- <AccordionItem> {/* Columns */}
862
- <AccordionItemHeading>
863
- <AccordionItemButton>
864
- Columns
865
- </AccordionItemButton>
866
- </AccordionItemHeading>
867
- <AccordionItemPanel>
868
- <label className="edit-block geo">
869
- <span className="edit-label column-heading">Geography</span>
870
- <select value={state.columns.geo ? state.columns.geo.name : columnsOptions[0] } onChange={(event) => { editColumn("geo", "name", event.target.value) }}>
871
- {columnsOptions}
872
- </select>
873
- </label>
874
- {"navigation" !== state.general.type &&
875
- <fieldset className="primary-fieldset edit-block">
876
- <label>
877
- <span className="edit-label column-heading">Primary</span>
878
- <select value={state.columns.primary ? state.columns.primary.name : columnsOptions[0] } onChange={(event) => { editColumn("primary", "name", event.target.value) }}>
879
- {columnsOptions}
880
- </select>
881
- </label>
882
- <TextField value={columns.primary.label} section="columns" subsection="primary" fieldName="label" label="Label" updateField={updateField} />
883
- <ul className="column-edit">
884
- <li className="three-col">
885
- <TextField value={columns.primary.prefix} section="columns" subsection="primary" fieldName="prefix" label="Prefix" updateField={updateField} />
886
- <TextField value={columns.primary.suffix} section="columns" subsection="primary" fieldName="suffix" label="Suffix" updateField={updateField} />
887
- <TextField type="number" value={columns.primary.roundToPlace} section="columns" subsection="primary" fieldName="roundToPlace" label="Round" updateField={updateField} />
888
- </li>
889
- <li>
890
- <label className="checkbox">
891
- <input type="checkbox" checked={ state.columns.primary.useCommas } onChange={(event) => { editColumn("primary", "useCommas", event.target.checked) }} />
892
- <span className="edit-label">Add Commas to Numbers</span>
893
- </label>
894
- </li>
895
- <li>
896
- <label className="checkbox">
897
- <input type="checkbox" checked={ state.columns.primary.dataTable || false} onChange={(event) => { editColumn("primary", "dataTable", event.target.checked) }} />
898
- <span className="edit-label">Display in Data Table</span>
899
- </label>
900
- </li>
901
- <li>
902
- <label className="checkbox">
903
- <input type="checkbox" checked={ state.columns.primary.tooltip || false} onChange={(event) => { editColumn("primary", "tooltip", event.target.checked) }} />
904
- <span className="edit-label">Display in Tooltips</span>
905
- </label>
906
- </li>
907
- <li>
1447
+ </AccordionItemPanel>
1448
+ </AccordionItem>
1449
+ <AccordionItem>
1450
+ {' '}
1451
+ {/* Columns */}
1452
+ <AccordionItemHeading>
1453
+ <AccordionItemButton>Columns</AccordionItemButton>
1454
+ </AccordionItemHeading>
1455
+ <AccordionItemPanel>
1456
+ <label className='edit-block geo'>
1457
+ <span className='edit-label column-heading'>
1458
+ Geography
1459
+ <Tooltip style={{textTransform: 'none'}}>
1460
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
1461
+ <Tooltip.Content>
1462
+ <p>Select the source column containing the map location names or, for county-level maps, the FIPS codes.</p>
1463
+ </Tooltip.Content>
1464
+ </Tooltip>
1465
+ </span>
1466
+ <select
1467
+ value={state.columns.geo ? state.columns.geo.name : columnsOptions[0]}
1468
+ onChange={(event) => {
1469
+ editColumn('geo', 'name', event.target.value);
1470
+ }}
1471
+ >
1472
+ {columnsOptions}
1473
+ </select>
1474
+ </label>
1475
+
1476
+ {'navigation' !== state.general.type && (
1477
+ <fieldset className='primary-fieldset edit-block'>
908
1478
  <label>
909
- <span className="edit-label">Special Classes</span>
910
- </label>
911
- <ReactTags
912
- placeholder="Separate by comma"
913
- delimiters={[' ',',','Enter']}
914
- allowNew={true}
915
- minQueryLength={1}
916
- tags={specialClasses}
917
- onDelete={(event) => { editColumn("primary", "specialClassDelete", event) }}
918
- onAddition={(value) => { editColumn("primary", "specialClassAdd", value) }}
919
- />
920
- </li>
921
- </ul>
922
- </fieldset>}
923
- <label className="edit-block navigate column-heading">
924
- <span className="edit-label column-heading">Navigation</span>
925
- <select value={state.columns.navigate ? state.columns.navigate.name : columnsOptions[0] } onChange={(event) => { editColumn("navigate", "name", event.target.value) }}>
926
- {columnsOptions}
927
- </select>
928
- </label>
929
- {"navigation" !== state.general.type && additionalColumns.map((val) => (
930
- <fieldset className="edit-block" key={val}>
931
- <button className="remove-column" onClick={(event) => { event.preventDefault(); removeAdditionalColumn(val)}}>Remove</button>
932
- <label>
933
- <span className="edit-label column-heading">Column</span>
934
- <select value={state.columns[val] ? state.columns[val].name : columnsOptions[0] } onChange={(event) => { editColumn(val, "name", event.target.value) }}>
1479
+ <span className='edit-label column-heading'>
1480
+ Data Column
1481
+ <Tooltip style={{textTransform: 'none'}}>
1482
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
1483
+ <Tooltip.Content>
1484
+ <p>Select the source column containing the categorical or numeric values to be mapped.</p>
1485
+ </Tooltip.Content>
1486
+ </Tooltip>
1487
+ </span>
1488
+ <select
1489
+ value={
1490
+ state.columns.primary
1491
+ ? state.columns.primary.name
1492
+ : columnsOptions[0]
1493
+ }
1494
+ onChange={(event) => {
1495
+ editColumn('primary', 'name', event.target.value);
1496
+ }}
1497
+ >
935
1498
  {columnsOptions}
936
1499
  </select>
937
1500
  </label>
938
- <TextField value={columns[val].label} section="columns" subsection={val} fieldName="label" label="Label" updateField={updateField} />
939
- <ul className="column-edit">
940
- <li className="three-col">
941
- <TextField value={columns[val].prefix} section="columns" subsection={val} fieldName="prefix" label="Prefix" updateField={updateField} />
942
- <TextField value={columns[val].suffix} section="columns" subsection={val} fieldName="suffix" label="Suffix" updateField={updateField} />
943
- <TextField type="number" value={columns[val].roundToPlace} section="columns" subsection={val} fieldName="roundToPlace" label="Round" updateField={updateField} />
1501
+ <TextField
1502
+ value={columns.primary.label}
1503
+ section='columns'
1504
+ subsection='primary'
1505
+ fieldName='label'
1506
+ label='Label'
1507
+ updateField={updateField}
1508
+ tooltip={
1509
+ <Tooltip style={{textTransform: 'none'}}>
1510
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
1511
+ <Tooltip.Content>
1512
+ <p>Enter a data label for use in tooltips and the data table.</p>
1513
+ </Tooltip.Content>
1514
+ </Tooltip>
1515
+ }
1516
+ />
1517
+ <ul className='column-edit'>
1518
+ <li className='three-col'>
1519
+ <TextField
1520
+ value={columns.primary.prefix}
1521
+ section='columns'
1522
+ subsection='primary'
1523
+ fieldName='prefix'
1524
+ label='Prefix'
1525
+ updateField={updateField}
1526
+ />
1527
+ <TextField
1528
+ value={columns.primary.suffix}
1529
+ section='columns'
1530
+ subsection='primary'
1531
+ fieldName='suffix'
1532
+ label='Suffix'
1533
+ updateField={updateField}
1534
+ />
1535
+ <TextField
1536
+ type='number'
1537
+ value={columns.primary.roundToPlace}
1538
+ section='columns'
1539
+ subsection='primary'
1540
+ fieldName='roundToPlace'
1541
+ label='Round'
1542
+ updateField={updateField}
1543
+ />
944
1544
  </li>
945
1545
  <li>
946
- <label className="checkbox">
947
- <input type="checkbox" checked={ state.columns[val].useCommas } onChange={(event) => { editColumn(val, "useCommas", event.target.checked) }} />
948
- <span className="edit-label">Add Commas to Numbers</span>
1546
+ <label className='checkbox'>
1547
+ <input
1548
+ type='checkbox'
1549
+ checked={state.columns.primary.useCommas}
1550
+ onChange={(event) => {
1551
+ editColumn(
1552
+ 'primary',
1553
+ 'useCommas',
1554
+ event.target.checked
1555
+ );
1556
+ }}
1557
+ />
1558
+ <span className='edit-label'>Add Commas to Numbers</span>
949
1559
  </label>
950
1560
  </li>
951
1561
  <li>
952
- <label className="checkbox">
953
- <input type="checkbox" checked={ state.columns[val].dataTable } onChange={(event) => { editColumn(val, "dataTable", event.target.checked) }} />
954
- <span className="edit-label">Display in Data Table</span>
1562
+ <label className='checkbox'>
1563
+ <input
1564
+ type='checkbox'
1565
+ checked={state.columns.primary.dataTable || false}
1566
+ onChange={(event) => {
1567
+ editColumn(
1568
+ 'primary',
1569
+ 'dataTable',
1570
+ event.target.checked
1571
+ );
1572
+ }}
1573
+ />
1574
+ <span className='edit-label'>Display in Data Table</span>
955
1575
  </label>
956
1576
  </li>
957
1577
  <li>
958
- <label className="checkbox">
959
- <input type="checkbox" checked={ state.columns[val].tooltip } onChange={(event) => { editColumn(val, "tooltip", event.target.checked) }} />
960
- <span className="edit-label">Display in Tooltips</span>
1578
+ <label className='checkbox'>
1579
+ <input
1580
+ type='checkbox'
1581
+ checked={state.columns.primary.tooltip || false}
1582
+ onChange={(event) => {
1583
+ editColumn('primary', 'tooltip', event.target.checked);
1584
+ }}
1585
+ />
1586
+ <span className='edit-label'>Display in Tooltips</span>
961
1587
  </label>
962
1588
  </li>
963
1589
  </ul>
964
1590
  </fieldset>
965
- ))}
966
- {"navigation" !== state.general.type && <button className={"btn full-width"} onClick={(event) => {event.preventDefault(); addAdditionalColumn(additionalColumns.length + 1)}}>Add Column</button>}
967
- </AccordionItemPanel>
968
- </AccordionItem> {/* Columns */}
969
- {"navigation" !== state.general.type && <AccordionItem> {/* Legend */}
970
- <AccordionItemHeading>
971
- <AccordionItemButton>
972
- Legend
973
- </AccordionItemButton>
974
- </AccordionItemHeading>
975
- <AccordionItemPanel>
976
- <label>
977
- <span className="edit-label">Legend Type</span>
978
- <select value={legend.type} onChange={(event) => { handleEditorChanges("legendType", event.target.value) }}>
979
- <option value="equalnumber">Equal Number</option>
980
- <option value="equalinterval">Equal Interval</option>
981
- <option value="category">Categorical</option>
982
- </select>
983
- </label>
984
- {"category" !== legend.type && (
985
- <label className="checkbox">
986
- <input type="checkbox"
987
- checked={legend.separateZero || false}
988
- onChange={(event) => handleEditorChanges("separateZero", event.target.checked)}
989
- />
990
- <span className="edit-label">Separate Zero</span>
991
- </label>)}
992
- {"category" !== legend.type &&
993
- <label>
994
- <span className="edit-label">Number of Items</span>
995
- <select value={legend.numberOfItems} onChange={(event) => { handleEditorChanges("legendNumber", event.target.value) }}>
996
- {[...Array(numberOfItemsLimit).keys()].map( (num) => {
997
- return (<option value={num + 1} key={num + 1}>{num + 1}</option>)
998
- })}
999
- </select>
1000
- </label>
1001
- }
1002
- {"category" === legend.type &&
1003
- <React.Fragment>
1591
+ )}
1592
+
1593
+
1594
+
1595
+ { (state.general.type === 'bubble') && state.legend.type === 'category' && (
1596
+ <fieldset className='primary-fieldset edit-block'>
1597
+ <label>
1598
+ <span className='edit-label column-heading'>
1599
+ Category Column
1600
+ <Tooltip style={{textTransform: 'none'}}>
1601
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
1602
+ <Tooltip.Content>
1603
+ <p>Select the source column containing the categorical bubble values to be mapped.</p>
1604
+ </Tooltip.Content>
1605
+ </Tooltip>
1606
+ </span>
1607
+ <select
1608
+ value={
1609
+ state.columns.categorical
1610
+ ? state.columns.categorical.name
1611
+ : columnsOptions[0]
1612
+ }
1613
+ onChange={(event) => {
1614
+ editColumn('categorical', 'name', event.target.value);
1615
+ }}
1616
+ >
1617
+ {columnsOptions}
1618
+ </select>
1619
+ </label>
1620
+ </fieldset>
1621
+ )}
1622
+
1623
+ {'navigation' !== state.general.type && (
1624
+ <fieldset className="primary-fieldset edit-block">
1625
+ <label>
1626
+ <span className='edit-label'>
1627
+ Special Classes
1628
+ <Tooltip style={{textTransform: 'none'}}>
1629
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
1630
+ <Tooltip.Content>
1631
+ <p>For secondary values such as "NA", the system can automatically color-code them in shades of gray, one shade for each special class.</p>
1632
+ </Tooltip.Content>
1633
+ </Tooltip>
1634
+ </span>
1635
+ </label>
1636
+ {specialClasses.map((specialClass, i) => (
1637
+ <div className="edit-block" key={`special-class-${i}`}>
1638
+ <button className="remove-column"
1639
+ onClick={(e) => {
1640
+ e.preventDefault();
1641
+ editColumn('primary', 'specialClassDelete', i);
1642
+ }}
1643
+ >Remove</button>
1644
+ <p>Special Class {i + 1}</p>
1645
+ <label>
1646
+ <span className="edit-label column-heading">Data Key</span>
1647
+ <select value={specialClass.key} onChange={(e) => {
1648
+ editColumn('primary', 'specialClassEdit', {prop: 'key', index: i, value: e.target.value});
1649
+ }}>
1650
+ {columnsOptions}
1651
+ </select>
1652
+ </label>
1653
+ <label>
1654
+ <span className="edit-label column-heading">Value</span>
1655
+ <select value={specialClass.value} onChange={(e) => {
1656
+ editColumn('primary', 'specialClassEdit', {prop: 'value', index: i, value: e.target.value});
1657
+ }}>
1658
+ <option value="">- Select Value -</option>
1659
+ {columnsByKey[specialClass.key] && columnsByKey[specialClass.key].sort().map(option => (
1660
+ <option key={`special-class-value-option-${i}-${option}`}>{option}</option>
1661
+ ))}
1662
+ </select>
1663
+ </label>
1664
+ <label>
1665
+ <span className="edit-label column-heading">Label</span>
1666
+ <input type="text" value={specialClass.label} onChange={(e) => {
1667
+ editColumn('primary', 'specialClassEdit', {prop: 'label', index: i, value: e.target.value});
1668
+ }} />
1669
+ </label>
1670
+ </div>
1671
+ ))}
1672
+ <button className="btn full-width" onClick={(e) => {
1673
+ e.preventDefault();
1674
+ editColumn('primary', 'specialClassAdd', {});
1675
+ }}>
1676
+ Add Special Class
1677
+ </button>
1678
+ </fieldset>
1679
+ )}
1680
+
1681
+ <label className='edit-block navigate column-heading'>
1682
+ <span className='edit-label column-heading'>
1683
+ Navigation
1684
+ <Tooltip style={{textTransform: 'none'}}>
1685
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
1686
+ <Tooltip.Content>
1687
+ <p>To provide end users with navigation functionality, select the source column containing the navigation URLs.</p>
1688
+ </Tooltip.Content>
1689
+ </Tooltip>
1690
+ </span>
1691
+ <select
1692
+ value={
1693
+ state.columns.navigate ? state.columns.navigate.name : columnsOptions[0]
1694
+ }
1695
+ onChange={(event) => {
1696
+ editColumn('navigate', 'name', event.target.value);
1697
+ }}
1698
+ >
1699
+ {columnsOptions}
1700
+ </select>
1701
+ </label>
1702
+ {'navigation' !== state.general.type &&
1703
+ <fieldset className="primary-fieldset edit-block">
1004
1704
  <label>
1005
- <span className="edit-label">Category Order</span>
1705
+ <span className="edit-label">
1706
+ Additional Columns
1707
+ <Tooltip style={{textTransform: 'none'}}>
1708
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
1709
+ <Tooltip.Content>
1710
+ <p>You can specify additional columns to display in tooltips and / or the supporting data table.</p>
1711
+ </Tooltip.Content>
1712
+ </Tooltip>
1713
+ </span>
1006
1714
  </label>
1007
- {/* TODO: Swap out this drag and drop library back to something simpler. I had to remove the old one because it hadn't been updated and wouldn't work with Webpack 5. This is overkill for our needs. */}
1008
- <DragDropContext onDragEnd={({source, destination}) => categoryMove(source.index, destination.index)}>
1009
- <Droppable droppableId="category_order">
1010
- {(provided) => (
1011
- <ul
1012
- {...provided.droppableProps}
1013
- className="sort-list"
1014
- ref={provided.innerRef}
1715
+ {additionalColumns.map((val) => (
1716
+ <fieldset className="edit-block" key={val}>
1717
+ <button
1718
+ className="remove-column"
1719
+ onClick={(event) => {
1720
+ event.preventDefault()
1721
+ removeAdditionalColumn(val)
1722
+ }}
1723
+ >
1724
+ Remove
1725
+ </button>
1726
+ <label>
1727
+ <span className="edit-label column-heading">Column</span>
1728
+ <select
1729
+ value={
1730
+ state.columns[val]
1731
+ ? state.columns[val].name
1732
+ : columnsOptions[0]
1733
+ }
1734
+ onChange={(event) => {
1735
+ editColumn(val, 'name', event.target.value)
1736
+ }}
1015
1737
  >
1016
- <CategoryList />
1017
- {provided.placeholder}
1018
- </ul>
1019
- )}
1020
- </Droppable>
1021
- </DragDropContext>
1022
- {editorCatOrder.length >= 9 && <section className="error-box my-2"><div><strong className="pt-1">Warning</strong><p>The maximum number of categorical legend items is 9. If your data has more than 9 categories your map will not display properly.</p></div></section>}
1023
- </React.Fragment>
1024
- }
1025
- <TextField value={legend.title} updateField={updateField} section="legend" fieldName="title" label="Legend Title" placeholder="Legend Title" />
1026
- {false === legend.dynamicDescription && (
1027
- <TextField type="textarea" value={legend.description} updateField={updateField} section="legend" fieldName="description" label="Legend Description" />
1028
- )}
1029
- {true === legend.dynamicDescription && (
1030
- <React.Fragment>
1031
- <label>
1032
- <span>Legend Description</span>
1033
- <span className="subtext">For {displayFilterLegendValue( activeFilterValueForDescription )}</span>
1034
- <DynamicDesc value={legend.descriptions[String(activeFilterValueForDescription)]} />
1035
- </label>
1036
- <label>
1037
- <select value={String(activeFilterValueForDescription)} onChange={(event) => { handleEditorChanges("changeActiveFilterValue", event.target.value) }}>
1038
- {filterValueOptionList.map( (arr, i) => {
1039
- return (<option value={arr} key={i}>{displayFilterLegendValue(arr)}</option>)
1040
- })}
1041
- </select>
1042
- </label>
1043
- </React.Fragment>)}
1044
- {filtersJSX.length > 0 && (
1045
- <label className="checkbox">
1046
- <input type="checkbox" checked={ legend.dynamicDescription} onChange={() => { handleEditorChanges("dynamicDescription", filterValueOptionList[0]) }} />
1047
- <span className="edit-label">Dynamic Legend Description</span>
1048
- </label>)}
1049
- {filtersJSX.length > 0 &&
1050
- <label className="checkbox">
1051
- <input type="checkbox"
1052
- checked={legend.unified}
1053
- onChange={(event) => handleEditorChanges("unifiedLegend", event.target.checked)}
1054
- />
1055
- <span className="edit-label">Unified Legend</span>
1056
- </label>
1057
- }
1058
- </AccordionItemPanel>
1059
- </AccordionItem>}
1060
- {"navigation" !== state.general.type && <AccordionItem> {/* Filters */}
1061
- <AccordionItemHeading>
1062
- <AccordionItemButton>
1063
- Filters
1064
- </AccordionItemButton>
1065
- </AccordionItemHeading>
1066
- <AccordionItemPanel>
1067
- {filtersJSX.length > 0 ? filtersJSX : (<p style={{textAlign: "center"}}>There are currently no filters.</p>) }
1068
- <button className={"btn full-width"} onClick={(event) => {event.preventDefault(); changeFilter(null, "addNew")}}>Add Filter</button>
1069
- </AccordionItemPanel>
1070
- </AccordionItem>}
1071
- {"navigation" !== state.general.type && (
1072
- <AccordionItem> {/* Data Table */}
1073
- <AccordionItemHeading>
1074
- <AccordionItemButton>
1075
- Data Table
1076
- </AccordionItemButton>
1077
- </AccordionItemHeading>
1078
- <AccordionItemPanel>
1079
- <TextField value={dataTable.title} updateField={updateField} section="dataTable" fieldName="title" label="Data Table Title" placeholder="Data Table" />
1080
- <TextField value={dataTable.indexTitle} updateField={updateField} section="dataTable" fieldName="indexTitle" label="Index Column Title" placeholder="Location" />
1081
- <label className="checkbox">
1082
- <input type="checkbox" checked={ state.dataTable.forceDisplay !== undefined ? state.dataTable.forceDisplay : !isDashboard } onChange={(event) => { handleEditorChanges("showDataTable", event.target.checked) }} />
1083
- <span className="edit-label">Show Table</span>
1084
- <Helper text="Data tables are required for 508 compliance. When choosing to hide this data table, replace with your own version." />
1085
- </label>
1086
- <label className="checkbox">
1087
- <input type="checkbox" checked={ state.general.expandDataTable || false } onChange={(event) => { handleEditorChanges("expandDataTable", event.target.checked) }} />
1088
- <span className="edit-label">Map loads with data table expanded</span>
1089
- </label>
1090
- <label className="checkbox">
1091
- <input type="checkbox" checked={ state.general.showDownloadButton } onChange={(event) => { handleEditorChanges("toggleDownloadButton", event.target.checked) }} />
1092
- <span className="edit-label">Enable Download CSV Button</span>
1093
- </label>
1094
- </AccordionItemPanel>
1095
- </AccordionItem>)}
1096
- <AccordionItem> {/* Tooltips */}
1097
- <AccordionItemHeading>
1098
- <AccordionItemButton>
1099
- Interactivity
1100
- </AccordionItemButton>
1101
- </AccordionItemHeading>
1102
- <AccordionItemPanel>
1103
- <label>
1104
- <span className="edit-label">Detail displays on <Helper text="At mobile sizes, information always appears in a popover modal when a user taps on an item." /></span>
1105
- <select value={state.tooltips.appearanceType } onChange={(event) => { handleEditorChanges("appearanceType", event.target.value) }}>
1106
- <option value="hover">Hover - Tooltip</option>
1107
- <option value="click">Click - Popover Modal</option>
1108
- </select>
1109
- </label>
1110
- {'click' === state.tooltips.appearanceType &&
1111
- <TextField value={tooltips.linkLabel} section="tooltips" fieldName="linkLabel" label="Tooltips Link Label" updateField={updateField} />
1112
- }
1113
- <label className="checkbox">
1114
- <input type="checkbox" checked={state.tooltips.capitalizeLabels} onChange={(event) => { handleEditorChanges("capitalizeLabels", event.target.checked) }} />
1115
- <span className="edit-label">Capitalize text inside tooltip</span>
1116
- </label>
1117
- </AccordionItemPanel>
1118
- </AccordionItem>
1119
- <AccordionItem> {/* Visual */}
1120
- <AccordionItemHeading>
1121
- <AccordionItemButton>
1122
- Visual
1123
- </AccordionItemButton>
1124
- </AccordionItemHeading>
1125
- <AccordionItemPanel>
1126
- <label className="header">
1127
- <span className="edit-label">Header Theme</span>
1128
- <ul className="color-palette">
1129
- {headerColors.map( (palette) => {
1130
-
1131
- return (
1132
- <li title={ palette } key={ palette } onClick={ () => { handleEditorChanges("headerColor", palette) }} className={ state.general.headerColor === palette ? "selected " + palette : palette}>
1738
+ {columnsOptions}
1739
+ </select>
1740
+ </label>
1741
+ <TextField
1742
+ value={columns[val].label}
1743
+ section="columns"
1744
+ subsection={val}
1745
+ fieldName="label"
1746
+ label="Label"
1747
+ updateField={updateField}
1748
+ />
1749
+ <ul className="column-edit">
1750
+ <li className="three-col">
1751
+ <TextField
1752
+ value={columns[val].prefix}
1753
+ section="columns"
1754
+ subsection={val}
1755
+ fieldName="prefix"
1756
+ label="Prefix"
1757
+ updateField={updateField}
1758
+ />
1759
+ <TextField
1760
+ value={columns[val].suffix}
1761
+ section="columns"
1762
+ subsection={val}
1763
+ fieldName="suffix"
1764
+ label="Suffix"
1765
+ updateField={updateField}
1766
+ />
1767
+ <TextField
1768
+ type="number"
1769
+ value={columns[val].roundToPlace}
1770
+ section="columns"
1771
+ subsection={val}
1772
+ fieldName="roundToPlace"
1773
+ label="Round"
1774
+ updateField={updateField}
1775
+ />
1133
1776
  </li>
1134
- )
1135
- })}
1136
- </ul>
1137
- </label>
1138
- <label className="checkbox">
1139
- <input type="checkbox" checked={ state.general.showTitle || false } onChange={(event) => { handleEditorChanges("showTitle", event.target.checked) }} />
1140
- <span className="edit-label">Show Title</span>
1141
- </label>
1142
- {"navigation" !== state.general.type && (
1143
- <label className="checkbox">
1144
- <input type="checkbox" checked={ state.general.showSidebar || false } onChange={(event) => { handleEditorChanges("showSidebar", event.target.checked) }} />
1145
- <span className="edit-label">Show Legend</span>
1146
- </label>)}
1147
- {"navigation" !== state.general.type && (
1148
- <label>
1149
- <span className="edit-label">Legend Position</span>
1150
- <select value={legend.position || false } onChange={(event) => { handleEditorChanges("sidebarPosition", event.target.value) }}>
1151
- <option value="side">Side</option>
1152
- <option value="bottom">Bottom</option>
1153
- </select>
1154
- </label>)}
1155
- {"side" === legend.position && (
1156
- <label className="checkbox">
1157
- <input type="checkbox" checked={ legend.singleColumn} onChange={(event) => { handleEditorChanges("singleColumnLegend", event.target.checked) }} />
1158
- <span className="edit-label">Single Column Legend</span>
1159
- </label>)}
1160
- {"navigation" === state.general.type && (
1161
- <label className="checkbox">
1162
- <input type="checkbox" checked={ state.general.fullBorder || false } onChange={(event) => { handleEditorChanges("fullBorder", event.target.checked) }} />
1163
- <span className="edit-label">Add border around map</span>
1164
- </label>)}
1165
- <label>
1166
- <span className="edit-label">Geo Border Color</span>
1167
- <select value={state.general.geoBorderColor || false } onChange={(event) => { handleEditorChanges("geoBorderColor", event.target.value) }}>
1168
- <option value="darkGray">Dark Gray (Default)</option>
1169
- <option value="sameAsBackground">White</option>
1170
- </select>
1171
- </label>
1172
- <label>
1173
- <span className="edit-label">Map Color Palette</span>
1174
- </label>
1175
- <span className="h5">Quantitative</span>
1176
- <ul className="color-palette">
1177
- {Object.keys(colorPalettes).filter((name) => !name.includes('qualitative')).map( (palette) => {
1178
-
1179
- const colorOne = {
1180
- backgroundColor: colorPalettes[palette][2]
1181
- }
1182
-
1183
- const colorTwo = {
1184
- backgroundColor: colorPalettes[palette][4]
1185
- }
1186
-
1187
- const colorThree = {
1188
- backgroundColor: colorPalettes[palette][6]
1189
- }
1190
-
1191
- return (
1192
- <li title={ palette } key={ palette } onClick={ () => { handleEditorChanges("color", palette) }} className={ state.color === palette ? "selected" : ""}>
1193
- <span style={colorOne}></span>
1194
- <span style={colorTwo}></span>
1195
- <span style={colorThree}></span>
1196
- </li>
1197
- )
1198
- })}
1199
- </ul>
1200
- <span className="h5">Qualitative</span>
1201
- <ul className="color-palette">
1202
- {Object.keys(colorPalettes).filter((name) => name.includes('qualitative')).map( (palette) => {
1203
-
1204
- const colorOne = {
1205
- backgroundColor: colorPalettes[palette][2]
1206
- }
1207
-
1208
- const colorTwo = {
1209
- backgroundColor: colorPalettes[palette][4]
1777
+ <li>
1778
+ <label className="checkbox">
1779
+ <input
1780
+ type="checkbox"
1781
+ checked={state.columns[val].useCommas}
1782
+ onChange={(event) => {
1783
+ editColumn(val, 'useCommas', event.target.checked)
1784
+ }}
1785
+ />
1786
+ <span className="edit-label">Add Commas to Numbers</span>
1787
+ </label>
1788
+ </li>
1789
+ <li>
1790
+ <label className="checkbox">
1791
+ <input
1792
+ type="checkbox"
1793
+ checked={state.columns[val].dataTable}
1794
+ onChange={(event) => {
1795
+ editColumn(val, 'dataTable', event.target.checked)
1796
+ }}
1797
+ />
1798
+ <span className="edit-label">Display in Data Table</span>
1799
+ </label>
1800
+ </li>
1801
+ <li>
1802
+ <label className="checkbox">
1803
+ <input
1804
+ type="checkbox"
1805
+ checked={state.columns[val].tooltip}
1806
+ onChange={(event) => {
1807
+ editColumn(val, 'tooltip', event.target.checked)
1808
+ }}
1809
+ />
1810
+ <span className="edit-label">Display in Tooltips</span>
1811
+ </label>
1812
+ </li>
1813
+ </ul>
1814
+ </fieldset>
1815
+ ))}
1816
+ <button
1817
+ className={'btn full-width'}
1818
+ onClick={(event) => {
1819
+ event.preventDefault();
1820
+ addAdditionalColumn(additionalColumns.length + 1);
1821
+ }}
1822
+ >
1823
+ Add Column
1824
+ </button>
1825
+ </fieldset>
1826
+ }
1827
+ </AccordionItemPanel>
1828
+ </AccordionItem>{' '}
1829
+ {/* Columns */}
1830
+ {'navigation' !== state.general.type && (
1831
+ <AccordionItem>
1832
+ {' '}
1833
+ {/* Legend */}
1834
+ <AccordionItemHeading>
1835
+ <AccordionItemButton>Legend</AccordionItemButton>
1836
+ </AccordionItemHeading>
1837
+ <AccordionItemPanel>
1838
+ {(state.legend.type === "equalnumber" || state.legend.type === 'equalinterval') && (
1839
+ <label>
1840
+ <span className='edit-label'>Legend Type</span>
1841
+ <select
1842
+ value={legend.type}
1843
+ onChange={(event) => {
1844
+ handleEditorChanges('legendType', event.target.value);
1845
+ }}
1846
+ >
1847
+ <option value='equalnumber'>Equal Number</option>
1848
+ <option value='equalinterval'>Equal Interval</option>
1849
+ <option value='category'>Categorical</option>
1850
+ </select>
1851
+ </label>
1852
+ )}
1853
+ {'category' !== legend.type && (
1854
+ <label className='checkbox'>
1855
+ <input
1856
+ type='checkbox'
1857
+ checked={legend.separateZero || false}
1858
+ onChange={(event) =>
1859
+ handleEditorChanges('separateZero', event.target.checked)
1860
+ }
1861
+ />
1862
+ <span className='edit-label'>
1863
+ Separate Zero
1864
+ <Tooltip style={{textTransform: 'none'}}>
1865
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
1866
+ <Tooltip.Content>
1867
+ <p>For numeric data, you can separate the zero value as its own data class.</p>
1868
+ </Tooltip.Content>
1869
+ </Tooltip>
1870
+ </span>
1871
+
1872
+ </label>
1873
+ )}
1874
+ {/* Temp Checkbox */}
1875
+ {state.legend.type === 'equalnumber' &&
1876
+ <label className="checkbox mt-4">
1877
+ <input type="checkbox" checked={ state.general.equalNumberOptIn } onChange={(event) => { handleEditorChanges("showEqualNumber", event.target.checked) }} />
1878
+ <span className="edit-label">Use new quantile legend</span>
1879
+ <Tooltip style={{textTransform: 'none'}}>
1880
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
1881
+ <Tooltip.Content>
1882
+ <p>This prevents numbers from being used in more than one category (ie. 0-1, 1-2, 2-3) </p>
1883
+ </Tooltip.Content>
1884
+ </Tooltip>
1885
+ </label>
1886
+ }
1887
+ {'category' !== legend.type && (
1888
+ <label>
1889
+ <span className='edit-label'>
1890
+ Number of Items
1891
+ <Tooltip style={{textTransform: 'none'}}>
1892
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
1893
+ <Tooltip.Content>
1894
+ <p>For numeric maps, select the number of data classes. Do not include designated special classes.</p>
1895
+ </Tooltip.Content>
1896
+ </Tooltip>
1897
+ </span>
1898
+ <select
1899
+ value={legend.numberOfItems}
1900
+ onChange={(event) => {
1901
+ handleEditorChanges('legendNumber', event.target.value);
1902
+ }}
1903
+ >
1904
+ {[...Array(numberOfItemsLimit).keys()].map((num) => {
1905
+ return (
1906
+ <option value={num + 1} key={num + 1}>
1907
+ {num + 1}
1908
+ </option>
1909
+ );
1910
+ })}
1911
+ </select>
1912
+ </label>
1913
+ )}
1914
+ {'category' === legend.type && (
1915
+ <React.Fragment>
1916
+ <label>
1917
+ <span className='edit-label'>
1918
+ Category Order
1919
+ <Tooltip style={{textTransform: 'none'}}>
1920
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
1921
+ <Tooltip.Content>
1922
+ <p>Drag map categories into preferred legend order. </p>
1923
+ </Tooltip.Content>
1924
+ </Tooltip>
1925
+ </span>
1926
+ </label>
1927
+ {/* TODO: Swap out this drag and drop library back to something simpler. I had to remove the old one because it hadn't been updated and wouldn't work with Webpack 5. This is overkill for our needs. */}
1928
+ <DragDropContext
1929
+ onDragEnd={({ source, destination }) =>
1930
+ categoryMove(source.index, destination.index)
1931
+ }
1932
+ >
1933
+ <Droppable droppableId='category_order'>
1934
+ {(provided) => (
1935
+ <ul
1936
+ {...provided.droppableProps}
1937
+ className='sort-list'
1938
+ ref={provided.innerRef}
1939
+ >
1940
+ <CategoryList />
1941
+ {provided.placeholder}
1942
+ </ul>
1943
+ )}
1944
+ </Droppable>
1945
+ </DragDropContext>
1946
+ {editorCatOrder.length >= 10 && (
1947
+ <section className='error-box my-2'>
1948
+ <div>
1949
+ <strong className='pt-1'>Warning</strong>
1950
+ <p>
1951
+ The maximum number of categorical legend items is 10. If
1952
+ your data has more than 10 categories your map will not
1953
+ display properly.
1954
+ </p>
1955
+ </div>
1956
+ </section>
1957
+ )}
1958
+ </React.Fragment>
1959
+ )}
1960
+ <TextField
1961
+ value={legend.title}
1962
+ updateField={updateField}
1963
+ section='legend'
1964
+ fieldName='title'
1965
+ label='Legend Title'
1966
+ placeholder='Legend Title'
1967
+ />
1968
+ {false === legend.dynamicDescription && (
1969
+ <TextField
1970
+ type='textarea'
1971
+ value={legend.description}
1972
+ updateField={updateField}
1973
+ section='legend'
1974
+ fieldName='description'
1975
+ label='Legend Description'
1976
+ />
1977
+ )}
1978
+ {true === legend.dynamicDescription && (
1979
+ <React.Fragment>
1980
+ <label>
1981
+ <span>Legend Description</span>
1982
+ <span className='subtext'>
1983
+ For {displayFilterLegendValue(activeFilterValueForDescription)}
1984
+ </span>
1985
+ <DynamicDesc
1986
+ value={
1987
+ legend.descriptions[String(activeFilterValueForDescription)]
1988
+ }
1989
+ />
1990
+ </label>
1991
+ <label>
1992
+ <select
1993
+ value={String(activeFilterValueForDescription)}
1994
+ onChange={(event) => {
1995
+ handleEditorChanges(
1996
+ 'changeActiveFilterValue',
1997
+ event.target.value
1998
+ );
1999
+ }}
2000
+ >
2001
+ {filterValueOptionList.map((arr, i) => {
2002
+ return (
2003
+ <option value={arr} key={i}>
2004
+ {displayFilterLegendValue(arr)}
2005
+ </option>
2006
+ );
2007
+ })}
2008
+ </select>
2009
+ </label>
2010
+ </React.Fragment>
2011
+ )}
2012
+ {filtersJSX.length > 0 && (
2013
+ <label className='checkbox'>
2014
+ <input
2015
+ type='checkbox'
2016
+ checked={legend.dynamicDescription}
2017
+ onChange={() => {
2018
+ handleEditorChanges(
2019
+ 'dynamicDescription',
2020
+ filterValueOptionList[0]
2021
+ );
2022
+ }}
2023
+ />
2024
+ <span className='edit-label'>
2025
+ Dynamic Legend Description
2026
+ <Tooltip style={{textTransform: 'none'}}>
2027
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
2028
+ <Tooltip.Content>
2029
+ <p>Check this option if the map has multiple filter controls and you want to specify a description for each filter selection.</p>
2030
+ </Tooltip.Content>
2031
+ </Tooltip>
2032
+ </span>
2033
+ </label>
2034
+ )}
2035
+ {filtersJSX.length > 0 || state.general.type === 'bubble' && (
2036
+ <label className='checkbox'>
2037
+ <input
2038
+ type='checkbox'
2039
+ checked={legend.unified}
2040
+ onChange={(event) =>
2041
+ handleEditorChanges('unifiedLegend', event.target.checked)
2042
+ }
2043
+ />
2044
+ <span className='edit-label'>
2045
+ Unified Legend
2046
+ <Tooltip style={{textTransform: 'none'}}>
2047
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
2048
+ <Tooltip.Content>
2049
+ <p>For a map with filters, check this option if you want the high and low values in the legend to be based on <em>all</em> mapped values.</p>
2050
+ </Tooltip.Content>
2051
+ </Tooltip>
2052
+ </span>
2053
+ </label>
2054
+ )}
2055
+ </AccordionItemPanel>
2056
+ </AccordionItem>
2057
+ )}
2058
+ {'navigation' !== state.general.type && (
2059
+ <AccordionItem>
2060
+ {' '}
2061
+ {/* Filters */}
2062
+ <AccordionItemHeading>
2063
+ <AccordionItemButton>Filters</AccordionItemButton>
2064
+ </AccordionItemHeading>
2065
+ <AccordionItemPanel>
2066
+ {filtersJSX.length > 0 ? (
2067
+ filtersJSX
2068
+ ) : (
2069
+ <p style={{ textAlign: 'center' }}>There are currently no filters.</p>
2070
+ )}
2071
+ <button
2072
+ className={'btn full-width'}
2073
+ onClick={(event) => {
2074
+ event.preventDefault();
2075
+ changeFilter(null, 'addNew');
2076
+ }}
2077
+ >
2078
+ Add Filter
2079
+ </button>
2080
+ </AccordionItemPanel>
2081
+ </AccordionItem>
2082
+ )}
2083
+ {'navigation' !== state.general.type && (
2084
+ <AccordionItem>
2085
+ {' '}
2086
+ {/* Data Table */}
2087
+ <AccordionItemHeading>
2088
+ <AccordionItemButton>Data Table</AccordionItemButton>
2089
+ </AccordionItemHeading>
2090
+ <AccordionItemPanel>
2091
+ <TextField
2092
+ value={dataTable.title}
2093
+ updateField={updateField}
2094
+ section='dataTable'
2095
+ fieldName='title'
2096
+ label='Data Table Title'
2097
+ placeholder='Data Table'
2098
+ />
2099
+ <TextField
2100
+ value={dataTable.indexLabel || ''}
2101
+ updateField={updateField}
2102
+ section='dataTable'
2103
+ fieldName='indexLabel'
2104
+ label='Index Column Header'
2105
+ placeholder='Location'
2106
+ tooltip={
2107
+ <Tooltip style={{textTransform: 'none'}}>
2108
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
2109
+ <Tooltip.Content>
2110
+ <p>To comply with 508 standards, if the first column in the data table has no header, enter a brief one here.</p>
2111
+ </Tooltip.Content>
2112
+ </Tooltip>
1210
2113
  }
1211
-
1212
- const colorThree = {
1213
- backgroundColor: colorPalettes[palette][6]
2114
+ />
2115
+ <TextField
2116
+ value={dataTable.caption}
2117
+ updateField={updateField}
2118
+ section='dataTable'
2119
+ fieldName='caption'
2120
+ label='Data Table Caption'
2121
+ placeholder='Data Table'
2122
+ tooltip={
2123
+ <Tooltip style={{textTransform: 'none'}}>
2124
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
2125
+ <Tooltip.Content>
2126
+ <p>Enter a description of the data table to be read by screen readers.</p>
2127
+ </Tooltip.Content>
2128
+ </Tooltip>
1214
2129
  }
1215
-
1216
- return (
1217
- <li title={ palette } key={ palette } onClick={ () => { handleEditorChanges("color", palette) }} className={ state.color === palette ? "selected" : ""}>
1218
- <span style={colorOne}></span>
1219
- <span style={colorTwo}></span>
1220
- <span style={colorThree}></span>
1221
- </li>
1222
- )
1223
- })}
1224
- </ul>
1225
- </AccordionItemPanel>
1226
- </AccordionItem>
1227
- </Accordion>
1228
- </form>
1229
- <a href="https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/data-map.html" target="_blank" rel="noopener noreferrer" className="guidance-link">
1230
- <MapIcon />
1231
- <div>
1232
- <span className="heading-3">Get Maps Help</span>
1233
- <p>Examples and documentation</p>
1234
- </div>
1235
- </a>
1236
- <div className="advanced">
1237
- <span className="advanced-toggle-link" onClick={() => setAdvancedToggle(!advancedToggle)}><span>{advancedToggle ? `— ` : `+ `}</span>Advanced Options</span>
1238
- {advancedToggle && (
1239
- <React.Fragment>
1240
- <section className="error-box py-2 px-3 my-2"><div><strong className="pt-1">Warning</strong><p>This can cause serious errors in your map.</p></div></section>
1241
- <p className="pb-2">This tool displays the actual map configuration <acronym title="JavaScript Object Notation">JSON</acronym> that is generated by this editor and allows you to edit properties directly and apply them.</p>
1242
- <textarea value={ configTextboxValue } onChange={(event) => setConfigTextbox(event.target.value)} />
1243
- <button className="btn full-width" onClick={() => loadConfig( JSON.parse( configTextboxValue ) )}>Apply</button>
1244
- </React.Fragment>
1245
- )}
1246
- </div>
1247
- </section>
1248
- </section>
1249
- </ErrorBoundary>
1250
- )
1251
- }
2130
+ type="textarea"
2131
+ />
2132
+ <label className='checkbox'>
2133
+ <input
2134
+ type='checkbox'
2135
+ checked={
2136
+ state.dataTable.forceDisplay !== undefined
2137
+ ? state.dataTable.forceDisplay
2138
+ : !isDashboard
2139
+ }
2140
+ onChange={(event) => {
2141
+ handleEditorChanges('showDataTable', event.target.checked);
2142
+ }}
2143
+ />
2144
+ <span className='edit-label'>
2145
+ Show Table
2146
+ <Tooltip style={{textTransform: 'none'}}>
2147
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
2148
+ <Tooltip.Content>
2149
+ <p>Data tables are required for 508 compliance. When choosing to hide this data table, replace with your own version.</p>
2150
+ </Tooltip.Content>
2151
+ </Tooltip>
2152
+ </span>
2153
+ </label>
2154
+ <label className='checkbox'>
2155
+ <input
2156
+ type='checkbox'
2157
+ checked={ state.dataTable.limitHeight }
2158
+ onChange={(event) => {
2159
+ handleEditorChanges('limitDataTableHeight', event.target.checked);
2160
+ }}
2161
+ />
2162
+ <span className='edit-label'>Limit Table Height</span>
2163
+ </label>
2164
+ {state.dataTable.limitHeight &&
2165
+ <TextField
2166
+ value={dataTable.height}
2167
+ updateField={updateField}
2168
+ section='dataTable'
2169
+ fieldName='height'
2170
+ label='Data Table Height'
2171
+ placeholder='Height(px)'
2172
+ type="number"
2173
+ min="0"
2174
+ max="500"
2175
+ />
2176
+ }
2177
+ <label className='checkbox'>
2178
+ <input
2179
+ type='checkbox'
2180
+ checked={state.general.expandDataTable || false}
2181
+ onChange={(event) => {
2182
+ handleEditorChanges('expandDataTable', event.target.checked);
2183
+ }}
2184
+ />
2185
+ <span className='edit-label'>Map loads with data table expanded</span>
2186
+ </label>
2187
+ <label className='checkbox'>
2188
+ <input
2189
+ type='checkbox'
2190
+ checked={state.general.showDownloadButton}
2191
+ onChange={(event) => {
2192
+ handleEditorChanges('toggleDownloadButton', event.target.checked);
2193
+ }}
2194
+ />
2195
+ <span className='edit-label'>Enable Download CSV Button</span>
2196
+ </label>
2197
+ </AccordionItemPanel>
2198
+ </AccordionItem>
2199
+ )}
2200
+ <AccordionItem>
2201
+ {' '}
2202
+ {/* Tooltips */}
2203
+ <AccordionItemHeading>
2204
+ <AccordionItemButton>Interactivity</AccordionItemButton>
2205
+ </AccordionItemHeading>
2206
+ <AccordionItemPanel>
2207
+ <label>
2208
+ <span className='edit-label'>
2209
+ Detail displays on{' '}
2210
+ <Tooltip style={{textTransform: 'none'}}>
2211
+ <Tooltip.Target><Icon display="question" style={{marginLeft: '0.5rem'}}/></Tooltip.Target>
2212
+ <Tooltip.Content>
2213
+ <p>At mobile sizes, information always appears in a popover modal when a user taps on an item.</p>
2214
+ </Tooltip.Content>
2215
+ </Tooltip>
2216
+ </span>
2217
+ <select
2218
+ value={state.tooltips.appearanceType}
2219
+ onChange={(event) => {
2220
+ handleEditorChanges('appearanceType', event.target.value);
2221
+ }}
2222
+ >
2223
+ <option value='hover'>Hover - Tooltip</option>
2224
+ <option value='click'>Click - Popover Modal</option>
2225
+ </select>
2226
+ </label>
2227
+ {'click' === state.tooltips.appearanceType && (
2228
+ <TextField
2229
+ value={tooltips.linkLabel}
2230
+ section='tooltips'
2231
+ fieldName='linkLabel'
2232
+ label='Tooltips Link Label'
2233
+ updateField={updateField}
2234
+ />
2235
+ )}
2236
+ <label className='checkbox'>
2237
+ <input
2238
+ type='checkbox'
2239
+ checked={state.tooltips.capitalizeLabels}
2240
+ onChange={(event) => {
2241
+ handleEditorChanges('capitalizeLabels', event.target.checked);
2242
+ }}
2243
+ />
2244
+ <span className='edit-label'>Capitalize text inside tooltip</span>
2245
+ </label>
2246
+ </AccordionItemPanel>
2247
+ </AccordionItem>
2248
+ <AccordionItem>
2249
+ {' '}
2250
+ {/* Visual */}
2251
+ <AccordionItemHeading>
2252
+ <AccordionItemButton>Visual</AccordionItemButton>
2253
+ </AccordionItemHeading>
2254
+ <AccordionItemPanel>
2255
+ <label>
2256
+ <span className='edit-label'>Header Theme</span>
2257
+ <ul className='color-palette'>
2258
+ {headerColors.map((palette) => {
2259
+ return (
2260
+ <li
2261
+ title={palette}
2262
+ key={palette}
2263
+ onClick={() => {
2264
+ handleEditorChanges('headerColor', palette);
2265
+ }}
2266
+ className={
2267
+ state.general.headerColor === palette
2268
+ ? 'selected ' + palette
2269
+ : palette
2270
+ }
2271
+ ></li>
2272
+ );
2273
+ })}
2274
+ </ul>
2275
+ </label>
2276
+ <label className='checkbox'>
2277
+ <input
2278
+ type='checkbox'
2279
+ checked={state.general.showTitle || false}
2280
+ onChange={(event) => {
2281
+ handleEditorChanges('showTitle', event.target.checked);
2282
+ }}
2283
+ />
2284
+ <span className='edit-label'>Show Title</span>
2285
+ </label>
2286
+
2287
+ <label className='checkbox'>
2288
+ <input
2289
+ type='checkbox'
2290
+ checked={state.general.hideGeoColumnInTooltip || false}
2291
+ onChange={(event) => {
2292
+ handleEditorChanges('hideGeoColumnInTooltip', event.target.checked);
2293
+ }}
2294
+ />
2295
+ <span className='edit-label'>Hide Geography Column Name in Tooltip</span>
2296
+ </label>
2297
+
2298
+ <label className='checkbox'>
2299
+ <input
2300
+ type='checkbox'
2301
+ checked={state.general.hidePrimaryColumnInTooltip || false}
2302
+ onChange={(event) => {
2303
+ handleEditorChanges('hidePrimaryColumnInTooltip', event.target.checked);
2304
+ }}
2305
+ />
2306
+ <span className='edit-label'>Hide Primary Column Name in Tooltip</span>
2307
+ </label>
2308
+
2309
+ {'navigation' !== state.general.type && (
2310
+ <label className='checkbox'>
2311
+ <input
2312
+ type='checkbox'
2313
+ checked={state.general.showSidebar || false}
2314
+ onChange={(event) => {
2315
+ handleEditorChanges('showSidebar', event.target.checked);
2316
+ }}
2317
+ />
2318
+ <span className='edit-label'>Show Legend</span>
2319
+ </label>
2320
+ )}
2321
+ {'navigation' !== state.general.type && (
2322
+ <label>
2323
+ <span className='edit-label'>Legend Position</span>
2324
+ <select
2325
+ value={legend.position || false}
2326
+ onChange={(event) => {
2327
+ handleEditorChanges('sidebarPosition', event.target.value);
2328
+ }}
2329
+ >
2330
+ <option value='side'>Side</option>
2331
+ <option value='bottom'>Bottom</option>
2332
+ </select>
2333
+ </label>
2334
+ )}
2335
+ {'side' === legend.position && (
2336
+ <label className='checkbox'>
2337
+ <input
2338
+ type='checkbox'
2339
+ checked={legend.singleColumn}
2340
+ onChange={(event) => {
2341
+ handleEditorChanges('singleColumnLegend', event.target.checked);
2342
+ }}
2343
+ />
2344
+ <span className='edit-label'>Single Column Legend</span>
2345
+ </label>
2346
+ )}
2347
+ {'navigation' === state.general.type && (
2348
+ <label className='checkbox'>
2349
+ <input
2350
+ type='checkbox'
2351
+ checked={state.general.fullBorder || false}
2352
+ onChange={(event) => {
2353
+ handleEditorChanges('fullBorder', event.target.checked);
2354
+ }}
2355
+ />
2356
+ <span className='edit-label'>Add border around map</span>
2357
+ </label>
2358
+ )}
2359
+ <label>
2360
+ <span className='edit-label'>Geo Border Color</span>
2361
+ <select
2362
+ value={state.general.geoBorderColor || false}
2363
+ onChange={(event) => {
2364
+ handleEditorChanges('geoBorderColor', event.target.value);
2365
+ }}
2366
+ >
2367
+ <option value='darkGray'>Dark Gray (Default)</option>
2368
+ <option value='sameAsBackground'>White</option>
2369
+ </select>
2370
+ </label>
2371
+ <label>
2372
+ <span className='edit-label'>Map Color Palette</span>
2373
+ </label>
2374
+ {/* <InputCheckbox section="general" subsection="palette" fieldName='isReversed' size='small' label='Use selected palette in reverse order' updateField={updateField} value={isPaletteReversed} /> */}
2375
+ <InputToggle type='3d' section="general" subsection="palette" fieldName='isReversed' size='small' label='Use selected palette in reverse order' updateField={updateField} value={isPaletteReversed} />
2376
+ <span>Sequential</span>
2377
+ <ul className='color-palette'>
2378
+ {filteredPallets
2379
+ .map((palette) => {
2380
+ const colorOne = {
2381
+ backgroundColor: colorPalettes[palette][2],
2382
+ };
2383
+
2384
+ const colorTwo = {
2385
+ backgroundColor: colorPalettes[palette][4],
2386
+ };
2387
+
2388
+ const colorThree = {
2389
+ backgroundColor: colorPalettes[palette][6],
2390
+ };
2391
+
2392
+ return (
2393
+ <li
2394
+ title={palette}
2395
+ key={palette}
2396
+ onClick={() => {
2397
+ handleEditorChanges('color', palette);
2398
+ }}
2399
+ className={state.color === palette ? 'selected' : ''}
2400
+ >
2401
+ <span style={colorOne}></span>
2402
+ <span style={colorTwo}></span>
2403
+ <span style={colorThree}></span>
2404
+ </li>
2405
+ );
2406
+ })}
2407
+ </ul>
2408
+ <span>Non-Sequential</span>
2409
+ <ul className='color-palette'>
2410
+ {filteredQualitative
2411
+ .map((palette) => {
2412
+ const colorOne = {
2413
+ backgroundColor: colorPalettes[palette][2],
2414
+ };
2415
+
2416
+ const colorTwo = {
2417
+ backgroundColor: colorPalettes[palette][4],
2418
+ };
2419
+
2420
+ const colorThree = {
2421
+ backgroundColor: colorPalettes[palette][6],
2422
+ };
2423
+
2424
+ return (
2425
+ <li
2426
+ title={palette}
2427
+ key={palette}
2428
+ onClick={() => {
2429
+ handleEditorChanges('color', palette);
2430
+ }}
2431
+ className={state.color === palette ? 'selected' : ''}
2432
+ >
2433
+ <span style={colorOne}></span>
2434
+ <span style={colorTwo}></span>
2435
+ <span style={colorThree}></span>
2436
+ </li>
2437
+ );
2438
+ })}
2439
+ </ul>
2440
+ <TextField
2441
+ type='number'
2442
+ value={state.visual.minBubbleSize}
2443
+ section='visual'
2444
+ fieldName='minBubbleSize'
2445
+ label='Minimum Bubble Size'
2446
+ updateField={updateField}
2447
+ />
2448
+ <TextField
2449
+ type='number'
2450
+ value={state.visual.maxBubbleSize}
2451
+ section='visual'
2452
+ fieldName='maxBubbleSize'
2453
+ label='Maximum Bubble Size'
2454
+ updateField={updateField}
2455
+ />
2456
+ { (state.general.geoType === 'world' || state.general.geoType === 'us') &&
2457
+ <label className='checkbox'>
2458
+ <input
2459
+ type='checkbox'
2460
+ checked={state.visual.showBubbleZeros}
2461
+ onChange={(event) => {
2462
+ handleEditorChanges('showBubbleZeros', event.target.checked);
2463
+ }}
2464
+ />
2465
+ <span className='edit-label'>Show Data with Zero's on Bubble Map</span>
2466
+ </label>
2467
+ }
2468
+ {state.general.geoType === 'world' &&
2469
+ <label className='checkbox'>
2470
+ <input
2471
+ type='checkbox'
2472
+ checked={state.general.allowMapZoom}
2473
+ onChange={(event) => {
2474
+ handleEditorChanges('allowMapZoom', event.target.checked);
2475
+ }}
2476
+ />
2477
+ <span className='edit-label'>Allow Map Zooming</span>
2478
+ </label>
2479
+ }
2480
+ {state.general.type === 'bubble' &&
2481
+ <label className='checkbox'>
2482
+ <input
2483
+ type='checkbox'
2484
+ checked={state.visual.extraBubbleBorder}
2485
+ onChange={(event) => {
2486
+ handleEditorChanges('toggleExtraBubbleBorder', event.target.checked);
2487
+ }}
2488
+ />
2489
+ <span className='edit-label'>Bubble Map has extra border</span>
2490
+ </label>
2491
+ }
2492
+ </AccordionItemPanel>
2493
+ </AccordionItem>
2494
+ </Accordion>
2495
+ </form>
2496
+ <AdvancedEditor loadConfig={loadConfig} state={state} convertStateToConfig={convertStateToConfig} />
2497
+
2498
+ </section>
2499
+ </section>
2500
+ </ErrorBoundary>
2501
+ );
2502
+ };
1252
2503
 
1253
2504
  export default EditorPanel;