@cdc/map 4.23.3 → 4.23.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcmap.js +22422 -22053
- package/examples/custom-map-layers.json +764 -0
- package/examples/default-county.json +169 -155
- package/examples/example-city-state.json +31 -9
- package/examples/testing-layer-2.json +1 -0
- package/examples/testing-layer.json +96 -0
- package/index.html +6 -5
- package/package.json +3 -3
- package/src/CdcMap.jsx +55 -50
- package/src/components/CountyMap.jsx +30 -5
- package/src/components/EditorPanel.jsx +255 -129
- package/src/components/UsaMap.jsx +17 -11
- package/src/data/initial-state.js +7 -3
- package/src/hooks/useMapLayers.jsx +243 -0
- package/src/index.jsx +4 -8
- package/src/scss/editor-panel.scss +97 -97
- package/src/scss/filters.scss +0 -2
- package/src/scss/main.scss +25 -26
- package/src/components/Filters.jsx +0 -113
|
@@ -27,6 +27,9 @@ import AlabamaGraphic from '@cdc/core/assets/icon-map-alabama.svg'
|
|
|
27
27
|
import worldDefaultConfig from '../../examples/default-world.json'
|
|
28
28
|
import usaDefaultConfig from '../../examples/default-usa.json'
|
|
29
29
|
import countyDefaultConfig from '../../examples/default-county.json'
|
|
30
|
+
import useMapLayers from '../hooks/useMapLayers'
|
|
31
|
+
|
|
32
|
+
import { useFilters } from '@cdc/core/components/Filters'
|
|
30
33
|
|
|
31
34
|
const TextField = ({ label, section = null, subsection = null, fieldName, updateField, value: stateValue, type = 'input', tooltip, ...attributes }) => {
|
|
32
35
|
const [value, setValue] = useState(stateValue)
|
|
@@ -65,7 +68,7 @@ const TextField = ({ label, section = null, subsection = null, fieldName, update
|
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
const EditorPanel = props => {
|
|
68
|
-
const { state, columnsInData = [], loadConfig, setState, isDashboard, setParentConfig,
|
|
71
|
+
const { state, columnsInData = [], loadConfig, setState, isDashboard, setParentConfig, runtimeFilters, runtimeLegend, changeFilterActive, isDebug, setRuntimeFilters } = props
|
|
69
72
|
|
|
70
73
|
const { general, columns, legend, dataTable, tooltips } = state
|
|
71
74
|
|
|
@@ -79,8 +82,22 @@ const EditorPanel = props => {
|
|
|
79
82
|
|
|
80
83
|
const [activeFilterValueForDescription, setActiveFilterValueForDescription] = useState([0, 0])
|
|
81
84
|
|
|
85
|
+
const { handleFilterOrder, filterOrderOptions, filterStyleOptions } = useFilters({ config: state, setConfig: setState, filteredData: runtimeFilters, setFilteredData: setRuntimeFilters })
|
|
86
|
+
|
|
82
87
|
const headerColors = ['theme-blue', 'theme-purple', 'theme-brown', 'theme-teal', 'theme-pink', 'theme-orange', 'theme-slate', 'theme-indigo', 'theme-cyan', 'theme-green', 'theme-amber']
|
|
83
88
|
|
|
89
|
+
const {
|
|
90
|
+
// prettier-ignore
|
|
91
|
+
MapLayerHandlers: {
|
|
92
|
+
handleAddLayer,
|
|
93
|
+
handleMapLayerName,
|
|
94
|
+
handleMapLayerUrl,
|
|
95
|
+
handleRemoveLayer,
|
|
96
|
+
handleMapLayerNamespace,
|
|
97
|
+
handleMapLayerTooltip
|
|
98
|
+
}
|
|
99
|
+
} = useMapLayers(state, setState, false, true)
|
|
100
|
+
|
|
84
101
|
const categoryMove = (idx1, idx2) => {
|
|
85
102
|
let categoryValuesOrder = [...state.legend.categoryValuesOrder]
|
|
86
103
|
|
|
@@ -119,7 +136,7 @@ const EditorPanel = props => {
|
|
|
119
136
|
}
|
|
120
137
|
|
|
121
138
|
const CheckBox = memo(({ label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes }) => (
|
|
122
|
-
<label className='checkbox'>
|
|
139
|
+
<label className='checkbox column-heading'>
|
|
123
140
|
<input
|
|
124
141
|
type='checkbox'
|
|
125
142
|
name={fieldName}
|
|
@@ -136,23 +153,6 @@ const EditorPanel = props => {
|
|
|
136
153
|
</label>
|
|
137
154
|
))
|
|
138
155
|
|
|
139
|
-
const handleFilterOrder = (idx1, idx2, filterIndex, filter) => {
|
|
140
|
-
let filterOrder = filter.values
|
|
141
|
-
let [movedItem] = filterOrder.splice(idx1, 1)
|
|
142
|
-
filterOrder.splice(idx2, 0, movedItem)
|
|
143
|
-
let filters = [...runtimeFilters]
|
|
144
|
-
let filterItem = { ...runtimeFilters[filterIndex] }
|
|
145
|
-
filterItem.active = filter.values[0]
|
|
146
|
-
filterItem.values = filterOrder
|
|
147
|
-
filterItem.order = 'cust'
|
|
148
|
-
filters[filterIndex] = filterItem
|
|
149
|
-
|
|
150
|
-
setState({
|
|
151
|
-
...state,
|
|
152
|
-
filters
|
|
153
|
-
})
|
|
154
|
-
}
|
|
155
|
-
|
|
156
156
|
const DynamicDesc = ({ label, fieldName, value: stateValue, type = 'input', ...attributes }) => {
|
|
157
157
|
const [value, setValue] = useState(stateValue)
|
|
158
158
|
|
|
@@ -201,6 +201,17 @@ const EditorPanel = props => {
|
|
|
201
201
|
}
|
|
202
202
|
})
|
|
203
203
|
break
|
|
204
|
+
|
|
205
|
+
case 'toggleDataTableLink':
|
|
206
|
+
setState({
|
|
207
|
+
...state,
|
|
208
|
+
table: {
|
|
209
|
+
...state.table,
|
|
210
|
+
showDataTableLink: value
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
break
|
|
214
|
+
|
|
204
215
|
case 'toggleDataUrl':
|
|
205
216
|
setState({
|
|
206
217
|
...state,
|
|
@@ -498,7 +509,7 @@ const EditorPanel = props => {
|
|
|
498
509
|
})
|
|
499
510
|
break
|
|
500
511
|
default:
|
|
501
|
-
console.warn('Map type not set')
|
|
512
|
+
console.warn('COVE: Map type not set') // eslint-disable-line
|
|
502
513
|
break
|
|
503
514
|
}
|
|
504
515
|
break
|
|
@@ -733,8 +744,14 @@ const EditorPanel = props => {
|
|
|
733
744
|
}
|
|
734
745
|
})
|
|
735
746
|
break
|
|
747
|
+
case 'filterBehavior':
|
|
748
|
+
setState({
|
|
749
|
+
...state,
|
|
750
|
+
filterBehavior: value
|
|
751
|
+
})
|
|
752
|
+
break
|
|
736
753
|
default:
|
|
737
|
-
console.warn(`Did not recognize editor property.`)
|
|
754
|
+
console.warn(`COVE: Did not recognize editor property.`) // eslint-disable-line
|
|
738
755
|
break
|
|
739
756
|
}
|
|
740
757
|
}
|
|
@@ -857,6 +874,10 @@ const EditorPanel = props => {
|
|
|
857
874
|
newFilters.splice(idx, 1)
|
|
858
875
|
}
|
|
859
876
|
break
|
|
877
|
+
case 'filterStyle':
|
|
878
|
+
newFilters[idx] = { ...newFilters[idx] }
|
|
879
|
+
newFilters[idx].filterStyle = value
|
|
880
|
+
break
|
|
860
881
|
case 'columnName':
|
|
861
882
|
newFilters[idx] = { ...newFilters[idx] }
|
|
862
883
|
newFilters[idx].columnName = value
|
|
@@ -907,6 +928,33 @@ const EditorPanel = props => {
|
|
|
907
928
|
})
|
|
908
929
|
}
|
|
909
930
|
|
|
931
|
+
const MapFilters = () => {
|
|
932
|
+
return (
|
|
933
|
+
<>
|
|
934
|
+
<label>
|
|
935
|
+
Filter Behavior
|
|
936
|
+
<select
|
|
937
|
+
value={state.filterBehavior}
|
|
938
|
+
onChange={e => {
|
|
939
|
+
setState({
|
|
940
|
+
...state,
|
|
941
|
+
filterBehavior: e.target.value
|
|
942
|
+
})
|
|
943
|
+
}}
|
|
944
|
+
>
|
|
945
|
+
<option key='Apply Button' value='Apply Button'>
|
|
946
|
+
Apply Button
|
|
947
|
+
</option>
|
|
948
|
+
<option key='Filter Change' value='Filter Change'>
|
|
949
|
+
Filter Change
|
|
950
|
+
</option>
|
|
951
|
+
</select>
|
|
952
|
+
</label>
|
|
953
|
+
{filtersJSX}
|
|
954
|
+
</>
|
|
955
|
+
)
|
|
956
|
+
}
|
|
957
|
+
|
|
910
958
|
const removeAdditionalColumn = columnName => {
|
|
911
959
|
const newColumns = state.columns
|
|
912
960
|
|
|
@@ -1008,7 +1056,7 @@ const EditorPanel = props => {
|
|
|
1008
1056
|
if (!isReversed && state.color.endsWith('reverse')) {
|
|
1009
1057
|
paletteName = state.color.slice(0, -7)
|
|
1010
1058
|
}
|
|
1011
|
-
if(paletteName){
|
|
1059
|
+
if (paletteName) {
|
|
1012
1060
|
handleEditorChanges('color', paletteName)
|
|
1013
1061
|
}
|
|
1014
1062
|
}, [isReversed])
|
|
@@ -1140,99 +1188,106 @@ const EditorPanel = props => {
|
|
|
1140
1188
|
usedFilterColumns[filter.columnName] = true
|
|
1141
1189
|
}
|
|
1142
1190
|
|
|
1143
|
-
const filterOptions = [
|
|
1144
|
-
{
|
|
1145
|
-
label: 'Ascending Alphanumeric',
|
|
1146
|
-
value: 'asc'
|
|
1147
|
-
},
|
|
1148
|
-
{
|
|
1149
|
-
label: 'Descending Alphanumeric',
|
|
1150
|
-
value: 'desc'
|
|
1151
|
-
},
|
|
1152
|
-
{
|
|
1153
|
-
label: 'Custom',
|
|
1154
|
-
value: 'cust'
|
|
1155
|
-
}
|
|
1156
|
-
]
|
|
1157
|
-
|
|
1158
1191
|
return (
|
|
1159
|
-
|
|
1160
|
-
<
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
e
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
>
|
|
1167
|
-
Remove
|
|
1168
|
-
</button>
|
|
1169
|
-
<TextField value={state.filters[index].label} section='filters' subsection={index} fieldName='label' label='Label' updateField={updateField} />
|
|
1170
|
-
<label>
|
|
1171
|
-
<span className='edit-label column-heading'>
|
|
1172
|
-
Filter Column
|
|
1173
|
-
<Tooltip style={{ textTransform: 'none' }}>
|
|
1174
|
-
<Tooltip.Target>
|
|
1175
|
-
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
1176
|
-
</Tooltip.Target>
|
|
1177
|
-
<Tooltip.Content>
|
|
1178
|
-
<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>
|
|
1179
|
-
</Tooltip.Content>
|
|
1180
|
-
</Tooltip>
|
|
1181
|
-
</span>
|
|
1182
|
-
<select
|
|
1183
|
-
value={filter.columnName}
|
|
1184
|
-
onChange={event => {
|
|
1185
|
-
changeFilter(index, 'columnName', event.target.value)
|
|
1192
|
+
<>
|
|
1193
|
+
<fieldset className='edit-block' key={`filter-${index}`}>
|
|
1194
|
+
<button
|
|
1195
|
+
className='remove-column'
|
|
1196
|
+
onClick={e => {
|
|
1197
|
+
e.preventDefault()
|
|
1198
|
+
changeFilter(index, 'remove')
|
|
1186
1199
|
}}
|
|
1187
1200
|
>
|
|
1188
|
-
|
|
1189
|
-
</
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1201
|
+
Remove
|
|
1202
|
+
</button>
|
|
1203
|
+
<TextField value={state.filters[index].label} section='filters' subsection={index} fieldName='label' label='Label' updateField={updateField} />
|
|
1204
|
+
<label>
|
|
1205
|
+
<span className='edit-label column-heading'>
|
|
1206
|
+
Filter Column
|
|
1207
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
1208
|
+
<Tooltip.Target>
|
|
1209
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
1210
|
+
</Tooltip.Target>
|
|
1211
|
+
<Tooltip.Content>
|
|
1212
|
+
<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>
|
|
1213
|
+
</Tooltip.Content>
|
|
1214
|
+
</Tooltip>
|
|
1215
|
+
</span>
|
|
1216
|
+
<select
|
|
1217
|
+
value={filter.columnName}
|
|
1218
|
+
onChange={event => {
|
|
1219
|
+
changeFilter(index, 'columnName', event.target.value)
|
|
1220
|
+
}}
|
|
1221
|
+
>
|
|
1222
|
+
{columnsOptions.filter(({ key }) => undefined === usedFilterColumns[key] || filter.columnName === key)}
|
|
1223
|
+
</select>
|
|
1224
|
+
</label>
|
|
1225
|
+
|
|
1226
|
+
{/* COMING SOON: 4.23.5: FILTER STYLES */}
|
|
1227
|
+
|
|
1228
|
+
{/* <label>
|
|
1229
|
+
<span className='edit-filterOrder column-heading'>Filter Style</span>
|
|
1230
|
+
<select
|
|
1231
|
+
value={filter.filterStyle}
|
|
1232
|
+
onChange={e => {
|
|
1233
|
+
changeFilter(index, 'filterStyle', e.target.value)
|
|
1234
|
+
}}
|
|
1235
|
+
>
|
|
1236
|
+
{filterStyleOptions.map((option, index) => {
|
|
1237
|
+
return (
|
|
1238
|
+
<option value={option} key={`filter-${option}--${index}`}>
|
|
1239
|
+
{option}
|
|
1240
|
+
</option>
|
|
1241
|
+
)
|
|
1242
|
+
})}
|
|
1243
|
+
</select>
|
|
1244
|
+
</label> */}
|
|
1245
|
+
|
|
1246
|
+
<label>
|
|
1247
|
+
<span className='edit-filterOrder column-heading'>Filter Order</span>
|
|
1248
|
+
<select
|
|
1249
|
+
value={filter.order}
|
|
1250
|
+
onChange={e => {
|
|
1251
|
+
changeFilter(index, 'filterOrder', e.target.value)
|
|
1252
|
+
changeFilterActive(index, filter.values[0])
|
|
1253
|
+
}}
|
|
1254
|
+
>
|
|
1255
|
+
{filterOrderOptions.map((option, index) => {
|
|
1256
|
+
return (
|
|
1257
|
+
<option value={option.value} key={`filter-${index}`}>
|
|
1258
|
+
{option.label}
|
|
1259
|
+
</option>
|
|
1260
|
+
)
|
|
1261
|
+
})}
|
|
1262
|
+
</select>
|
|
1263
|
+
</label>
|
|
1264
|
+
|
|
1265
|
+
{filter.order === 'cust' && (
|
|
1266
|
+
<DragDropContext onDragEnd={({ source, destination }) => handleFilterOrder(source.index, destination.index, index, state.filters[index])}>
|
|
1267
|
+
<Droppable droppableId='filter_order'>
|
|
1268
|
+
{provided => (
|
|
1269
|
+
<ul {...provided.droppableProps} className='sort-list' ref={provided.innerRef} style={{ marginTop: '1em' }}>
|
|
1270
|
+
{state.filters[index]?.values.map((value, index) => {
|
|
1271
|
+
return (
|
|
1272
|
+
<Draggable key={value} draggableId={`draggableFilter-${value}`} index={index}>
|
|
1273
|
+
{(provided, snapshot) => (
|
|
1274
|
+
<li>
|
|
1275
|
+
<div className={snapshot.isDragging ? 'currently-dragging' : ''} style={getItemStyle(snapshot.isDragging, provided.draggableProps.style, sortableItemStyles)} ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
|
|
1276
|
+
{value}
|
|
1277
|
+
</div>
|
|
1278
|
+
</li>
|
|
1279
|
+
)}
|
|
1280
|
+
</Draggable>
|
|
1281
|
+
)
|
|
1282
|
+
})}
|
|
1283
|
+
{provided.placeholder}
|
|
1284
|
+
</ul>
|
|
1285
|
+
)}
|
|
1286
|
+
</Droppable>
|
|
1287
|
+
</DragDropContext>
|
|
1288
|
+
)}
|
|
1289
|
+
</fieldset>
|
|
1290
|
+
</>
|
|
1236
1291
|
)
|
|
1237
1292
|
})
|
|
1238
1293
|
|
|
@@ -1315,6 +1370,24 @@ const EditorPanel = props => {
|
|
|
1315
1370
|
)
|
|
1316
1371
|
}
|
|
1317
1372
|
|
|
1373
|
+
const isLoadedFromUrl = state?.dataKey?.includes('http://') || state?.dataKey?.includes('https://')
|
|
1374
|
+
|
|
1375
|
+
// if isDebug = true, then try to set the Geography Col and Data col to reduce clicking
|
|
1376
|
+
const setGeoColumn = () => {
|
|
1377
|
+
// only for debug mode
|
|
1378
|
+
let geoColFound = columnsInData.includes(state.columns.geo.name)
|
|
1379
|
+
if (undefined !== isDebug && isDebug && !geoColFound) {
|
|
1380
|
+
// then try to set the x axis to appropriate value so we dont have to manually do it
|
|
1381
|
+
let mapcols = columnsInData[0]
|
|
1382
|
+
if (mapcols !== '') editColumn('geo', 'name', mapcols)
|
|
1383
|
+
|
|
1384
|
+
if (!state.columns.hasOwnProperty('primary') || undefined === state.columns.primary.name || '' === state.columns.primary.name || !state.columns.primary.name) {
|
|
1385
|
+
editColumn('primary', 'name', columnsInData[1]) // blindly picks first value col
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (isDebug) setGeoColumn()
|
|
1390
|
+
|
|
1318
1391
|
return (
|
|
1319
1392
|
<ErrorBoundary component='EditorPanel'>
|
|
1320
1393
|
{state?.runtime?.editorErrorMessage.length > 0 && <Error />}
|
|
@@ -2131,6 +2204,7 @@ const EditorPanel = props => {
|
|
|
2131
2204
|
</label>
|
|
2132
2205
|
)}
|
|
2133
2206
|
{/* always show */}
|
|
2207
|
+
{/*
|
|
2134
2208
|
<label className='checkbox'>
|
|
2135
2209
|
<input
|
|
2136
2210
|
type='checkbox'
|
|
@@ -2140,7 +2214,7 @@ const EditorPanel = props => {
|
|
|
2140
2214
|
}}
|
|
2141
2215
|
/>
|
|
2142
2216
|
<span className='edit-label'>Show Special Classes Last</span>
|
|
2143
|
-
</label>
|
|
2217
|
+
</label> */}
|
|
2144
2218
|
{'category' !== legend.type && (
|
|
2145
2219
|
<label className='checkbox'>
|
|
2146
2220
|
<input type='checkbox' checked={legend.separateZero || false} onChange={event => handleEditorChanges('separateZero', event.target.checked)} />
|
|
@@ -2158,7 +2232,6 @@ const EditorPanel = props => {
|
|
|
2158
2232
|
</label>
|
|
2159
2233
|
)}
|
|
2160
2234
|
{/* Temp Checkbox */}
|
|
2161
|
-
|
|
2162
2235
|
{state.legend.type === 'equalnumber' && (
|
|
2163
2236
|
<label className='checkbox mt-4'>
|
|
2164
2237
|
<input
|
|
@@ -2322,7 +2395,7 @@ const EditorPanel = props => {
|
|
|
2322
2395
|
<AccordionItemButton>Filters</AccordionItemButton>
|
|
2323
2396
|
</AccordionItemHeading>
|
|
2324
2397
|
<AccordionItemPanel>
|
|
2325
|
-
{filtersJSX.length > 0 ?
|
|
2398
|
+
{filtersJSX.length > 0 ? <MapFilters /> : <p style={{ textAlign: 'center' }}>There are currently no filters.</p>}
|
|
2326
2399
|
<button
|
|
2327
2400
|
className={'btn full-width'}
|
|
2328
2401
|
onClick={event => {
|
|
@@ -2370,11 +2443,11 @@ const EditorPanel = props => {
|
|
|
2370
2443
|
handleEditorChanges('showDataTable', event.target.checked)
|
|
2371
2444
|
}}
|
|
2372
2445
|
/>
|
|
2373
|
-
<span className='edit-label'>
|
|
2374
|
-
Show Table
|
|
2446
|
+
<span className='edit-label column-heading'>
|
|
2447
|
+
Show Data Table
|
|
2375
2448
|
<Tooltip style={{ textTransform: 'none' }}>
|
|
2376
2449
|
<Tooltip.Target>
|
|
2377
|
-
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
2450
|
+
<Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
|
|
2378
2451
|
</Tooltip.Target>
|
|
2379
2452
|
<Tooltip.Content>
|
|
2380
2453
|
<p>Data tables are required for 508 compliance. When choosing to hide this data table, replace with your own version.</p>
|
|
@@ -2440,16 +2513,30 @@ const EditorPanel = props => {
|
|
|
2440
2513
|
/>
|
|
2441
2514
|
<span className='edit-label'>Map loads with data table expanded</span>
|
|
2442
2515
|
</label>
|
|
2443
|
-
|
|
2444
|
-
<
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2516
|
+
{isDashboard && (
|
|
2517
|
+
<label className='checkbox'>
|
|
2518
|
+
<input
|
|
2519
|
+
type='checkbox'
|
|
2520
|
+
checked={state.table.showDataTableLink}
|
|
2521
|
+
onChange={event => {
|
|
2522
|
+
handleEditorChanges('toggleDataTableLink', event.target.checked)
|
|
2523
|
+
}}
|
|
2524
|
+
/>
|
|
2525
|
+
<span className='edit-label'>Show Data Table Name & Link</span>
|
|
2526
|
+
</label>
|
|
2527
|
+
)}
|
|
2528
|
+
{isLoadedFromUrl && (
|
|
2529
|
+
<label className='checkbox'>
|
|
2530
|
+
<input
|
|
2531
|
+
type='checkbox'
|
|
2532
|
+
checked={state.table.showDownloadUrl}
|
|
2533
|
+
onChange={event => {
|
|
2534
|
+
handleEditorChanges('toggleDataUrl', event.target.checked)
|
|
2535
|
+
}}
|
|
2536
|
+
/>
|
|
2537
|
+
<span className='edit-label'>Show URL to Automatically Updated Data</span>
|
|
2538
|
+
</label>
|
|
2539
|
+
)}
|
|
2453
2540
|
<label className='checkbox'>
|
|
2454
2541
|
<input
|
|
2455
2542
|
type='checkbox'
|
|
@@ -2458,7 +2545,7 @@ const EditorPanel = props => {
|
|
|
2458
2545
|
handleEditorChanges('toggleDownloadButton', event.target.checked)
|
|
2459
2546
|
}}
|
|
2460
2547
|
/>
|
|
2461
|
-
<span className='edit-label'>
|
|
2548
|
+
<span className='edit-label'>Show Download CSV Link</span>
|
|
2462
2549
|
</label>
|
|
2463
2550
|
{/* <label className='checkbox'>
|
|
2464
2551
|
<input
|
|
@@ -2722,6 +2809,45 @@ const EditorPanel = props => {
|
|
|
2722
2809
|
))}
|
|
2723
2810
|
</AccordionItemPanel>
|
|
2724
2811
|
</AccordionItem>
|
|
2812
|
+
{/* HIDING FOR 4.23.4 */}
|
|
2813
|
+
{/* <AccordionItem>
|
|
2814
|
+
<AccordionItemHeading>
|
|
2815
|
+
<AccordionItemButton>Custom Map Layers</AccordionItemButton>
|
|
2816
|
+
</AccordionItemHeading>
|
|
2817
|
+
<AccordionItemPanel>
|
|
2818
|
+
{state.map.layers.length === 0 && <p>There are currently no layers.</p>}
|
|
2819
|
+
|
|
2820
|
+
{state.map.layers.map((layer, index) => {
|
|
2821
|
+
return (
|
|
2822
|
+
<>
|
|
2823
|
+
<Accordion allowZeroExpanded>
|
|
2824
|
+
<AccordionItem className='series-item map-layers-list'>
|
|
2825
|
+
<AccordionItemHeading className='series-item__title map-layers-list--title'>
|
|
2826
|
+
<AccordionItemButton>{`Layer ${index + 1}: ${layer.name}`}</AccordionItemButton>
|
|
2827
|
+
</AccordionItemHeading>
|
|
2828
|
+
<AccordionItemPanel>
|
|
2829
|
+
<div className='map-layers-panel'>
|
|
2830
|
+
<label htmlFor='layerName'>Layer Name:</label>
|
|
2831
|
+
<input type='text' name='layerName' value={layer.name} onChange={e => handleMapLayerName(e, index)} />
|
|
2832
|
+
<label htmlFor='layerFilename'>File:</label>
|
|
2833
|
+
<input type='text' name='layerFilename' value={layer.url} onChange={e => handleMapLayerUrl(e, index)} />
|
|
2834
|
+
<label htmlFor='layerNamespace'>TOPOJSON Namespace:</label>
|
|
2835
|
+
<input type='text' name='layerNamespace' value={layer.namespace} onChange={e => handleMapLayerNamespace(e, index)} />
|
|
2836
|
+
<label htmlFor='layerTooltip'>Tooltip:</label>
|
|
2837
|
+
<textarea name='layerTooltip' value={layer.tooltip} onChange={e => handleMapLayerTooltip(e, index)}></textarea>
|
|
2838
|
+
<button onClick={e => handleRemoveLayer(e, index)}>Remove Layer</button>
|
|
2839
|
+
</div>
|
|
2840
|
+
</AccordionItemPanel>
|
|
2841
|
+
</AccordionItem>
|
|
2842
|
+
</Accordion>
|
|
2843
|
+
</>
|
|
2844
|
+
)
|
|
2845
|
+
})}
|
|
2846
|
+
<button className={'btn full-width'} onClick={handleAddLayer}>
|
|
2847
|
+
Add Map Layer
|
|
2848
|
+
</button>
|
|
2849
|
+
</AccordionItemPanel>
|
|
2850
|
+
</AccordionItem> */}
|
|
2725
2851
|
</Accordion>
|
|
2726
2852
|
</form>
|
|
2727
2853
|
<AdvancedEditor loadConfig={loadConfig} state={state} convertStateToConfig={convertStateToConfig} />
|
|
@@ -2,7 +2,7 @@ import React, { useState, useEffect, memo } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import { jsx } from '@emotion/react'
|
|
4
4
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
5
|
-
import { geoCentroid } from 'd3-geo'
|
|
5
|
+
import { geoCentroid, geoPath } from 'd3-geo'
|
|
6
6
|
import { feature } from 'topojson-client'
|
|
7
7
|
import topoJSON from '../data/us-topo.json'
|
|
8
8
|
import hexTopoJSON from '../data/us-hex-topo.json'
|
|
@@ -11,6 +11,9 @@ import chroma from 'chroma-js'
|
|
|
11
11
|
import CityList from './CityList'
|
|
12
12
|
import BubbleList from './BubbleList'
|
|
13
13
|
import { supportedCities, supportedStates } from '../data/supported-geos'
|
|
14
|
+
import { geoAlbersUsa } from 'd3-composite-projections'
|
|
15
|
+
|
|
16
|
+
import useMapLayers from '../hooks/useMapLayers'
|
|
14
17
|
|
|
15
18
|
const { features: unitedStates } = feature(topoJSON, topoJSON.objects.states)
|
|
16
19
|
const { features: unitedStatesHex } = feature(hexTopoJSON, hexTopoJSON.objects.states)
|
|
@@ -118,7 +121,7 @@ const UsaMap = props => {
|
|
|
118
121
|
const territoriesList = territoriesKeys.filter(key => data[key])
|
|
119
122
|
setTerritoriesData(territoriesList)
|
|
120
123
|
}
|
|
121
|
-
}, [data, state.general.territoriesAlwaysShow])
|
|
124
|
+
}, [data, state.general.territoriesAlwaysShow])
|
|
122
125
|
|
|
123
126
|
const geoStrokeColor = state.general.geoBorderColor === 'darkGray' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.7)'
|
|
124
127
|
|
|
@@ -171,10 +174,7 @@ const UsaMap = props => {
|
|
|
171
174
|
}
|
|
172
175
|
}
|
|
173
176
|
|
|
174
|
-
return <Shape key={label} label={label} css={styles} text={styles.color} strokeWidth={1.5} textColor={textColor} onClick={() => geoClickHandler(territory, territoryData)}
|
|
175
|
-
data-tooltip-id="tooltip"
|
|
176
|
-
data-tooltip-html={toolTip}
|
|
177
|
-
/>
|
|
177
|
+
return <Shape key={label} label={label} css={styles} text={styles.color} strokeWidth={1.5} textColor={textColor} onClick={() => geoClickHandler(territory, territoryData)} data-tooltip-id='tooltip' data-tooltip-html={toolTip} />
|
|
178
178
|
}
|
|
179
179
|
})
|
|
180
180
|
|
|
@@ -221,6 +221,9 @@ const UsaMap = props => {
|
|
|
221
221
|
)
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
let pathGenerator = geoPath().projection(geoAlbersUsa().translate(translate))
|
|
225
|
+
const { pathArray } = useMapLayers(state, '', pathGenerator)
|
|
226
|
+
|
|
224
227
|
// Constructs and displays markup for all geos on the map (except territories right now)
|
|
225
228
|
const constructGeoJsx = (geographies, projection) => {
|
|
226
229
|
let showLabel = state.general.displayStateLabels
|
|
@@ -296,11 +299,7 @@ const UsaMap = props => {
|
|
|
296
299
|
|
|
297
300
|
return (
|
|
298
301
|
<g data-name={geoName} key={key}>
|
|
299
|
-
<g className='geo-group' css={styles} onClick={() => geoClickHandler(geoDisplayName, geoData)}
|
|
300
|
-
id={geoName}
|
|
301
|
-
data-tooltip-id="tooltip"
|
|
302
|
-
data-tooltip-html={tooltip}
|
|
303
|
-
>
|
|
302
|
+
<g className='geo-group' css={styles} onClick={() => geoClickHandler(geoDisplayName, geoData)} id={geoName} data-tooltip-id='tooltip' data-tooltip-html={tooltip}>
|
|
304
303
|
<path tabIndex={-1} className='single-geo' strokeWidth={1.3} d={path} />
|
|
305
304
|
{(isHex || showLabel) && geoLabel(geo, legendColors[0], projection)}
|
|
306
305
|
</g>
|
|
@@ -344,6 +343,13 @@ const UsaMap = props => {
|
|
|
344
343
|
geosJsx.push(<BubbleList key='bubbles' data={state.data} runtimeData={data} state={state} projection={projection} applyLegendToRow={applyLegendToRow} applyTooltipsToGeo={applyTooltipsToGeo} displayGeoName={displayGeoName} />)
|
|
345
344
|
}
|
|
346
345
|
|
|
346
|
+
// })
|
|
347
|
+
|
|
348
|
+
if (pathArray.length > 0) {
|
|
349
|
+
pathArray.map(layer => {
|
|
350
|
+
return geosJsx.push(layer)
|
|
351
|
+
})
|
|
352
|
+
}
|
|
347
353
|
return geosJsx
|
|
348
354
|
}
|
|
349
355
|
|
|
@@ -25,7 +25,6 @@ export default {
|
|
|
25
25
|
hideGeoColumnInTooltip: false,
|
|
26
26
|
hidePrimaryColumnInTooltip: false
|
|
27
27
|
},
|
|
28
|
-
|
|
29
28
|
type: 'map',
|
|
30
29
|
color: 'pinkpurple',
|
|
31
30
|
columns: {
|
|
@@ -67,7 +66,8 @@ export default {
|
|
|
67
66
|
title: 'Data Table'
|
|
68
67
|
},
|
|
69
68
|
table: {
|
|
70
|
-
showDownloadUrl: false
|
|
69
|
+
showDownloadUrl: false,
|
|
70
|
+
showDataTableLink: true
|
|
71
71
|
},
|
|
72
72
|
tooltips: {
|
|
73
73
|
appearanceType: 'hover',
|
|
@@ -85,5 +85,9 @@ export default {
|
|
|
85
85
|
geoCodeCircleSize: 2,
|
|
86
86
|
showBubbleZeros: false
|
|
87
87
|
},
|
|
88
|
-
mapPosition: { coordinates: [0, 30], zoom: 1 }
|
|
88
|
+
mapPosition: { coordinates: [0, 30], zoom: 1 },
|
|
89
|
+
map: {
|
|
90
|
+
layers: []
|
|
91
|
+
},
|
|
92
|
+
filterBehavior: 'Filter Change'
|
|
89
93
|
}
|