# Charts & media

> Video, image, SVG, Lottie and caption layers, the Media chain, and animated charts built from native nodes.

## Media layers

```ts
Layer.video("assets/footage/clip.mp4", { width: 1920, height: 1080, fit: "cover", trimStartSec: 2, muted: true });
Layer.image("assets/images/still.webp", { width: 800, height: 600, fit: "contain" }); // .png .jpg .webp
Layer.svg("assets/images/logo.svg", { width: 400, height: 120, fit: "contain" });
Layer.lottie("assets/lottie/badge.json", { width: 400, height: 400, fit: "contain", loop: true }); // .json .lottie
Layer.caption("assets/captions/subs.srt", { /* bottom-anchored, auto-wrapping cues */ });
```

| Builder | Notes |
| --- | --- |
| `Layer.video` | decoded with mediabunny/WebCodecs; `fit`, `trimStartSec`, `muted`, `volume` |
| `Layer.image` | raster only. Passing `.json`/`.lottie`/video/audio is an error (`unsupported_image_format`) |
| `Layer.svg` | vector image |
| `Layer.lottie` | Lottie JSON and dotLottie via ThorVG; `fit: "contain" \| "cover" \| "fill"`, `loop`, `speed`, `autoplay` |
| `Layer.caption` | SRT-driven subtitles |

All of them position, animate, matte, take effects and tilt into 3D like any layer. Asset paths resolve from the working directory; build them from `import.meta.url` if the [preview](/docs/guides/preview/) must load them too.

## Media chains

Process a single clip outside the layer tree, e.g. to grade footage or extract a still.

```ts
import { ApplyLUT, Media } from "framefields";

const graded = Media.video("assets/footage/raw_log.mp4").apply(new ApplyLUT({ lutUrl: "assets/luts/rec709.cube" }));
await graded.renderFrame({ atMs: 1500 });             // PNG Buffer
await graded.renderVideo({ outputPath: "output/graded.mp4" });
```

`Media.video`, `Media.image`, `Media.lottie`, `Media.audio`; each has `apply`, `renderFrame`, `renderFrameGrid`, `renderVideo`.

## Charts

`Layer.chart(options, boxProps)` builds a chart out of ordinary boxes, paths and text — d3 does the scales and geometry, framefields draws it with your fonts.

```ts
Layer.chart(
  {
    type: "bar",               // line | area | bar | scatter | candlestick | pie | donut
    width: 900, height: 480,
    categories: ["Q1", "Q2", "Q3", "Q4"],
    series: [
      { name: "Revenue", data: [12, 19, 24, 31] },
      { name: "Costs", data: [8, 11, 13, 15] },
    ],
    yAxis: { min: 0, ticks: 4, format: "$,.0f" },          // d3-format string or (v) => string
    fontFamily: "Inter", fontSize: 24, textColor: "#e6e8e6", gridColor: "#2a2b2d",
    animate: { start: 10, duration: 30, stagger: 6, ease: "power3.out" }, // or false
  },
  { id: "revenue", position: "absolute", x: 120, y: 200 },
);
```

| Option | Notes |
| --- | --- |
| `series: [{ name, data, color, type, curve, pointRadius }]` or `data` | `data` for pie/donut slices and candles |
| `stacked` | stack bars/areas |
| `xAxis`, `yAxis` | `min`, `max`, `ticks`, `grid`, `format` |
| `legend`, `valueLabels` | booleans |
| `colors`, `textColor`, `gridColor`, `background`, `cornerRadius` | style |
| `innerRadius` | donut hole |
| `upColor`, `downColor` | candlesticks |
| `animate` | built-in reveal: lines draw on, bars grow, points and slices stagger |

Children get stable ids — `<id>-bar-<series>-<i>`, `<id>-line-<series>`, `<id>-slice-<i>` — so you can find them in lint output. Keep scatter plots to a few hundred points: each point is a node.
