@biohub/scatterplot 0.1.4
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/LICENSE +21 -0
- package/README.md +501 -0
- package/dist/index.d.ts +354 -0
- package/dist/scatterplot.css +1 -0
- package/dist/scatterplot.js +985 -0
- package/dist/scatterplot.js.map +1 -0
- package/dist/scatterplot.umd.js +106 -0
- package/dist/scatterplot.umd.js.map +1 -0
- package/package.json +100 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scatterplot.js","sources":["../src/hooks/useSelection.ts","../src/types/camera.ts","../src/utils/flags.ts","../src/utils/webgl.ts","../src/hooks/useCamera.ts","../src/hooks/useContainerSize.ts","../src/hooks/useLatestRef.ts","../src/hooks/useLasso.ts","../src/shaders/scatterplot.frag.glsl?raw","../src/shaders/scatterplot.vert.glsl?raw","../src/theme/cssProperties.ts","../src/theme/createTheme.ts","../src/theme/presets.ts","../src/utils/geometry.ts","../src/utils/interaction.ts","../src/utils/matrix.ts","../src/components/DebugPanel.tsx","../src/components/LassoOverlay.tsx","../src/components/ScatterplotGL.tsx","../src/components/Scatterplot.tsx"],"sourcesContent":["/**\n * Hook for managing point selection state\n *\n * Supports both single-point (click) and multi-point (lasso) selection.\n */\n\nimport { useCallback, useState } from 'react';\n\nexport interface UseSelectionResult {\n /** Currently selected point indices (Set for efficient lookup) */\n selectedIndices: Set<number>;\n\n /** Handle point click - selects clicked point or clears if clicking empty space */\n handlePointClick: (index: number | null) => void;\n\n /** Set selection to specific indices (used by lasso tool) */\n setSelection: (indices: Set<number>) => void;\n\n /** Clear all selections */\n clearSelection: () => void;\n\n /** Check if a specific index is selected */\n isSelected: (index: number) => boolean;\n}\n\n/**\n * Hook for managing point selection state\n *\n * @example\n * ```tsx\n * const { selectedIndices, handlePointClick } = useSelection();\n *\n * <ScatterplotGL\n * data={data}\n * flags={createFlagBuffer(data.length, selectedIndices)}\n * onPointClick={handlePointClick}\n * />\n * ```\n */\nexport function useSelection(): UseSelectionResult {\n const [selectedIndices, setSelectedIndices] = useState<Set<number>>(\n new Set()\n );\n\n const handlePointClick = useCallback((index: number | null) => {\n if (index === null) {\n // Clicked empty space - clear selection\n setSelectedIndices(new Set());\n } else {\n // Clicked a point - select only that point (single-select for clicks)\n setSelectedIndices(new Set([index]));\n }\n }, []);\n\n const setSelection = useCallback((indices: Set<number>) => {\n setSelectedIndices(new Set(indices)); // Create new Set to trigger re-render\n }, []);\n\n const clearSelection = useCallback(() => {\n setSelectedIndices(new Set());\n }, []);\n\n const isSelected = useCallback(\n (index: number) => {\n return selectedIndices.has(index);\n },\n [selectedIndices]\n );\n\n return {\n selectedIndices,\n handlePointClick,\n setSelection,\n clearSelection,\n isSelected,\n };\n}\n","/**\n * Camera state for scatterplot visualization\n */\nexport interface Camera {\n /** Zoom level (1.0 = no zoom) */\n zoom: number;\n /** Pan offset in normalized device coordinates (-1 to 1) */\n pan: { x: number; y: number };\n}\n\n/**\n * Default camera state: no zoom, no pan\n */\nexport const DEFAULT_CAMERA: Camera = {\n zoom: 1.0,\n pan: { x: 0, y: 0 },\n};\n","/**\n * Point Flag System for Selection and Highlighting\n *\n * Uses bitmask flags stored as integers (Uint8Array) and decoded in the shader\n * using native bitwise operations (WebGL 2.0 feature).\n *\n * Supported flags:\n * - selected: the point is currently selected\n * - background: the point is background information (de-emphasized)\n * - highlight: the point is currently highlighted\n */\n\n// JavaScript constants for flag encoding (must match shader constants)\nexport const FLAG_SELECTED = 1;\nexport const FLAG_BACKGROUND = 2;\nexport const FLAG_HIGHLIGHT = 4;\n\n/**\n * Encode multiple boolean flags into a single integer value\n * @param isSelected - Point is selected\n * @param isBackground - Point is background (de-emphasized)\n * @param isHighlight - Point is highlighted (emphasized)\n * @returns Encoded flag value as integer (0-7)\n */\nexport function encodeFlags(\n isSelected: boolean,\n isBackground: boolean,\n isHighlight: boolean\n): number {\n let flag = 0;\n if (isSelected) flag |= FLAG_SELECTED;\n if (isBackground) flag |= FLAG_BACKGROUND;\n if (isHighlight) flag |= FLAG_HIGHLIGHT;\n return flag;\n}\n\n/**\n * Create a flag buffer for a dataset\n * @param count - Number of points\n * @param selectedIndices - Set of selected point indices (optional)\n * @param highlightedIndices - Set of highlighted point indices (optional)\n * @param backgroundIndices - Set of background point indices (optional).\n * If not provided and selectedIndices has items, non-selected/non-highlighted points are auto-marked as background.\n * @returns Uint8Array of encoded flags (8 bits per point, 0-255)\n */\nexport function createFlagBuffer(\n count: number,\n selectedIndices?: Set<number>,\n highlightedIndices?: Set<number>,\n backgroundIndices?: Set<number>\n): Uint8Array {\n const flags = new Uint8Array(count);\n const hasSelection =\n selectedIndices !== undefined && selectedIndices.size > 0;\n\n for (let i = 0; i < count; i++) {\n // When no selection exists, treat all points as selected\n const isSelected = hasSelection ? selectedIndices.has(i) : true;\n const isHighlight = highlightedIndices?.has(i) ?? false;\n\n // Auto-compute background: when there's a selection, non-selected/non-highlighted points are background\n const isBackground =\n backgroundIndices !== undefined\n ? backgroundIndices.has(i)\n : hasSelection && !isSelected && !isHighlight;\n\n flags[i] = encodeFlags(isSelected, isBackground, isHighlight);\n }\n\n return flags;\n}\n","/**\n * WebGL utility functions for shader compilation and program creation\n */\n\nimport type { Point } from '../types';\n\n/** Default fallback color for invalid or missing colors (blue #3498db) */\nexport const DEFAULT_COLOR = '#3498db';\nexport const DEFAULT_COLOR_RGB_BYTES: [number, number, number] = [52, 152, 219]; // 0-255 range\n/** Default alpha value (fully opaque) */\nexport const DEFAULT_ALPHA = 255;\n\n/**\n * Compiles a WebGL shader\n * @param gl - WebGL 2 rendering context\n * @param type - Shader type (gl.VERTEX_SHADER or gl.FRAGMENT_SHADER)\n * @param source - GLSL shader source code\n * @returns Compiled shader or null if compilation failed\n */\nexport function compileShader(\n gl: WebGL2RenderingContext,\n type: number,\n source: string\n): WebGLShader | null {\n const shader = gl.createShader(type);\n if (!shader) {\n console.error('Failed to create WebGL shader');\n return null;\n }\n\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n console.error('Shader compile error:', gl.getShaderInfoLog(shader));\n gl.deleteShader(shader);\n return null;\n }\n\n return shader;\n}\n\n/**\n * Creates and links a WebGL program from vertex and fragment shaders\n * @param gl - WebGL 2 rendering context\n * @param vertexShader - Compiled vertex shader\n * @param fragmentShader - Compiled fragment shader\n * @returns Linked program or null if linking failed\n */\nexport function createProgram(\n gl: WebGL2RenderingContext,\n vertexShader: WebGLShader,\n fragmentShader: WebGLShader\n): WebGLProgram | null {\n const program = gl.createProgram();\n if (!program) {\n console.error('Failed to create WebGL program');\n return null;\n }\n\n gl.attachShader(program, vertexShader);\n gl.attachShader(program, fragmentShader);\n gl.linkProgram(program);\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n console.error('Program link error:', gl.getProgramInfoLog(program));\n gl.deleteProgram(program);\n return null;\n }\n\n return program;\n}\n\n/**\n * Converts hex color string to RGB integer values (0-255 range)\n * @param hex - Hex color string (e.g., \"#ff0000\" or \"#f00\")\n * @returns Array of [r, g, b] values in 0-255 range\n */\nexport function hexToRgb(hex: string): [number, number, number] {\n // Validate input\n if (!hex || !/^#?[0-9A-Fa-f]{3,6}$/.test(hex)) {\n console.warn(\n `Invalid hex color: \"${hex}\", using fallback (${DEFAULT_COLOR})`\n );\n return DEFAULT_COLOR_RGB_BYTES;\n }\n\n // Remove # if present\n const cleanHex = hex.replace('#', '');\n\n // Handle 3-digit hex codes (e.g., #f00 -> #ff0000)\n const fullHex =\n cleanHex.length === 3\n ? cleanHex\n .split('')\n .map((c) => c + c)\n .join('')\n : cleanHex;\n\n const num = parseInt(fullHex, 16);\n\n // Extract RGB components as integers (0-255)\n const r = (num >> 16) & 255;\n const g = (num >> 8) & 255;\n const b = num & 255;\n\n return [r, g, b];\n}\n\n/**\n * Converts hex color string to RGBA byte values (0-255 range)\n * @param hex - Hex color string (e.g., \"#ff0000\" or \"#f00\")\n * @param alpha - Alpha value (0-255). For default, @see {@link DEFAULT_ALPHA}\n * @returns Array of [r, g, b, a] values in 0-255 range\n */\nexport function hexToRgba(\n hex: string,\n alpha: number = DEFAULT_ALPHA\n): [number, number, number, number] {\n const [r, g, b] = hexToRgb(hex);\n return [r, g, b, alpha];\n}\n\n/**\n * Converts Point[] array to raw position and color buffers\n * @param data - Array of points with x, y, and optional color properties\n * @param defaultColor - Default color to use if point has no color. For default, @see {@link DEFAULT_COLOR}\n * @returns Object containing positions (Float32Array) and colors (Uint8Array)\n */\nexport function pointsToBuffers(\n data: Point[],\n defaultColor: string = DEFAULT_COLOR\n): { positions: Float32Array; colors: Uint8Array } {\n const positions = new Float32Array(data.length * 2);\n const colors = new Uint8Array(data.length * 4);\n\n for (let i = 0; i < data.length; i++) {\n const point = data[i];\n\n // Extract positions\n positions[i * 2] = point.x;\n positions[i * 2 + 1] = point.y;\n\n // Convert hex color to RGBA bytes (0-255)\n const [r, g, b, a] = hexToRgba(point.color || defaultColor, DEFAULT_ALPHA);\n colors[i * 4] = r;\n colors[i * 4 + 1] = g;\n colors[i * 4 + 2] = b;\n colors[i * 4 + 3] = a;\n }\n\n return { positions, colors };\n}\n","import { useEffect, useRef } from 'react';\nimport type { Camera } from '../types/camera';\n\n/**\n * Props for the useCamera hook\n */\ninterface UseCameraProps {\n canvas: HTMLCanvasElement | null;\n width: number;\n height: number;\n onCameraChange: (camera: Camera | ((prev: Camera) => Camera)) => void;\n enabled?: boolean;\n}\n\n/**\n * Return type for useCamera hook\n */\nexport interface UseCameraResult {\n /** Ref that is true immediately after panning finishes (resets on next mousedown) */\n justFinishedPanningRef: React.RefObject<boolean>;\n}\n\n/**\n * Hook that manages camera interactions (pan/zoom) for a canvas element.\n *\n * Features:\n * - Mouse wheel zoom with zoom-to-cursor behavior\n * - Mouse drag panning\n * - Handles edge case of mouse re-entering canvas while dragging\n *\n * @param canvas - The canvas element (or null if not yet mounted)\n * @param width - Canvas width in pixels\n * @param height - Canvas height in pixels\n * @param onCameraChange - Callback when camera changes (supports functional updates)\n * @param enabled - When true, enables camera interactions (default: true)\n * @returns Object containing justFinishedPanningRef to detect if click should be suppressed\n */\nexport function useCamera({\n canvas,\n width,\n height,\n onCameraChange,\n enabled = true,\n}: UseCameraProps): UseCameraResult {\n const justFinishedPanningRef = useRef<boolean>(false);\n // Mouse wheel zoom\n useEffect(() => {\n if (!canvas || !enabled) return;\n\n const handleWheel = (e: WheelEvent) => {\n e.preventDefault();\n\n // Get mouse position relative to canvas\n const rect = canvas.getBoundingClientRect();\n const mouseX = e.clientX - rect.left;\n const mouseY = e.clientY - rect.top;\n\n // Convert to normalized device coordinates (-1 to 1)\n const ndcX = (mouseX / width) * 2 - 1;\n const ndcY = -((mouseY / height) * 2 - 1); // Y is inverted in WebGL\n\n // Calculate zoom factor\n const zoomFactor = e.deltaY < 0 ? 1.1 : 1 / 1.1;\n\n // Use functional update to access current camera state atomically\n onCameraChange((prevCamera) => {\n const currentZoom = prevCamera.zoom;\n const currentPan = prevCamera.pan;\n\n // Update zoom and pan together\n const newZoom = currentZoom * zoomFactor;\n\n // The point in clip space before zoom\n const worldX = (ndcX - currentPan.x) / currentZoom;\n const worldY = (ndcY - currentPan.y) / currentZoom;\n\n // After zoom, adjust pan so this world point stays at the same NDC position\n const newPanX = ndcX - worldX * newZoom;\n const newPanY = ndcY - worldY * newZoom;\n\n return {\n zoom: newZoom,\n pan: { x: newPanX, y: newPanY },\n };\n });\n };\n\n canvas.addEventListener('wheel', handleWheel, { passive: false });\n return () => canvas.removeEventListener('wheel', handleWheel);\n }, [canvas, width, height, onCameraChange, enabled]);\n\n // Mouse drag pan\n useEffect(() => {\n if (!canvas || !enabled) return;\n\n let isDragging = false;\n let hasMoved = false;\n let lastMousePos = { x: 0, y: 0 };\n\n const handleMouseDown = (e: MouseEvent) => {\n isDragging = true;\n hasMoved = false;\n justFinishedPanningRef.current = false;\n lastMousePos = { x: e.clientX, y: e.clientY };\n };\n\n const handleMouseMove = (e: MouseEvent) => {\n if (!isDragging) return;\n\n // Calculate mouse movement in pixels\n const deltaX = e.clientX - lastMousePos.x;\n const deltaY = e.clientY - lastMousePos.y;\n\n // Mark as moved if there's any significant movement\n if (Math.abs(deltaX) > 2 || Math.abs(deltaY) > 2) {\n hasMoved = true;\n }\n\n // Convert to WebGL coordinate space (-1 to 1)\n const glDeltaX = (deltaX / width) * 2;\n const glDeltaY = -(deltaY / height) * 2; // Negative because Y is inverted in WebGL\n\n // Use functional update to maintain zoom while updating pan\n onCameraChange((prevCamera) => ({\n zoom: prevCamera.zoom,\n pan: {\n x: prevCamera.pan.x + glDeltaX,\n y: prevCamera.pan.y + glDeltaY,\n },\n }));\n\n lastMousePos = { x: e.clientX, y: e.clientY };\n };\n\n const handleMouseUp = () => {\n if (hasMoved) {\n justFinishedPanningRef.current = true;\n }\n isDragging = false;\n hasMoved = false;\n };\n\n const handleMouseEnter = (e: MouseEvent) => {\n // Resume panning if mouse re-enters with button still pressed\n if ((e.buttons & 1) === 1) {\n isDragging = true;\n lastMousePos = { x: e.clientX, y: e.clientY };\n } else {\n // Button was released outside canvas, stop dragging\n isDragging = false;\n }\n };\n\n canvas.addEventListener('mousedown', handleMouseDown);\n canvas.addEventListener('mousemove', handleMouseMove);\n canvas.addEventListener('mouseup', handleMouseUp);\n canvas.addEventListener('mouseenter', handleMouseEnter);\n\n return () => {\n canvas.removeEventListener('mousedown', handleMouseDown);\n canvas.removeEventListener('mousemove', handleMouseMove);\n canvas.removeEventListener('mouseup', handleMouseUp);\n canvas.removeEventListener('mouseenter', handleMouseEnter);\n };\n }, [canvas, width, height, onCameraChange, enabled]);\n\n return { justFinishedPanningRef };\n}\n","/**\n * Hook for tracking container size using ResizeObserver\n *\n * Returns the width and height of a container element, updating whenever\n * the container is resized. Uses ResizeObserver with requestAnimationFrame\n * for smooth updates synchronized with the browser's paint cycle.\n *\n * @example\n * ```tsx\n * const containerRef = useRef<HTMLDivElement>(null);\n * const { width, height } = useContainerSize(containerRef);\n *\n * return (\n * <div ref={containerRef}>\n * <Canvas width={width} height={height} />\n * </div>\n * );\n * ```\n */\n\nimport { type RefObject, useEffect, useRef, useState } from 'react';\n\nexport interface ContainerSize {\n width: number;\n height: number;\n}\n\n/**\n * Hook that tracks the size of a container element using ResizeObserver\n *\n * Uses requestAnimationFrame to synchronize updates with the browser's\n * paint cycle. Cancels pending updates when new resize events arrive\n * to avoid unnecessary re-renders.\n *\n * @param containerRef - Ref to the container element to observe\n * @returns Current width and height of the container\n */\nexport function useContainerSize<T extends HTMLElement>(\n containerRef: RefObject<T | null>\n): ContainerSize {\n const [size, setSize] = useState<ContainerSize>({ width: 0, height: 0 });\n const rafIdRef = useRef<number | null>(null);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n // Create ResizeObserver to watch container size changes\n const resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (!entry) return;\n\n const { width, height } = entry.contentRect;\n\n // Cancel pending update if new resize arrives before previous frame\n if (rafIdRef.current) {\n cancelAnimationFrame(rafIdRef.current);\n }\n\n // Schedule update on next paint for smooth rendering\n rafIdRef.current = requestAnimationFrame(() => {\n setSize({ width, height });\n rafIdRef.current = null;\n });\n });\n\n // Start observing\n resizeObserver.observe(container);\n\n // Set initial size immediately\n const { clientWidth, clientHeight } = container;\n if (clientWidth > 0 && clientHeight > 0) {\n setSize({ width: clientWidth, height: clientHeight });\n }\n\n // Cleanup\n return () => {\n if (rafIdRef.current) {\n cancelAnimationFrame(rafIdRef.current);\n }\n resizeObserver.disconnect();\n };\n }, [containerRef]);\n\n return size;\n}\n","import { useRef } from 'react';\n\n/**\n * Hook that keeps a ref always in sync with the latest value.\n *\n * Useful for accessing latest props/state in event handlers without\n * needing to re-attach listeners when values change.\n *\n * @param value - The value to keep in sync\n * @returns A ref that always contains the latest value\n */\nexport function useLatestRef<T>(value: T) {\n const ref = useRef(value);\n ref.current = value;\n return ref;\n}\n","/**\n * Hook for managing lasso drawing interaction\n *\n * Handles mouse events for drawing a lasso polygon over the scatterplot.\n * Provides the current lasso path and state for rendering.\n */\n\nimport { useEffect, useRef, useState } from 'react';\nimport { useLatestRef } from './useLatestRef';\n\n// Constants\nexport const LASSO_MIN_POINTS = 3; // Minimum points required to form a valid lasso polygon\nexport const LASSO_MIN_DISPLACEMENT = 10; // Minimum displacement in pixels to be considered a lasso (vs click)\n\nexport interface UseLassoResult {\n /** Current lasso path as array of [x, y] screen coordinates */\n lassoPath: [number, number][];\n\n /** Whether lasso is currently being drawn */\n isDrawing: boolean;\n}\n\nexport interface UseLassoOptions {\n /** Canvas element to attach listeners to */\n canvas: HTMLCanvasElement | null;\n\n /** Whether lasso is enabled */\n enabled: boolean;\n\n /** Called when lasso drawing is complete */\n onLassoComplete: (polygon: [number, number][]) => void;\n\n /** Called during lasso drawing (real-time updates) */\n onLassoUpdate?: (polygon: [number, number][]) => void;\n\n /** Minimum number of points before completing lasso. For default, @see {@link LASSO_MIN_POINTS} */\n minPoints?: number;\n\n /** Minimum displacement (pixels) to be considered a lasso vs click. For default, @see {@link LASSO_MIN_DISPLACEMENT} */\n minDisplacement?: number;\n}\n\n/**\n * Hook for managing lasso drawing interaction\n *\n * Automatically manages event listener lifecycle based on enabled state.\n *\n * @example\n * ```tsx\n * const { lassoPath, isDrawing } = useLasso({\n * canvas: canvasRef.current,\n * enabled: lassoMode,\n * onLassoComplete: (polygon) => {\n * const indices = findPointsInLasso(polygon, data, ...);\n * setSelection(indices);\n * }\n * });\n * // No manual lifecycle management needed!\n * ```\n */\nexport function useLasso(options: UseLassoOptions): UseLassoResult {\n const {\n canvas,\n enabled,\n onLassoComplete,\n onLassoUpdate,\n minDisplacement = LASSO_MIN_DISPLACEMENT,\n } = options;\n\n const [lassoPath, setLassoPath] = useState<[number, number][]>([]);\n const [isDrawing, setIsDrawing] = useState(false);\n\n // Store callbacks in refs to avoid stale closures in RAF callbacks\n // and to prevent effect re-runs when callback identity changes\n const onLassoCompleteRef = useLatestRef(onLassoComplete);\n const onLassoUpdateRef = useLatestRef(onLassoUpdate);\n\n // RAF throttling for mousemove (limits to once per frame)\n const rafPendingRef = useRef(false);\n\n // Manage event listener lifecycle\n useEffect(() => {\n if (!enabled || !canvas) {\n return;\n }\n\n // Transient state for drawing session (closure variables)\n let drawing = false;\n let currentPath: [number, number][] = [];\n let startPosition: [number, number] | null = null;\n\n const handleMouseDown = (e: MouseEvent) => {\n if (e.button !== 0) return; // Only left click\n\n const target = e.currentTarget as HTMLCanvasElement;\n const rect = target.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n\n const initialPath: [number, number][] = [[x, y]];\n startPosition = [x, y];\n currentPath = initialPath;\n drawing = true;\n\n setIsDrawing(true);\n setLassoPath(initialPath);\n };\n\n // Throttled handler - processes at most once per frame\n const handleMouseMove = (e: MouseEvent) => {\n if (!drawing) return;\n if (rafPendingRef.current) return;\n\n // Process immediately, then block until next frame\n rafPendingRef.current = true;\n requestAnimationFrame(() => {\n rafPendingRef.current = false;\n });\n\n const target = e.currentTarget as HTMLCanvasElement;\n const rect = target.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n\n const newPath: [number, number][] = [...currentPath, [x, y]];\n currentPath = newPath;\n setLassoPath(newPath);\n\n if (onLassoUpdateRef.current) {\n onLassoUpdateRef.current(newPath);\n }\n };\n\n const handleMouseUp = () => {\n if (!drawing) return;\n\n drawing = false;\n setIsDrawing(false);\n\n // Calculate displacement from start position to end position\n let displacement = 0;\n if (startPosition && currentPath.length > 0) {\n const endPosition = currentPath[currentPath.length - 1];\n const dx = endPosition[0] - startPosition[0];\n const dy = endPosition[1] - startPosition[1];\n displacement = Math.sqrt(dx * dx + dy * dy);\n }\n\n // Only treat as lasso if displacement exceeds threshold\n // Small/no movement = click = pass empty polygon (deselect)\n const isLasso = displacement >= minDisplacement;\n onLassoCompleteRef.current(isLasso ? currentPath : []);\n\n // Clear state\n currentPath = [];\n startPosition = null;\n setLassoPath([]);\n };\n\n canvas.addEventListener('mousedown', handleMouseDown);\n canvas.addEventListener('mousemove', handleMouseMove);\n canvas.addEventListener('mouseup', handleMouseUp);\n canvas.addEventListener('mouseleave', handleMouseUp);\n\n return () => {\n canvas.removeEventListener('mousedown', handleMouseDown);\n canvas.removeEventListener('mousemove', handleMouseMove);\n canvas.removeEventListener('mouseup', handleMouseUp);\n canvas.removeEventListener('mouseleave', handleMouseUp);\n };\n }, [enabled, canvas, minDisplacement, onLassoCompleteRef, onLassoUpdateRef]);\n\n return {\n lassoPath,\n isDrawing,\n };\n}\n","export default \"#version 300 es\\nprecision mediump float;\\n\\nuniform float u_opacity;\\nuniform float u_backgroundOpacity;\\nuniform float u_highlightBrightness;\\n\\nin vec4 v_color;\\nin float v_isSelected;\\nin float v_isBackground;\\nin float v_isHighlight;\\n\\nout vec4 fragColor;\\n\\n// Constants for circular point rendering\\nconst vec2 CENTER = vec2(0.5, 0.5);\\nconst float RADIUS = 0.5;\\n\\n\\nvoid main() {\\n // gl_PointCoord gives us the pixel coordinate within the point (0,0 to 1,1)\\n // Calculate distance from center\\n float dist = length(gl_PointCoord - CENTER);\\n\\n // Discard pixels outside the circle (hard edge, no AA)\\n // This approach avoids depth buffer artifacts when points overlap\\n if (dist > RADIUS) {\\n discard;\\n }\\n\\n // Apply flag-based visual changes\\n vec3 finalColor = v_color.rgb;\\n float finalAlpha = v_color.a * u_opacity;\\n\\n // Background points: reduce opacity (de-emphasize when something else is selected)\\n if (v_isBackground > 0.5) {\\n finalAlpha *= u_backgroundOpacity;\\n }\\n\\n // Highlighted points: brighten color (temporary during lasso drawing)\\n if (v_isHighlight > 0.5) {\\n finalColor = min(v_color.rgb * u_highlightBrightness, vec3(1.0));\\n }\\n\\n // Output premultiplied alpha to avoid edge halos with transparency\\n // Requires blendFunc(ONE, ONE_MINUS_SRC_ALPHA) in WebGL setup\\n fragColor = vec4(finalColor * finalAlpha, finalAlpha);\\n}\\n\"","export default \"#version 300 es\\nprecision mediump float;\\n\\nin vec2 a_position;\\nin vec4 a_color;\\nin uint a_flag;\\nuniform mat3 u_matrix;\\nuniform float u_pointSize;\\nuniform float u_highlightSizeScale;\\nuniform float u_unselectedSizeScale;\\n\\nout vec4 v_color;\\nout float v_isSelected;\\nout float v_isBackground;\\nout float v_isHighlight;\\n\\n// Flag constants (must match JavaScript constants)\\nconst uint FLAG_SELECTED = 1u;\\nconst uint FLAG_BACKGROUND = 2u;\\nconst uint FLAG_HIGHLIGHT = 4u;\\n\\n// Z-depth constants for layering (WebGL clip space: -1 near, +1 far)\\nconst float Z_BACKGROUND = 0.99; // Background points (farthest back)\\nconst float Z_NORMAL = 0.0; // Normal points (middle layer)\\nconst float Z_TOP = -1.0; // Selected/highlighted points (front)\\n\\nvoid main() {\\n // Apply transformation matrix to position\\n vec3 transformedPosition = u_matrix * vec3(a_position, 1.0);\\n\\n // Decode flags using bitwise operations (WebGL 2.0 feature)\\n bool isSelected = (a_flag & FLAG_SELECTED) != 0u;\\n bool isBackground = (a_flag & FLAG_BACKGROUND) != 0u;\\n bool isHighlight = (a_flag & FLAG_HIGHLIGHT) != 0u;\\n\\n // Calculate z-depth based on flags (determines render order)\\n float z = isBackground ? Z_BACKGROUND : ((isSelected || isHighlight) ? Z_TOP : Z_NORMAL);\\n\\n // Adjust point size based on flags\\n float pointSize = u_pointSize;\\n if (isHighlight) {\\n pointSize *= u_highlightSizeScale;\\n } else if (!isSelected) {\\n pointSize *= u_unselectedSizeScale;\\n }\\n\\n gl_Position = vec4(transformedPosition.xy, z, 1.0);\\n gl_PointSize = pointSize;\\n\\n // Pass color and flags to fragment shader\\n // Convert bools to floats for varying variables\\n v_color = a_color;\\n v_isSelected = isSelected ? 1.0 : 0.0;\\n v_isBackground = isBackground ? 1.0 : 0.0;\\n v_isHighlight = isHighlight ? 1.0 : 0.0;\\n}\\n\"","import type { DebugTheme, LassoTheme, ScatterplotTheme } from './types';\n\nconst CSS_PREFIX = '--scatterplot';\n\n/**\n * Only lasso and debug are exposed as CSS vars.\n * canvas and points are WebGL-only (CSS can't reach them).\n */\nconst CSS_SECTIONS = ['lasso', 'debug'] as const;\n\nfunction toKebab(str: string): string {\n return str.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n}\n\n/**\n * Convert DOM-relevant theme sections to CSS custom properties.\n */\nexport function themeToCssProperties(\n theme: ScatterplotTheme\n): Record<string, string> {\n const vars: Record<string, string> = {};\n\n for (const section of CSS_SECTIONS) {\n const values = theme[section] as LassoTheme | DebugTheme;\n for (const [key, value] of Object.entries(values)) {\n vars[`${CSS_PREFIX}-${section}-${toKebab(key)}`] = String(value);\n }\n }\n\n return vars;\n}\n","import type { PartialTheme, ScatterplotTheme } from './types';\n\n/**\n * Base theme with all default values.\n * Other themes extend this with only their differences.\n */\nexport const BASE_THEME: ScatterplotTheme = {\n canvas: {\n background: '#ffffff',\n dataPadding: 20,\n },\n points: {\n defaultColor: '#3498db',\n size: 5,\n opacity: 1.0,\n backgroundOpacity: 0.5,\n highlightBrightness: 1.4,\n highlightSizeScale: 1.3,\n unselectedSizeScale: 0.2,\n },\n lasso: {\n fill: 'rgba(59, 130, 246, 0.1)',\n stroke: 'rgb(59, 130, 246)',\n strokeWidth: 2,\n strokeDasharray: '5,5',\n },\n debug: {\n background: 'rgba(0, 0, 0, 0.8)',\n color: '#00ff00',\n fontFamily: 'monospace',\n fontSize: '12px',\n },\n};\n\n/**\n * Create a theme by merging overrides with a base theme.\n *\n * Uses explicit property spreading instead of a generic deep merge because\n * the theme structure is fixed at 2 levels. This keeps the implementation\n * simple and avoids adding a dependency for something trivial.\n */\nexport function createTheme(\n overrides: PartialTheme,\n base: ScatterplotTheme = BASE_THEME\n): ScatterplotTheme {\n return {\n canvas: { ...base.canvas, ...overrides.canvas },\n points: { ...base.points, ...overrides.points },\n lasso: { ...base.lasso, ...overrides.lasso },\n debug: { ...base.debug, ...overrides.debug },\n };\n}\n","import { BASE_THEME, createTheme } from './createTheme';\n\nexport const lightTheme = BASE_THEME;\n\nexport const darkTheme = createTheme({\n canvas: {\n background: '#1a1a2e',\n },\n points: {\n defaultColor: '#5dade2',\n backgroundOpacity: 0.4,\n highlightBrightness: 1.5,\n },\n lasso: {\n fill: 'rgba(93, 173, 226, 0.15)',\n stroke: 'rgb(93, 173, 226)',\n },\n debug: {\n background: 'rgba(0, 0, 0, 0.9)',\n },\n});\n\nexport const highContrastTheme = createTheme({\n canvas: {\n background: '#000000',\n },\n points: {\n defaultColor: '#ffffff',\n size: 6,\n backgroundOpacity: 0.3,\n highlightBrightness: 1.6,\n highlightSizeScale: 1.5,\n },\n lasso: {\n fill: 'rgba(255, 255, 0, 0.2)',\n stroke: '#ffff00',\n strokeWidth: 3,\n strokeDasharray: 'none',\n },\n debug: {\n background: '#000000',\n color: '#ffffff',\n fontSize: '14px',\n },\n});\n\nexport const defaultTheme = lightTheme;\n","/**\n * Geometry and normalization utilities for scatterplot\n */\n\nexport interface DataBounds {\n xMin: number;\n xMax: number;\n yMin: number;\n yMax: number;\n}\n\n/**\n * Calculate data bounds from raw position buffer\n * @param positions - Float32Array of [x, y, x, y, ...] coordinates\n * @param count - Number of points\n * @returns Data bounds\n */\nexport function calculateBounds(\n positions: Float32Array,\n count: number\n): DataBounds {\n if (count === 0) {\n return { xMin: 0, xMax: 1, yMin: 0, yMax: 1 };\n }\n\n if (positions.length < count * 2) {\n throw new Error(\n `positions buffer too small: expected at least ${count * 2} elements for ${count} points, got ${positions.length}`\n );\n }\n\n let xMin = positions[0];\n let xMax = positions[0];\n let yMin = positions[1];\n let yMax = positions[1];\n\n for (let i = 0; i < count; i++) {\n const x = positions[i * 2];\n const y = positions[i * 2 + 1];\n if (x < xMin) xMin = x;\n if (x > xMax) xMax = x;\n if (y < yMin) yMin = y;\n if (y > yMax) yMax = y;\n }\n\n return { xMin, xMax, yMin, yMax };\n}\n\n/**\n * Step 1: Data Normalization\n * Calculates scaling and offset factors to map raw data into a unit square (0 to 1)\n * while preserving the data's internal aspect ratio (isotropic).\n * Used when preparing data for the GPU or normalization.\n * @param xMin - Minimum X value in data space\n * @param xMax - Maximum X value in data space\n * @param yMin - Minimum Y value in data space\n * @param yMax - Maximum Y value in data space\n * @returns Object containing scale and offsets for centering\n */\nexport function getNormalizationScales(\n xMin: number,\n xMax: number,\n yMin: number,\n yMax: number\n): { scale: number; xOffset: number; yOffset: number } {\n const xRange = xMax - xMin;\n const yRange = yMax - yMin;\n const scale = Math.max(xRange, yRange) || 1;\n const xOffset = 0.5 - xRange / scale / 2;\n const yOffset = 0.5 - yRange / scale / 2;\n\n return { scale, xOffset, yOffset };\n}\n\n/**\n * Step 2: Viewport Fitting\n * Calculates scale factors that map the normalized unit square to the screen space,\n * accounting for the viewport's aspect ratio, zoom level, and data padding.\n * Matches the logic used in the vertex shader for consistent data-to-screen mapping.\n * @param width - Canvas width in pixels\n * @param height - Canvas height in pixels\n * @param zoom - Zoom level (1 = 100%)\n * @param dataPadding - Padding around data in pixels\n */\nexport function getViewportScales(\n width: number,\n height: number,\n zoom: number,\n dataPadding: number\n): { scaleX: number; scaleY: number } {\n const viewportAspect = width / height;\n // Convert pixel padding to fraction based on shorter dimension\n const minDimension = Math.min(width, height);\n const paddingFraction = minDimension > 0 ? dataPadding / minDimension : 0;\n const baseScale = 1 - 2 * paddingFraction;\n let scaleX = zoom * baseScale;\n let scaleY = zoom * baseScale;\n\n if (viewportAspect > 1) {\n // Viewport is wider than tall - scale X down to prevent horizontal stretch\n scaleX = (zoom * baseScale) / viewportAspect;\n } else if (viewportAspect < 1) {\n // Viewport is taller than wide - scale Y down to prevent vertical stretch\n scaleY = zoom * baseScale * viewportAspect;\n }\n // When viewportAspect === 1 (square), scaleX and scaleY remain equal - no adjustment needed\n\n return { scaleX, scaleY };\n}\n\n/**\n * Normalizes raw positions to WebGL space (-1 to 1) with isotropic scaling\n * Preserves aspect ratio by using the same scale for both X and Y dimensions\n * @param positions - Float32Array of [x, y, x, y, ...] in data space\n * @param count - Number of points\n * @param xMin - Minimum X value in data space\n * @param xMax - Maximum X value in data space\n * @param yMin - Minimum Y value in data space\n * @param yMax - Maximum Y value in data space\n * @returns New Float32Array with normalized coordinates\n */\nexport function normalizePositionsIsotropic(\n positions: Float32Array,\n count: number,\n xMin: number,\n xMax: number,\n yMin: number,\n yMax: number\n): Float32Array {\n const normalized = new Float32Array(count * 2);\n const { scale, xOffset, yOffset } = getNormalizationScales(\n xMin,\n xMax,\n yMin,\n yMax\n );\n\n for (let i = 0; i < count; i++) {\n const x = positions[i * 2];\n const y = positions[i * 2 + 1];\n\n // Map to 0-1 range with centering\n const normX = (x - xMin) / scale;\n const normY = (y - yMin) / scale;\n\n const centeredX = normX + xOffset;\n const centeredY = normY + yOffset;\n\n // Map to WebGL -1 to 1 range\n normalized[i * 2] = -1 + 2 * centeredX;\n normalized[i * 2 + 1] = -1 + 2 * centeredY;\n }\n\n return normalized;\n}\n\n/**\n * Transform a data point to screen (canvas pixel) coordinates\n * This is the core projection pipeline used for hit-testing and interaction\n * @param x - Data X coordinate\n * @param y - Data Y coordinate\n * @param xMin - Minimum X value in data space\n * @param yMin - Minimum Y value in data space\n * @param scale - Isotropic scale factor from getNormalizationScales\n * @param xOffset - X centering offset from getNormalizationScales\n * @param yOffset - Y centering offset from getNormalizationScales\n * @param scaleX - Viewport X scale from getViewportScales\n * @param scaleY - Viewport Y scale from getViewportScales\n * @param panX - Camera pan X offset\n * @param panY - Camera pan Y offset\n * @param canvasWidth - Canvas width in pixels\n * @param canvasHeight - Canvas height in pixels\n * @returns Screen coordinates { screenX, screenY }\n */\nexport function dataPointToScreen(\n x: number,\n y: number,\n xMin: number,\n yMin: number,\n scale: number,\n xOffset: number,\n yOffset: number,\n scaleX: number,\n scaleY: number,\n panX: number,\n panY: number,\n canvasWidth: number,\n canvasHeight: number\n): { screenX: number; screenY: number } {\n // Transform data coordinate to normalized WebGL space (-1 to 1)\n const normalizedX = (x - xMin) / scale;\n const normalizedY = (y - yMin) / scale;\n\n const centeredX = normalizedX + xOffset;\n const centeredY = normalizedY + yOffset;\n\n const webglX = -1 + 2 * centeredX;\n const webglY = -1 + 2 * centeredY;\n\n // Apply scale (with aspect ratio correction and padding) and pan transformations\n const transformedX = webglX * scaleX + panX;\n const transformedY = webglY * scaleY + panY;\n\n // Convert from WebGL space (-1 to 1) to canvas pixel space\n const screenX = ((transformedX + 1) * canvasWidth) / 2;\n const screenY = ((1 - transformedY) * canvasHeight) / 2; // Flip Y axis\n\n return { screenX, screenY };\n}\n\n/**\n * Check if a point is inside a polygon using ray casting algorithm\n * @param x - Point X coordinate\n * @param y - Point Y coordinate\n * @param polygon - Array of [x, y] coordinates defining the polygon\n * @returns true if point is inside polygon\n */\nexport function isPointInPolygon(\n x: number,\n y: number,\n polygon: [number, number][]\n): boolean {\n if (polygon.length < 3) return false;\n\n let inside = false;\n for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n const xi = polygon[i][0];\n const yi = polygon[i][1];\n const xj = polygon[j][0];\n const yj = polygon[j][1];\n\n const intersect =\n yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;\n\n if (intersect) inside = !inside;\n }\n\n return inside;\n}\n","import { defaultTheme } from '../theme/presets';\nimport type { Camera } from '../types/camera';\n\n/** Default distance in pixels for point interaction */\nexport const DEFAULT_INTERACTION_DISTANCE_PX = 10;\n\n/**\n * Interaction utilities for scatterplot\n * Handles mouse/touch interactions like click detection and lasso selection\n */\n\nimport {\n type DataBounds,\n dataPointToScreen,\n getNormalizationScales,\n getViewportScales,\n isPointInPolygon,\n} from './geometry';\n\n/**\n * Internal helper to calculate projection constants for interaction\n */\nfunction getScreenProjectionUtils(\n canvasWidth: number,\n canvasHeight: number,\n zoom: number,\n bounds: DataBounds,\n dataPadding: number\n) {\n const { scale, xOffset, yOffset } = getNormalizationScales(\n bounds.xMin,\n bounds.xMax,\n bounds.yMin,\n bounds.yMax\n );\n const { scaleX, scaleY } = getViewportScales(\n canvasWidth,\n canvasHeight,\n zoom,\n dataPadding\n );\n return { scale, xOffset, yOffset, scaleX, scaleY };\n}\n\n/**\n * Find the closest point to a click position\n * @param clickX - Click X coordinate in canvas space (0 to width)\n * @param clickY - Click Y coordinate in canvas space (0 to height)\n * @param positions - Float32Array of [x, y, x, y, ...] coordinates\n * @param count - Number of points\n * @param canvasWidth - Canvas width in pixels\n * @param canvasHeight - Canvas height in pixels\n * @param camera - Current camera state (zoom and pan)\n * @param maxDistance - Maximum distance in pixels to consider. For default, @see {@link DEFAULT_INTERACTION_DISTANCE_PX}\n * @param bounds - Pre-computed data bounds\n * @param dataPadding - Padding around data in pixels. For default, @see {@link defaultTheme.canvas.dataPadding}\n * @returns Index of closest point, or null if no point within maxDistance\n */\nexport function findClosestPointRaw(\n clickX: number,\n clickY: number,\n positions: Float32Array,\n count: number,\n canvasWidth: number,\n canvasHeight: number,\n camera: Camera,\n maxDistance: number = DEFAULT_INTERACTION_DISTANCE_PX,\n bounds: DataBounds,\n dataPadding: number = defaultTheme.canvas.dataPadding\n): number | null {\n if (count === 0) return null;\n\n const { zoom, pan } = camera;\n\n const { scale, xOffset, yOffset, scaleX, scaleY } = getScreenProjectionUtils(\n canvasWidth,\n canvasHeight,\n zoom,\n bounds,\n dataPadding\n );\n\n let closestIndex: number | null = null;\n let minDistance = maxDistance;\n\n for (let i = 0; i < count; i++) {\n const x = positions[i * 2];\n const y = positions[i * 2 + 1];\n const { screenX, screenY } = dataPointToScreen(\n x,\n y,\n bounds.xMin,\n bounds.yMin,\n scale,\n xOffset,\n yOffset,\n scaleX,\n scaleY,\n pan.x,\n pan.y,\n canvasWidth,\n canvasHeight\n );\n\n const dx = screenX - clickX;\n const dy = screenY - clickY;\n const distance = Math.sqrt(dx * dx + dy * dy);\n\n if (distance < minDistance) {\n minDistance = distance;\n closestIndex = i;\n }\n }\n\n return closestIndex;\n}\n\n/**\n * Find all points within a lasso polygon\n * @param polygon - Array of [x, y] screen coordinates defining the lasso\n * @param positions - Float32Array of [x, y, x, y, ...] coordinates\n * @param count - Number of points\n * @param canvasWidth - Canvas width in CSS pixels\n * @param canvasHeight - Canvas height in CSS pixels\n * @param camera - Current camera state (zoom and pan)\n * @param bounds - Pre-computed data bounds\n * @param dataPadding - Padding around data in pixels. For default, @see {@link defaultTheme.canvas.dataPadding}\n * @returns Set of indices for points within the lasso\n */\nexport function findPointsInLassoRaw(\n polygon: [number, number][],\n positions: Float32Array,\n count: number,\n canvasWidth: number,\n canvasHeight: number,\n camera: Camera,\n bounds: DataBounds,\n dataPadding: number = defaultTheme.canvas.dataPadding\n): Set<number> {\n if (polygon.length < 3) return new Set();\n\n const { zoom, pan } = camera;\n\n const { scale, xOffset, yOffset, scaleX, scaleY } = getScreenProjectionUtils(\n canvasWidth,\n canvasHeight,\n zoom,\n bounds,\n dataPadding\n );\n\n const selectedIndices = new Set<number>();\n\n for (let i = 0; i < count; i++) {\n const x = positions[i * 2];\n const y = positions[i * 2 + 1];\n const { screenX, screenY } = dataPointToScreen(\n x,\n y,\n bounds.xMin,\n bounds.yMin,\n scale,\n xOffset,\n yOffset,\n scaleX,\n scaleY,\n pan.x,\n pan.y,\n canvasWidth,\n canvasHeight\n );\n\n if (isPointInPolygon(screenX, screenY, polygon)) {\n selectedIndices.add(i);\n }\n }\n\n return selectedIndices;\n}\n","/**\n * Matrix utility functions for 2D transformations in WebGL\n */\n\n/**\n * Creates a 3x3 scale matrix\n * @param scaleX - Scale factor on X axis\n * @param scaleY - Scale factor on Y axis\n * @returns 3x3 scale matrix as Float32Array\n */\nexport function createScaleMatrix(\n scaleX: number,\n scaleY: number\n): Float32Array {\n return new Float32Array([scaleX, 0, 0, 0, scaleY, 0, 0, 0, 1]);\n}\n\n/**\n * Creates a 3x3 translation matrix\n * @param tx - Translation on X axis\n * @param ty - Translation on Y axis\n * @returns 3x3 translation matrix as Float32Array\n */\nexport function createTranslationMatrix(tx: number, ty: number): Float32Array {\n return new Float32Array([1, 0, 0, 0, 1, 0, tx, ty, 1]);\n}\n\n/**\n * Multiplies two 3x3 matrices\n * @param a - First matrix\n * @param b - Second matrix\n * @returns Result of a * b as Float32Array\n * @note optimization: Skips input validation (e.g. length checks) for performance in hot paths (assumes internal use only).\n */\nexport function multiplyMatrices(\n a: Float32Array,\n b: Float32Array\n): Float32Array {\n return new Float32Array([\n a[0] * b[0] + a[3] * b[1] + a[6] * b[2],\n a[1] * b[0] + a[4] * b[1] + a[7] * b[2],\n a[2] * b[0] + a[5] * b[1] + a[8] * b[2],\n\n a[0] * b[3] + a[3] * b[4] + a[6] * b[5],\n a[1] * b[3] + a[4] * b[4] + a[7] * b[5],\n a[2] * b[3] + a[5] * b[4] + a[8] * b[5],\n\n a[0] * b[6] + a[3] * b[7] + a[6] * b[8],\n a[1] * b[6] + a[4] * b[7] + a[7] * b[8],\n a[2] * b[6] + a[5] * b[7] + a[8] * b[8],\n ]);\n}\n","/**\n * Debug panel showing basic scatterplot state and performance metrics\n *\n * Styles are controlled via CSS custom properties (--scatterplot-debug-*).\n */\n\nimport type { Camera } from '../types/camera';\nimport './scatterplot.css';\n\nexport interface LassoPerformanceMetrics {\n /** Time taken in milliseconds */\n timeMs: number;\n /** Number of points selected */\n selectedCount: number;\n}\n\ninterface DebugPanelProps {\n /** Number of points being rendered */\n count: number;\n /** Current camera state (zoom and pan) */\n camera: Camera;\n /** Last lasso selection performance metrics */\n lassoMetrics?: LassoPerformanceMetrics;\n}\n\nexport function DebugPanel({ count, camera, lassoMetrics }: DebugPanelProps) {\n const { zoom, pan } = camera;\n return (\n <div className=\"scatterplot-debug-panel\">\n <div className=\"scatterplot-debug-panel-title\">Debug Info</div>\n <div>Points: {count.toLocaleString()}</div>\n <div>Zoom: {zoom.toFixed(3)}x</div>\n <div>\n Pan: ({pan.x.toFixed(3)}, {pan.y.toFixed(3)})\n </div>\n {lassoMetrics && (\n <div\n style={{\n marginTop: '5px',\n borderTop: '1px solid #444',\n paddingTop: '5px',\n }}\n >\n <div>Last lasso: {lassoMetrics.timeMs.toFixed(1)}ms</div>\n <div>Selected: {lassoMetrics.selectedCount.toLocaleString()}</div>\n </div>\n )}\n </div>\n );\n}\n","/**\n * SVG overlay component for rendering lasso selection path\n *\n * Displays the lasso polygon being drawn by the user as a semi-transparent\n * overlay on top of the scatterplot canvas.\n *\n * Styles are controlled via CSS custom properties (--scatterplot-lasso-*).\n */\n\nimport './scatterplot.css';\n\ninterface LassoOverlayProps {\n /** Current lasso path as array of [x, y] screen coordinates */\n lassoPath: [number, number][];\n\n /** Canvas width in pixels */\n width: number;\n\n /** Canvas height in pixels */\n height: number;\n\n /** Whether lasso is currently being drawn */\n isDrawing: boolean;\n}\n\n/**\n * Renders an SVG overlay showing the lasso selection path\n */\nexport function LassoOverlay({\n lassoPath,\n width,\n height,\n isDrawing,\n}: LassoOverlayProps) {\n if (!isDrawing || lassoPath.length < 2) {\n return null;\n }\n\n // Convert path to SVG polyline points string\n const points = lassoPath.map(([x, y]) => `${x},${y}`).join(' ');\n\n return (\n <svg\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n width,\n height,\n pointerEvents: 'none', // Prevents blocking canvas mouse events (critical for lasso drawing)\n zIndex: 10,\n }}\n >\n <polygon className=\"scatterplot-lasso-polygon\" points={points} />\n </svg>\n );\n}\n","import {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useCamera } from '../hooks/useCamera';\nimport { useContainerSize } from '../hooks/useContainerSize';\nimport { useLasso } from '../hooks/useLasso';\nimport fragmentShaderSource from '../shaders/scatterplot.frag.glsl?raw';\nimport vertexShaderSource from '../shaders/scatterplot.vert.glsl?raw';\nimport { themeToCssProperties } from '../theme/cssProperties';\nimport { defaultTheme } from '../theme/presets';\nimport type { ScatterplotTheme } from '../theme/types';\nimport type { Camera } from '../types/camera';\nimport { DEFAULT_CAMERA } from '../types/camera';\nimport { createFlagBuffer } from '../utils/flags';\nimport {\n calculateBounds,\n getViewportScales,\n normalizePositionsIsotropic,\n} from '../utils/geometry';\nimport {\n findClosestPointRaw,\n findPointsInLassoRaw,\n} from '../utils/interaction';\nimport {\n createScaleMatrix,\n createTranslationMatrix,\n multiplyMatrices,\n} from '../utils/matrix';\nimport { compileShader, createProgram, hexToRgb } from '../utils/webgl';\nimport { DebugPanel, type LassoPerformanceMetrics } from './DebugPanel';\nimport { LassoOverlay } from './LassoOverlay';\n\n/** Default width in pixels if container size is unavailable */\nexport const DEFAULT_FALLBACK_WIDTH = 800;\n/** Default height in pixels if container size is unavailable */\nexport const DEFAULT_FALLBACK_HEIGHT = 600;\n\n/**\n * Props for the ScatterplotGL component\n *\n * @example\n * ```tsx\n * <ScatterplotGL\n * positions={new Float32Array([0, 0, 1, 1, 2, 0])}\n * colors={new Uint8Array([255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255])}\n * width={800}\n * height={600}\n * onLassoComplete={(indices) => console.log('Selected:', indices)}\n * />\n * ```\n */\ninterface ScatterplotGLProps {\n // === Dimensions ===\n\n /** Canvas width in CSS pixels (if not provided, fills container) */\n width?: number;\n /** Canvas height in CSS pixels (if not provided, fills container) */\n height?: number;\n\n // === Data (required) ===\n\n /** Point positions as [x, y, x, y, ...] in data space. Automatically normalized with isotropic scaling. */\n positions: Float32Array;\n /** Point colors as [r, g, b, a, ...] in 0-255 range. Hardware-normalized to 0.0-1.0 in shader. */\n colors: Uint8Array;\n\n // === Rendering options ===\n\n /** Show debug panel with performance metrics. */\n debug?: boolean;\n /** Device pixel ratio for crisp rendering on high-DPI displays. */\n pixelRatio?: number;\n /** Flag buffer for selection/highlight state (one byte per point). Use createFlagBuffer() to generate. */\n flags?: Uint8Array;\n\n // === Point interaction ===\n\n /** Called when user clicks on or near a point. Receives point index or null if no point nearby. */\n onPointClick?: (index: number | null) => void;\n\n // === Lasso selection ===\n\n /** Enable lasso selection mode (disables pan/zoom and point click). */\n lassoEnabled?: boolean;\n /** Called when lasso selection completes with indices of selected points. */\n onLassoComplete?: (indices: Set<number>) => void;\n /** Called during lasso drawing for real-time highlight feedback. */\n onLassoUpdate?: (indices: Set<number>) => void;\n\n // === Camera (controlled mode) ===\n\n /** Current camera state (zoom and pan). If provided, component is controlled. */\n camera?: Camera;\n /** Called when camera changes (for controlled mode or to sync state). Supports functional updates. */\n onCameraChange?: (camera: Camera | ((prev: Camera) => Camera)) => void;\n /** Enable pan and zoom interactions. */\n panZoomEnabled?: boolean;\n\n // === Styling ===\n\n /** Theme configuration for styling the scatterplot */\n theme?: ScatterplotTheme;\n /** Optional className for the wrapper div */\n className?: string;\n}\n\nfunction ScatterplotGL({\n width,\n height,\n positions,\n colors,\n debug = false,\n pixelRatio = window.devicePixelRatio || 1,\n flags,\n onPointClick,\n lassoEnabled = false,\n onLassoComplete,\n onLassoUpdate,\n camera: controlledCamera,\n onCameraChange,\n panZoomEnabled = true,\n theme = defaultTheme,\n className,\n}: ScatterplotGLProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const containerSize = useContainerSize(containerRef);\n\n // Use provided dimensions or container size (with fallback to avoid 0 dimensions)\n const finalWidth =\n width ??\n (containerSize.width > 0 ? containerSize.width : DEFAULT_FALLBACK_WIDTH);\n const finalHeight =\n height ??\n (containerSize.height > 0 ? containerSize.height : DEFAULT_FALLBACK_HEIGHT);\n\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n // Canvas state for hooks that need re-render when ref is attached\n // useLayoutEffect runs synchronously after DOM mutations, ensuring hooks\n // get the canvas on the first paint rather than after an extra render cycle\n const [canvas, setCanvas] = useState<HTMLCanvasElement | null>(null);\n useLayoutEffect(() => {\n setCanvas(canvasRef.current);\n }, []);\n\n // Track lasso performance metrics (only when debug mode is enabled)\n const [lassoMetrics, setLassoMetrics] = useState<\n LassoPerformanceMetrics | undefined\n >(undefined);\n\n // Derive count from array sizes\n const count = positions.length / 2;\n\n // Refs to store WebGL resources (avoid recompilation on every render)\n const glRef = useRef<WebGL2RenderingContext | null>(null);\n const programRef = useRef<WebGLProgram | null>(null);\n const positionLocationRef = useRef<number>(-1);\n const colorLocationRef = useRef<number>(-1);\n const flagLocationRef = useRef<number>(-1);\n const matrixLocationRef = useRef<WebGLUniformLocation | null>(null);\n const pointSizeLocationRef = useRef<WebGLUniformLocation | null>(null);\n const opacityLocationRef = useRef<WebGLUniformLocation | null>(null);\n const backgroundOpacityLocationRef = useRef<WebGLUniformLocation | null>(\n null\n );\n const highlightBrightnessLocationRef = useRef<WebGLUniformLocation | null>(\n null\n );\n const highlightSizeScaleLocationRef = useRef<WebGLUniformLocation | null>(\n null\n );\n const unselectedSizeScaleLocationRef = useRef<WebGLUniformLocation | null>(\n null\n );\n const positionBufferRef = useRef<WebGLBuffer | null>(null);\n const colorBufferRef = useRef<WebGLBuffer | null>(null);\n const flagBufferRef = useRef<WebGLBuffer | null>(null);\n\n // Calculate data bounds for isotropic scaling\n const dataBounds = useMemo(() => {\n return calculateBounds(positions, count);\n }, [positions, count]);\n\n // Convert theme to CSS custom properties for DOM children\n const cssVars = useMemo(() => themeToCssProperties(theme), [theme]);\n\n // Convert canvas background color to WebGL format (0-1 range)\n const bgColor = useMemo(() => {\n const [r, g, b] = hexToRgb(theme.canvas.background);\n return [r / 255, g / 255, b / 255, 1.0] as const;\n }, [theme.canvas.background]);\n\n // Internal state (used when uncontrolled)\n const [internalCamera, setInternalCamera] = useState<Camera>(DEFAULT_CAMERA);\n\n // Determine if controlled or uncontrolled\n const isCameraControlled = controlledCamera !== undefined;\n\n // Use controlled value if provided, otherwise use internal state\n const camera = isCameraControlled ? controlledCamera : internalCamera;\n\n // Handler that works for both controlled and uncontrolled\n // Wrapped in useCallback to prevent re-creating on every render\n const handleCameraChange = useCallback(\n (newCamera: Camera | ((prev: Camera) => Camera)) => {\n if (onCameraChange) {\n onCameraChange(newCamera);\n }\n if (!isCameraControlled) {\n setInternalCamera(newCamera);\n }\n },\n [onCameraChange, isCameraControlled]\n );\n\n // Setup camera controls (zoom and pan interactions)\n const { justFinishedPanningRef } = useCamera({\n canvas,\n width: finalWidth,\n height: finalHeight,\n onCameraChange: handleCameraChange,\n enabled: panZoomEnabled,\n });\n\n // Setup lasso selection - hook manages lifecycle automatically\n const { lassoPath, isDrawing } = useLasso({\n canvas,\n enabled: lassoEnabled,\n onLassoComplete: (polygon) => {\n if (onLassoComplete) {\n const startTime = debug ? performance.now() : 0;\n const indices = findPointsInLassoRaw(\n polygon,\n positions,\n count,\n finalWidth,\n finalHeight,\n camera,\n dataBounds,\n theme.canvas.dataPadding\n );\n if (debug) {\n setLassoMetrics({\n timeMs: performance.now() - startTime,\n selectedCount: indices.size,\n });\n }\n onLassoComplete(indices);\n }\n },\n onLassoUpdate: (polygon) => {\n if (onLassoUpdate) {\n const indices = findPointsInLassoRaw(\n polygon,\n positions,\n count,\n finalWidth,\n finalHeight,\n camera,\n dataBounds,\n theme.canvas.dataPadding\n );\n onLassoUpdate(indices);\n }\n },\n });\n\n // Setup click detection (disabled when lasso is enabled to prevent conflict)\n /* c8 ignore start -- requires browser canvas */\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas || !onPointClick || lassoEnabled) return;\n\n const handleClick = (event: MouseEvent) => {\n // Suppress click if a pan just occurred\n if (justFinishedPanningRef.current) {\n justFinishedPanningRef.current = false;\n return;\n }\n\n const rect = canvas.getBoundingClientRect();\n const clickX = event.clientX - rect.left;\n const clickY = event.clientY - rect.top;\n\n const closestIndex = findClosestPointRaw(\n clickX,\n clickY,\n positions,\n count,\n finalWidth,\n finalHeight,\n camera,\n theme.points.size, // Use point size as max distance threshold\n dataBounds, // Pass pre-computed bounds for performance\n theme.canvas.dataPadding\n );\n\n onPointClick(closestIndex);\n };\n\n canvas.addEventListener('click', handleClick);\n return () => canvas.removeEventListener('click', handleClick);\n }, [\n positions,\n count,\n finalWidth,\n finalHeight,\n camera,\n theme.points.size,\n onPointClick,\n dataBounds,\n theme.canvas.dataPadding,\n lassoEnabled,\n justFinishedPanningRef,\n ]);\n /* c8 ignore stop */\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Effect 1: Initialize WebGL\n // Runs: once on mount\n // ─────────────────────────────────────────────────────────────────────────────\n /* c8 ignore start -- WebGL effects require browser context */\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n // Get WebGL 2 context\n // - preserveDrawingBuffer: prevent flicker on resize\n // - antialias: false because we do our own AA in the shader; hardware MSAA causes artifacts at point edges\n const gl = canvas.getContext('webgl2', {\n preserveDrawingBuffer: true,\n antialias: false,\n });\n if (!gl) {\n console.error('WebGL 2 not supported');\n return;\n }\n\n // Store context in ref\n glRef.current = gl;\n\n // Compile shaders\n const vertexShader = compileShader(\n gl,\n gl.VERTEX_SHADER,\n vertexShaderSource\n );\n const fragmentShader = compileShader(\n gl,\n gl.FRAGMENT_SHADER,\n fragmentShaderSource\n );\n if (!vertexShader || !fragmentShader) return;\n\n // Create and link program\n const program = createProgram(gl, vertexShader, fragmentShader);\n if (!program) return;\n\n // Store program in ref\n programRef.current = program;\n\n // Get and store attribute/uniform locations\n positionLocationRef.current = gl.getAttribLocation(program, 'a_position');\n colorLocationRef.current = gl.getAttribLocation(program, 'a_color');\n flagLocationRef.current = gl.getAttribLocation(program, 'a_flag');\n matrixLocationRef.current = gl.getUniformLocation(program, 'u_matrix');\n pointSizeLocationRef.current = gl.getUniformLocation(\n program,\n 'u_pointSize'\n );\n opacityLocationRef.current = gl.getUniformLocation(program, 'u_opacity');\n backgroundOpacityLocationRef.current = gl.getUniformLocation(\n program,\n 'u_backgroundOpacity'\n );\n highlightBrightnessLocationRef.current = gl.getUniformLocation(\n program,\n 'u_highlightBrightness'\n );\n highlightSizeScaleLocationRef.current = gl.getUniformLocation(\n program,\n 'u_highlightSizeScale'\n );\n unselectedSizeScaleLocationRef.current = gl.getUniformLocation(\n program,\n 'u_unselectedSizeScale'\n );\n\n // Validate attribute locations\n if (positionLocationRef.current === -1) {\n console.error('Failed to get attribute location for a_position');\n return;\n }\n if (colorLocationRef.current === -1) {\n console.error('Failed to get attribute location for a_color');\n return;\n }\n if (flagLocationRef.current === -1) {\n console.error('Failed to get attribute location for a_flag');\n return;\n }\n if (!matrixLocationRef.current) {\n console.error('Failed to get uniform location for u_matrix');\n return;\n }\n if (!pointSizeLocationRef.current) {\n console.error('Failed to get uniform location for u_pointSize');\n return;\n }\n\n // Create buffers (will be populated in render effect)\n const positionBuffer = gl.createBuffer();\n const colorBuffer = gl.createBuffer();\n const flagBuffer = gl.createBuffer();\n positionBufferRef.current = positionBuffer;\n colorBufferRef.current = colorBuffer;\n flagBufferRef.current = flagBuffer;\n\n // Enable alpha blending with premultiplied alpha for correct edge rendering\n // Shader outputs premultiplied RGB (color * alpha) to avoid halos at transparent edges\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n\n // Enable depth testing for z-depth layering\n gl.enable(gl.DEPTH_TEST);\n gl.depthFunc(gl.LESS); // Closer points (lower z) drawn on top\n // Note: When multiple points have the same z-value, first-drawn wins.\n // This works correctly for single-point selection (current implementation).\n\n // Cleanup on unmount\n return () => {\n if (vertexShader) gl.deleteShader(vertexShader);\n if (fragmentShader) gl.deleteShader(fragmentShader);\n if (programRef.current) {\n gl.deleteProgram(programRef.current);\n programRef.current = null;\n }\n if (positionBufferRef.current) {\n gl.deleteBuffer(positionBufferRef.current);\n positionBufferRef.current = null;\n }\n if (colorBufferRef.current) {\n gl.deleteBuffer(colorBufferRef.current);\n colorBufferRef.current = null;\n }\n if (flagBufferRef.current) {\n gl.deleteBuffer(flagBufferRef.current);\n flagBufferRef.current = null;\n }\n };\n }, []);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Effect 2: Upload positions\n // Runs: when positions change (rare, expensive - requires normalization)\n // ─────────────────────────────────────────────────────────────────────────────\n useEffect(() => {\n const gl = glRef.current;\n const positionBuffer = positionBufferRef.current;\n if (!gl || !positionBuffer || count === 0) return;\n\n const { xMin, xMax, yMin, yMax } = dataBounds;\n\n // Normalize positions with isotropic scaling (expensive)\n const normalizedPositions = normalizePositionsIsotropic(\n positions,\n count,\n xMin,\n xMax,\n yMin,\n yMax\n );\n gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, normalizedPositions, gl.STATIC_DRAW);\n gl.enableVertexAttribArray(positionLocationRef.current);\n gl.vertexAttribPointer(\n positionLocationRef.current,\n 2,\n gl.FLOAT,\n false,\n 0,\n 0\n );\n }, [positions, count, dataBounds]);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Effect 3: Upload colors\n // Runs: when colors change (frequent, cheap - direct buffer copy)\n // ─────────────────────────────────────────────────────────────────────────────\n useEffect(() => {\n const gl = glRef.current;\n const colorBuffer = colorBufferRef.current;\n if (!gl || !colorBuffer || count === 0) return;\n\n // Direct upload - no normalization needed (cheap)\n gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);\n gl.enableVertexAttribArray(colorLocationRef.current);\n gl.vertexAttribPointer(\n colorLocationRef.current,\n 4,\n gl.UNSIGNED_BYTE,\n true,\n 0,\n 0\n );\n }, [colors, count]);\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Effect 4: Render\n // Runs: when zoom/pan/flags/size change (most frequent - 60fps during interaction)\n // ─────────────────────────────────────────────────────────────────────────────\n // NOTE: positions and colors must be in deps to trigger redraw when data changes\n // without camera interaction. Removing them causes the screen to not update until\n // zoom/pan occurs. See test: \"should redraw when $property change\"\n // biome-ignore lint/correctness/useExhaustiveDependencies: see NOTE above\n useEffect(() => {\n const gl = glRef.current;\n const program = programRef.current;\n const positionBuffer = positionBufferRef.current;\n const colorBuffer = colorBufferRef.current;\n const flagBuffer = flagBufferRef.current;\n\n // Destructure camera for easier access\n const { zoom, pan } = camera;\n\n // Early exit if not initialized\n if (!gl || !program) return;\n\n // Handle empty data\n if (count === 0) {\n gl.clearColor(bgColor[0], bgColor[1], bgColor[2], bgColor[3]);\n gl.clear(gl.COLOR_BUFFER_BIT);\n return;\n }\n\n // biome-ignore lint/correctness/useHookAtTopLevel: WebGL API, not a React hook\n gl.useProgram(program);\n\n // Always update flag buffer (cheap operation, ensures consistency)\n if (flagBuffer) {\n const flagData = flags ?? createFlagBuffer(count);\n gl.bindBuffer(gl.ARRAY_BUFFER, flagBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, flagData, gl.DYNAMIC_DRAW);\n gl.enableVertexAttribArray(flagLocationRef.current);\n gl.vertexAttribIPointer(\n flagLocationRef.current,\n 1,\n gl.UNSIGNED_BYTE,\n 0,\n 0\n );\n }\n\n // Always update transformation matrix (cheap operation)\n // Account for viewport aspect ratio and data padding to prevent stretching\n const { scaleX, scaleY } = getViewportScales(\n finalWidth,\n finalHeight,\n zoom,\n theme.canvas.dataPadding\n );\n const scaleMatrix = createScaleMatrix(scaleX, scaleY);\n const translationMatrix = createTranslationMatrix(pan.x, pan.y);\n const finalMatrix = multiplyMatrices(translationMatrix, scaleMatrix);\n gl.uniformMatrix3fv(matrixLocationRef.current, false, finalMatrix);\n\n // Always update point size uniform (cheap operation, scale by pixel ratio for crisp rendering)\n gl.uniform1f(pointSizeLocationRef.current, theme.points.size * pixelRatio);\n\n // Update theme-based uniforms\n gl.uniform1f(opacityLocationRef.current, theme.points.opacity);\n gl.uniform1f(\n backgroundOpacityLocationRef.current,\n theme.points.backgroundOpacity\n );\n gl.uniform1f(\n highlightBrightnessLocationRef.current,\n theme.points.highlightBrightness\n );\n gl.uniform1f(\n highlightSizeScaleLocationRef.current,\n theme.points.highlightSizeScale\n );\n gl.uniform1f(\n unselectedSizeScaleLocationRef.current,\n theme.points.unselectedSizeScale\n );\n\n // Ensure all attribute buffers are bound before drawing\n if (positionBuffer) {\n gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);\n gl.enableVertexAttribArray(positionLocationRef.current);\n gl.vertexAttribPointer(\n positionLocationRef.current,\n 2,\n gl.FLOAT,\n false,\n 0,\n 0\n );\n }\n\n if (colorBuffer) {\n gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);\n gl.enableVertexAttribArray(colorLocationRef.current);\n gl.vertexAttribPointer(\n colorLocationRef.current,\n 4,\n gl.UNSIGNED_BYTE,\n true,\n 0,\n 0\n );\n }\n\n if (flagBuffer) {\n gl.bindBuffer(gl.ARRAY_BUFFER, flagBuffer);\n gl.enableVertexAttribArray(flagLocationRef.current);\n gl.vertexAttribIPointer(\n flagLocationRef.current,\n 1,\n gl.UNSIGNED_BYTE,\n 0,\n 0\n );\n }\n\n // Clear and draw (use pixel ratio for actual canvas resolution)\n gl.viewport(0, 0, finalWidth * pixelRatio, finalHeight * pixelRatio);\n gl.clearColor(bgColor[0], bgColor[1], bgColor[2], bgColor[3]);\n gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n gl.drawArrays(gl.POINTS, 0, count);\n }, [\n positions,\n colors,\n flags,\n camera,\n theme,\n pixelRatio,\n count,\n finalWidth,\n finalHeight,\n bgColor,\n ]);\n /* c8 ignore stop */\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{\n position: 'relative',\n lineHeight: 0,\n width: width ? `${width}px` : '100%',\n height: height ? `${height}px` : '100%',\n ...cssVars,\n }}\n >\n <canvas\n ref={canvasRef}\n width={finalWidth * pixelRatio}\n height={finalHeight * pixelRatio}\n style={{\n width: `${finalWidth}px`,\n height: `${finalHeight}px`,\n ...(debug ? { border: '1px dashed #c00' } : {}),\n }}\n />\n {lassoEnabled && (\n <LassoOverlay\n lassoPath={lassoPath}\n width={finalWidth}\n height={finalHeight}\n isDrawing={isDrawing}\n />\n )}\n {debug && (\n <DebugPanel count={count} camera={camera} lassoMetrics={lassoMetrics} />\n )}\n </div>\n );\n}\n\nexport default ScatterplotGL;\n","/**\n * Scatterplot - High-level wrapper for WebGL scatterplot visualization\n *\n * Encapsulates all state management (zoom, pan, selection, lasso mode) and provides\n * a simple, configurable interface for rendering interactive scatterplots.\n *\n * @example\n * ```tsx\n * // Simple usage\n * <Scatterplot\n * data={myData}\n * onSelectionChange={(indices) => console.log('Selected:', indices)}\n * />\n *\n * // With configuration\n * <Scatterplot\n * data={myData}\n * width={1200}\n * height={800}\n * enableLasso={true}\n * theme={darkTheme}\n * debug={true}\n * />\n * ```\n */\n\nimport { useCallback, useMemo, useState } from 'react';\nimport { useSelection } from '../hooks/useSelection';\nimport type { ScatterplotTheme } from '../theme/types';\nimport type { Point } from '../types';\nimport type { Camera } from '../types/camera';\nimport { DEFAULT_CAMERA } from '../types/camera';\nimport { createFlagBuffer } from '../utils/flags';\nimport { pointsToBuffers } from '../utils/webgl';\nimport ScatterplotGL, {\n DEFAULT_FALLBACK_HEIGHT,\n DEFAULT_FALLBACK_WIDTH,\n} from './ScatterplotGL';\n\n// Default threshold of points for disabling real-time lasso highlighting\nexport const DEFAULT_LASSO_REALTIME_POINTS_THRESHOLD = 1_000_000;\n\nexport interface ScatterplotProps {\n /** Data points to visualize */\n points: Point[];\n\n /** Canvas width in pixels (if not provided, fills container). For default, @see {@link DEFAULT_FALLBACK_WIDTH} */\n width?: number;\n\n /** Canvas height in pixels (if not provided, fills container). For default, @see {@link DEFAULT_FALLBACK_HEIGHT} */\n height?: number;\n\n /** Initial camera state (zoom and pan) */\n initialCamera?: Camera;\n\n /** Theme configuration for styling */\n theme?: ScatterplotTheme;\n\n /** Device pixel ratio for high-DPI displays */\n pixelRatio?: number;\n\n /** Enable lasso selection mode */\n enableLasso?: boolean;\n\n /** Enable pan and zoom interactions */\n enablePanZoom?: boolean;\n\n /** Show debug panel with performance metrics */\n debug?: boolean;\n\n /** Callback when selection changes (receives Set of selected point indices) */\n onSelectionChange?: (indices: Set<number>) => void;\n\n /**\n * Threshold for disabling real-time lasso highlighting.\n * For default, @see {@link DEFAULT_LASSO_REALTIME_POINTS_THRESHOLD}\n */\n lassoRealtimeThreshold?: number;\n\n /**\n * Advanced: Control camera externally\n * - If provided: fully controlled (parent manages state)\n * - If omitted: component manages state internally\n */\n controlled?: {\n camera: Camera;\n onCameraChange?: (camera: Camera | ((prev: Camera) => Camera)) => void;\n };\n}\n\nexport function Scatterplot({\n points: data,\n width,\n height,\n initialCamera = DEFAULT_CAMERA,\n theme,\n pixelRatio,\n enableLasso = true,\n enablePanZoom = true,\n debug = false,\n onSelectionChange,\n lassoRealtimeThreshold = DEFAULT_LASSO_REALTIME_POINTS_THRESHOLD,\n controlled,\n}: ScatterplotProps) {\n // Internal camera state (used when uncontrolled)\n const [internalCamera, setInternalCamera] = useState<Camera>(initialCamera);\n\n // Determine if controlled or uncontrolled\n const isCameraControlled = controlled?.camera !== undefined;\n\n // Use controlled value if provided, otherwise use internal state\n const camera =\n isCameraControlled && controlled?.camera\n ? controlled.camera\n : internalCamera;\n\n // Selection and highlight state\n const { selectedIndices, handlePointClick, setSelection } = useSelection();\n const [highlightedIndices, setHighlightedIndices] = useState<Set<number>>(\n new Set()\n );\n\n // Convert Point[] to raw buffers (memoized for performance)\n const { positions, colors } = useMemo(() => pointsToBuffers(data), [data]);\n\n // Handler: update internal state AND notify parent callback (if provided)\n const handleCameraChange = useCallback(\n (newCamera: Camera | ((prev: Camera) => Camera)) => {\n if (controlled?.onCameraChange) {\n controlled.onCameraChange(newCamera);\n }\n if (!isCameraControlled) {\n setInternalCamera(newCamera);\n }\n },\n [controlled?.onCameraChange, isCameraControlled]\n );\n\n // Create flag buffer based on selection and highlight state\n const flags = useMemo(\n () =>\n createFlagBuffer(\n data.length,\n selectedIndices.size > 0 ? selectedIndices : undefined,\n highlightedIndices.size > 0 ? highlightedIndices : undefined\n ),\n [data.length, selectedIndices, highlightedIndices]\n );\n\n // Handle lasso completion\n const handleLassoComplete = (indices: Set<number>) => {\n setHighlightedIndices(new Set()); // Clear highlights\n setSelection(indices); // Set selection\n\n // Notify parent if callback provided\n if (onSelectionChange) {\n onSelectionChange(indices);\n }\n };\n\n // Handle lasso update (real-time highlighting during drawing)\n const handleLassoUpdate = (indices: Set<number>) => {\n // Only highlight during drawing for small datasets (performance optimization)\n if (data.length >= lassoRealtimeThreshold) {\n return;\n }\n\n // Only update if the set actually changed (avoid infinite loops)\n setHighlightedIndices((prev) => {\n const areSetsEqual =\n prev.size === indices.size &&\n [...prev].every((value) => indices.has(value));\n\n return areSetsEqual ? prev : indices;\n });\n };\n\n // Handle point click\n const handleClick = (index: number | null) => {\n handlePointClick(index);\n\n // Notify parent if callback provided\n if (onSelectionChange) {\n onSelectionChange(index !== null ? new Set([index]) : new Set());\n }\n };\n\n return (\n <ScatterplotGL\n width={width}\n height={height}\n positions={positions}\n colors={colors}\n flags={flags}\n camera={camera}\n onCameraChange={handleCameraChange}\n onPointClick={handleClick}\n panZoomEnabled={enablePanZoom}\n lassoEnabled={enableLasso}\n onLassoComplete={handleLassoComplete}\n onLassoUpdate={handleLassoUpdate}\n theme={theme}\n pixelRatio={pixelRatio}\n debug={debug}\n />\n );\n}\n"],"names":["useSelection","selectedIndices","setSelectedIndices","useState","handlePointClick","useCallback","index","setSelection","indices","clearSelection","isSelected","DEFAULT_CAMERA","FLAG_SELECTED","FLAG_BACKGROUND","FLAG_HIGHLIGHT","encodeFlags","isBackground","isHighlight","flag","createFlagBuffer","count","highlightedIndices","backgroundIndices","flags","hasSelection","i","DEFAULT_COLOR","DEFAULT_COLOR_RGB_BYTES","DEFAULT_ALPHA","compileShader","gl","type","source","shader","createProgram","vertexShader","fragmentShader","program","hexToRgb","hex","cleanHex","fullHex","c","num","r","g","b","hexToRgba","alpha","pointsToBuffers","data","defaultColor","positions","colors","point","a","useCamera","canvas","width","height","onCameraChange","enabled","justFinishedPanningRef","useRef","useEffect","handleWheel","e","rect","mouseX","mouseY","ndcX","ndcY","zoomFactor","prevCamera","currentZoom","currentPan","newZoom","worldX","worldY","newPanX","newPanY","isDragging","hasMoved","lastMousePos","handleMouseDown","handleMouseMove","deltaX","deltaY","glDeltaX","glDeltaY","handleMouseUp","handleMouseEnter","useContainerSize","containerRef","size","setSize","rafIdRef","container","resizeObserver","entries","entry","clientWidth","clientHeight","useLatestRef","value","ref","LASSO_MIN_DISPLACEMENT","useLasso","options","onLassoComplete","onLassoUpdate","minDisplacement","lassoPath","setLassoPath","isDrawing","setIsDrawing","onLassoCompleteRef","onLassoUpdateRef","rafPendingRef","drawing","currentPath","startPosition","x","y","initialPath","newPath","displacement","endPosition","dx","dy","isLasso","fragmentShaderSource","vertexShaderSource","CSS_PREFIX","CSS_SECTIONS","toKebab","str","themeToCssProperties","theme","vars","section","values","key","BASE_THEME","createTheme","overrides","base","lightTheme","darkTheme","highContrastTheme","defaultTheme","calculateBounds","xMin","xMax","yMin","yMax","getNormalizationScales","xRange","yRange","scale","xOffset","yOffset","getViewportScales","zoom","dataPadding","viewportAspect","minDimension","baseScale","scaleX","scaleY","normalizePositionsIsotropic","normalized","normX","normY","centeredX","centeredY","dataPointToScreen","panX","panY","canvasWidth","canvasHeight","normalizedX","normalizedY","webglX","webglY","transformedX","transformedY","screenX","screenY","isPointInPolygon","polygon","inside","j","xi","yi","xj","yj","DEFAULT_INTERACTION_DISTANCE_PX","getScreenProjectionUtils","bounds","findClosestPointRaw","clickX","clickY","camera","maxDistance","pan","closestIndex","minDistance","distance","findPointsInLassoRaw","createScaleMatrix","createTranslationMatrix","tx","ty","multiplyMatrices","DebugPanel","lassoMetrics","jsxs","jsx","LassoOverlay","points","DEFAULT_FALLBACK_WIDTH","DEFAULT_FALLBACK_HEIGHT","ScatterplotGL","debug","pixelRatio","onPointClick","lassoEnabled","controlledCamera","panZoomEnabled","className","containerSize","finalWidth","finalHeight","canvasRef","setCanvas","useLayoutEffect","setLassoMetrics","glRef","programRef","positionLocationRef","colorLocationRef","flagLocationRef","matrixLocationRef","pointSizeLocationRef","opacityLocationRef","backgroundOpacityLocationRef","highlightBrightnessLocationRef","highlightSizeScaleLocationRef","unselectedSizeScaleLocationRef","positionBufferRef","colorBufferRef","flagBufferRef","dataBounds","useMemo","cssVars","bgColor","internalCamera","setInternalCamera","isCameraControlled","handleCameraChange","newCamera","startTime","handleClick","event","positionBuffer","colorBuffer","flagBuffer","normalizedPositions","flagData","scaleMatrix","translationMatrix","finalMatrix","DEFAULT_LASSO_REALTIME_POINTS_THRESHOLD","Scatterplot","initialCamera","enableLasso","enablePanZoom","onSelectionChange","lassoRealtimeThreshold","controlled","setHighlightedIndices","prev"],"mappings":";;AAuCO,SAASA,KAAmC;AACjD,QAAM,CAACC,GAAiBC,CAAkB,IAAIC;AAAA,wBACxC,IAAA;AAAA,EAAI,GAGJC,IAAmBC,EAAY,CAACC,MAAyB;AAC7D,IAEEJ,EAFEI,MAAU,OAEO,oBAAI,QAGJ,oBAAI,IAAI,CAACA,CAAK,CAAC,CAHN;AAAA,EAKhC,GAAG,CAAA,CAAE,GAECC,IAAeF,EAAY,CAACG,MAAyB;AACzD,IAAAN,EAAmB,IAAI,IAAIM,CAAO,CAAC;AAAA,EACrC,GAAG,CAAA,CAAE,GAECC,IAAiBJ,EAAY,MAAM;AACvC,IAAAH,EAAmB,oBAAI,KAAK;AAAA,EAC9B,GAAG,CAAA,CAAE,GAECQ,IAAaL;AAAA,IACjB,CAACC,MACQL,EAAgB,IAAIK,CAAK;AAAA,IAElC,CAACL,CAAe;AAAA,EAAA;AAGlB,SAAO;AAAA,IACL,iBAAAA;AAAA,IACA,kBAAAG;AAAA,IACA,cAAAG;AAAA,IACA,gBAAAE;AAAA,IACA,YAAAC;AAAA,EAAA;AAEJ;AC/DO,MAAMC,KAAyB;AAAA,EACpC,MAAM;AAAA,EACN,KAAK,EAAE,GAAG,GAAG,GAAG,EAAA;AAClB,GCHaC,KAAgB,GAChBC,KAAkB,GAClBC,KAAiB;AASvB,SAASC,GACdL,GACAM,GACAC,GACQ;AACR,MAAIC,IAAO;AACX,SAAIR,MAAYQ,KAAQN,KACpBI,MAAcE,KAAQL,KACtBI,MAAaC,KAAQJ,KAClBI;AACT;AAWO,SAASC,GACdC,GACAnB,GACAoB,GACAC,GACY;AACZ,QAAMC,IAAQ,IAAI,WAAWH,CAAK,GAC5BI,IACJvB,MAAoB,UAAaA,EAAgB,OAAO;AAE1D,WAASwB,IAAI,GAAGA,IAAIL,GAAOK,KAAK;AAE9B,UAAMf,IAAac,IAAevB,EAAgB,IAAIwB,CAAC,IAAI,IACrDR,IAAcI,GAAoB,IAAII,CAAC,KAAK,IAG5CT,IACJM,MAAsB,SAClBA,EAAkB,IAAIG,CAAC,IACvBD,KAAgB,CAACd,KAAc,CAACO;AAEtC,IAAAM,EAAME,CAAC,IAAIV,GAAYL,GAAYM,GAAcC,CAAW;AAAA,EAC9D;AAEA,SAAOM;AACT;AC/DO,MAAMG,KAAgB,WAChBC,KAAoD,CAAC,IAAI,KAAK,GAAG,GAEjEC,KAAgB;AAStB,SAASC,GACdC,GACAC,GACAC,GACoB;AACpB,QAAMC,IAASH,EAAG,aAAaC,CAAI;AACnC,SAAKE,KAKLH,EAAG,aAAaG,GAAQD,CAAM,GAC9BF,EAAG,cAAcG,CAAM,GAElBH,EAAG,mBAAmBG,GAAQH,EAAG,cAAc,IAM7CG,KALL,QAAQ,MAAM,yBAAyBH,EAAG,iBAAiBG,CAAM,CAAC,GAClEH,EAAG,aAAaG,CAAM,GACf,UAVP,QAAQ,MAAM,+BAA+B,GACtC;AAaX;AASO,SAASC,GACdJ,GACAK,GACAC,GACqB;AACrB,QAAMC,IAAUP,EAAG,cAAA;AACnB,SAAKO,KAKLP,EAAG,aAAaO,GAASF,CAAY,GACrCL,EAAG,aAAaO,GAASD,CAAc,GACvCN,EAAG,YAAYO,CAAO,GAEjBP,EAAG,oBAAoBO,GAASP,EAAG,WAAW,IAM5CO,KALL,QAAQ,MAAM,uBAAuBP,EAAG,kBAAkBO,CAAO,CAAC,GAClEP,EAAG,cAAcO,CAAO,GACjB,UAXP,QAAQ,MAAM,gCAAgC,GACvC;AAcX;AAOO,SAASC,GAASC,GAAuC;AAE9D,MAAI,CAACA,KAAO,CAAC,uBAAuB,KAAKA,CAAG;AAC1C,mBAAQ;AAAA,MACN,uBAAuBA,CAAG,sBAAsBb,EAAa;AAAA,IAAA,GAExDC;AAIT,QAAMa,IAAWD,EAAI,QAAQ,KAAK,EAAE,GAG9BE,IACJD,EAAS,WAAW,IAChBA,EACG,MAAM,EAAE,EACR,IAAI,CAACE,MAAMA,IAAIA,CAAC,EAChB,KAAK,EAAE,IACVF,GAEAG,IAAM,SAASF,GAAS,EAAE,GAG1BG,IAAKD,KAAO,KAAM,KAClBE,IAAKF,KAAO,IAAK,KACjBG,IAAIH,IAAM;AAEhB,SAAO,CAACC,GAAGC,GAAGC,CAAC;AACjB;AAQO,SAASC,GACdR,GACAS,IAAgBpB,IACkB;AAClC,QAAM,CAAC,GAAGiB,GAAGC,CAAC,IAAIR,GAASC,CAAG;AAC9B,SAAO,CAAC,GAAGM,GAAGC,GAAGE,CAAK;AACxB;AAQO,SAASC,GACdC,GACAC,IAAuBzB,IAC0B;AACjD,QAAM0B,IAAY,IAAI,aAAaF,EAAK,SAAS,CAAC,GAC5CG,IAAS,IAAI,WAAWH,EAAK,SAAS,CAAC;AAE7C,WAAS,IAAI,GAAG,IAAIA,EAAK,QAAQ,KAAK;AACpC,UAAMI,IAAQJ,EAAK,CAAC;AAGpB,IAAAE,EAAU,IAAI,CAAC,IAAIE,EAAM,GACzBF,EAAU,IAAI,IAAI,CAAC,IAAIE,EAAM;AAG7B,UAAM,CAACV,GAAGC,GAAGC,GAAGS,CAAC,IAAIR,GAAUO,EAAM,SAASH,GAAcvB,EAAa;AACzE,IAAAyB,EAAO,IAAI,CAAC,IAAIT,GAChBS,EAAO,IAAI,IAAI,CAAC,IAAIR,GACpBQ,EAAO,IAAI,IAAI,CAAC,IAAIP,GACpBO,EAAO,IAAI,IAAI,CAAC,IAAIE;AAAA,EACtB;AAEA,SAAO,EAAE,WAAAH,GAAW,QAAAC,EAAA;AACtB;ACnHO,SAASG,GAAU;AAAA,EACxB,QAAAC;AAAA,EACA,OAAAC;AAAA,EACA,QAAAC;AAAA,EACA,gBAAAC;AAAA,EACA,SAAAC,IAAU;AACZ,GAAoC;AAClC,QAAMC,IAAyBC,EAAgB,EAAK;AAEpD,SAAAC,EAAU,MAAM;AACd,QAAI,CAACP,KAAU,CAACI,EAAS;AAEzB,UAAMI,IAAc,CAACC,MAAkB;AACrC,MAAAA,EAAE,eAAA;AAGF,YAAMC,IAAOV,EAAO,sBAAA,GACdW,IAASF,EAAE,UAAUC,EAAK,MAC1BE,IAASH,EAAE,UAAUC,EAAK,KAG1BG,IAAQF,IAASV,IAAS,IAAI,GAC9Ba,IAAO,EAAGF,IAASV,IAAU,IAAI,IAGjCa,IAAaN,EAAE,SAAS,IAAI,MAAM,IAAI;AAG5C,MAAAN,EAAe,CAACa,MAAe;AAC7B,cAAMC,IAAcD,EAAW,MACzBE,IAAaF,EAAW,KAGxBG,IAAUF,IAAcF,GAGxBK,KAAUP,IAAOK,EAAW,KAAKD,GACjCI,KAAUP,IAAOI,EAAW,KAAKD,GAGjCK,IAAUT,IAAOO,IAASD,GAC1BI,IAAUT,IAAOO,IAASF;AAEhC,eAAO;AAAA,UACL,MAAMA;AAAA,UACN,KAAK,EAAE,GAAGG,GAAS,GAAGC,EAAA;AAAA,QAAQ;AAAA,MAElC,CAAC;AAAA,IACH;AAEA,WAAAvB,EAAO,iBAAiB,SAASQ,GAAa,EAAE,SAAS,IAAO,GACzD,MAAMR,EAAO,oBAAoB,SAASQ,CAAW;AAAA,EAC9D,GAAG,CAACR,GAAQC,GAAOC,GAAQC,GAAgBC,CAAO,CAAC,GAGnDG,EAAU,MAAM;AACd,QAAI,CAACP,KAAU,CAACI,EAAS;AAEzB,QAAIoB,IAAa,IACbC,IAAW,IACXC,IAAe,EAAE,GAAG,GAAG,GAAG,EAAA;AAE9B,UAAMC,IAAkB,CAAClB,MAAkB;AACzC,MAAAe,IAAa,IACbC,IAAW,IACXpB,EAAuB,UAAU,IACjCqB,IAAe,EAAE,GAAGjB,EAAE,SAAS,GAAGA,EAAE,QAAA;AAAA,IACtC,GAEMmB,IAAkB,CAACnB,MAAkB;AACzC,UAAI,CAACe,EAAY;AAGjB,YAAMK,IAASpB,EAAE,UAAUiB,EAAa,GAClCI,IAASrB,EAAE,UAAUiB,EAAa;AAGxC,OAAI,KAAK,IAAIG,CAAM,IAAI,KAAK,KAAK,IAAIC,CAAM,IAAI,OAC7CL,IAAW;AAIb,YAAMM,IAAYF,IAAS5B,IAAS,GAC9B+B,IAAW,EAAEF,IAAS5B,KAAU;AAGtC,MAAAC,EAAe,CAACa,OAAgB;AAAA,QAC9B,MAAMA,EAAW;AAAA,QACjB,KAAK;AAAA,UACH,GAAGA,EAAW,IAAI,IAAIe;AAAA,UACtB,GAAGf,EAAW,IAAI,IAAIgB;AAAA,QAAA;AAAA,MACxB,EACA,GAEFN,IAAe,EAAE,GAAGjB,EAAE,SAAS,GAAGA,EAAE,QAAA;AAAA,IACtC,GAEMwB,IAAgB,MAAM;AAC1B,MAAIR,MACFpB,EAAuB,UAAU,KAEnCmB,IAAa,IACbC,IAAW;AAAA,IACb,GAEMS,IAAmB,CAACzB,MAAkB;AAE1C,OAAKA,EAAE,UAAU,OAAO,KACtBe,IAAa,IACbE,IAAe,EAAE,GAAGjB,EAAE,SAAS,GAAGA,EAAE,QAAA,KAGpCe,IAAa;AAAA,IAEjB;AAEA,WAAAxB,EAAO,iBAAiB,aAAa2B,CAAe,GACpD3B,EAAO,iBAAiB,aAAa4B,CAAe,GACpD5B,EAAO,iBAAiB,WAAWiC,CAAa,GAChDjC,EAAO,iBAAiB,cAAckC,CAAgB,GAE/C,MAAM;AACX,MAAAlC,EAAO,oBAAoB,aAAa2B,CAAe,GACvD3B,EAAO,oBAAoB,aAAa4B,CAAe,GACvD5B,EAAO,oBAAoB,WAAWiC,CAAa,GACnDjC,EAAO,oBAAoB,cAAckC,CAAgB;AAAA,IAC3D;AAAA,EACF,GAAG,CAAClC,GAAQC,GAAOC,GAAQC,GAAgBC,CAAO,CAAC,GAE5C,EAAE,wBAAAC,EAAA;AACX;AClIO,SAAS8B,GACdC,GACe;AACf,QAAM,CAACC,GAAMC,CAAO,IAAI5F,EAAwB,EAAE,OAAO,GAAG,QAAQ,GAAG,GACjE6F,IAAWjC,EAAsB,IAAI;AAE3C,SAAAC,EAAU,MAAM;AACd,UAAMiC,IAAYJ,EAAa;AAC/B,QAAI,CAACI,EAAW;AAGhB,UAAMC,IAAiB,IAAI,eAAe,CAACC,MAAY;AACrD,YAAMC,IAAQD,EAAQ,CAAC;AACvB,UAAI,CAACC,EAAO;AAEZ,YAAM,EAAE,OAAA1C,GAAO,QAAAC,EAAA,IAAWyC,EAAM;AAGhC,MAAIJ,EAAS,WACX,qBAAqBA,EAAS,OAAO,GAIvCA,EAAS,UAAU,sBAAsB,MAAM;AAC7C,QAAAD,EAAQ,EAAE,OAAArC,GAAO,QAAAC,GAAQ,GACzBqC,EAAS,UAAU;AAAA,MACrB,CAAC;AAAA,IACH,CAAC;AAGD,IAAAE,EAAe,QAAQD,CAAS;AAGhC,UAAM,EAAE,aAAAI,GAAa,cAAAC,EAAA,IAAiBL;AACtC,WAAII,IAAc,KAAKC,IAAe,KACpCP,EAAQ,EAAE,OAAOM,GAAa,QAAQC,GAAc,GAI/C,MAAM;AACX,MAAIN,EAAS,WACX,qBAAqBA,EAAS,OAAO,GAEvCE,EAAe,WAAA;AAAA,IACjB;AAAA,EACF,GAAG,CAACL,CAAY,CAAC,GAEVC;AACT;AC1EO,SAASS,GAAgBC,GAAU;AACxC,QAAMC,IAAM1C,EAAOyC,CAAK;AACxB,SAAAC,EAAI,UAAUD,GACPC;AACT;ACHO,MAAMC,KAAyB;AAgD/B,SAASC,GAASC,GAA0C;AACjE,QAAM;AAAA,IACJ,QAAAnD;AAAA,IACA,SAAAI;AAAA,IACA,iBAAAgD;AAAA,IACA,eAAAC;AAAA,IACA,iBAAAC,IAAkBL;AAAA,EAAA,IAChBE,GAEE,CAACI,GAAWC,CAAY,IAAI9G,EAA6B,CAAA,CAAE,GAC3D,CAAC+G,GAAWC,CAAY,IAAIhH,EAAS,EAAK,GAI1CiH,IAAqBb,GAAaM,CAAe,GACjDQ,IAAmBd,GAAaO,CAAa,GAG7CQ,IAAgBvD,EAAO,EAAK;AAGlC,SAAAC,EAAU,MAAM;AACd,QAAI,CAACH,KAAW,CAACJ;AACf;AAIF,QAAI8D,IAAU,IACVC,IAAkC,CAAA,GAClCC,IAAyC;AAE7C,UAAMrC,IAAkB,CAAClB,MAAkB;AACzC,UAAIA,EAAE,WAAW,EAAG;AAGpB,YAAMC,IADSD,EAAE,cACG,sBAAA,GACdwD,IAAIxD,EAAE,UAAUC,EAAK,MACrBwD,IAAIzD,EAAE,UAAUC,EAAK,KAErByD,IAAkC,CAAC,CAACF,GAAGC,CAAC,CAAC;AAC/C,MAAAF,IAAgB,CAACC,GAAGC,CAAC,GACrBH,IAAcI,GACdL,IAAU,IAEVJ,EAAa,EAAI,GACjBF,EAAaW,CAAW;AAAA,IAC1B,GAGMvC,IAAkB,CAACnB,MAAkB;AAEzC,UADI,CAACqD,KACDD,EAAc,QAAS;AAG3B,MAAAA,EAAc,UAAU,IACxB,sBAAsB,MAAM;AAC1B,QAAAA,EAAc,UAAU;AAAA,MAC1B,CAAC;AAGD,YAAMnD,IADSD,EAAE,cACG,sBAAA,GACdwD,IAAIxD,EAAE,UAAUC,EAAK,MACrBwD,IAAIzD,EAAE,UAAUC,EAAK,KAErB0D,IAA8B,CAAC,GAAGL,GAAa,CAACE,GAAGC,CAAC,CAAC;AAC3D,MAAAH,IAAcK,GACdZ,EAAaY,CAAO,GAEhBR,EAAiB,WACnBA,EAAiB,QAAQQ,CAAO;AAAA,IAEpC,GAEMnC,IAAgB,MAAM;AAC1B,UAAI,CAAC6B,EAAS;AAEd,MAAAA,IAAU,IACVJ,EAAa,EAAK;AAGlB,UAAIW,IAAe;AACnB,UAAIL,KAAiBD,EAAY,SAAS,GAAG;AAC3C,cAAMO,IAAcP,EAAYA,EAAY,SAAS,CAAC,GAChDQ,IAAKD,EAAY,CAAC,IAAIN,EAAc,CAAC,GACrCQ,IAAKF,EAAY,CAAC,IAAIN,EAAc,CAAC;AAC3C,QAAAK,IAAe,KAAK,KAAKE,IAAKA,IAAKC,IAAKA,CAAE;AAAA,MAC5C;AAIA,YAAMC,IAAUJ,KAAgBf;AAChC,MAAAK,EAAmB,QAAQc,IAAUV,IAAc,CAAA,CAAE,GAGrDA,IAAc,CAAA,GACdC,IAAgB,MAChBR,EAAa,CAAA,CAAE;AAAA,IACjB;AAEA,WAAAxD,EAAO,iBAAiB,aAAa2B,CAAe,GACpD3B,EAAO,iBAAiB,aAAa4B,CAAe,GACpD5B,EAAO,iBAAiB,WAAWiC,CAAa,GAChDjC,EAAO,iBAAiB,cAAciC,CAAa,GAE5C,MAAM;AACX,MAAAjC,EAAO,oBAAoB,aAAa2B,CAAe,GACvD3B,EAAO,oBAAoB,aAAa4B,CAAe,GACvD5B,EAAO,oBAAoB,WAAWiC,CAAa,GACnDjC,EAAO,oBAAoB,cAAciC,CAAa;AAAA,IACxD;AAAA,EACF,GAAG,CAAC7B,GAASJ,GAAQsD,GAAiBK,GAAoBC,CAAgB,CAAC,GAEpE;AAAA,IACL,WAAAL;AAAA,IACA,WAAAE;AAAA,EAAA;AAEJ;AChLA,MAAAiB,KAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GCAfC,KAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GCETC,KAAa,iBAMbC,KAAe,CAAC,SAAS,OAAO;AAEtC,SAASC,GAAQC,GAAqB;AACpC,SAAOA,EAAI,QAAQ,UAAU,CAAC9F,MAAM,IAAIA,EAAE,YAAA,CAAa,EAAE;AAC3D;AAKO,SAAS+F,GACdC,GACwB;AACxB,QAAMC,IAA+B,CAAA;AAErC,aAAWC,KAAWN,IAAc;AAClC,UAAMO,IAASH,EAAME,CAAO;AAC5B,eAAW,CAACE,GAAKtC,CAAK,KAAK,OAAO,QAAQqC,CAAM;AAC9C,MAAAF,EAAK,GAAGN,EAAU,IAAIO,CAAO,IAAIL,GAAQO,CAAG,CAAC,EAAE,IAAI,OAAOtC,CAAK;AAAA,EAEnE;AAEA,SAAOmC;AACT;ACxBO,MAAMI,KAA+B;AAAA,EAC1C,QAAQ;AAAA,IACN,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf,QAAQ;AAAA,IACN,cAAc;AAAA,IACd,MAAM;AAAA,IACN,SAAS;AAAA,IACT,mBAAmB;AAAA,IACnB,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,IACpB,qBAAqB;AAAA,EAAA;AAAA,EAEvB,OAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,iBAAiB;AAAA,EAAA;AAAA,EAEnB,OAAO;AAAA,IACL,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,UAAU;AAAA,EAAA;AAEd;AASO,SAASC,GACdC,GACAC,IAAyBH,IACP;AAClB,SAAO;AAAA,IACL,QAAQ,EAAE,GAAGG,EAAK,QAAQ,GAAGD,EAAU,OAAA;AAAA,IACvC,QAAQ,EAAE,GAAGC,EAAK,QAAQ,GAAGD,EAAU,OAAA;AAAA,IACvC,OAAO,EAAE,GAAGC,EAAK,OAAO,GAAGD,EAAU,MAAA;AAAA,IACrC,OAAO,EAAE,GAAGC,EAAK,OAAO,GAAGD,EAAU,MAAA;AAAA,EAAM;AAE/C;ACjDO,MAAME,KAAaJ,IAEbK,KAAYJ,GAAY;AAAA,EACnC,QAAQ;AAAA,IACN,YAAY;AAAA,EAAA;AAAA,EAEd,QAAQ;AAAA,IACN,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,qBAAqB;AAAA,EAAA;AAAA,EAEvB,OAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,EAAA;AAAA,EAEV,OAAO;AAAA,IACL,YAAY;AAAA,EAAA;AAEhB,CAAC,GAEYK,KAAoBL,GAAY;AAAA,EAC3C,QAAQ;AAAA,IACN,YAAY;AAAA,EAAA;AAAA,EAEd,QAAQ;AAAA,IACN,cAAc;AAAA,IACd,MAAM;AAAA,IACN,mBAAmB;AAAA,IACnB,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,EAAA;AAAA,EAEtB,OAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,iBAAiB;AAAA,EAAA;AAAA,EAEnB,OAAO;AAAA,IACL,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,UAAU;AAAA,EAAA;AAEd,CAAC,GAEYM,KAAeH;AC7BrB,SAASI,GACdnG,GACAhC,GACY;AACZ,MAAIA,MAAU;AACZ,WAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,EAAA;AAG5C,MAAIgC,EAAU,SAAShC,IAAQ;AAC7B,UAAM,IAAI;AAAA,MACR,iDAAiDA,IAAQ,CAAC,iBAAiBA,CAAK,gBAAgBgC,EAAU,MAAM;AAAA,IAAA;AAIpH,MAAIoG,IAAOpG,EAAU,CAAC,GAClBqG,IAAOrG,EAAU,CAAC,GAClBsG,IAAOtG,EAAU,CAAC,GAClBuG,IAAOvG,EAAU,CAAC;AAEtB,WAAS3B,IAAI,GAAGA,IAAIL,GAAOK,KAAK;AAC9B,UAAMiG,IAAItE,EAAU3B,IAAI,CAAC,GACnBkG,IAAIvE,EAAU3B,IAAI,IAAI,CAAC;AAC7B,IAAIiG,IAAI8B,MAAMA,IAAO9B,IACjBA,IAAI+B,MAAMA,IAAO/B,IACjBC,IAAI+B,MAAMA,IAAO/B,IACjBA,IAAIgC,MAAMA,IAAOhC;AAAA,EACvB;AAEA,SAAO,EAAE,MAAA6B,GAAM,MAAAC,GAAM,MAAAC,GAAM,MAAAC,EAAA;AAC7B;AAaO,SAASC,GACdJ,GACAC,GACAC,GACAC,GACqD;AACrD,QAAME,IAASJ,IAAOD,GAChBM,IAASH,IAAOD,GAChBK,IAAQ,KAAK,IAAIF,GAAQC,CAAM,KAAK,GACpCE,IAAU,MAAMH,IAASE,IAAQ,GACjCE,IAAU,MAAMH,IAASC,IAAQ;AAEvC,SAAO,EAAE,OAAAA,GAAO,SAAAC,GAAS,SAAAC,EAAA;AAC3B;AAYO,SAASC,GACdxG,GACAC,GACAwG,GACAC,GACoC;AACpC,QAAMC,IAAiB3G,IAAQC,GAEzB2G,IAAe,KAAK,IAAI5G,GAAOC,CAAM,GAErC4G,IAAY,IAAI,KADED,IAAe,IAAIF,IAAcE,IAAe;AAExE,MAAIE,IAASL,IAAOI,GAChBE,IAASN,IAAOI;AAEpB,SAAIF,IAAiB,IAEnBG,IAAUL,IAAOI,IAAaF,IACrBA,IAAiB,MAE1BI,IAASN,IAAOI,IAAYF,IAIvB,EAAE,QAAAG,GAAQ,QAAAC,EAAA;AACnB;AAaO,SAASC,GACdtH,GACAhC,GACAoI,GACAC,GACAC,GACAC,GACc;AACd,QAAMgB,IAAa,IAAI,aAAavJ,IAAQ,CAAC,GACvC,EAAE,OAAA2I,GAAO,SAAAC,GAAS,SAAAC,EAAA,IAAYL;AAAA,IAClCJ;AAAA,IACAC;AAAA,IACAC;AAAA,IACAC;AAAA,EAAA;AAGF,WAASlI,IAAI,GAAGA,IAAIL,GAAOK,KAAK;AAC9B,UAAMiG,IAAItE,EAAU3B,IAAI,CAAC,GACnBkG,IAAIvE,EAAU3B,IAAI,IAAI,CAAC,GAGvBmJ,KAASlD,IAAI8B,KAAQO,GACrBc,KAASlD,IAAI+B,KAAQK,GAErBe,IAAYF,IAAQZ,GACpBe,IAAYF,IAAQZ;AAG1B,IAAAU,EAAWlJ,IAAI,CAAC,IAAI,KAAK,IAAIqJ,GAC7BH,EAAWlJ,IAAI,IAAI,CAAC,IAAI,KAAK,IAAIsJ;AAAA,EACnC;AAEA,SAAOJ;AACT;AAoBO,SAASK,GACdtD,GACAC,GACA6B,GACAE,GACAK,GACAC,GACAC,GACAO,GACAC,GACAQ,GACAC,GACAC,GACAC,GACsC;AAEtC,QAAMC,KAAe3D,IAAI8B,KAAQO,GAC3BuB,KAAe3D,IAAI+B,KAAQK,GAE3Be,IAAYO,IAAcrB,GAC1Be,IAAYO,IAAcrB,GAE1BsB,IAAS,KAAK,IAAIT,GAClBU,IAAS,KAAK,IAAIT,GAGlBU,IAAeF,IAASf,IAASS,GACjCS,IAAeF,IAASf,IAASS,GAGjCS,KAAYF,IAAe,KAAKN,IAAe,GAC/CS,KAAY,IAAIF,KAAgBN,IAAgB;AAEtD,SAAO,EAAE,SAAAO,GAAS,SAAAC,EAAA;AACpB;AASO,SAASC,GACdnE,GACAC,GACAmE,GACS;AACT,MAAIA,EAAQ,SAAS,EAAG,QAAO;AAE/B,MAAIC,IAAS;AACb,WAAS,IAAI,GAAGC,IAAIF,EAAQ,SAAS,GAAG,IAAIA,EAAQ,QAAQE,IAAI,KAAK;AACnE,UAAMC,IAAKH,EAAQ,CAAC,EAAE,CAAC,GACjBI,IAAKJ,EAAQ,CAAC,EAAE,CAAC,GACjBK,IAAKL,EAAQE,CAAC,EAAE,CAAC,GACjBI,IAAKN,EAAQE,CAAC,EAAE,CAAC;AAKvB,IAFEE,IAAKvE,KAAMyE,IAAKzE,KAAKD,KAAMyE,IAAKF,MAAOtE,IAAIuE,MAAQE,IAAKF,KAAMD,UAExC,CAACF;AAAA,EAC3B;AAEA,SAAOA;AACT;AC1OO,MAAMM,KAAkC;AAkB/C,SAASC,GACPnB,GACAC,GACAjB,GACAoC,GACAnC,GACA;AACA,QAAM,EAAE,OAAAL,GAAO,SAAAC,GAAS,SAAAC,EAAA,IAAYL;AAAA,IAClC2C,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,IACPA,EAAO;AAAA,EAAA,GAEH,EAAE,QAAA/B,GAAQ,QAAAC,EAAA,IAAWP;AAAA,IACzBiB;AAAA,IACAC;AAAA,IACAjB;AAAA,IACAC;AAAA,EAAA;AAEF,SAAO,EAAE,OAAAL,GAAO,SAAAC,GAAS,SAAAC,GAAS,QAAAO,GAAQ,QAAAC,EAAA;AAC5C;AAgBO,SAAS+B,GACdC,GACAC,GACAtJ,GACAhC,GACA+J,GACAC,GACAuB,GACAC,IAAsBP,IACtBE,GACAnC,IAAsBd,GAAa,OAAO,aAC3B;AACf,MAAIlI,MAAU,EAAG,QAAO;AAExB,QAAM,EAAE,MAAA+I,GAAM,KAAA0C,EAAA,IAAQF,GAEhB,EAAE,OAAA5C,GAAO,SAAAC,GAAS,SAAAC,GAAS,QAAAO,GAAQ,QAAAC,MAAW6B;AAAA,IAClDnB;AAAA,IACAC;AAAA,IACAjB;AAAA,IACAoC;AAAA,IACAnC;AAAA,EAAA;AAGF,MAAI0C,IAA8B,MAC9BC,IAAcH;AAElB,WAASnL,IAAI,GAAGA,IAAIL,GAAOK,KAAK;AAC9B,UAAMiG,IAAItE,EAAU3B,IAAI,CAAC,GACnBkG,IAAIvE,EAAU3B,IAAI,IAAI,CAAC,GACvB,EAAE,SAAAkK,GAAS,SAAAC,EAAA,IAAYZ;AAAA,MAC3BtD;AAAA,MACAC;AAAA,MACA4E,EAAO;AAAA,MACPA,EAAO;AAAA,MACPxC;AAAA,MACAC;AAAA,MACAC;AAAA,MACAO;AAAA,MACAC;AAAA,MACAoC,EAAI;AAAA,MACJA,EAAI;AAAA,MACJ1B;AAAA,MACAC;AAAA,IAAA,GAGIpD,IAAK2D,IAAUc,GACfxE,IAAK2D,IAAUc,GACfM,IAAW,KAAK,KAAKhF,IAAKA,IAAKC,IAAKA,CAAE;AAE5C,IAAI+E,IAAWD,MACbA,IAAcC,GACdF,IAAerL;AAAA,EAEnB;AAEA,SAAOqL;AACT;AAcO,SAASG,GACdnB,GACA1I,GACAhC,GACA+J,GACAC,GACAuB,GACAJ,GACAnC,IAAsBd,GAAa,OAAO,aAC7B;AACb,MAAIwC,EAAQ,SAAS,EAAG,4BAAW,IAAA;AAEnC,QAAM,EAAE,MAAA3B,GAAM,KAAA0C,EAAA,IAAQF,GAEhB,EAAE,OAAA5C,GAAO,SAAAC,GAAS,SAAAC,GAAS,QAAAO,GAAQ,QAAAC,MAAW6B;AAAA,IAClDnB;AAAA,IACAC;AAAA,IACAjB;AAAA,IACAoC;AAAA,IACAnC;AAAA,EAAA,GAGInK,wBAAsB,IAAA;AAE5B,WAASwB,IAAI,GAAGA,IAAIL,GAAOK,KAAK;AAC9B,UAAMiG,IAAItE,EAAU3B,IAAI,CAAC,GACnBkG,IAAIvE,EAAU3B,IAAI,IAAI,CAAC,GACvB,EAAE,SAAAkK,GAAS,SAAAC,EAAA,IAAYZ;AAAA,MAC3BtD;AAAA,MACAC;AAAA,MACA4E,EAAO;AAAA,MACPA,EAAO;AAAA,MACPxC;AAAA,MACAC;AAAA,MACAC;AAAA,MACAO;AAAA,MACAC;AAAA,MACAoC,EAAI;AAAA,MACJA,EAAI;AAAA,MACJ1B;AAAA,MACAC;AAAA,IAAA;AAGF,IAAIS,GAAiBF,GAASC,GAASE,CAAO,KAC5C7L,EAAgB,IAAIwB,CAAC;AAAA,EAEzB;AAEA,SAAOxB;AACT;ACxKO,SAASiN,GACd1C,GACAC,GACc;AACd,SAAO,IAAI,aAAa,CAACD,GAAQ,GAAG,GAAG,GAAGC,GAAQ,GAAG,GAAG,GAAG,CAAC,CAAC;AAC/D;AAQO,SAAS0C,GAAwBC,GAAYC,GAA0B;AAC5E,SAAO,IAAI,aAAa,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAGD,GAAIC,GAAI,CAAC,CAAC;AACvD;AASO,SAASC,GACd/J,GACAT,GACc;AACd,SAAO,IAAI,aAAa;AAAA,IACtBS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC;AAAA,IACtCS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC;AAAA,IACtCS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC;AAAA,IAEtCS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC;AAAA,IACtCS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC;AAAA,IACtCS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC;AAAA,IAEtCS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC;AAAA,IACtCS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC;AAAA,IACtCS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC,IAAIS,EAAE,CAAC,IAAIT,EAAE,CAAC;AAAA,EAAA,CACvC;AACH;AC1BO,SAASyK,GAAW,EAAE,OAAAnM,GAAO,QAAAuL,GAAQ,cAAAa,KAAiC;AAC3E,QAAM,EAAE,MAAArD,GAAM,KAAA0C,EAAA,IAAQF;AACtB,SACE,gBAAAc,EAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,IAAA,gBAAAC,EAAC,OAAA,EAAI,WAAU,iCAAgC,UAAA,cAAU;AAAA,sBACxD,OAAA,EAAI,UAAA;AAAA,MAAA;AAAA,MAAStM,EAAM,eAAA;AAAA,IAAe,GAAE;AAAA,sBACpC,OAAA,EAAI,UAAA;AAAA,MAAA;AAAA,MAAO+I,EAAK,QAAQ,CAAC;AAAA,MAAE;AAAA,IAAA,GAAC;AAAA,sBAC5B,OAAA,EAAI,UAAA;AAAA,MAAA;AAAA,MACI0C,EAAI,EAAE,QAAQ,CAAC;AAAA,MAAE;AAAA,MAAGA,EAAI,EAAE,QAAQ,CAAC;AAAA,MAAE;AAAA,IAAA,GAC9C;AAAA,IACCW,KACC,gBAAAC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,OAAO;AAAA,UACL,WAAW;AAAA,UACX,WAAW;AAAA,UACX,YAAY;AAAA,QAAA;AAAA,QAGd,UAAA;AAAA,UAAA,gBAAAA,EAAC,OAAA,EAAI,UAAA;AAAA,YAAA;AAAA,YAAaD,EAAa,OAAO,QAAQ,CAAC;AAAA,YAAE;AAAA,UAAA,GAAE;AAAA,4BAClD,OAAA,EAAI,UAAA;AAAA,YAAA;AAAA,YAAWA,EAAa,cAAc,eAAA;AAAA,UAAe,EAAA,CAAE;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAC9D,GAEJ;AAEJ;ACrBO,SAASG,GAAa;AAAA,EAC3B,WAAA3G;AAAA,EACA,OAAAtD;AAAA,EACA,QAAAC;AAAA,EACA,WAAAuD;AACF,GAAsB;AACpB,MAAI,CAACA,KAAaF,EAAU,SAAS;AACnC,WAAO;AAIT,QAAM4G,IAAS5G,EAAU,IAAI,CAAC,CAACU,GAAGC,CAAC,MAAM,GAAGD,CAAC,IAAIC,CAAC,EAAE,EAAE,KAAK,GAAG;AAE9D,SACE,gBAAA+F;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,eAAY;AAAA,MACZ,OAAO;AAAA,QACL,UAAU;AAAA,QACV,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAAhK;AAAA,QACA,QAAAC;AAAA,QACA,eAAe;AAAA;AAAA,QACf,QAAQ;AAAA,MAAA;AAAA,MAGV,UAAA,gBAAA+J,EAAC,WAAA,EAAQ,WAAU,6BAA4B,QAAAE,EAAA,CAAgB;AAAA,IAAA;AAAA,EAAA;AAGrE;ACnBO,MAAMC,KAAyB,KAEzBC,KAA0B;AAuEvC,SAASC,GAAc;AAAA,EACrB,OAAArK;AAAA,EACA,QAAAC;AAAA,EACA,WAAAP;AAAA,EACA,QAAAC;AAAA,EACA,OAAA2K,IAAQ;AAAA,EACR,YAAAC,IAAa,OAAO,oBAAoB;AAAA,EACxC,OAAA1M;AAAA,EACA,cAAA2M;AAAA,EACA,cAAAC,IAAe;AAAA,EACf,iBAAAtH;AAAA,EACA,eAAAC;AAAA,EACA,QAAQsH;AAAA,EACR,gBAAAxK;AAAA,EACA,gBAAAyK,IAAiB;AAAA,EACjB,OAAA3F,IAAQY;AAAA,EACR,WAAAgF;AACF,GAAuB;AACrB,QAAMzI,IAAe9B,EAAuB,IAAI,GAC1CwK,IAAgB3I,GAAiBC,CAAY,GAG7C2I,IACJ9K,MACC6K,EAAc,QAAQ,IAAIA,EAAc,QAAQV,KAC7CY,IACJ9K,MACC4K,EAAc,SAAS,IAAIA,EAAc,SAAST,KAE/CY,IAAY3K,EAA0B,IAAI,GAK1C,CAACN,GAAQkL,CAAS,IAAIxO,EAAmC,IAAI;AACnE,EAAAyO,GAAgB,MAAM;AACpB,IAAAD,EAAUD,EAAU,OAAO;AAAA,EAC7B,GAAG,CAAA,CAAE;AAGL,QAAM,CAAClB,GAAcqB,CAAe,IAAI1O,EAEtC,MAAS,GAGLiB,IAAQgC,EAAU,SAAS,GAG3B0L,IAAQ/K,EAAsC,IAAI,GAClDgL,IAAahL,EAA4B,IAAI,GAC7CiL,IAAsBjL,EAAe,EAAE,GACvCkL,IAAmBlL,EAAe,EAAE,GACpCmL,IAAkBnL,EAAe,EAAE,GACnCoL,IAAoBpL,EAAoC,IAAI,GAC5DqL,IAAuBrL,EAAoC,IAAI,GAC/DsL,KAAqBtL,EAAoC,IAAI,GAC7DuL,KAA+BvL;AAAA,IACnC;AAAA,EAAA,GAEIwL,KAAiCxL;AAAA,IACrC;AAAA,EAAA,GAEIyL,KAAgCzL;AAAA,IACpC;AAAA,EAAA,GAEI0L,KAAiC1L;AAAA,IACrC;AAAA,EAAA,GAEI2L,IAAoB3L,EAA2B,IAAI,GACnD4L,IAAiB5L,EAA2B,IAAI,GAChD6L,IAAgB7L,EAA2B,IAAI,GAG/C8L,IAAaC,EAAQ,MAClBvG,GAAgBnG,GAAWhC,CAAK,GACtC,CAACgC,GAAWhC,CAAK,CAAC,GAGf2O,KAAUD,EAAQ,MAAMrH,GAAqBC,CAAK,GAAG,CAACA,CAAK,CAAC,GAG5DsH,IAAUF,EAAQ,MAAM;AAC5B,UAAM,CAAClN,GAAGC,GAAG,CAAC,IAAIP,GAASoG,EAAM,OAAO,UAAU;AAClD,WAAO,CAAC9F,IAAI,KAAKC,IAAI,KAAK,IAAI,KAAK,CAAG;AAAA,EACxC,GAAG,CAAC6F,EAAM,OAAO,UAAU,CAAC,GAGtB,CAACuH,IAAgBC,EAAiB,IAAI/P,EAAiBQ,EAAc,GAGrEwP,IAAqB/B,MAAqB,QAG1CzB,IAASwD,IAAqB/B,IAAmB6B,IAIjDG,KAAqB/P;AAAA,IACzB,CAACgQ,MAAmD;AAClD,MAAIzM,KACFA,EAAeyM,CAAS,GAErBF,KACHD,GAAkBG,CAAS;AAAA,IAE/B;AAAA,IACA,CAACzM,GAAgBuM,CAAkB;AAAA,EAAA,GAI/B,EAAE,wBAAArM,GAAA,IAA2BN,GAAU;AAAA,IAC3C,QAAAC;AAAA,IACA,OAAO+K;AAAA,IACP,QAAQC;AAAA,IACR,gBAAgB2B;AAAA,IAChB,SAAS/B;AAAA,EAAA,CACV,GAGK,EAAE,WAAArH,IAAW,WAAAE,GAAA,IAAcP,GAAS;AAAA,IACxC,QAAAlD;AAAA,IACA,SAAS0K;AAAA,IACT,iBAAiB,CAACrC,MAAY;AAC5B,UAAIjF,GAAiB;AACnB,cAAMyJ,IAAYtC,IAAQ,YAAY,IAAA,IAAQ,GACxCxN,IAAUyM;AAAA,UACdnB;AAAA,UACA1I;AAAA,UACAhC;AAAA,UACAoN;AAAA,UACAC;AAAA,UACA9B;AAAA,UACAkD;AAAA,UACAnH,EAAM,OAAO;AAAA,QAAA;AAEf,QAAIsF,KACFa,EAAgB;AAAA,UACd,QAAQ,YAAY,IAAA,IAAQyB;AAAA,UAC5B,eAAe9P,EAAQ;AAAA,QAAA,CACxB,GAEHqG,EAAgBrG,CAAO;AAAA,MACzB;AAAA,IACF;AAAA,IACA,eAAe,CAACsL,MAAY;AAC1B,UAAIhF,GAAe;AACjB,cAAMtG,IAAUyM;AAAA,UACdnB;AAAA,UACA1I;AAAA,UACAhC;AAAA,UACAoN;AAAA,UACAC;AAAA,UACA9B;AAAA,UACAkD;AAAA,UACAnH,EAAM,OAAO;AAAA,QAAA;AAEf,QAAA5B,EAActG,CAAO;AAAA,MACvB;AAAA,IACF;AAAA,EAAA,CACD;AAID,SAAAwD,EAAU,MAAM;AACd,UAAMP,IAASiL,EAAU;AACzB,QAAI,CAACjL,KAAU,CAACyK,KAAgBC,EAAc;AAE9C,UAAMoC,IAAc,CAACC,MAAsB;AAEzC,UAAI1M,GAAuB,SAAS;AAClC,QAAAA,GAAuB,UAAU;AACjC;AAAA,MACF;AAEA,YAAMK,IAAOV,EAAO,sBAAA,GACdgJ,IAAS+D,EAAM,UAAUrM,EAAK,MAC9BuI,IAAS8D,EAAM,UAAUrM,EAAK,KAE9B2I,IAAeN;AAAA,QACnBC;AAAA,QACAC;AAAA,QACAtJ;AAAA,QACAhC;AAAA,QACAoN;AAAA,QACAC;AAAA,QACA9B;AAAA,QACAjE,EAAM,OAAO;AAAA;AAAA,QACbmH;AAAA;AAAA,QACAnH,EAAM,OAAO;AAAA,MAAA;AAGf,MAAAwF,EAAapB,CAAY;AAAA,IAC3B;AAEArJ,WAAAA,EAAO,iBAAiB,SAAS8M,CAAW,GACrC,MAAM9M,EAAO,oBAAoB,SAAS8M,CAAW;AAAA,EAC9D,GAAG;AAAA,IACDnN;AAAA,IACAhC;AAAA,IACAoN;AAAA,IACAC;AAAA,IACA9B;AAAA,IACAjE,EAAM,OAAO;AAAA,IACbwF;AAAA,IACA2B;AAAA,IACAnH,EAAM,OAAO;AAAA,IACbyF;AAAA,IACArK;AAAA,EAAA,CACD,GAQDE,EAAU,MAAM;AACd,UAAMP,IAASiL,EAAU;AACzB,QAAI,CAACjL,EAAQ;AAKb,UAAM3B,IAAK2B,EAAO,WAAW,UAAU;AAAA,MACrC,uBAAuB;AAAA,MACvB,WAAW;AAAA,IAAA,CACZ;AACD,QAAI,CAAC3B,GAAI;AACP,cAAQ,MAAM,uBAAuB;AACrC;AAAA,IACF;AAGA,IAAAgN,EAAM,UAAUhN;AAGhB,UAAMK,IAAeN;AAAA,MACnBC;AAAA,MACAA,EAAG;AAAA,MACHsG;AAAA,IAAA,GAEIhG,IAAiBP;AAAA,MACrBC;AAAA,MACAA,EAAG;AAAA,MACHqG;AAAA,IAAA;AAEF,QAAI,CAAChG,KAAgB,CAACC,EAAgB;AAGtC,UAAMC,IAAUH,GAAcJ,GAAIK,GAAcC,CAAc;AAC9D,QAAI,CAACC,EAAS;AAiCd,QA9BA0M,EAAW,UAAU1M,GAGrB2M,EAAoB,UAAUlN,EAAG,kBAAkBO,GAAS,YAAY,GACxE4M,EAAiB,UAAUnN,EAAG,kBAAkBO,GAAS,SAAS,GAClE6M,EAAgB,UAAUpN,EAAG,kBAAkBO,GAAS,QAAQ,GAChE8M,EAAkB,UAAUrN,EAAG,mBAAmBO,GAAS,UAAU,GACrE+M,EAAqB,UAAUtN,EAAG;AAAA,MAChCO;AAAA,MACA;AAAA,IAAA,GAEFgN,GAAmB,UAAUvN,EAAG,mBAAmBO,GAAS,WAAW,GACvEiN,GAA6B,UAAUxN,EAAG;AAAA,MACxCO;AAAA,MACA;AAAA,IAAA,GAEFkN,GAA+B,UAAUzN,EAAG;AAAA,MAC1CO;AAAA,MACA;AAAA,IAAA,GAEFmN,GAA8B,UAAU1N,EAAG;AAAA,MACzCO;AAAA,MACA;AAAA,IAAA,GAEFoN,GAA+B,UAAU3N,EAAG;AAAA,MAC1CO;AAAA,MACA;AAAA,IAAA,GAIE2M,EAAoB,YAAY,IAAI;AACtC,cAAQ,MAAM,iDAAiD;AAC/D;AAAA,IACF;AACA,QAAIC,EAAiB,YAAY,IAAI;AACnC,cAAQ,MAAM,8CAA8C;AAC5D;AAAA,IACF;AACA,QAAIC,EAAgB,YAAY,IAAI;AAClC,cAAQ,MAAM,6CAA6C;AAC3D;AAAA,IACF;AACA,QAAI,CAACC,EAAkB,SAAS;AAC9B,cAAQ,MAAM,6CAA6C;AAC3D;AAAA,IACF;AACA,QAAI,CAACC,EAAqB,SAAS;AACjC,cAAQ,MAAM,gDAAgD;AAC9D;AAAA,IACF;AAGA,UAAMqB,IAAiB3O,EAAG,aAAA,GACpB4O,IAAc5O,EAAG,aAAA,GACjB6O,KAAa7O,EAAG,aAAA;AACtB,WAAA4N,EAAkB,UAAUe,GAC5Bd,EAAe,UAAUe,GACzBd,EAAc,UAAUe,IAIxB7O,EAAG,OAAOA,EAAG,KAAK,GAClBA,EAAG,UAAUA,EAAG,KAAKA,EAAG,mBAAmB,GAG3CA,EAAG,OAAOA,EAAG,UAAU,GACvBA,EAAG,UAAUA,EAAG,IAAI,GAKb,MAAM;AACX,MAAIK,KAAcL,EAAG,aAAaK,CAAY,GAC1CC,KAAgBN,EAAG,aAAaM,CAAc,GAC9C2M,EAAW,YACbjN,EAAG,cAAciN,EAAW,OAAO,GACnCA,EAAW,UAAU,OAEnBW,EAAkB,YACpB5N,EAAG,aAAa4N,EAAkB,OAAO,GACzCA,EAAkB,UAAU,OAE1BC,EAAe,YACjB7N,EAAG,aAAa6N,EAAe,OAAO,GACtCA,EAAe,UAAU,OAEvBC,EAAc,YAChB9N,EAAG,aAAa8N,EAAc,OAAO,GACrCA,EAAc,UAAU;AAAA,IAE5B;AAAA,EACF,GAAG,CAAA,CAAE,GAML5L,EAAU,MAAM;AACd,UAAMlC,IAAKgN,EAAM,SACX2B,IAAiBf,EAAkB;AACzC,QAAI,CAAC5N,KAAM,CAAC2O,KAAkBrP,MAAU,EAAG;AAE3C,UAAM,EAAE,MAAAoI,GAAM,MAAAC,GAAM,MAAAC,GAAM,MAAAC,MAASkG,GAG7Be,IAAsBlG;AAAA,MAC1BtH;AAAA,MACAhC;AAAA,MACAoI;AAAA,MACAC;AAAA,MACAC;AAAA,MACAC;AAAA,IAAA;AAEF,IAAA7H,EAAG,WAAWA,EAAG,cAAc2O,CAAc,GAC7C3O,EAAG,WAAWA,EAAG,cAAc8O,GAAqB9O,EAAG,WAAW,GAClEA,EAAG,wBAAwBkN,EAAoB,OAAO,GACtDlN,EAAG;AAAA,MACDkN,EAAoB;AAAA,MACpB;AAAA,MACAlN,EAAG;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ,GAAG,CAACsB,GAAWhC,GAAOyO,CAAU,CAAC,GAMjC7L,EAAU,MAAM;AACd,UAAMlC,IAAKgN,EAAM,SACX4B,IAAcf,EAAe;AACnC,IAAI,CAAC7N,KAAM,CAAC4O,KAAetP,MAAU,MAGrCU,EAAG,WAAWA,EAAG,cAAc4O,CAAW,GAC1C5O,EAAG,WAAWA,EAAG,cAAcuB,GAAQvB,EAAG,WAAW,GACrDA,EAAG,wBAAwBmN,EAAiB,OAAO,GACnDnN,EAAG;AAAA,MACDmN,EAAiB;AAAA,MACjB;AAAA,MACAnN,EAAG;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ,GAAG,CAACuB,GAAQjC,CAAK,CAAC,GAUlB4C,EAAU,MAAM;AACd,UAAMlC,IAAKgN,EAAM,SACXzM,IAAU0M,EAAW,SACrB0B,IAAiBf,EAAkB,SACnCgB,IAAcf,EAAe,SAC7BgB,IAAaf,EAAc,SAG3B,EAAE,MAAAzF,GAAM,KAAA0C,EAAA,IAAQF;AAGtB,QAAI,CAAC7K,KAAM,CAACO,EAAS;AAGrB,QAAIjB,MAAU,GAAG;AACf,MAAAU,EAAG,WAAWkO,EAAQ,CAAC,GAAGA,EAAQ,CAAC,GAAGA,EAAQ,CAAC,GAAGA,EAAQ,CAAC,CAAC,GAC5DlO,EAAG,MAAMA,EAAG,gBAAgB;AAC5B;AAAA,IACF;AAMA,QAHAA,EAAG,WAAWO,CAAO,GAGjBsO,GAAY;AACd,YAAME,KAAWtP,KAASJ,GAAiBC,CAAK;AAChD,MAAAU,EAAG,WAAWA,EAAG,cAAc6O,CAAU,GACzC7O,EAAG,WAAWA,EAAG,cAAc+O,IAAU/O,EAAG,YAAY,GACxDA,EAAG,wBAAwBoN,EAAgB,OAAO,GAClDpN,EAAG;AAAA,QACDoN,EAAgB;AAAA,QAChB;AAAA,QACApN,EAAG;AAAA,QACH;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAIA,UAAM,EAAE,QAAA0I,IAAQ,QAAAC,GAAA,IAAWP;AAAA,MACzBsE;AAAA,MACAC;AAAA,MACAtE;AAAA,MACAzB,EAAM,OAAO;AAAA,IAAA,GAEToI,KAAc5D,GAAkB1C,IAAQC,EAAM,GAC9CsG,KAAoB5D,GAAwBN,EAAI,GAAGA,EAAI,CAAC,GACxDmE,KAAc1D,GAAiByD,IAAmBD,EAAW;AACnE,IAAAhP,EAAG,iBAAiBqN,EAAkB,SAAS,IAAO6B,EAAW,GAGjElP,EAAG,UAAUsN,EAAqB,SAAS1G,EAAM,OAAO,OAAOuF,CAAU,GAGzEnM,EAAG,UAAUuN,GAAmB,SAAS3G,EAAM,OAAO,OAAO,GAC7D5G,EAAG;AAAA,MACDwN,GAA6B;AAAA,MAC7B5G,EAAM,OAAO;AAAA,IAAA,GAEf5G,EAAG;AAAA,MACDyN,GAA+B;AAAA,MAC/B7G,EAAM,OAAO;AAAA,IAAA,GAEf5G,EAAG;AAAA,MACD0N,GAA8B;AAAA,MAC9B9G,EAAM,OAAO;AAAA,IAAA,GAEf5G,EAAG;AAAA,MACD2N,GAA+B;AAAA,MAC/B/G,EAAM,OAAO;AAAA,IAAA,GAIX+H,MACF3O,EAAG,WAAWA,EAAG,cAAc2O,CAAc,GAC7C3O,EAAG,wBAAwBkN,EAAoB,OAAO,GACtDlN,EAAG;AAAA,MACDkN,EAAoB;AAAA,MACpB;AAAA,MACAlN,EAAG;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,IAAA,IAIA4O,MACF5O,EAAG,WAAWA,EAAG,cAAc4O,CAAW,GAC1C5O,EAAG,wBAAwBmN,EAAiB,OAAO,GACnDnN,EAAG;AAAA,MACDmN,EAAiB;AAAA,MACjB;AAAA,MACAnN,EAAG;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,IAAA,IAIA6O,MACF7O,EAAG,WAAWA,EAAG,cAAc6O,CAAU,GACzC7O,EAAG,wBAAwBoN,EAAgB,OAAO,GAClDpN,EAAG;AAAA,MACDoN,EAAgB;AAAA,MAChB;AAAA,MACApN,EAAG;AAAA,MACH;AAAA,MACA;AAAA,IAAA,IAKJA,EAAG,SAAS,GAAG,GAAG0M,IAAaP,GAAYQ,IAAcR,CAAU,GACnEnM,EAAG,WAAWkO,EAAQ,CAAC,GAAGA,EAAQ,CAAC,GAAGA,EAAQ,CAAC,GAAGA,EAAQ,CAAC,CAAC,GAC5DlO,EAAG,MAAMA,EAAG,mBAAmBA,EAAG,gBAAgB,GAClDA,EAAG,WAAWA,EAAG,QAAQ,GAAGV,CAAK;AAAA,EACnC,GAAG;AAAA,IACDgC;AAAA,IACAC;AAAA,IACA9B;AAAA,IACAoL;AAAA,IACAjE;AAAA,IACAuF;AAAA,IACA7M;AAAA,IACAoN;AAAA,IACAC;AAAA,IACAuB;AAAA,EAAA,CACD,GAIC,gBAAAvC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAK5H;AAAA,MACL,WAAAyI;AAAA,MACA,OAAO;AAAA,QACL,UAAU;AAAA,QACV,YAAY;AAAA,QACZ,OAAO5K,IAAQ,GAAGA,CAAK,OAAO;AAAA,QAC9B,QAAQC,IAAS,GAAGA,CAAM,OAAO;AAAA,QACjC,GAAGoM;AAAA,MAAA;AAAA,MAGL,UAAA;AAAA,QAAA,gBAAArC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,KAAKgB;AAAA,YACL,OAAOF,IAAaP;AAAA,YACpB,QAAQQ,IAAcR;AAAA,YACtB,OAAO;AAAA,cACL,OAAO,GAAGO,CAAU;AAAA,cACpB,QAAQ,GAAGC,CAAW;AAAA,cACtB,GAAIT,IAAQ,EAAE,QAAQ,sBAAsB,CAAA;AAAA,YAAC;AAAA,UAC/C;AAAA,QAAA;AAAA,QAEDG,KACC,gBAAAT;AAAA,UAACC;AAAA,UAAA;AAAA,YACC,WAAA3G;AAAA,YACA,OAAOwH;AAAA,YACP,QAAQC;AAAA,YACR,WAAAvH;AAAA,UAAA;AAAA,QAAA;AAAA,QAGH8G,KACC,gBAAAN,EAACH,IAAA,EAAW,OAAAnM,GAAc,QAAAuL,GAAgB,cAAAa,EAAA,CAA4B;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAI9E;ACvoBO,MAAMyD,KAA0C;AAkDhD,SAASC,GAAY;AAAA,EAC1B,QAAQhO;AAAA,EACR,OAAAQ;AAAA,EACA,QAAAC;AAAA,EACA,eAAAwN,IAAgBxQ;AAAA,EAChB,OAAA+H;AAAA,EACA,YAAAuF;AAAA,EACA,aAAAmD,IAAc;AAAA,EACd,eAAAC,IAAgB;AAAA,EAChB,OAAArD,IAAQ;AAAA,EACR,mBAAAsD;AAAA,EACA,wBAAAC,IAAyBN;AAAA,EACzB,YAAAO;AACF,GAAqB;AAEnB,QAAM,CAACvB,GAAgBC,CAAiB,IAAI/P,EAAiBgR,CAAa,GAGpEhB,IAAqBqB,GAAY,WAAW,QAG5C7E,IACJwD,KAAsBqB,GAAY,SAC9BA,EAAW,SACXvB,GAGA,EAAE,iBAAAhQ,GAAiB,kBAAAG,GAAkB,cAAAG,EAAA,IAAiBP,GAAA,GACtD,CAACqB,GAAoBoQ,CAAqB,IAAItR;AAAA,wBAC9C,IAAA;AAAA,EAAI,GAIJ,EAAE,WAAAiD,GAAW,QAAAC,EAAA,IAAWyM,EAAQ,MAAM7M,GAAgBC,CAAI,GAAG,CAACA,CAAI,CAAC,GAGnEkN,IAAqB/P;AAAA,IACzB,CAACgQ,MAAmD;AAClD,MAAImB,GAAY,kBACdA,EAAW,eAAenB,CAAS,GAEhCF,KACHD,EAAkBG,CAAS;AAAA,IAE/B;AAAA,IACA,CAACmB,GAAY,gBAAgBrB,CAAkB;AAAA,EAAA,GAI3C5O,IAAQuO;AAAA,IACZ,MACE3O;AAAA,MACE+B,EAAK;AAAA,MACLjD,EAAgB,OAAO,IAAIA,IAAkB;AAAA,MAC7CoB,EAAmB,OAAO,IAAIA,IAAqB;AAAA,IAAA;AAAA,IAEvD,CAAC6B,EAAK,QAAQjD,GAAiBoB,CAAkB;AAAA,EAAA;AAyCnD,SACE,gBAAAqM;AAAA,IAACK;AAAA,IAAA;AAAA,MACC,OAAArK;AAAA,MACA,QAAAC;AAAA,MACA,WAAAP;AAAA,MACA,QAAAC;AAAA,MACA,OAAA9B;AAAA,MACA,QAAAoL;AAAA,MACA,gBAAgByD;AAAA,MAChB,cAlBgB,CAAC9P,MAAyB;AAC5C,QAAAF,EAAiBE,CAAK,GAGlBgR,KACFA,EAAkBhR,MAAU,OAAO,oBAAI,IAAI,CAACA,CAAK,CAAC,IAAI,oBAAI,KAAK;AAAA,MAEnE;AAAA,MAYI,gBAAgB+Q;AAAA,MAChB,cAAcD;AAAA,MACd,iBAjDwB,CAAC5Q,MAAyB;AACpD,QAAAiR,EAAsB,oBAAI,KAAK,GAC/BlR,EAAaC,CAAO,GAGhB8Q,KACFA,EAAkB9Q,CAAO;AAAA,MAE7B;AAAA,MA0CI,eAvCsB,CAACA,MAAyB;AAElD,QAAI0C,EAAK,UAAUqO,KAKnBE,EAAsB,CAACC,MAEnBA,EAAK,SAASlR,EAAQ,QACtB,CAAC,GAAGkR,CAAI,EAAE,MAAM,CAAClL,MAAUhG,EAAQ,IAAIgG,CAAK,CAAC,IAEzBkL,IAAOlR,CAC9B;AAAA,MACH;AAAA,MA0BI,OAAAkI;AAAA,MACA,YAAAuF;AAAA,MACA,OAAAD;AAAA,IAAA;AAAA,EAAA;AAGN;"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
(function(v,T){typeof exports=="object"&&typeof module<"u"?T(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],T):(v=typeof globalThis<"u"?globalThis:v||self,T(v.CxgScatterplot={},v.jsxRuntime,v.React))})(this,(function(v,T,u){"use strict";function re(){const[e,n]=u.useState(new Set),o=u.useCallback(i=>{n(i===null?new Set:new Set([i]))},[]),t=u.useCallback(i=>{n(new Set(i))},[]),s=u.useCallback(()=>{n(new Set)},[]),c=u.useCallback(i=>e.has(i),[e]);return{selectedIndices:e,handlePointClick:o,setSelection:t,clearSelection:s,isSelected:c}}const q={zoom:1,pan:{x:0,y:0}},Te=1,Be=2,Fe=4;function Ce(e,n,o){let t=0;return e&&(t|=Te),n&&(t|=Be),o&&(t|=Fe),t}function K(e,n,o,t){const s=new Uint8Array(e),c=n!==void 0&&n.size>0;for(let i=0;i<e;i++){const a=c?n.has(i):!0,f=o?.has(i)??!1,g=t!==void 0?t.has(i):c&&!a&&!f;s[i]=Ce(a,g,f)}return s}const se="#3498db",Pe=[52,152,219],ie=255;function ce(e,n,o){const t=e.createShader(n);return t?(e.shaderSource(t,o),e.compileShader(t),e.getShaderParameter(t,e.COMPILE_STATUS)?t:(console.error("Shader compile error:",e.getShaderInfoLog(t)),e.deleteShader(t),null)):(console.error("Failed to create WebGL shader"),null)}function we(e,n,o){const t=e.createProgram();return t?(e.attachShader(t,n),e.attachShader(t,o),e.linkProgram(t),e.getProgramParameter(t,e.LINK_STATUS)?t:(console.error("Program link error:",e.getProgramInfoLog(t)),e.deleteProgram(t),null)):(console.error("Failed to create WebGL program"),null)}function ae(e){if(!e||!/^#?[0-9A-Fa-f]{3,6}$/.test(e))return console.warn(`Invalid hex color: "${e}", using fallback (${se})`),Pe;const n=e.replace("#",""),o=n.length===3?n.split("").map(a=>a+a).join(""):n,t=parseInt(o,16),s=t>>16&255,c=t>>8&255,i=t&255;return[s,c,i]}function le(e,n=ie){const[o,t,s]=ae(e);return[o,t,s,n]}function ue(e,n=se){const o=new Float32Array(e.length*2),t=new Uint8Array(e.length*4);for(let s=0;s<e.length;s++){const c=e[s];o[s*2]=c.x,o[s*2+1]=c.y;const[i,a,f,g]=le(c.color||n,ie);t[s*4]=i,t[s*4+1]=a,t[s*4+2]=f,t[s*4+3]=g}return{positions:o,colors:t}}function ze({canvas:e,width:n,height:o,onCameraChange:t,enabled:s=!0}){const c=u.useRef(!1);return u.useEffect(()=>{if(!e||!s)return;const i=a=>{a.preventDefault();const f=e.getBoundingClientRect(),g=a.clientX-f.left,_=a.clientY-f.top,S=g/n*2-1,E=-(_/o*2-1),p=a.deltaY<0?1.1:1/1.1;t(d=>{const L=d.zoom,A=d.pan,R=L*p,m=(S-A.x)/L,h=(E-A.y)/L,B=S-m*R,F=E-h*R;return{zoom:R,pan:{x:B,y:F}}})};return e.addEventListener("wheel",i,{passive:!1}),()=>e.removeEventListener("wheel",i)},[e,n,o,t,s]),u.useEffect(()=>{if(!e||!s)return;let i=!1,a=!1,f={x:0,y:0};const g=p=>{i=!0,a=!1,c.current=!1,f={x:p.clientX,y:p.clientY}},_=p=>{if(!i)return;const d=p.clientX-f.x,L=p.clientY-f.y;(Math.abs(d)>2||Math.abs(L)>2)&&(a=!0);const A=d/n*2,R=-(L/o)*2;t(m=>({zoom:m.zoom,pan:{x:m.pan.x+A,y:m.pan.y+R}})),f={x:p.clientX,y:p.clientY}},S=()=>{a&&(c.current=!0),i=!1,a=!1},E=p=>{(p.buttons&1)===1?(i=!0,f={x:p.clientX,y:p.clientY}):i=!1};return e.addEventListener("mousedown",g),e.addEventListener("mousemove",_),e.addEventListener("mouseup",S),e.addEventListener("mouseenter",E),()=>{e.removeEventListener("mousedown",g),e.removeEventListener("mousemove",_),e.removeEventListener("mouseup",S),e.removeEventListener("mouseenter",E)}},[e,n,o,t,s]),{justFinishedPanningRef:c}}function fe(e){const[n,o]=u.useState({width:0,height:0}),t=u.useRef(null);return u.useEffect(()=>{const s=e.current;if(!s)return;const c=new ResizeObserver(f=>{const g=f[0];if(!g)return;const{width:_,height:S}=g.contentRect;t.current&&cancelAnimationFrame(t.current),t.current=requestAnimationFrame(()=>{o({width:_,height:S}),t.current=null})});c.observe(s);const{clientWidth:i,clientHeight:a}=s;return i>0&&a>0&&o({width:i,height:a}),()=>{t.current&&cancelAnimationFrame(t.current),c.disconnect()}},[e]),n}function de(e){const n=u.useRef(e);return n.current=e,n}const Me=10;function De(e){const{canvas:n,enabled:o,onLassoComplete:t,onLassoUpdate:s,minDisplacement:c=Me}=e,[i,a]=u.useState([]),[f,g]=u.useState(!1),_=de(t),S=de(s),E=u.useRef(!1);return u.useEffect(()=>{if(!o||!n)return;let p=!1,d=[],L=null;const A=h=>{if(h.button!==0)return;const F=h.currentTarget.getBoundingClientRect(),P=h.clientX-F.left,w=h.clientY-F.top,z=[[P,w]];L=[P,w],d=z,p=!0,g(!0),a(z)},R=h=>{if(!p||E.current)return;E.current=!0,requestAnimationFrame(()=>{E.current=!1});const F=h.currentTarget.getBoundingClientRect(),P=h.clientX-F.left,w=h.clientY-F.top,z=[...d,[P,w]];d=z,a(z),S.current&&S.current(z)},m=()=>{if(!p)return;p=!1,g(!1);let h=0;if(L&&d.length>0){const F=d[d.length-1],P=F[0]-L[0],w=F[1]-L[1];h=Math.sqrt(P*P+w*w)}const B=h>=c;_.current(B?d:[]),d=[],L=null,a([])};return n.addEventListener("mousedown",A),n.addEventListener("mousemove",R),n.addEventListener("mouseup",m),n.addEventListener("mouseleave",m),()=>{n.removeEventListener("mousedown",A),n.removeEventListener("mousemove",R),n.removeEventListener("mouseup",m),n.removeEventListener("mouseleave",m)}},[o,n,c,_,S]),{lassoPath:i,isDrawing:f}}const Ie=`#version 300 es
|
|
2
|
+
precision mediump float;
|
|
3
|
+
|
|
4
|
+
uniform float u_opacity;
|
|
5
|
+
uniform float u_backgroundOpacity;
|
|
6
|
+
uniform float u_highlightBrightness;
|
|
7
|
+
|
|
8
|
+
in vec4 v_color;
|
|
9
|
+
in float v_isSelected;
|
|
10
|
+
in float v_isBackground;
|
|
11
|
+
in float v_isHighlight;
|
|
12
|
+
|
|
13
|
+
out vec4 fragColor;
|
|
14
|
+
|
|
15
|
+
// Constants for circular point rendering
|
|
16
|
+
const vec2 CENTER = vec2(0.5, 0.5);
|
|
17
|
+
const float RADIUS = 0.5;
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
void main() {
|
|
21
|
+
// gl_PointCoord gives us the pixel coordinate within the point (0,0 to 1,1)
|
|
22
|
+
// Calculate distance from center
|
|
23
|
+
float dist = length(gl_PointCoord - CENTER);
|
|
24
|
+
|
|
25
|
+
// Discard pixels outside the circle (hard edge, no AA)
|
|
26
|
+
// This approach avoids depth buffer artifacts when points overlap
|
|
27
|
+
if (dist > RADIUS) {
|
|
28
|
+
discard;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Apply flag-based visual changes
|
|
32
|
+
vec3 finalColor = v_color.rgb;
|
|
33
|
+
float finalAlpha = v_color.a * u_opacity;
|
|
34
|
+
|
|
35
|
+
// Background points: reduce opacity (de-emphasize when something else is selected)
|
|
36
|
+
if (v_isBackground > 0.5) {
|
|
37
|
+
finalAlpha *= u_backgroundOpacity;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Highlighted points: brighten color (temporary during lasso drawing)
|
|
41
|
+
if (v_isHighlight > 0.5) {
|
|
42
|
+
finalColor = min(v_color.rgb * u_highlightBrightness, vec3(1.0));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Output premultiplied alpha to avoid edge halos with transparency
|
|
46
|
+
// Requires blendFunc(ONE, ONE_MINUS_SRC_ALPHA) in WebGL setup
|
|
47
|
+
fragColor = vec4(finalColor * finalAlpha, finalAlpha);
|
|
48
|
+
}
|
|
49
|
+
`,Oe=`#version 300 es
|
|
50
|
+
precision mediump float;
|
|
51
|
+
|
|
52
|
+
in vec2 a_position;
|
|
53
|
+
in vec4 a_color;
|
|
54
|
+
in uint a_flag;
|
|
55
|
+
uniform mat3 u_matrix;
|
|
56
|
+
uniform float u_pointSize;
|
|
57
|
+
uniform float u_highlightSizeScale;
|
|
58
|
+
uniform float u_unselectedSizeScale;
|
|
59
|
+
|
|
60
|
+
out vec4 v_color;
|
|
61
|
+
out float v_isSelected;
|
|
62
|
+
out float v_isBackground;
|
|
63
|
+
out float v_isHighlight;
|
|
64
|
+
|
|
65
|
+
// Flag constants (must match JavaScript constants)
|
|
66
|
+
const uint FLAG_SELECTED = 1u;
|
|
67
|
+
const uint FLAG_BACKGROUND = 2u;
|
|
68
|
+
const uint FLAG_HIGHLIGHT = 4u;
|
|
69
|
+
|
|
70
|
+
// Z-depth constants for layering (WebGL clip space: -1 near, +1 far)
|
|
71
|
+
const float Z_BACKGROUND = 0.99; // Background points (farthest back)
|
|
72
|
+
const float Z_NORMAL = 0.0; // Normal points (middle layer)
|
|
73
|
+
const float Z_TOP = -1.0; // Selected/highlighted points (front)
|
|
74
|
+
|
|
75
|
+
void main() {
|
|
76
|
+
// Apply transformation matrix to position
|
|
77
|
+
vec3 transformedPosition = u_matrix * vec3(a_position, 1.0);
|
|
78
|
+
|
|
79
|
+
// Decode flags using bitwise operations (WebGL 2.0 feature)
|
|
80
|
+
bool isSelected = (a_flag & FLAG_SELECTED) != 0u;
|
|
81
|
+
bool isBackground = (a_flag & FLAG_BACKGROUND) != 0u;
|
|
82
|
+
bool isHighlight = (a_flag & FLAG_HIGHLIGHT) != 0u;
|
|
83
|
+
|
|
84
|
+
// Calculate z-depth based on flags (determines render order)
|
|
85
|
+
float z = isBackground ? Z_BACKGROUND : ((isSelected || isHighlight) ? Z_TOP : Z_NORMAL);
|
|
86
|
+
|
|
87
|
+
// Adjust point size based on flags
|
|
88
|
+
float pointSize = u_pointSize;
|
|
89
|
+
if (isHighlight) {
|
|
90
|
+
pointSize *= u_highlightSizeScale;
|
|
91
|
+
} else if (!isSelected) {
|
|
92
|
+
pointSize *= u_unselectedSizeScale;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
gl_Position = vec4(transformedPosition.xy, z, 1.0);
|
|
96
|
+
gl_PointSize = pointSize;
|
|
97
|
+
|
|
98
|
+
// Pass color and flags to fragment shader
|
|
99
|
+
// Convert bools to floats for varying variables
|
|
100
|
+
v_color = a_color;
|
|
101
|
+
v_isSelected = isSelected ? 1.0 : 0.0;
|
|
102
|
+
v_isBackground = isBackground ? 1.0 : 0.0;
|
|
103
|
+
v_isHighlight = isHighlight ? 1.0 : 0.0;
|
|
104
|
+
}
|
|
105
|
+
`,ke="--scatterplot",Ue=["lasso","debug"];function Ye(e){return e.replace(/[A-Z]/g,n=>`-${n.toLowerCase()}`)}function Ne(e){const n={};for(const o of Ue){const t=e[o];for(const[s,c]of Object.entries(t))n[`${ke}-${o}-${Ye(s)}`]=String(c)}return n}const ge={canvas:{background:"#ffffff",dataPadding:20},points:{defaultColor:"#3498db",size:5,opacity:1,backgroundOpacity:.5,highlightBrightness:1.4,highlightSizeScale:1.3,unselectedSizeScale:.2},lasso:{fill:"rgba(59, 130, 246, 0.1)",stroke:"rgb(59, 130, 246)",strokeWidth:2,strokeDasharray:"5,5"},debug:{background:"rgba(0, 0, 0, 0.8)",color:"#00ff00",fontFamily:"monospace",fontSize:"12px"}};function V(e,n=ge){return{canvas:{...n.canvas,...e.canvas},points:{...n.points,...e.points},lasso:{...n.lasso,...e.lasso},debug:{...n.debug,...e.debug}}}const he=ge,He=V({canvas:{background:"#1a1a2e"},points:{defaultColor:"#5dade2",backgroundOpacity:.4,highlightBrightness:1.5},lasso:{fill:"rgba(93, 173, 226, 0.15)",stroke:"rgb(93, 173, 226)"},debug:{background:"rgba(0, 0, 0, 0.9)"}}),Ge=V({canvas:{background:"#000000"},points:{defaultColor:"#ffffff",size:6,backgroundOpacity:.3,highlightBrightness:1.6,highlightSizeScale:1.5},lasso:{fill:"rgba(255, 255, 0, 0.2)",stroke:"#ffff00",strokeWidth:3,strokeDasharray:"none"},debug:{background:"#000000",color:"#ffffff",fontSize:"14px"}}),Z=he;function me(e,n){if(n===0)return{xMin:0,xMax:1,yMin:0,yMax:1};if(e.length<n*2)throw new Error(`positions buffer too small: expected at least ${n*2} elements for ${n} points, got ${e.length}`);let o=e[0],t=e[0],s=e[1],c=e[1];for(let i=0;i<n;i++){const a=e[i*2],f=e[i*2+1];a<o&&(o=a),a>t&&(t=a),f<s&&(s=f),f>c&&(c=f)}return{xMin:o,xMax:t,yMin:s,yMax:c}}function Se(e,n,o,t){const s=n-e,c=t-o,i=Math.max(s,c)||1,a=.5-s/i/2,f=.5-c/i/2;return{scale:i,xOffset:a,yOffset:f}}function pe(e,n,o,t){const s=e/n,c=Math.min(e,n),a=1-2*(c>0?t/c:0);let f=o*a,g=o*a;return s>1?f=o*a/s:s<1&&(g=o*a*s),{scaleX:f,scaleY:g}}function _e(e,n,o,t,s,c){const i=new Float32Array(n*2),{scale:a,xOffset:f,yOffset:g}=Se(o,t,s,c);for(let _=0;_<n;_++){const S=e[_*2],E=e[_*2+1],p=(S-o)/a,d=(E-s)/a,L=p+f,A=d+g;i[_*2]=-1+2*L,i[_*2+1]=-1+2*A}return i}function J(e,n,o,t,s,c,i,a,f,g,_,S,E){const p=(e-o)/s,d=(n-t)/s,L=p+c,A=d+i,R=-1+2*L,m=-1+2*A,h=R*a+g,B=m*f+_,F=(h+1)*S/2,P=(1-B)*E/2;return{screenX:F,screenY:P}}function Xe(e,n,o){if(o.length<3)return!1;let t=!1;for(let s=0,c=o.length-1;s<o.length;c=s++){const i=o[s][0],a=o[s][1],f=o[c][0],g=o[c][1];a>n!=g>n&&e<(f-i)*(n-a)/(g-a)+i&&(t=!t)}return t}const je=10;function Ae(e,n,o,t,s){const{scale:c,xOffset:i,yOffset:a}=Se(t.xMin,t.xMax,t.yMin,t.yMax),{scaleX:f,scaleY:g}=pe(e,n,o,s);return{scale:c,xOffset:i,yOffset:a,scaleX:f,scaleY:g}}function ve(e,n,o,t,s,c,i,a=je,f,g=Z.canvas.dataPadding){if(t===0)return null;const{zoom:_,pan:S}=i,{scale:E,xOffset:p,yOffset:d,scaleX:L,scaleY:A}=Ae(s,c,_,f,g);let R=null,m=a;for(let h=0;h<t;h++){const B=o[h*2],F=o[h*2+1],{screenX:P,screenY:w}=J(B,F,f.xMin,f.yMin,E,p,d,L,A,S.x,S.y,s,c),z=P-e,y=w-n,I=Math.sqrt(z*z+y*y);I<m&&(m=I,R=h)}return R}function Q(e,n,o,t,s,c,i,a=Z.canvas.dataPadding){if(e.length<3)return new Set;const{zoom:f,pan:g}=c,{scale:_,xOffset:S,yOffset:E,scaleX:p,scaleY:d}=Ae(t,s,f,i,a),L=new Set;for(let A=0;A<o;A++){const R=n[A*2],m=n[A*2+1],{screenX:h,screenY:B}=J(R,m,i.xMin,i.yMin,_,S,E,p,d,g.x,g.y,t,s);Xe(h,B,e)&&L.add(A)}return L}function $e(e,n){return new Float32Array([e,0,0,0,n,0,0,0,1])}function We(e,n){return new Float32Array([1,0,0,0,1,0,e,n,1])}function Ze(e,n){return new Float32Array([e[0]*n[0]+e[3]*n[1]+e[6]*n[2],e[1]*n[0]+e[4]*n[1]+e[7]*n[2],e[2]*n[0]+e[5]*n[1]+e[8]*n[2],e[0]*n[3]+e[3]*n[4]+e[6]*n[5],e[1]*n[3]+e[4]*n[4]+e[7]*n[5],e[2]*n[3]+e[5]*n[4]+e[8]*n[5],e[0]*n[6]+e[3]*n[7]+e[6]*n[8],e[1]*n[6]+e[4]*n[7]+e[7]*n[8],e[2]*n[6]+e[5]*n[7]+e[8]*n[8]])}function qe({count:e,camera:n,lassoMetrics:o}){const{zoom:t,pan:s}=n;return T.jsxs("div",{className:"scatterplot-debug-panel",children:[T.jsx("div",{className:"scatterplot-debug-panel-title",children:"Debug Info"}),T.jsxs("div",{children:["Points: ",e.toLocaleString()]}),T.jsxs("div",{children:["Zoom: ",t.toFixed(3),"x"]}),T.jsxs("div",{children:["Pan: (",s.x.toFixed(3),", ",s.y.toFixed(3),")"]}),o&&T.jsxs("div",{style:{marginTop:"5px",borderTop:"1px solid #444",paddingTop:"5px"},children:[T.jsxs("div",{children:["Last lasso: ",o.timeMs.toFixed(1),"ms"]}),T.jsxs("div",{children:["Selected: ",o.selectedCount.toLocaleString()]})]})]})}function Ke({lassoPath:e,width:n,height:o,isDrawing:t}){if(!t||e.length<2)return null;const s=e.map(([c,i])=>`${c},${i}`).join(" ");return T.jsx("svg",{"aria-hidden":"true",style:{position:"absolute",top:0,left:0,width:n,height:o,pointerEvents:"none",zIndex:10},children:T.jsx("polygon",{className:"scatterplot-lasso-polygon",points:s})})}const Ve=800,Je=600;function Le({width:e,height:n,positions:o,colors:t,debug:s=!1,pixelRatio:c=window.devicePixelRatio||1,flags:i,onPointClick:a,lassoEnabled:f=!1,onLassoComplete:g,onLassoUpdate:_,camera:S,onCameraChange:E,panZoomEnabled:p=!0,theme:d=Z,className:L}){const A=u.useRef(null),R=fe(A),m=e??(R.width>0?R.width:Ve),h=n??(R.height>0?R.height:Je),B=u.useRef(null),[F,P]=u.useState(null);u.useLayoutEffect(()=>{P(B.current)},[]);const[w,z]=u.useState(void 0),y=o.length/2,I=u.useRef(null),U=u.useRef(null),x=u.useRef(-1),D=u.useRef(-1),Y=u.useRef(-1),$=u.useRef(null),ee=u.useRef(null),Ee=u.useRef(null),Re=u.useRef(null),ye=u.useRef(null),be=u.useRef(null),xe=u.useRef(null),G=u.useRef(null),X=u.useRef(null),W=u.useRef(null),j=u.useMemo(()=>me(o,y),[o,y]),nn=u.useMemo(()=>Ne(d),[d]),O=u.useMemo(()=>{const[r,l,C]=ae(d.canvas.background);return[r/255,l/255,C/255,1]},[d.canvas.background]),[tn,on]=u.useState(q),ne=S!==void 0,N=ne?S:tn,rn=u.useCallback(r=>{E&&E(r),ne||on(r)},[E,ne]),{justFinishedPanningRef:te}=ze({canvas:F,width:m,height:h,onCameraChange:rn,enabled:p}),{lassoPath:sn,isDrawing:cn}=De({canvas:F,enabled:f,onLassoComplete:r=>{if(g){const l=s?performance.now():0,C=Q(r,o,y,m,h,N,j,d.canvas.dataPadding);s&&z({timeMs:performance.now()-l,selectedCount:C.size}),g(C)}},onLassoUpdate:r=>{if(_){const l=Q(r,o,y,m,h,N,j,d.canvas.dataPadding);_(l)}}});return u.useEffect(()=>{const r=B.current;if(!r||!a||f)return;const l=C=>{if(te.current){te.current=!1;return}const M=r.getBoundingClientRect(),b=C.clientX-M.left,H=C.clientY-M.top,k=ve(b,H,o,y,m,h,N,d.points.size,j,d.canvas.dataPadding);a(k)};return r.addEventListener("click",l),()=>r.removeEventListener("click",l)},[o,y,m,h,N,d.points.size,a,j,d.canvas.dataPadding,f,te]),u.useEffect(()=>{const r=B.current;if(!r)return;const l=r.getContext("webgl2",{preserveDrawingBuffer:!0,antialias:!1});if(!l){console.error("WebGL 2 not supported");return}I.current=l;const C=ce(l,l.VERTEX_SHADER,Oe),M=ce(l,l.FRAGMENT_SHADER,Ie);if(!C||!M)return;const b=we(l,C,M);if(!b)return;if(U.current=b,x.current=l.getAttribLocation(b,"a_position"),D.current=l.getAttribLocation(b,"a_color"),Y.current=l.getAttribLocation(b,"a_flag"),$.current=l.getUniformLocation(b,"u_matrix"),ee.current=l.getUniformLocation(b,"u_pointSize"),Ee.current=l.getUniformLocation(b,"u_opacity"),Re.current=l.getUniformLocation(b,"u_backgroundOpacity"),ye.current=l.getUniformLocation(b,"u_highlightBrightness"),be.current=l.getUniformLocation(b,"u_highlightSizeScale"),xe.current=l.getUniformLocation(b,"u_unselectedSizeScale"),x.current===-1){console.error("Failed to get attribute location for a_position");return}if(D.current===-1){console.error("Failed to get attribute location for a_color");return}if(Y.current===-1){console.error("Failed to get attribute location for a_flag");return}if(!$.current){console.error("Failed to get uniform location for u_matrix");return}if(!ee.current){console.error("Failed to get uniform location for u_pointSize");return}const H=l.createBuffer(),k=l.createBuffer(),oe=l.createBuffer();return G.current=H,X.current=k,W.current=oe,l.enable(l.BLEND),l.blendFunc(l.ONE,l.ONE_MINUS_SRC_ALPHA),l.enable(l.DEPTH_TEST),l.depthFunc(l.LESS),()=>{C&&l.deleteShader(C),M&&l.deleteShader(M),U.current&&(l.deleteProgram(U.current),U.current=null),G.current&&(l.deleteBuffer(G.current),G.current=null),X.current&&(l.deleteBuffer(X.current),X.current=null),W.current&&(l.deleteBuffer(W.current),W.current=null)}},[]),u.useEffect(()=>{const r=I.current,l=G.current;if(!r||!l||y===0)return;const{xMin:C,xMax:M,yMin:b,yMax:H}=j,k=_e(o,y,C,M,b,H);r.bindBuffer(r.ARRAY_BUFFER,l),r.bufferData(r.ARRAY_BUFFER,k,r.STATIC_DRAW),r.enableVertexAttribArray(x.current),r.vertexAttribPointer(x.current,2,r.FLOAT,!1,0,0)},[o,y,j]),u.useEffect(()=>{const r=I.current,l=X.current;!r||!l||y===0||(r.bindBuffer(r.ARRAY_BUFFER,l),r.bufferData(r.ARRAY_BUFFER,t,r.STATIC_DRAW),r.enableVertexAttribArray(D.current),r.vertexAttribPointer(D.current,4,r.UNSIGNED_BYTE,!0,0,0))},[t,y]),u.useEffect(()=>{const r=I.current,l=U.current,C=G.current,M=X.current,b=W.current,{zoom:H,pan:k}=N;if(!r||!l)return;if(y===0){r.clearColor(O[0],O[1],O[2],O[3]),r.clear(r.COLOR_BUFFER_BIT);return}if(r.useProgram(l),b){const dn=i??K(y);r.bindBuffer(r.ARRAY_BUFFER,b),r.bufferData(r.ARRAY_BUFFER,dn,r.DYNAMIC_DRAW),r.enableVertexAttribArray(Y.current),r.vertexAttribIPointer(Y.current,1,r.UNSIGNED_BYTE,0,0)}const{scaleX:oe,scaleY:an}=pe(m,h,H,d.canvas.dataPadding),ln=$e(oe,an),un=We(k.x,k.y),fn=Ze(un,ln);r.uniformMatrix3fv($.current,!1,fn),r.uniform1f(ee.current,d.points.size*c),r.uniform1f(Ee.current,d.points.opacity),r.uniform1f(Re.current,d.points.backgroundOpacity),r.uniform1f(ye.current,d.points.highlightBrightness),r.uniform1f(be.current,d.points.highlightSizeScale),r.uniform1f(xe.current,d.points.unselectedSizeScale),C&&(r.bindBuffer(r.ARRAY_BUFFER,C),r.enableVertexAttribArray(x.current),r.vertexAttribPointer(x.current,2,r.FLOAT,!1,0,0)),M&&(r.bindBuffer(r.ARRAY_BUFFER,M),r.enableVertexAttribArray(D.current),r.vertexAttribPointer(D.current,4,r.UNSIGNED_BYTE,!0,0,0)),b&&(r.bindBuffer(r.ARRAY_BUFFER,b),r.enableVertexAttribArray(Y.current),r.vertexAttribIPointer(Y.current,1,r.UNSIGNED_BYTE,0,0)),r.viewport(0,0,m*c,h*c),r.clearColor(O[0],O[1],O[2],O[3]),r.clear(r.COLOR_BUFFER_BIT|r.DEPTH_BUFFER_BIT),r.drawArrays(r.POINTS,0,y)},[o,t,i,N,d,c,y,m,h,O]),T.jsxs("div",{ref:A,className:L,style:{position:"relative",lineHeight:0,width:e?`${e}px`:"100%",height:n?`${n}px`:"100%",...nn},children:[T.jsx("canvas",{ref:B,width:m*c,height:h*c,style:{width:`${m}px`,height:`${h}px`,...s?{border:"1px dashed #c00"}:{}}}),f&&T.jsx(Ke,{lassoPath:sn,width:m,height:h,isDrawing:cn}),s&&T.jsx(qe,{count:y,camera:N,lassoMetrics:w})]})}const Qe=1e6;function en({points:e,width:n,height:o,initialCamera:t=q,theme:s,pixelRatio:c,enableLasso:i=!0,enablePanZoom:a=!0,debug:f=!1,onSelectionChange:g,lassoRealtimeThreshold:_=Qe,controlled:S}){const[E,p]=u.useState(t),d=S?.camera!==void 0,L=d&&S?.camera?S.camera:E,{selectedIndices:A,handlePointClick:R,setSelection:m}=re(),[h,B]=u.useState(new Set),{positions:F,colors:P}=u.useMemo(()=>ue(e),[e]),w=u.useCallback(x=>{S?.onCameraChange&&S.onCameraChange(x),d||p(x)},[S?.onCameraChange,d]),z=u.useMemo(()=>K(e.length,A.size>0?A:void 0,h.size>0?h:void 0),[e.length,A,h]),y=x=>{B(new Set),m(x),g&&g(x)},I=x=>{e.length>=_||B(D=>D.size===x.size&&[...D].every($=>x.has($))?D:x)},U=x=>{R(x),g&&g(x!==null?new Set([x]):new Set)};return T.jsx(Le,{width:n,height:o,positions:F,colors:P,flags:z,camera:L,onCameraChange:w,onPointClick:U,panZoomEnabled:a,lassoEnabled:i,onLassoComplete:y,onLassoUpdate:I,theme:s,pixelRatio:c,debug:f})}v.DEFAULT_CAMERA=q,v.Scatterplot=en,v.ScatterplotGL=Le,v.calculateBounds=me,v.createFlagBuffer=K,v.createTheme=V,v.darkTheme=He,v.dataPointToScreen=J,v.defaultTheme=Z,v.findClosestPointRaw=ve,v.findPointsInLassoRaw=Q,v.hexToRgba=le,v.highContrastTheme=Ge,v.lightTheme=he,v.normalizePositionsIsotropic=_e,v.pointsToBuffers=ue,v.useContainerSize=fe,v.useSelection=re,Object.defineProperty(v,Symbol.toStringTag,{value:"Module"})}));
|
|
106
|
+
//# sourceMappingURL=scatterplot.umd.js.map
|