Waffle Chart
render_waffle_chartVisualize proportional composition using a 10x10 grid of colored squares, where each value represents a percentage and all sum to 100.
Instructions
Render a waffle chart - 'What is the composition?' 10x10 grid of colored squares showing proportional composition. Values should sum to 100.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| title | Yes | Chart title | |
| data | Yes | Array of {label, value} composition items | |
| 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/waffle.ts:20-89 (handler)The main handler function renderWaffleChart that renders a 100-cell waffle chart. It resolves themes, calculates proportional counts per item, builds a grid of cells with colors, renders the HTML with legend, attaches a click handler to send selection messages, and adds export/refresh buttons.
export function renderWaffleChart(container: HTMLElement, payload: WaffleData): 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 colors = resolveColors(undefined, payload.data.length); const total = payload.data.reduce((s, d) => s + d.value, 0); // Build 100-cell grid const cells: { color: string; label: string }[] = []; let remaining = 100; payload.data.forEach((item, i) => { const count = Math.round((item.value / total) * 100); const actualCount = Math.min(count, remaining); const color = item.color || colors[i % colors.length]; for (let j = 0; j < actualCount; j++) { cells.push({ color, label: item.label }); } remaining -= actualCount; }); // Fill any rounding gap with last color while (cells.length < 100 && payload.data.length > 0) { const last = payload.data[payload.data.length - 1]; cells.push({ color: last.color || colors[(payload.data.length - 1) % colors.length], label: last.label }); } const cellsHtml = cells.map((c, i) => `<div class="waffle__cell" style="background:${c.color}" title="${escapeHtml(c.label)}" data-idx="${i}"></div>` ).join(""); const legendHtml = payload.data.map((item, i) => { const color = item.color || colors[i % colors.length]; const pct = total > 0 ? ((item.value / total) * 100).toFixed(0) : "0"; return ` <span class="waffle__legend-item"> <span class="waffle__legend-dot" style="background:${color}"></span> ${escapeHtml(item.label)} (${pct}%) </span> `; }).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="waffle"> <div class="waffle__grid">${cellsHtml}</div> <div class="waffle__legend">${legendHtml}</div> </div> </div> </div> `; container.querySelector(".waffle__grid")?.addEventListener("click", (e) => { const cell = (e.target as HTMLElement).closest<HTMLElement>(".waffle__cell"); if (!cell) return; sendClickMessage(`[Waffle] "${payload.title}" - ${cell.title}`); }); const card = container.querySelector<HTMLElement>(".chart-card")!; addHtmlExportButton(card, payload.title); addRefreshButton(card, () => (window as any).__mcpRefresh?.()); } - src/charts/waffle.ts:4-18 (schema)TypeScript interfaces WaffleItem (label, value, optional color) and WaffleData (type 'waffle', title, data array, optional theme/palette/typography/effects) defining the input schema for the waffle chart tool.
interface WaffleItem { label: string; value: number; color?: string; } interface WaffleData { type: "waffle"; title: string; data: WaffleItem[]; theme?: string; palette?: string; typography?: string; effects?: string; } - src/charts/waffle.ts:91-91 (registration)Registration call: registerChart('waffle', 'render_waffle_chart', renderWaffleChart) which maps the chart type 'waffle' to tool name 'render_waffle_chart' and the render handler.
registerChart("waffle", "render_waffle_chart", renderWaffleChart); - src/app.ts:27-27 (registration)Side-effect import of ./charts/waffle.js in app.ts which triggers the self-registration via registerChart() call.
import "./charts/waffle.js"; - src/charts/shared.ts:188-194 (helper)The registerChart function in shared.ts which stores chart type -> {toolName, render} entries in the CHART_REGISTRY for later dispatch by app.ts.
export function registerChart( type: string, toolName: string, render: (root: HTMLElement, data: any) => void, ): void { CHART_REGISTRY[type] = { toolName, render }; }