TanStack
Catalog

Token use calendar heatmap

time

333 lines · 3 files · 9.2 kB

cases/118-token-usage-calendar/example.tsx140 lines · entry
cases/118-token-usage-calendar/example.tsx
import { cell, defineChart } from '@tanstack/charts'
import { Chart } from '@tanstack/charts/react/tooltip'
import { tooltip } from '@tanstack/charts/tooltip'
import { portal } from '@tanstack/charts/tooltip/portal'
import { scaleBand, scaleOrdinal } from 'd3-scale'
import {
  calendarMonthTicks,
  calendarWeekCount,
  formatTokenUsage,
  tokenUsageCalendar,
  usageColors,
  usageLevels,
  weekdays,
} from './model'
import {
  calendarBandPaddingInner,
  calendarBandPaddingOuter,
  calendarBottomMargin,
  calendarMargin,
} from './layout'

export interface ChartOptions {
  width: number
  height: number
  revision: number
  preview?: boolean
}

const weekDomain = Array.from(
  { length: calendarWeekCount },
  (_value, index) => index,
)

export const exampleAriaLabel =
  'Token activity from August 2025 through July 2026. Weeks are columns and Sunday through Saturday are rows. Pale gray means no usage; blue intensity ranges from up to 25 million through over 150 million tokens.'

export const exampleTooltip = {
  anchor: 'point' as const,
  className: 'token-activity-tooltip',
  format: ({
    datum,
  }: {
    datum: ReturnType<typeof tokenUsageCalendar>[number]
  }) => formatTokenUsage(datum),
  offset: 5,
  portal,
}

export function createExampleChart(input: ChartOptions) {
  const days = tokenUsageCalendar(input.revision)
  const monthTicks = calendarMonthTicks()

  return defineChart(
    {
      marks: [
        cell(days, {
          x: 'week',
          y: 'weekday',
          color: 'level',
          key: 'dateKey',
          inset: 0,
          radius: 3,
        }),
      ],
      scales: {
        x: {
          scale: scaleBand<number>()
            .domain(weekDomain)
            .paddingInner(calendarBandPaddingInner)
            .paddingOuter(calendarBandPaddingOuter),
          axis: {
            line: false,
            ticks: {
              values: monthTicks.values,
              size: 0,
              padding: input.preview ? 3 : 7,
              format: (week: number) => monthTicks.labels.get(week) ?? '',
            },
            tickLabels: {
              fontSize: input.preview ? 8 : 13,
              opacity: 0.62,
              anchor: ({ index }) => (index === 0 ? 'start' : undefined),
              dx: ({ index, bandwidth }) =>
                index === 0 ? -bandwidth / 2 : undefined,
              thin: { minGap: input.preview ? 2 : 8, priority: 'ends' },
            },
          },
        },
        y: {
          scale: scaleBand<string>()
            .domain(weekdays)
            .paddingInner(calendarBandPaddingInner)
            .paddingOuter(calendarBandPaddingOuter),
          axis: false,
        },
      },

      color: {
        scale: scaleOrdinal<string, string>()
          .domain(usageLevels)
          .range(usageColors),
      },
      margin: input.preview
        ? { top: 0, right: 0, bottom: 16, left: 0 }
        : {
            ...calendarMargin,
            bottom: calendarBottomMargin(input.width, input.height),
          },
    },
    {
      keyboard: true,
      tooltip: { use: tooltip, ...exampleTooltip },
    },
  )
}

export const chart = createExampleChart({
  width: 720,
  height: 300,
  revision: 0,
})

export default function Example() {
  return (
    <div style={{ height: 300 }}>
      <style>{`
        .ts-chart-tooltip.token-activity-tooltip {
          padding: 6px 9px !important;
          border: 1px solid #e5e7eb !important;
          border-radius: 9px !important;
          background: #fff !important;
          color: #202124 !important;
          box-shadow: none !important;
          white-space: nowrap !important;
        }
      `}</style>
      <Chart definition={chart} ariaLabel={exampleAriaLabel} height={300} />
    </div>
  )
}
cases/118-token-usage-calendar/layout.ts36 lines · dependency
cases/118-token-usage-calendar/layout.ts
import { calendarWeekCount } from './model'

export const calendarBandPaddingInner = 0.08
export const calendarBandPaddingOuter = 0
export const calendarMargin = {
  top: 4,
  right: 0,
  bottom: 30,
  left: 0,
} as const

export function calendarGridHeight(width: number): number {
  const plotWidth = Math.max(
    1,
    width - calendarMargin.left - calendarMargin.right,
  )
  const xStep =
    plotWidth /
    (calendarWeekCount -
      calendarBandPaddingInner +
      calendarBandPaddingOuter * 2)
  return xStep * (7 - calendarBandPaddingInner + calendarBandPaddingOuter * 2)
}

export function calendarChartHeight(width: number): number {
  return Math.ceil(
    calendarMargin.top + calendarGridHeight(width) + calendarMargin.bottom,
  )
}

export function calendarBottomMargin(width: number, height: number): number {
  return Math.max(
    calendarMargin.bottom,
    height - calendarMargin.top - calendarGridHeight(width),
  )
}
cases/118-token-usage-calendar/model.ts157 lines · dependency
cases/118-token-usage-calendar/model.ts
import { binTimeX } from '@tanstack/charts'
import { utcDay, utcMonth, utcSunday } from 'd3-time'

export interface TokenUsageEvent {
  readonly at: Date
  readonly tokens: number
}

export const weekdays = [
  'Sun',
  'Mon',
  'Tue',
  'Wed',
  'Thu',
  'Fri',
  'Sat',
] as const

export const usageLevels = [
  'No usage',
  'Up to 25M',
  '25M–75M',
  '75M–150M',
  'Over 150M',
] as const

export type UsageLevel = (typeof usageLevels)[number]

export const usageColors = [
  '#f3f4f6',
  '#dbeafe',
  '#bfdbfe',
  '#93c5fd',
  '#3b82f6',
] as const

export interface TokenUsageDay {
  readonly date: Date
  readonly dateKey: string
  readonly week: number
  readonly weekday: (typeof weekdays)[number]
  readonly tokens: number
  readonly sessions: number
  readonly level: UsageLevel
  readonly source: readonly TokenUsageEvent[]
  readonly sourceIndexes: readonly number[]
}

export const calendarStart = new Date('2025-08-03T00:00:00Z')
export const calendarEnd = new Date('2026-08-01T00:00:00Z')
export const calendarWeekCount = utcSunday.count(calendarStart, calendarEnd) + 1

const dateFormat = new Intl.DateTimeFormat('en-US', {
  month: 'short',
  day: 'numeric',
  timeZone: 'UTC',
})

const monthFormat = new Intl.DateTimeFormat('en-US', {
  month: 'short',
  timeZone: 'UTC',
})

const tokenFormat = new Intl.NumberFormat('en-US', {
  notation: 'compact',
  maximumFractionDigits: 1,
})

export function tokenUsageCalendar(revision: number): TokenUsageDay[] {
  const events = tokenUsageEvents(revision)
  const daily = binTimeX(events, {
    value: 'at',
    interval: utcDay,
    domain: [calendarStart, calendarEnd],
    outputs: {
      tokens: { value: 'tokens', reduce: 'sum' },
      sessions: { reduce: 'count' },
    },
  })

  return daily.map((day) => {
    const date = day.x1
    const dateKey = isoDate(date)
    return {
      date,
      dateKey,
      week: utcSunday.count(calendarStart, date),
      weekday: weekdays[date.getUTCDay()] ?? 'Sun',
      tokens: day.tokens,
      sessions: day.sessions,
      level: usageLevel(day.tokens),
      source: day.source,
      sourceIndexes: day.sourceIndexes,
    }
  })
}

export function tokenUsageEvents(revision: number): TokenUsageEvent[] {
  const dates = utcDay.range(calendarStart, utcDay.offset(calendarEnd, 1))
  return dates.flatMap((date, dayIndex) => {
    const weekday = date.getUTCDay()
    const weekend = weekday === 0 || weekday === 6
    const progress = dayIndex / Math.max(1, dates.length - 1)
    const activityRamp = Math.min(1, Math.max(0, (progress - 0.46) / 0.08))
    const activity = (dayIndex * 37 + weekday * 17 + revision * 11) % 100
    const quietThreshold = Math.round(99 - activityRamp * (weekend ? 67 : 81))
    if (activity < quietThreshold) return []

    const intensityRamp = Math.min(1, Math.max(0, (progress - 0.5) / 0.5))
    const surge =
      intensityRamp > 0.8 && (dayIndex * 11 + weekday * 7 + revision) % 13 === 0
        ? 90_000_000
        : 0
    const total =
      4_000_000 +
      ((dayIndex * 7_919_000 + weekday * 13_337_000 + revision * 9_973_000) %
        28_000_000) +
      Math.round(intensityRamp * 78_000_000) +
      surge
    const sessions = 1 + ((dayIndex + revision + weekday) % 3)
    return Array.from({ length: sessions }, (_value, sessionIndex) => ({
      at: new Date(date.getTime() + (8 + sessionIndex * 4) * 3_600_000),
      tokens:
        Math.floor(total / sessions) +
        (sessionIndex < total % sessions ? 1 : 0),
    }))
  })
}

export function calendarMonthTicks(): {
  readonly values: readonly number[]
  readonly labels: ReadonlyMap<number, string>
} {
  const dates = [calendarStart, ...utcMonth.range(calendarStart, calendarEnd)]
  const labels = new Map<number, string>()
  for (const date of dates) {
    const week = utcSunday.count(calendarStart, date)
    if (!labels.has(week)) labels.set(week, monthFormat.format(date))
  }
  return { values: [...labels.keys()], labels }
}

export function formatTokenUsage(day: TokenUsageDay): string {
  return `${tokenFormat.format(day.tokens)} tokens on ${dateFormat.format(day.date)}`
}

function usageLevel(tokens: number): UsageLevel {
  if (tokens === 0) return 'No usage'
  if (tokens <= 25_000_000) return 'Up to 25M'
  if (tokens <= 75_000_000) return '25M–75M'
  if (tokens <= 150_000_000) return '75M–150M'
  return 'Over 150M'
}

function isoDate(date: Date): string {
  return date.toISOString().slice(0, 10)
}