@cdc/map 4.25.3 → 4.25.6
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/.idea/map.iml +12 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/dist/cdcmap.js +31254 -32242
- package/examples/hex-colors.json +3 -3
- package/examples/m2.json +32904 -0
- package/examples/private/test.json +470 -1457
- package/examples/private/{mmr.json → wastewatermap.json} +86 -115
- package/index.html +36 -63
- package/package.json +7 -19
- package/src/CdcMap.tsx +56 -1552
- package/src/CdcMapComponent.tsx +608 -0
- package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +10 -0
- package/src/_stories/CdcMap.Legend.stories.tsx +67 -0
- package/src/_stories/CdcMap.Table.stories.tsx +19 -0
- package/src/_stories/CdcMap.stories.tsx +12 -1
- package/src/_stories/UsaMap.NoData.stories.tsx +4 -4
- package/src/_stories/_mock/default-patterns.json +8 -5
- package/src/_stories/_mock/legend-bins.json +428 -0
- package/{examples/private/default-patterns.json → src/_stories/_mock/legends/legend-tests.json} +36 -131
- package/src/cdcMapComponent.styles.css +9 -0
- package/src/components/Annotation/Annotation.Draggable.tsx +27 -26
- package/src/components/Annotation/AnnotationDropdown.tsx +5 -6
- package/src/components/BubbleList.tsx +135 -49
- package/src/components/CityList.tsx +89 -87
- package/src/components/DataTable.tsx +8 -8
- package/src/components/EditorPanel/components/EditorPanel.tsx +823 -885
- package/src/components/EditorPanel/components/Error.tsx +9 -2
- package/src/components/EditorPanel/components/HexShapeSettings.tsx +127 -141
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +55 -86
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +89 -75
- package/src/components/EditorPanel/components/editorPanel.styles.css +95 -0
- package/src/components/Geo.tsx +9 -1
- package/src/components/GoogleMap/components/GoogleMap.tsx +1 -1
- package/src/components/Legend/components/Legend.tsx +92 -87
- package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +128 -0
- package/src/components/Legend/components/LegendGroup/legend.group.css +27 -0
- package/src/components/Legend/components/LegendItem.Hex.tsx +4 -1
- package/src/components/Legend/components/index.scss +74 -17
- package/src/components/Modal.tsx +17 -7
- package/src/components/NavigationMenu.tsx +11 -9
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +12 -8
- package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +4 -4
- package/src/components/UsaMap/components/TerritoriesSection.tsx +33 -10
- package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +12 -10
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +12 -14
- package/src/components/UsaMap/components/Territory/TerritoryShape.ts +2 -1
- package/src/components/UsaMap/components/UsaMap.County.tsx +138 -96
- package/src/components/UsaMap/components/UsaMap.Region.styles.css +72 -0
- package/src/components/UsaMap/components/UsaMap.Region.tsx +56 -103
- package/src/components/UsaMap/components/UsaMap.SingleState.styles.css +10 -0
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +65 -74
- package/src/components/UsaMap/components/UsaMap.State.tsx +112 -91
- package/src/components/UsaMap/helpers/map.ts +1 -1
- package/src/components/UsaMap/helpers/shapes.ts +20 -7
- package/src/components/WorldMap/WorldMap.tsx +64 -118
- package/src/components/WorldMap/worldMap.styles.css +28 -0
- package/src/components/ZoomControls.tsx +15 -13
- package/src/components/zoomControls.styles.css +53 -0
- package/src/context.ts +17 -9
- package/src/data/initial-state.js +5 -2
- package/src/helpers/addUIDs.ts +150 -0
- package/src/helpers/applyColorToLegend.ts +39 -64
- package/src/helpers/applyLegendToRow.ts +51 -0
- package/src/helpers/colorDistributions.ts +12 -0
- package/src/helpers/constants.ts +44 -0
- package/src/helpers/displayGeoName.ts +9 -2
- package/src/helpers/formatLegendLocation.ts +3 -2
- package/src/helpers/generateColorsArray.ts +2 -1
- package/src/helpers/generateRuntimeData.ts +78 -0
- package/src/helpers/generateRuntimeFilters.ts +63 -0
- package/src/helpers/generateRuntimeLegend.ts +566 -0
- package/src/helpers/generateRuntimeLegendHash.ts +16 -15
- package/src/helpers/getColumnNames.ts +19 -0
- package/src/helpers/getMapContainerClasses.ts +23 -0
- package/src/helpers/getStatePicked.ts +8 -0
- package/src/helpers/handleMapTabbing.ts +31 -0
- package/src/helpers/hashObj.ts +1 -1
- package/src/helpers/index.ts +22 -0
- package/src/helpers/navigationHandler.ts +3 -3
- package/src/helpers/resetLegendToggles.ts +13 -0
- package/src/helpers/setBinNumbers.ts +5 -0
- package/src/helpers/sortSpecialClassesLast.ts +7 -0
- package/src/helpers/tests/getColumnNames.test.ts +52 -0
- package/src/helpers/titleCase.ts +1 -1
- package/src/helpers/toggleLegendActive.ts +25 -0
- package/src/hooks/useApplyTooltipsToGeo.tsx +51 -0
- package/src/hooks/useColumnsRequiredChecker.ts +51 -0
- package/src/hooks/useGeoClickHandler.ts +45 -0
- package/src/hooks/useLegendSeparators.ts +26 -0
- package/src/hooks/useMapLayers.tsx +34 -60
- package/src/hooks/useModal.ts +22 -0
- package/src/hooks/useResizeObserver.ts +4 -5
- package/src/hooks/useStateZoom.tsx +52 -75
- package/src/hooks/useTooltip.ts +2 -3
- package/src/index.jsx +3 -9
- package/src/scss/editor-panel.scss +3 -99
- package/src/scss/main.scss +1 -19
- package/src/scss/map.scss +15 -220
- package/src/store/map.actions.ts +46 -0
- package/src/store/map.reducer.ts +96 -0
- package/src/types/Annotations.ts +24 -0
- package/src/types/MapConfig.ts +23 -3
- package/src/types/MapContext.ts +36 -35
- package/src/types/Modal.ts +1 -0
- package/src/types/RuntimeData.ts +3 -0
- package/examples/private/DEV-9644.json +0 -184
- package/examples/private/DEV-9989.json +0 -229
- package/examples/private/ardi.json +0 -180
- package/examples/private/colors 2.json +0 -416
- package/examples/private/colors.json +0 -416
- package/examples/private/colors.json.zip +0 -0
- package/examples/private/customColors.json +0 -45348
- package/examples/test.json +0 -183
- package/src/helpers/closeModal.ts +0 -9
- package/src/scss/btn.scss +0 -69
- package/src/scss/filters.scss +0 -27
- package/src/scss/variables.scss +0 -1
- /package/src/hooks/{useActiveElement.js → useActiveElement.ts} +0 -0
|
@@ -1,37 +1,96 @@
|
|
|
1
|
-
import React, { useContext
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
2
|
import { scaleLinear } from 'd3-scale'
|
|
3
3
|
import { countryCoordinates } from '../data/country-coordinates'
|
|
4
4
|
import stateCoordinates from '../data/state-coordinates'
|
|
5
|
-
import ConfigContext from '../context'
|
|
5
|
+
import ConfigContext, { MapDispatchContext } from '../context'
|
|
6
|
+
import { type Coordinate, DataRow } from '../types/MapConfig'
|
|
7
|
+
import useApplyTooltipsToGeo from '../hooks/useApplyTooltipsToGeo'
|
|
8
|
+
import { applyLegendToRow } from '../helpers/applyLegendToRow'
|
|
9
|
+
import { displayGeoName, SVG_HEIGHT, SVG_WIDTH } from '../helpers'
|
|
10
|
+
import { geoMercator, geoAlbersUsa, type GeoProjection } from 'd3-geo'
|
|
11
|
+
import { getColumnNames } from '../helpers/getColumnNames'
|
|
12
|
+
import { MapContext } from '../types/MapContext'
|
|
13
|
+
import useGeoClickHandler from '../hooks/useGeoClickHandler'
|
|
6
14
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const hasBubblesWithZeroOnMap = state.visual.showBubbleZeros ? 0 : 1
|
|
12
|
-
// sort runtime data. Smaller bubbles should appear on top.
|
|
13
|
-
const sortedRuntimeData = Object.values(runtimeData).sort((a, b) => (a[state.columns.primary.name] < b[state.columns.primary.name] ? 1 : -1))
|
|
14
|
-
if (!sortedRuntimeData) return
|
|
15
|
+
type BubbleListProps = {
|
|
16
|
+
customProjection?: GeoProjection
|
|
17
|
+
}
|
|
15
18
|
|
|
19
|
+
export const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
20
|
+
const { config, tooltipId, legendMemo, legendSpecialClassLastMemo, setRuntimeData, runtimeData, runtimeLegend } =
|
|
21
|
+
useContext<MapContext>(ConfigContext)
|
|
22
|
+
const { columns, data, general, visual } = config
|
|
23
|
+
const { geoType, allowMapZoom } = general
|
|
24
|
+
const { minBubbleSize, maxBubbleSize, showBubbleZeros, extraBubbleBorder } = visual
|
|
25
|
+
const hasBubblesWithZeroOnMap = showBubbleZeros ? 0 : 1
|
|
16
26
|
const clickTolerance = 10
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
const dispatch = useContext(MapDispatchContext)
|
|
28
|
+
const { geoClickHandler } = useGeoClickHandler()
|
|
29
|
+
|
|
30
|
+
// hooks
|
|
31
|
+
const { applyTooltipsToGeo } = useApplyTooltipsToGeo()
|
|
32
|
+
const { primaryColumnName, geoColumnName } = getColumnNames(columns)
|
|
33
|
+
|
|
34
|
+
const maxDataValue = Math.max(...data.map(d => d[primaryColumnName]))
|
|
35
|
+
const size = scaleLinear().domain([hasBubblesWithZeroOnMap, maxDataValue]).range([minBubbleSize, maxBubbleSize])
|
|
36
|
+
|
|
37
|
+
const getProjection = () => {
|
|
38
|
+
try {
|
|
39
|
+
if (geoType === 'world') return geoMercator()
|
|
40
|
+
if (geoType === 'us') return geoAlbersUsa().translate([SVG_WIDTH / 2 + 15, SVG_HEIGHT / 2]) // translate is half of each svg x/y viewbox values
|
|
41
|
+
if (customProjection) return customProjection
|
|
42
|
+
throw new Error('No projection found in BubbleList component')
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.error(e)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const projection = getProjection()
|
|
19
49
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
50
|
+
const handleBubbleClick = (dataRow: DataRow) => {
|
|
51
|
+
if (!allowMapZoom) return
|
|
52
|
+
const newRuntimeData = data.filter(item => item[geoColumnName] === dataRow[geoColumnName])
|
|
53
|
+
const _filteredCountryCode = newRuntimeData[0]?.uid
|
|
54
|
+
if (!_filteredCountryCode) return null
|
|
55
|
+
const coordinates = countryCoordinates[_filteredCountryCode]
|
|
56
|
+
const long = coordinates[1]
|
|
57
|
+
const lat = coordinates[0]
|
|
58
|
+
const reversedCoordinates: Coordinate = [long, lat]
|
|
59
|
+
const filteredCountryObj = runtimeData[_filteredCountryCode]
|
|
60
|
+
const _tempRuntimeData = {
|
|
61
|
+
[_filteredCountryCode]: filteredCountryObj
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Zoom the map in...
|
|
65
|
+
dispatch({ type: 'SET_POSITION', payload: { coordinates: reversedCoordinates, zoom: 3 } })
|
|
66
|
+
|
|
67
|
+
// ...and show the data for the clicked country
|
|
68
|
+
setRuntimeData(_tempRuntimeData)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const sortedRuntimeData: DataRow = Object.values(runtimeData).sort((a, b) =>
|
|
72
|
+
a[primaryColumnName] < b[primaryColumnName] ? 1 : -1
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if (!sortedRuntimeData) return
|
|
76
|
+
|
|
77
|
+
if (geoType === 'world') {
|
|
78
|
+
return (
|
|
23
79
|
sortedRuntimeData &&
|
|
24
|
-
sortedRuntimeData.map(
|
|
80
|
+
sortedRuntimeData.map(country => {
|
|
25
81
|
let coordinates = countryCoordinates[country.uid]
|
|
26
82
|
|
|
27
83
|
if (!coordinates) return true
|
|
28
84
|
|
|
29
|
-
const countryName = displayGeoName(country[
|
|
85
|
+
const countryName = displayGeoName(country[geoColumnName])
|
|
30
86
|
const toolTip = applyTooltipsToGeo(countryName, country)
|
|
31
|
-
const legendColors = applyLegendToRow(country)
|
|
87
|
+
const legendColors = applyLegendToRow(country, config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
|
|
32
88
|
|
|
33
|
-
|
|
34
|
-
|
|
89
|
+
if (
|
|
90
|
+
(Math.floor(Number(country[primaryColumnName])) === 0 || country[primaryColumnName] === '') &&
|
|
91
|
+
!showBubbleZeros
|
|
92
|
+
)
|
|
93
|
+
return
|
|
35
94
|
|
|
36
95
|
let transform = `translate(${projection([coordinates[1], coordinates[0]])})`
|
|
37
96
|
|
|
@@ -45,9 +104,9 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
|
|
|
45
104
|
tabIndex={-1}
|
|
46
105
|
key={`circle-${countryName.replace(' ', '')}`}
|
|
47
106
|
className={`bubble country--${countryName}`}
|
|
48
|
-
cx={Number(projection(coordinates[1], coordinates[0])[0]) || 0}
|
|
107
|
+
cx={Number(projection(coordinates[1], coordinates[0])[0]) || 0}
|
|
49
108
|
cy={Number(projection(coordinates[1], coordinates[0])[1]) || 0}
|
|
50
|
-
r={Number(size(country[
|
|
109
|
+
r={Number(size(country[primaryColumnName]))}
|
|
51
110
|
fill={legendColors[0]}
|
|
52
111
|
stroke={legendColors[0]}
|
|
53
112
|
strokeWidth={1.25}
|
|
@@ -57,8 +116,15 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
|
|
|
57
116
|
pointerY = e.clientY
|
|
58
117
|
}}
|
|
59
118
|
onPointerUp={e => {
|
|
60
|
-
if (
|
|
61
|
-
|
|
119
|
+
if (
|
|
120
|
+
pointerX &&
|
|
121
|
+
pointerY &&
|
|
122
|
+
e.clientX > pointerX - clickTolerance &&
|
|
123
|
+
e.clientX < pointerX + clickTolerance &&
|
|
124
|
+
e.clientY > pointerY - clickTolerance &&
|
|
125
|
+
e.clientY < pointerY + clickTolerance
|
|
126
|
+
) {
|
|
127
|
+
handleBubbleClick(country)
|
|
62
128
|
pointerX = undefined
|
|
63
129
|
pointerY = undefined
|
|
64
130
|
}
|
|
@@ -69,14 +135,14 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
|
|
|
69
135
|
data-tooltip-html={toolTip}
|
|
70
136
|
/>
|
|
71
137
|
|
|
72
|
-
{
|
|
138
|
+
{extraBubbleBorder && (
|
|
73
139
|
<circle
|
|
74
140
|
tabIndex={-1}
|
|
75
141
|
key={`circle-${countryName.replace(' ', '')}`}
|
|
76
142
|
className='bubble'
|
|
77
|
-
cx={Number(projection(coordinates[1], coordinates[0])[0]) || 0}
|
|
143
|
+
cx={Number(projection(coordinates[1], coordinates[0])[0]) || 0}
|
|
78
144
|
cy={Number(projection(coordinates[1], coordinates[0])[1]) || 0}
|
|
79
|
-
r={Number(size(country[
|
|
145
|
+
r={Number(size(country[primaryColumnName])) + 1}
|
|
80
146
|
fill={'transparent'}
|
|
81
147
|
stroke={'white'}
|
|
82
148
|
strokeWidth={0.5}
|
|
@@ -85,8 +151,15 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
|
|
|
85
151
|
pointerY = e.clientY
|
|
86
152
|
}}
|
|
87
153
|
onPointerUp={e => {
|
|
88
|
-
if (
|
|
89
|
-
|
|
154
|
+
if (
|
|
155
|
+
pointerX &&
|
|
156
|
+
pointerY &&
|
|
157
|
+
e.clientX > pointerX - clickTolerance &&
|
|
158
|
+
e.clientX < pointerX + clickTolerance &&
|
|
159
|
+
e.clientY > pointerY - clickTolerance &&
|
|
160
|
+
e.clientY < pointerY + clickTolerance
|
|
161
|
+
) {
|
|
162
|
+
handleBubbleClick(country)
|
|
90
163
|
pointerX = undefined
|
|
91
164
|
pointerY = undefined
|
|
92
165
|
}
|
|
@@ -106,21 +179,20 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
|
|
|
106
179
|
</g>
|
|
107
180
|
)
|
|
108
181
|
})
|
|
109
|
-
|
|
182
|
+
)
|
|
110
183
|
}
|
|
111
184
|
|
|
112
|
-
if (
|
|
113
|
-
|
|
185
|
+
if (geoType === 'us') {
|
|
186
|
+
return (
|
|
114
187
|
sortedRuntimeData &&
|
|
115
|
-
sortedRuntimeData.map(
|
|
188
|
+
sortedRuntimeData.map(item => {
|
|
116
189
|
let stateData = stateCoordinates[item.uid]
|
|
117
|
-
|
|
118
|
-
if (Number(size(item[primaryKey])) === 0) return
|
|
190
|
+
if (Number(size(item[primaryColumnName])) === 0) return
|
|
119
191
|
|
|
120
|
-
if (item[
|
|
192
|
+
if (item[primaryColumnName] === null) item[primaryColumnName] = ''
|
|
121
193
|
|
|
122
|
-
|
|
123
|
-
|
|
194
|
+
if ((Math.floor(Number(item[primaryColumnName])) === 0 || item[primaryColumnName] === '') && !showBubbleZeros)
|
|
195
|
+
return
|
|
124
196
|
|
|
125
197
|
if (!stateData) return true
|
|
126
198
|
let longitude = Number(stateData.Longitude)
|
|
@@ -131,7 +203,7 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
|
|
|
131
203
|
|
|
132
204
|
stateName = displayGeoName(stateName)
|
|
133
205
|
const toolTip = applyTooltipsToGeo(stateName, item)
|
|
134
|
-
const legendColors = applyLegendToRow(item)
|
|
206
|
+
const legendColors = applyLegendToRow(item, config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
|
|
135
207
|
|
|
136
208
|
let transform = `translate(${projection([coordinates[1], coordinates[0]])})`
|
|
137
209
|
|
|
@@ -144,9 +216,9 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
|
|
|
144
216
|
tabIndex={-1}
|
|
145
217
|
key={`circle-${stateName.replace(' ', '')}`}
|
|
146
218
|
className='bubble'
|
|
147
|
-
cx={projection(coordinates)[0] || 0}
|
|
219
|
+
cx={projection(coordinates)[0] || 0}
|
|
148
220
|
cy={projection(coordinates)[1] || 0}
|
|
149
|
-
r={Number(size(item[
|
|
221
|
+
r={Number(size(item[primaryColumnName]))}
|
|
150
222
|
fill={legendColors[0]}
|
|
151
223
|
stroke={legendColors[0]}
|
|
152
224
|
strokeWidth={1.25}
|
|
@@ -156,8 +228,15 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
|
|
|
156
228
|
pointerY = e.clientY
|
|
157
229
|
}}
|
|
158
230
|
onPointerUp={e => {
|
|
159
|
-
if (
|
|
160
|
-
|
|
231
|
+
if (
|
|
232
|
+
pointerX &&
|
|
233
|
+
pointerY &&
|
|
234
|
+
e.clientX > pointerX - clickTolerance &&
|
|
235
|
+
e.clientX < pointerX + clickTolerance &&
|
|
236
|
+
e.clientY > pointerY - clickTolerance &&
|
|
237
|
+
e.clientY < pointerY + clickTolerance
|
|
238
|
+
) {
|
|
239
|
+
geoClickHandler(stateName, stateData)
|
|
161
240
|
pointerX = undefined
|
|
162
241
|
pointerY = undefined
|
|
163
242
|
}
|
|
@@ -167,14 +246,14 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
|
|
|
167
246
|
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
168
247
|
data-tooltip-html={toolTip}
|
|
169
248
|
/>
|
|
170
|
-
{
|
|
249
|
+
{extraBubbleBorder && (
|
|
171
250
|
<circle
|
|
172
251
|
tabIndex={-1}
|
|
173
252
|
key={`circle-${stateName.replace(' ', '')}`}
|
|
174
253
|
className='bubble'
|
|
175
|
-
cx={projection(coordinates)[0] || 0}
|
|
254
|
+
cx={projection(coordinates)[0] || 0}
|
|
176
255
|
cy={projection(coordinates)[1] || 0}
|
|
177
|
-
r={Number(size(item[
|
|
256
|
+
r={Number(size(item[primaryColumnName])) + 1}
|
|
178
257
|
fill={'transparent'}
|
|
179
258
|
stroke={'white'}
|
|
180
259
|
strokeWidth={0.5}
|
|
@@ -184,8 +263,15 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
|
|
|
184
263
|
pointerY = e.clientY
|
|
185
264
|
}}
|
|
186
265
|
onPointerUp={e => {
|
|
187
|
-
if (
|
|
188
|
-
|
|
266
|
+
if (
|
|
267
|
+
pointerX &&
|
|
268
|
+
pointerY &&
|
|
269
|
+
e.clientX > pointerX - clickTolerance &&
|
|
270
|
+
e.clientX < pointerX + clickTolerance &&
|
|
271
|
+
e.clientY > pointerY - clickTolerance &&
|
|
272
|
+
e.clientY < pointerY + clickTolerance
|
|
273
|
+
) {
|
|
274
|
+
geoClickHandler(stateName, stateData)
|
|
189
275
|
pointerX = undefined
|
|
190
276
|
pointerY = undefined
|
|
191
277
|
}
|
|
@@ -201,7 +287,7 @@ export const BubbleList = ({ data: dataImport, state, projection, applyLegendToR
|
|
|
201
287
|
|
|
202
288
|
return <g key={`group-${stateName.replace(' ', '')}`}>{circle}</g>
|
|
203
289
|
})
|
|
204
|
-
|
|
290
|
+
)
|
|
205
291
|
}
|
|
206
292
|
}
|
|
207
293
|
export default BubbleList
|
|
@@ -1,86 +1,97 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { jsx } from '@emotion/react'
|
|
4
|
-
import { supportedCities } from '../data/supported-geos'
|
|
1
|
+
import { useContext, useEffect, useState } from 'react'
|
|
5
2
|
import { scaleLinear } from 'd3-scale'
|
|
6
|
-
import {
|
|
7
|
-
import { getFilterControllingStatePicked } from './UsaMap/helpers/map'
|
|
8
|
-
import { titleCase } from '../helpers/titleCase'
|
|
9
|
-
|
|
3
|
+
import { GlyphCircle, GlyphDiamond, GlyphSquare, GlyphStar, GlyphTriangle } from '@visx/glyph'
|
|
10
4
|
import ConfigContext from '../context'
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const { state, topoData, runtimeData, position } = useContext(ConfigContext)
|
|
26
|
-
if (!projection) return
|
|
27
|
-
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
const citiesDictionary = {}
|
|
5
|
+
import { supportedCities } from '../data/supported-geos'
|
|
6
|
+
import { getFilterControllingStatePicked } from './UsaMap/helpers/map'
|
|
7
|
+
import { displayGeoName, getGeoStrokeColor, SVG_HEIGHT, SVG_PADDING, SVG_WIDTH, titleCase } from '../helpers'
|
|
8
|
+
import useGeoClickHandler from '../hooks/useGeoClickHandler'
|
|
9
|
+
import useApplyTooltipsToGeo from '../hooks/useApplyTooltipsToGeo'
|
|
10
|
+
import { applyLegendToRow } from '../helpers/applyLegendToRow'
|
|
11
|
+
import { getColumnNames } from '../helpers/getColumnNames'
|
|
12
|
+
|
|
13
|
+
type CityListProps = {
|
|
14
|
+
setSharedFilterValue: string
|
|
15
|
+
isFilterValueSupported: boolean
|
|
16
|
+
tooltipId: string
|
|
17
|
+
projection: any
|
|
18
|
+
}
|
|
30
19
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
20
|
+
const CityList: React.FC<CityListProps> = ({ setSharedFilterValue, isFilterValueSupported, tooltipId, projection }) => {
|
|
21
|
+
const {
|
|
22
|
+
config,
|
|
23
|
+
topoData,
|
|
24
|
+
data: runtimeData,
|
|
25
|
+
position,
|
|
26
|
+
legendMemo,
|
|
27
|
+
legendSpecialClassLastMemo,
|
|
28
|
+
runtimeLegend
|
|
29
|
+
} = useContext(ConfigContext)
|
|
30
|
+
const { geoClickHandler } = useGeoClickHandler()
|
|
31
|
+
const { applyTooltipsToGeo } = useApplyTooltipsToGeo()
|
|
32
|
+
|
|
33
|
+
const { geoColumnName, latitudeColumnName, longitudeColumnName, primaryColumnName } = getColumnNames(config.columns)
|
|
34
|
+
const { additionalCityStyles } = config.visual || []
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
}, [data])
|
|
36
|
+
if (!projection) return
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
const citiesData = runtimeData
|
|
39
|
+
? Object.keys(runtimeData).reduce((acc, key) => {
|
|
40
|
+
const city = runtimeData[key]
|
|
41
|
+
acc[city[geoColumnName]] = city
|
|
42
|
+
return acc
|
|
43
|
+
}, {})
|
|
44
|
+
: {}
|
|
45
|
+
|
|
46
|
+
if (config.general.type === 'bubble') {
|
|
47
|
+
const maxDataValue = Math.max(
|
|
48
|
+
...(runtimeData ? Object.keys(runtimeData).map(key => runtimeData[key][config.columns.primary.name]) : [0])
|
|
49
|
+
)
|
|
50
|
+
const sortedRuntimeData = Object.values(runtimeData).sort((a, b) =>
|
|
51
|
+
a[primaryColumnName] < b[primaryColumnName] ? 1 : -1
|
|
45
52
|
)
|
|
46
53
|
if (!sortedRuntimeData) return
|
|
47
54
|
|
|
48
55
|
// Set bubble sizes
|
|
49
|
-
var size = scaleLinear().domain([1, maxDataValue]).range([
|
|
56
|
+
var size = scaleLinear().domain([1, maxDataValue]).range([config.visual.minBubbleSize, config.visual.maxBubbleSize])
|
|
50
57
|
}
|
|
51
|
-
|
|
58
|
+
const cityList = Object.keys(citiesData).filter(c => undefined !== c || undefined !== runtimeData[c])
|
|
52
59
|
if (!cityList) return true
|
|
53
60
|
|
|
54
61
|
// Cities output
|
|
55
|
-
|
|
56
|
-
let geoData
|
|
57
|
-
if (
|
|
58
|
-
Object.keys(
|
|
59
|
-
if (city ===
|
|
60
|
-
geoData =
|
|
62
|
+
return cityList.map((city, i) => {
|
|
63
|
+
let geoData: Object
|
|
64
|
+
if (runtimeData) {
|
|
65
|
+
Object.keys(runtimeData).forEach(key => {
|
|
66
|
+
if (city === runtimeData[key][config.columns.geo.name]) {
|
|
67
|
+
geoData = runtimeData[key]
|
|
61
68
|
}
|
|
62
69
|
})
|
|
63
70
|
}
|
|
64
71
|
if (!geoData) {
|
|
65
|
-
geoData =
|
|
72
|
+
geoData = runtimeData ? runtimeData[city] : undefined
|
|
66
73
|
}
|
|
67
74
|
const cityDisplayName = titleCase(displayGeoName(city))
|
|
68
75
|
|
|
69
|
-
const legendColors = geoData
|
|
76
|
+
const legendColors = geoData
|
|
77
|
+
? applyLegendToRow(geoData, config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
|
|
78
|
+
: runtimeData[city]
|
|
79
|
+
? applyLegendToRow(runtimeData[city], config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
|
|
80
|
+
: false
|
|
70
81
|
|
|
71
82
|
if (legendColors === false) {
|
|
72
83
|
return true
|
|
73
84
|
}
|
|
74
85
|
|
|
75
|
-
const toolTip = applyTooltipsToGeo(cityDisplayName, geoData ||
|
|
86
|
+
const toolTip = applyTooltipsToGeo(cityDisplayName, geoData || runtimeData[city])
|
|
76
87
|
|
|
77
|
-
const radius =
|
|
88
|
+
const radius = config.visual.geoCodeCircleSize || 8
|
|
78
89
|
|
|
79
90
|
const additionalProps = {
|
|
80
|
-
fillOpacity:
|
|
91
|
+
fillOpacity: config.general.type === 'bubble' ? 0.4 : 1
|
|
81
92
|
}
|
|
82
93
|
|
|
83
|
-
const geoStrokeColor = getGeoStrokeColor(
|
|
94
|
+
const geoStrokeColor = getGeoStrokeColor(config)
|
|
84
95
|
|
|
85
96
|
const pin = (
|
|
86
97
|
<path
|
|
@@ -101,8 +112,8 @@ const CityList = ({
|
|
|
101
112
|
let transform = ''
|
|
102
113
|
|
|
103
114
|
if (
|
|
104
|
-
!geoData?.[
|
|
105
|
-
!geoData?.[
|
|
115
|
+
!geoData?.[longitudeColumnName] &&
|
|
116
|
+
!geoData?.[latitudeColumnName] &&
|
|
106
117
|
city &&
|
|
107
118
|
supportedCities[city.toUpperCase()]
|
|
108
119
|
) {
|
|
@@ -111,34 +122,26 @@ const CityList = ({
|
|
|
111
122
|
|
|
112
123
|
let needsPointer = false
|
|
113
124
|
|
|
114
|
-
if (geoData?.[
|
|
115
|
-
let coords = [Number(geoData?.[
|
|
125
|
+
if (geoData?.[longitudeColumnName] && geoData?.[latitudeColumnName]) {
|
|
126
|
+
let coords = [Number(geoData?.[longitudeColumnName]), Number(geoData?.[latitudeColumnName])]
|
|
116
127
|
transform = `translate(${projection(coords)})`
|
|
117
128
|
needsPointer = true
|
|
118
129
|
}
|
|
119
130
|
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
geoData?.[state.columns.latitude.name] &&
|
|
123
|
-
state.general.geoType === 'single-state'
|
|
124
|
-
) {
|
|
125
|
-
const statePicked = getFilterControllingStatePicked(state, runtimeData)
|
|
131
|
+
if (geoData?.[longitudeColumnName] && geoData?.[latitudeColumnName] && config.general.geoType === 'single-state') {
|
|
132
|
+
const statePicked = getFilterControllingStatePicked(config, runtimeData)
|
|
126
133
|
const _statePickedData = topoData?.states?.find(s => s.properties.name === statePicked)
|
|
127
|
-
// SVG ITEMS
|
|
128
|
-
const WIDTH = 880
|
|
129
|
-
const HEIGHT = 500
|
|
130
|
-
const PADDING = 50
|
|
131
134
|
|
|
132
135
|
const newProjection = projection.fitExtent(
|
|
133
136
|
[
|
|
134
|
-
[
|
|
135
|
-
[
|
|
137
|
+
[SVG_PADDING, SVG_PADDING],
|
|
138
|
+
[SVG_WIDTH - SVG_PADDING, SVG_HEIGHT - SVG_PADDING]
|
|
136
139
|
],
|
|
137
140
|
_statePickedData
|
|
138
141
|
)
|
|
139
|
-
let coords = [Number(geoData?.[
|
|
142
|
+
let coords = [Number(geoData?.[longitudeColumnName]), Number(geoData?.[latitudeColumnName])]
|
|
140
143
|
transform = `translate(${newProjection(coords)}) scale(${
|
|
141
|
-
|
|
144
|
+
config.visual.geoCodeCircleSize / (position.zoom > 1 ? position.zoom : 1)
|
|
142
145
|
})`
|
|
143
146
|
needsPointer = true
|
|
144
147
|
}
|
|
@@ -153,8 +156,8 @@ const CityList = ({
|
|
|
153
156
|
stroke:
|
|
154
157
|
setSharedFilterValue &&
|
|
155
158
|
isFilterValueSupported &&
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
runtimeData[city] &&
|
|
160
|
+
runtimeData[city][config.columns.geo.name] === setSharedFilterValue
|
|
158
161
|
? 'rgba(0, 0, 0, 1)'
|
|
159
162
|
: 'rgba(0, 0, 0, 0.4)',
|
|
160
163
|
'&:hover': {
|
|
@@ -170,15 +173,15 @@ const CityList = ({
|
|
|
170
173
|
|
|
171
174
|
// If we need to add a cursor pointer
|
|
172
175
|
if (
|
|
173
|
-
(
|
|
174
|
-
|
|
176
|
+
(config.columns.navigate && geoData?.[config.columns.navigate.name] && geoData[config.columns.navigate.name]) ||
|
|
177
|
+
config.tooltips.appearanceType === 'click'
|
|
175
178
|
) {
|
|
176
179
|
styles.cursor = 'pointer'
|
|
177
180
|
}
|
|
178
181
|
|
|
179
182
|
const shapeProps = {
|
|
180
183
|
onClick: () => geoClickHandler(cityDisplayName, geoData),
|
|
181
|
-
size:
|
|
184
|
+
size: config.general.type === 'bubble' ? size(geoData[primaryColumnName]) : radius * 30,
|
|
182
185
|
title: 'Select for more information',
|
|
183
186
|
'data-tooltip-id': `tooltip__${tooltipId}`,
|
|
184
187
|
'data-tooltip-html': toolTip,
|
|
@@ -197,9 +200,8 @@ const CityList = ({
|
|
|
197
200
|
triangle: <GlyphTriangle {...shapeProps} />
|
|
198
201
|
}
|
|
199
202
|
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
.filter(d => additionalCityStyles.some(style => String(d[style.column]) === String(style.value)))
|
|
203
|
+
const cityStyle = Object.values(runtimeData)
|
|
204
|
+
.filter(d => additionalCityStyles?.some(style => String(d[style.column]) === String(style.value)))
|
|
203
205
|
.map(d => {
|
|
204
206
|
const conditionsMatched = additionalCityStyles.find(style => String(d[style.column]) === String(style.value))
|
|
205
207
|
return { ...conditionsMatched, ...d }
|
|
@@ -210,8 +212,8 @@ const CityList = ({
|
|
|
210
212
|
|
|
211
213
|
if (cityStyle !== undefined && cityStyle.shape) {
|
|
212
214
|
if (
|
|
213
|
-
!geoData?.[
|
|
214
|
-
!geoData?.[
|
|
215
|
+
!geoData?.[longitudeColumnName] &&
|
|
216
|
+
!geoData?.[latitudeColumnName] &&
|
|
215
217
|
city &&
|
|
216
218
|
supportedCities[city.toUpperCase()]
|
|
217
219
|
) {
|
|
@@ -224,10 +226,10 @@ const CityList = ({
|
|
|
224
226
|
)
|
|
225
227
|
}
|
|
226
228
|
|
|
227
|
-
if (geoData?.[
|
|
228
|
-
const coords = [Number(geoData?.[
|
|
229
|
+
if (geoData?.[longitudeColumnName] && geoData?.[latitudeColumnName]) {
|
|
230
|
+
const coords = [Number(geoData?.[longitudeColumnName]), Number(geoData?.[latitudeColumnName])]
|
|
229
231
|
let translate = `translate(${projection(coords)})`
|
|
230
|
-
|
|
232
|
+
|
|
231
233
|
return (
|
|
232
234
|
<g key={i} transform={translate} style={styles} className='geo-point' tabIndex={-1}>
|
|
233
235
|
{cityStyleShapes[cityStyle.shape.toLowerCase()]}
|
|
@@ -235,14 +237,14 @@ const CityList = ({
|
|
|
235
237
|
)
|
|
236
238
|
}
|
|
237
239
|
}
|
|
240
|
+
if (legendColors?.[0] === '#000000') return
|
|
241
|
+
|
|
238
242
|
return (
|
|
239
243
|
<g key={i} transform={transform} style={styles} className='geo-point' tabIndex={-1}>
|
|
240
|
-
{cityStyleShapes[
|
|
244
|
+
{cityStyleShapes[config.visual.cityStyle.toLowerCase()]}
|
|
241
245
|
</g>
|
|
242
246
|
)
|
|
243
247
|
})
|
|
244
|
-
|
|
245
|
-
return cities
|
|
246
248
|
}
|
|
247
249
|
|
|
248
250
|
export default CityList
|
|
@@ -10,8 +10,8 @@ import MediaControls from '@cdc/core/components/MediaControls'
|
|
|
10
10
|
import SkipTo from '@cdc/core/components/elements/SkipTo'
|
|
11
11
|
|
|
12
12
|
import Loading from '@cdc/core/components/Loading'
|
|
13
|
-
import { navigationHandler } from '../helpers
|
|
14
|
-
import ConfigContext from '../context'
|
|
13
|
+
import { navigationHandler } from '../helpers'
|
|
14
|
+
import ConfigContext, { MapDispatchContext } from '../context'
|
|
15
15
|
|
|
16
16
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
|
|
17
17
|
const DataTable = props => {
|
|
@@ -29,10 +29,10 @@ const DataTable = props => {
|
|
|
29
29
|
applyLegendToRow,
|
|
30
30
|
displayGeoName,
|
|
31
31
|
formatLegendLocation,
|
|
32
|
-
tabbingId
|
|
33
|
-
setFilteredCountryCode
|
|
32
|
+
tabbingId
|
|
34
33
|
} = props
|
|
35
34
|
|
|
35
|
+
const dispatch = useContext(MapDispatchContext)
|
|
36
36
|
const { currentViewport: viewport } = useContext(ConfigContext)
|
|
37
37
|
const [expanded, setExpanded] = useState(expandDataTable)
|
|
38
38
|
const [sortBy, setSortBy] = useState({ column: 'geo', asc: false })
|
|
@@ -181,7 +181,7 @@ const DataTable = props => {
|
|
|
181
181
|
aria-label='Download this data in a CSV file format.'
|
|
182
182
|
className={`${headerColor} no-border`}
|
|
183
183
|
id={`${skipId}`}
|
|
184
|
-
data-html2canvas-ignore
|
|
184
|
+
data-html2canvas-ignore={true}
|
|
185
185
|
role='button'
|
|
186
186
|
>
|
|
187
187
|
Download Data (CSV)
|
|
@@ -217,7 +217,7 @@ const DataTable = props => {
|
|
|
217
217
|
if (!state.data) return <Loading />
|
|
218
218
|
|
|
219
219
|
const rows = Object.keys(runtimeData)
|
|
220
|
-
.filter(row => applyLegendToRow(runtimeData[row]))
|
|
220
|
+
.filter(row => applyLegendToRow(runtimeData[row], state))
|
|
221
221
|
.sort((a, b) => {
|
|
222
222
|
const sortVal = customSort(
|
|
223
223
|
runtimeData[a][state.columns[sortBy.column].name],
|
|
@@ -326,7 +326,7 @@ const DataTable = props => {
|
|
|
326
326
|
|
|
327
327
|
if (column === 'geo') {
|
|
328
328
|
const rowObj = runtimeData[row]
|
|
329
|
-
const legendColor = applyLegendToRow(rowObj)
|
|
329
|
+
const legendColor = applyLegendToRow(rowObj, state)
|
|
330
330
|
|
|
331
331
|
var labelValue
|
|
332
332
|
if (state.general.geoType !== 'us-county' || state.general.type === 'us-geocode') {
|
|
@@ -355,7 +355,7 @@ const DataTable = props => {
|
|
|
355
355
|
state.general.type === 'bubble' &&
|
|
356
356
|
state.general.allowMapZoom &&
|
|
357
357
|
state.general.geoType === 'world'
|
|
358
|
-
?
|
|
358
|
+
? dispatch({ type: 'SET_FILTERED_COUNTRY_CODE', payload: row })
|
|
359
359
|
: true
|
|
360
360
|
}
|
|
361
361
|
>
|