Get Started
Introduction
Components
Accordion
Badge
Button
Card
Checkbox
Command
Dialog
Dropdown Menu
Input
Select
Switch
Table
Tabs
Toast
Tooltip
Forms
Introduction
Validation
Field Arrays

Pivot Table

Dynamic row/column grouping with multi-level aggregation, drag-and-drop axis configuration, and expand/collapse group rows.

#Preview

Pivot Table

16 records 13 rows
Available
Salesperson
Quantity
Rows
Region
Product
Columns
Quarter
Values
Amount ($)
Aggregate by:
Sum
Average
Count
Region / ProductQ1Q2Q3Q4Total
East
15,8009,80016,20010,50052,300
Gadget
9,80010,50020,300
Widget
15,80016,20032,000
North
20,20014,5009,10043,800
Gadget
8,2009,10017,300
Widget
12,00014,50026,500
South
6,70011,5007,40013,20038,800
Gizmo
6,7007,40014,100
Widget
11,50013,20024,700
West
5,5006,20017,10011,30040,100
Gadget
11,30011,300
Gizmo
5,5006,20011,700
Widget
17,10017,100
Grand Total48,20042,00049,80035,000175,000
Aggregation: sum· Value: Amount ($)
1"use client"23import { createSignal, createMemo } from '@barefootjs/client'45const SAMPLE_DATA = [6  { region: 'North', product: 'Widget', quarter: 'Q1', amount: 12000 },7  // ...16 records total8]910function PivotTable() {11  const [rowFields, setRowFields] = createSignal(['region', 'product'])12  const [columnField, setColumnField] = createSignal('quarter')13  const [aggregationFn, setAggregationFn] = createSignal('sum')14  const [expandedGroups, setExpandedGroups] = createSignal(new Set(['North', 'South', 'East', 'West']))1516  // L2: group data by row fields17  const groupedData = createMemo(() =>18    buildGroupTree(SAMPLE_DATA, rowFields(), 0)19  )2021  // L3: unique column split values22  const columnValues = createMemo(() => {23    const col = columnField()24    return [...new Set(SAMPLE_DATA.map(r => r[col]))].sort()25  })2627  // L4: aggregate each (groupPath, columnValue) pair28  const aggregatedCells = createMemo(() => {29    const fn = aggregationFn()30    const col = columnField()31    const colVals = columnValues()32    // ... computes cells for all (row × column) intersections33    return computeCells(groupedData(), col, colVals, fn)34  })3536  // L5: flatten tree respecting expand/collapse state37  const visibleRows = createMemo(() =>38    flattenTree(groupedData(), expandedGroups(), '')39  )4041  // L6: grand totals per column42  const grandTotals = createMemo(() =>43    computeTotals(SAMPLE_DATA, columnField(), columnValues(), aggregationFn())44  )4546  return (47    <table>48      <thead>49        <tr>50          <th>Groups</th>51          {columnValues().map(cv => <th key={cv}>{cv}</th>)}52          <th>Total</th>53        </tr>54      </thead>55      <tbody>56        {visibleRows().map(row => (57          <tr key={row.id}>58            <td style={`padding-left: ${12 + row.depth * 16}px`}>59              {row.isGroup && (60                <button onClick={() => toggleExpand(row.id)}>▶</button>61              )}62              {row.label}63            </td>64            {columnValues().map(cv => (65              <td key={cv}>{aggregatedCells()[row.id]?.[cv] ?? '—'}</td>66            ))}67            <td>{aggregatedCells()[row.id]?.['__total__']}</td>68          </tr>69        ))}70      </tbody>71      <tfoot>72        <tr>73          <td>Grand Total</td>74          {columnValues().map(cv => <td key={cv}>{grandTotals()[cv]}</td>)}75          <td>{grandTotals()['__total__']}</td>76        </tr>77      </tfoot>78    </table>79  )80}

#Features

6-Level Memo Dependency Chain

rowFields signal → groupedData memo → aggregatedCells memo (also depends on columnValues, valueField, aggregationFn) → visibleRows memo (also depends on expandedGroups) → render. Changing aggregationFn only recomputes aggregatedCells and downstream — groupedData is not re-evaluated, demonstrating memo caching.

Nested Group Loops

Row fields create a hierarchical tree (e.g., Region → Product). Expand/collapse state is tracked in a Set signal. The visibleRows memo flattens the tree respecting expansion state, producing a dynamic flat list for rendering. Each group row shows aggregated values computed from all descendant records.

Dynamic Loop Structure via Axis Reconfiguration

Drag fields between Available, Rows, Columns, and Values zones to restructure the table. Removing a row field collapses a grouping level; removing the column field eliminates the column split and shows a single Total column. These changes propagate through the entire 6-level memo chain, testing dynamic loop reconstruction.

Multi-Input Memo Convergence

aggregatedCells depends on groupedData (L2), columnValues (L3), valueField signal, and aggregationFn signal — four upstream sources converging on one memo. This tests that the compiler correctly batches multi-source updates without redundant recomputation.

Drag-and-Drop Axis Configuration

Fields are draggable between axis zones using the HTML5 Drag and Drop API. Each zone enforces type constraints (dimensions only in Rows/Columns, measures only in Values). X buttons provide a non-drag fallback for removing fields from an axis.