ProPivot
⚡ Enterprise-grade · open source · runs entirely in the browser

Pivot millions of rows in a heartbeat — right inside your app.

ProPivot is an enterprise-grade, open-source JavaScript library for building pivot tables over millions of rows. It works with React, Angular, Vue, and any JavaScript framework — or plain JavaScript via a <script> tag. Lightning fast, lightweight, feature-rich, highly configurable — and instantly embedded in any project. The columnar engine aggregates entirely in the browser, with no server round-trips.

$ npm install @proteus/propivot

Live demo

Pivot real data, right here.

This grid is the actual library running in your browser on a generated dataset — no server, no iframe. Change the controls, drag fields, double-click a cell to drill through, export.

Tip: hover a column header and click ▾ to open its properties (aggregation, conditional formatting, display format, filter); drag a header edge to resize, or drag headers to reorder; double-click a value cell to drill through.

Features

Everything a pivot needs, nothing it doesn't.

A framework-agnostic core behind a stable facade — the same engine powers the headless API, the Web Worker, and the virtualized grid.

🧮

17 aggregations

Sum, average, median, distinct-count, stdev, plus the positional family — difference, %difference, runningtotals — along a configurable row/column axis.

Calculated measures

Formula parser + evaluator: sum('sales')/sum('qty'). Mix calculated and native measures freely; each measure aggregates independently.

🎨

Conditional formatting

Both #value# and #value dialects. Color cells by threshold; bring your own classes via customizeCell.

Virtualized grid

Viewport-only DOM with frozen headers handles high-cardinality data smoothly. Compact, flat, and classic layouts.

🖱️

Drag-drop field list

Move fields between Rows / Columns / Measures / Filters, change a measure's aggregation, sort by measure, Top-N filter — all at runtime.

📤

Five exports

CSV, HTML, real .xlsx, dependency-free .pdf, and an .svg image of the grid (rasterizes to PNG in-browser).

🧵

Optional Web Worker

Offload aggregation off the main thread behind one async interface — falls back automatically when workers aren't available.

🦆

DuckDB-WASM accelerator

Opt-in: offload aggregation of millions of rows to in-browser DuckDB SQL GROUPING SETS, with automatic, result-identical fallback to the built-in engine.

🔌

Any framework

First-class <Pivot> components for React, Vue, and an <pro-pivot> Angular wrapper. Or read window.ProPivot from a plain <script> — Svelte, jQuery, a WebView, anything.

Golden-tested

A two-layer golden suite pins both the computed cell matrix and the rendered grid, so the compatibility contract can't silently drift.

Works with any JavaScript framework

Instantly embedded in any project.

First-class React, Vue, and Angular components — or read window.ProPivot from a plain <script> for Svelte, jQuery, server-rendered pages, or an Android WebView. One report object drives them all. Full docs →

React

import { Pivot } from '@proteus/propivot/react';
import '@proteus/propivot/propivot.css';

<Pivot report={report} toolbar
  onReady={(pivot) => console.log('ready', pivot)} />;

Vue

import { Pivot } from '@proteus/propivot/vue';
import '@proteus/propivot/propivot.css';

<Pivot :report="report" toolbar
  @ready="onReady" />

Angular

// import { ProPivotComponent } from './pro-pivot.component';
<pro-pivot
  [report]="report"
  [toolbar]="true"
  (cellclick)="onCellClick($event)">
</pro-pivot>

Plain JavaScript

<!-- defines window.ProPivot -->
<script src="propivot.global.js"></script>

new ProPivot({ container: '#pivot',
  toolbar: true, report });

Usage

A report is just an object.

Describe the slice, measures, formats, and conditions declaratively. ProPivot does the rest.

const pivot = new ProPivot({
  container: '#pivot',
  toolbar: true,
  report: {
    dataSource: { type: 'json', data, mapping },
    slice: {
      rows: [{ uniqueName: 'region' }, { uniqueName: 'category' }],
      columns: [{ uniqueName: 'year' }],
      measures: [
        { uniqueName: 'sales', aggregation: 'sum', format: 'cur' },
        { uniqueName: 'aov', formula: "sum('sales')/sum('qty')", caption: 'Avg Price' },
        { uniqueName: 'sales', aggregation: 'runningtotals', positionalAxis: 'rows' },
      ],
    },
    formats: [{ name: 'cur', currencySymbol: '$', decimalPlaces: 0 }],
    conditions: [{ formula: '#value > 100000', measure: 'sales', format: { backgroundColor: '#c5e1a5' } }],
  },
  reportcomplete: () => console.log('ready'),
});