/* 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. * * Follows the canonical deck.gl v9 extension pattern: * - defaultProps with type:'accessor' enables getSubLayerProps() to wrap * accessors via getSubLayerAccessor(), which unwraps __source.object to * access the original data item through CompositeLayer sublayer chains. * - stepMode:'dynamic' handles per-instance counting automatically. * - isEnabled() restricts to SolidPolygonLayer (fill) sublayers only. * * Accepts an optional custom palette in the constructor for per-feature color overrides. */ function paletteToGlsl(palette: [number, number, number][]): string { return 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'; static defaultProps = { getCenter: { type: 'accessor', value: [0, 0] }, getRatios0: { type: 'accessor', value: [1, 0, 0, 0] }, getRatios1: { type: 'accessor', value: [0, 0, 0, 0] }, getRatios2: { type: 'accessor', value: [0, 0] }, }; private paletteGlsl: string; constructor(palette?: [number, number, number][]) { super(); this.paletteGlsl = paletteToGlsl(palette ?? ENUM_PALETTE); } isEnabled(layer: any): boolean { return layer.id.endsWith('-fill'); } getShaders(this: any, 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]( ${extension.paletteGlsl} );`, '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.add({ instancePieCenter: { size: 2, stepMode: 'dynamic', accessor: 'getCenter', }, instanceRatios0: { size: 4, stepMode: 'dynamic', accessor: 'getRatios0', }, instanceRatios1: { size: 4, stepMode: 'dynamic', accessor: 'getRatios1', }, instanceRatios2: { size: 2, stepMode: 'dynamic', accessor: 'getRatios2', }, }); } }