npm package: @pxch/pixi-charts
Description: GPU-accelerated chart library built on PixiJS
Latest version: 0.1.0

# @pxch/pixi-charts

GPU-accelerated chart library built on PixiJS v8.

[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)

## Features

- **GPU-accelerated rendering** -- WebGL2/WebGPU via PixiJS v8
- **Pie and donut charts** with configurable slices, labels, and legends
- **Smooth animations** -- sweep/fade entry animations, data transitions
- **Interactive** -- hover tooltips, click selection with typed events
- **Configurable theming** -- colors, typography, spacing (Tableau 10 default palette)
- **Framework-agnostic imperative API** -- works with any framework or vanilla JS
- **Angular 19+ components** -- standalone signal-based wrappers
- **Full TypeScript support** with type declarations for all exports
- **Tree-shakeable ESM output** -- import only what you need

## Installation

```bash
npm install @pxch/pixi-charts pixi.js
```

`pixi.js` is a peer dependency and must be installed alongside the library.

## Quick Start (Imperative API)

```html
<div id="chart" style="width: 400px; height: 400px;"></div>
```

```ts
import { createPieChart } from '@pxch/pixi-charts';

const chart = await createPieChart(document.getElementById('chart')!, {
  data: [
    { value: 30, label: 'Chrome' },
    { value: 25, label: 'Firefox' },
    { value: 20, label: 'Safari' },
    { value: 15, label: 'Edge' },
    { value: 10, label: 'Other' },
  ],
  legend: { position: 'bottom' },
});

// Update data reactively
chart.update({
  data: [
    { value: 40, label: 'Chrome' },
    { value: 30, label: 'Firefox' },
    { value: 30, label: 'Safari' },
  ],
});

// Listen for events
chart.on('select', (e) => console.log('Selected:', e));

// Clean up when done
chart.destroy();
```

The `createPieChart` function returns a `Promise<PieChartHandle>` with these methods:

| Method                | Description                              |
| --------------------- | ---------------------------------------- |
| `update(options)`     | Update data and/or options               |
| `destroy()`           | Destroy the chart, release GPU resources |
| `getTheme()`          | Get a copy of the current resolved theme |
| `setTheme(theme)`     | Set a new theme (merges with defaults)   |
| `getCanvas()`         | Get the underlying canvas element        |
| `on(event, handler)`  | Subscribe to a chart event               |
| `off(event, handler)` | Unsubscribe from a chart event           |

## Donut Chart

```ts
import { createDonutChart } from '@pxch/pixi-charts';

const chart = await createDonutChart(document.getElementById('chart')!, {
  data: [
    { value: 60, label: 'Used' },
    { value: 40, label: 'Free' },
  ],
});
```

`createDonutChart` is a convenience wrapper that sets `innerRadius` to `0.6` (60% of the outer radius) by default. You can override it:

```ts
const chart = await createPieChart(container, {
  data: myData,
  innerRadius: 0.5, // custom inner radius ratio
});
```

Values between 0 and 1 are treated as a proportion of the outer radius. Values >= 1 are treated as absolute pixels.

## Quick Start (Angular)

Requires `@angular/core` >= 19.0.0. The Angular wrappers are standalone signal-based components.

```ts
import { PixiPieChartComponent } from '@pxch/pixi-charts/angular';
import { PixiDonutChartComponent } from '@pxch/pixi-charts/angular';
```

```ts
@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [PixiPieChartComponent],
  template: `
    <pixi-pie-chart
      [data]="chartData"
      [options]="chartOptions"
      [width]="'400px'"
      [height]="'400px'"
      (select)="onSliceSelect($event)"
      (hover)="onSliceHover($event)"
    />
  `,
})
export class DashboardComponent {
  chartData: SliceData[] = [
    { value: 30, label: 'Chrome' },
    { value: 25, label: 'Firefox' },
    { value: 20, label: 'Safari' },
  ];

  chartOptions = {
    legend: { position: 'bottom' as const },
    theme: { colors: { palette: [0x4e79a7, 0xf28e2b, 0xe15759] } },
  };

  onSliceSelect(event: SelectEvent) {
    console.log('Selected slice:', event);
  }

  onSliceHover(event: HoverEvent) {
    console.log('Hovered slice:', event);
  }
}
```

### Angular Component Inputs

| Input     | Type                             | Default  | Description              |
| --------- | -------------------------------- | -------- | ------------------------ |
| `data`    | `SliceData[]`                    | required | Chart slice data         |
| `options` | `Partial<PieChartCreateOptions>` | `{}`     | Chart configuration      |
| `width`   | `string`                         | `'100%'` | CSS width of container   |
| `height`  | `string`                         | `'100%'` | CSS height of container  |

### Angular Component Outputs

| Output      | Type              | Description                   |
| ----------- | ----------------- | ----------------------------- |
| `select`    | `SelectEvent`     | Slice selected                |
| `hover`     | `HoverEvent`      | Slice hovered                 |
| `rendered`  | `RenderedEvent`   | Chart finished initial render |
| `destroyed` | `DestroyedEvent`  | Chart destroyed               |
| `error`     | `ChartErrorEvent` | Chart encountered an error    |

## Theming

Charts use the Tableau 10 palette by default. Customize by passing a theme object:

```ts
import { createPieChart } from '@pxch/pixi-charts';

const chart = await createPieChart(container, {
  data: myData,
  theme: {
    colors: {
      palette: [0x1f77b4, 0xff7f0e, 0x2ca02c, 0xd62728],
      background: 0xf5f5f5,
      text: 0x333333,
      textMuted: 0x999999,
    },
    typography: {
      fontFamily: 'Inter, sans-serif',
      labelFontSize: 12,
      titleFontSize: 18,
    },
    spacing: {
      chartPadding: 20,
      legendItemGap: 8,
      legendGap: 16,
      tooltipPadding: 8,
    },
  },
});
```

Use `mergeTheme` for partial overrides on top of defaults:

```ts
import { mergeTheme, defaultTheme } from '@pxch/pixi-charts/themes';

const myTheme = mergeTheme(defaultTheme, {
  colors: { palette: [0x1f77b4, 0xff7f0e, 0x2ca02c] },
  typography: { fontFamily: 'Inter, sans-serif' },
});
```

You can also update the theme at runtime:

```ts
chart.setTheme({
  colors: { palette: [0xe15759, 0x76b7b2, 0x59a14f] },
});
```

## Labels

Configure labels with the `labels` option:

```ts
const chart = await createPieChart(container, {
  data: myData,
  labels: {
    position: 'outside',  // 'inside' | 'outside' | 'auto'
    formatter: (data) => `${data.label}: ${data.percentage}%`,
  },
});
```

Labels are enabled by default. Disable them with `labels: false`.

Position modes:
- **`'inside'`** -- Labels rendered inside slices at 70% from inner to outer radius
- **`'outside'`** -- Labels rendered outside with leader lines
- **`'auto'`** (default) -- Inside for large slices, outside for small slices (< 15 degrees)

HTML label templates are also supported:

```ts
labels: {
  position: 'outside',
  htmlTemplate: '<b>{{label}}</b>: {{percentage}}%',
}
```

Template variables: `{{value}}`, `{{percentage}}`, `{{label}}`, `{{index}}`.

## Legend

Enable the legend by providing a position:

```ts
const chart = await createPieChart(container, {
  data: myData,
  legend: {
    position: 'bottom',  // 'top' | 'bottom' | 'left' | 'right'
  },
});
```

The legend is interactive -- click an entry to select its slice, hover to highlight.

## Events

Subscribe to typed events using `on()` and `off()`:

```ts
// Slice selection
chart.on('select', (e: SelectEvent) => {
  // e.index, e.data, e.selected, e.position
  console.log(`Slice ${e.index} ${e.selected ? 'selected' : 'deselected'}`);
});

// Slice hover
chart.on('hover', (e: HoverEvent) => {
  // e.index, e.data, e.hovering
  console.log(`Hovering slice ${e.index}`);
});

// Lifecycle events
chart.on('rendered', () => console.log('Chart rendered'));
chart.on('destroyed', () => console.log('Chart destroyed'));
chart.on('error', (e) => console.error('Chart error:', e.message));

// Unsubscribe
const handler = (e: SelectEvent) => { /* ... */ };
chart.on('select', handler);
chart.off('select', handler);
```

## Architecture

```
+------------------------------------------------------------------+
|                      @pxch/pixi-charts                           |
|                                                                  |
|  +-------------------+    +-----------------------------------+  |
|  |   Angular Layer   |    |        Imperative API             |  |
|  |                   |    |                                   |  |
|  | PixiPieChartComp  |--->| createPieChart(container, opts)   |  |
|  | PixiDonutChartComp|--->| createDonutChart(container, opts) |  |
|  | (signal I/O)      |    | => PieChartHandle                 |  |
|  +-------------------+    +-----------------------------------+  |
|                                      |                           |
|                                      v                           |
|  +------------------------------------------------------------+  |
|  |                     Core Engine                             |  |
|  |                                                             |  |
|  |  ChartRenderer         PieChart          EventBus           |  |
|  |  (PixiJS v8 app)       (slice geometry)  (typed pub/sub)   |  |
|  +------------------------------------------------------------+  |
|                       |              |                            |
|          +------------+--------------+-------------+              |
|          v            v              v             v              |
|  +-----------+ +------------+ +-----------+ +-----------+        |
|  |  Theming  | | Animation  | |Interaction| |  Labels   |        |
|  |           | |            | |           | |           |        |
|  | colors    | | sweep/fade | | hover mgr | | inside/   |        |
|  | typo      | | transitions| | selection | | outside   |        |
|  | spacing   | | easing     | | tooltips  | | HTML tmpl |        |
|  +-----------+ +------------+ +-----------+ +-----------+        |
|                                      |                           |
|                                      v                           |
|                              +--------------+                    |
|                              |    Legend     |                    |
|                              | interactive  |                    |
|                              | hover/click  |                    |
|                              +--------------+                    |
|                                                                  |
+------------------------------------------------------------------+
                               |
                               v
                     +-------------------+
                     |    PixiJS v8      |
                     | WebGL2 / WebGPU  |
                     +-------------------+
```

The Angular layer is a thin wrapper around the imperative API. All rendering logic lives in the framework-agnostic core. Angular components use `NgZone.runOutsideAngular` to prevent PixiJS's `requestAnimationFrame` loop from triggering change detection.

## Subpath Exports

Import from granular subpaths for tree-shaking or direct access:

| Import Path                        | Contents                                  |
| ---------------------------------- | ----------------------------------------- |
| `@pxch/pixi-charts`               | Core API: createPieChart, createDonutChart, types, utilities |
| `@pxch/pixi-charts/angular`       | Angular components: PixiPieChartComponent, PixiDonutChartComponent |
| `@pxch/pixi-charts/themes`        | Theme types, defaultTheme, mergeTheme     |
| `@pxch/pixi-charts/animation`     | Animation engine, easing functions, types  |
| `@pxch/pixi-charts/interactions`  | Hover manager, selection manager, types    |
| `@pxch/pixi-charts/legend`        | Legend renderer and types                  |
| `@pxch/pixi-charts/tooltips`      | Tooltip renderer and types                |

## TypeScript

All types are exported. Configure your `tsconfig.json` with one of these module resolution strategies:

```json
{
  "compilerOptions": {
    "moduleResolution": "bundler"
  }
}
```

Or use `"node16"` / `"nodenext"`. Both correctly resolve the `exports` field in `package.json`.

Key types available from the root import:

```ts
import type {
  SliceData,
  PieChartHandle,
  PieChartCreateOptions,
  ChartEventMap,
  SelectEvent,
  HoverEvent,
  Theme,
  DeepPartial,
} from '@pxch/pixi-charts';
```

## Browser Support

Requires a modern browser with **WebGL2** or **WebGPU** support:

- Chrome 56+
- Firefox 51+
- Safari 15+
- Edge 79+

**No SSR support.** The library requires a browser context (DOM + WebGL/WebGPU). For server-rendered applications, load the chart component lazily on the client side.

## Peer Dependencies

| Package         | Version    | Required |
| --------------- | ---------- | -------- |
| `pixi.js`       | `^8.15.0`  | Yes      |
| `@angular/core` | `>=19.0.0` | No (only for Angular components) |

## License

[MIT](./LICENSE)