@cdc/map 4.25.8 → 4.25.10
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/.claude/settings.local.json +30 -0
- package/dist/cdcmap.js +54263 -52600
- package/examples/private/c.json +290 -0
- package/examples/private/canvas-city-hover.json +787 -0
- package/examples/private/d.json +345 -0
- package/examples/private/g.json +1 -0
- package/examples/private/h.json +105911 -0
- package/examples/private/measles-data.json +378 -0
- package/examples/private/measles.json +211 -0
- package/examples/private/north-dakota.json +1132 -0
- package/examples/private/state-with-pattern.json +883 -0
- package/index.html +35 -34
- package/package.json +26 -5
- package/src/CdcMap.tsx +23 -8
- package/src/CdcMapComponent.tsx +215 -309
- package/src/_stories/CdcMap.Filters.stories.tsx +2 -2
- package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +3 -3
- package/src/_stories/CdcMap.Legend.stories.tsx +7 -4
- package/src/_stories/CdcMap.Patterns.stories.tsx +2 -2
- package/src/_stories/CdcMap.Table.stories.tsx +2 -2
- package/src/_stories/CdcMap.stories.tsx +15 -5
- package/src/_stories/GoogleMap.stories.tsx +2 -2
- package/src/_stories/UsaMap.NoData.stories.tsx +2 -2
- package/src/_stories/_mock/equal-number.json +1109 -0
- package/src/_stories/_mock/us-bubble-cities.json +306 -0
- package/src/components/BubbleList.tsx +16 -12
- package/src/components/CityList.tsx +85 -107
- package/src/components/DataTable.tsx +37 -9
- package/src/components/EditorPanel/components/EditorPanel.tsx +177 -165
- package/src/components/EditorPanel/components/HexShapeSettings.tsx +3 -2
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +7 -5
- package/src/components/Geo.tsx +2 -0
- package/src/components/Legend/components/Legend.tsx +109 -73
- package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +10 -7
- package/src/components/MapContainer.tsx +52 -0
- package/src/components/MapControls.tsx +44 -0
- package/src/components/NavigationMenu.tsx +11 -2
- package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +24 -7
- package/src/components/UsaMap/components/UsaMap.County.tsx +111 -37
- package/src/components/UsaMap/components/UsaMap.Region.tsx +23 -5
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +6 -6
- package/src/components/UsaMap/components/UsaMap.State.tsx +28 -10
- package/src/components/UsaMap/helpers/map.ts +2 -2
- package/src/components/WorldMap/WorldMap.tsx +113 -25
- package/src/components/ZoomControls.tsx +6 -9
- package/src/context/LegendMemoContext.tsx +30 -0
- package/src/context.ts +1 -40
- package/src/data/initial-state.js +143 -130
- package/src/data/supported-geos.js +17 -2
- package/src/helpers/applyColorToLegend.ts +116 -20
- package/src/helpers/applyLegendToRow.ts +10 -6
- package/src/helpers/componentHelpers.ts +8 -0
- package/src/helpers/constants.ts +12 -0
- package/src/helpers/dataTableHelpers.ts +6 -0
- package/src/helpers/displayGeoName.ts +1 -1
- package/src/helpers/generateRuntimeLegend.ts +44 -8
- package/src/helpers/generateRuntimeLegendHash.ts +4 -2
- package/src/helpers/getColumnNames.ts +1 -1
- package/src/helpers/getPatternForRow.ts +36 -0
- package/src/helpers/getStatesPicked.ts +8 -5
- package/src/helpers/index.ts +11 -3
- package/src/helpers/isLegendItemDisabled.ts +16 -0
- package/src/helpers/mapObserverHelpers.ts +40 -0
- package/src/helpers/resetLegendToggles.ts +3 -2
- package/src/helpers/toggleLegendActive.ts +6 -11
- package/src/helpers/urlDataHelpers.ts +70 -0
- package/src/hooks/useGeoClickHandler.ts +35 -1
- package/src/hooks/useLegendMemo.ts +17 -0
- package/src/hooks/useMapLayers.tsx +5 -4
- package/src/hooks/useStateZoom.tsx +25 -6
- package/src/hooks/useTooltip.ts +1 -2
- package/src/index.jsx +0 -2
- package/src/store/map.reducer.ts +17 -6
- package/src/test/CdcMap.test.jsx +11 -0
- package/src/types/MapConfig.ts +23 -14
- package/src/types/MapContext.ts +0 -7
- package/src/types/runtimeLegend.ts +17 -1
- package/vite.config.js +2 -7
- package/vitest.config.ts +16 -0
- package/src/coreStyles_map.scss +0 -3
- package/src/helpers/colorDistributions.ts +0 -12
- package/src/helpers/generateColorsArray.ts +0 -14
- package/src/helpers/tests/generateColorsArray.test.ts +0 -18
- package/src/helpers/tests/generateRuntimeLegendHash.test.ts +0 -11
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
{
|
|
2
|
+
"annotations": [],
|
|
3
|
+
"general": {
|
|
4
|
+
"title": "US Cities Bubble Map - CityList Test",
|
|
5
|
+
"subtext": "This bubble map uses CityList component to render bubble sizes based on data values",
|
|
6
|
+
"type": "bubble",
|
|
7
|
+
"geoType": "us",
|
|
8
|
+
"headerColor": "theme-green",
|
|
9
|
+
"showSidebar": true,
|
|
10
|
+
"showTitle": true,
|
|
11
|
+
"showDownloadButton": true,
|
|
12
|
+
"expandDataTable": false,
|
|
13
|
+
"backgroundColor": "#ffffff",
|
|
14
|
+
"geoBorderColor": "darkGray",
|
|
15
|
+
"territoriesLabel": "Territories",
|
|
16
|
+
"language": "en",
|
|
17
|
+
"hasRegions": false,
|
|
18
|
+
"showDownloadMediaButton": false,
|
|
19
|
+
"displayAsHex": false,
|
|
20
|
+
"displayStateLabels": false,
|
|
21
|
+
"fullBorder": false,
|
|
22
|
+
"palette": {
|
|
23
|
+
"isReversed": false
|
|
24
|
+
},
|
|
25
|
+
"allowMapZoom": true,
|
|
26
|
+
"hideGeoColumnInTooltip": false,
|
|
27
|
+
"hidePrimaryColumnInTooltip": false,
|
|
28
|
+
"territoriesAlwaysShow": false,
|
|
29
|
+
"geoLabelOverride": "",
|
|
30
|
+
"noDataFoundMessage": "No data found",
|
|
31
|
+
"convertFipsCodes": false
|
|
32
|
+
},
|
|
33
|
+
"type": "map",
|
|
34
|
+
"color": "orangered",
|
|
35
|
+
"columns": {
|
|
36
|
+
"geo": {
|
|
37
|
+
"name": "city",
|
|
38
|
+
"label": "City",
|
|
39
|
+
"tooltip": true,
|
|
40
|
+
"dataTable": true
|
|
41
|
+
},
|
|
42
|
+
"primary": {
|
|
43
|
+
"dataTable": true,
|
|
44
|
+
"tooltip": true,
|
|
45
|
+
"prefix": "",
|
|
46
|
+
"suffix": " cases",
|
|
47
|
+
"name": "covid_cases",
|
|
48
|
+
"label": "COVID-19 Cases"
|
|
49
|
+
},
|
|
50
|
+
"latitude": {
|
|
51
|
+
"name": "latitude",
|
|
52
|
+
"label": "Latitude"
|
|
53
|
+
},
|
|
54
|
+
"longitude": {
|
|
55
|
+
"name": "longitude",
|
|
56
|
+
"label": "Longitude"
|
|
57
|
+
},
|
|
58
|
+
"navigate": {
|
|
59
|
+
"name": ""
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"legend": {
|
|
63
|
+
"descriptions": {},
|
|
64
|
+
"specialClasses": [],
|
|
65
|
+
"unified": false,
|
|
66
|
+
"singleColumn": true,
|
|
67
|
+
"dynamicDescription": false,
|
|
68
|
+
"type": "equalinterval",
|
|
69
|
+
"numberOfItems": 5,
|
|
70
|
+
"position": "side",
|
|
71
|
+
"title": "COVID-19 Cases",
|
|
72
|
+
"showTitle": true,
|
|
73
|
+
"reversedOrder": false,
|
|
74
|
+
"singleColumnLegend": false,
|
|
75
|
+
"hideBorder": false
|
|
76
|
+
},
|
|
77
|
+
"filters": [],
|
|
78
|
+
"table": {
|
|
79
|
+
"label": "Data Table",
|
|
80
|
+
"expanded": false,
|
|
81
|
+
"limitHeight": false,
|
|
82
|
+
"height": "",
|
|
83
|
+
"caption": "",
|
|
84
|
+
"showDownloadUrl": false,
|
|
85
|
+
"showDataTableLink": true,
|
|
86
|
+
"showFullGeoNameInCSV": false,
|
|
87
|
+
"forceDisplay": true,
|
|
88
|
+
"download": true,
|
|
89
|
+
"indexLabel": "",
|
|
90
|
+
"wrapColumns": false,
|
|
91
|
+
"showDownloadLinkBelow": true
|
|
92
|
+
},
|
|
93
|
+
"tooltips": {
|
|
94
|
+
"appearanceType": "hover",
|
|
95
|
+
"linkLabel": "Learn More",
|
|
96
|
+
"capitalizeLabels": true,
|
|
97
|
+
"opacity": 90
|
|
98
|
+
},
|
|
99
|
+
"runtime": {
|
|
100
|
+
"editorErrorMessage": []
|
|
101
|
+
},
|
|
102
|
+
"visual": {
|
|
103
|
+
"minBubbleSize": 5,
|
|
104
|
+
"maxBubbleSize": 40,
|
|
105
|
+
"extraBubbleBorder": true,
|
|
106
|
+
"cityStyle": "circle",
|
|
107
|
+
"geoCodeCircleSize": 12,
|
|
108
|
+
"showBubbleZeros": true,
|
|
109
|
+
"cityStyleLabel": "City Bubbles",
|
|
110
|
+
"additionalCityStyles": []
|
|
111
|
+
},
|
|
112
|
+
"mapPosition": {
|
|
113
|
+
"coordinates": [0, 39],
|
|
114
|
+
"zoom": 1
|
|
115
|
+
},
|
|
116
|
+
"map": {
|
|
117
|
+
"layers": [],
|
|
118
|
+
"patterns": []
|
|
119
|
+
},
|
|
120
|
+
"hexMap": {
|
|
121
|
+
"type": "",
|
|
122
|
+
"shapeGroups": []
|
|
123
|
+
},
|
|
124
|
+
"data": [
|
|
125
|
+
{
|
|
126
|
+
"city": "New York",
|
|
127
|
+
"latitude": 40.7128,
|
|
128
|
+
"longitude": -74.0060,
|
|
129
|
+
"covid_cases": 2845
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"city": "Los Angeles",
|
|
133
|
+
"latitude": 34.0522,
|
|
134
|
+
"longitude": -118.2437,
|
|
135
|
+
"covid_cases": 2234
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"city": "Chicago",
|
|
139
|
+
"latitude": 41.8781,
|
|
140
|
+
"longitude": -87.6298,
|
|
141
|
+
"covid_cases": 1567
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"city": "Houston",
|
|
145
|
+
"latitude": 29.7604,
|
|
146
|
+
"longitude": -95.3698,
|
|
147
|
+
"covid_cases": 1890
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"city": "Phoenix",
|
|
151
|
+
"latitude": 33.4484,
|
|
152
|
+
"longitude": -112.0740,
|
|
153
|
+
"covid_cases": 1234
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"city": "Philadelphia",
|
|
157
|
+
"latitude": 39.9526,
|
|
158
|
+
"longitude": -75.1652,
|
|
159
|
+
"covid_cases": 1678
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"city": "San Antonio",
|
|
163
|
+
"latitude": 29.4241,
|
|
164
|
+
"longitude": -98.4936,
|
|
165
|
+
"covid_cases": 987
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"city": "San Diego",
|
|
169
|
+
"latitude": 32.7157,
|
|
170
|
+
"longitude": -117.1611,
|
|
171
|
+
"covid_cases": 1456
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"city": "Dallas",
|
|
175
|
+
"latitude": 32.7767,
|
|
176
|
+
"longitude": -96.7970,
|
|
177
|
+
"covid_cases": 1789
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"city": "San Jose",
|
|
181
|
+
"latitude": 37.3382,
|
|
182
|
+
"longitude": -121.8863,
|
|
183
|
+
"covid_cases": 934
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"city": "Austin",
|
|
187
|
+
"latitude": 30.2672,
|
|
188
|
+
"longitude": -97.7431,
|
|
189
|
+
"covid_cases": 876
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"city": "Jacksonville",
|
|
193
|
+
"latitude": 30.3322,
|
|
194
|
+
"longitude": -81.6557,
|
|
195
|
+
"covid_cases": 743
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"city": "Fort Worth",
|
|
199
|
+
"latitude": 32.7555,
|
|
200
|
+
"longitude": -97.3308,
|
|
201
|
+
"covid_cases": 654
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"city": "Columbus",
|
|
205
|
+
"latitude": 39.9612,
|
|
206
|
+
"longitude": -82.9988,
|
|
207
|
+
"covid_cases": 567
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"city": "Charlotte",
|
|
211
|
+
"latitude": 35.2271,
|
|
212
|
+
"longitude": -80.8431,
|
|
213
|
+
"covid_cases": 789
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"city": "San Francisco",
|
|
217
|
+
"latitude": 37.7749,
|
|
218
|
+
"longitude": -122.4194,
|
|
219
|
+
"covid_cases": 1123
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"city": "Indianapolis",
|
|
223
|
+
"latitude": 39.7684,
|
|
224
|
+
"longitude": -86.1581,
|
|
225
|
+
"covid_cases": 445
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"city": "Seattle",
|
|
229
|
+
"latitude": 47.6062,
|
|
230
|
+
"longitude": -122.3321,
|
|
231
|
+
"covid_cases": 823
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"city": "Denver",
|
|
235
|
+
"latitude": 39.7392,
|
|
236
|
+
"longitude": -104.9903,
|
|
237
|
+
"covid_cases": 634
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"city": "Boston",
|
|
241
|
+
"latitude": 42.3601,
|
|
242
|
+
"longitude": -71.0589,
|
|
243
|
+
"covid_cases": 976
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"city": "El Paso",
|
|
247
|
+
"latitude": 31.7619,
|
|
248
|
+
"longitude": -106.4850,
|
|
249
|
+
"covid_cases": 312
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"city": "Nashville",
|
|
253
|
+
"latitude": 36.1627,
|
|
254
|
+
"longitude": -86.7816,
|
|
255
|
+
"covid_cases": 456
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
"city": "Detroit",
|
|
259
|
+
"latitude": 42.3314,
|
|
260
|
+
"longitude": -83.0458,
|
|
261
|
+
"covid_cases": 578
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"city": "Oklahoma City",
|
|
265
|
+
"latitude": 35.4676,
|
|
266
|
+
"longitude": -97.5164,
|
|
267
|
+
"covid_cases": 289
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"city": "Portland",
|
|
271
|
+
"latitude": 45.5152,
|
|
272
|
+
"longitude": -122.6784,
|
|
273
|
+
"covid_cases": 387
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"city": "Las Vegas",
|
|
277
|
+
"latitude": 36.1699,
|
|
278
|
+
"longitude": -115.1398,
|
|
279
|
+
"covid_cases": 534
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
"city": "Memphis",
|
|
283
|
+
"latitude": 35.1495,
|
|
284
|
+
"longitude": -90.0490,
|
|
285
|
+
"covid_cases": 267
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"city": "Louisville",
|
|
289
|
+
"latitude": 38.2527,
|
|
290
|
+
"longitude": -85.7585,
|
|
291
|
+
"covid_cases": 189
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
"city": "Baltimore",
|
|
295
|
+
"latitude": 39.2904,
|
|
296
|
+
"longitude": -76.6122,
|
|
297
|
+
"covid_cases": 445
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
"city": "Milwaukee",
|
|
301
|
+
"latitude": 43.0389,
|
|
302
|
+
"longitude": -87.9065,
|
|
303
|
+
"covid_cases": 334
|
|
304
|
+
}
|
|
305
|
+
]
|
|
306
|
+
}
|
|
@@ -3,6 +3,7 @@ import { scaleLinear } from 'd3-scale'
|
|
|
3
3
|
import { countryCoordinates } from '../data/country-coordinates'
|
|
4
4
|
import stateCoordinates from '../data/state-coordinates'
|
|
5
5
|
import ConfigContext, { MapDispatchContext } from '../context'
|
|
6
|
+
import { useLegendMemoContext } from '../context/LegendMemoContext'
|
|
6
7
|
import { type Coordinate, DataRow } from '../types/MapConfig'
|
|
7
8
|
import useApplyTooltipsToGeo from '../hooks/useApplyTooltipsToGeo'
|
|
8
9
|
import { applyLegendToRow } from '../helpers/applyLegendToRow'
|
|
@@ -17,8 +18,8 @@ type BubbleListProps = {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
20
|
-
const { config, tooltipId,
|
|
21
|
-
|
|
21
|
+
const { config, tooltipId, runtimeData, runtimeLegend } = useContext<MapContext>(ConfigContext)
|
|
22
|
+
const { legendMemo, legendSpecialClassLastMemo } = useLegendMemoContext()
|
|
22
23
|
const { columns, data, general, visual } = config
|
|
23
24
|
const { geoType, allowMapZoom } = general
|
|
24
25
|
const { minBubbleSize, maxBubbleSize, showBubbleZeros, extraBubbleBorder } = visual
|
|
@@ -65,7 +66,7 @@ export const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
65
66
|
dispatch({ type: 'SET_POSITION', payload: { coordinates: reversedCoordinates, zoom: 3 } })
|
|
66
67
|
|
|
67
68
|
// ...and show the data for the clicked country
|
|
68
|
-
|
|
69
|
+
dispatch({ type: 'SET_RUNTIME_DATA', payload: _tempRuntimeData })
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
const sortedRuntimeData: DataRow = Object.values(runtimeData).sort((a, b) =>
|
|
@@ -77,7 +78,7 @@ export const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
77
78
|
if (geoType === 'world') {
|
|
78
79
|
return (
|
|
79
80
|
sortedRuntimeData &&
|
|
80
|
-
sortedRuntimeData.map(country => {
|
|
81
|
+
sortedRuntimeData.map((country, index) => {
|
|
81
82
|
let coordinates = countryCoordinates[country.uid]
|
|
82
83
|
|
|
83
84
|
if (!coordinates) return true
|
|
@@ -99,10 +100,9 @@ export const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
99
100
|
if (!projection(coordinates)) return true
|
|
100
101
|
|
|
101
102
|
const circle = (
|
|
102
|
-
|
|
103
|
+
<React.Fragment key={`circle-fragment-${countryName.replace(' ', '')}`}>
|
|
103
104
|
<circle
|
|
104
105
|
tabIndex={-1}
|
|
105
|
-
key={`circle-${countryName.replace(' ', '')}`}
|
|
106
106
|
className={`bubble country--${countryName}`}
|
|
107
107
|
cx={Number(projection(coordinates[1], coordinates[0])[0]) || 0}
|
|
108
108
|
cy={Number(projection(coordinates[1], coordinates[0])[1]) || 0}
|
|
@@ -111,6 +111,7 @@ export const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
111
111
|
stroke={legendColors[0]}
|
|
112
112
|
strokeWidth={1.25}
|
|
113
113
|
fillOpacity={0.4}
|
|
114
|
+
onMouseEnter={() => {}}
|
|
114
115
|
onPointerDown={e => {
|
|
115
116
|
pointerX = e.clientX
|
|
116
117
|
pointerY = e.clientY
|
|
@@ -138,7 +139,7 @@ export const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
138
139
|
{extraBubbleBorder && (
|
|
139
140
|
<circle
|
|
140
141
|
tabIndex={-1}
|
|
141
|
-
key={`circle-${countryName.replace(' ', '')}`}
|
|
142
|
+
key={`circle-border-${countryName.replace(' ', '')}`}
|
|
142
143
|
className='bubble'
|
|
143
144
|
cx={Number(projection(coordinates[1], coordinates[0])[0]) || 0}
|
|
144
145
|
cy={Number(projection(coordinates[1], coordinates[0])[1]) || 0}
|
|
@@ -146,6 +147,7 @@ export const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
146
147
|
fill={'transparent'}
|
|
147
148
|
stroke={'white'}
|
|
148
149
|
strokeWidth={0.5}
|
|
150
|
+
onMouseEnter={() => {}}
|
|
149
151
|
onPointerDown={e => {
|
|
150
152
|
pointerX = e.clientX
|
|
151
153
|
pointerY = e.clientY
|
|
@@ -170,11 +172,11 @@ export const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
170
172
|
data-tooltip-html={toolTip}
|
|
171
173
|
/>
|
|
172
174
|
)}
|
|
173
|
-
|
|
175
|
+
</React.Fragment>
|
|
174
176
|
)
|
|
175
177
|
|
|
176
178
|
return (
|
|
177
|
-
<g key={`group-${countryName.replace(' ', '')}`} tabIndex={-1}>
|
|
179
|
+
<g key={`group-${index}-${countryName.replace(' ', '')}`} tabIndex={-1}>
|
|
178
180
|
{circle}
|
|
179
181
|
</g>
|
|
180
182
|
)
|
|
@@ -185,7 +187,7 @@ export const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
185
187
|
if (geoType === 'us') {
|
|
186
188
|
return (
|
|
187
189
|
sortedRuntimeData &&
|
|
188
|
-
sortedRuntimeData.map(item => {
|
|
190
|
+
sortedRuntimeData.map((item, index) => {
|
|
189
191
|
let stateData = stateCoordinates[item.uid]
|
|
190
192
|
if (Number(size(item[primaryColumnName])) === 0) return
|
|
191
193
|
|
|
@@ -223,6 +225,7 @@ export const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
223
225
|
stroke={legendColors[0]}
|
|
224
226
|
strokeWidth={1.25}
|
|
225
227
|
fillOpacity={0.4}
|
|
228
|
+
onMouseEnter={() => {}}
|
|
226
229
|
onPointerDown={e => {
|
|
227
230
|
pointerX = e.clientX
|
|
228
231
|
pointerY = e.clientY
|
|
@@ -249,7 +252,7 @@ export const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
249
252
|
{extraBubbleBorder && (
|
|
250
253
|
<circle
|
|
251
254
|
tabIndex={-1}
|
|
252
|
-
key={`circle-${stateName.replace(' ', '')}`}
|
|
255
|
+
key={`circle-border-${stateName.replace(' ', '')}`}
|
|
253
256
|
className='bubble'
|
|
254
257
|
cx={projection(coordinates)[0] || 0}
|
|
255
258
|
cy={projection(coordinates)[1] || 0}
|
|
@@ -258,6 +261,7 @@ export const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
258
261
|
stroke={'white'}
|
|
259
262
|
strokeWidth={0.5}
|
|
260
263
|
fillOpacity={0.4}
|
|
264
|
+
onMouseEnter={() => {}}
|
|
261
265
|
onPointerDown={e => {
|
|
262
266
|
pointerX = e.clientX
|
|
263
267
|
pointerY = e.clientY
|
|
@@ -285,7 +289,7 @@ export const BubbleList: React.FC<BubbleListProps> = ({ customProjection }) => {
|
|
|
285
289
|
</>
|
|
286
290
|
)
|
|
287
291
|
|
|
288
|
-
return <g key={`group-${stateName.replace(' ', '')}`}>{circle}</g>
|
|
292
|
+
return <g key={`group-${index}-${stateName.replace(' ', '')}`}>{circle}</g>
|
|
289
293
|
})
|
|
290
294
|
)
|
|
291
295
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { useContext,
|
|
1
|
+
import { useContext, useMemo } from 'react'
|
|
2
2
|
import { scaleLinear } from 'd3-scale'
|
|
3
3
|
import { GlyphCircle, GlyphDiamond, GlyphSquare, GlyphStar, GlyphTriangle } from '@visx/glyph'
|
|
4
4
|
import ConfigContext from '../context'
|
|
5
|
+
import { useLegendMemoContext } from '../context/LegendMemoContext'
|
|
5
6
|
import { supportedCities } from '../data/supported-geos'
|
|
6
7
|
import { getFilterControllingStatesPicked } from './UsaMap/helpers/map'
|
|
7
|
-
import { displayGeoName, getGeoStrokeColor, SVG_HEIGHT, SVG_PADDING, SVG_WIDTH,
|
|
8
|
+
import { displayGeoName, getGeoStrokeColor, SVG_HEIGHT, SVG_PADDING, SVG_WIDTH, isLegendItemDisabled } from '../helpers'
|
|
8
9
|
import useGeoClickHandler from '../hooks/useGeoClickHandler'
|
|
9
10
|
import useApplyTooltipsToGeo from '../hooks/useApplyTooltipsToGeo'
|
|
10
11
|
import { applyLegendToRow } from '../helpers/applyLegendToRow'
|
|
@@ -18,72 +19,86 @@ type CityListProps = {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
const CityList: React.FC<CityListProps> = ({ setSharedFilterValue, isFilterValueSupported, tooltipId, projection }) => {
|
|
21
|
-
const {
|
|
22
|
-
|
|
23
|
-
topoData,
|
|
24
|
-
data: runtimeData,
|
|
25
|
-
position,
|
|
26
|
-
legendMemo,
|
|
27
|
-
legendSpecialClassLastMemo,
|
|
28
|
-
runtimeLegend
|
|
29
|
-
} = useContext(ConfigContext)
|
|
22
|
+
const { config, topoData, runtimeData, position, runtimeLegend } = useContext(ConfigContext)
|
|
23
|
+
const { legendMemo, legendSpecialClassLastMemo } = useLegendMemoContext()
|
|
30
24
|
const { geoClickHandler } = useGeoClickHandler()
|
|
31
25
|
const { applyTooltipsToGeo } = useApplyTooltipsToGeo()
|
|
32
26
|
|
|
33
|
-
const { geoColumnName, latitudeColumnName, longitudeColumnName, primaryColumnName } =
|
|
34
|
-
|
|
27
|
+
const { geoColumnName, latitudeColumnName, longitudeColumnName, primaryColumnName } =
|
|
28
|
+
getColumnNames(config.columns) || {}
|
|
35
29
|
|
|
36
|
-
|
|
30
|
+
// Memoize expensive city data creation
|
|
31
|
+
const citiesData = useMemo(() => {
|
|
32
|
+
if (!runtimeData) return {}
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
return Object.keys(runtimeData).reduce((acc, key) => {
|
|
35
|
+
const city = runtimeData[key]
|
|
36
|
+
if (city && city[geoColumnName]) {
|
|
41
37
|
acc[city[geoColumnName]] = city
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
}
|
|
39
|
+
return acc
|
|
40
|
+
}, {})
|
|
41
|
+
}, [runtimeData, geoColumnName])
|
|
42
|
+
|
|
43
|
+
// Memoize bubble size calculation
|
|
44
|
+
const size = useMemo(() => {
|
|
45
|
+
if (config.general.type !== 'bubble' || !runtimeData) {
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
)
|
|
53
|
-
|
|
49
|
+
const maxVal = Math.max(...Object.keys(runtimeData).map(key => runtimeData[key][config.columns.primary.name]))
|
|
50
|
+
|
|
51
|
+
if (maxVal <= 0) {
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return scaleLinear().domain([1, maxVal]).range([config.visual.minBubbleSize, config.visual.maxBubbleSize])
|
|
56
|
+
}, [
|
|
57
|
+
config.general.type,
|
|
58
|
+
config.columns.primary.name,
|
|
59
|
+
config.visual.minBubbleSize,
|
|
60
|
+
config.visual.maxBubbleSize,
|
|
61
|
+
runtimeData
|
|
62
|
+
])
|
|
63
|
+
|
|
64
|
+
// Get the list of cities to render
|
|
65
|
+
const cityList = useMemo(() => {
|
|
66
|
+
return Object.keys(citiesData).filter(cityName => cityName && citiesData[cityName])
|
|
67
|
+
}, [citiesData])
|
|
68
|
+
|
|
69
|
+
// Early exit for map types that don't use city rendering
|
|
70
|
+
if (!projection) {
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
54
73
|
|
|
55
|
-
|
|
56
|
-
|
|
74
|
+
// Early exit if no cities to render
|
|
75
|
+
if (!cityList.length) {
|
|
76
|
+
return null
|
|
57
77
|
}
|
|
58
|
-
const cityList = Object.keys(citiesData).filter(c => undefined !== c || undefined !== runtimeData[c])
|
|
59
|
-
if (!cityList) return true
|
|
60
78
|
|
|
61
79
|
// Cities output
|
|
62
80
|
return cityList.map((city, i) => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (city === runtimeData[key][config.columns.geo.name]) {
|
|
67
|
-
geoData = runtimeData[key]
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
}
|
|
81
|
+
// Get the city data directly from our memoized citiesData
|
|
82
|
+
const geoData = citiesData[city]
|
|
83
|
+
|
|
71
84
|
if (!geoData) {
|
|
72
|
-
|
|
85
|
+
return null
|
|
73
86
|
}
|
|
74
|
-
const cityDisplayName = titleCase(displayGeoName(city))
|
|
75
87
|
|
|
76
|
-
const
|
|
77
|
-
? applyLegendToRow(geoData, config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
|
|
78
|
-
: runtimeData[city]
|
|
79
|
-
? applyLegendToRow(runtimeData[city], config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
|
|
80
|
-
: false
|
|
88
|
+
const cityDisplayName = displayGeoName(city)
|
|
81
89
|
|
|
82
|
-
|
|
83
|
-
|
|
90
|
+
const legendColors = applyLegendToRow(geoData, config, runtimeLegend, legendMemo, legendSpecialClassLastMemo)
|
|
91
|
+
|
|
92
|
+
if (!legendColors || legendColors.length === 0) {
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Don't render if legend item is disabled
|
|
97
|
+
if (isLegendItemDisabled(geoData, runtimeLegend, legendMemo, legendSpecialClassLastMemo, config)) {
|
|
98
|
+
return null
|
|
84
99
|
}
|
|
85
100
|
|
|
86
|
-
const toolTip = applyTooltipsToGeo(cityDisplayName, geoData
|
|
101
|
+
const toolTip = applyTooltipsToGeo(cityDisplayName, geoData)
|
|
87
102
|
|
|
88
103
|
const radius = config.visual.geoCodeCircleSize || 8
|
|
89
104
|
|
|
@@ -94,19 +109,21 @@ const CityList: React.FC<CityListProps> = ({ setSharedFilterValue, isFilterValue
|
|
|
94
109
|
const geoStrokeColor = getGeoStrokeColor(config)
|
|
95
110
|
|
|
96
111
|
const pin = (
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
<g>
|
|
113
|
+
<title>Select for more information</title>
|
|
114
|
+
<path
|
|
115
|
+
className='marker'
|
|
116
|
+
d='M0,0l-8.8-17.7C-12.1-24.3-7.4-32,0-32h0c7.4,0,12.1,7.7,8.8,14.3L0,0z'
|
|
117
|
+
onClick={() => geoClickHandler(cityDisplayName, geoData)}
|
|
118
|
+
data-tooltip-id={`tooltip__${tooltipId}`}
|
|
119
|
+
data-tooltip-html={toolTip}
|
|
120
|
+
transform={`scale(${radius / 7.5})`}
|
|
121
|
+
stroke={geoStrokeColor}
|
|
122
|
+
strokeWidth={'2px'}
|
|
123
|
+
tabIndex={-1}
|
|
124
|
+
{...additionalProps}
|
|
125
|
+
/>
|
|
126
|
+
</g>
|
|
110
127
|
)
|
|
111
128
|
|
|
112
129
|
let transform = ''
|
|
@@ -130,7 +147,7 @@ const CityList: React.FC<CityListProps> = ({ setSharedFilterValue, isFilterValue
|
|
|
130
147
|
|
|
131
148
|
if (geoData?.[longitudeColumnName] && geoData?.[latitudeColumnName] && config.general.geoType === 'single-state') {
|
|
132
149
|
const statesPicked = getFilterControllingStatesPicked(config, runtimeData)
|
|
133
|
-
const _statesPickedData = topoData?.states?.find(s => statesPicked.includes(s.properties.name))
|
|
150
|
+
const _statesPickedData = (topoData as any)?.states?.find(s => statesPicked.includes(s.properties.name))
|
|
134
151
|
|
|
135
152
|
const newProjection = projection.fitExtent(
|
|
136
153
|
[
|
|
@@ -141,7 +158,7 @@ const CityList: React.FC<CityListProps> = ({ setSharedFilterValue, isFilterValue
|
|
|
141
158
|
)
|
|
142
159
|
let coords = [Number(geoData?.[longitudeColumnName]), Number(geoData?.[latitudeColumnName])]
|
|
143
160
|
transform = `translate(${newProjection(coords)}) scale(${
|
|
144
|
-
config.visual.geoCodeCircleSize / (position.zoom > 1 ? position.zoom : 1)
|
|
161
|
+
config.visual.geoCodeCircleSize / ((position as any).zoom > 1 ? (position as any).zoom : 1)
|
|
145
162
|
})`
|
|
146
163
|
needsPointer = true
|
|
147
164
|
}
|
|
@@ -181,8 +198,7 @@ const CityList: React.FC<CityListProps> = ({ setSharedFilterValue, isFilterValue
|
|
|
181
198
|
|
|
182
199
|
const shapeProps = {
|
|
183
200
|
onClick: () => geoClickHandler(cityDisplayName, geoData),
|
|
184
|
-
size: config.general.type === 'bubble' ? size(geoData[primaryColumnName]) : radius * 30,
|
|
185
|
-
title: 'Select for more information',
|
|
201
|
+
size: config.general.type === 'bubble' && size ? size(geoData[primaryColumnName]) : radius * 30,
|
|
186
202
|
'data-tooltip-id': `tooltip__${tooltipId}`,
|
|
187
203
|
'data-tooltip-html': toolTip,
|
|
188
204
|
stroke: geoStrokeColor,
|
|
@@ -200,48 +216,10 @@ const CityList: React.FC<CityListProps> = ({ setSharedFilterValue, isFilterValue
|
|
|
200
216
|
triangle: <GlyphTriangle {...shapeProps} />
|
|
201
217
|
}
|
|
202
218
|
|
|
203
|
-
|
|
204
|
-
.filter(d => additionalCityStyles?.some(style => String(d[style.column]) === String(style.value)))
|
|
205
|
-
.map(d => {
|
|
206
|
-
const conditionsMatched = additionalCityStyles.find(style => String(d[style.column]) === String(style.value))
|
|
207
|
-
return { ...conditionsMatched, ...d }
|
|
208
|
-
})
|
|
209
|
-
.find(item => {
|
|
210
|
-
return Object.keys(item).find(key => item[key] === city)
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
if (cityStyle !== undefined && cityStyle.shape) {
|
|
214
|
-
if (
|
|
215
|
-
!geoData?.[longitudeColumnName] &&
|
|
216
|
-
!geoData?.[latitudeColumnName] &&
|
|
217
|
-
city &&
|
|
218
|
-
supportedCities[city.toUpperCase()]
|
|
219
|
-
) {
|
|
220
|
-
let translate = `translate(${projection(supportedCities[city.toUpperCase()])})`
|
|
221
|
-
|
|
222
|
-
return (
|
|
223
|
-
<g key={i} transform={translate} style={styles} className='geo-point' tabIndex={-1}>
|
|
224
|
-
{cityStyleShapes[cityStyle.shape.toLowerCase()]}
|
|
225
|
-
</g>
|
|
226
|
-
)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (geoData?.[longitudeColumnName] && geoData?.[latitudeColumnName]) {
|
|
230
|
-
const coords = [Number(geoData?.[longitudeColumnName]), Number(geoData?.[latitudeColumnName])]
|
|
231
|
-
let translate = `translate(${projection(coords)})`
|
|
232
|
-
|
|
233
|
-
return (
|
|
234
|
-
<g key={i} transform={translate} style={styles} className='geo-point' tabIndex={-1}>
|
|
235
|
-
{cityStyleShapes[cityStyle.shape.toLowerCase()]}
|
|
236
|
-
</g>
|
|
237
|
-
)
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
if (legendColors?.[0] === '#000000') return
|
|
241
|
-
|
|
219
|
+
// Render the city marker
|
|
242
220
|
return (
|
|
243
221
|
<g key={i} transform={transform} style={styles} className='geo-point' tabIndex={-1}>
|
|
244
|
-
{cityStyleShapes[config.visual.cityStyle
|
|
222
|
+
{cityStyleShapes[config.visual.cityStyle?.toLowerCase() || 'circle']}
|
|
245
223
|
</g>
|
|
246
224
|
)
|
|
247
225
|
})
|