TanStack
Catalog

Flare analytics treemap

hierarchy

TanStack

TanStack source

142 chart lines · 3 files · 3.8 kBBenchmark harness excluded · shared/mount.ts

Flare package hierarchy252 records · CSV · 8.0 kB
@charts-poc/demo-data/flare

Selection: Complete published snapshot

name
string
size
number | null

Flare visualization toolkit@observablehq/sample-datasets@1.0.1 · revision 732c0148de74 · flare.csv · ISC distribution; upstream source credited · SHA-256 704349b51b70Pinned snapshot

cases/74-recharts-treemap/tanstack.ts90 lines · entry
cases/74-recharts-treemap/tanstack.ts
import { defineChart, rect, text } from '@tanstack/charts'
import { flare } from '@charts-poc/demo-data/flare'
import { hierarchy, treemap } from 'd3-hierarchy'
import { scaleLinear } from 'd3-scale'
import { selectTreemapData } from './selection'
import { flareLabel, flareTree } from './transform'
import { tanstackMount } from '../../shared/mount'
import type { FlareTreeNode } from './transform'
import type { ConformanceInput } from '../../types'

const colors = ['#2563eb', '#8b5cf6', '#10b981']

interface BundleCell {
  name: string
  family: string
  x1: number
  x2: number
  y1: number
  y2: number
  labelX: number
  labelY: number
}

function layoutCells(): readonly BundleCell[] {
  const treemapData = selectTreemapData(flare)
  const root = hierarchy(flareTree(treemapData))
    .sum((node) => node.size ?? 0)
    .sort((left, right) => (right.value ?? 0) - (left.value ?? 0))

  const layoutRoot = treemap<FlareTreeNode>()
    .size([100, 100])
    .paddingInner(0.5)
    .round(false)(root)

  return layoutRoot.leaves().map((leaf) => {
    return {
      name: leaf.data.name,
      family: leaf.data.family,
      x1: leaf.x0,
      x2: leaf.x1,
      y1: leaf.y0,
      y2: leaf.y1,
      labelX: (leaf.x0 + leaf.x1) / 2,
      labelY: (leaf.y0 + leaf.y1) / 2,
    }
  })
}

const definition = (input: ConformanceInput) => {
  const cells = layoutCells()
  const labels = cells.filter((cell) => {
    const label = flareLabel(cell.name)
    const width = ((cell.x2 - cell.x1) / 100) * input.width
    const height = ((cell.y2 - cell.y1) / 100) * input.height
    return width >= label.length * 4.8 + 8 && height >= 14
  })

  return defineChart({
    marks: [
      rect(cells, {
        x1: 'x1',
        x2: 'x2',
        y1: 'y1',
        y2: 'y2',
        color: 'family',
        inset: 1,
        stroke: '#ffffff',
        strokeWidth: 1,
      }),
      text(labels, {
        x: 'labelX',
        y: 'labelY',
        text: (cell) => flareLabel(cell.name),
        fill: '#ffffff',
        fontSize: 8,
        fontWeight: 600,
        anchor: 'middle',
      }),
    ],
    x: { scale: scaleLinear().domain([0, 100]) },
    y: { scale: scaleLinear().domain([100, 0]) },
    color: {
      range: colors,
    },
    guides: false,
    margin: 0,
  })
}

export const mount = tanstackMount(definition, 'Flare analytics treemap')
cases/74-recharts-treemap/selection.ts9 lines · support
cases/74-recharts-treemap/selection.ts
import type { FlareRow } from '@charts-poc/demo-data/flare'

const subtree = 'flare.analytics'

export function selectTreemapData(rows: readonly FlareRow[]) {
  return rows.filter(
    (row) => row.name === subtree || row.name.startsWith(`${subtree}.`),
  )
}
cases/74-recharts-treemap/transform.ts43 lines · support
cases/74-recharts-treemap/transform.ts
import { stratify } from 'd3-hierarchy'
import type { FlareRow } from '@charts-poc/demo-data/flare'
import type { HierarchyNode } from 'd3-hierarchy'

export interface FlareTreeNode {
  [key: string]: unknown
  name: string
  size: number | null
  family: string
  children?: readonly FlareTreeNode[]
}

export function flareTree(rows: readonly FlareRow[]): FlareTreeNode {
  const rootName = rows[0]?.name
  if (rootName === undefined) {
    throw new TypeError('The Flare treemap selection is empty')
  }

  const root = stratify<FlareRow>().path((row) =>
    row.name.slice(rootName.length).replaceAll('.', '/'),
  )(Array.from(rows))

  return treeNode(root, rootName)
}

export function flareLabel(name: string): string {
  return name.slice(name.lastIndexOf('.') + 1)
}

function treeNode(
  node: HierarchyNode<FlareRow>,
  inheritedFamily: string,
): FlareTreeNode {
  const family = node.depth === 1 ? node.data.name : inheritedFamily
  const children = node.children?.map((child) => treeNode(child, family))

  return {
    name: node.data.name,
    size: node.data.size,
    family,
    ...(children === undefined ? {} : { children }),
  }
}