/* eslint-disable @typescript-eslint/no-explicit-any */ import { LayerExtension } from '@deck.gl/core'; import { ENUM_PALETTE } from './consts'; /** * LayerExtension that turns polygon fills into pie charts. * Injects a fragment shader that computes angle from each fragment's position * to the polygon centroid, then picks a slice color from the enum palette. * * Works with H3HexagonLayer (hex fills) and GeoJsonLayer (postcode fills). * Only activates on SolidPolygonLayer sublayers (fill), not PathLayer (stroke). * * Required layer props when this extension is active: * getCenter: (d) => [lon, lat] — polygon centroid in world coordinates * getRatios0: (d) => number[4] — pie ratios for slices 0-3 * getRatios1: (d) => number[4] — pie ratios for slices 4-7 * getRatios2: (d) => number[2] — pie ratios for slices 8-9 */ // Build palette as GLSL vec3 constants (normalized 0-1) const PALETTE_GLSL = ENUM_PALETTE.map( (c) => `vec3(${(c[0] / 255).toFixed(4)}, ${(c[1] / 255).toFixed(4)}, ${(c[2] / 255).toFixed(4)})` ).join(',\n '); export class PieHexExtension extends LayerExtension { static extensionName = 'PieHexExtension'; isEnabled(layer: any): boolean { // Only apply to fill sublayers (SolidPolygonLayer), not stroke (PathLayer) return layer.id.endsWith('-fill'); } getShaders(extension: any): any { if (!extension.isEnabled(this)) return null; return { modules: [ { name: 'pieHex', inject: { 'vs:#decl': `\ in vec2 instancePieCenter; in vec4 instanceRatios0; in vec4 instanceRatios1; in vec2 instanceRatios2; out vec2 vPieCenter; out vec2 vPieFragPos; out vec4 vRatios0; out vec4 vRatios1; out vec2 vRatios2;`, 'vs:#main-end': `\ vPieCenter = project_position(vec3(instancePieCenter, 0.0)).xy; vPieFragPos = geometry.position.xy; vRatios0 = instanceRatios0; vRatios1 = instanceRatios1; vRatios2 = instanceRatios2;`, 'fs:#decl': `\ in vec2 vPieCenter; in vec2 vPieFragPos; in vec4 vRatios0; in vec4 vRatios1; in vec2 vRatios2; const vec3 pieColors[10] = vec3[10]( ${PALETTE_GLSL} );`, 'fs:DECKGL_FILTER_COLOR': `\ { vec2 delta = vPieFragPos - vPieCenter; float angle = atan(delta.x, -delta.y) / (2.0 * 3.14159265) + 0.5; float ratios[10]; ratios[0] = vRatios0.x; ratios[1] = vRatios0.y; ratios[2] = vRatios0.z; ratios[3] = vRatios0.w; ratios[4] = vRatios1.x; ratios[5] = vRatios1.y; ratios[6] = vRatios1.z; ratios[7] = vRatios1.w; ratios[8] = vRatios2.x; ratios[9] = vRatios2.y; float cumulative = 0.0; vec3 sliceColor = pieColors[0]; for (int i = 0; i < 10; i++) { cumulative += ratios[i]; if (angle < cumulative) { sliceColor = pieColors[i]; break; } } color = vec4(sliceColor, 1.0); }`, }, uniformTypes: {}, }, ], }; } initializeState(this: any, _context: any, extension: any): void { if (!extension.isEnabled(this)) return; const am = this.getAttributeManager(); if (!am) return; am.addInstanced({ instancePieCenter: { size: 2, type: 'float32', accessor: 'getCenter', }, instanceRatios0: { size: 4, type: 'float32', accessor: 'getRatios0', }, instanceRatios1: { size: 4, type: 'float32', accessor: 'getRatios1', }, instanceRatios2: { size: 2, type: 'float32', accessor: 'getRatios2', }, }); } }