Issue
The @mbostock/voronoi-stippling Observable notebook embed failed to render stippling points due to cross-origin restrictions preventing access to the local portrait image (portrait.jpg), and included an unwanted attribution footer that cluttered the UI. The iframe-based solution provided no control over rendering behavior.
Culprit
The iframe embed approach introduced two critical limitations:
- Cross-origin access restrictions: The notebook could not load local image assets, causing the stippling algorithm to receive no pixel data (resulting in no dots rendered).
- Unremovable UI clutter: Observable’s default UI framework injected an attribution footer that could not be modified without altering the source notebook.
Solution
To resolve these issues, a custom canvas-based implementation was developed:
- Web Worker Offloading: The computationally intensive Lloyd’s relaxation algorithm (80 iterations) was moved to a Web Worker to prevent UI freezing.
- Image Processing: The local portrait was loaded as a grayscale image, with luminance values inverted to create density weights for stippling.
- Progressive Rendering: Intermediate states were rendered using
requestAnimationFramefor smooth performance, with a final output of 20,000+ dots at 0.8px radius. - High-DPI Support: Device pixel ratio adjustments ensured crisp rendering on high-resolution displays.
Key Files Modified:
public/voronoi-worker.js: Dedicated Web Worker for algorithm execution.src/pages/indexcopy.astro: Replaced iframe with custom canvas rendering logic.package.json: Addedd3-delaunaydependency for Voronoi calculations.