• Stars
    star
    6,378
  • Rank 6,240 (Top 0.2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 7 years ago
  • Updated about 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Tools for smoother shape animations.

Build Status

flubber

Some best-guess methods for smoothly interpolating between 2-D shapes.

Flubber in action

Why?

Let's say you want to animate between two SVG paths or canvas shapes in a visualization. If you plug in their coordinates or their path strings to something like d3.transition(), it might work if the shapes correspond to each other really well - for example, turning a triangle into a different triangle. But once your shapes don't really correspond, you'll get unpredictable results with weird inversions and sudden jumps.

The goal of this library is to provide a best-guess interpolation for any two arbitrary shapes (or collections of shapes) that results in a reasonably smooth animation, without overthinking it.

Installation

In a browser (exposes the flubber global):

<script src="https://unpkg.com/[email protected]"></script>

With NPM:

npm install flubber

And then import/require it:

var flubber = require("flubber"); // Node classic
import { interpolate } from "flubber" // ES6

How to use

Flubber expects a shape input to be either an SVG path string or an array of [x, y] points (a "ring"):

"M100,100 L200,100 L150,200Z" // A triangle as a path string
[[100, 100], [200, 100], [150, 200]] // A triangle as a ring

Flubber methods return interpolators, functions that you can call later with a value from 0 to 1 to get back the corresponding shape, where 0 is the beginning of the animation and 1 is the end.

Using D3, usage could look something like:

var triangle = [[1, 0], [2, 2], [0, 2]],
    pentagon = [[0, 0], [2, 0], [2, 1], [1, 2], [0, 1]];

var interpolator = flubber.interpolate(triangle, pentagon);

d3.select("path")
    .transition()
    .attrTween("d", function(){ return interpolator; });

Without D3, usage might look something like this:

// Mixing and matching input types is OK
var triangle = "M1,0 L2,2 L0,2 Z",
    pentagon = [[0, 0], [2, 0], [2, 1], [1, 2], [0, 1]];

var interpolator = flubber.interpolate(triangle, pentagon);

requestAnimationFrame(draw);

function draw(time) {
    var t = howFarAlongTheAnimationIsOnAScaleOfZeroToOne(time);
    myPathElement.setAttribute("d", interpolator(t));
    if (t < 1) {
        requestAnimationFrame(draw);
    }
}

Note: it doesn't matter whether your ring has a closing point identical to the first point.

API

flubber.interpolate(fromShape, toShape [, options])

fromShape and toShape should each be a ring or an SVG path string. If your path string includes holes or multiple shapes in a single string, everything but the first outer shape will be ignored.

This returns a function that takes a value t from 0 to 1 and returns the in-between shape:

var interpolator = flubber.interpolate(triangle, octagon);

interpolator(0); // returns an SVG triangle path string
interpolator(0.5); // returns something halfway between the triangle and the octagon
interpolator(1); // returns an SVG octagon path string

options can include the following keys:

string: whether to output results as an SVG path string or an array of points. (default: true)
maxSegmentLength: the lower this number is, the smoother the resulting animation will be, at the expense of performance. Represents a number in pixels (if no transforms are involved). Set it to false or Infinity for no smoothing. (default: 10)

.interpolate() in action with SVG paths as input

.interpolate() in action with GeoJSON coordinates as input

flubber.toCircle(fromShape, x, y, r[, options])

Like interpolate(), but for the specific case of transforming the shape to a circle centered at [x, y] with radius r.

var interpolator = flubber.toCircle(triangle, 100, 100, 10);

interpolator(0); // returns an SVG triangle path string
interpolator(0.5); // returns something halfway between the triangle and the circle
interpolator(1); // returns a circle path string centered at 100, 100 with a radius of 10

.toCircle() in action

flubber.toRect(fromShape, x, y, width, height[, options])

Like interpolate(), but for the specific case of transforming the shape to a rectangle with the upper-left corner [x, y] and the dimensions width x height.

var interpolator = flubber.toRect(triangle, 10, 50, 100, 200);

interpolator(0); // returns an SVG triangle path string
interpolator(0.5); // returns something halfway between the triangle and the rectangle
interpolator(1); // returns a rectangle path string from [10, 50] in the upper left to [110, 250] in the lower right

.toRect() in action

flubber.fromCircle(x, y, r, toShape[, options])

Like toCircle() but reversed.

flubber.fromRect(x, y, width, height, toShape[, options])

Like toRect() but reversed.

flubber.separate(fromShape, toShapeList[, options])

If you're trying to interpolate between a single shape and multiple shapes (for example, a group of three circles turning into a single big circle), this method will break your shapes into pieces so you can animate between the two sets. This isn't terribly performant and has some quirks but it tends to get the job done.

fromShape should be a ring or SVG path string, and toShapeList should be an array of them.

The options are the same as for interpolate(), with the additional option of single, which defaults to false.

If single is false, this returns an array of n interpolator functions, where n is the length of toShapeList. If single is set to true this returns one interpolator that combines things into one giant path string or one big array of rings.

// returns an array of two interpolator functions
var interpolators = flubber.separate(triangle, [square, otherSquare]);

d3.selectAll("path")
    .data(interpolators)
    .transition()
    .attrTween("d", function(interpolator) { return interpolator; });

.separate() in action

// returns a single interpolator function
var combinedInterpolator = flubber.separate(triangle, [square, otherSquare], { single: true });

// This one path element will be two squares at the end
d3.select("path")
    .transition()
    .attrTween("d", function() { return combinedInterpolator; });

.separate({ single: true }) in action

flubber.combine(fromShapeList, toShape[, options])

Like separate() but reversed.

flubber.interpolateAll(fromShapeList, toShapeList[, options])

Like separate() or combine() but instead expects two arrays of shapes the same length (e.g. an array of three triangles turning into an array of three squares). The shapes will be matched up in the order of the arrays (the first fromShapeList item will turn into the first toShapeList item, and so on).

.interpolateAll() in action

.interpolateAll({ single: true }) in action

flubber.toPathString(ring)

A helper function for converting an array of points to an SVG path string.

flubber.toPathString([[1, 1], [2, 1], [1.5, 2]]);
// Returns "M1,1L2,1L1.5,2Z"

flubber.splitPathString(pathString)

A helper function for splitting an SVG path string that might contain multiple shapes into an array of one-shape path strings.

flubber.splitPathString("M1,1 L2,1 L1.5,2Z M3,3 L4,3 L3.5,4 Z");
// Returns ["M1,1 L2,1 L1.5,2Z", "M3,3 L4,3 L3.5,4 Z"]

Examples

Note: most of these demos use D3 to keep the code concise, but this can be used with any library, or with no library at all.

Morphing SVG paths

Morphing GeoJSON coordinates

Morphing to and from circles

Morphing to and from rectangles

Morphing between one shape and multiple shapes (one element)

Morphing between one shape and multiple shapes (multiple elements)

Morphing between two sets of multiple shapes

Vanilla JS + Canvas

Medley of different methods

To do

  • Maintain original vertices when polygonizing a path string with curves
  • Add force: true option to collapse small additional polygons onto the perimeter of the largest
  • Support unclosed lines
  • Use curves between points for fromCircle() and toCircle()
  • Deal with holes?
  • Accept SVG elements as arguments instead of just path strings?
  • Add pre-simplification as an option
  • Simulated annealing or random swapping for multishape matching?

Video

OpenVisConf 2017 talk about shape interpolation

Alternatives

react-svg-morph - utility for morphing between two SVGs in React

GreenSock MorphSVG plugin - GSAP shape morphing utility (costs money, not open source)

d3.geo2rect - a plugin for morphing between GeoJSON and a rectangular SVG grid

d3-interpolate-path - a D3 interpolator to interpolate between two unclosed lines, for things like line chart transitions with mismatched data

Wilderness - an SVG manipulation and animation library

Cirque - JS utility for morphing between circles and polygons

Credits

Many thanks to:

License

MIT License

Copyright (c) 2017 Noah Veltman

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

More Repositories

1

clmystery

A command-line murder mystery
5,317
star
2

principles

Things to keep in mind when making stuff for the web
1,265
star
3

learninglunches

Materials for a series of learning lunches on news development topics.
237
star
4

gifs

Testing various ways of generating gifs and videos off data-driven JS animations.
HTML
179
star
5

csvgeocode

Node module for bulk geocoding addresses in a CSV.
JavaScript
163
star
6

wherewolf

A server-less boundary service. Find what geographic feature (e.g. an election district) a given point lies in.
HTML
160
star
7

mapstarter

A tool for generating starter SVG maps from a geographic data source
JavaScript
129
star
8

ca-license-plates

Vanity license plate applications from the California DMV.
122
star
9

pancakejs

A mini-library for easily flattening SVG and Canvas elements into images on the fly.
JavaScript
86
star
10

d3-stateplane

D3-friendly State Plane projections
86
star
11

openvis

Links and bibliography for OpenVisConf 2017
84
star
12

fourscore

An open-source version of the WNYC sentiment tracker.
JavaScript
46
star
13

nflplays

Cleaned-up NFL play-by-play data from 2002-2012
42
star
14

stakeout

For watching a set of URLs and notifying someone when something has changed.
JavaScript
31
star
15

xyz-affair

Generate list of x/y/z Spherical Mercator tiles based on a bounding box and a zoom level.
JavaScript
31
star
16

endorsements

Data on newspaper presidential endorsements
28
star
17

markdowneyjr

A hacky Markdown-to-JSON parser for easier copy editing.
JavaScript
25
star
18

loopify

Seamless looping with the WebAudioAPI.
JavaScript
22
star
19

maps-nicar14

Notes for map session with Tom MacWright (@tmcw) at NICAR14.
21
star
20

d3-unconf

HTML
17
star
21

presidential-nouns

Nouns of assemblage for US presidents.
13
star
22

flipbookjs

For automatically flip-booking progress while developing something for the web.
JavaScript
13
star
23

snd3

D3.js resources for SND/NYC 2018
HTML
13
star
24

mahalanobis

Calculate Mahalanobis distances for multivariate data.
JavaScript
13
star
25

ire2014

A list of resources for getting started with interactive news projects.
12
star
26

notsimple

Chrome extension that puts quotes around any instance of the words 'simple,' 'simply,' 'easy,' and 'easily' in GitHub and RTD docs.
JavaScript
11
star
27

gobblefunk

Rename your JS variables with silly words derived from Dr. Seuss, Roald Dahl, and Lewis Carroll.
JavaScript
9
star
28

hashnav

Simple JS hash-based navigation library
JavaScript
9
star
29

oh-snap

Snap points to nearest point in a different set.
JavaScript
7
star
30

hhbaworkshop

Mapping History - A Leaflet.js workshop for the Hacks/Hackers Buenos Aires Media Party
CSS
6
star
31

headless-gif

Saving web worker'd gif via PhantomJS.
JavaScript
6
star
32

dotmapper

Auto-generate a dotmap from a GeoJSON file
JavaScript
5
star
33

node-geosupport

Node.js wrapper for fast geocoding with NYC's Geosupport system
JavaScript
5
star
34

streets

A bunch of convoluted processing for turning OSM data into Leaflet-mappable street objects with names.
Python
5
star
35

fresh-start

A full bootstrap script for a new installation of Ubuntu Desktop
Shell
5
star
36

presidential-election-results

1976-2020 presidential election popular vote totals by state and party
4
star
37

chopped-and-viewed

Node module for chopping fixed-width text files.
JavaScript
4
star
38

congressional-acronyms

Raw data on congressional acronyms, 1972-2013
3
star
39

insecurity

Analyze HTML, CSS, JS, etc. files for insecure URLs.
JavaScript
3
star
40

nicar15-scrapeoff

Gathering notes/suggestions for a Scrape-off at NICAR 15.
2
star
41

knickout

For simulating the rest of the Knicks season.
JavaScript
2
star
42

point-on-line

Test whether a point is on a line or LineString
JavaScript
2
star
43

postal-abbreviations

A no-fuss US postal abbreviations module.
JavaScript
2
star
44

BriefMemorableSlug

Generate random adjective-adjective-animal slugs
JavaScript
1
star
45

state-population-by-age

State population by age, 1960-2040
1
star
46

dst

Collecting data to visualize effects of DST
Python
1
star
47

lazy-vector-tiles

A lazy vector tile system. Not robust or high-performance, but rather straightforward.
JavaScript
1
star
48

bracket

CSS
1
star
49

rosetta

TK
1
star
50

euler

Solving Project Euler problems in JS, Python, and maybe Ruby
JavaScript
1
star
51

gulp-insecurity

Gulp plugin for detecting insecure URLs that could cause mixed content errors.
JavaScript
1
star
52

statelympics

Python
1
star
53

webpack-babel-bug-repro

JavaScript
1
star
54

fuel-prices

Misc. fuel price data
JavaScript
1
star
55

recs

Travel recommendations in Markdown
CSS
1
star
56

ticker-tape

A list of ticker tape parades in New York City
1
star
57

jsv

1
star
58

emojify

Replace PHP variable names with emojis.
PHP
1
star
59

supreme-court-transcripts

Attempting to parse supreme court oral argument transcripts.
JavaScript
1
star
60

split-multipart-features

A script for selectively splitting up multipart features in QGIS.
Python
1
star
61

nyc-css

NYC in CSS (Chrome only)
HTML
1
star