Bar Chart
render_bar_chartRender interactive bar charts supporting vertical/horizontal, stacked, multi-series, and click-to-drill-down views. Customize themes, annotations, and palettes for styled visuals.
Instructions
Render an interactive bar chart. Supports vertical/horizontal, stacked, multi-series, and click-to-drill-down (options.drilldown). Supports themes for styled visuals.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| title | Yes | Chart title | |
| labels | Yes | Category labels for the x-axis | |
| datasets | Yes | One or more data series | |
| options | No | ||
| 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/bar.ts:219-264 (handler)The main handler function for the 'render_bar_chart' tool. It creates the chart DOM structure (card with title, breadcrumb, body), resolves theme, delegates to initBarDrilldown for rendering the Chart.js bar chart, and adds export/refresh buttons.
export function renderBarChart(container: HTMLElement, payload: BarData): void { const { title, options } = payload; const theme = resolveTheme(payload.theme, { palette: payload.palette, typography: payload.typography, effects: payload.effects, }); if (theme) applyTheme(container, theme); const shimmerClass = theme?.effects.shimmerTitle ? " shimmer-text" : ""; container.innerHTML = ` <div class="chart-view"> <div class="card chart-card"> <div class="chart-card__header"> <div> <div class="chart-card__title${shimmerClass}">${escapeHtml(title)}</div> <div class="chart-card__subtitle">${payload.datasets.length} series - ${payload.labels.length} categories</div> </div> </div> <div class="drilldown-breadcrumb"></div> <div class="chart-card__body"> <canvas id="chart-canvas"></canvas> </div> </div> </div> `; const card = container.querySelector<HTMLElement>(".chart-card")!; const { proxy } = initBarDrilldown({ card, title, labels: payload.labels, datasets: payload.datasets, horizontal: options.horizontal, stacked: options.stacked, colors: options.colors, annotations: options.annotations, drilldown: options.drilldown, shimmerClass, }); addExportButton(container, proxy as any, title); addRefreshButton(container, () => (window as any).__mcpRefresh?.()); } - src/charts/bar.ts:21-36 (schema)The BarData interface defines the input schema for the bar chart tool, specifying title, labels, datasets (with optional per-dataset colors), options (horizontal, stacked, colors, annotations, drilldown), and theming properties.
interface BarData { title: string; labels: string[]; datasets: Array<{ label: string; data: (number | null)[]; colors?: string[] }>; options: { horizontal?: boolean; stacked?: boolean; colors?: string[]; annotations?: any[]; drilldown?: Record<string, DrilldownLevel>; }; theme?: string; palette?: string; typography?: string; effects?: string; } - src/charts/bar.ts:16-19 (schema)The DrilldownLevel interface defines the data structure for drill-down sub-levels within the bar chart.
interface DrilldownLevel { labels: string[]; datasets: Array<{ label: string; data: number[] }>; } - src/charts/bar.ts:266-266 (registration)Registers the 'render_bar_chart' tool name with type 'bar' in the global chart registry, mapping it to the renderBarChart handler function.
registerChart("bar", "render_bar_chart", renderBarChart); - src/charts/bar.ts:56-217 (handler)Helper function that sets up the actual Chart.js bar chart with drill-down support, breadcrumb navigation, click handling, and responsive resize.
export function initBarDrilldown(cfg: BarDrilldownConfig): { proxy: any } { const { card, title, horizontal: isHorizontal = false, stacked: isStacked = false, shimmerClass = "", tickSize = 11 } = cfg; // Add breadcrumb if not present let bc = card.querySelector<HTMLElement>(".drilldown-breadcrumb"); if (!bc) { bc = document.createElement("div"); bc.className = "drilldown-breadcrumb"; const body = card.querySelector(".chart-card__body"); if (body) card.insertBefore(bc, body); } interface HistoryEntry { title: string; labels: string[]; datasets: Array<{ label: string; data: (number | null)[]; colors?: string[] }>; annotations?: any[]; drilldown?: Record<string, DrilldownLevel>; } const history: HistoryEntry[] = []; let currentChart: Chart | null = null; function updateBreadcrumb(): void { if (history.length === 0) { bc!.innerHTML = ""; bc!.style.display = "none"; return; } bc!.style.display = "flex"; const items = history.map((h, i) => `<span class="drilldown-breadcrumb__item" data-level="${i}">${escapeHtml(h.title)}</span>` ); bc!.innerHTML = items.join('<span class="drilldown-breadcrumb__sep">\u203A</span>'); bc!.querySelectorAll<HTMLElement>(".drilldown-breadcrumb__item").forEach(item => { item.addEventListener("click", () => { const level = parseInt(item.dataset.level ?? "0", 10); const target = history[level]; history.length = level; renderLevel(target.title, target.labels, target.datasets, target.annotations, target.drilldown); }); }); } function renderLevel( levelTitle: string, labels: string[], datasets: Array<{ label: string; data: (number | null)[]; colors?: string[] }>, annotations?: any[], drilldown?: Record<string, DrilldownLevel>, ): void { if (currentChart) { currentChart.destroy(); currentChart = null; } const titleEl = card.querySelector<HTMLElement>(".chart-card__title"); if (titleEl) { titleEl.textContent = levelTitle; if (shimmerClass) titleEl.className = `chart-card__title${shimmerClass}`; } const subtitleEl = card.querySelector<HTMLElement>(".chart-card__subtitle"); if (subtitleEl) subtitleEl.textContent = `${datasets.length} series - ${labels.length} categories`; updateBreadcrumb(); let canvas = card.querySelector<HTMLCanvasElement>("canvas"); if (!canvas) { canvas = document.createElement("canvas"); card.querySelector(".chart-card__body")!.appendChild(canvas); } const palette = resolveColors(cfg.colors, datasets.length); const hasDrilldown = drilldown && Object.keys(drilldown).length > 0; currentChart = new Chart(canvas, { type: "bar", data: { labels, datasets: datasets.map((ds, i) => { const base = palette[i % palette.length]; const bg = ds.colors ? ds.colors.map(c => c + "CC") : base + "CC"; const border = ds.colors ?? base; return { label: ds.label, data: ds.data, backgroundColor: bg, borderColor: border, borderWidth: 1, borderRadius: 4, borderSkipped: false, }; }), }, options: { indexAxis: isHorizontal ? "y" : "x", responsive: true, maintainAspectRatio: false, interaction: { mode: "index", intersect: false }, onClick: (_event, elements) => { if (elements.length === 0) return; const el = elements[0]; const label = labels[el.index]; if (hasDrilldown && drilldown![label]) { const sub = drilldown![label]; history.push({ title: levelTitle, labels, datasets, annotations, drilldown }); renderLevel(`${levelTitle} \u203A ${label}`, sub.labels, sub.datasets); return; } const values = datasets.map((ds) => `${ds.label}: ${ds.data[el.index]?.toLocaleString()}`).join(", "); sendClickMessage(`${label} (${values}) in "${levelTitle}"`); }, scales: { x: { stacked: isStacked, border: { display: false }, grid: { display: isHorizontal, color: getCSSVar("--border") }, ticks: { color: getCSSVar("--text-secondary"), font: { size: tickSize } }, }, y: { stacked: isStacked, border: { display: false }, grid: { display: !isHorizontal, color: getCSSVar("--border") }, ticks: { color: getCSSVar("--text-secondary"), font: { size: tickSize }, padding: 8 }, }, }, plugins: { legend: { display: datasets.length > 1, position: "top", align: "end", labels: { color: getCSSVar("--text-secondary"), boxWidth: 10, padding: 12, font: { size: tickSize } }, }, tooltip: { ...tooltipStyle(), callbacks: hasDrilldown ? { afterBody: (contexts) => { const label = contexts[0]?.label ?? ""; return drilldown![label] ? ["", "Click to drill down \u2193"] : []; }, } : undefined, }, annotation: buildAnnotations(annotations) ? { annotations: buildAnnotations(annotations) } : undefined, }, }, }); deferResize(currentChart); } renderLevel(title, cfg.labels, cfg.datasets, cfg.annotations, cfg.drilldown); const proxy = { get toBase64Image() { return () => currentChart?.toBase64Image() ?? ""; }, get canvas() { return currentChart?.canvas; }, }; return { proxy }; }