@cdc/map 4.22.10 → 4.23.1

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