Skip to main content

Metrics Tree

Visualize how your metrics break down from a North Star into its underlying drivers. Makes metric dependencies and relationships easy to scan for your whole team.

Best for: KPI decomposition, metric dependency mapping, business driver analysis

Key techniques: col_totals for aggregated values, CSS Grid multi-level layout, pseudo-element connectors

Metrics Tree Example
Sparklines coming soon

We're working on a sparkline component that will let you embed inline trend charts inside each node. Once available, you'll be able to show how each metric is trending right alongside its current value.

Metrics Tree with Sparklines (preview)

Template Code

<style>
.metric-tree-v1 {
/* Tree width cap to avoid node stretching */
width: 1350px;
--tree-pd-left: 160px;
--tree-pd-right: 10px;
margin: 0 auto;

/* Responsive sizing */
--node-w: clamp(100px, 12vw, 150px);
--gap: clamp(6px, 2vw, 12px);

/* Derived layout */
--double-w: calc(var(--node-w) * 2 + var(--gap));

/* Connectors */
--line-color: grey;
--line-w: 3px;
--line-r: 10px;
--line-h: clamp(34px, 4vw, 44px);
--node-border-w: 3px;
padding: 10px var(--tree-pd-right) 10px var(--tree-pd-left);
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
}

/* Levels */
.metric-tree-v1 .level {
display: grid;
gap: var(--gap);
justify-content: center;
width: 100%;
box-sizing: border-box;
}
.metric-tree-v1 .level-1 {
grid-template-columns: var(--double-w);
}
.metric-tree-v1 .level-2 {
grid-template-columns: repeat(3, var(--double-w));
}
.metric-tree-v1 .level-3 {
grid-template-columns: repeat(4, var(--double-w));
}
.metric-tree-v1 .level-4 {
grid-template-columns: repeat(5, var(--double-w));
}

/* Connector rows */
.metric-tree-v1 .lines {
display: grid;
justify-content: center;
width: 100%;
box-sizing: border-box;
}
.metric-tree-v1 .lines-1 {
grid-template-columns: calc(var(--double-w) * 2);
}
.metric-tree-v1 .lines-2 {
grid-template-columns: repeat(3, var(--double-w));
}
.metric-tree-v1 .lines-3 {
grid-template-columns: repeat(4, var(--double-w));
}
.metric-tree-v1 .line {
height: var(--line-h);
position: relative;
}
.metric-tree-v1 .line:before {
content: "";
width: var(--line-w);
height: 50%;
background: var(--line-color);
position: absolute;
left: calc(50% - var(--line-w) / 2);
top: 0;
}
.metric-tree-v1 .line:after {
content: "";
position: absolute;
left: calc(var(--line-w) * -1);
bottom: 0;
width: calc(100% + var(--line-w));
height: 50%;
border-style: solid;
border-color: var(--line-color);
border-width: var(--line-w) var(--line-w) 0 var(--line-w);
border-radius: var(--line-r) var(--line-r) 0 0;
box-sizing: border-box;
}

/* KPI card */
.metric-tree-v1 .node {
width: var(--node-w);
margin: 0 auto;
padding: 12px 12px 10px;
border: var(--node-border-w) solid grey;
border-radius: 14px;
background: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
display: flex;
flex-direction: column;
gap: 2px;
}
.metric-tree-v1 .label {
font-size: 15px;
opacity: 0.75;
text-align: center;
padding: 0;
margin: 0;
line-height: 1.2;
color: black;
}
.metric-tree-v1 .value {
font-size: 17px;
font-weight: 700;
text-align: center;
margin: 0 !important;
line-height: 1.1;
}
.metric-tree-v1 .metric-subtext {
font-size: 10px;
opacity: 0.65;
text-align: center;
margin: 0 !important;
line-height: 1.2;
}
.metric-tree-v1 .empty {
width: var(--node-w);
}

/* Optional accents */
.metric-tree-v1 .pos {
border-color: rgba(16, 185, 129, 0.35);
}
.metric-tree-v1 .neg {
border-color: rgba(239, 68, 68, 0.35);
}
</style>

<div class="metric-tree-v1">
<div class="level level-1">
<article class="node">
<p class="label">North Star</p>
<p class="value">{{ col_totals.`North Star`.formatted }}</p>
<p class="metric-subtext">Primary outcome</p>
</article>
</div>

<div class="lines lines-1"><div class="line"></div></div>

<div class="level level-2">
<article class="node pos">
<p class="label">Revenue</p>
<p class="value">{{ col_totals.`Revenue` }}</p>
<p class="metric-subtext">Money from customers</p>
</article>
<div class="empty"></div>
<article class="node neg">
<p class="label">Cost</p>
<p class="value">{{ col_totals.`Cost` }}</p>
<p class="metric-subtext">Money paid out</p>
</article>
</div>

<div class="lines lines-2">
<div class="line"></div>
<div class="empty"></div>
<div class="line"></div>
</div>

<div class="level level-3">
<article class="node pos">
<p class="label">Buyers</p>
<p class="value">{{ col_totals.`Buyers` }}</p>
<p class="metric-subtext">People who purchased</p>
</article>
<article class="node pos">
<p class="label">AOV</p>
<p class="value">{{ col_totals.`Aov` }}</p>
<p class="metric-subtext">Avg order value</p>
</article>
<article class="node neg">
<p class="label">COGS</p>
<p class="value">{{ col_totals.`Cogs` }}</p>
<p class="metric-subtext">Cost to make</p>
</article>
<article class="node neg">
<p class="label">Shipping</p>
<p class="value">{{ col_totals.`Shipping` }}</p>
<p class="metric-subtext">Cost to deliver</p>
</article>
</div>

<div class="lines lines-3"><div class="line"></div></div>

<div class="level level-4">
<article class="node pos">
<p class="label">Sessions</p>
<p class="value">{{ col_totals.`Sessions` }}</p>
<p class="metric-subtext">Traffic</p>
</article>
<article class="node pos">
<p class="label">Conversion Rate</p>
<p class="value">{{ col_totals.`Conversion Rate` }}</p>
<p class="metric-subtext">Sessions → buyers</p>
</article>
</div>
</div>

How It Works

The tree is built with pure CSS Grid — no JavaScript needed. Each level is a grid row, and connector lines between levels are drawn using CSS pseudo-elements (:before and :after).

Layout structure:

  • Each level (level-1 through level-4) is a CSS Grid with a set number of columns
  • Between levels, a .lines row draws the branching connectors
  • Empty .empty divs act as spacers to position nodes correctly under their parent

Data access:

  • All values use col_totals to pull aggregated totals across the entire dataset
  • This means the tree shows a single rolled-up number per metric, not row-by-row data

Required Data Fields

FieldTypeSlotDescription
MonthDimensionRowTime dimension for aggregation
North StarMeasureValueTop-level metric (e.g., Profit)
RevenueMeasureValueRevenue metric
CostMeasureValueTotal cost metric
BuyersMeasureValueNumber of buyers
AovMeasureValueAverage order value
CogsMeasureValueCost of goods sold
ShippingMeasureValueShipping costs
SessionsMeasureValueNumber of sessions
Conversion RateMeasureValueSessions-to-buyers conversion rate

Setting Up the Block

In the Visualization Block definition, add the time dimension as a Row field and all metrics as Value fields with aggregation: 'sum'. Enable Show Column Total in the block settings — the template reads from col_totals to display the aggregated values.

Sample Data

Import this data into Holistics to use: sample-data-metrics-tree.csv

Month,North Star,Revenue,Cost,Buyers,AOV,COGS,Shipping,Sessions,Conversion Rate
2024-01,245149,352188,107039,7322,48.1,79692,27347,239226,0.0306
2024-02,265628,382769,117141,7996,47.87,86680,30461,262095,0.0305
2024-03,299244,431517,132273,9020,47.84,97050,35223,290974,0.031
2024-04,335063,482296,147233,9769,49.37,107089,40144,318913,0.0306
2024-05,342112,490557,148445,9774,50.19,107249,41196,339084,0.0288
2024-06,343323,489927,146604,9597,51.05,105620,40984,346630,0.0277
2024-07,320203,454882,134679,8921,50.99,97219,37460,340021,0.0262
2024-08,293177,415243,122066,8038,51.66,88673,33393,321534,0.025
2024-09,274734,388849,114115,7574,51.34,83640,30475,296710,0.0255
2024-10,250143,354674,104531,7061,50.23,77305,27226,272909,0.0259
2024-11,239129,340346,101217,6811,49.97,75343,25874,257353,0.0265
2024-12,250370,358061,107691,7203,49.71,80355,27336,255162,0.0282

Customization Tips

Adapt the tree to your metrics

This template uses an e-commerce profit tree as an example, but you can adapt it to any metric hierarchy. Replace the metric names, labels, and subtexts to match your business:

  • SaaS: MRR → New MRR + Expansion - Churn → Leads × Conversion Rate
  • Marketplace: GMV → Buyers × AOV → Traffic × Conversion × Average Basket
  • Content: Engagement → Views × Time on Page → Impressions × CTR

Change node colors

The .pos and .neg classes add green and red accent borders. Adjust the colors or add new classes:

.metric-tree-v1 .pos {
border-color: rgba(16, 185, 129, 0.35); /* green */
}
.metric-tree-v1 .neg {
border-color: rgba(239, 68, 68, 0.35); /* red */
}
/* Add a neutral accent */
.metric-tree-v1 .neutral {
border-color: rgba(59, 130, 246, 0.35); /* blue */
}

Add or remove levels

To add a 5th level, create a new .level-5 grid rule and a .lines-4 connector row:

.metric-tree-v1 .level-5 {
grid-template-columns: repeat(6, var(--double-w));
}
.metric-tree-v1 .lines-4 {
grid-template-columns: repeat(5, var(--double-w));
}

Then add the corresponding HTML for the new nodes and connectors.

Adjust node sizing

The node width is controlled by a single CSS variable. Change the clamp() values to make nodes wider or narrower:

--node-w: clamp(100px, 12vw, 150px);  /* min, preferred, max */

Let us know what you think about this document :)