@cdc/map 4.25.7 → 4.25.8
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.local.md +0 -0
- package/dist/cdcmap.js +22037 -22074
- package/examples/private/filter-map.json +909 -0
- package/examples/private/rsv-data.json +532 -0
- package/examples/private/test.json +222 -640
- package/index.html +34 -35
- package/package.json +3 -3
- package/src/CdcMap.tsx +7 -2
- package/src/CdcMapComponent.tsx +26 -8
- package/src/_stories/CdcMap.stories.tsx +8 -11
- package/src/_stories/_mock/multi-state.json +21389 -0
- package/src/components/CityList.tsx +4 -4
- package/src/components/DataTable.tsx +8 -4
- package/src/components/EditorPanel/components/EditorPanel.tsx +24 -38
- package/src/components/Legend/components/Legend.tsx +23 -35
- package/src/components/Modal.tsx +2 -8
- package/src/components/NavigationMenu.tsx +4 -1
- package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +21 -15
- package/src/components/UsaMap/components/TerritoriesSection.tsx +2 -2
- package/src/components/UsaMap/components/UsaMap.County.tsx +6 -1
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +36 -24
- package/src/components/UsaMap/helpers/map.ts +16 -8
- package/src/components/WorldMap/WorldMap.tsx +17 -0
- package/src/context.ts +1 -0
- package/src/data/initial-state.js +8 -6
- package/src/data/supported-geos.js +185 -2
- package/src/helpers/addUIDs.ts +8 -8
- package/src/helpers/applyColorToLegend.ts +24 -43
- package/src/helpers/applyLegendToRow.ts +5 -7
- package/src/helpers/displayGeoName.ts +11 -6
- package/src/helpers/formatLegendLocation.ts +1 -3
- package/src/helpers/generateRuntimeLegend.ts +149 -333
- package/src/helpers/getStatesPicked.ts +11 -0
- package/src/helpers/handleMapAriaLabels.ts +2 -2
- package/src/hooks/useStateZoom.tsx +116 -86
- package/src/index.jsx +6 -1
- package/src/scss/main.scss +23 -12
- package/src/store/map.actions.ts +2 -2
- package/src/store/map.reducer.ts +4 -4
- package/src/types/MapConfig.ts +2 -3
- package/src/types/MapContext.ts +2 -1
- package/src/types/runtimeLegend.ts +1 -15
- package/src/_stories/_mock/floating-point.json +0 -427
- package/src/helpers/getStatePicked.ts +0 -8
|
@@ -33,16 +33,6 @@ export type GeneratedLegend = {
|
|
|
33
33
|
items: LegendItem[] | []
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
// Helper function to convert and round values consistently
|
|
37
|
-
const convertAndRoundValue = (value: any, roundToPlace: number): number => {
|
|
38
|
-
const num = Number(value)
|
|
39
|
-
if (isNaN(num)) return NaN
|
|
40
|
-
|
|
41
|
-
// Apply rounding to handle floating-point precision issues
|
|
42
|
-
const factor = Math.pow(10, roundToPlace)
|
|
43
|
-
return Math.round(num * factor) / factor
|
|
44
|
-
}
|
|
45
|
-
|
|
46
36
|
export const generateRuntimeLegend = (
|
|
47
37
|
configObj,
|
|
48
38
|
runtimeData: object[],
|
|
@@ -61,8 +51,8 @@ export const generateRuntimeLegend = (
|
|
|
61
51
|
if (!legendSpecialClassLastMemo) Error('No legend special class last memo provided')
|
|
62
52
|
|
|
63
53
|
// Define variables..
|
|
64
|
-
const newLegendMemo = new Map
|
|
65
|
-
const newLegendSpecialClassLastMemo = new Map
|
|
54
|
+
const newLegendMemo = new Map() // Reset memoization
|
|
55
|
+
const newLegendSpecialClassLastMemo = new Map() // Reset bin memoization
|
|
66
56
|
const countryKeys = Object.keys(supportedCountries)
|
|
67
57
|
const { legend, columns, general } = configObj
|
|
68
58
|
const primaryColName = columns.primary.name
|
|
@@ -89,12 +79,9 @@ export const generateRuntimeLegend = (
|
|
|
89
79
|
// Unified will base the legend off ALL the data maps received. Otherwise, it will use
|
|
90
80
|
let dataSet = legend.unified ? data : Object?.values(runtimeData)
|
|
91
81
|
|
|
92
|
-
|
|
93
|
-
let domainNums = Array.from(
|
|
94
|
-
new Set(dataSet?.map(item => convertAndRoundValue(item[configObj.columns.primary.name], roundToPlace)))
|
|
95
|
-
)
|
|
82
|
+
let domainNums = Array.from(new Set(dataSet?.map(item => item[configObj.columns.primary.name])))
|
|
96
83
|
.filter(d => typeof d === 'number' && !isNaN(d))
|
|
97
|
-
.sort((a, b) =>
|
|
84
|
+
.sort((a, b) => a - b)
|
|
98
85
|
|
|
99
86
|
let specialClasses = 0
|
|
100
87
|
let specialClassesHash = {}
|
|
@@ -116,6 +103,12 @@ export const generateRuntimeLegend = (
|
|
|
116
103
|
label: specialClass.label
|
|
117
104
|
})
|
|
118
105
|
|
|
106
|
+
result.items[result.items.length - 1].color = applyColorToLegend(
|
|
107
|
+
result.items.length - 1,
|
|
108
|
+
configObj,
|
|
109
|
+
result.items
|
|
110
|
+
)
|
|
111
|
+
|
|
119
112
|
specialClasses += 1
|
|
120
113
|
}
|
|
121
114
|
|
|
@@ -124,7 +117,7 @@ export const generateRuntimeLegend = (
|
|
|
124
117
|
// color the configObj if val is in row
|
|
125
118
|
specialColor = result.items.findIndex(p => p.value === val)
|
|
126
119
|
|
|
127
|
-
newLegendMemo.set(
|
|
120
|
+
newLegendMemo.set(hashObj(row), specialColor)
|
|
128
121
|
|
|
129
122
|
return false
|
|
130
123
|
}
|
|
@@ -146,14 +139,14 @@ export const generateRuntimeLegend = (
|
|
|
146
139
|
if (undefined === value) continue
|
|
147
140
|
|
|
148
141
|
if (false === uniqueValues.has(value)) {
|
|
149
|
-
uniqueValues.set(value, [
|
|
142
|
+
uniqueValues.set(value, [hashObj(row)])
|
|
150
143
|
count++
|
|
151
144
|
} else {
|
|
152
|
-
uniqueValues.get(value).push(
|
|
145
|
+
uniqueValues.get(value).push(hashObj(row))
|
|
153
146
|
}
|
|
154
147
|
}
|
|
155
148
|
|
|
156
|
-
let sorted =
|
|
149
|
+
let sorted = [...uniqueValues.keys()]
|
|
157
150
|
|
|
158
151
|
if (legend.additionalCategories) {
|
|
159
152
|
legend.additionalCategories.forEach(additionalCategory => {
|
|
@@ -191,103 +184,54 @@ export const generateRuntimeLegend = (
|
|
|
191
184
|
let arr = uniqueValues.get(val)
|
|
192
185
|
|
|
193
186
|
if (arr) {
|
|
194
|
-
arr.forEach(hashedRow => newLegendMemo.set(
|
|
187
|
+
arr.forEach(hashedRow => newLegendMemo.set(hashedRow, lastIdx))
|
|
195
188
|
}
|
|
196
189
|
})
|
|
197
190
|
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
item: { ...item }, // Create a copy to avoid reference issues
|
|
205
|
-
originalIndex: index
|
|
206
|
-
}))
|
|
207
|
-
|
|
208
|
-
// Move all special legend items from "Special Classes" to the end of the legend
|
|
209
|
-
sortSpecialClassesLast(result, legend)
|
|
210
|
-
|
|
211
|
-
// Update legend memo to reflect new positions after sorting for categorical legends
|
|
212
|
-
if (legend.showSpecialClassesLast) {
|
|
213
|
-
const updatedLegendMemo = new Map()
|
|
191
|
+
// Add color to new legend item (normal items only, not special classes)
|
|
192
|
+
for (let i = 0; i < result.items.length; i++) {
|
|
193
|
+
if (!result.items[i].special) {
|
|
194
|
+
result.items[i].color = applyColorToLegend(i, configObj, result.items)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
214
197
|
|
|
215
|
-
|
|
216
|
-
|
|
198
|
+
// Now apply special class colors last, to overwrite if needed
|
|
199
|
+
for (let i = 0; i < result.items.length; i++) {
|
|
200
|
+
if (result.items[i].special) {
|
|
201
|
+
result.items[i].color = applyColorToLegend(i, configObj, result.items)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
217
204
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
205
|
+
// Overwrite legendMemo for special class rows to ensure correct color lookup
|
|
206
|
+
result.items.forEach((item, idx) => {
|
|
207
|
+
if (item.special) {
|
|
208
|
+
// Find all rows in the data that match this special class value
|
|
209
|
+
let specialRows = data.filter(row => {
|
|
210
|
+
// If special class has a key, use it, otherwise use primaryColName
|
|
211
|
+
const key = legend.specialClasses.find(sc => String(sc.value) === String(item.value))?.key || primaryColName
|
|
212
|
+
return String(row[key]) === String(item.value)
|
|
226
213
|
})
|
|
214
|
+
specialRows.forEach(row => {
|
|
215
|
+
newLegendMemo.set(hashObj(row), idx)
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
})
|
|
227
219
|
|
|
228
|
-
|
|
229
|
-
indexMapping.set(originalData.originalIndex, newIndex)
|
|
230
|
-
}
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
// Update all memo entries using the index mapping
|
|
234
|
-
newLegendMemo.forEach((originalIndex, rowHash) => {
|
|
235
|
-
const newIndex = indexMapping.get(originalIndex)
|
|
236
|
-
if (newIndex !== undefined) {
|
|
237
|
-
updatedLegendMemo.set(rowHash, newIndex)
|
|
238
|
-
} else {
|
|
239
|
-
// Fallback: clamp to valid range
|
|
240
|
-
const clampedIndex = Math.min(originalIndex, result.items.length - 1)
|
|
241
|
-
updatedLegendMemo.set(rowHash, clampedIndex)
|
|
242
|
-
}
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
legendMemo.current = updatedLegendMemo
|
|
246
|
-
|
|
247
|
-
// Apply colors based on original positions before sorting for categorical legends
|
|
248
|
-
for (let i = 0; i < result.items.length; i++) {
|
|
249
|
-
const currentItem = result.items[i]
|
|
220
|
+
legendMemo.current = newLegendMemo
|
|
250
221
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return item.special && item.value === currentItem.value
|
|
255
|
-
} else {
|
|
256
|
-
return !item.special && item.value === currentItem.value
|
|
257
|
-
}
|
|
258
|
-
})
|
|
222
|
+
// before returning the legend result
|
|
223
|
+
// add property for bin number and set to index location
|
|
224
|
+
setBinNumbers(result)
|
|
259
225
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const contextArray = originalCategoricalItems.slice(0, originalData.originalIndex + 1).map(o => o.item)
|
|
263
|
-
const appliedColor = applyColorToLegend(originalData.originalIndex, configObj, contextArray)
|
|
264
|
-
result.items[i].color = appliedColor
|
|
265
|
-
} else {
|
|
266
|
-
// Fallback: apply color normally
|
|
267
|
-
const contextArray = result.items.slice(0, i + 1)
|
|
268
|
-
const appliedColor = applyColorToLegend(i, configObj, contextArray)
|
|
269
|
-
result.items[i].color = appliedColor
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
} else {
|
|
273
|
-
// Simple case: no special sorting, just apply colors normally
|
|
274
|
-
legendMemo.current = newLegendMemo
|
|
275
|
-
|
|
276
|
-
for (let i = 0; i < result.items.length; i++) {
|
|
277
|
-
// Create a context array that simulates the original incremental state
|
|
278
|
-
const contextArray = result.items.slice(0, i + 1)
|
|
279
|
-
const appliedColor = applyColorToLegend(i, configObj, contextArray)
|
|
280
|
-
result.items[i].color = appliedColor
|
|
281
|
-
}
|
|
282
|
-
}
|
|
226
|
+
// Move all special legend items from "Special Classes" to the end of the legend
|
|
227
|
+
sortSpecialClassesLast(result, legend)
|
|
283
228
|
|
|
284
229
|
const assignSpecialClassLastIndex = (value, key) => {
|
|
285
230
|
const newIndex = result.items.findIndex(d => d.bin === value)
|
|
286
231
|
newLegendSpecialClassLastMemo.set(key, newIndex)
|
|
287
232
|
}
|
|
288
233
|
|
|
289
|
-
|
|
290
|
-
legendMemo.current.forEach(assignSpecialClassLastIndex)
|
|
234
|
+
newLegendMemo.forEach(assignSpecialClassLastIndex)
|
|
291
235
|
legendSpecialClassLastMemo.current = newLegendSpecialClassLastMemo
|
|
292
236
|
|
|
293
237
|
return result
|
|
@@ -304,26 +248,30 @@ export const generateRuntimeLegend = (
|
|
|
304
248
|
if (true === legend.separateZero && !general.equalNumberOptIn) {
|
|
305
249
|
let addLegendItem = false
|
|
306
250
|
|
|
307
|
-
// First, add the zero bucket
|
|
308
|
-
result.items.push({
|
|
309
|
-
min: 0,
|
|
310
|
-
max: 0
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
// Then process zero values and assign them to the zero bucket (index 0)
|
|
314
251
|
for (let i = 0; i < dataSet.length; i++) {
|
|
315
252
|
if (dataSet[i][primaryColName] === 0) {
|
|
316
253
|
addLegendItem = true
|
|
317
254
|
|
|
318
255
|
let row = dataSet.splice(i, 1)[0]
|
|
319
256
|
|
|
320
|
-
newLegendMemo.set(
|
|
257
|
+
newLegendMemo.set(hashObj(row), result.items.length)
|
|
321
258
|
i--
|
|
322
259
|
}
|
|
323
260
|
}
|
|
324
261
|
|
|
325
262
|
if (addLegendItem) {
|
|
326
263
|
legendNumber -= 1 // This zero takes up one legend item
|
|
264
|
+
|
|
265
|
+
// Add new legend item
|
|
266
|
+
result.items.push({
|
|
267
|
+
min: 0,
|
|
268
|
+
max: 0
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
let lastIdx = result.items.length - 1
|
|
272
|
+
|
|
273
|
+
// Add color to new legend item
|
|
274
|
+
result.items[lastIdx].color = applyColorToLegend(lastIdx, configObj, result.items)
|
|
327
275
|
}
|
|
328
276
|
}
|
|
329
277
|
|
|
@@ -365,7 +313,7 @@ export const generateRuntimeLegend = (
|
|
|
365
313
|
|
|
366
314
|
// eslint-disable-next-line
|
|
367
315
|
removedRows.forEach(row => {
|
|
368
|
-
newLegendMemo.set(
|
|
316
|
+
newLegendMemo.set(hashObj(row), result.items.length)
|
|
369
317
|
})
|
|
370
318
|
|
|
371
319
|
result.items.push({
|
|
@@ -373,25 +321,35 @@ export const generateRuntimeLegend = (
|
|
|
373
321
|
max
|
|
374
322
|
})
|
|
375
323
|
|
|
324
|
+
result.items[result.items.length - 1].color = applyColorToLegend(
|
|
325
|
+
result.items.length - 1,
|
|
326
|
+
configObj,
|
|
327
|
+
result.items
|
|
328
|
+
)
|
|
329
|
+
|
|
376
330
|
changingNumber -= 1
|
|
377
331
|
numberOfRows -= chunkAmt
|
|
378
332
|
}
|
|
379
333
|
} else {
|
|
380
|
-
// Use the appropriate rounding precision
|
|
381
|
-
const roundingPrecision =
|
|
382
|
-
general?.equalNumberOptIn && columns?.primary?.roundToPlace !== undefined
|
|
383
|
-
? Number(columns.primary.roundToPlace)
|
|
384
|
-
: roundToPlace
|
|
385
|
-
|
|
386
334
|
let colors = colorPalettes[configObj.color]
|
|
387
335
|
let colorRange = colors.slice(0, legend.numberOfItems)
|
|
388
336
|
|
|
389
337
|
const getDomain = () => {
|
|
390
|
-
|
|
338
|
+
// backwards compatibility
|
|
339
|
+
if (columns?.primary?.roundToPlace !== undefined && general?.equalNumberOptIn) {
|
|
340
|
+
return _.uniq(
|
|
341
|
+
dataSet.map(item => Number(item[columns.primary.name]).toFixed(Number(columns?.primary?.roundToPlace)))
|
|
342
|
+
)
|
|
343
|
+
}
|
|
344
|
+
return _.uniq(dataSet.map(item => Math.round(Number(item[columns.primary.name]))))
|
|
391
345
|
}
|
|
392
346
|
|
|
393
347
|
const getBreaks = scale => {
|
|
394
|
-
|
|
348
|
+
// backwards compatibility
|
|
349
|
+
if (columns?.primary?.roundToPlace !== undefined && general?.equalNumberOptIn) {
|
|
350
|
+
return scale.quantiles().map(b => Number(b)?.toFixed(Number(columns?.primary?.roundToPlace)))
|
|
351
|
+
}
|
|
352
|
+
return scale.quantiles().map(item => Number(Math.round(item)))
|
|
395
353
|
}
|
|
396
354
|
|
|
397
355
|
let scale = d3
|
|
@@ -405,125 +363,85 @@ export const generateRuntimeLegend = (
|
|
|
405
363
|
cachedBreaks = breaks
|
|
406
364
|
}
|
|
407
365
|
|
|
408
|
-
//
|
|
409
|
-
if (
|
|
410
|
-
|
|
411
|
-
result.items.push({
|
|
412
|
-
min: 0,
|
|
413
|
-
max: 0
|
|
414
|
-
})
|
|
415
|
-
|
|
416
|
-
// Assign all zero values to this bucket
|
|
417
|
-
dataSet.forEach(row => {
|
|
418
|
-
let number = convertAndRoundValue(row[columns.primary.name], roundingPrecision)
|
|
419
|
-
if (number === 0) {
|
|
420
|
-
newLegendMemo.set(String(hashObj(row)), 0)
|
|
421
|
-
}
|
|
422
|
-
})
|
|
366
|
+
// if separating zero force it into breaks
|
|
367
|
+
if (cachedBreaks[0] !== 0) {
|
|
368
|
+
cachedBreaks.unshift(0)
|
|
423
369
|
}
|
|
424
370
|
|
|
425
|
-
//
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (dataForBreaks.length > 0) {
|
|
431
|
-
// Recalculate scale and breaks for non-zero data
|
|
432
|
-
const getNonZeroDomain = () => {
|
|
433
|
-
return _.uniq(
|
|
434
|
-
dataForBreaks.map(item => convertAndRoundValue(item[columns.primary.name], roundingPrecision))
|
|
435
|
-
).sort((a: number, b: number) => a - b)
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const nonZeroDomain = getNonZeroDomain()
|
|
439
|
-
const numberOfBuckets = legend.separateZero ? legend.numberOfItems - 1 : legend.numberOfItems
|
|
371
|
+
// eslint-disable-next-line array-callback-return
|
|
372
|
+
cachedBreaks.map((item, index) => {
|
|
373
|
+
const setMin = index => {
|
|
374
|
+
let min = cachedBreaks[index]
|
|
440
375
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
.range(colorPalettes[configObj.color].slice(0, numberOfBuckets))
|
|
376
|
+
// if first break is a seperated zero, min is zero
|
|
377
|
+
if (index === 0 && legend.separateZero) {
|
|
378
|
+
min = 0
|
|
379
|
+
}
|
|
446
380
|
|
|
447
|
-
|
|
381
|
+
// if we're on the second break, and separating out zero, increment min to 1.
|
|
382
|
+
if (index === 1 && legend.separateZero) {
|
|
383
|
+
min = 1
|
|
384
|
+
}
|
|
448
385
|
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
const
|
|
452
|
-
|
|
386
|
+
// For non-first ranges, add small increment to prevent overlap
|
|
387
|
+
if (index > 0 && !legend.separateZero) {
|
|
388
|
+
const decimalPlace = Number(configObj?.columns?.primary?.roundToPlace) || 1
|
|
389
|
+
min = Number(cachedBreaks[index]) + Math.pow(10, -decimalPlace)
|
|
390
|
+
}
|
|
453
391
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
buckets.push({
|
|
457
|
-
min: sortedDomain[0],
|
|
458
|
-
max: sortedDomain[sortedDomain.length - 1]
|
|
459
|
-
})
|
|
460
|
-
} else {
|
|
461
|
-
// First bucket: min value to first break
|
|
462
|
-
buckets.push({
|
|
463
|
-
min: sortedDomain[0],
|
|
464
|
-
max: quantileBreaks[0]
|
|
465
|
-
})
|
|
392
|
+
return min
|
|
393
|
+
}
|
|
466
394
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
buckets.push({
|
|
471
|
-
min: convertAndRoundValue(quantileBreaks[i - 1] + increment, roundingPrecision),
|
|
472
|
-
max: quantileBreaks[i]
|
|
473
|
-
})
|
|
474
|
-
}
|
|
395
|
+
const getDecimalPlace = n => {
|
|
396
|
+
return Math.pow(10, -n)
|
|
397
|
+
}
|
|
475
398
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
const increment = Math.pow(10, -roundingPrecision)
|
|
479
|
-
buckets.push({
|
|
480
|
-
min: convertAndRoundValue(quantileBreaks[quantileBreaks.length - 1] + increment, roundingPrecision),
|
|
481
|
-
max: sortedDomain[sortedDomain.length - 1]
|
|
482
|
-
})
|
|
483
|
-
}
|
|
484
|
-
}
|
|
399
|
+
const setMax = index => {
|
|
400
|
+
let max = Number(breaks[index + 1])
|
|
485
401
|
|
|
486
|
-
|
|
402
|
+
if (index === 0 && legend.separateZero) {
|
|
403
|
+
max = 0
|
|
487
404
|
}
|
|
488
405
|
|
|
489
|
-
|
|
406
|
+
if (index + 1 === breaks.length) {
|
|
407
|
+
max = Number(domainNums[domainNums.length - 1])
|
|
408
|
+
}
|
|
490
409
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
result.items.push(bucket)
|
|
494
|
-
})
|
|
410
|
+
return max
|
|
411
|
+
}
|
|
495
412
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
let number = convertAndRoundValue(row[columns.primary.name], roundingPrecision)
|
|
499
|
-
let assigned = false
|
|
413
|
+
let min = setMin(index)
|
|
414
|
+
let max = setMax(index)
|
|
500
415
|
|
|
501
|
-
|
|
502
|
-
|
|
416
|
+
result.items.push({
|
|
417
|
+
min,
|
|
418
|
+
max
|
|
419
|
+
})
|
|
420
|
+
result.items[result.items.length - 1].color = applyColorToLegend(
|
|
421
|
+
result.items.length - 1,
|
|
422
|
+
configObj,
|
|
423
|
+
result.items
|
|
424
|
+
)
|
|
503
425
|
|
|
504
|
-
|
|
426
|
+
dataSet.forEach(row => {
|
|
427
|
+
let number = row[columns.primary.name]
|
|
428
|
+
let updated = result.items.length - 1
|
|
505
429
|
|
|
506
|
-
|
|
507
|
-
newLegendMemo.set(String(hashObj(row)), itemIndex)
|
|
508
|
-
assigned = true
|
|
509
|
-
break
|
|
510
|
-
}
|
|
511
|
-
}
|
|
430
|
+
if (result.items?.[updated]?.min === undefined || result.items?.[updated]?.max === undefined) return
|
|
512
431
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
newLegendMemo.set(String(hashObj(row)), fallbackIndex)
|
|
432
|
+
// Check if this row hasn't been assigned yet to prevent double assignment
|
|
433
|
+
if (!newLegendMemo.has(hashObj(row))) {
|
|
434
|
+
if (number >= result.items[updated].min && number <= result.items[updated].max) {
|
|
435
|
+
newLegendMemo.set(hashObj(row), updated)
|
|
518
436
|
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
437
|
+
}
|
|
438
|
+
})
|
|
439
|
+
})
|
|
522
440
|
|
|
523
441
|
// Final pass: handle any unassigned rows
|
|
524
442
|
dataSet.forEach(row => {
|
|
525
|
-
if (!newLegendMemo.has(
|
|
526
|
-
let number =
|
|
443
|
+
if (!newLegendMemo.has(hashObj(row))) {
|
|
444
|
+
let number = row[columns.primary.name]
|
|
527
445
|
let assigned = false
|
|
528
446
|
|
|
529
447
|
// Find the correct range for this value - check both boundaries
|
|
@@ -534,7 +452,7 @@ export const generateRuntimeLegend = (
|
|
|
534
452
|
|
|
535
453
|
// Check if value falls within range (inclusive of both min and max)
|
|
536
454
|
if (number >= item.min && number <= item.max) {
|
|
537
|
-
newLegendMemo.set(
|
|
455
|
+
newLegendMemo.set(hashObj(row), itemIndex)
|
|
538
456
|
assigned = true
|
|
539
457
|
break
|
|
540
458
|
}
|
|
@@ -555,7 +473,7 @@ export const generateRuntimeLegend = (
|
|
|
555
473
|
}
|
|
556
474
|
}
|
|
557
475
|
|
|
558
|
-
newLegendMemo.set(
|
|
476
|
+
newLegendMemo.set(hashObj(row), closestIndex)
|
|
559
477
|
}
|
|
560
478
|
}
|
|
561
479
|
})
|
|
@@ -591,7 +509,7 @@ export const generateRuntimeLegend = (
|
|
|
591
509
|
|
|
592
510
|
// Add rows in dataSet that belong to this new legend item since we've got the data sorted
|
|
593
511
|
while (pointer < dataSet.length && dataSet[pointer][primaryColName] <= max) {
|
|
594
|
-
newLegendMemo.set(
|
|
512
|
+
newLegendMemo.set(hashObj(dataSet[pointer]), result.items.length)
|
|
595
513
|
pointer += 1
|
|
596
514
|
}
|
|
597
515
|
|
|
@@ -601,11 +519,19 @@ export const generateRuntimeLegend = (
|
|
|
601
519
|
}
|
|
602
520
|
|
|
603
521
|
result.items.push(range)
|
|
522
|
+
|
|
523
|
+
result.items[result.items.length - 1].color = applyColorToLegend(
|
|
524
|
+
result.items.length - 1,
|
|
525
|
+
configObj,
|
|
526
|
+
result.items
|
|
527
|
+
)
|
|
604
528
|
}
|
|
605
529
|
}
|
|
606
530
|
|
|
607
531
|
setBinNumbers(result)
|
|
608
532
|
|
|
533
|
+
legendMemo.current = newLegendMemo
|
|
534
|
+
|
|
609
535
|
if (general.geoType === 'world') {
|
|
610
536
|
const runtimeDataKeys = Object.keys(runtimeData)
|
|
611
537
|
const isCountriesWithNoDataState =
|
|
@@ -621,123 +547,13 @@ export const generateRuntimeLegend = (
|
|
|
621
547
|
}
|
|
622
548
|
|
|
623
549
|
setBinNumbers(result)
|
|
624
|
-
|
|
625
|
-
// Only do complex sorting and color mapping if showSpecialClassesLast is enabled
|
|
626
|
-
if (legend.showSpecialClassesLast) {
|
|
627
|
-
// Store original legend items with their indices before sorting
|
|
628
|
-
const originalItems = result.items.map((item, index) => ({
|
|
629
|
-
item: { ...item }, // Create a copy to avoid reference issues
|
|
630
|
-
originalIndex: index
|
|
631
|
-
}))
|
|
632
|
-
|
|
633
|
-
sortSpecialClassesLast(result, legend)
|
|
634
|
-
|
|
635
|
-
// Update legend memo to reflect new positions after sorting
|
|
636
|
-
const updatedLegendMemo = new Map()
|
|
637
|
-
|
|
638
|
-
// Create a mapping from old index to new index
|
|
639
|
-
const indexMapping = new Map()
|
|
640
|
-
|
|
641
|
-
// For each item in the new sorted order, find its original position
|
|
642
|
-
result.items.forEach((newItem, newIndex) => {
|
|
643
|
-
const originalData = originalItems.find(({ item }) => {
|
|
644
|
-
if (newItem.special) {
|
|
645
|
-
return item.special && item.value === newItem.value
|
|
646
|
-
} else {
|
|
647
|
-
return !item.special && item.min === newItem.min && item.max === newItem.max
|
|
648
|
-
}
|
|
649
|
-
})
|
|
650
|
-
|
|
651
|
-
if (originalData) {
|
|
652
|
-
indexMapping.set(originalData.originalIndex, newIndex)
|
|
653
|
-
}
|
|
654
|
-
})
|
|
655
|
-
|
|
656
|
-
// Update all memo entries using the index mapping
|
|
657
|
-
newLegendMemo.forEach((originalIndex, rowHash) => {
|
|
658
|
-
const newIndex = indexMapping.get(originalIndex)
|
|
659
|
-
if (newIndex !== undefined) {
|
|
660
|
-
updatedLegendMemo.set(rowHash, newIndex)
|
|
661
|
-
} else {
|
|
662
|
-
// For unmapped entries, check if it was originally a special class
|
|
663
|
-
const originalItem = originalItems[originalIndex]?.item
|
|
664
|
-
if (originalItem?.special) {
|
|
665
|
-
// Find the special class in its new position
|
|
666
|
-
const specialIndex = result.items.findIndex(item => item.special && item.value === originalItem.value)
|
|
667
|
-
if (specialIndex !== -1) {
|
|
668
|
-
updatedLegendMemo.set(rowHash, specialIndex)
|
|
669
|
-
} else {
|
|
670
|
-
// Fallback: clamp to valid range
|
|
671
|
-
const clampedIndex = Math.min(originalIndex, result.items.length - 1)
|
|
672
|
-
updatedLegendMemo.set(rowHash, clampedIndex)
|
|
673
|
-
}
|
|
674
|
-
} else {
|
|
675
|
-
// Fallback: clamp to valid range
|
|
676
|
-
const clampedIndex = Math.min(originalIndex, result.items.length - 1)
|
|
677
|
-
updatedLegendMemo.set(rowHash, clampedIndex)
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
})
|
|
681
|
-
|
|
682
|
-
legendMemo.current = updatedLegendMemo
|
|
683
|
-
|
|
684
|
-
// Apply colors based on original positions to maintain proper color sequence
|
|
685
|
-
for (let i = 0; i < result.items.length; i++) {
|
|
686
|
-
const currentItem = result.items[i]
|
|
687
|
-
|
|
688
|
-
// Find the original position of this item
|
|
689
|
-
const originalData = originalItems.find(({ item }) => {
|
|
690
|
-
if (currentItem.special) {
|
|
691
|
-
return item.special && item.value === currentItem.value
|
|
692
|
-
} else {
|
|
693
|
-
return !item.special && item.min === currentItem.min && item.max === currentItem.max
|
|
694
|
-
}
|
|
695
|
-
})
|
|
696
|
-
|
|
697
|
-
if (originalData) {
|
|
698
|
-
// Use the original index for color calculation to maintain proper color sequence
|
|
699
|
-
const contextArray = originalItems.slice(0, originalData.originalIndex + 1).map(o => o.item)
|
|
700
|
-
const appliedColor = applyColorToLegend(originalData.originalIndex, configObj, contextArray)
|
|
701
|
-
result.items[i].color = appliedColor
|
|
702
|
-
} else {
|
|
703
|
-
// Fallback: apply color normally
|
|
704
|
-
const contextArray = result.items.slice(0, i + 1)
|
|
705
|
-
const appliedColor = applyColorToLegend(i, configObj, contextArray)
|
|
706
|
-
result.items[i].color = appliedColor
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
// Final step: Ensure special class rows are correctly mapped to their legend items
|
|
711
|
-
result.items.forEach((item, idx) => {
|
|
712
|
-
if (item.special) {
|
|
713
|
-
// Find all rows in the original data that match this special class value
|
|
714
|
-
let specialRows = data.filter(row => {
|
|
715
|
-
// If special class has a key, use it, otherwise use primaryColName
|
|
716
|
-
const key = legend.specialClasses.find(sc => String(sc.value) === String(item.value))?.key || primaryColName
|
|
717
|
-
return String(row[key]) === String(item.value)
|
|
718
|
-
})
|
|
719
|
-
specialRows.forEach(row => {
|
|
720
|
-
legendMemo.current.set(String(hashObj(row)), idx)
|
|
721
|
-
})
|
|
722
|
-
}
|
|
723
|
-
})
|
|
724
|
-
} else {
|
|
725
|
-
legendMemo.current = newLegendMemo
|
|
726
|
-
|
|
727
|
-
for (let i = 0; i < result.items.length; i++) {
|
|
728
|
-
const contextArray = result.items.slice(0, i + 1)
|
|
729
|
-
const appliedColor = applyColorToLegend(i, configObj, contextArray)
|
|
730
|
-
result.items[i].color = appliedColor
|
|
731
|
-
}
|
|
732
|
-
}
|
|
550
|
+
sortSpecialClassesLast(result, legend)
|
|
733
551
|
|
|
734
552
|
const assignSpecialClassLastIndex = (value, key) => {
|
|
735
553
|
const newIndex = result.items.findIndex(d => d.bin === value)
|
|
736
554
|
newLegendSpecialClassLastMemo.set(key, newIndex)
|
|
737
555
|
}
|
|
738
|
-
|
|
739
|
-
// Use the current legend memo (which might have been updated after sorting)
|
|
740
|
-
legendMemo.current.forEach(assignSpecialClassLastIndex)
|
|
556
|
+
newLegendMemo.forEach(assignSpecialClassLastIndex)
|
|
741
557
|
legendSpecialClassLastMemo.current = newLegendSpecialClassLastMemo
|
|
742
558
|
|
|
743
559
|
return result
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { getFilterControllingStatesPicked } from '../components/UsaMap/helpers/map'
|
|
2
|
+
import { supportedStatesFipsCodes } from '../data/supported-geos'
|
|
3
|
+
|
|
4
|
+
export const getStatesPicked = (config, runtimeData) => {
|
|
5
|
+
const stateNames = getFilterControllingStatesPicked(config, runtimeData)
|
|
6
|
+
|
|
7
|
+
return stateNames.map(stateName => ({
|
|
8
|
+
fipsCode: Object.keys(supportedStatesFipsCodes).find(key => supportedStatesFipsCodes[key] === stateName),
|
|
9
|
+
stateName
|
|
10
|
+
}))
|
|
11
|
+
}
|