Open jgaffuri opened 1 month ago
Some advice from Mr. Chat:
Here are several strategies to optimize the rendering logic for your gridviz
library:
r2
(resolution * 0.5
) and z
(geoCanvas.view.z
), are recomputed multiple times but can be calculated once outside loops.z
), cache those values before entering the loop.cells.filter(this.filter)
) and sort operations outside the main rendering loop. This minimizes the number of iterations for rendering.geoCanvas.ctx.fillStyle
once if many cells share the same color).Instead of setting styles (fillStyle
, etc.) or transformations repeatedly within the loop, group cells by shared attributes like color, shape, and size and render them in batches.
Offload heavy computations (e.g., filtering, sorting, or calculating offsets) to a Web Worker. For example:
cells
data to the worker.[{x, y, size, color, shape}, ...]
) to the main thread for rendering.This frees up the main thread for UI updates and rendering, reducing perceived lag.
Wrap rendering logic in requestAnimationFrame
to ensure it aligns with the browser's refresh rate, minimizing dropped frames and enhancing visual smoothness:
function renderFrame() {
requestAnimationFrame(() => {
gridviz.redraw(); // Call your redraw logic
});
}
Minimize state changes like ctx.globalAlpha
, ctx.globalCompositeOperation
, and transformations (setCanvasTransform
). Group cells with similar properties to reduce the number of changes:
ctx.save()
and ctx.restore()
sparingly, as each call involves a performance cost.Instead of recalculating positions and offsets for every cell:
Path2D
objects for repeated shapes (like circles or triangles). Example:
const circlePath = new Path2D();
circlePath.arc(0, 0, radius, 0, Math.PI * 2);
geoCanvas.ctx.fill(circlePath);
if (layer.visible && !layer.visible(z)) continue
efficiently excludes non-relevant layers.Use Chrome DevTools or similar tools to identify rendering bottlenecks:
If datasets are massive and 2D Canvas becomes a bottleneck:
Divide the rendering into chunks to avoid blocking the main thread:
Here’s a basic outline for combining Web Workers and canvas rendering:
const worker = new Worker('dataProcessor.js');
worker.onmessage = (event) => {
const processedData = event.data; // Lightweight rendering instructions
renderCells(processedData); // Render on canvas
};
worker.postMessage({ cells, resolution, zoomLevel });
Web Worker (dataProcessor.js)
onmessage = (event) => {
const { cells, resolution, zoomLevel } = event.data;
// Perform filtering and offset calculations
const processed = cells.map(c => ({
x: c.x,
y: c.y,
color: computeColor(c, zoomLevel),
size: computeSize(c, resolution, zoomLevel),
shape: computeShape(c, zoomLevel),
})).filter(c => c.color !== 'none');
postMessage(processed);
};
This approach distributes work across threads and accelerates rendering on the main thread.
Important: workers can't directly accept functions as arguments. This means that dataset.preprocess cannot be passed to a web worker.
Some devTools performance results using TiledGrid with ShapeColorSizeStyle:
ctx.fillStyle = col
appears to be slow. Maybe we should look into grouping cells by color in order to reduce calls to fillStyle.this.filter
should not be running if the user hasnt defined a filter. I have changed the default to: this.filter = opts.filter || undefined
.Some useful guidance: https://web.dev/articles/optimize-long-tasks?utm_source=devtools
Consider: Minimizing heavy JavaScript computations by using Web Workers or async/await. Deferring or lazily loading non-essential resources to reduce initial load. Breaking up large tasks into smaller, asynchronous tasks using requestAnimationFrame or setTimeout. Optimizing DOM manipulation by reducing unnecessary changes and batch updates.