This commit is contained in:
Andras Schmelczer 2026-05-09 09:26:40 +01:00
parent 701c17a703
commit f114ada255
44 changed files with 5264 additions and 1674 deletions

View file

@ -72,7 +72,7 @@ export const PARTY_FEATURE_GRADIENTS: Record<string, GradientStop[]> = {
'% Liberal Democrat': partyGradient([255, 100, 0]), // Liberal Democrat orange
'% Reform UK': partyGradient([18, 182, 207]), // Reform UK cyan
'% Green': partyGradient([106, 176, 35]), // Green Party green
'% Other parties': partyGradient([107, 114, 128]), // neutral fallback for grouped parties
'% Other parties': partyGradient([107, 114, 128]), // neutral color for grouped parties
};
export const PARTY_FEATURE_COLORS: Record<string, string> = Object.fromEntries(
@ -127,9 +127,6 @@ export const POI_GROUP_COLORS: Record<string, [number, number, number]> = {
Shops: [99, 102, 241],
};
/** Default color for unknown POI groups */
export const POI_DEFAULT_COLOR: [number, number, number] = [107, 114, 128];
/** POI category → icon/logo URL for branded and transport categories */
export const POI_CATEGORY_LOGOS: Record<string, string> = {
Airport: '/assets/twemoji/2708.png',
@ -152,22 +149,22 @@ export const POI_CATEGORY_LOGOS: Record<string, string> = {
'Co-op': '/assets/poi-icons/logos/coop.svg',
COOK: '/assets/poi-icons/brands_2024/cook.svg',
'Convenience Store': '/assets/twemoji/1f3ea.png',
Costco: '/assets/poi-icons/brands/costco.svg',
Costco: '/assets/poi-icons/logos/costco.svg',
'Deli & Specialty': '/assets/twemoji/1f9c6.png',
'Dunnes Stores': '/assets/poi-icons/brands_2024/dunnes_stores.svg',
Farmfoods: '/assets/poi-icons/brands_2023/supermarkets/farmfoods.svg',
Ferry: '/assets/twemoji/26f4.png',
Greengrocer: '/assets/twemoji/1f96c.png',
'Heron Foods': '/assets/poi-icons/brands_2023/supermarkets/heron_foods.svg',
Iceland: '/assets/poi-icons/logos/iceland.svg',
Iceland: '/assets/poi-icons/brands_2024/iceland.svg',
Lidl: '/assets/poi-icons/logos/lidl.svg',
Makro: '/assets/poi-icons/brands_2024/makro.svg',
'M&S': '/assets/poi-icons/brands/mns.svg',
'M&S Clothing': '/assets/poi-icons/brands/mns_high_street.svg',
'M&S Food': '/assets/poi-icons/brands/mns_food.svg',
'M&S Hospital': '/assets/poi-icons/brands/mns_hospital.svg',
'M&S MSA': '/assets/poi-icons/brands/mns_moto.svg',
'M&S Outlet': '/assets/poi-icons/brands/mns_outlet.svg',
'M&S': '/assets/poi-icons/brands_2024/mns.svg',
'M&S Clothing': '/assets/poi-icons/brands_2024/mns.svg',
'M&S Food': '/assets/poi-icons/visuals/mns.svg',
'M&S Hospital': '/assets/poi-icons/brands_2024/mns.svg',
'M&S MSA': '/assets/poi-icons/brands_2024/mns.svg',
'M&S Outlet': '/assets/poi-icons/brands_2024/mns.svg',
Morrisons: '/assets/poi-icons/logos/morrisons.svg',
'Morrisons Daily': '/assets/poi-icons/brands_2024/morrisons_daily.svg',
'Off-Licence': '/assets/twemoji/1f377.png',
@ -181,10 +178,10 @@ export const POI_CATEGORY_LOGOS: Record<string, string> = {
'Tesco Express': '/assets/poi-icons/logos/tesco_express.svg',
'Tesco Extra': '/assets/poi-icons/logos/tesco_extra.svg',
'Taxi rank': '/assets/twemoji/1f695.png',
'The Food Warehouse': '/assets/poi-icons/logos/iceland.svg',
'The Food Warehouse': '/assets/poi-icons/logos/the_food_warehouse.png',
'Tube station': '/assets/poi-icons/public_transport/london_tube.svg',
Waitrose: '/assets/poi-icons/logos/waitrose.svg',
'Little Waitrose': '/assets/poi-icons/brands/little_waitrose.svg',
'Little Waitrose': '/assets/poi-icons/brands_2023/supermarkets/little_waitrose.svg',
'Whole Foods Market': '/assets/poi-icons/brands_2024/wholefoods.svg',
};
@ -329,9 +326,8 @@ export const ENUM_PALETTE: [number, number, number][] = [
];
/**
* Per-feature color overrides for enum values on the map and dashboard.
* Per-feature color definitions for enum values on the map and dashboard.
* Keys are feature names (as returned by the server), values map enum value RGB.
* Any value not listed falls back to ENUM_PALETTE by index.
*/
export const ENUM_COLOR_OVERRIDES: Record<string, Record<string, [number, number, number]>> = {
'Property type': {
@ -341,49 +337,105 @@ export const ENUM_COLOR_OVERRIDES: Record<string, Record<string, [number, number
'Flats/Maisonettes': [236, 72, 153], // pink
Other: [107, 114, 128], // gray
},
'Leasehold/Freehold': {
Freehold: [59, 130, 246],
Leasehold: [245, 158, 11],
},
'Former council house': {
Yes: [239, 68, 68],
No: [34, 197, 94],
},
'Current energy rating': {
A: [22, 163, 74],
B: [132, 204, 22],
C: [234, 179, 8],
D: [245, 158, 11],
E: [249, 115, 22],
F: [239, 68, 68],
G: [126, 34, 206],
},
'Potential energy rating': {
A: [22, 163, 74],
B: [132, 204, 22],
C: [234, 179, 8],
D: [245, 158, 11],
E: [249, 115, 22],
F: [239, 68, 68],
G: [126, 34, 206],
},
'Max available download speed (Mbps)': {
'10': [107, 114, 128],
'30': [245, 158, 11],
'100': [59, 130, 246],
'300': [20, 184, 166],
'1000': [34, 197, 94],
},
};
/**
* Build a 10-color palette for a given feature, using overrides where defined.
* Returns the default ENUM_PALETTE when no overrides exist.
* Build the 10-color shader palette for a given enum feature.
* The trailing slots are invisible for features with fewer than 10 enum values.
*/
export function getEnumPaletteForFeature(
featureName: string | null,
values?: string[]
featureName: string,
values: string[]
): [number, number, number][] {
if (!featureName || !values) return ENUM_PALETTE;
const overrides = ENUM_COLOR_OVERRIDES[featureName];
if (!overrides) return ENUM_PALETTE;
if (!overrides) {
throw new Error(`Missing enum color definitions for '${featureName}'`);
}
const palette: [number, number, number][] = [];
for (let i = 0; i < 10; i++) {
if (i < values.length && overrides[values[i]]) {
palette.push(overrides[values[i]]);
if (i < values.length) {
const color = overrides[values[i]];
if (!color) {
throw new Error(`Missing enum color for '${featureName}' value '${values[i]}'`);
}
palette.push(color);
} else {
palette.push(ENUM_PALETTE[i % ENUM_PALETTE.length]);
palette.push([0, 0, 0]);
}
}
return palette;
}
/** Look up override color for a specific enum value, or null if none. */
/** Look up the configured color for a specific enum value. */
export function getEnumValueColor(
featureName: string,
valueName: string
): [number, number, number] | null {
return ENUM_COLOR_OVERRIDES[featureName]?.[valueName] ?? null;
): [number, number, number] {
const color = ENUM_COLOR_OVERRIDES[featureName]?.[valueName];
if (!color) {
throw new Error(`Missing enum color for '${featureName}' value '${valueName}'`);
}
return color;
}
/** Colors for stacked bar segments */
export const SEGMENT_COLORS = [
'#ef4444', // red-500
'#f97316', // orange-500
'#eab308', // yellow-500
'#22c55e', // green-500
'#14b8a6', // teal-500
'#06b6d4', // cyan-500
'#3b82f6', // blue-500
'#8b5cf6', // violet-500
'#d946ef', // fuchsia-500
'#ec4899', // pink-500
];
/** Explicit colors for stacked bar segments. */
export const STACKED_SEGMENT_COLORS: Record<string, string> = {
'Violence and sexual offences (avg/yr)': '#ef4444',
'Robbery (avg/yr)': '#f97316',
'Burglary (avg/yr)': '#eab308',
'Possession of weapons (avg/yr)': '#8b5cf6',
'Anti-social behaviour (avg/yr)': '#14b8a6',
'Criminal damage and arson (avg/yr)': '#f97316',
'Shoplifting (avg/yr)': '#ec4899',
'Bicycle theft (avg/yr)': '#22c55e',
'Theft from the person (avg/yr)': '#d946ef',
'Other theft (avg/yr)': '#06b6d4',
'Vehicle crime (avg/yr)': '#3b82f6',
'Public order (avg/yr)': '#8b5cf6',
'Drugs (avg/yr)': '#22c55e',
'Other crime (avg/yr)': '#6b7280',
'% White': '#3b82f6',
'% South Asian': '#f97316',
'% East Asian': '#eab308',
'% Black': '#8b5cf6',
'% Mixed': '#14b8a6',
'% Other': '#6b7280',
'Anti-social': '#14b8a6',
Vehicle: '#3b82f6',
Burglary: '#eab308',
Other: '#6b7280',
};