Skip to content

Shape ​

The Shape family holds the outlines enclose draws around its members. A shape is the value of the shape option of enclose. The padding and the paint stay on enclose.

js
gf.layer([
  gf.spread({ dir: "x", spacing: 95, alignment: "middle" }, [
    gf.circle({ r: 5, fill: "#111" }).name("a"),
    gf.circle({ r: 5, fill: "#111" }).name("b"),
    gf.circle({ r: 5, fill: "#111" }).name("c"),
  ]),
])
  .relate(({ a, b, c }) => [
    gf.enclose(
      {
        shape: gf.Shape.spine({ avoiding: [b], clearance: 6 }),
        padding: 22,
        fill: "#ff2400",
        fillOpacity: 0.3,
      },
      [a, c]
    ),
  ])
  .render(root, { w: 380, h: 150 });

Signature ​

ts
Shape.box({ rx?, ry? });
Shape.oval();
Shape.blob({ convexity?, holes?, roundness?, neck?, avoiding?, clearance? });
Shape.hull({ convexity?, holes?, roundness?, neck?, avoiding?, clearance? });
Shape.spine({ convexity?, holes?, roundness?, neck?, avoiding?, clearance? });

Every member is a function call. Each returns a plain object, { kind, ...params }, of type Shape.Shape, for example { kind: "box", rx: 16, ry: 16 }. Shape.hull() and Shape.spine() are Shape.blob() with some params preset, so their objects have kind "blob": Shape.hull(opts) is Shape.blob({ convexity: 1, neck: 1, ...opts }), and Shape.spine(opts) is Shape.blob({ convexity: 0, ...opts }). Any option overrides the preset. The same object is the shape's wire form, except avoiding, which holds nodes in JavaScript and their references on the wire.

The family is also a module of its own:

ts
import { Shape } from "gofish-graphics";
import { hull } from "gofish-graphics/shape"; // the same function as Shape.hull

Members ​

MemberIs exactlyWhat it draws
Shape.box()its own kindThe members' bounding box, grown by padding, rounded corners.
Shape.oval()its own kindThe smallest-area ellipse around the padded members.
Shape.blob()the general regionMembers, links between them and filled gaps, rounded.
Shape.hull()Shape.blob({ convexity: 1, neck: 1, ...opts })The padded convex hull, rounded.
Shape.spine()Shape.blob({ convexity: 0, ...opts })A tree of tubes that links the members, and nothing else.

Shape.box() is the default. A blob is a union of discs, tubes and triangles, so no blob setting draws a box.

Shape.box() ​

OptionTypeDefaultDescription
rxnumberCorner radius in pixels, x. Default: ry, or 2 when neither is given.
rynumberCorner radius in pixels, y. Default: rx, or 2 when neither is given.

Shape.oval() ​

OptionTypeDefaultDescription

The ellipse holds every member grown by padding (a circle is a disc, a line is a tube along its segments, and any other node is its bounding box), and of all such ellipses it has the least area. It is not fit to the bounding box, so it may be rotated: it follows the members, not the axes. A single member gives a circle.

js
gf.enclose(
  {
    shape: gf.Shape.oval(),
    padding: 10,
    fill: "#4c78a8",
    fillOpacity: 0.15,
    stroke: "#1f3b63",
  },
  [
    gf.layer([
      gf.position({ x: 10, y: 10 }, [gf.circle({ r: 6, fill: "#4c78a8" })]),
      gf.position({ x: 60, y: 30 }, [gf.circle({ r: 9, fill: "#4c78a8" })]),
      gf.position({ x: 110, y: 70 }, [gf.circle({ r: 6, fill: "#4c78a8" })]),
      gf.position({ x: 170, y: 90 }, [gf.circle({ r: 12, fill: "#4c78a8" })]),
    ]),
  ]
).render(root, { w: 260, h: 160 });

Shape.blob() ​

OptionTypeDefaultDescription
convexitynumber0.5The share of the members' convex hull that is filled, smallest gaps first, from 0 (the spanning tree alone) to 1 (the whole hull). It is the only option that fills gaps between members, so it decides the pieces and the filled area.
holesbooleanfalseWhether a gap left open inside the silhouette stays a hole. Off, every hole is filled and each avoided node it held is cut back out as its clearance circle, as allow_holes in PostGIS and Shapely.
roundnessnumberHow far the outline's corners are rounded, in pixels. It changes how sharply the outline bends and nothing else: no gap fills, no hole opens, no two pieces join. Default: padding.
necknumber1How far the outline is NOT pulled in between members, above 0 and at most 1: a lone link becomes a tube neck times the bulge, and the edge of a filled region sinks in between its members. 1 does not pull in. 0 is not allowed: a link neck times the bulge would have no width, and its two sides would meet. It never changes the pieces or holes.
avoidingNodeSelectionIRNodes to keep out: a list of nodes or refs, or a selectAll(name) selection. A listed node that is also a member is ignored. Each avoided node inside the region becomes a round hole, and links route around the others.
clearancenumber4Pixels kept between the outline and each avoided node.

A blob is built from these pieces, in the enclosure's pixels:

  • each member grown by padding (a circle is a disc, a line is a tube along its segments, and any other node is its bounding box);
  • the links of the shortest tree that joins the members, as tubes that taper from one member's bulge to the other's, routed around the avoided nodes;
  • the Delaunay triangles of the members' points, filled from the smallest gap up, until they hold convexity of the convex hull's area.

Each avoided node, grown by clearance, is cut out of the union. The outline of the rest is traced exactly, pulled in by neck between members, and rounded by roundness. convexity alone decides which gaps fill, so it decides the pieces and the holes; roundness and neck never change them. The Enclose essay describes the construction.

Shape.hull() ​

OptionTypeDefaultDescription
convexitynumber1The share of the members' convex hull that is filled, smallest gaps first, from 0 (the spanning tree alone) to 1 (the whole hull). It is the only option that fills gaps between members, so it decides the pieces and the filled area.
holesbooleanfalseWhether a gap left open inside the silhouette stays a hole. Off, every hole is filled and each avoided node it held is cut back out as its clearance circle, as allow_holes in PostGIS and Shapely.
roundnessnumberHow far the outline's corners are rounded, in pixels. It changes how sharply the outline bends and nothing else: no gap fills, no hole opens, no two pieces join. Default: padding.
necknumber1How far the outline is NOT pulled in between members, above 0 and at most 1: a lone link becomes a tube neck times the bulge, and the edge of a filled region sinks in between its members. 1 does not pull in. 0 is not allowed: a link neck times the bulge would have no width, and its two sides would meet. It never changes the pieces or holes.
avoidingNodeSelectionIRNodes to keep out: a list of nodes or refs, or a selectAll(name) selection. A listed node that is also a member is ignored. Each avoided node inside the region becomes a round hole, and links route around the others.
clearancenumber4Pixels kept between the outline and each avoided node.

Shape.spine() ​

OptionTypeDefaultDescription
convexitynumber0The share of the members' convex hull that is filled, smallest gaps first, from 0 (the spanning tree alone) to 1 (the whole hull). It is the only option that fills gaps between members, so it decides the pieces and the filled area.
holesbooleanfalseWhether a gap left open inside the silhouette stays a hole. Off, every hole is filled and each avoided node it held is cut back out as its clearance circle, as allow_holes in PostGIS and Shapely.
roundnessnumberHow far the outline's corners are rounded, in pixels. It changes how sharply the outline bends and nothing else: no gap fills, no hole opens, no two pieces join. Default: padding.
necknumber1How far the outline is NOT pulled in between members, above 0 and at most 1: a lone link becomes a tube neck times the bulge, and the edge of a filled region sinks in between its members. 1 does not pull in. 0 is not allowed: a link neck times the bulge would have no width, and its two sides would meet. It never changes the pieces or holes.
avoidingNodeSelectionIRNodes to keep out: a list of nodes or refs, or a selectAll(name) selection. A listed node that is also a member is ignored. Each avoided node inside the region becomes a round hole, and links route around the others.
clearancenumber4Pixels kept between the outline and each avoided node.