Jerry Orta
← Blog

Charts as a Library, Not a Component

Most Angular charting goes one of two ways. You install a third-party library and have charts on the screen within the hour — until the first time you need the one thing it doesn't do. Or you start writing components: a BarChartComponent, then a LineChartComponent, then a ScatterChartComponent, each with its own svg, its own axes, its own tooltip — and six charts in you have six copies of the same layout math and no two that look alike. I wanted neither: one chart, many layers, configured with data — a small library I own, not a black box I rent or a pile of components I hand-maintain.

One component, driven by a config

The whole surface is a single nge-chart element that takes one input — a plain ChartConfig you build from a preset factory. The component lays out the plot area, draws the shared axes, and hands each layer a render context. It knows nothing about bars or lines.

import { createBarChartConfig } from '@nge/charts';

config = createBarChartConfig({
  data: [
    { label: 'A', value: 30 },
    { label: 'B', value: 55 },
  ],
  tooltip: { enabled: true },
});

Layers compose

A layer is { type, data, renderer } — a pure D3 function that draws into the shared context. Because every layer shares one base layout and one set of scales, you stack them: bars under a line under a scatter, all on the same axes, by appending to config.layers. A new chart type is a render function, a config, a theme slice, and a preset — never another component wired into a central switch.

Own the library, don't rent a component

The reflexive answer to "we need charts" is to install one — Highcharts, ECharts, Chart.js, one of the Angular wrappers. It's the right call surprisingly rarely. A third-party chart library is fast until the day you need the one thing it doesn't do: a custom mark, a specific interaction, a chart inside a tooltip. Then you're monkey-patching around an abstraction you can't see into, or wrapping it to speak your design system's tokens, or waiting on someone else's release schedule for a fix. You took the dependency to write less code, and you end up writing adapters and workarounds instead — around the part you control least.

Owning changes what you're responsible for. D3 — the dataviz primitives underneath, the scales and shape generators — is a toolkit, not a framework; a thin architecture over it means the code you own is small and close to the metal, not a general-purpose charting engine wrapped around primitives you can't reach. The surface you maintain is just the handful of types your product actually renders, each a render function against a fixed contract. No black box, so there's no wall to hit — when you need a chart in a tooltip, you write it, you don't file an issue.

The usual objection is maintenance: own it and you've signed up to be the charting team. That was true when owning meant hand-writing every type. It stops being true the moment the architecture makes a new type a bounded, additive change — which is the whole point of the next two sections.

Why a scalable shape matters

Everything rides on one property: a new chart type is four small, additive pieces — a render function, a layer config, a theme slice, and a preset — and it touches no shared switch. Nothing that already works has to change for something new to appear. Styling rides a domain-agnostic --giga-chart-* CSS-variable contract on top of that, so a chart is themeable without touching its code and every chart on a page reads as one system.

That is the payoff of a library over a pile of components — and over a rented one: the tenth chart costs what the second one did. A scalable shape isn't a nicety here; it's the thing that keeps owning cheaper than renting as the catalog grows. For the mechanics behind it — layer composition, per-import tree-shaking, and the D3-driven tooltip — see the concept note Charts Library Architecture.

Built and extended with Claude

An additive shape with no central switch isn't just cheap for me to extend — it's cheap for an agent to extend, and that is what finally dissolves the maintenance objection to owning. I build and grow these charts with Claude Code. A new type isn't an open-ended design problem; it's a fixed contract to fill — four pieces, a written architecture doc that pins the invariants, and the library's own lint and tests as the gate. It's the same set of skills I lean on elsewhere, pointed at charts.

So the calculus flips. Owning used to trade "fast to start" against "expensive to maintain," and renting traded "fast to start" against "expensive the day you hit the wall." With an agent working a written contract, an owned library is fast to start and cheap to grow — and there's no wall, because there's no black box. That's why a config-driven, layered library I own beats both a folder of one-off components and a third-party dependency. More in the ngx-experiments overview.