Line Chart
render_line_chartRender interactive line or area charts with multiple series, smooth curves, gradient fill, and customizable themes. Use for visualizing trends over time with annotations and style options.
Instructions
Render an interactive line or area chart. Supports smooth curves, gradient fill, and multiple series. Supports themes for styled visuals.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| title | Yes | Chart title | |
| labels | Yes | X-axis labels (e.g. dates, categories) | |
| 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/line.ts:45-163 (handler)The main handler function that renders a line chart using Chart.js. Creates a canvas-based line chart with configurable fill, smoothness, points, colors, annotations, and theme support. Also sets up click interaction, export/refresh buttons, and deferred resize handling.
export function renderLineChart(container: HTMLElement, payload: LineData): void { const { title, labels, datasets, options } = payload; const shouldFill = options.fill !== false; const isSmooth = options.smooth !== false; const showPoints = options.showPoints === true; // Apply theme if specified const theme = resolveTheme(payload.theme, { palette: payload.palette, typography: payload.typography, effects: payload.effects, }); if (theme) applyTheme(container, theme); container.innerHTML = ` <div class="chart-view"> <div class="card chart-card"> <div class="chart-card__header"> <div> <div class="chart-card__title${theme?.effects.shimmerTitle ? " shimmer-text" : ""}">${escapeHtml(title)}</div> <div class="chart-card__subtitle">${datasets.length} series - ${labels.length} points</div> </div> </div> <div class="chart-card__body"> <canvas id="chart-canvas"></canvas> </div> </div> </div> `; const canvas = container.querySelector<HTMLCanvasElement>("#chart-canvas")!; const palette = resolveColors(options.colors, datasets.length); const chartInstance = new Chart(canvas, { type: "line", data: { labels, datasets: datasets.map((ds, i) => { const color = palette[i % palette.length]; return { label: ds.label, data: ds.data, borderColor: color, borderWidth: 2, tension: isSmooth ? 0.4 : 0, fill: shouldFill, backgroundColor: (ctx: any) => { if (!shouldFill) return "transparent"; const gradient = ctx.chart.ctx.createLinearGradient( 0, 0, 0, ctx.chart.height ); gradient.addColorStop(0, color + "40"); gradient.addColorStop(1, color + "00"); return gradient; }, pointRadius: showPoints ? 4 : 0, pointHoverRadius: 6, pointBackgroundColor: color, pointBorderColor: getCSSVar("--bg-card"), pointBorderWidth: 2, pointHoverBorderWidth: 2, }; }), }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: "index", intersect: false, }, onClick: (_event, elements) => { if (elements.length === 0) return; const idx = elements[0].index; const label = labels[idx]; const values = datasets.map((ds) => `${ds.label}: ${ds.data[idx]?.toLocaleString()}`).join(", "); sendClickMessage(`${label} (${values}) in "${title}"`); }, scales: { x: { border: { display: false }, grid: { display: false }, ticks: { color: getCSSVar("--text-secondary"), font: { size: 11 } }, }, y: { border: { display: false }, grid: { color: getCSSVar("--border"), drawTicks: false, }, ticks: { color: getCSSVar("--text-secondary"), font: { size: 11 }, padding: 8, }, }, }, plugins: { legend: { display: datasets.length > 1, position: "top", align: "end", labels: { color: getCSSVar("--text-secondary"), boxWidth: 10, padding: 12, font: { size: 11 }, }, }, tooltip: tooltipStyle(), annotation: buildAnnotations(options.annotations) ? { annotations: buildAnnotations(options.annotations) } : undefined, }, }, }); deferResize(chartInstance); addExportButton(container, chartInstance, title); addRefreshButton(container, () => (window as any).__mcpRefresh?.()); } - src/charts/line.ts:28-43 (schema)The LineData interface defines the input schema for renderLineChart: title, labels, datasets (with label + data array), options (fill, smooth, showPoints, colors, annotations), and optional theme/palette/typography/effects styling.
interface LineData { title: string; labels: string[]; datasets: Array<{ label: string; data: number[] }>; options: { fill?: boolean; smooth?: boolean; showPoints?: boolean; colors?: string[]; annotations?: any[]; }; theme?: string; palette?: string; typography?: string; effects?: string; } - src/charts/line.ts:165-165 (registration)Registers the line chart under the type 'line' with the tool name 'render_line_chart' and the renderLineChart handler function. This is called as a side-effect when the module is imported.
registerChart("line", "render_line_chart", renderLineChart); - src/charts/auto.ts:272-300 (helper)Helper function that transforms raw data (object key-value pairs or array of objects with date/number keys) into the LineData format expected by renderLineChart. Used by the auto chart renderer when it detects a line chart is appropriate.
function toLineData(title: string, data: any): Parameters<typeof renderLineChart>[1] { // Object {key: value} - treat keys as x-axis labels if (!Array.isArray(data) && typeof data === "object" && data !== null) { const flat = tryFlatten(data) as Record<string, any>; const entries = Object.entries(flat).filter(([, v]) => isNumeric(v)); return { title, labels: entries.map(([k]) => k), datasets: [{ label: title, data: entries.map(([, v]) => Number(v)) }], options: {}, }; } const { dateKeys, numberKeys, stringKeys } = analyzeArray(data); const xKey = dateKeys[0] ?? stringKeys[0] ?? Object.keys(data[0])[0]; const seriesKeys = numberKeys.length > 0 ? numberKeys : Object.keys(data[0]).filter((k) => k !== xKey && isNumeric(data[0][k])); return { title, labels: data.map((row: any) => String(row[xKey] ?? "")), datasets: seriesKeys.map((key) => ({ label: key, data: data.map((row: any) => Number(row[key] ?? 0)), })), options: {}, }; }