github jasondavies/d3-cloud v2.0.0

2 hours ago

d3-cloud 2.0 is a rewrite with a new class-based API: prepare sprites with getSprite(), place them with place(), and erase or clear them. Layouts can grow beyond size(), and the package is ESM-only.

npm install d3-cloud

Version 2 is not a drop-in upgrade; see the migration guide. Dependencies on ^1.x keep resolving to 1.x, and 1.x patches continue on the 1.x branch.

Breaking

  • Set the minimum Node.js version to 20, matching the CI test matrix.
  • Switched the package to ESM-only publishing and removed the old Browserify / CommonJS distribution shape.
  • Removed the UMD/script-tag build, the d3.layout.cloud global, and the d3-dispatch dependency. Browser scripts must use ESM imports.
  • Changed the default rotation from random multiples of 30 degrees to zero, and the default font size from sqrt(value) to 1. Set styling explicitly.
  • Input word objects are no longer mutated. Placement returns separate plain words, and callers control the word ordering.
  • Custom data is carried in a single datum option, stored by reference on sprites and placed words. Other option fields are ignored rather than copied onto sprites and results, so built-in field names are no longer reserved.
  • Replaced the old event-driven factory API (start(), stop(), on(), words(), timeInterval(), accessor-based text/font configuration) with the class-based CloudLayout / CloudSprite API.
  • Replaced the old spiral() API with strategy().
  • Moved source files under src/ and renamed the browser bundle to build/d3-cloud.js.
  • Changed placement configuration so size([width, height]) defines the centered layout box, while overflow(true | false) controls whether placement may extend beyond that box.
  • Text padding now uses round stroke joins instead of the canvas default miter joins, so padding extends exactly padding pixels from the glyph outline rather than up to ten times that at sharp corners. Padded masks, and layouts that depend on them, differ from 1.x.
  • The rectangular strategy now visits every point of a 4-pixel grid in rings shaped to the aspect ratio, instead of stepping four pixels times the aspect ratio along the longer axis. Rectangular layouts differ from 1.x, and pack densely at any aspect ratio.
  • Both built-in spirals stretch to the aspect ratio only up to 64:1. Beyond that they barely moved along the shorter axis, so a search starting in a blocked column or row could take minutes and then fail. Layouts with more extreme aspect ratios differ from 1.x.
  • Invalid arguments throw instead of being silently replaced: non-finite coordinates, sizes, rotations, padding, image dimensions and strategy candidates; negative padding; size() dimensions outside 0–16,777,216; blockSize() outside 1–4096; a random() that is not a function or returns anything but a number in [0, 1); fonts the canvas rejects; canvases without a 2d context; and canvases whose getImageData() returns more than one pixel per canvas unit, as some old high-density WebKit canvases did.

Added

  • Added CloudLayout.getSprite(...) for explicit sprite preparation and place() for caller-controlled placement. Sprites are immutable masks with no position, so one sprite can be placed any number of times; they can only be created by getSprite().
  • Added built-in placement strategies including noneStrategy for single-attempt placement.
  • Added erase() for erasing a word returned by place().
  • Added image sprite extraction from alpha masks, including optional image resizing with options.width and options.height. Loaded images without intrinsic dimensions, such as SVGs with only a viewBox, are drawn at an explicit width and height.
  • Added sparse packed block placement for effectively unbounded layouts.
  • Added browser examples for ESM loading, image sprites, and masked layout.
  • Added a node:test regression suite and a standalone benchmark script.

Changed

  • Placement first tries the seed, then searches from the nearest position where the sprite could touch the cloud (and, with overflow(false), fit in the box), skipping positions where it could not without a collision check. Distant seeds cost no extra time, and small clouds are not stepped over.
  • Search limits scale with the number of positions where the sprite could be placed, rather than the board diagonal, and apply equally to built-in and custom strategies. Strategies that repeat points still stop; one that moves away along one axis and never returns stops after a run proportional to the area it could have searched.
  • Placement uses a private copy of each sprite's mask and accepts only sprites created by getSprite(), so changing or forging a sprite cannot corrupt a layout.
  • Text rasters are sized from measured ink bounds, including italic overhang and accents, and redrawn larger if the ink reaches the raster edge, as some canvases under-report ink bounds. Text is rasterized with textAlign = "center", matching how placed words should be rendered.
  • Sprite geometry is trimmed to occupied pixels, independently of the packed row alignment, so bounded layouts do not reject words because of raster slack. Sprite extraction reads back only the raster and stores masks as Uint32Arrays.
  • Layouts may share one scratch canvas. A failed draw or readback, for example from a tainted canvas, makes every layout using it ask its factory for a canvas again; a rejected font does not.
  • getSprite() throws if called while a sprite is being drawn to the same canvas, and clear() and blockSize() throw while place() is searching, rather than corrupting masks or occupancy.
  • The package includes TypeScript declarations, for both the package entry and build/d3-cloud.js. datum is typed generically and inferred from getSprite() options. Image sources may be images, videos, canvases, or ImageBitmaps, including node-canvas, and placed words keep the source type, so word.image can be drawn directly.
  • Documented the rendering contract: image coordinates mark the sprite center, and text is rendered from x and y with a centered horizontal anchor.
  • Rewrote the examples for the ESM build.

Known limitations

  • The archimedean strategy samples points about a tenth of the radius apart and stretches its spiral to the aspect ratio, so in fixed boxes at extreme aspect ratios (around 20:1 and beyond) it places fewer words than at moderate ratios. Use the rectangular strategy for such boxes.
  • The rectangular strategy samples a finer grid than in 1.x, so it can be slower in wide layouts, in exchange for denser packing.

Don't miss a new d3-cloud release

NewReleases is sending notifications on new releases.