Volcano

Large Datasets and Performance

Keep thousands of leaves responsive with aggregation, drill-down, and measured limits.

Product strategy

Do not render every leaf in the first viewport. Five thousand rectangles may be technically laid out, but most are too small to identify or tap on a phone. Start with a meaningful overview, then use groups, a search/filter, or a detail screen to reveal the raw items.

Build the compact display tree in a ViewModel or repository, not during composition.

val overview = rawMarket.toDisplayTree(
  aggregation = HeatmapAggregation(
    maximumChildren = 12,
    minimumChildFraction = 0.01,
    othersLabel = "Others",
  ),
)
Heatmap(state = rememberHeatmapState(overview))

toDisplayTree() aggregates first and then sorts recursively by descending layout value. The synthetic Others node is intentionally a leaf, so it remains an honest compact summary rather than pretending to expose omitted data.

What the benchmark means

The shared layout engine benchmarks at approximately 2ms p95 for 5,000 leaves on the desktop JVM. That is a layout-only number, not an end-user frame guarantee. Android, iOS, and Desktop frame time also includes Compose composition, drawing, text measurement, images, device GPU behavior, and animations. Profile the target device with representative labels and image policy before setting a product SLA.

For a raw diagnostic view, disable transitions with HeatmapMotion(enabled = false). For normal users, the aggregate overview plus drill-down is the recommended experience.

Opening aggregated items

toDisplayTreeWithSources() returns a display root and aggregatedSources: root-relative HeatmapPath keys mapped to original direct children represented by each Others cell. Use this mapping to open a host-owned list or detail view. Existing toDisplayTree() remains available. maximumChildren limits retained children; Others is additional. Do not parse or persist synthetic IDs; use the returned path mapping.

Node layoutValue and effectiveMetric are calculated once at construction. Never mutate a supplied children list; create new snapshots with copy(children = ...) instead.

val display = raw.toDisplayTreeWithSources(
  aggregation = HeatmapAggregation(maximumChildren = 12),
)
val state = rememberHeatmapState(display.root)
Heatmap(state = state, onLeafClick = {
  state.selectionPath?.let { path ->
    display.aggregatedSources[path]?.let { originals -> showItems(originals) }
  }
})

On this page