# aeriz

A focused, fast canvas charting library. **21 chart types**, single rendering core, zero dependencies.

[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
[![npm](https://img.shields.io/badge/npm-%40aeriz%2Fchart-cb3837.svg)](https://www.npmjs.com/package/@aeriz/chart)
[![size](https://img.shields.io/badge/min-58_KB-green.svg)](dist/aeriz.min.js)

---

## At a glance

- **21 chart types** — bar, line, area, scatter, pie, radar, gauge, heatmap, treemap, realtime, waterfall, **composite** (mixed bar + line + area + scatter, dual y-axis), and more
- **~58 KB minified · ~94 KB unminified** · zero dependencies
- **Live theming** via CSS variables — change colors at runtime, call `invalidate()`, redraws
- **Pixel-perfect events** — hit testing attached to canvas, no DPR confusion
- **Realtime streaming** — `RealtimeChart.push()` with rolling window
- **State overlays** — `setLoading()`, `setEmpty()`, `setError()`, `clearState()`
- **TypeScript types included** (`src/index.d.ts`)

---

## Install

### CDN

```html
<!-- ✓ Production — pin a specific version (immutable, longest cache) -->
<script src="https://cdn.jsdelivr.net/npm/@aeriz/chart@1.0.0/dist/aeriz.min.js"></script>

<!-- Tracks the latest 1.x — auto-receives patches, never major bumps -->
<script src="https://cdn.jsdelivr.net/npm/@aeriz/chart@^1.0.0/dist/aeriz.min.js"></script>

<!-- Always-newest — convenient for prototyping, NOT for production -->
<script src="https://cdn.jsdelivr.net/npm/@aeriz/chart@latest/dist/aeriz.min.js"></script>

<!-- unpkg alternative (same files, different CDN) -->
<script src="https://unpkg.com/@aeriz/chart@1.0.0/dist/aeriz.min.js"></script>

<!-- ES Module — same versioning rules apply -->
<script type="module">
  import { BarChart } from 'https://cdn.jsdelivr.net/npm/@aeriz/chart@1.0.0/dist/aeriz.esm.min.js';
</script>
```

> **Why pin a version?** Pinned URLs (e.g. `@1.0.0`) are immutable and benefit
> from a one-year CDN cache. `@latest` redirects on every release and may pull
> in breaking changes when a new major version ships. Use `@latest` only for
> quick prototypes; pin a version everywhere else.

### npm / yarn / pnpm

```bash
npm install @aeriz/chart
yarn add @aeriz/chart
pnpm add @aeriz/chart
```

```js
import { BarChart, CompositeChart, math, PALETTE } from '@aeriz/chart';
// or, in CommonJS:
const Aeriz = require('@aeriz/chart');
// or, the minified build (smaller install footprint at runtime):
import * as Aeriz from '@aeriz/chart/min';
```

---

## Quick start

```html
<div id="chart" style="height:320px"></div>
<script src="https://cdn.jsdelivr.net/npm/@aeriz/chart@1.0.0/dist/aeriz.min.js"></script>
<script>
  const chart = new Aeriz.BarChart('#chart', {
    data: [
      { label: 'Jan', value: 64 },
      { label: 'Feb', value: 42 },
      { label: 'Mar', value: 78 },
    ],
    yTickFormat: v => v + 'k',
    onClick: hit => hit && console.log(hit),
  });
</script>
```

---

## Chart types

All 21 types share the same lifecycle (`new` → `setData` → `destroy`) and the same state overlays.

| Chart        | Class                | Notes                                              |
| ------------ | -------------------- | -------------------------------------------------- |
| Bar          | `BarChart`           | Vertical, supports negative values                 |
| H-Bar        | `HBarChart`          | Horizontal                                         |
| Line         | `LineChart`          | smooth · linear · step · log · time · auto-decimation |
| Area         | `AreaChart`          | Single + stacked                                   |
| Stacked Bar  | `StackedBarChart`    |                                                    |
| Grouped Bar  | `GroupedBarChart`    | Clustered                                          |
| Multi-Line   | `MultiLineChart`     | Shared crosshair tooltip                           |
| Scatter      | `ScatterChart`       | Grouped, batched rendering                         |
| Bubble       | `BubbleChart`        | Scatter with z → radius                            |
| Pie          | `PieChart`           |                                                    |
| Donut        | `PieChart` + `innerRadius` | Hover-aware center label                     |
| Radar        | `RadarChart`         | Multi-series, multi-axis                           |
| Polar        | `PolarChart`         | Radial bars                                        |
| Gauge        | `GaugeChart`         | Animated `setValue()`                              |
| Heatmap      | `HeatmapChart`       | Color interpolation                                |
| Boxplot      | `BoxplotChart`       | IQR + outliers                                     |
| Funnel       | `FunnelChart`        | Conversion stages                                  |
| Treemap      | `TreemapChart`       | Squarified layout                                  |
| Sparkline    | `Sparkline`          | Mini, no axes                                      |
| Realtime     | `RealtimeChart`      | `push()` streaming, rolling window                 |
| Waterfall    | `WaterfallChart`     | Cumulative + connector lines                       |
| **Composite**| `CompositeChart`     | **Mixed bar + line + area + scatter, dual y-axis** |

---

## Lifecycle

```js
const chart = new Aeriz.BarChart('#card', { /* options */ });

chart.setData(newData);     // animated transition
chart.setLoading('Fetching…');
chart.setEmpty('No data');
chart.setError('Network error');
chart.clearState();         // restore prior render
chart.invalidate();         // re-resolve theme + redraw
chart.destroy();            // remove canvas, observers, listeners
```

Methods other than `invalidate()` and `destroy()` return `this` for chaining.

### Type-specific methods

```js
gauge.setValue(82);                  // GaugeChart — animated needle
realtime.push([cpu, mem]);           // RealtimeChart — append a sample
realtime.push(value);                // single-series shortcut
realtime.clear();                    // empty the rolling window
```

---

## Composite (mixed) chart

What other libraries call "complex chart configuration" — here in one call:

```js
new Aeriz.CompositeChart('#card', {
  categories: ['Jan', 'Feb', 'Mar'],
  layers: [
    { type: 'bar',     name: 'Revenue',  values: [100, 130, 90] },
    { type: 'area',    name: 'Forecast', values: [90, 120, 100] },
    { type: 'line',    name: 'Margin %', values: [22, 28, 18], axis: 'right' },
    { type: 'scatter', name: 'Anomaly',  values: [{ x: 1, y: 35 }] },
  ],
  yLeftTickFormat:  v => '$' + v + 'k',
  yRightTickFormat: v => v + '%',
});
```

---

## Theming via CSS variables

Set any of the tokens below on `:root`, on the chart container, or on any
ancestor element. Then call `chart.invalidate()` and the chart re-resolves
its theme and redraws.

```css
:root {
  --aeriz-bg:           #0a0e14;
  --aeriz-surface:      transparent;
  --aeriz-text:         #e7eef5;
  --aeriz-muted:        #6a7a8a;
  --aeriz-grid:         rgba(100, 200, 255, 0.08);
  --aeriz-axis:         rgba(100, 200, 255, 0.25);
  --aeriz-accent:       #64c8ff;

  --aeriz-color-1:      #64c8ff;
  --aeriz-color-2:      #10b981;
  /* … --aeriz-color-12 */

  --aeriz-font:         ui-monospace, SFMono-Regular, Menlo, monospace;
  --aeriz-font-size:    13px;
  --aeriz-line-width:   2;
  --aeriz-point-radius: 3;
  --aeriz-bar-radius:   3;

  --aeriz-tooltip-bg:   rgba(0, 0, 0, 0.92);
  --aeriz-tooltip-fg:   #fff;
}
```

Then in JS:

```js
document.documentElement.style.setProperty('--aeriz-accent', '#ff6b9d');
chart.invalidate();
```

---

## Realtime streaming

```js
const rt = new Aeriz.RealtimeChart('#rt', {
  series: [{ name: 'CPU' }, { name: 'Memory' }],
  windowSize: 100,
});

setInterval(() => {
  rt.push([Math.random() * 100, Math.random() * 100]);
}, 100);
```

`push()` accepts a single number (one-series charts) or an array aligned
with `series`. Older samples beyond `windowSize` are discarded automatically.

---

## State overlays

```js
chart.setLoading('Fetching…');   // shows a spinner overlay
chart.setEmpty('No data');       // empty state
chart.setError('Network error'); // error state
chart.clearState();              // dismiss overlay, resume rendering
```

Overlays are non-destructive — `clearState()` restores whatever was
rendered before.

---

## Framework usage

The same constructor signature works in every framework. The patterns below
are the ones the docs site demonstrates per chart type.

<details>
<summary><strong>React</strong></summary>

```jsx
import { useEffect, useRef } from 'react';
import * as Aeriz from '@aeriz/chart';

export default function BarChartDemo() {
  const ref = useRef(null);
  useEffect(() => {
    const chart = new Aeriz.BarChart(ref.current, {
      data: [{ label: 'A', value: 5 }, { label: 'B', value: 9 }],
    });
    return () => chart.destroy();
  }, []);
  return <div ref={ref} style={{ width: '100%', height: 320 }} />;
}
```
</details>

<details>
<summary><strong>Vue 3</strong></summary>

```vue
<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue';
import * as Aeriz from '@aeriz/chart';

const el = ref(null);
let chart = null;
onMounted(() => {
  chart = new Aeriz.BarChart(el.value, {
    data: [{ label: 'A', value: 5 }, { label: 'B', value: 9 }],
  });
});
onBeforeUnmount(() => chart && chart.destroy());
</script>

<template>
  <div ref="el" style="width:100%;height:320px"></div>
</template>
```
</details>

<details>
<summary><strong>Svelte</strong></summary>

```svelte
<script>
  import { onMount } from 'svelte';
  import * as Aeriz from '@aeriz/chart';

  let el;
  onMount(() => {
    const chart = new Aeriz.BarChart(el, {
      data: [{ label: 'A', value: 5 }, { label: 'B', value: 9 }],
    });
    return () => chart.destroy();
  });
</script>

<div bind:this={el} style="width:100%;height:320px"></div>
```
</details>

<details>
<summary><strong>jQuery</strong></summary>

```js
$(function () {
  const chart = new Aeriz.BarChart($('#bar')[0], {
    data: [{ label: 'A', value: 5 }, { label: 'B', value: 9 }],
  });
});
```
</details>

<details>
<summary><strong>ExtJS</strong></summary>

```js
Ext.create('Ext.Component', {
  renderTo: Ext.getBody(),
  width: '100%',
  height: 320,
  autoEl: { tag: 'div' },
  listeners: {
    afterrender(cmp) {
      cmp.aerizChart = new Aeriz.BarChart(cmp.getEl().dom, {
        data: [{ label: 'A', value: 5 }, { label: 'B', value: 9 }],
      });
    },
    beforedestroy(cmp) {
      cmp.aerizChart && cmp.aerizChart.destroy();
    },
  },
});
```
</details>

---

## TypeScript

Types ship in the package — no `@types/aeriz__chart` needed.

```ts
import {
  BarChart,
  CompositeChart,
  GaugeChart,
  RealtimeChart,
  math,
  PALETTE,
} from '@aeriz/chart';

const bar = new BarChart('#x', {
  data: [{ label: 'A', value: 5 }],
  onClick: hit => console.log(hit),
});

const gauge = new GaugeChart(document.body, { value: 50, max: 100 });
gauge.setValue(82);

const ticks: number[] = math.niceTicks(0, 100, 5);
```

Discriminated unions cover composite layers — `type: 'bar'` requires
`values: number[]` while `type: 'scatter'` requires `values: { x, y }[]`.

---

## API reference

### `Chart` (base class)

| Method                       | Returns | Description                                   |
| ---------------------------- | ------- | --------------------------------------------- |
| `new Chart(container, opts)` | —       | `container`: CSS selector or `HTMLElement`    |
| `setData(data)`              | `this`  | Replace data with an animated transition      |
| `setLoading(msg?)`           | `this`  | Show loading overlay                          |
| `setEmpty(msg?)`             | `this`  | Show empty-state overlay                      |
| `setError(msg?)`             | `this`  | Show error overlay                            |
| `clearState()`               | `this`  | Dismiss any overlay                           |
| `invalidate()`               | `void`  | Re-resolve theme + palette and redraw         |
| `destroy()`                  | `void`  | Tear down listeners, observers, canvas        |

### Common options

Accepted by every chart that has them:

| Option           | Type                       | Description                                  |
| ---------------- | -------------------------- | -------------------------------------------- |
| `palette`        | `string[]`                 | Override the default palette                 |
| `dataLabels`     | `boolean`                  | Render value labels on bars / points         |
| `onClick`        | `(hit, point, chart) => void` | Click handler                             |
| `xTickFormat`    | `(value, index) => string` | Format x-axis ticks                          |
| `yTickFormat`    | `(value) => string`        | Format y-axis ticks                          |

### Math helpers

```js
import { math, PALETTE } from '@aeriz/chart';

math.clamp(15, 0, 10);          // 10
math.lerp(0, 1, 0.5);           // 0.5
math.niceTicks(0, 100, 5);      // [0, 20, 40, 60, 80, 100]
math.niceDomain(3, 97, 5);      // [0, 100]
math.colorMix('#000', '#fff', 0.5);
PALETTE;                        // 8-color qualitative palette
```

Full list: `TAU`, `clamp`, `lerp`, `extent`, `niceTicks`, `niceDomain`,
`timeNiceTicks`, `timeFormat`, `minMaxDecimate`, `shouldDecimate`,
`bezierControlPoints`, `springStep`, `colorParse`, `colorAlpha`,
`colorMix`, `PALETTE`.

---

## Files

```
src/
  index.js          # public entry — re-exports
  index.d.ts        # TypeScript definitions
  math.js           # scales, ticks, color, decimation
  engine.js         # canvas primitives + DPR
  base.js           # Chart base class
  charts.js         # 20 chart types
  composite.js      # mixed chart

dist/
  aeriz.js          # IIFE — window.Aeriz (~94 KB)
  aeriz.min.js      # IIFE minified (~58 KB) — CDN default
  aeriz.esm.js      # ES module
  aeriz.esm.min.js  # ES module minified
  aeriz.cjs         # CommonJS — for `require('@aeriz/chart')`
  aeriz.min.cjs     # CommonJS minified

scripts/
  build.mjs         # build all 6 dist files
test/
  verify.mjs        # 53 unit + integration tests
  bundle.mjs        # IIFE bundle integrity
```

Total source: ~2,600 lines for 21 chart types.

---

## Build

Zero dependencies. Just run:

```bash
node scripts/build.mjs
```

Produces all 6 files in `dist/`.

---

## Test

```bash
npm test
```

53 tests covering:

- Engine DPR + dimensions
- Math primitives (`clamp`, `lerp`, ticks, color ops)
- Pixel-perfect mouse coordinates
- `invalidate()` theme reactivity (palette + gradient cache)
- All 21 chart types instantiate + render
- State overlays (`loading`/`empty`/`error`/`clear`)
- Tooltip lifecycle, click handlers, animation completion
- Destroy cleanup (no leaked listeners or DOM)

---

## License

[Apache License 2.0](LICENSE) — Copyright © 2026
[code-repositories](https://github.com/code-repositories).
