Pivot UI configurator
Let end users reshape the cross-tab themselves: drag fields between Rows, Columns, and
Values and the grid re-pivots. In Dash this is the built-in configurator (enable_pivot_ui) —
the panel only emits a pivot_state ({rows, columns, values}); a callback feeds it to
engine.pivot so the backend does the aggregation, never the browser. In the adapters you own a
small picker UI and change the component’s rows / cols / measures, which re-pivots client-side.
from dash import Dash, Input, Output, callback, no_updatefrom dash_tensor_grid import GridDataEngine, TensorGrid
ENGINE = GridDataEngine(sales)FIELDS = { "dimensions": ["region", "country", "year", "quarter", "category"], "values": ["revenue", "cost"], "labels": {"revenue": "Revenue", "cost": "Cost"},}INITIAL = {"rows": ["region", "country"], "columns": ["year"], "values": ["revenue"]}FORMATS = {"revenue": "currency:USD", "cost": "currency:USD"}
out = ENGINE.pivot(rows=INITIAL["rows"], cols=INITIAL["columns"], agg=dict.fromkeys(INITIAL["values"], "sum"), formats=FORMATS)
app = Dash(__name__)app.layout = TensorGrid( id="grid", row_data=out["row_data"], column_defs=out["column_defs"], enable_pivot_ui=True, # show the drag-and-drop configurator pivot_fields=FIELDS, # the fields it offers pivot_state=INITIAL, # seed the layout (the panel writes it back))
@callback(Output("grid", "row_data"), Output("grid", "column_defs"), Input("grid", "pivot_state"), prevent_initial_call=True)def repivot(state): rows, cols, vals = state.get("rows"), state.get("columns"), state.get("values") if not rows or not vals: return no_update, no_update out = ENGINE.pivot(rows=rows, cols=cols, agg=dict.fromkeys(vals, "sum"), formats=FORMATS) return out["row_data"], out["column_defs"]import { useState } from 'react';import { TensorPivot } from '@tensorgrid/react';
// You own the Rows / Columns / Values picker; changing rows/cols re-pivots client-side.export function Configurable({ data }: { data: Record<string, unknown>[] }) { const [rows, setRows] = useState(['region', 'country']); const [cols, setCols] = useState(['year']); return ( <TensorPivot data={data} rows={rows} cols={cols} measures={{ revenue: 'sum' }} /> ); // Wire setRows / setCols to your drag-and-drop panel.}<script setup lang="ts">import { ref } from 'vue';import { TensorPivot } from '@tensorgrid/vue';
const rows = ref(['region', 'country']);const cols = ref(['year']); // your picker mutates these; the pivot recomputes</script>
<template> <TensorPivot :data="data" :rows="rows" :cols="cols" :measures="{ revenue: 'sum' }" /></template>import { mountTensorPivot } from '@tensorgrid/vanilla';
const handle = mountTensorPivot(document.getElementById('grid'), { data, rows: ['region', 'country'], cols: ['year'], measures: { revenue: 'sum' },});
// From your own Rows / Columns / Values control, re-pivot (expansion state is kept):handle.update({ rows: ['category'], cols: ['year', 'quarter'] });pivot_stateis two-way in Dash — seed the initial layout with it, and the configurator writes it back on every change. Use it as a callbackInputto recomputerow_data/column_defswithengine.pivot. See the Dash props reference.pivot_fieldsdeclares what the panel offers:{dimensions, values, labels?}. Dimensions can be dropped into Rows or Columns; values become the aggregated measures.enable_group_collapsepairs well here — the reshaped column groups stay collapsible.- Adapters have no built-in panel: the drag-and-drop UI is a Dash affordance. In React / Vue /
Vanilla you supply the picker and drive
rows/cols(props) orhandle.update({ rows, cols }), and the component re-pivots. Start from a plain pivot.