Skip to content

← Examples

What's in a Bottle of Wine ​

A wine bottle sliced into proportional bands by ingredient and exploded into a vertical stack, with each slice labeled by category and its share of the bottle.

ts
import { Constraint, chart, image, layer, spread, text } from "gofish-graphics";
import bottlePng from "./wilsonblanco.png";

const bottleData = [
  { category: "Marketing", amount: 6 },
  { category: "Pretentiousness", amount: 7 },
  { category: "Sulfites", amount: 2 },
  { category: "Tannins", amount: 3 },
  { category: "Water", amount: 40 },
  { category: "Grape juice", amount: 42 },
];

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

// The cut ties each slice's image band to data order (slice i = band i,
// top→bottom). To land Grape juice (the bulk) at the BOTTOM showing the
// bottle BODY, it must be the LAST data row (bottom band) AND positioned
// last — so reverse the data and keep `reverse: true` on the spread.
chart([...bottleData].reverse())
  .flow(spread({ dir: "y", spacing: 20, reverse: true }))
  .mark(
    image({ href: bottlePng, w: 193, h: 600 }).cut({
      dir: "y",
      size: "amount",
      inset: 4,
    })
  )
  .layer(
    chart().mark(((data: any[]) =>
      layer(
        data.map((d) =>
          layer([
            d.name("slice"),
            text({
              fontSize: 18,
              fontWeight: "bold",
              fill: "#1c5e20",
              text: d.datum.category,
            }).name("label"),
            text({
              fontSize: 36,
              fontFamily: "Impact",
              fill: "#1c5e20",
              text: `${d.datum.amount}`,
            }).name("amount"),
          ]).relate(({ slice, label, amount }) => [
            Constraint.align({ y: "middle" }, [slice, label]),
            Constraint.distribute(
              { dir: "x", spacing: 12 },
              [slice, label]
            ),
            Constraint.align({ x: "middle" }, [slice, amount]),
            Constraint.align({ y: "middle" }, [slice, amount]),
          ])
        )
      )) as any
    )
  )
  .render(container, { axes: false });