screengrid

API Reference

Complete reference documentation for all public APIs in ScreenGrid Library.

Table of Contents


ScreenGridLayerGL

Main orchestrator class that composes all modules. This is the primary interface for using the library.

Constructor

new ScreenGridLayerGL(options)

Creates a new ScreenGrid layer instance.

Parameters:

Returns: ScreenGridLayerGL instance

Example:

const layer = new ScreenGridLayerGL({
  data: myData,
  getPosition: (d) => d.coordinates,
  getWeight: (d) => d.value,
  cellSizePixels: 50
});
map.addLayer(layer);

Properties

id (getter)

type (getter)

renderingMode (getter)


Methods

onAdd(map, gl)

Called automatically by MapLibre GL when the layer is added to the map. You typically don’t call this directly.

Parameters:

Returns: void


prerender()

Called automatically before each render. Projects points to screen space.

Returns: void


render()

Renders the grid layer to the canvas. Called automatically by MapLibre GL render loop.

Returns: void

Note: This method performs aggregation and drawing. For performance, avoid calling manually unless necessary.


onRemove()

Called automatically when the layer is removed from the map. Cleans up event bindings and canvas.

Returns: void


setData(newData)

Updates the data source for the layer.

Parameters:

Returns: void

Example:

layer.setData(updatedData);

Note: Automatically re-projects points after updating data.


setConfig(updates)

Updates layer configuration with partial options.

Parameters:

Returns: void

Example:

layer.setConfig({
  cellSizePixels: 80,
  colorScale: (v) => [255 * v, 0, 0, 200],
  enableGlyphs: true
});

Note: Automatically re-projects points after updating config.


getCellAt(point)

Get cell information at a specific screen point.

Parameters:

Returns: Object|null - Cell information object or null if no cell at point.

Cell Object Structure:

{
  col: number,           // Grid column index
  row: number,           // Grid row index
  value: number,         // Aggregated value
  cellData: Array,       // Array of original data points in this cell
  x: number,             // Screen X coordinate (top-left of cell)
  y: number,             // Screen Y coordinate (top-left of cell)
  cellSize: number,      // Cell size in pixels
  index: number          // Linear index in grid array
}

Example:

map.on('mousemove', (e) => {
  const cell = layer.getCellAt({ x: e.point.x, y: e.point.y });
  if (cell) {
    console.log(`Cell [${cell.col}, ${cell.row}]: ${cell.value}`);
    console.log(`Contains ${cell.cellData.length} data points`);
  }
});

getCellsInBounds(bounds)

Get all cells with data in a rectangular region.

Parameters:

Returns: Array<Object> - Array of cell information objects (same structure as getCellAt)

Example:

const cells = layer.getCellsInBounds({
  minX: 100,
  minY: 100,
  maxX: 500,
  maxY: 400
});

console.log(`Found ${cells.length} cells in region`);
cells.forEach(cell => {
  console.log(`Cell [${cell.col}, ${cell.row}]: ${cell.value}`);
});

getGridStats()

Get statistics about the current grid aggregation.

Returns: Object|null - Statistics object or null if grid not yet aggregated.

Statistics Object:

{
  totalCells: number,      // Total number of cells in grid
  cellsWithData: number,    // Number of cells containing data
  maxValue: number,         // Maximum aggregated value
  minValue: number,         // Minimum aggregated value (excluding zeros)
  avgValue: number,         // Average value across cells with data
  totalValue: number        // Sum of all cell values
}

Example:

const stats = layer.getGridStats();
if (stats) {
  console.log(`Grid: ${stats.cols}×${stats.rows} cells`);
  console.log(`${stats.cellsWithData} cells contain data`);
  console.log(`Value range: ${stats.minValue} - ${stats.maxValue}`);
}

Static Methods (Glyph Utilities)

These are convenience aliases to GlyphUtilities methods. See GlyphUtilities for detailed documentation.

ScreenGridLayerGL.drawCircleGlyph(ctx, x, y, radius, color, alpha)
ScreenGridLayerGL.drawBarGlyph(ctx, x, y, values, maxValue, cellSize, colors)
ScreenGridLayerGL.drawPieGlyph(ctx, x, y, values, radius, colors)
ScreenGridLayerGL.drawScatterGlyph(ctx, x, y, points, cellSize, color)
ScreenGridLayerGL.drawDonutGlyph(ctx, x, y, values, outerRadius, innerRadius, colors)
ScreenGridLayerGL.drawHeatmapGlyph(ctx, x, y, radius, normalizedValue, colorScale)
ScreenGridLayerGL.drawRadialBarGlyph(ctx, x, y, values, maxValue, maxRadius, color)
ScreenGridLayerGL.drawTimeSeriesGlyph(ctx, x, y, timeSeriesData, cellSize, options)

Configuration Options

Options passed to ScreenGridLayerGL constructor or setConfig().

Basic Options

id

data

getPosition

Example:

getPosition: (d) => [d.longitude, d.latitude]
// or
getPosition: (d) => d.location

getWeight

Example:

getWeight: (d) => d.population
// or
getWeight: (d) => d.count || 1

cellSizePixels

colorScale

Example:

// Heatmap (red to yellow)
colorScale: (v) => [255 * v, 200 * (1 - v), 50, 220]

// Blue scale
colorScale: (v) => [50, 100, 255 * v, 220]

// Custom gradient
colorScale: (v) => {
  const r = Math.floor(255 * Math.sin(v * Math.PI));
  const g = Math.floor(255 * Math.cos(v * Math.PI));
  const b = Math.floor(255 * v);
  return [r, g, b, 200];
}

Glyph Options

enableGlyphs

onDrawCell

cellInfo Object:

{
  cellData: Array,        // Array of original data points in this cell
  cellSize: number,      // Size of the cell in pixels
  glyphRadius: number,   // Recommended radius for glyph drawing
  normalizedValue: number, // Same as normVal
  col: number,           // Grid column index
  row: number,           // Grid row index
  value: number,         // Raw aggregated value
  customData: any,       // Result returned from onAfterAggregate for this cell
  zoomLevel: number,     // Current map zoom level
  isHovered: boolean,    // True if cell is currently hovered by mouse
  grid: Array<number>    // Full grid array for context
}

**Example:**
```javascript
onDrawCell: (ctx, x, y, normVal, cellInfo) => {
  const { cellData, glyphRadius } = cellInfo;
  const total = cellData.reduce((sum, item) => sum + item.data.value, 0);
  
  ctx.fillStyle = `hsl(${normVal * 360}, 70%, 50%)`;
  ctx.beginPath();
  ctx.arc(x, y, glyphRadius, 0, 2 * Math.PI);
  ctx.fill();
}

glyphSize

onAfterAggregate

Example (Multivariate Averages):

onAfterAggregate: (cellData) => {
  const sumVal1 = cellData.reduce((s, p) => s + p.data.val1, 0);
  const sumVal2 = cellData.reduce((s, p) => s + p.data.val2, 0);
  return { 
    avgVal1: sumVal1 / cellData.length,
    avgVal2: sumVal2 / cellData.length 
  };
}

Adaptive Sizing Options

adaptiveCellSize

minCellSize

maxCellSize

zoomBasedSize


Event Callbacks

onAggregate

gridData Object:

{
  grid: Array<number>,      // Array of aggregated values
  cellData: Array<Array>,   // 2D array of original data points per cell
  cols: number,             // Number of columns
  rows: number,             // Number of rows
  width: number,            // Canvas width
  height: number,           // Canvas height
  cellSizePixels: number    // Cell size used
}

Example:

onAggregate: (gridData) => {
  console.log(`Grid: ${gridData.cols}×${gridData.rows}`);
  console.log(`Max value: ${Math.max(...gridData.grid)}`);
}

onHover

Example:

onHover: ({ cell, event }) => {
  if (cell) {
    console.log(`Hovering over cell [${cell.col}, ${cell.row}]`);
    console.log(`Value: ${cell.value}`);
    console.log(`Contains ${cell.cellData.length} points`);
  }
}

onClick

Example:

onClick: ({ cell, event }) => {
  if (cell) {
    showDetailsModal(cell.cellData);
  }
}

Other Options

enabled


Geometry Input Options (v2.1.0+)

These options allow you to use non-point geometries (Polygon, LineString, etc.) instead of point data.

source

Example:

const layer = new ScreenGridLayerGL({
  source: geojsonData,  // GeoJSON FeatureCollection
  placement: { strategy: 'centroid' },
  renderMode: 'feature-anchors'
});

placement

Placement Config Structure:

{
  strategy: 'centroid' | 'polylabel' | 'line-sample' | 'grid-geo' | 'grid-screen' | 'point',
  spacing?: { meters: number } | { pixels: number },
  partition?: 'union' | 'per-part',
  maxPerFeature?: number,
  minArea?: number,
  minLength?: number,
  jitterPixels?: number,
  zoomAdaptive?: boolean
}

Example:

placement: {
  strategy: 'line-sample',
  spacing: { meters: 200 },
  zoomAdaptive: true
}

renderMode

Example:

renderMode: 'feature-anchors'  // Draw one glyph per feature

anchorSizePixels

Example:

anchorSizePixels: 18  // Fixed glyph size in pixels

Geometry Input Options (v2.1.0+)

These options allow you to use non-point geometries (Polygon, LineString, etc.) instead of point data.

source

Example:

const layer = new ScreenGridLayerGL({
  source: geojsonData,  // GeoJSON FeatureCollection
  placement: { strategy: 'centroid' },
  renderMode: 'feature-anchors'
});

placement

Placement Config Structure:

{
  strategy: 'centroid' | 'polylabel' | 'line-sample' | 'grid-geo' | 'grid-screen' | 'point',
  spacing?: { meters: number } | { pixels: number },
  partition?: 'union' | 'per-part',
  maxPerFeature?: number,
  minArea?: number,
  minLength?: number,
  jitterPixels?: number,
  zoomAdaptive?: boolean
}

Example:

placement: {
  strategy: 'line-sample',
  spacing: { meters: 200 },
  zoomAdaptive: true
}

renderMode

Example:

renderMode: 'feature-anchors'  // Draw one glyph per feature

anchorSizePixels

Example:

anchorSizePixels: 18  // Fixed glyph size in pixels

GlyphUtilities

Static utility class for drawing common glyph types. Can be imported directly or accessed via ScreenGridLayerGL static methods.

Import

import { GlyphUtilities } from 'screengrid';

Methods

drawCircleGlyph(ctx, x, y, radius, color, alpha)

Draw a simple circle glyph.

Parameters:

Returns: void

Example:

GlyphUtilities.drawCircleGlyph(ctx, 100, 100, 20, '#3498db', 0.8);

drawBarGlyph(ctx, x, y, values, maxValue, cellSize, colors)

Draw a horizontal bar chart glyph showing multiple values.

Parameters:

Returns: void

Example:

GlyphUtilities.drawBarGlyph(
  ctx, x, y,
  [10, 25, 15],      // values
  30,                // maxValue
  50,                // cellSize
  ['#e74c3c', '#3498db', '#2ecc71']  // colors
);

drawPieGlyph(ctx, x, y, values, radius, colors)

Draw a pie chart glyph showing proportions.

Parameters:

Returns: void

Example:

GlyphUtilities.drawPieGlyph(
  ctx, x, y,
  [30, 20, 10],      // values
  15,                // radius
  ['#e74c3c', '#3498db', '#2ecc71']  // colors
);

drawDonutGlyph(ctx, x, y, values, outerRadius, innerRadius, colors)

Draw a donut chart glyph (pie chart with central hole).

Parameters:

Returns: void

Example:

GlyphUtilities.drawDonutGlyph(
  ctx, x, y,
  [30, 20, 10],      // values
  20,                // outerRadius
  10,                // innerRadius
  ['#e74c3c', '#3498db', '#2ecc71']
);

drawScatterGlyph(ctx, x, y, points, cellSize, color)

Draw a scatter plot glyph showing individual data points.

Parameters:

Returns: void

Example:

GlyphUtilities.drawScatterGlyph(
  ctx, x, y,
  cellData,  // Array of {data, weight, ...} objects
  50,        // cellSize
  '#e74c3c'  // color
);

drawHeatmapGlyph(ctx, x, y, radius, normalizedValue, colorScale)

Draw a heatmap-style glyph with color intensity based on normalized value.

Parameters:

Returns: void

Example:

GlyphUtilities.drawHeatmapGlyph(
  ctx, x, y,
  15,        // radius
  0.75,      // normalizedValue
  (v) => `hsl(${v * 240}, 70%, 50%)`  // colorScale
);

drawRadialBarGlyph(ctx, x, y, values, maxValue, maxRadius, color)

Draw a radial bar glyph (bars radiating from center, like a radar chart).

Parameters:

Returns: void

Example:

GlyphUtilities.drawRadialBarGlyph(
  ctx, x, y,
  [10, 20, 15, 25],  // values
  30,                 // maxValue
  20,                 // maxRadius
  '#3498db'          // color
);

drawTimeSeriesGlyph(ctx, x, y, timeSeriesData, cellSize, options)

Draw a time series line chart glyph showing temporal trends.

Parameters:

Options Object:

{
  lineColor: string,        // Line color. Default: '#3498db'
  pointColor: string,       // Data point color. Default: '#e74c3c'
  lineWidth: number,        // Line width. Default: 2
  pointRadius: number,      // Point radius. Default: 2
  showPoints: boolean,      // Show data points. Default: true
  showArea: boolean,        // Fill area under line. Default: false
  areaColor: string,       // Area fill color. Default: 'rgba(52, 152, 219, 0.2)'
  padding: number           // Padding as fraction of cellSize. Default: 0.1
}

Returns: void

Example:

const timeSeriesData = [
  { year: 2020, value: 10 },
  { year: 2021, value: 15 },
  { year: 2022, value: 12 },
  { year: 2023, value: 20 }
];

GlyphUtilities.drawTimeSeriesGlyph(
  ctx, x, y,
  timeSeriesData,
  50,  // cellSize
  {
    lineColor: '#2ecc71',
    pointColor: '#27ae60',
    lineWidth: 2,
    showPoints: true,
    showArea: true,
    areaColor: 'rgba(46, 204, 113, 0.15)',
    padding: 0.15
  }
);

ConfigManager

Static utility class for managing configuration with defaults and validation.

Import

import { ConfigManager } from 'screengrid';

Methods

create(options)

Create configuration from user options merged with defaults.

Parameters:

Returns: Object - Merged configuration object

Example:

const config = ConfigManager.create({
  data: myData,
  cellSizePixels: 60
});
// config now has all defaults plus user options

update(config, updates)

Update configuration with partial options.

Parameters:

Returns: Object - Updated configuration object

Example:

const updatedConfig = ConfigManager.update(config, {
  cellSizePixels: 80,
  enableGlyphs: true
});

isValid(config)

Validate configuration structure.

Parameters:

Returns: boolean - true if valid, false otherwise

Example:

if (ConfigManager.isValid(config)) {
  // Configuration is valid
}

Aggregator

Pure business logic class for aggregating points into grid cells.

Import

import { Aggregator } from 'screengrid';

Static Methods

aggregate(projectedPoints, originalData, width, height, cellSizePixels)

Aggregate projected points into a grid.

Parameters:

Returns: Object - Aggregation result:

{
  grid: Array<number>,        // Array of aggregated values
  cellData: Array<Array>,    // 2D array of original data points per cell
  cols: number,               // Number of columns
  rows: number,               // Number of rows
  width: number,              // Canvas width
  height: number,             // Canvas height
  cellSizePixels: number      // Cell size used
}

Example:

const projectedPoints = [
  { x: 100, y: 200, w: 1.5 },
  { x: 150, y: 250, w: 2.0 }
];

const result = Aggregator.aggregate(
  projectedPoints,
  originalData,
  800,  // width
  600,  // height
  50    // cellSizePixels
);

getStats(aggregationResult)

Get statistics about a grid aggregation.

Parameters:

Returns: Object - Statistics:

{
  totalCells: number,      // Total number of cells
  cellsWithData: number,   // Number of cells with data
  maxValue: number,        // Maximum value
  minValue: number,        // Minimum value (excluding zeros)
  avgValue: number,        // Average value
  totalValue: number       // Sum of all values
}

Example:

const stats = Aggregator.getStats(result);
console.log(`Found ${stats.cellsWithData} cells with data`);
console.log(`Max value: ${stats.maxValue}`);

Instance Methods

aggregate(projectedPoints, originalData, width, height, cellSizePixels)

Instance method that calls the static method. Same parameters and return value.

getStats(aggregationResult)

Instance method that calls the static method. Same parameters and return value.


Projector

Pure function class for projecting geographic coordinates to screen space.

Import

import { Projector } from 'screengrid';

Static Methods

projectPoints(data, getPosition, getWeight, map)

Project geographic coordinates to screen space.

Parameters:

Returns: Array<Object> - Array of projected points: {x: number, y: number, w: number}

Example:

const projected = Projector.projectPoints(
  myData,
  (d) => d.coordinates,
  (d) => d.value,
  map
);

Instance Methods

constructor(map)

Create a new Projector instance.

Parameters:

setMap(map)

Set the map reference.

Parameters:

project(data, getPosition, getWeight)

Project points using stored map reference.

Parameters:

Returns: Array<Object> - Projected points

Example:

const projector = new Projector(map);
const projected = projector.project(
  myData,
  (d) => d.coordinates,
  (d) => d.value
);

CellQueryEngine

Query engine for finding and accessing grid cells.

Import

import { CellQueryEngine } from 'screengrid';

Static Methods

getCellAt(aggregationResult, point)

Get cell information at a specific point.

Parameters:

Returns: Object|null - Cell information object (see ScreenGridLayerGL.getCellAt)


getCellsInBounds(aggregationResult, bounds)

Get all cells with data in a rectangular region.

Parameters:

Returns: Array<Object> - Array of cell information objects


getCellsAboveThreshold(aggregationResult, threshold)

Get all cells with values above a threshold.

Parameters:

Returns: Array<Object> - Array of cell information objects


Instance Methods

constructor(aggregationResult)

Create a new CellQueryEngine instance.

Parameters:

setAggregationResult(aggregationResult)

Set the aggregation result for queries.

Parameters:

getCellAt(point)

Query cell at point using stored result.

Parameters:

Returns: Object|null - Cell information

getCellsInBounds(bounds)

Query cells in bounds using stored result.

Parameters:

Returns: Array<Object> - Cells in bounds

getCellsAboveThreshold(threshold)

Query cells above threshold using stored result.

Parameters:

Returns: Array<Object> - Cells above threshold


Geometry Placement Modules (v2.1.0+)

Modules for handling non-point geometries (Polygon, LineString, etc.) and converting them to anchor points.

PlacementEngine

Converts GeoJSON geometries to anchor points based on placement strategy.

Import:

import { PlacementEngine } from 'screengrid';

Usage:

const engine = new PlacementEngine(map);
const anchors = engine.processFeatures(features, placementConfig);

PlacementValidator

Validates placement configuration objects.

Import:

import { PlacementValidator } from 'screengrid';

Usage:

const isValid = PlacementValidator.validate(placementConfig);

PlacementStrategyRegistry

Registry for placement strategies (centroid, line-sample, grid-geo, etc.).

Import:

import { PlacementStrategyRegistry } from 'screengrid';

Usage:

const strategy = PlacementStrategyRegistry.get('centroid');

GeometryUtils

Utility functions for geometry operations.

Import:

import { GeometryUtils } from 'screengrid';

Methods:


Legend

Main Legend class for rendering data-driven legends.

Import

import { Legend } from 'screengrid';

Constructor

new Legend(options)

Parameters:

Returns: Legend instance

Example:

const legend = new Legend({
  layer: gridLayer,
  type: 'auto',
  position: 'bottom-right',
  title: 'Data Legend'
});

Methods

update(gridData, config)

Update legend with new data.

Parameters:

Returns: void

show()

Show the legend.

Returns: void

hide()

Hide the legend.

Returns: void

remove()

Remove the legend from DOM.

Returns: void

setPosition(position)

Update legend position.

Parameters:

Returns: void

setTitle(title)

Update legend title.

Parameters:

Returns: void


CanvasManager

Manages canvas creation, sizing, and cleanup. Typically used internally by ScreenGridLayerGL.

Import

import { CanvasManager } from 'screengrid';

Methods

init(map)

Initialize the canvas overlay.

Parameters:

Throws: Error if canvas cannot be initialized

Returns: void

getContext()

Get the 2D rendering context.

Returns: CanvasRenderingContext2D

getCanvas()

Get the overlay canvas element.

Returns: HTMLCanvasElement

resize()

Resize canvas to match map canvas with DPI scaling.

Returns: void

clear()

Clear the canvas.

Returns: void

getDisplaySize()

Get canvas dimensions in CSS pixels.

Returns: Object - {width: number, height: number}

cleanup()

Clean up resources.

Returns: void


Renderer

Canvas drawing logic for grid cells. Typically used internally by ScreenGridLayerGL.

Import

import { Renderer } from 'screengrid';

Static Methods

render(aggregationResult, ctx, config)

Render grid cells to canvas.

Parameters:

Returns: void

renderGlyphs(aggregationResult, ctx, onDrawCell, glyphSize)

Render with glyph mode enabled.

Parameters:

Returns: void

renderColors(aggregationResult, ctx, colorScale)

Render with color mode enabled.

Parameters:

Returns: void


EventBinder

Manages event binding and unbinding. Typically used internally by ScreenGridLayerGL.

Import

import { EventBinder } from 'screengrid';

Methods

bind(map, eventHandlers)

Bind events to the map.

Parameters:

Returns: void

unbind()

Unbind events from the map.

Returns: void

bindEvent(eventName, handler)

Bind a specific event.

Parameters:

Returns: void

unbindEvent(eventName)

Unbind a specific event.

Parameters:

Returns: void


EventHandlers

Event handler implementations. Typically used internally by ScreenGridLayerGL.

Import

import { EventHandlers } from 'screengrid';

Static Methods

handleHover(event, cellQueryEngine, onHover)

Handle hover events.

Parameters:

Returns: void

handleClick(event, cellQueryEngine, onClick)

Handle click events.

Parameters:

Returns: void

handleZoom(map, config, onZoom)

Handle zoom events.

Parameters:

Returns: void

handleMove(onMove)

Handle move events.

Parameters:

Returns: void


Data Normalization and Aggregation Procedure

Understanding how ScreenGrid processes data is crucial for effective visualization. This section explains the complete pipeline from raw data to rendered cells.

Overview

The library processes data through three main stages:

  1. Projection: Geographic coordinates → Screen coordinates
  2. Aggregation: Screen points → Grid cells (summing weights)
  3. Normalization: Raw cell values → Normalized values (0-1) for rendering

Stage 1: Projection

Purpose: Transform geographic coordinates [lng, lat] to screen pixel coordinates {x, y}.

Process:

// For each data point:
const [lng, lat] = getPosition(dataPoint);
const screenPoint = map.project([lng, lat]);
const weight = getWeight(dataPoint);

// Result: {x: screenPoint.x, y: screenPoint.y, w: weight}

Example:

// Input data:
[
  { coordinates: [-122.4, 37.74], population: 1000 },
  { coordinates: [-122.5, 37.75], population: 2000 }
]

// After projection (example screen coordinates):
[
  { x: 400, y: 300, w: 1000 },
  { x: 450, y: 350, w: 2000 }
]

Key Points:


Stage 2: Aggregation

Purpose: Assign projected points to grid cells and sum their weights.

Algorithm:

  1. Calculate Grid Dimensions:
    const cols = Math.ceil(canvasWidth / cellSizePixels);
    const rows = Math.ceil(canvasHeight / cellSizePixels);
    // Example: 800px width, 50px cells → 16 columns
    
  2. Determine Cell Assignment:
    // For each projected point {x, y, w}:
    const col = Math.floor(x / cellSizePixels);
    const row = Math.floor(y / cellSizePixels);
    const cellIndex = row * cols + col;
    
  3. Sum Weights:
    // All points in the same cell have their weights summed:
    grid[cellIndex] += weight;
       
    // Original data is also stored:
    cellData[cellIndex].push({
      data: originalDataPoint,
      weight: weight,
      projectedX: x,
      projectedY: y
    });
    

Mathematical Example:

Given:

Projected points:

Point A: {x: 75, y: 125, w: 10}   → Cell [1, 2]
Point B: {x: 80, y: 130, w: 5}    → Cell [1, 2]  (same cell!)
Point C: {x: 200, y: 300, w: 20}  → Cell [4, 6]

Result:

grid[1 * 16 + 2] = 10 + 5 = 15  // Cell [1, 2]
grid[4 * 16 + 6] = 20           // Cell [4, 6]

Aggregation Function:

The library uses summation as the aggregation function:

cellValue = Σ(weights of all points in cell)

Why Summation?

Custom Aggregation:

If you need different aggregation (average, max, etc.), you can:

  1. Pre-process data before passing to ScreenGrid:
    // Convert to per-cell data
    const aggregatedData = preAggregate(data);
    // Then use getWeight to return 1 for each aggregated cell
    
  2. Post-process in onDrawCell:
    onDrawCell: (ctx, x, y, normVal, cellInfo) => {
      const { cellData } = cellInfo;
      // Calculate average instead of sum
      const avg = cellData.reduce((sum, item) => sum + item.data.value, 0) 
                 / cellData.length;
      // Use avg for visualization
    }
    

Stage 3: Normalization

Purpose: Convert raw aggregated cell values to normalized range (0-1) for consistent rendering.

Process:

  1. Find Maximum Value:
    const maxValue = Math.max(...grid);
    // Example: grid = [0, 5, 10, 15, 20, 0, ...]
    // maxValue = 20
    
  2. Normalize Each Cell:
    // For each cell with value > 0:
    const normalizedValue = cellValue / maxValue;
    // Cell with value 20 → normalizedValue = 1.0
    // Cell with value 10 → normalizedValue = 0.5
    // Cell with value 5  → normalizedValue = 0.25
    

Important: Only cells with value > 0 are normalized. Empty cells (value = 0) are not rendered.

Example Normalization:

Raw Grid Values:
[0, 5, 10, 15, 20, 0, 8, 12]

Max Value: 20

Normalized Values:
[0, 0.25, 0.5, 0.75, 1.0, 0, 0.4, 0.6]

Where Normalization is Used:

  1. Color Rendering:
    // colorScale receives normalized value (0-1)
    const [r, g, b, a] = colorScale(normalizedValue);
    // Example: normalizedValue = 0.5 → colorScale(0.5) → [127, 50, 100, 200]
    
  2. Glyph Rendering:
    // onDrawCell receives normalized value
    onDrawCell: (ctx, x, y, normalizedValue, cellInfo) => {
      // Use normalizedValue for size, opacity, etc.
      const radius = glyphRadius * normalizedValue;
    }
    

Complete Example

Let’s trace a complete example from data to rendering:

Input Data:

const data = [
  { coordinates: [-122.4, 37.74], population: 1000 },
  { coordinates: [-122.4, 37.74], population: 500 },  // Same location
  { coordinates: [-122.5, 37.75], population: 2000 }
];

Configuration:

const layer = new ScreenGridLayerGL({
  data: data,
  getPosition: (d) => d.coordinates,
  getWeight: (d) => d.population,
  cellSizePixels: 50
});

Step-by-Step Processing:

  1. Projection (geographic → screen):
    Point 1: [-122.4, 37.74] → {x: 400, y: 300, w: 1000}
    Point 2: [-122.4, 37.74] → {x: 400, y: 300, w: 500}   // Same screen position
    Point 3: [-122.5, 37.75] → {x: 450, y: 350, w: 2000}
    
  2. Aggregation (assign to cells):
    Canvas: 800×600px, Cell size: 50px
    Grid: 16×12 cells
       
    Point 1: {x: 400, y: 300} → Cell [8, 6]
    Point 2: {x: 400, y: 300} → Cell [8, 6]  // Same cell!
    Point 3: {x: 450, y: 350} → Cell [9, 7]
       
    grid[8 * 16 + 6] = 1000 + 500 = 1500
    grid[9 * 16 + 7] = 2000
    
  3. Normalization (for rendering):
    maxValue = Math.max(1500, 2000) = 2000
       
    Cell [8, 6]: normalizedValue = 1500 / 2000 = 0.75
    Cell [9, 7]: normalizedValue = 2000 / 2000 = 1.0
    
  4. Rendering:
    // Cell [8, 6]:
    colorScale(0.75)  [191, 25, 50, 200]  // Example color
       
    // Cell [9, 7]:
    colorScale(1.0)  [255, 100, 200, 200]  // Maximum intensity
    

Key Behaviors

Empty Cells

Cells with no data points have value = 0 and are not rendered.

// Cell contains no points
grid[index] = 0  // Not drawn, skipped in render loop

Multiple Points in Same Cell

When multiple points fall in the same cell, their weights are summed:

// 3 points in same cell with weights [10, 5, 15]
cellValue = 10 + 5 + 15 = 30

Normalization Range

Normalized values are always in range [0, 1]:

Important: The normalization is relative to the current grid, not global data. When you zoom or filter data, the maximum may change, and normalization adjusts accordingly.

Weight Function Impact

The getWeight function directly affects aggregation:

// Different weight functions produce different aggregations:

// Sum of values
getWeight: (d) => d.value
// Result: Cell value = sum of all values in cell

// Count (each point = 1)
getWeight: () => 1
// Result: Cell value = number of points in cell

// Custom calculation
getWeight: (d) => d.population * d.density
// Result: Cell value = sum of (population × density) for all points

Performance Considerations

  1. Grid Size:
    • Larger cells → fewer cells → faster aggregation
    • Smaller cells → more cells → slower but more detailed
  2. Data Size:
    • More points → longer projection/aggregation time
    • Consider filtering data based on zoom level
  3. Normalization:
    • Calculated once per render
    • Requires finding max value: O(n) where n = number of cells

Advanced: Custom Normalization

For custom normalization strategies, you can override in onDrawCell:

onDrawCell: (ctx, x, y, normVal, cellInfo) => {
  const { cellData, value } = cellInfo;
  
  // Use raw value instead of normalized
  const customNormalized = value / customMaxValue;
  
  // Or use percentile-based normalization
  const percentile = calculatePercentile(value, allValues);
  
  // Then use for visualization
}


Version

This API reference is for ScreenGrid Library v2.2.0+