@cdc/map 4.25.6-1 → 4.25.7-2
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 +52348 -30886
- package/index.html +9 -0
- package/package.json +3 -3
- package/src/CdcMap.tsx +5 -0
- package/src/CdcMapComponent.tsx +31 -4
- package/src/components/EditorPanel/components/EditorPanel.tsx +52 -51
- package/src/components/Legend/components/Legend.tsx +36 -35
- package/src/helpers/generateRuntimeLegend.ts +1 -1
- package/src/scss/main.scss +1 -1
package/index.html
CHANGED
|
@@ -34,6 +34,15 @@
|
|
|
34
34
|
|
|
35
35
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
36
36
|
<script type="module" src="./src/index.jsx"></script>
|
|
37
|
+
<!-- add cove_loaded listener -->
|
|
38
|
+
<!-- <script>
|
|
39
|
+
document.addEventListener('cove_loaded', function () {
|
|
40
|
+
// This is a temporary fix to ensure the map loads after Cove has loaded
|
|
41
|
+
// and the cdc-map-outer-container is available.
|
|
42
|
+
debugger
|
|
43
|
+
console.log('Cove has loaded, initializing map...');
|
|
44
|
+
});
|
|
45
|
+
</script> -->
|
|
37
46
|
</body>
|
|
38
47
|
|
|
39
48
|
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/map",
|
|
3
|
-
"version": "4.25.
|
|
3
|
+
"version": "4.25.7-2",
|
|
4
4
|
"description": "React component for visualizing tabular data on a map of the United States or the world.",
|
|
5
5
|
"moduleName": "CdcMap",
|
|
6
6
|
"main": "dist/cdcmap",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"license": "Apache-2.0",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@cdc/core": "^4.25.
|
|
28
|
+
"@cdc/core": "^4.25.7",
|
|
29
29
|
"@googlemaps/markerclusterer": "^2.5.3",
|
|
30
30
|
"@hello-pangea/dnd": "^16.2.0",
|
|
31
31
|
"@react-google-maps/api": "^2.20.6",
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"react": "^18.2.0",
|
|
46
46
|
"react-dom": "^18.2.0"
|
|
47
47
|
},
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "432a2d1acab22915fafe793cb9da1f10318ff793"
|
|
49
49
|
}
|
package/src/CdcMap.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import initialState from './data/initial-state'
|
|
|
6
6
|
import coveUpdateWorker from '@cdc/core/helpers/coveUpdateWorker'
|
|
7
7
|
import { addUIDs, validateFipsCodeLength } from './helpers'
|
|
8
8
|
import EditorContext from '@cdc/editor/src/ConfigContext'
|
|
9
|
+
import { extractCoveData, updateVegaData } from '@cdc/core/helpers/vegaConfig'
|
|
9
10
|
import { MapConfig } from './types/MapConfig'
|
|
10
11
|
import _, { set } from 'lodash'
|
|
11
12
|
|
|
@@ -53,6 +54,10 @@ const CdcMap: React.FC<CdcMapProps> = ({
|
|
|
53
54
|
if (newState.dataUrl) {
|
|
54
55
|
let newData = await fetchRemoteData(newState.dataUrl, 'map')
|
|
55
56
|
|
|
57
|
+
if (newState.vegaConfig) {
|
|
58
|
+
newData = extractCoveData(updateVegaData(newState.vegaConfig, newData))
|
|
59
|
+
}
|
|
60
|
+
|
|
56
61
|
if (newData && newState.dataDescription) {
|
|
57
62
|
newData = transform.autoStandardize(newData)
|
|
58
63
|
newData = transform.developerStandardize(newData, newState.dataDescription)
|
package/src/CdcMapComponent.tsx
CHANGED
|
@@ -242,12 +242,39 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
245
|
+
/**
|
|
246
|
+
* Publishes 'cove_loaded' only after the map SVG is rendered in the DOM.
|
|
247
|
+
* Checks immediately, then uses a MutationObserver as a fallback for async rendering.
|
|
248
|
+
* Update the mapSvg ref if the map container changes.
|
|
249
|
+
*/
|
|
250
|
+
const observeMapSvgLoaded = (mapSvgRef, config, coveLoadedHasRan, publish, dispatch) => {
|
|
251
|
+
// Immediate check in case SVG is already present
|
|
252
|
+
const svgEl = mapSvgRef.current?.querySelector('svg')
|
|
253
|
+
if (svgEl && svgEl.childNodes.length > 0) {
|
|
254
|
+
publish('cove_loaded', { config })
|
|
248
255
|
dispatch({ type: 'SET_COVE_LOADED_HAS_RAN', payload: true })
|
|
256
|
+
return () => {}
|
|
249
257
|
}
|
|
250
|
-
|
|
258
|
+
|
|
259
|
+
// Fallback to observer for async SVG rendering
|
|
260
|
+
const observer = new MutationObserver(() => {
|
|
261
|
+
const svgEl = mapSvgRef.current?.querySelector('svg')
|
|
262
|
+
if (svgEl && svgEl.childNodes.length > 0) {
|
|
263
|
+
publish('cove_loaded', { config })
|
|
264
|
+
dispatch({ type: 'SET_COVE_LOADED_HAS_RAN', payload: true })
|
|
265
|
+
observer.disconnect()
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
observer.observe(mapSvgRef.current, { childList: true, subtree: true })
|
|
270
|
+
|
|
271
|
+
return () => observer.disconnect()
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
useEffect(() => {
|
|
275
|
+
if (!mapSvg.current || coveLoadedHasRan) return
|
|
276
|
+
return observeMapSvgLoaded(mapSvg, config, coveLoadedHasRan, publish, dispatch)
|
|
277
|
+
}, [config, loading, runtimeData, coveLoadedHasRan])
|
|
251
278
|
|
|
252
279
|
useEffect(() => {
|
|
253
280
|
// UID
|
|
@@ -1104,8 +1104,9 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
1104
1104
|
</span>
|
|
1105
1105
|
<ul className='geo-buttons d-grid' style={{ gridTemplateColumns: 'repeat(2, 1fr)', gap: '8px' }}>
|
|
1106
1106
|
<button
|
|
1107
|
-
className={`${
|
|
1108
|
-
|
|
1107
|
+
className={`${
|
|
1108
|
+
config.general.geoType === 'us' || config.general.geoType === 'us-county' ? 'active' : ''
|
|
1109
|
+
} full-width`}
|
|
1109
1110
|
onClick={e => {
|
|
1110
1111
|
e.preventDefault()
|
|
1111
1112
|
handleEditorChanges('geoType', 'us')
|
|
@@ -3110,19 +3111,19 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
3110
3111
|
)}
|
|
3111
3112
|
{(config.general.geoType === 'world' ||
|
|
3112
3113
|
(config.general.geoType === 'us' && config.general.type === 'bubble')) && (
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3114
|
+
<label className='checkbox'>
|
|
3115
|
+
<input
|
|
3116
|
+
type='checkbox'
|
|
3117
|
+
checked={config.visual.showBubbleZeros}
|
|
3118
|
+
onChange={event => {
|
|
3119
|
+
const _newConfig = _.cloneDeep(config)
|
|
3120
|
+
_newConfig.visual.showBubbleZeros = event.target.checked
|
|
3121
|
+
setConfig(_newConfig)
|
|
3122
|
+
}}
|
|
3123
|
+
/>
|
|
3124
|
+
<span className='edit-label'>Show Data with Zero's on Bubble Map</span>
|
|
3125
|
+
</label>
|
|
3126
|
+
)}
|
|
3126
3127
|
{(config.general.geoType === 'world' || config.general.geoType === 'single-state') && (
|
|
3127
3128
|
<label className='checkbox'>
|
|
3128
3129
|
<input
|
|
@@ -3156,42 +3157,42 @@ const EditorPanel: React.FC<MapEditorPanelProps> = ({ datasets }) => {
|
|
|
3156
3157
|
{(config.general.geoType === 'us' ||
|
|
3157
3158
|
config.general.geoType === 'us-county' ||
|
|
3158
3159
|
config.general.geoType === 'world') && (
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3160
|
+
<>
|
|
3161
|
+
<label>
|
|
3162
|
+
<span className='edit-label'>Default City Style</span>
|
|
3163
|
+
<select
|
|
3164
|
+
value={config.visual.cityStyle || false}
|
|
3165
|
+
onChange={event => {
|
|
3166
|
+
handleEditorChanges('handleCityStyle', event.target.value)
|
|
3167
|
+
}}
|
|
3168
|
+
>
|
|
3169
|
+
<option value='circle'>Circle</option>
|
|
3170
|
+
<option value='pin'>Pin</option>
|
|
3171
|
+
<option value='square'>Square</option>
|
|
3172
|
+
<option value='triangle'>Triangle</option>
|
|
3173
|
+
<option value='diamond'>Diamond</option>
|
|
3174
|
+
<option value='star'>Star</option>
|
|
3175
|
+
</select>
|
|
3176
|
+
</label>
|
|
3177
|
+
<TextField
|
|
3178
|
+
value={config.visual.cityStyleLabel}
|
|
3179
|
+
section='visual'
|
|
3180
|
+
fieldName='cityStyleLabel'
|
|
3181
|
+
label='Label (Optional) '
|
|
3182
|
+
updateField={updateField}
|
|
3183
|
+
tooltip={
|
|
3184
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
3185
|
+
<Tooltip.Target>
|
|
3186
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
3187
|
+
</Tooltip.Target>
|
|
3188
|
+
<Tooltip.Content>
|
|
3189
|
+
<p>When a label is provided, the default city style will appear in the legend.</p>
|
|
3190
|
+
</Tooltip.Content>
|
|
3191
|
+
</Tooltip>
|
|
3192
|
+
}
|
|
3193
|
+
/>
|
|
3194
|
+
</>
|
|
3195
|
+
)}
|
|
3195
3196
|
{/* <AdditionalCityStyles /> */}
|
|
3196
3197
|
<>
|
|
3197
3198
|
{config.visual.additionalCityStyles.length > 0 &&
|
|
@@ -135,6 +135,7 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
135
135
|
}
|
|
136
136
|
}}
|
|
137
137
|
tabIndex={0}
|
|
138
|
+
role='button'
|
|
138
139
|
>
|
|
139
140
|
<LegendShape shape={config.legend.style === 'boxes' ? 'square' : 'circle'} fill={item.color} />
|
|
140
141
|
<span>{item.label}</span>
|
|
@@ -313,41 +314,41 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
313
314
|
|
|
314
315
|
{((config.visual.additionalCityStyles && config.visual.additionalCityStyles.some(c => c.label)) ||
|
|
315
316
|
config.visual.cityStyleLabel) && (
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
317
|
+
<>
|
|
318
|
+
<hr />
|
|
319
|
+
<div className={legendClasses.div.join(' ') || ''}>
|
|
320
|
+
{config.visual.cityStyleLabel && (
|
|
321
|
+
<div>
|
|
322
|
+
<svg>
|
|
323
|
+
<Group
|
|
324
|
+
top={
|
|
325
|
+
config.visual.cityStyle === 'pin' ? 19 : config.visual.cityStyle === 'triangle' ? 13 : 11
|
|
326
|
+
}
|
|
327
|
+
left={10}
|
|
328
|
+
>
|
|
329
|
+
{cityStyleShapes[config.visual.cityStyle.toLowerCase()]}
|
|
330
|
+
</Group>
|
|
331
|
+
</svg>
|
|
332
|
+
<p>{config.visual.cityStyleLabel}</p>
|
|
333
|
+
</div>
|
|
334
|
+
)}
|
|
335
|
+
|
|
336
|
+
{config.visual.additionalCityStyles.map(
|
|
337
|
+
({ shape, label }) =>
|
|
338
|
+
label && (
|
|
339
|
+
<div>
|
|
340
|
+
<svg>
|
|
341
|
+
<Group top={shape === 'Pin' ? 19 : shape === 'Triangle' ? 13 : 11} left={10}>
|
|
342
|
+
{cityStyleShapes[shape.toLowerCase()]}
|
|
343
|
+
</Group>
|
|
344
|
+
</svg>
|
|
345
|
+
<p>{label}</p>
|
|
346
|
+
</div>
|
|
347
|
+
)
|
|
348
|
+
)}
|
|
349
|
+
</div>
|
|
350
|
+
</>
|
|
351
|
+
)}
|
|
351
352
|
{runtimeLegend.disabledAmt > 0 && (
|
|
352
353
|
<Button className={legendClasses.showAllButton.join(' ')} onClick={handleReset}>
|
|
353
354
|
Show All
|
|
@@ -462,7 +462,7 @@ export const generateRuntimeLegend = (
|
|
|
462
462
|
if (!assigned) {
|
|
463
463
|
console.warn('Value not assigned to any range:', number, 'assigning to closest range')
|
|
464
464
|
let closestIndex = 0
|
|
465
|
-
let minDistance = Math.abs(number - (
|
|
465
|
+
let minDistance = Math.abs(number - (result.items[0].min + result.items[0].max) / 2)
|
|
466
466
|
|
|
467
467
|
for (let i = 1; i < result.items.length; i++) {
|
|
468
468
|
const midpoint = (result.items[i].min + result.items[i].max) / 2
|