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.
| Option | Type | Default | Description |
|---|---|---|---|
fill | string | FieldExpr | Fill color, or a field name for a color scale. | |
stroke | string | FieldExpr | Stroke color. Defaults to fill. | |
strokeWidth | number | 0 | Stroke width in pixels. |
debug | boolean | Dev-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
| Option | Type | Default | Description |
|---|---|---|---|
x | number | string | FieldExpr | Left edge position. | |
cx | number | string | FieldExpr | Center x. | |
x2 | number | string | FieldExpr | Right edge position. | |
w | number | string | FieldExpr | Width. | |
emX | boolean | Embed x in the parent's x space. | |
y | number | string | FieldExpr | Top/bottom edge position (y-up: bottom). | |
cy | number | string | FieldExpr | Center y. | |
y2 | number | string | FieldExpr | Other y edge position. | |
h | number | string | FieldExpr | Height. | |
emY | boolean | Embed y in the parent's y space. | |
dims | Record<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}. |
