Grouping, spanners and pivots

Grouping and subtotals

Group rows and add subtotals when readers need to see both the individual rows and a rolled-up total per category, like revenue by region and segment.

What you can do

  • grouping.groupBy is a non-empty array of field names; a 2-field array nests groups two levels deep, outer field first.
  • aggregations computes one summary value per group, each an object of field plus agg: sum, avg, min, max, or count.
  • subtotals defaults to true and adds a summary row after each group; grandTotal defaults to false and adds one final row across the whole table.
  • defaultExpanded defaults to true; set it to false to load every group collapsed behind its caret.
  • totalLabel (default "Total") labels the grand total row; subtotalLabelTemplate (default "{value}") labels each subtotal row and can template {value} alongside literal text.
  • grouping and pivot both live on TableSpec; if a spec sets both, pivot wins, since the schema has no oneOf between them.

Set on TableSpec.grouping on a table panel; a chart:* panel has no grouping equivalent, since it groups by re-querying instead.

Spec

View spec
{
  "id": "revenue-by-region-segment",
  "type": "table",
  "title": "Revenue by region and segment",
  "data": {
    "rows": [
      {
        "region": "West",
        "segment": "Enterprise",
        "revenue": 410000,
        "deals": 12
      },
      {
        "region": "West",
        "segment": "SMB",
        "revenue": 180000,
        "deals": 30
      },
      {
        "region": "East",
        "segment": "Enterprise",
        "revenue": 390000,
        "deals": 9
      },
      {
        "region": "East",
        "segment": "SMB",
        "revenue": 150000,
        "deals": 22
      }
    ]
  },
  "spec": {
    "columns": [
      {
        "field": "region",
        "label": "Region"
      },
      {
        "field": "segment",
        "label": "Segment"
      },
      {
        "field": "revenue",
        "format": {
          "type": "currency",
          "currency": "USD",
          "compact": true
        },
        "align": "right"
      },
      {
        "field": "deals",
        "align": "right"
      }
    ],
    "grouping": {
      "groupBy": [
        "region",
        "segment"
      ],
      "aggregations": [
        {
          "field": "revenue",
          "agg": "sum"
        },
        {
          "field": "deals",
          "agg": "avg"
        }
      ],
      "subtotals": true,
      "grandTotal": true,
      "defaultExpanded": true,
      "totalLabel": "All regions",
      "subtotalLabelTemplate": "Subtotal: {value}"
    }
  }
}

groupBy nests region then segment; aggregations sums revenue and averages deals per group, and subtotalLabelTemplate templates {value} into each subtotal row’s label.

Try it live

Open the rich-tables template in the Builder

Open the rich-tables template, page p3-structure, panel p3-grouped for two-level grouping with subtotals, a grand total, two columnGroups, and three footnotes.

Build it in dvt

Every table capability on this site is a few lines of the same declarative JSON spec. Read the full spec format, or connect an AI agent to your warehouse and build one live in the quickstart.

Follow the build

dvt is in founding research. Leave your email and we'll reach out when there's something to see — no newsletters, no noise.

No spam. We use this to reach out directly, nothing else.