Skip to content

petal ​

Draws a petal: a wedge whose sides follow the enclosing coordinate system's angular and radial axes. It is the polar counterpart of rect, used for flower and rose glyphs where a rectangle's straight sides would look wrong.

ts
import { chart, color, group, layer, petal, polar, rect, scatter, selectAll, spread, stackX, v } from "gofish-graphics";
import { catchLocations, seafood } from "./dataset";

const FLOWER_RADIUS = 40;

const stemData = seafood.map((d) => ({
  ...d,
  x: catchLocations[d.lake as keyof typeof catchLocations].x,
}));

const container = document.getElementById("app");

// The same shape as a labeled bar chart — but the bars are stems and the
// labels are flowers. The stems are a real bar chart, so their heights
// share one scale and grow with each lake's total catch (the `count` size
// channel auto-sums per lake); each flower is the stem's "label", placed on
// top of it via the selectAll + spread pattern.
layer([
  // Stems: one thin green bar per lake, planted at the lake's x location,
  // height = total catch (one chart, so all stems share the height scale).
  chart(stemData)
    .flow(scatter({ by: "lake", x: "x" }))
    .mark(rect({ w: 4, h: "count", fill: color.green[5] }).name("stems")),
  // Flowers: each stem's label. `selectAll("stems")` yields one ref per
  // lake (its datum is that lake's species rows); stack a polar petal fan
  // on top of the stem.
  chart(selectAll("stems"))
    .flow(group({ by: "lake" }))
    .mark(((d: any[]) =>
      spread({ dir: "y", alignment: "middle", spacing: -FLOWER_RADIUS }, [
        d[0],
        layer({ coord: polar() }, [
          stackX(
            {
              h: FLOWER_RADIUS,
              spacing: 0,
              alignment: "start",
              sharedScale: true,
            },
            (d[0].datum as { species: string; count: number }[]).map((r) =>
              petal({
                w: v(r.count),
                fill: v(r.species).lighten(0.5),
              })
            )
          ),
        ]),
      ])) as any),
]).render(container, {
  w: 400,
  h: 400,
  axes: false,
});
Dataset
ts
export type Lakes =
  | "Lake A"
  | "Lake B"
  | "Lake C"
  | "Lake D"
  | "Lake E"
  | "Lake F";

export type CatchData = {
  lake: Lakes;
  species: "Bass" | "Trout" | "Catfish" | "Perch" | "Salmon";
  count: number;
};

export const catchLocations: Record<Lakes, { x: number; y: number }> = {
  "Lake A": { x: 5.26, y: 22.64 },
  "Lake B": { x: 30.87, y: 120.75 },
  "Lake C": { x: 50.01, y: 60.94 },
  "Lake D": { x: 115.13, y: 94.16 },
  "Lake E": { x: 133.05, y: 50.44 },
  "Lake F": { x: 85.99, y: 172.78 },
};

export const catchLocationsArray = Object.entries(catchLocations).map(
  ([lake, { x, y }]) => ({
    lake,
    x,
    y,
  })
);

export const seafood: CatchData[] = [
  {
    lake: "Lake A",
    species: "Bass",
    count: 23,
  },
  {
    lake: "Lake A",
    species: "Trout",
    count: 31,
  },
  {
    lake: "Lake A",
    species: "Catfish",
    count: 29,
  },
  {
    lake: "Lake A",
    species: "Perch",
    count: 12,
  },
  {
    lake: "Lake A",
    species: "Salmon",
    count: 8,
  },
  // … 25 more rows
];

export const catchData = seafood;

export const catchDataWithLocations = seafood.map((catchItem) => {
  const location = catchLocationsArray.find(
    (loc) => loc.lake === catchItem.lake
  );
  return {
    ...catchItem,
    x: location?.x,
    y: location?.y,
  };
});

export type CatchDataWithLocation = (typeof catchDataWithLocations)[0];

Signature ​

ts
petal({ w?, h?, dims?, fill?, stroke?, strokeWidth? })

Use it inside a polar() or clock() coordinate space, where w/h read as the angular and radial extents, and dims: { theta: ..., r: ... } names the same two axes by their polar names (see Axis names).

Parameters ​

A polar-only wedge/petal shape (Petal.tsx). Box geometry via the shared dims channels.

OptionTypeDefaultDescription
fillstring | FieldExprFill color, or a field name for a color scale.
strokestring | FieldExprStroke color. Defaults to fill.
strokeWidthnumber0Stroke width in pixels.
debugbooleanDev-only flag: logs this mark's key and datum to the console as it is built. It changes nothing about what is drawn.

Box dimensions ​

OptionTypeDefaultDescription
xnumber | string | FieldExprLeft edge position.
cxnumber | string | FieldExprCenter x.
x2number | string | FieldExprRight edge position.
wnumber | string | FieldExprWidth.
emXbooleanEmbed x in the parent's x space.
ynumber | string | FieldExprTop/bottom edge position (y-up: bottom).
cynumber | string | FieldExprCenter y.
y2number | string | FieldExprOther y edge position.
hnumber | string | FieldExprHeight.
emYbooleanEmbed y in the parent's y space.
dimsRecord<string, AxisDimsValue>Box dimensions by axis name: x/y, or a name the enclosing coordinate space declares (polar theta/r, geo lon/lat). Each value is a position (like x) or an interval {min, center, max, size, embedded}.