Awesome
Cirque
Utilities for negotiating between circles and paths in SVG. See demo.
npm install --save cirque
(or yarn add cirque
)
Functions
<a href="#interpolatePath" name="interpolatePath">#</a> interpolatePath(a, b)
Interpolates between two SVG path description strings.
Expects a circle path (which gets sampled) and a polygonal chain path (which gets oversampled when necessary), although two circles work just by ordinary interpolation, and two chains should work in many cases as well.
<a href="#interpolatePaths" name="interpolatePaths">#</a> interpolatePaths(a, b)
Individually interpolates (using interpolatePath) between corresponding items in two parallel arrays of SVG path strings.
<a href="#circlePath" name="circlePath">#</a> circlePath(circle)
Converts the passed circle object to an SVG path string (i.e. a circle path consisting of two arc commands).
<a href="#geoToCircle" name="geoToCircle">#</a> geoToCircle(geometry, [path], [radius], [object])
Converts the geometry object to a circle object sharing its centroid.
Circle objects take the form: { x, y, r }
- geometry: any GeoJSON geometry or feature (required)
- path: geographic path generator. Defaults to bare
d3.geoPath()
, which assumes pre-projected geometry - radius: circle radius. Defaults to deriving radius from projected area
- object: mutates passed existing object rather than creating a new one
<a href="#polygonToCircle" name="polygonToCircle">#</a> polygonToCircle(polygon)
Converts a polygon to a circle object sharing its centroid.
- polygon: an array of polygon vertices (as two-element arrays) (required)
- radius: circle radius. Defaults to computing radius from polygon area
- object: mutates passed existing object rather than creating a new one
<a href="#avoidOverlap" name="avoidOverlap">#</a> avoidOverlap(objects, [margin])
Pass an array of circle objects to separate colliding circles so that no overlaps remain. Mutates objects in place. Margin (minimum gap, or maximum overlap if negative) defaults to 0. (Uses d3.forceCollide
.)
<a href="#radiusScale" name="radiusScale">#</a> radiusScale(area, value)
Receives total area and total value as arguments, and returns a D3 scale in which the area of a circle with the given radius corresponds to a linearly-scaled value.
Examples
let render // Given a function that renders SVG paths
let path // Given a geo path generator
Example: geometry
import { geoToCircle, circlePath, interpolatePath } from 'cirque'
let geometry // Given a GeoJSON Polygon or MultiPolygon geometry
const interpolator = interpolatePath(
path(geometry),
circlePath( geoToCircle(geometry, path) )
)
d3.transition().tween('shape', () => t => { render( interpolator(t) ) })
Example: features
import * as cirque from 'cirque'
let features // Given an array of GeoJSON Polygon or MultiPolygon features
const scale = cirque.radiusScale( path.area(mergedFeatures), 7.5e9 )
const circles = features.map(feature =>
circle.geoToCircle(feature, path, scale(feature.properties['population']))
)
const separatedCircles = cirque.avoidOverlap(circles)
const circlePaths = separatedCircles.map(cirque.circlePath)
const interpolator = cirque.interpolatePaths(features.map(path), circlePaths)
d3.transition().tween('shapes', () => t => { render( interpolator(t) ) })
Approach
- Aligns circle and path by tracing a circle using the path's commands
- Splits path commands when necessary to maintain a balance between a) mapping path commands uniformly, and b) aligning per distance unit
- Avoids more sophisticated circle approximation methods in favor of high-rate sampling
Limitations
The chain path is a SVG path description of a polygonal chain (i.e. polyline, composite Bézier, etc.) containing any SVG path commands except arcs, support for which is planned.
The circle path is a SVG path description containing an M command followed by at least one A command (but typically two). circlePath
is a utility for generating simple, compatible circle paths. Some more flexiblility in the format may come in the future, including (optionally) adhering to winding order.
Rationale
Just as a lack of color is physically considered black (though artistically often considered white), a lack of shape can in a certain sense be called a circle (or a n-sphere generally): no discrete segmentation, and no starting point any better than another.
This shapelessness is desirable for comparing values in a controlled way (say, in a bubble map) to minimize distortion and distraction.
The tools in this package amount to a method for going between precise forms such as geographic areas, and corresponding value-sized bubbles, while maintaining constancy.
Shape morphing alternatives
- SVG.js path morphing plugin: simple and well done. Has some of the same approach and associated limitations
- SVG Morpheus: works well and supports arbitrary SVG 👍
- MorphSVG: proprietary but seems quite customizable and the demos are awesome
Discussion and contribution
Open an issue or pull request with either high-level use cases or practical support tickets, or contact us on twitter. We intend to keep this package focused on its stated mission, but advice, critique, and experiences are very welcome.