screengrid

ScreenGrid Library - Modular Architecture

Overview

The refactored ScreenGrid library follows clean architecture principles with separated concerns and modular design. The monolithic screengrid.js has been decomposed into focused, reusable modules.

Directory Structure

src/
├── index.js                              # Main export file
├── ScreenGridLayerGL.js                  # Orchestrator class (~220 lines)
│
├── config/
│   └── ConfigManager.js                  # Configuration management
│
├── core/
│   ├── Aggregator.js                     # Grid aggregation (pure logic)
│   ├── Projector.js                      # Coordinate projection
│   ├── CellQueryEngine.js                # Cell queries & spatial analysis
│   └── geometry/                         # Geometry placement (v2.1.0+)
│       ├── PlacementEngine.js            # Geometry to anchor conversion
│       ├── PlacementValidator.js         # Config validation
│       ├── GeometryUtils.js              # Geometry utilities
│       └── strategies/                   # Placement strategies
│           ├── CentroidStrategy.js
│           ├── PolylabelStrategy.js
│           ├── LineSampleStrategy.js
│           ├── GridGeoStrategy.js
│           ├── GridScreenStrategy.js
│           └── PointStrategy.js
│
├── canvas/
│   ├── CanvasManager.js                  # Canvas lifecycle & sizing
│   └── Renderer.js                       # Drawing logic
│
├── events/
│   ├── EventBinder.js                    # Event attachment/detachment
│   └── EventHandlers.js                  # Event handler implementations
│
├── glyphs/
│   ├── GlyphUtilities.js                 # Reusable glyph drawing functions
│   └── GlyphRegistry.js                  # Plugin registry (v2.0.0+)
│
├── aggregation/                          # Aggregation system (v2.1.0+)
│   ├── AggregationModeRegistry.js        # Mode registry
│   ├── modes/                            # Aggregation modes
│   │   ├── ScreenGridMode.js             # Rectangular grid mode
│   │   ├── ScreenHexMode.js              # Hexagonal mode (v2.2.0+)
│   │   └── index.js                      # Mode registration
│   └── functions/                        # Aggregation functions
│       ├── AggregationFunctionRegistry.js
│       ├── SumAggregation.js
│       ├── MeanAggregation.js
│       ├── CountAggregation.js
│       ├── MaxAggregation.js
│       ├── MinAggregation.js
│       └── index.js
│
├── normalization/                        # Normalization system (v2.1.0+)
│   └── functions/
│       ├── NormalizationFunctionRegistry.js
│       ├── MaxLocalNormalization.js
│       ├── MaxGlobalNormalization.js
│       ├── ZScoreNormalization.js
│       ├── PercentileNormalization.js
│       └── index.js
│
├── utils/                                # Utility modules (v2.1.0+)
│   ├── Logger.js                         # Debug logging
│   └── DataUtilities.js                  # Data processing utilities
│
└── legend/                               # Legend system (v2.0.0+)
    ├── Legend.js                         # Main legend class
    ├── LegendDataExtractor.js            # Data extraction
    └── LegendRenderers.js                # Rendering utilities

Module Descriptions

config/ConfigManager.js

Purpose: Centralized configuration management

Key Methods:

Usage:

import { ConfigManager } from 'screengrid';

const config = ConfigManager.create({
  data: myData,
  cellSizePixels: 50,
  colorScale: (v) => [255 * v, 100, 200, 200]
});

// Update config later
const updated = ConfigManager.update(config, { cellSizePixels: 75 });

Benefits:


core/Projector.js

Purpose: Transform geographic coordinates to screen space

Key Methods:

Usage:

import { Projector } from 'screengrid';

const projector = new Projector(map);
const projected = projector.project(
  data,
  (d) => d.coordinates,
  (d) => d.weight
);
// Returns: [{x, y, w}, {x, y, w}, ...]

Benefits:


core/Aggregator.js

Purpose: Pure grid aggregation logic

Key Methods:

📖 For detailed explanation of the aggregation and normalization process, see API_REFERENCE.md

Usage:

import { Aggregator } from 'screengrid';

const aggregator = new Aggregator();
const result = aggregator.aggregate(
  projectedPoints,      // [{x, y, w}, ...]
  data,                 // Original data array
  800,                  // Canvas width
  600,                  // Canvas height
  50                    // Cell size
);

// Result contains:
// {
//   grid: [0, 5, 10, ...],           // Aggregated values
//   cellData: [[], [item1, ...], ...], // Raw data per cell
//   cols: 16,
//   rows: 12,
//   ...
// }

const stats = aggregator.getStats(result);
// Returns: {totalCells, cellsWithData, maxValue, minValue, avgValue, totalValue}

Benefits:


core/CellQueryEngine.js

Purpose: Spatial queries on the aggregated grid

Key Methods:

Usage:

import { CellQueryEngine } from 'screengrid';

const engine = new CellQueryEngine(aggregationResult);

// Get cell at mouse position
const cell = engine.getCellAt({x: 100, y: 200});
// Returns: {col, row, value, cellData, x, y, cellSize, index}

// Get all cells in a region
const cells = engine.getCellsInBounds({
  minX: 0, minY: 0, maxX: 400, maxY: 300
});

// Get high-value cells
const hotspots = engine.getCellsAboveThreshold(50);

Benefits:


canvas/CanvasManager.js

Purpose: Canvas lifecycle management (create, resize, cleanup, DPI handling)

Key Methods:

Usage:

import { CanvasManager } from 'screengrid';

const canvasManager = new CanvasManager();
canvasManager.init(map);

const ctx = canvasManager.getContext();
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 50, 50);

// Automatically handles resize
// On cleanup:
canvasManager.cleanup();

Benefits:


canvas/Renderer.js

Purpose: Draw grid cells (color-based or glyph-based)

Key Methods:

Usage:

import { Renderer } from 'screengrid';

const renderer = new Renderer();

// Color-based rendering
renderer.renderColors(aggregationResult, ctx, (value) => {
  return [255 * value, 100, 200, 200]; // [r, g, b, a]
});

// Custom glyph rendering
renderer.renderGlyphs(
  aggregationResult,
  ctx,
  (ctx, x, y, normVal, cellInfo) => {
    // Draw custom visualization
    ctx.fillStyle = 'blue';
    ctx.fillRect(x - 10, y - 10, 20, 20);
  },
  0.8 // glyphSize
);

Benefits:


events/EventHandlers.js

Purpose: Event handler implementations

Key Methods:

Usage:

import { EventHandlers } from 'screengrid';

// Hover handling
EventHandlers.handleHover(
  mapLibreEvent,
  cellQueryEngine,
  ({cell, event}) => {
    console.log('Hovered cell:', cell);
  }
);

// Zoom handling with cell size adjustment
EventHandlers.handleZoom(map, config, () => {
  console.log('Map zoomed');
});

Benefits:


events/EventBinder.js

Purpose: Manage event attachment and detachment

Key Methods:

Usage:

import { EventBinder } from 'screengrid';

const binder = new EventBinder();
binder.bind(map, {
  handleHover: (e) => console.log('hover'),
  handleClick: (e) => console.log('click'),
  handleZoom: () => console.log('zoom'),
  handleMove: () => console.log('move')
});

// Later, unbind
binder.unbind();

Benefits:


glyphs/GlyphUtilities.js

Purpose: Reusable glyph drawing functions

Key Methods:

Usage:

import { GlyphUtilities } from 'screengrid';

// Use in custom glyph drawing
const onDrawCell = (ctx, x, y, normVal, cellInfo) => {
  GlyphUtilities.drawCircleGlyph(ctx, x, y, 15, '#ff0000', 0.8);
};

// Or use directly
GlyphUtilities.drawPieGlyph(ctx, 100, 100, [30, 20, 10], 20, ['red', 'green', 'blue']);

Benefits:


glyphs/GlyphRegistry.js (v2.0.0+)

Purpose: Registry for glyph plugins

Key Methods:

Usage:

import { GlyphRegistry } from 'screengrid';

// Register custom plugin
GlyphRegistry.register('myGlyph', {
  draw(ctx, x, y, normVal, cellInfo, config) {
    // Drawing logic
  }
});

// Use by name
const layer = new ScreenGridLayerGL({
  glyph: 'myGlyph',
  enableGlyphs: true
});

Benefits:


aggregation/ (v2.1.0+)

Purpose: Aggregation modes and functions system

Components:

Usage:

import { AggregationModeRegistry, AggregationFunctions } from 'screengrid';

// Use hex mode
const layer = new ScreenGridLayerGL({
  aggregationMode: 'screen-hex',
  aggregationModeConfig: { hexSize: 50 }
});

// Use custom aggregation function
const layer = new ScreenGridLayerGL({
  aggregationFunction: AggregationFunctions.mean
});

Benefits:


normalization/ (v2.1.0+)

Purpose: Normalization functions system

Components:

Usage:

import { NormalizationFunctions } from 'screengrid';

// Use z-score normalization
const layer = new ScreenGridLayerGL({
  normalizationFunction: NormalizationFunctions.zScore
});

// Use global normalization with context
const layer = new ScreenGridLayerGL({
  normalizationFunction: NormalizationFunctions.maxGlobal,
  normalizationContext: { globalMax: 1000 }
});

Benefits:


utils/ (v2.1.0+)

Purpose: Utility modules

Components:

Logger.js

Controlled debug logging:

import { Logger, setDebug } from 'screengrid';
setDebug(true);
Logger.log('Debug message');

DataUtilities.js

Data processing utilities:

import { groupBy, extractAttributes, computeStats, groupByTime } from 'screengrid';

// Group by category
const categories = groupBy(cellData, 'category');

// Extract multiple attributes
const attrs = extractAttributes(cellData, { total: w => w.weight });

// Compute statistics
const stats = computeStats(cellData);

// Group by time
const timeSeries = groupByTime(cellData, 'year', 'value');

Benefits:


legend/ (v2.0.0+)

Purpose: Legend generation system

Components:

Usage:

import { Legend } from 'screengrid';

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

Legend Types:

Benefits:


core/geometry/ (v2.1.0+)

Purpose: Geometry placement for non-point inputs

Components:

Usage:

import { PlacementEngine, PlacementValidator } from 'screengrid';

// Validate config
const isValid = PlacementValidator.validate(placementConfig);

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

Benefits:


ScreenGridLayerGL - Orchestrator

Purpose: Main class that composes all modules

Key Methods:

Usage (Unchanged from user perspective):

import { ScreenGridLayerGL } from 'screengrid';

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

map.addLayer(gridLayer);

Benefits:


Advanced Usage Examples

Using Core Modules Independently

import { Projector, Aggregator, CellQueryEngine } from 'screengrid';

// Use aggregation without rendering
const projector = new Projector(map);
const projected = projector.project(data, getPos, getWeight);

const aggregator = new Aggregator();
const result = aggregator.aggregate(projected, data, 800, 600, 50);

// Query results
const engine = new CellQueryEngine(result);
const stats = aggregator.getStats(result);

console.log(`Grid has ${stats.cellsWithData} cells with data`);

Custom Rendering

import { Renderer, GlyphUtilities } from 'screengrid';

const renderer = new Renderer();
renderer.renderGlyphs(result, ctx, (ctx, x, y, normVal, cellInfo) => {
  // Use multiple glyphs
  GlyphUtilities.drawCircleGlyph(ctx, x, y, 10, '#ff0000', normVal);
  GlyphUtilities.drawRadialBarGlyph(
    ctx, x, y,
    cellInfo.cellData.map(d => d.weight),
    Math.max(...cellInfo.cellData.map(d => d.weight)),
    15,
    '#ffffff'
  );
});

Dynamic Event Binding

import { EventBinder, EventHandlers, CellQueryEngine } from 'screengrid';

const binder = new EventBinder();
const engine = new CellQueryEngine(aggregationResult);

binder.bind(map, {
  handleHover: (e) => {
    const cell = engine.getCellAt({x: e.point.x, y: e.point.y});
    if (cell) updateTooltip(cell);
  },
  handleClick: (e) => { /* ... */ },
  handleZoom: () => { /* ... */ },
  handleMove: () => { /* ... */ }
});

Benefits of Modular Architecture

Aspect Before After
File Size 470 lines 50-120 lines per module
Single Responsibility Multiple per class One per module
Testability Hard to isolate logic Pure functions, easy to test
Reusability Tied to ScreenGridLayerGL Modules reusable independently
Maintainability Large monolith Clear, focused modules
Extensibility Hard to extend Easy to add features
Coupling High inter-dependencies Loose coupling via composition
Code Cohesion Mixed concerns High cohesion per module

Migration from Old Code

For Users of the Library

No changes needed! The public API is identical:

// This still works exactly the same
import { ScreenGridLayerGL } from 'screengrid';

const layer = new ScreenGridLayerGL(options);
map.addLayer(layer);

For Library Developers

New options for advanced usage:

// Import individual modules
import { Aggregator, Projector, CellQueryEngine } from 'screengrid';

// Use modules independently
const agg = new Aggregator();
const result = agg.aggregate(...);

Testing Strategy

Unit Tests by Module

// test/core/Aggregator.test.js
import { Aggregator } from 'screengrid';

describe('Aggregator', () => {
  it('should aggregate points correctly', () => {
    const result = Aggregator.aggregate(
      [{x: 10, y: 10, w: 1}],
      data,
      100, 100, 50
    );
    expect(result.grid[0]).toBe(1);
  });
});

Integration Tests

// test/ScreenGridLayerGL.integration.test.js
import { ScreenGridLayerGL } from 'screengrid';

describe('ScreenGridLayerGL', () => {
  it('should render grid on map', () => {
    const layer = new ScreenGridLayerGL(options);
    map.addLayer(layer);
    expect(layer.gridData).toBeDefined();
  });
});

Future Enhancements

The modular structure enables:

  1. Plugin System ✅ - Glyph plugin registry implemented (v2.0.0+)
  2. Server-side Aggregation - Use Aggregator on backend
  3. Alternative Renderers - WebGL renderer via separate module
  4. Extended Glyphs ✅ - Community plugin library (v2.0.0+)
  5. Performance Optimizations - Optimize individual modules
  6. Framework Adapters - React, Vue bindings
  7. Hexagonal Mode ✅ - Hex tessellation (v2.2.0+)
  8. Geometry Input ✅ - Non-point geometries (v2.1.0+)
  9. Legend System ✅ - Auto-generated legends (v2.0.0+)
  10. Data Utilities ✅ - Helper functions (v2.1.0+)

Summary

The refactored ScreenGrid library provides:

Modularity - Independent, composable modules
Testability - Pure functions, easy unit tests
Reusability - Use modules anywhere
Maintainability - Clear separation of concerns
Extensibility - Easy to add features
Backward Compatibility - Public API unchanged

This architecture follows SOLID principles and makes the codebase more professional and sustainable for long-term development.