A Grafana panel plugin that provides a PowerBI-style matrix table with multi-level column and row hierarchy. Pivot flat data from any Grafana data source into a hierarchical table with dynamic column grouping — no manual grouping configuration needed.
- N-level column hierarchy — select multiple column fields, each becoming a header level with automatic
colspan - N-level row hierarchy — select multiple row fields, each becoming a left-hand header level with automatic
rowspan - Dynamic pivoting — new values in your data automatically create new column groups (no config changes needed)
- Aggregation — Sum, Average, Min, Max, or Count when multiple rows map to the same cell
- Row & column totals — toggleable summary columns and rows
- Grafana theme-aware — respects your Grafana theme for colors, fonts, and spacing
- Sticky table headers — column headers stay visible during vertical scroll
npm install
npm run build
docker compose up -dOpen http://localhost:3001 in your browser. A pre-configured demo dashboard titled "Matrix Table Demo" is automatically provisioned with sample data.
- Node.js 20 or later
- Docker Desktop (for local Grafana)
- npm 10 or later
Terminal 1 — Build the plugin (with live reload):
npm run devThis watches for file changes and rebuilds automatically.
Terminal 2 — Start local Grafana:
docker compose up -dGrafana starts at http://localhost:3001 with:
- Anonymous access enabled (no login required)
- Your plugin volume-mounted from
./dist - A pre-configured TestData data source
- A provisioned demo dashboard with sample data
After making code changes:
If using npm run dev, the plugin rebuilds automatically. Simply refresh the browser to see changes.
If using npm run build, rebuild manually then refresh:
npm run builddocker compose downTo also remove the Grafana data volume (resets all dashboards and settings):
docker compose down -vIf port 3001 is already in use on your machine, edit docker-compose.yaml and change the port mapping:
ports:
- '3099:3000' # Change 3099 to any free portProvide flat data from any Grafana data source. The plugin expects rows where:
- Some columns describe what each row represents (e.g., Category, Region)
- Some columns define the column axis (e.g., Month, Metric)
- One column holds the numeric value
Example query result:
| Category | Month | Metric | Amount |
|---|---|---|---|
| Revenue | June | Forecast | 500 |
| Revenue | June | Actuals | 480 |
| Revenue | June | Variance | 20 |
| Revenue | July | Forecast | 550 |
| Revenue | July | Actuals | 530 |
| Costs | June | Forecast | 200 |
| Costs | June | Actuals | 210 |
| … | … | … | … |
In the panel editor sidebar, configure:
| Setting | Example | Description |
|---|---|---|
| Row Fields | [Category] |
Fields displayed as left-hand row headers (ordered) |
| Column Fields | [Month, Metric] |
Fields pivoted into column headers (ordered, top-level first) |
| Value Field | Amount |
Field containing the numeric value for each cell |
| Aggregation | Sum |
How to combine values when multiple rows hit the same cell |
| Show Row Totals | off | Adds a "Total" column at the right |
| Show Column Totals | off | Adds a "Total" row at the bottom |
With the configuration above, the panel renders:
| June | July |
| Forecast|Actual|Var | Forecast|Actual|Var |
-----------|---------|------|----|---------|------|----|
Revenue | 500 | 480 | 20 | 550 | 530 | 20 |
Costs | 200 | 210 |-10 | 220 | 230 |-10 |
- "June" and "July" span 3 subcolumns each (Forecast, Actuals, Variance)
- "Revenue" and "Costs" are row headers
- Values are summed when multiple raw rows map to the same cell
Select 3 column fields (e.g., [Year, Month, Metric]) for a 3-level header.
Select 2 row fields (e.g., [Category, Region]) for a 2-level left header with rowspan grouping.
The plugin includes a provisioned dashboard with two sample panels:
- Revenue & Costs Matrix —
[Month, Metric]columns,[Category]rows — demonstrates 2-level column headers - Revenue by Region & Subcategory —
[Month]columns,[Region, Product]rows — demonstrates 2-level row headers
The demo dashboard loads automatically on startup.
To use with a real data source (PostgreSQL, MySQL, etc.):
- Structure your query to return flat rows matching the pivoting model
- Add the panel to a dashboard
- Select your data source in the query editor
- In panel options, select your Row Fields, Column Fields, and Value Field from the dropdowns
- The dropdowns auto-populate from the query result fields
SQL example (PostgreSQL):
SELECT
category AS "Category",
month AS "Month",
metric AS "Metric",
amount AS "Amount"
FROM financials
WHERE month IN ('June', 'July', 'August')
ORDER BY category, month, metricimranmaszeri-multi-level-table-panel/
├── src/
│ ├── module.ts # Plugin entry point, panel options builder
│ ├── types.ts # Panel options interface & internal types
│ ├── plugin.json # Grafana plugin metadata
│ ├── components/
│ │ ├── MatrixTable.tsx # Main React table component
│ │ └── MatrixTable.styles.ts # Emotion CSS-in-JS styles
│ ├── utils/
│ │ └── pivot.ts # Core pivot engine: flat rows → matrix
│ └── img/
│ └── logo.svg # Plugin icon
├── provisioning/
│ ├── datasources/
│ │ └── sample-data.yaml # Auto-configured TestData source
│ └── dashboards/
│ ├── matrix-demo.json # Pre-built demo dashboard
│ └── dashboard-provider.yaml # Dashboard provisioning config
├── .config/
│ └── webpack/
│ └── webpack.config.ts # Webpack build configuration
├── docker-compose.yaml # Local Grafana 11.0 + plugin mount
├── package.json
└── tsconfig.json
| Command | Description |
|---|---|
npm run build |
Build the plugin for production |
npm run dev |
Build with file watcher (live reload) |
npm run server |
Start local Grafana via Docker |
npm run typecheck |
Run TypeScript type checking |
- React 18 — UI framework (provided by Grafana runtime)
- TypeScript — type-safe development
- @grafana/ui — Grafana's component library and theming
- @emotion/css — CSS-in-JS styling
- SWC — fast transpilation for webpack builds
- Docker — local Grafana instance for development and demos
MIT © Imran Maszeri