@cdc/map 4.25.6-1 → 4.25.7
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 +43647 -22049
- package/index.html +9 -0
- package/package.json +3 -3
- package/src/CdcMap.tsx +5 -0
- package/src/CdcMapComponent.tsx +31 -4
- package/src/_stories/CdcMap.stories.tsx +11 -1
- package/src/_stories/_mock/floating-point.json +427 -0
- package/src/components/EditorPanel/components/EditorPanel.tsx +52 -51
- package/src/components/Legend/components/Legend.tsx +33 -3
- package/src/helpers/applyColorToLegend.ts +43 -24
- package/src/helpers/applyLegendToRow.ts +7 -5
- package/src/helpers/generateRuntimeLegend.ts +334 -150
- package/src/scss/main.scss +1 -1
- package/src/types/runtimeLegend.ts +15 -1
|
@@ -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 &&
|
|
@@ -94,7 +94,8 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
94
94
|
label: parse(legendLabel),
|
|
95
95
|
disabled: entry.disabled,
|
|
96
96
|
special: entry.hasOwnProperty('special'),
|
|
97
|
-
value: [entry.min, entry.max]
|
|
97
|
+
value: legend.type === 'category' ? entry.value : [entry.min, entry.max],
|
|
98
|
+
categoryValue: legend.type === 'category' ? entry.value : undefined
|
|
98
99
|
}
|
|
99
100
|
})
|
|
100
101
|
} catch (e) {
|
|
@@ -122,19 +123,48 @@ const Legend = forwardRef<HTMLDivElement, LegendProps>((props, ref) => {
|
|
|
122
123
|
dispatch({ type: 'SET_ACCESSIBLE_STATUS', payload: message })
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
// Find the correct runtime index for toggling
|
|
127
|
+
// This is needed because special classes may have been moved to the end
|
|
128
|
+
const findRuntimeIndex = () => {
|
|
129
|
+
if (!runtimeLegend.items) return idx
|
|
130
|
+
|
|
131
|
+
return runtimeLegend.items.findIndex(runtimeItem => {
|
|
132
|
+
if (item.special && runtimeItem.special) {
|
|
133
|
+
// For special classes, match by label (since formatted item label comes from runtime item)
|
|
134
|
+
const runtimeLabel = runtimeItem.label || runtimeItem.value
|
|
135
|
+
const itemLabel = typeof item.label === 'string' ? item.label : item.label?.props?.children || item.label
|
|
136
|
+
return runtimeLabel === itemLabel
|
|
137
|
+
} else if (!item.special && !runtimeItem.special) {
|
|
138
|
+
// For categorical/qualitative items, match by single value
|
|
139
|
+
if (config.legend.type === 'category' && item.categoryValue !== undefined) {
|
|
140
|
+
return runtimeItem.value === item.categoryValue
|
|
141
|
+
}
|
|
142
|
+
// For numeric items, match by min/max values
|
|
143
|
+
return runtimeItem.min === item.value?.[0] && runtimeItem.max === item.value?.[1]
|
|
144
|
+
}
|
|
145
|
+
return false
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const runtimeIndex = findRuntimeIndex()
|
|
150
|
+
const safeRuntimeIndex = runtimeIndex >= 0 ? runtimeIndex : idx
|
|
151
|
+
|
|
125
152
|
return (
|
|
126
153
|
<li
|
|
127
154
|
className={handleListItemClass()}
|
|
128
155
|
key={idx}
|
|
129
156
|
title={`Legend item ${item.label} - Click to disable`}
|
|
130
|
-
onClick={() =>
|
|
157
|
+
onClick={() =>
|
|
158
|
+
toggleLegendActive(safeRuntimeIndex, item.label, runtimeLegend, setRuntimeLegend, setAccessibleStatus)
|
|
159
|
+
}
|
|
131
160
|
onKeyDown={e => {
|
|
132
161
|
if (e.key === 'Enter') {
|
|
133
162
|
e.preventDefault()
|
|
134
|
-
toggleLegendActive(
|
|
163
|
+
toggleLegendActive(safeRuntimeIndex, item.label, runtimeLegend, setRuntimeLegend, setAccessibleStatus)
|
|
135
164
|
}
|
|
136
165
|
}}
|
|
137
166
|
tabIndex={0}
|
|
167
|
+
role='button'
|
|
138
168
|
>
|
|
139
169
|
<LegendShape shape={config.legend.style === 'boxes' ? 'square' : 'circle'} fill={item.color} />
|
|
140
170
|
<span>{item.label}</span>
|
|
@@ -7,49 +7,68 @@ type LegendItem = {
|
|
|
7
7
|
special: boolean
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* @param legendIdx legend item index
|
|
13
|
-
* @param config chart config
|
|
14
|
-
* @param result hash of legend items
|
|
15
|
-
* @returns string - the corresponding color for the legend item
|
|
16
|
-
*/
|
|
17
|
-
export const applyColorToLegend = (legendIdx: number, config: MapConfig, result: LegendItem[] = []): string => {
|
|
10
|
+
// Applies color to a legend item based on its index and special classes.
|
|
11
|
+
export const applyColorToLegend = (legendItemIndex: number, config: MapConfig, result: LegendItem[] = []): string => {
|
|
18
12
|
if (!config) throw new Error('Config is required')
|
|
19
13
|
|
|
20
14
|
const { legend, customColors, general, color } = config
|
|
21
15
|
const { geoType, palette } = general
|
|
22
16
|
const specialClasses = legend?.specialClasses ?? []
|
|
23
|
-
const
|
|
17
|
+
const colorPalette = customColors ?? colorPalettes[color] ?? colorPalettes['bluegreen']
|
|
24
18
|
|
|
25
19
|
// Handle Region Maps need for a 10th color
|
|
26
|
-
if (geoType === 'us-region' &&
|
|
27
|
-
const
|
|
20
|
+
if (geoType === 'us-region' && colorPalette.length < 10 && colorPalette.length > 8) {
|
|
21
|
+
const darkenedColor = chroma(colorPalette[palette.isReversed ? 0 : 8])
|
|
28
22
|
.darken(0.75)
|
|
29
23
|
.hex()
|
|
30
|
-
palette.isReversed ?
|
|
24
|
+
palette.isReversed ? colorPalette.unshift(darkenedColor) : colorPalette.push(darkenedColor)
|
|
31
25
|
}
|
|
32
26
|
|
|
33
|
-
|
|
27
|
+
// Check if there are actually any special classes in the current result
|
|
28
|
+
const actualSpecialClassesCount = result.filter(item => item.special).length
|
|
29
|
+
|
|
30
|
+
const regularItemColorIndex = legendItemIndex - actualSpecialClassesCount
|
|
34
31
|
|
|
35
32
|
// Handle special classes coloring
|
|
36
|
-
if (result[
|
|
33
|
+
if (result[legendItemIndex]?.special) {
|
|
37
34
|
const specialClassColors = chroma.scale(['#D4D4D4', '#939393']).colors(specialClasses.length)
|
|
38
|
-
return specialClassColors[
|
|
35
|
+
return specialClassColors[legendItemIndex]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// For categorical maps with custom colors, use color distribution logic
|
|
39
|
+
if (config.legend?.type === 'category' && customColors) {
|
|
40
|
+
const amt = config.legend.additionalCategories?.length ?? 10
|
|
41
|
+
const distributionArray = colorDistributions[amt] ?? []
|
|
42
|
+
|
|
43
|
+
const specificColor = distributionArray[legendItemIndex - specialClasses.length] ?? colorPalette.at(-1)
|
|
44
|
+
|
|
45
|
+
// If specificColor is a number, use it as an index; otherwise return the color directly
|
|
46
|
+
return colorPalette[specificColor] ?? '#fff'
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
// Use qualitative color palettes directly
|
|
42
|
-
if (color.includes('qualitative'))
|
|
50
|
+
if (color.includes('qualitative')) {
|
|
51
|
+
const safeIndex = Math.max(0, Math.min(regularItemColorIndex, colorPalette.length - 1))
|
|
52
|
+
return colorPalette[safeIndex]
|
|
53
|
+
}
|
|
43
54
|
|
|
44
|
-
// Determine color distribution
|
|
45
|
-
const
|
|
46
|
-
Math.max(result.length -
|
|
47
|
-
? Math.max(result.length -
|
|
55
|
+
// Determine color distribution - use actual special classes count for consistent coloring
|
|
56
|
+
const legendItemCount =
|
|
57
|
+
Math.max(result.length - actualSpecialClassesCount, 1) < 10
|
|
58
|
+
? Math.max(result.length - actualSpecialClassesCount, 1)
|
|
48
59
|
: Object.keys(colorDistributions).length
|
|
49
|
-
const distributionArray = colorDistributions[amt] ?? []
|
|
50
60
|
|
|
51
|
-
const
|
|
52
|
-
|
|
61
|
+
const colorDistributionArray = colorDistributions[legendItemCount] ?? []
|
|
62
|
+
|
|
63
|
+
const rowDistributionIndex = colorDistributionArray[legendItemIndex - actualSpecialClassesCount]
|
|
64
|
+
|
|
65
|
+
const colorValue = rowDistributionIndex ?? colorPalette[regularItemColorIndex] ?? colorPalette.at(-1)
|
|
66
|
+
|
|
67
|
+
// Check if specificColor is a string(e.g., a valid color code)
|
|
68
|
+
if (typeof colorValue === 'string' && config.legend?.type === 'category' && customColors) {
|
|
69
|
+
return colorValue
|
|
70
|
+
}
|
|
53
71
|
|
|
54
|
-
|
|
72
|
+
// Otherwise, use specificColor as an index for mapColorPalette
|
|
73
|
+
return colorPalette[colorValue]
|
|
55
74
|
}
|
|
@@ -29,21 +29,23 @@ export const applyLegendToRow = (
|
|
|
29
29
|
return generateColorsArray(mapColorPalette[3])
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const hash = hashObj(rowObj)
|
|
32
|
+
const hash = String(hashObj(rowObj))
|
|
33
33
|
|
|
34
34
|
if (!legendMemo.current.has(hash)) {
|
|
35
35
|
return generateColorsArray()
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const idx = legendMemo.current.get(hash)!
|
|
39
|
-
const disabledIdx = showSpecialClassesLast ? legendSpecialClassLastMemo.current.get(hash) ?? idx : idx
|
|
40
39
|
|
|
41
|
-
if (runtimeLegend.items?.[
|
|
40
|
+
if (runtimeLegend.items?.[idx]?.disabled) {
|
|
42
41
|
return generateColorsArray()
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
// Use the index from legendMemo which should already be updated for reordered items
|
|
45
|
+
const legendItem = runtimeLegend.items?.[idx]
|
|
46
|
+
const legendBinColor = legendItem?.color
|
|
47
|
+
|
|
48
|
+
return generateColorsArray(legendBinColor, legendItem?.special)
|
|
47
49
|
} catch (e) {
|
|
48
50
|
console.error('COVE: ', e)
|
|
49
51
|
return null
|