Skip to main content

Dash TensorGrid

Plug-and-play OLAP / pivot grid for Dash -- the enterprise-grade, multi-dimensional data grid that Dash has been missing, free and open-source.

Symmetric roll-up / roll-down, aggregations from sum to formula and arbitrary Python (including numpy linear algebra), drill-through to source rows, and a Polars backend that does all the maths so the browser does none of it.

Status: alpha (0.1.0) — usable today; APIs may still change before 1.0. See CHANGELOG.md for progress.

Pure Python, no Node required

Dash TensorGrid ships as a normal Python package. The compiled JavaScript component is bundled inside the wheel, so installing and using it needs only pip -- no Node.js, no npm, on any machine.

pip install dash-tensor-grid            # core (Polars backend)
pip install "dash-tensor-grid[pandas]"  # plus the pandas ingestion path
pip install "dash-tensor-grid[yaml]"    # plus YAML data sources + YAML table configs

(Node.js is only needed by maintainers who change the React source; the rebuilt bundle is committed so the wheel can be built and published with Python alone.)

Why

Client-side grids choke on nested group aggregation, and the existing answers for server-side row models and pivoting in Dash sit behind paid enterprise licences. Dash TensorGrid moves all aggregation to a fast Polars backend and ships a deliberately thin React frontend: the backend owns the data, the frontend owns the view.

Feature highlights

  • Symmetric roll-up / roll-down of hierarchical dimensions.
  • Every aggregation style in one API:
    • built-in names: sum, mean, min, max, product, median, std, ...
    • Polars expressions: weighted sums, ratios of sums, dot products (vectorised).
    • arbitrary Python callables over each group: numpy matrix products, custom maths.
    • calculated measures: diff, multiplication, division, exponentiation between measures.
  • Drill-through from any aggregated cell back to the source rows.
  • Searching, sorting, and typed column filters (text, numeric range, select) - client-side and instant.
  • Light and dark themes, fully customisable via CSS variables (theme_overrides); reads common host CSS variables so it blends into your app's existing theme.
  • Action-button columns and computed synthetic columns.
  • Agnostic input: Polars, pandas, list[dict], or a JSON string.
  • Correct at every level: parents re-aggregate from source rows, so means, medians and ratios never suffer the average-of-averages error.

Spreadsheet & grid interactions

  • Cell range selection (marquee): drag or shift-click a rectangular block (enable_range_selection -> range_selection), copy it as TSV with Ctrl/Cmd-C (clipboard_copy), and total it with the engine into a status-bar range aggregation (GridDataEngine.aggregate_cells -> range_aggregation).
  • Floating filter row under the header (enable_floating_filters) and quick-filter match highlighting (highlight_matches).
  • Column tool-panel to show/hide + reorder columns (enable_column_panel), plus a density toggle (enable_density_toggle) and PDF / print export (enable_print).
  • Clear-all-filters button with an active-filter count (enable_clear_filters) and a keyboard-shortcut help dialog (enable_shortcut_help).
  • Row drag-reorder (enable_row_drag -> row_moved) and cell overflow tooltips (overflow_tooltip).
  • Auto-group column for a tidy tree/hierarchy presentation (auto_group_column_def); adjacency-list tree ingestion (GridDataEngine.build_adjacency_tree).
  • Per-column aggregation footer computed by the engine (GridDataEngine.column_footer -> footer).
  • Typed cell renderers: rating, boolean/checkbox, email/url/phone links, tags, badges/chips, progress bars, sparklines, data bars and icon sets - all display-only.
  • Scales to large grids: row and column virtualization (enable_virtualization, enable_column_virtualization), pagination, pinned rows and columns; touch / pointer support; keyboard navigation; internationalised UI strings (locale_text). The frontend still does zero maths.

Quick start

import polars as pl
from dash import Dash, Input, Output, callback
from dash_tensor_grid import TensorGrid, GridDataEngine

df = pl.DataFrame(
    {
        "region": ["EMEA", "EMEA", "AMER", "AMER"],
        "country": ["DE", "FR", "US", "US"],
        "revenue": [100.0, 80.0, 200.0, 40.0],
        "cost": [60.0, 50.0, 120.0, 25.0],
    }
)
engine = GridDataEngine(df)

app = Dash(__name__)
app.layout = TensorGrid(id="grid", data_mode="full")


@callback(Output("grid", "row_data"), Output("grid", "column_defs"), Input("grid", "id"))
def load(_):
    agg = {"revenue": "sum", "cost": "sum"}
    calculated = {"margin": lambda m: (m["revenue"] - m["cost"]) / m["revenue"]}
    rows = engine.get_tree_payload(["region", "country"], agg, calculated=calculated)
    cols = engine.make_column_defs(
        ["region", "country"], agg, calculated=calculated,
        formats={"revenue": "currency:USD", "cost": "currency:USD", "margin": "percentage:1"},
    )
    return rows, cols


if __name__ == "__main__":
    app.run(debug=True)

Examples

Runnable demos over synthetic high-dimensional data (no JS build needed):

  • examples/engine_demo.py -- roll-up tree, lazy mode, drill-through (engine only).
  • examples/aggregations_showcase.py -- every aggregation style, incl. a weighted vector dot product and a numpy matrix-multiplication aggregation.
  • examples/showcase_app.py -- feature-dense grid (grouping, selection, export, cell-click, column chooser, action buttons with icons), all via callbacks.
  • examples/pivot_app.py / examples/pivot_demo.py -- symmetric column pivoting.
  • examples/client_side_agg.py -- front-end (in-browser) aggregation mode.
  • examples/formula_app.py -- complex calculated measures evaluated in JS.
  • examples/config_app.py + examples/table_config.yaml -- the whole table defined in a declarative YAML config.
  • examples/lazy_app.py -- lazy / server-side row model over 200k rows.
  • examples/infinite_app.py -- infinite (server-side) row model over a huge flat table.
  • examples/actions_app.py -- action-button columns + a computed synthetic column.
  • examples/editing_app.py -- inline cell editing (double-click, commit on Enter).
  • examples/master_detail_app.py -- master/detail: expand a row to a detail panel.
  • examples/source_editing_app.py -- master/detail SOURCE-row editing: edit a detail cell and apply_edit recomputes every aggregate server-side (stale edits rejected).
  • examples/pivot_ui_app.py -- interactive pivot configurator (drag Rows/Columns/Values).
  • examples/showcase_colgroups.py -- nested column groups without aggregation.
  • examples/showcase_pivot.py -- column groups with aggregation (a cross-tab pivot).
  • examples/watchlist_app.py -- in-cell sparkline / bar charts.
  • examples/showcase_locale.py -- locale / region number & date formatting.
  • examples/adjacency_app.py -- flat parent/child rows -> nested tree with rollups.
  • examples/range_agg_app.py -- cell range selection -> engine-computed aggregation.
  • examples/auto_group_app.py -- the auto-group (hierarchy) column.
  • examples/footer_app.py -- an engine-computed per-column aggregation footer.
  • examples/showcase_all.py -- many interaction features enabled on one grid.
  • usage.py -- a full Dash application.

The multi-framework monorepo (tensor-grid-*)

The same engine, beyond Dash: packages/ hosts an npm-workspaces monorepo that replicates the Python engine in TypeScript and ships thin adapters for multiple frontends. The Python Polars engine is the oracle — the TS port is verified byte-for-byte against golden fixtures generated by the real engine (tools/oracle/gen_fixtures.py), and CI re-proves parity against a freshly regenerated oracle on every push (parity-drift job). 110 node --test checks (golden-fixture parity suites + view / interaction / filter units).

Package What it is License
tensor-grid-contract Wire types + closed-enum guards + wire-format.md (the scalar serialization contract, incl. the editing write-back shape) SSPL-1.0
tensor-grid-core The TS engine replica: tree roll-up, pivot, reducers, formula DSL, drill-through, selection/export, source-row editing, view/virtualization/format/render helpers SSPL-1.0
tensor-grid-react, tensor-grid-vue, tensor-grid-vanilla Thin adapters: grid + pivot + virtual grid components/mounts SSPL-1.0

Feature surface (all adapters, one engine): roll-up grid, symmetric cross-tab, windowed virtual grid (65,640 nodes, O(window) DOM), click-to-sort, quick + column filters, ctrl/cmd-click selection with source-row CSV export, master-detail (leaf expands to its drill-through source rows), and editing in both modes — client-side (applyEdit: source-row write-back with a stale guard, aggregates recomputed by the engine) and server-side (the engine's apply_edit behind a POST /api/edit wire route: one round-trip returns the recomputed tree; stale edits are rejected by the engine). The frontend does zero math in every mode.

Framework-neutral filter UI. Beyond the quick filter, the tensor-grid-react and tensor-grid-vue adapters render an opt-in per-column filter across all three grids (enableColumnFilters): a root-dimension control chosen by rootFilter ('text' | 'select' | 'set' | 'conditions' | 'date' — substring box, single-value dropdown, multi-value checkbox list, an operator builder, or a from/to date range) plus a min/max range control per measure on <TensorGrid>. The same control is reused by <TensorVirtualGrid> (a filter bar above the windowed scroller — root dimension only) and <TensorPivot> (a filter row that prunes the row hierarchy). <TensorPivot> adds enableColumnGroupFilter: a corner checkbox picker that hides pivoted column groups — a client-side display prune (the grand-total column is kept; nothing is re-aggregated). All of it is driven by ONE framework-neutral core — the pure filterReducer, columnFilterPredicate, facetCounts (value lists), the single-sourced condition evaluator (conditions.ts), and the pivot column helpers (pivotColumnGroups / filterPivotColumnDefs) — over the contract's typed discriminated FilterValue model. Each adapter renders only the idiomatic controls (React hooks / Vue reactive) via one shared root-control builder per framework and drives the shared reducer through the exported useColumnFilters hook / composable — zero math in the adapter, same predicates client and server.

Conditional formatting. A cellStyles prop / option (per measure column) styles cells by their raw value: { revenue: [{ op: '<', value: 0, style: { color: 'red' }, className: 'neg' }] }. Rules use the SAME operator vocabulary as the filter conditions and are evaluated first-match-wins by the core evalCellStyle (a CellStyleRule model) — so a < format rule colours the same cells a < filter keeps, and the frontend does zero math. Supported on all three grids in every adapter and both reference renderers (renderGridHtml + renderPivotHtml): <TensorGrid> and <TensorVirtualGrid> key cellStyles by measure accessorKey; <TensorPivot> keys by measure name so a rule heatmaps every pivoted column cell of that measure. React/Vue take camelCase CSS in style; the string renderers auto-convert to kebab for the inline attribute.

Top-N. A topN prop / option ({ by, n, others?, othersLabel? }) keeps the top-n children per level by a measure and (with others) rolls the rest into an "Others" node that SUMS the measures — applied over the built tree via the core topNChildren (after filtering, before sort; the frontend does no math). Available on <TensorGrid> in all three adapters. Note it ranks every top-level node, so avoid combining it with grandTotal (the grand-total row would occupy a top-N slot).

Window columns. A windowColumns prop / option ([{ kind, by, as, header?, format?, descending?, dense?, percent? }]) adds derived read-only measure columns via core transforms — kind is one of runningTotal (cumulative sum of by across siblings), rank (1-based sibling rank), pctOfTotal (share of the grand total), pctOfParent (share of the parent), delta (change from the previous sibling — period-over-period; absolute, or a fraction when percent: true; a grand-total row is skipped), or movingAverage (trailing simple moving average over the last window siblings — default 3). Each writes into key as and renders as an extra measure column after the base measures — the frontend does no math. The percent kinds store a fraction in [0, 1], so format them with a % pattern (e.g. format: '0.0%'). Available on <TensorGrid> in all three adapters.

Footer (grand-total) row. A showFooter prop / option renders a sticky <tfoot> with each base measure aggregated over the WHOLE frame via core gridGrandTotal (the engine's semantics — not a client-side sum of pre-aggregated leaves, which would give mean-of-means); footerLabel sets the label (default "Total"). Available on <TensorGrid> in all three adapters + the reference renderGridHtml.

Color scale (heatmap). A colorScales prop / option ({ revenue: { min, max, mid?, domainMin?, domainMax? } }) heatmaps a measure column — each cell's background is interpolated from its raw value's position in that column's range (over the whole tree, excluding a grand-total row so it doesn't compress the scale). Presentation only (like formatting) — the grid does no math; an explicit cellStyles background wins. Available on <TensorGrid> in all three adapters + the reference renderGridHtml.

Data bars. A dataBars prop / option ({ revenue: { color, domainMin?, domainMax? } }) draws an in-cell bar (a linear-gradient background) whose length is the value's fraction of [0, column max] — works on base measures and window columns. Cell-background precedence when several apply: an explicit cellStyles background > dataBars > colorScales. Available on <TensorGrid> in all three adapters + the reference renderGridHtml.

Column pinning. A pinFirstColumn prop / option freezes the group (label) column via position: sticky so it stays visible on horizontal scroll (handy when many measure / window columns overflow). The adapter positions the group cells and tags them with a tg-pinned class; the HOST wraps the grid in an overflow-x: auto container and gives .tg-pinned an opaque background (so scrolled content does not bleed through and row hover / selection styling still applies). Available on <TensorGrid> / mountTensorGrid in all three adapters, and on the reference renderGridHtml.

Range selection. Beyond ctrl/cmd-click toggle, a shift-click extends the selection to the contiguous span of rows from the anchor to the clicked row (inclusive, either direction, in visible order). The anchor is seeded by a ctrl/meta toggle or a prior shift-range, and the core selectionRange(orderedIds, anchor, target) owns the range math — the adapter only wires the event and unions the result into the selection. Available on <TensorGrid> / mountTensorGrid in all three adapters: React/Vue fire onSelectionChange with the selected ids; the vanilla mount exposes them via handle.getSelected().

Copy to clipboard. With a non-empty selection, Ctrl/Cmd-C copies the selected SOURCE rows as CSV. Enable it with enableClipboard (best-effort write to the system clipboard) and/or onCopy(csv) (a <TensorGrid> prop in React/Vue, a mountTensorGrid option in vanilla — receive the CSV directly). It composes the core toCsv(rowsForSelection(...)) — the same path the vanilla handle.selectionToCsv() uses — so the adapter does no math. Available on <TensorGrid> / mountTensorGrid in all three adapters.

Expand controls. Reveal or collapse the tree to any depth in one click. React/Vue take an opt-in showExpandControls prop that renders an Expand all / Collapse all / Level N toolbar above the grid; the vanilla mount exposes it imperatively as handle.expandToLevel(n) / handle.expandAll() / handle.collapseAll(). All of it runs the core expandToDepth(tree, depth) — which returns the expanded-state that reveals every node down to depth — so the adapter owns no expansion math (the host styles the .tg-expand-controls chrome).

Selection status bar. An opt-in showStatusBar (a <TensorGrid> prop in React/Vue, a mountTensorGrid option in vanilla) renders a .tg-status-bar footer with Selected: N and, per measure, count/sum/avg/min/max computed by the core summarizeRows over the selected SOURCE rows. It only appears while a selection exists, and the host styles the .tg-status-bar / .tg-status-count / .tg-status-measure chrome — the adapter does no math.

Keyboard navigation. An opt-in enableKeyboardNav (a <TensorGrid> prop in React/Vue, a mountTensorGrid option in vanilla) turns on roving-tabindex keyboard operation: Arrow Up/Down move a focused active row (.tg-active), Home/End jump to first/last, Space toggles the active row's selection, Shift+Arrow extends the selection range (core selectionRange), and Enter / ArrowRight / ArrowLeft expand or collapse a group row. The host styles the .tg-active chrome; the adapter wires the events and reuses the core range math.

Built-in search. An opt-in showSearch (a <TensorGrid> prop in React/Vue, a mountTensorGrid option in vanilla; plus searchPlaceholder) renders a built-in .tg-search input above the grid that filters rows via the core quickFilterPredicate (case-insensitive substring over the row dimensions). It ANDs with the quickFilter prop / setFilter predicate rather than replacing it; the host styles the .tg-search / .tg-search-input chrome. The vanilla mount keeps the input persistent across its innerHTML rebuild (a .tg-grid-host split) so its focus and cursor survive typing.

CSV export. An opt-in showExportButton renders an Export button above the grid; clicking it fires onExport(csv) with the CSV of the selected SOURCE rows (core rowsForSelection + toCsv) if a selection exists, else all source rows. React/Vue are callback-only (the host wires the download/upload); the vanilla mount additionally triggers a best-effort .csv download and exposes handle.exportCsv() (headless — the same selection-or-all CSV). The host styles the .tg-export / .tg-export-button chrome; the adapter does no math.

Selection toolbar. An opt-in showSelectionToolbar (a <TensorGrid> prop in React/Vue, a mountTensorGrid option in vanilla; plus selectAllLabel / clearSelectionLabel) renders a .tg-selection-toolbar with "Select all" / "Clear" buttons above the grid — a one-click complement to the export button (select all, then export the selection). "Select all" selects every currently-visible row except synthetic aggregate rows (the grand-total "Total" row and top-N "Others" roll-ups — core isSyntheticNodeId), since those do not map to selectable source rows; "Clear" empties the selection. React/Vue fire onSelectionChange; vanilla is pull-model (read the result via handle.getSelected()). The host styles the .tg-select-all / .tg-clear-selection chrome.

Density toggle. An opt-in showDensityToggle (a <TensorGrid> prop in React/Vue, a mountTensorGrid option in vanilla; plus a density initial level and, for React/Vue/vanilla, an onDensityChange callback) renders a .tg-density-toggle button that cycles the row density comfortable -> standard -> compact, applying the chosen level as a tg-density-<level> class on the .tg-grid table so the host styles the row padding/height. Setting just density (without the toggle) pins the level to that prop — the host controls it, and changing it later re-applies the class (React re-syncs via effect, Vue via a watcher, vanilla via handle.update({ density })). Not applicable to mountVirtualGrid, which sizes rows by a fixed rowHeight. The adapter does no layout math — it only toggles the class; mirrors the Dash component's enable_density_toggle.

No-rows message. An opt-in noRowsMessage (a <TensorGrid> prop in React/Vue, a mountTensorGrid option in vanilla) renders a single full-width .tg-no-rows placeholder row when the grid flattens to zero rows (e.g. a search/filter matched nothing) instead of an empty body. Omitted = empty body (byte-identical back-compat); the host styles the .tg-no-rows / .tg-no-rows-cell chrome. Mirrors the Dash component's no_rows_message.

Column visibility. An opt-in showColumnToggle (a <TensorGrid> prop in React/Vue, a mountTensorGrid option in vanilla; plus hiddenColumns for the initial hidden set and an onColumnVisibilityChange callback) renders a .tg-column-toggle control — one checkbox per measure column that shows/hides it entirely client-side (the group/dimension column is always shown). No backend round-trip: the adapter just renders fewer columns. The host styles the .tg-column-toggle / .tg-column-toggle-item chrome.

Column resize. An opt-in resizableColumns (a <TensorGrid> prop in React/Vue, a mountTensorGrid option in vanilla; plus columnWidths for initial widths and an onColumnResize callback) adds a drag handle to each header — the measure columns and the group/dimension column (keyed "__group__" in the widths map, kept sticky when pinFirstColumn is on) — dragging sets that column's width (client view state; core renderGridHtml emits the .tg-resize-handle). For strict widths incl. shrinking below content, give the table table-layout: fixed; otherwise a width acts as a minimum. The host styles the .tg-resize-handle chrome (e.g. an absolutely-positioned right-edge grip with cursor: col-resize).

npm install
npm run build:ts        # tsc -b (strict) across the workspaces
npm run test:ts         # 147 node:test checks incl. Polars-oracle parity

# Demos: serve packages/ statically and open /index.html
# The server-mode demo instead runs the real Python engine (thread-capped):
uv run python scripts/preview_wire_api.py   # then open http://127.0.0.1:8067/server-demo.html

Documentation

The examples/dash_gallery app is a runnable gallery of every feature — aggregation, pivot / cross-tab, the formula DSL, drill-through, number / date formatting, conditional formatting, live streaming, export, and more — each with its input data and source shown. Run it with:

python examples/dash_gallery/app.py    # then open http://127.0.0.1:8050/

Development

Hybrid Python package plus React component. Python 3.12 is the hard floor.

uv sync                  # Python deps (runtime + dev)
.\scripts\run_tests.ps1  # tests (core- and thread-capped)

# Only when the React source under src/ changes (needs Node.js):
npm install
npm run build            # rebuilds the bundle + generated Python class (commit them)
uv build                 # build the wheel (Python only)

# The tensor-grid-* TS monorepo (separate from the Dash bundle build):
npm run build:ts         # strict tsc -b across packages/
npm run test:ts          # node:test parity + unit suites

This package is Polars-based, and Polars uses every CPU core by default. Run tests and benchmarks through scripts/run_tests.ps1, which caps cores and threads to keep resource use bounded.

Licence

Server Side Public License, v1 (SSPL-1.0). Every package — the Dash component, tensor-grid-contract, tensor-grid-core, and the React / Vue / Vanilla adapters — is licensed under the SSPL.

In plain terms (the MongoDB model):

  • Free to use, self-host, modify, and build products on — individuals and companies alike, including internal/commercial use inside your own applications.
  • Offering TensorGrid as a service to third parties (a hosted / managed "TensorGrid-as-a-service", the AWS / Azure / cloud-provider case) triggers the SSPL's copyleft: you must release the entire service source under the SSPL, or obtain a commercial license.

Commercial licensing (to embed or offer TensorGrid without the SSPL's service-source obligations) is available from INTENDEV LTD — see COMMERCIAL-LICENSE.md.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dash_tensor_grid-0.1.3.tar.gz (2.9 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dash_tensor_grid-0.1.3-py3-none-any.whl (579.5 kB view details)

Uploaded Python 3

File details

Details for the file dash_tensor_grid-0.1.3.tar.gz.

File metadata

  • Download URL: dash_tensor_grid-0.1.3.tar.gz
  • Upload date:
  • Size: 2.9 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.11

File hashes

Hashes for dash_tensor_grid-0.1.3.tar.gz
Algorithm Hash digest
SHA256 8254500b0ba8d81da9b4a050d8826ba44a376fef0e537fdfac6e74a914bd791d
MD5 ce095d86ca39acf7f95e8d42182477f6
BLAKE2b-256 e022f788d08b8072f82f7680799eeb8fd5770809b9a5f065ab86f45630dbeba6

See more details on using hashes here.

File details

Details for the file dash_tensor_grid-0.1.3-py3-none-any.whl.

File metadata

File hashes

Hashes for dash_tensor_grid-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 2d3e3c43bb40af57b2f7d9c9bf5fc19e8b7b65874e6afd689f3892f6cc0990b1
MD5 ebd8ca7e576d798594177332771d7917
BLAKE2b-256 a3d7299a6947322a74a80f049123451bde52133622a270ef0b79a185d9cb5f31

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page