Resolved

Observable notebook embed fails to process local images and shows UI clutter

D3.js
JavaScript
Frontend
Canvas
Web Workers
Data Visualization
Astro
Performance

Embedded Voronoi stippling visualization displayed attribution footer and couldn't access local portrait image for processing | Required custom implementation to replace iframe embed with canvas-based solution

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:

  1. 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).
  2. 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 requestAnimationFrame for 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: Added d3-delaunay dependency for Voronoi calculations.