npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

d3-voronoi-map-tween

v0.0.1

Published

D3 plugin allowing to animate back and forth between two d3-voronoi-map.

Downloads

5

Readme

WORK IN PROGRESS

d3-voronoi-map-tween

This D3 plugin allows to animate back and forth between two d3-voronoi-map.

Considering the data coming from either the starting data set or the ending data set, each single datum has a corresponding cell in the starting Voronoï map and another in the ending Voronoï map. The objective of the plugin is to provide a way (i.e. an interpolator function) to smoothly interpolate between the starting cell and the ending cell of each data.

To do so, the algorithm does not interpolate polygons associated to each single datum in order to no have a mess of overlapping cells (cf. this easy-but-unsatisfying attempt). But it rather interpolates the characteristics of the sites producing each polygon and then compute a Voronoï map of these interpolated sites (thanks to d3-weighted-voronoi). It also takes care of cells found only in the starting Voronoï map (data only available in the starting data set) or found only in the ending Voronoï map (data only in the ending data set).

Because a picture is worth a thousand words:

animation

In this animation:

  • blue cells are cells available in both the starting and ending Voronoï maps, i.e. data both in the starting and ending sets; these cells smoothly evolve in order to reflect their starting and ending weights, which may be distinct
  • red cells are cells available only in the starting Voronoï map, i.e. data only in the starting data set; these cells smoothly disappear
  • green cells are cells available only in the ending Voronoï map, i.e. data only in the ending data set; these cells smoothly appear
  • 'evolving overall size' demonstrates that the plugin can handle animation between disks of distinct sizes; when enabled, the ending overall disk is smaller than the starting disk
  • 'evolving overall shape' demonstrates that the plugin can handle animation between distinct shapes; when enabled, the ending overall shape is a pentagone; here, the smooth interpolation between the two shapes (circle and pentagon) is handled with flubber
  • 'show internals' shows how the cells' sites evolve (they either appear/disappear/evolve in location and weight)

Available only for d3-voronoi-map v2.

Context

Animating a Voronoï map is already possible with the live arrangement feature of the d3-voronoi-map plugin. This feature is sufficient to handle updates of data (displayed as evolving cell areas) for a static overall shape, but can't handle addition or deletion of data (deletion = data no longer existing at the end of the animation, addition = data not existing at the begining of the animation) and can't handle an evolving overall shape (e.g. a shape becoming bigger, representing the increase of the total amount).

This is where the d3-voronoi-map-tween comes in:

  • added data are displayed as new and emmerging cells
  • deleted data are displayed as shrinking and removed cells
  • evolving overall shape is possible

Examples

Installing

<script src="https://d3js.org/d3.v6.min.js"></script>
<script src="https://rawcdn.githack.com/Kcnarf/d3-weighted-voronoi/v1.1.3/build/d3-weighted-voronoi.js"></script>
<script src="https://rawcdn.githack.com/Kcnarf/d3-voronoi-map/v2.1.1/build/d3-voronoi-map.js"></script>
<script src="https://rawcdn.githack.com/Kcnarf/d3-voronoi-treemap/v0.0.1/build/d3-voronoi-map-tween.js">
  <script>
    var voronoiMapTween = d3.voronoiMapTween();
</script>

TL;DR;

In your javascript, in order to define the tween:

var startingVoronoiMapSimulation = d3.voronoiMapSimulation(startingData);
goToFinalState(startingVoronoiMapSimulation); // get the most representative Voronoï map, using d3-voronoi-map's *static* computation feature
var endingVoronoiMapSimulation = d3.voronoiMapSimulation(endingData);
goToFinalState(endingVoronoiMapSimulation); // get the most representative Voronoï map, using d3-voronoi-map's *static* computation feature

var voronoiMapTween = d3.voronoiMapTween(startingVoronoiMapSimulation, endingVoronoiMapSimulation);
var voronoiMapInterpolator = voronoiMapTween.mapInterpolator(); // interpolator of the Voronoi maps

Then, later in your javascript, in order to compute the interpolated Voronoï map cells, set the desired interpolation value (within [0, 1]):

var interpolatedVoronoiMapCells = voronoiMapTween(0.5); // basic use case, returns a set of polygons/cells
var startingVoronoiMapCells = voronoiMapTween(0); // at 0, similar to startingVoronoiMap.state().polygons
var endingVoronoiMapCells = voronoiMapTween(1); // at 1, similar to endingVoronoiMap.state().polygons

API

# d3.voronoiMapTween(startingVoronoiMapSimluation, endingVoronoiMapSimluation)

Creates a new voronoiMapTween based on the two d3-voronoi-map simulations, and with the default configuration values and functions (startingKey, endingKey, clipInterpolator).

# voronoiMapTween.mapInterpolator()

Returns a function which is the interpolator between the starting Voronoï map and the ending Voronoï map. Calling mapInterpolator(interpolationValue) returns a Voronoï map, which is a sparse array of polygons, one for each data coming from either the starting data set or the ending data set. The interpolation value must be a float value within [0, 1]:

  • mapInterpolator(0) returns a Voronoï map similar* to startingVoronoiMapSimluation.state().polygons; similar* means same polygons, but not necessarily in the same order; there is no polygon for data exclusively in the endingVoronoiMapSimluation
  • mapInterpolator(1) returns a Voronoï map similar* to endingVoronoiMapSimluation.state().polygons; there is no polygon for data exclusively in the startingVoronoiMapSimluation
  • else, returns an intermediate Voronoï map inbetween the starting and ending Voronoï maps for any other value within ]0,1[

For each computed polygon p, p.site.originalObject gives access to the interpolated site and its caracteristics:

  • p.site.originalObject.key is the key, retrieved from either startingKey or endingKey, and which allows to make the correspondance between starting and ending data
  • p.site.originalObject.interpolatedX and p.site.originalObject.interpolatedY are its interpolate coordinates
  • p.site.originalObject.interpolatedDataWeight is the interpolated weight of the underlying data
  • p.site.originalObject.tweenType, in [ENTER_TWIN_TYPE, UPDATE_TWEEN_TYPE, EXIT_TWEEN_TYPE], defines if the site handles a entering/updating/exiting data
  • p.site.originalObject.startingData and p.site.endingData reference the starting and ending data; one of the two references may be null if the site corresponds to a datum only available in the starting data set or only in the ending data set

# voronoiMapTween.startingKey([key])

In order to make the correspondance between the starting and ending cells of a single datum, each starting cell is assigned a key, retrieved from its underlying datum throught the starting key accessor. The starting key accessor and the ending key accessor may be distincts.

If key is specified, sets the key accessor, which must be a function accepting a parameter wich reference a datum (i.e. a element of the starting data set used to compute the starting Voronoï map). If key is not specified, returns the current key accessor, which defaults to:

function key(d) {
  return d.id;
}

# voronoiMapTween.endingKey([key])

Same as startingKey, but for the ending cells.

# voronoiMapTween.clipInterpolator([ƒ])

If ƒ is specified, sets the clipping polygon interpolator. If ƒ is not specified, returns the current interpolator, which defaults to:

function ƒ(interpolationValue) {
  return startingVoronoiMapSimulation.clip();
}

By default, we consider the starting and ending Voronoï maps having the same clipping polygon (thus, the default clipInterpolator interpolates nothing ;-). When the clipping polygon evolves, this API should be used to provide the clipping polygon interpolator, which must be a function ƒ accepting a float parameter in [0, 1] where:

  • ƒ(0) returns the starting clipping polygon
  • ƒ(1) returns the ending clipping polygon
  • else returns an intermediate polygon inbetween the satrting and ending polygon for any other value within ]0,1[

As a simple first example, if the starting and ending clipping polygons are squares of different sizes, the clipInterpolator may look like:

const startingSize = 50;
const endingSize = 100;
function ƒ(interpolationValue) {
  const intermediateSize = (1 - interpolationValue) * startingSize + interpolationValue * endingSize; // lerp interpolation
  return [
    [0, 0],
    [0, intermediateSize],
    [intermediateSize, intermediateSize],
    [intermediateSize, 0],
  ];
}
// f(0) returns [[0,0], [0,50], [50,50], [50,0]]
// f(1) returns [[0,0], [0,100], [100,100], [100,0]]
// f(0.5) returns [[0,0], [0,75], [75,75], [75,0]]
voronoiMapTween.clipInterpolator(ƒ);

Note: if the starting and ending clipping polygons are of the same kind (e.g. a square, a disc) but with distinct sizes (as in the above example), you can try to use a static clipping polygon, and then scale the svg/paths.

As a second example, for more complexe use cases where the starting and ending shapes are not of the same kind (e.g. a circle and a pentagon), you can provide a clipInterpolator using flubber:

const startingClippingPolygon = [...]; // an array of 2D points, ordered counterclockwise, defining a convex shape
const endingClippingPolygon = [...]; // another array of 2D points
const ƒ = flubber.interpolate(startingClippingPolygon, endingClippingPolygon, {string: false}); // {string:false} produces an array of 2D points

voronoiMapTween.clipInterpolator(ƒ);

Dependencies

  • d3-voronoi-map.voronoiMapSimulation

Semantic Versioning

d3-voronoi-map-tween attempts to follow semantic versioning and bump major version only when backward incompatible changes are released.