@cdc/map 4.22.10-alpha.1 → 4.22.11

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