suggestChart
Recommend appropriate chart types for data visualization using Semiotic library. Provide sample data to get ranked suggestions with example properties.
Instructions
Recommend Semiotic chart types for a given data sample. Pass { data: [...] } with 1-5 sample objects. Optionally pass intent to narrow suggestions. Returns ranked recommendations with example props.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| data | Yes | 1-5 sample data objects | |
| intent | No | Visualization intent to narrow suggestions |
Implementation Reference
- ai/mcp-server.ts:82-208 (handler)The suggestChartHandler function analyzes the provided data and returns recommended chart types, including confidence levels and sample props.
async function suggestChartHandler(args: { data?: any[]; intent?: string }): Promise<ToolResult> { const data = args.data const intent = args.intent if (!data || !Array.isArray(data) || data.length === 0) { return { content: [{ type: "text" as const, text: "Pass { data: [{ ... }, ...] } with 1-5 sample data objects. Optionally include intent: 'comparison' | 'trend' | 'distribution' | 'relationship' | 'composition' | 'geographic' | 'network' | 'hierarchy'." }], isError: true, } } const sample = data[0] if (!sample || typeof sample !== "object") { return { content: [{ type: "text" as const, text: "Data items must be objects with key-value pairs." }], isError: true, } } const keys = Object.keys(sample) const suggestions: Array<{ component: string; confidence: string; reason: string; props: Record<string, string> }> = [] // Classify fields const numericFields: string[] = [] const stringFields: string[] = [] const dateFields: string[] = [] const geoFields: { lat?: string; lon?: string } = {} const networkFields: { source?: string; target?: string; value?: string } = {} const hierarchyFields: { children?: string; parent?: string } = {} for (const key of keys) { const values = data.map(d => d[key]).filter(v => v != null) if (values.length === 0) continue const first = values[0] if (typeof first === "number") { numericFields.push(key) } else if (typeof first === "string") { if (/^\d{4}[-/]\d{2}/.test(first) && !isNaN(Date.parse(first))) { dateFields.push(key) } else { stringFields.push(key) } } const kl = key.toLowerCase() if (kl === "lat" || kl === "latitude") geoFields.lat = key if (kl === "lon" || kl === "lng" || kl === "longitude") geoFields.lon = key if (kl === "source" || kl === "from") networkFields.source = key if (kl === "target" || kl === "to") networkFields.target = key if (kl === "value" || kl === "weight" || kl === "amount") networkFields.value = key if (kl === "children" || kl === "values") hierarchyFields.children = key if (kl === "parent") hierarchyFields.parent = key } const hasTime = dateFields.length > 0 const hasCat = stringFields.length > 0 const hasNum = numericFields.length > 0 const hasGeo = geoFields.lat && geoFields.lon const hasNetwork = networkFields.source && networkFields.target const hasHierarchy = hierarchyFields.children || hierarchyFields.parent // Network data if (hasNetwork && (!intent || intent === "network")) { const src = networkFields.source! const tgt = networkFields.target! if (networkFields.value) { suggestions.push({ component: "SankeyDiagram", confidence: "high", reason: `Data has ${src}→${tgt} with ${networkFields.value} — ideal for flow visualization`, props: { edges: "data", sourceAccessor: `"${src}"`, targetAccessor: `"${tgt}"`, valueAccessor: `"${networkFields.value}"` }, }) } suggestions.push({ component: "ForceDirectedGraph", confidence: networkFields.value ? "medium" : "high", reason: `Data has ${src}→${tgt} edges — force layout shows network structure. Nodes are auto-inferred from edges when not provided.`, props: { edges: "data", sourceAccessor: `"${src}"`, targetAccessor: `"${tgt}"` }, }) } // Hierarchy data if (hasHierarchy && (!intent || intent === "hierarchy")) { suggestions.push({ component: "Treemap", confidence: "high", reason: `Data has nested ${hierarchyFields.children || "parent"} structure — treemap shows hierarchical proportions`, props: { data: "rootObject", childrenAccessor: `"${hierarchyFields.children || "children"}"`, ...(numericFields[0] ? { valueAccessor: `"${numericFields[0]}"` } : {}) }, }) suggestions.push({ component: "TreeDiagram", confidence: "medium", reason: "Tree layout shows hierarchical relationships", props: { data: "rootObject", childrenAccessor: `"${hierarchyFields.children || "children"}"` }, }) } // Geographic data if (hasGeo && (!intent || intent === "geographic")) { const sizeField = numericFields.find(f => f !== geoFields.lat && f !== geoFields.lon) suggestions.push({ component: "ProportionalSymbolMap", confidence: "high", reason: `Data has ${geoFields.lat}/${geoFields.lon} coordinates — map shows spatial distribution`, props: { points: "data", xAccessor: `"${geoFields.lon}"`, yAccessor: `"${geoFields.lat}"`, ...(sizeField ? { sizeBy: `"${sizeField}"` } : {}) }, }) } // Time series if (hasTime && hasNum && (!intent || intent === "trend")) { const timeField = dateFields[0] const valueField = numericFields[0] suggestions.push({ component: "LineChart", confidence: "high", reason: `Data has dates (${timeField}) and numeric values (${valueField}) — line chart shows trends over time`, props: { data: "data", xAccessor: `"${timeField}"`, yAccessor: `"${valueField}"`, ...(hasCat ? { lineBy: `"${stringFields[0]}"`, colorBy: `"${stringFields[0]}"` } : {}) }, }) if (hasCat) { suggestions.push({ component: "StackedAreaChart", confidence: "medium", reason: `Multiple categories (${stringFields[0]}) over time — stacked area shows composition trends`, props: { data: "data", xAccessor: `"${timeField}"`, yAccessor: `"${valueField}"`, areaBy: `"${stringFields[0]}"`, colorBy: `"${stringFields[0]}"` }, }) } - ai/mcp-server.ts:410-418 (registration)The suggestChart tool is registered using srv.tool, defining the input schema and connecting it to the suggestChartHandler.
srv.tool( "suggestChart", "Recommend Semiotic chart types for a given data sample. Pass { data: [...] } with 1-5 sample objects. Optionally pass intent to narrow suggestions. Returns ranked recommendations with example props.", { data: z.array(z.record(z.string(), z.unknown())).min(1).max(5).describe("1-5 sample data objects"), intent: z.enum(["comparison", "trend", "distribution", "relationship", "composition", "geographic", "network", "hierarchy"]).optional().describe("Visualization intent to narrow suggestions"), }, suggestChartHandler )