Ribbon Chart
A ribbon chart tracking fish catch by species across six lakes, where each species' band is reordered at every lake so the largest sits on top and ribbons cross as rankings change.
ts
import { chart, field, rect, ribbon, spread, stack } from "gofish-graphics";
import { seafood } from "./dataset";
const container = document.getElementById("app");
chart(seafood, { h: 400 })
.flow(
spread({ by: "lake", dir: "x", spacing: 64 }),
stack({ by: field("species").sort("count"), dir: "y" })
)
.mark(rect({ h: "count", fill: "species" }))
.layer(ribbon({ opacity: 0.8 }))
.render(container);dataset.ts
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];