ScreenGrid provides minimal, flexible utility functions for extracting and processing cellData. These utilities help with common patterns mentioned in the Cartography & Multivariate Design guide.
The data utilities are pure functions that help you:
All utilities are exported from the main module:
import { groupBy, extractAttributes, computeStats, groupByTime } from 'screengrid';
Groups cellData by a key and aggregates values. Perfect for category breakdowns (pie charts, bar charts).
groupBy(cellData, keyExtractor, valueExtractor, aggregator)
cellData (Array): Array of {data, weight, projectedX, projectedY} objectskeyExtractor (Function |
string): Function to extract key from data, or property name |
valueExtractor (Function |
string, optional): Function to extract value, or property name (default: weight) |
aggregator (Function, optional): Aggregation function (default: sum)Map - Map of key -> aggregated value
import { groupBy } from 'screengrid';
// In your onDrawCell callback
onDrawCell: (ctx, x, y, normVal, cellInfo) => {
const { cellData } = cellInfo;
// Group by category property and sum values
const categories = groupBy(cellData, 'category', 'value');
// Returns: Map { 'A' => 100, 'B' => 200, 'C' => 50 }
// Group by custom extractor function
const types = groupBy(
cellData,
(item) => item.data.type || 'unknown',
(item) => item.weight
);
// Group with custom aggregator (mean instead of sum)
const avgByCategory = groupBy(
cellData,
'category',
'value',
(values) => values.reduce((sum, v) => sum + v, 0) / values.length
);
// Use for pie chart
const values = Array.from(categories.values());
const labels = Array.from(categories.keys());
GlyphUtilities.drawPieGlyph(ctx, x, y, values, radius, colors);
}
Extracts multiple attributes from cellData in a single call. Useful for multi-attribute aggregation.
extractAttributes(cellData, extractors)
cellData (Array): Array of {data, weight, ...} objectsextractors (Object): Object mapping attribute names to extractor functions or property namesObject - Object with extracted attributes aggregated (sum by default for numeric values)
import { extractAttributes } from 'screengrid';
onDrawCell: (ctx, x, y, normVal, cellInfo) => {
const { cellData } = cellInfo;
// Extract multiple attributes
const attrs = extractAttributes(cellData, {
total: (item) => item.weight,
count: () => 1,
avg: (item) => item.data.value,
category: (item) => item.data.category // Collects all values
});
// attrs = {
// total: 150, // Sum of weights
// count: 10, // Number of items
// avg: 15, // Sum of values
// category: ['A', 'B', 'A', ...] // Array of all categories
// }
// Use for multi-attribute visualization
const barValues = [attrs.total, attrs.count, attrs.avg];
GlyphUtilities.drawBarGlyph(ctx, x, y, barValues, maxValue, cellSize);
}
Computes basic statistics from cellData values. Perfect for uncertainty encoding (error bars, confidence intervals).
computeStats(cellData, valueExtractor)
cellData (Array): Array of {data, weight, ...} objectsvalueExtractor (Function |
string, optional): Function to extract value, or property name (default: weight) |
Object - Statistics: {mean, std, min, max, count, sum}
import { computeStats } from 'screengrid';
onDrawCell: (ctx, x, y, normVal, cellInfo) => {
const { cellData } = cellInfo;
// Compute statistics from weights
const stats = computeStats(cellData);
// stats = { mean: 15.5, std: 3.2, min: 10, max: 20, count: 10, sum: 155 }
// Compute statistics from a specific property
const tempStats = computeStats(cellData, 'temperature');
// Use for error bar glyph
const mean = stats.mean;
const error = stats.std;
// Draw bar with error bars
ctx.fillStyle = '#3498db';
ctx.fillRect(x - 5, y - mean/2, 10, mean);
// Error bars
ctx.strokeStyle = '#e74c3c';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(x, y - mean/2 - error);
ctx.lineTo(x, y - mean/2 + error);
ctx.stroke();
}
Groups cellData by time periods. Essential for temporal glyphs (time series, helix glyphs).
groupByTime(cellData, timeExtractor, valueExtractor, period, aggregator)
cellData (Array): Array of {data, weight, ...} objectstimeExtractor (Function |
string): Function to extract time from data, or property name |
valueExtractor (Function |
string, optional): Function to extract value, or property name (default: weight) |
period (string, optional): Time period: 'year', 'month', 'day', 'hour' (default: 'year')aggregator (Function, optional): Aggregation function per period (default: sum)Array - Array of {time, value} objects sorted by time
import { groupByTime } from 'screengrid';
onDrawCell: (ctx, x, y, normVal, cellInfo) => {
const { cellData } = cellInfo;
// Group by year and sum values
const timeSeries = groupByTime(cellData, 'year', 'value', 'year');
// Returns: [{time: 2024, value: 100}, {time: 2025, value: 150}, ...]
// Group by month
const monthly = groupByTime(cellData, 'date', 'temperature', 'month');
// Group by custom time extractor
const hourly = groupByTime(
cellData,
(item) => new Date(item.data.timestamp),
(item) => item.data.count,
'hour'
);
// Use with built-in time series glyph utility
GlyphUtilities.drawTimeSeriesGlyph(
ctx, x, y,
timeSeries,
cellSize,
{ lineColor: '#2ecc71', showArea: true }
);
}
import { groupBy } from 'screengrid';
const categories = groupBy(cellData, 'category', 'value');
const values = Array.from(categories.values());
const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1'];
GlyphUtilities.drawPieGlyph(ctx, x, y, values, radius, colors);
import { extractAttributes } from 'screengrid';
const attrs = extractAttributes(cellData, {
total: (item) => item.weight,
count: () => 1,
avg: (item) => item.data.value
});
// Use attrs.total, attrs.count, attrs.avg in your glyph
import { computeStats } from 'screengrid';
const stats = computeStats(cellData, 'temperature');
// Draw mean ± std as error bars
drawErrorBarGlyph(ctx, x, y, stats.mean, stats.std);
import { groupByTime } from 'screengrid';
const timeSeries = groupByTime(cellData, 'date', 'value', 'year');
GlyphUtilities.drawTimeSeriesGlyph(ctx, x, y, timeSeries, cellSize);
import { groupByTime, computeStats } from 'screengrid';
// Group by time, then compute stats for each period
const timeSeries = groupByTime(cellData, 'year', 'temperature', 'year');
const stats = timeSeries.map(period => ({
time: period.time,
stats: computeStats(
cellData.filter(item => {
const year = typeof item.data.year === 'number'
? item.data.year
: new Date(item.data.date).getFullYear();
return year === period.time;
}),
'temperature'
)
}));
// Now you have mean ± std for each time period
These utilities simplify common patterns. Compare:
Before (manual):
const yearData = {};
cellData.forEach(item => {
const year = item.data.year;
const value = item.data.value;
if (!yearData[year]) {
yearData[year] = { total: 0, count: 0 };
}
yearData[year].total += value;
yearData[year].count += 1;
});
const timeSeries = Object.values(yearData).map(d => ({
year: d.year,
value: d.total
}));
After (with utility):
const timeSeries = groupByTime(cellData, 'year', 'value', 'year');
The utility handles:
Use property names for simple cases: groupBy(cellData, 'category') is cleaner than groupBy(cellData, (item) => item.data.category)
Combine utilities: Use groupByTime then computeStats for statistical time series
Handle empty data: Always check if cellData is empty before using utilities
Custom aggregators: Provide custom aggregators when you need mean, max, or other functions instead of sum
Type safety: Utilities filter out null/undefined/NaN values automatically, but ensure your extractors return the expected types