Variance Chart
render_variance_chartRender a variance chart comparing actual vs budget with color-coded over/under indicators. Identify budget performance gaps at a glance.
Instructions
Render a variance chart - 'Over or under budget?' Bars showing actual vs budget with color-coded over/under indicators.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| title | Yes | Chart title | |
| data | Yes | Array of {label, budget, actual} items | |
| unit | No | Unit suffix (e.g. '$', 'k') | |
| theme | No | Theme preset: boardroom, corporate, sales-floor, golden-treasury, clinical, startup, ops-control, tokyo-midnight, zen-garden, consultant, black-tron, black-elegance, black-matrix, forest-amber, forest-earth, sky-light, sky-ocean, sky-twilight, gray-hf, gray-copilot | |
| palette | No | Override palette only (mix-and-match) | |
| typography | No | Override typography: professional, luxury, cyberpunk, editorial, mono, bold, system, techno | |
| effects | No | Override effects: none, subtle, shimmer, neon, energetic |
Implementation Reference
- src/charts/variance.ts:21-78 (handler)Main handler function that renders a variance (budget vs actual) chart. Computes bar widths, builds HTML rows with over/under styling, attaches click handlers for sending selection messages, and adds export/refresh buttons.
export function renderVarianceChart(container: HTMLElement, payload: VarianceData): void { const theme = resolveTheme(payload.theme, { palette: payload.palette, typography: payload.typography, effects: payload.effects, }); if (theme) applyTheme(container, theme); const shimmer = theme?.effects.shimmerTitle ? " shimmer-text" : ""; const unit = payload.unit || ""; const maxVal = Math.max(...payload.data.flatMap((d) => [d.budget, d.actual]), 1); const rows = payload.data.map((item, i) => { const budgetPct = (item.budget / maxVal) * 100; const actualPct = (item.actual / maxVal) * 100; const diff = item.actual - item.budget; const isOver = diff > 0; const diffLabel = `${isOver ? "+" : ""}${diff.toLocaleString()}${unit}`; const diffClass = isOver ? "variance__diff--over" : "variance__diff--under"; return ` <div class="variance__row" data-idx="${i}"> <div class="variance__label">${escapeHtml(item.label)}</div> <div class="variance__track"> <div class="variance__bar ${isOver ? "variance__bar--over" : "variance__bar--under"}" style="width:${actualPct}%"></div> <div class="variance__budget-marker" style="left:${budgetPct}%"></div> </div> <div class="variance__diff ${diffClass}">${diffLabel}</div> </div> `; }).join(""); container.className = "chart-view"; container.innerHTML = ` <div class="card chart-card"> <div class="chart-card__header"> <div><div class="chart-card__title${shimmer}">${escapeHtml(payload.title)}</div></div> </div> <div class="chart-card__body chart-card__body--css"> <div class="variance">${rows}</div> </div> </div> `; container.querySelectorAll<HTMLElement>(".variance__row").forEach((el) => { el.style.cursor = "pointer"; el.addEventListener("click", () => { const idx = parseInt(el.dataset.idx ?? "0", 10); const item = payload.data[idx]; const diff = item.actual - item.budget; sendClickMessage(`[Variance] "${payload.title}" - ${item.label}: actual ${item.actual}${unit} vs budget ${item.budget}${unit} (${diff > 0 ? "+" : ""}${diff}${unit})`); }); }); const card = container.querySelector<HTMLElement>(".chart-card")!; addHtmlExportButton(card, payload.title); addRefreshButton(card, () => (window as any).__mcpRefresh?.()); } - src/charts/variance.ts:4-19 (schema)Type definitions for the variance chart: VarianceItem (label, budget, actual) and VarianceData (type, title, data, optional unit/theme/palette/typography/effects).
interface VarianceItem { label: string; budget: number; actual: number; } interface VarianceData { type: "variance"; title: string; data: VarianceItem[]; unit?: string; theme?: string; palette?: string; typography?: string; effects?: string; } - src/charts/variance.ts:80-80 (registration)Registers the tool with name 'render_variance_chart' under the chart type 'variance' via the shared registerChart function.
registerChart("variance", "render_variance_chart", renderVarianceChart); - src/charts/shared.ts:188-194 (registration)The registerChart function itself, which stores toolName -> render function mappings in a CHART_REGISTRY.
export function registerChart( type: string, toolName: string, render: (root: HTMLElement, data: any) => void, ): void { CHART_REGISTRY[type] = { toolName, render }; } - src/charts/variance.ts:1-27 (helper)Imports for shared utilities (escapeHtml, sendClickMessage, addHtmlExportButton, addRefreshButton, registerChart) and theme helpers (resolveTheme, applyTheme).
import { escapeHtml, sendClickMessage, addHtmlExportButton, addRefreshButton, registerChart } from "./shared.js"; import { resolveTheme, applyTheme } from "../themes.js"; interface VarianceItem { label: string; budget: number; actual: number; } interface VarianceData { type: "variance"; title: string; data: VarianceItem[]; unit?: string; theme?: string; palette?: string; typography?: string; effects?: string; } export function renderVarianceChart(container: HTMLElement, payload: VarianceData): void { const theme = resolveTheme(payload.theme, { palette: payload.palette, typography: payload.typography, effects: payload.effects, }); if (theme) applyTheme(container, theme);