Skip to content

Commit 6955769

Browse files
committed
update README with plugin explanation
1 parent 81f83a2 commit 6955769

File tree

3 files changed

+179
-21
lines changed

3 files changed

+179
-21
lines changed

README.md

Lines changed: 146 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
A GPU/Canvas hybrid Screen-Space Grid Aggregation library for MapLibre GL JS. This library provides efficient real-time aggregation of point data into screen-space grids with customizable styling, interactive features, and advanced glyph drawing capabilities.
66

7-
This library is inspired by Aidan Slingsby's Gridded Glyphmaps and deck.gl's `ScreenGridLayer` but is built from the ground up with a modular architecture, focusing on performance, flexibility, and ease of use.
7+
This library is inspired by Aidan Slingsby's [Gridded Glyphmaps](https://openaccess.city.ac.uk/id/eprint/31115/) and borrows some basic concepts from deck.gl's [`ScreenGridLayer`](https://deck.gl/docs/api-reference/aggregation-layers/screen-grid-layer), but is built from the ground up with a modular architecture, focusing on performance, flexibility, and ease of use, particularly for MaplibreGL ecosystem.
88

99
![](./screengrid.png)
1010

@@ -14,6 +14,8 @@ This library is inspired by Aidan Slingsby's Gridded Glyphmaps and deck.gl's `Sc
1414
- **Customizable Styling**: Flexible color scales and cell sizing
1515
- **Interactive Events**: Hover and click handlers for grid cells
1616
- **Glyph Drawing**: Custom glyph rendering with Canvas 2D for advanced visualizations
17+
- **Plugin Ecosystem**: Reusable, named glyph plugins with registry system, lifecycle hooks, and legend integration
18+
- **Built-in Plugins**: Four ready-to-use plugins (`circle`, `bar`, `pie`, `heatmap`) plus utilities for custom plugins
1719
- **MapLibre Integration**: Seamless integration with MapLibre GL JS
1820
- **Performance Optimized**: Uses Canvas 2D rendering for optimal performance
1921
- **Responsive Design**: Automatically adjusts to map viewport changes
@@ -180,7 +182,7 @@ const maplibregl = require('maplibre-gl');
180182
`maplibre-gl` is a peer dependency and is not bundled. In UMD builds, it is expected as a global `maplibregl`.
181183
## 🎨 Glyph Drawing
182184

183-
The library supports custom glyph drawing through the `onDrawCell` callback. This enables rich multivariate visualizations including time series, categorical breakdowns, and complex relationships.
185+
The library supports custom glyph drawing through the `onDrawCell` callback and a powerful **Plugin Ecosystem** for reusable glyph visualizations. This enables rich multivariate visualizations including time series, categorical breakdowns, and complex relationships.
184186

185187
📖 **📚 Comprehensive Guide**: See [docs/GLYPH_DRAWING_GUIDE.md](./docs/GLYPH_DRAWING_GUIDE.md) for detailed documentation on:
186188
- All built-in glyph utilities (8 types including time series)
@@ -189,7 +191,7 @@ The library supports custom glyph drawing through the `onDrawCell` callback. Thi
189191
- Time series and spatio-temporal visualization
190192
- Advanced patterns and best practices
191193

192-
### Quick Example
194+
### Quick Example: Using onDrawCell
193195

194196
```javascript
195197
const gridLayer = new ScreenGridLayerGL({
@@ -219,6 +221,115 @@ const gridLayer = new ScreenGridLayerGL({
219221
});
220222
```
221223

224+
## 🔌 Plugin Ecosystem
225+
226+
ScreenGrid includes a **Plugin Ecosystem** that allows you to create reusable, named glyph visualizations. This system provides:
227+
228+
- **Built-in Plugins**: Four ready-to-use plugins (`circle`, `bar`, `pie`, `heatmap`)
229+
- **Plugin Registry**: Register custom plugins by name for reuse across multiple layers
230+
- **Lifecycle Hooks**: Support for initialization and cleanup
231+
- **Legend Integration**: Automatic legend generation for plugins
232+
- **Backward Compatible**: Existing `onDrawCell` callbacks work with highest precedence
233+
234+
📖 **📚 Full Documentation**: See [docs/PLUGIN_GLYPH_ECOSYSTEM.md](./docs/PLUGIN_GLYPH_ECOSYSTEM.md) for comprehensive plugin documentation, API reference, and usage patterns.
235+
236+
### Using Built-in Plugins
237+
238+
```javascript
239+
import { ScreenGridLayerGL } from 'screengrid';
240+
241+
// Use a built-in plugin
242+
const layer = new ScreenGridLayerGL({
243+
data,
244+
getPosition: (d) => d.coordinates,
245+
glyph: 'circle', // Built-in plugin name
246+
glyphConfig: {
247+
radius: 15,
248+
color: '#3498db',
249+
alpha: 0.9
250+
},
251+
enableGlyphs: true
252+
});
253+
```
254+
255+
### Available Built-in Plugins
256+
257+
1. **`circle`** - Simple filled circle with customizable size, color, and opacity
258+
2. **`bar`** - Horizontal bar chart showing multiple values side-by-side
259+
3. **`pie`** - Pie chart showing proportional distribution of values
260+
4. **`heatmap`** - Circle with color intensity representing normalized values
261+
262+
### Creating Custom Plugins
263+
264+
```javascript
265+
import { ScreenGridLayerGL, GlyphRegistry } from 'screengrid';
266+
267+
// Define a custom plugin
268+
const MyCustomGlyph = {
269+
draw(ctx, x, y, normalizedValue, cellInfo, config) {
270+
const radius = config.radius || cellInfo.glyphRadius;
271+
ctx.fillStyle = config.color || '#3498db';
272+
ctx.beginPath();
273+
ctx.arc(x, y, radius, 0, 2 * Math.PI);
274+
ctx.fill();
275+
},
276+
277+
// Optional: initialization hook
278+
init({ layer, config }) {
279+
console.log(`Initializing plugin for layer ${layer.id}`);
280+
return {
281+
destroy() {
282+
console.log('Cleaning up plugin');
283+
}
284+
};
285+
},
286+
287+
// Optional: legend support
288+
getLegend(gridData, config) {
289+
return {
290+
type: 'custom',
291+
title: 'My Visualization',
292+
items: [
293+
{ label: 'Category A', color: '#ff0000' },
294+
{ label: 'Category B', color: '#00ff00' }
295+
]
296+
};
297+
}
298+
};
299+
300+
// Register the plugin
301+
GlyphRegistry.register('myGlyph', MyCustomGlyph);
302+
303+
// Use the registered plugin
304+
const layer = new ScreenGridLayerGL({
305+
data,
306+
glyph: 'myGlyph', // Use by name
307+
glyphConfig: { radius: 15, color: '#ff6600' },
308+
enableGlyphs: true
309+
});
310+
```
311+
312+
### Plugin Precedence
313+
314+
When rendering glyphs, the system uses the following precedence order (highest to lowest):
315+
316+
1. **User-provided `onDrawCell` callback** (full backward compatibility)
317+
2. **Registered plugin via `glyph` name**
318+
3. **Color-mode rendering** (no glyphs)
319+
320+
This ensures backward compatibility while allowing gradual migration to the plugin system.
321+
322+
### Plugin Example
323+
324+
See [`examples/plugin-glyph.html`](./examples/plugin-glyph.html) for a complete example demonstrating:
325+
- Custom plugin registration (`grouped-bar` plugin)
326+
- Lifecycle hooks (`init` and `destroy`)
327+
- Legend integration
328+
- Global state management for cross-cell normalization
329+
- Interactive hover effects
330+
331+
📖 **For detailed plugin API documentation**: See [docs/PLUGIN_GLYPH_ECOSYSTEM.md](./docs/PLUGIN_GLYPH_ECOSYSTEM.md)
332+
222333
## 📚 Examples
223334

224335
### Running the Examples
@@ -238,6 +349,7 @@ npx http-server -p 8000
238349
4. **Legend Example** (`examples/legend-example.html`) - Demonstrates legend functionality
239350
5. **Time Series** (`examples/timeseries.html`) - Temporal data visualization
240351
6. **Multivariate Time Series** (`examples/multivariate-timeseries.html`) - Advanced multi-attribute temporal visualization
352+
7. **Plugin Glyph** (`examples/plugin-glyph.html`) - Complete plugin ecosystem example with custom `grouped-bar` plugin, lifecycle hooks, and legend integration
241353

242354
### Example Features
243355

@@ -266,8 +378,10 @@ npx http-server -p 8000
266378
| `onAggregate` | Function | `null` | Callback when grid is aggregated |
267379
| `onHover` | Function | `null` | Callback when hovering over cells |
268380
| `onClick` | Function | `null` | Callback when clicking cells |
269-
| `onDrawCell` | Function | `null` | Callback for custom glyph drawing |
381+
| `onDrawCell` | Function | `null` | Callback for custom glyph drawing (highest precedence) |
270382
| `enableGlyphs` | boolean | `false` | Enable glyph-based rendering |
383+
| `glyph` | string | `null` | Registered plugin name to use (e.g., `'circle'`, `'bar'`, `'pie'`, `'heatmap'`) |
384+
| `glyphConfig` | object | `{}` | Configuration object passed to plugin's `draw()` method |
271385
| `glyphSize` | number | `0.8` | Size of glyphs relative to cell size |
272386
| `adaptiveCellSize` | boolean | `false` | Enable adaptive cell sizing |
273387
| `minCellSize` | number | `20` | Minimum cell size in pixels |
@@ -300,6 +414,34 @@ ScreenGridLayerGL.drawHeatmapGlyph(ctx, x, y, radius, normalizedValue, colorScal
300414
ScreenGridLayerGL.drawRadialBarGlyph(ctx, x, y, values, maxValue, maxRadius, color);
301415
```
302416

417+
### GlyphRegistry API
418+
419+
The `GlyphRegistry` manages the plugin ecosystem and provides methods for registering and managing glyph plugins:
420+
421+
```javascript
422+
import { GlyphRegistry } from 'screengrid';
423+
424+
// Register a plugin
425+
GlyphRegistry.register(name, plugin, { overwrite = false })
426+
427+
// Retrieve a plugin
428+
GlyphRegistry.get(name)
429+
430+
// Check if plugin exists
431+
GlyphRegistry.has(name)
432+
433+
// List all registered plugins
434+
GlyphRegistry.list()
435+
436+
// Unregister a plugin
437+
GlyphRegistry.unregister(name)
438+
439+
// Clear all plugins (use with caution)
440+
GlyphRegistry.clear()
441+
```
442+
443+
See [Plugin Ecosystem](#-plugin-ecosystem) section above for detailed usage examples.
444+
303445
## 📊 Legend Module
304446

305447
The library includes a powerful Legend module for automatically generating data-driven legends:

docs/PLUGIN_GLYPHS.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ Status: ✅ **Fully implemented and production-ready**. Complete implementation
1818
- Registration: `GlyphRegistry.register(name, plugin, { overwrite=false })`
1919
- Plugin shape (required/optional fields):
2020
- `draw(ctx, x, y, normalizedValue, cellInfo, config)` — required. Draw into the provided Canvas 2D context.
21-
- `init?(opts)` — optional. Called by a layer if the layer wants to invoke initialization. (Not used in MVP.)
22-
- `destroy?()` — optional. Called on layer removal if used.
23-
- `getLegend?(cellInfo)` — optional. Return legend entries for the cell.
21+
- `init?({ layer, config })` — optional. Called when a layer using this plugin is created. Can return an object with a `destroy()` method for cleanup.
22+
- `destroy?({ layer })` — optional. Called on layer removal if used (or if `init()` didn't return a destroy method).
23+
- `getLegend?(gridData, config)` — optional. Return legend entries for the plugin visualization.
2424

2525
## Layer integration
2626

@@ -40,7 +40,7 @@ Implementation detail: the layer constructs a wrapper `onDrawCell` function from
4040
Register a glyph in your app startup code:
4141

4242
```javascript
43-
import { GlyphRegistry } from 'screengrid/src/glyphs/GlyphRegistry.js';
43+
import { GlyphRegistry } from 'screengrid';
4444

4545
GlyphRegistry.register('myGauge', {
4646
draw(ctx, x, y, normalizedValue, cellInfo, config) {

docs/PLUGIN_GLYPH_ECOSYSTEM.md

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -244,43 +244,59 @@ Custom plugins can be created by implementing the plugin interface. The ecosyste
244244

245245
### `grouped-bar` — Grouped Bar Chart Plugin
246246

247-
**Location**: `examples/plugin-glyph.html` (lines 203-384)
247+
**Location**: `examples/plugin-glyph.html` (lines 203-391)
248248

249-
**Description**: A sophisticated custom plugin that visualizes carbon savings vs. cost comparison across multiple technology categories (ASHP, EV, PV) and cost types (Labour, Material).
249+
**Description**: A sophisticated custom plugin that visualizes parking capacity data comparing bike racks vs. parking spaces using grouped bars. This plugin demonstrates advanced plugin features including global state management, cross-cell normalization, and interactive hover effects.
250250

251251
**Features**:
252-
- Multivariate data aggregation (carbon and cost values)
253-
- Category-specific color schemes
254-
- Dual-axis visualization (carbon for technologies, cost for expenses)
252+
- Multivariate data aggregation (racks and spaces values)
253+
- Category-specific color schemes (blue for racks, green for spaces)
254+
- Global normalization for cross-cell comparison
255255
- Integrated legend support via `getLegend()` method
256256
- Lifecycle hooks (`init()` and `destroy()`)
257+
- Interactive hover effects with comparison outlines
258+
- Global statistics tracking across all cells
257259

258260
**Key Implementation Details**:
259261

260262
```javascript
261263
const GroupedBarGlyph = {
264+
// Global stats for normalization across all cells
265+
globalStats: {
266+
maxRacks: 0,
267+
maxSpaces: 0,
268+
maxTotal: 0,
269+
initialized: false
270+
},
271+
262272
init({ layer, config } = {}) {
263-
// Optional initialization per layer instance
273+
// Store layer reference and reset global stats
274+
this.layerRef = layer;
275+
this.globalStats = { maxRacks: 0, maxSpaces: 0, maxTotal: 0, initialized: false };
264276
return {
265277
destroy() {
266-
// Cleanup if needed
278+
console.log('GroupedBarGlyph instance destroyed');
267279
}
268280
};
269281
},
270282

271283
draw(ctx, x, y, normalizedValue, cellInfo, config = {}) {
272-
// 1. Aggregate data from cellData
284+
// 1. Aggregate data from cellData (racks and spaces)
273285
// 2. Calculate chart dimensions
274-
// 3. Draw bars for each category
275-
// 4. Apply color schemes
286+
// 3. Draw grouped bars for each category (racks vs spaces)
287+
// 4. Apply color schemes (blue for racks, green for spaces)
288+
// 5. Show comparison outline when hovering over different cell
276289
},
277290

278291
getLegend(gridData, config = {}) {
279-
// Return legend metadata
280292
return {
281293
type: 'custom',
282-
title: 'Carbon vs Cost Comparison',
283-
items: [...]
294+
title: 'Parking Capacity',
295+
description: 'Grouped bar chart comparing bike racks and parking spaces:',
296+
items: [
297+
{ label: 'Racks', description: 'Number of bike racks (blue)', color: 'rgba(52, 152, 219, 0.85)' },
298+
{ label: 'Spaces', description: 'Number of parking spaces (green)', color: 'rgba(46, 204, 113, 0.85)' }
299+
]
284300
};
285301
}
286302
};

0 commit comments

Comments
 (0)