Semiotic ships a headless hook, useChartInterrogation, that lets users ask natural-language questions about a chart. It pairs an LLM-friendlystatistical summary of your data with a contract forvisual highlighting: your AI returns annotations, the chart renders them.
The hook owns no UI. You bring your own chat surface — input box, transcript, panel, whatever fits your product. The demo below is ~70 lines of plain React for context.
Interactive Demo
The demo uses a canned onQuery in place of a real LLM. Try"where is the peak?", "tell me about software", or"hardware growth".
Ask about trends, outliers, or specific data points.
How it works
- Summarize:
useChartInterrogation runs summarizeData on your data — min, max, mean, median, top categorical values, date ranges. - Ask: Your
onQuery receives the question plus the summary and any props you passed. Call your LLM, return { answer, annotations }. - Render: The hook merges your initial annotations with the AI's response and exposes the combined array — wire it to the chart's
annotations prop.
Implementation
JSX
import { LineChart, useChartInterrogation } from "semiotic/ai" function InterrogatableChart({ data }) { const { ask, history, annotations, loading } = useChartInterrogation({ data, componentName: "LineChart", props: { xAccessor: "month", yAccessor: "revenue" }, onQuery: async (query, context) => { const res = await fetch("/api/chat", { method: "POST", body: JSON.stringify({ query, summary: context.summary }), }).then((r) => r.json()) return { answer: res.text, annotations: res.highlights } }, }) return ( <> <LineChart data={data} xAccessor="month" yAccessor="revenue" annotations={annotations} /> <YourChatUI history={history} loading={loading} onAsk={ask} /> </> ) }
The statistical summary
context.summary is the payload to send to an LLM. It's compact, typed, and avoids shipping raw rows:
JSON
{ "rowCount": 12, "fields": { "revenue": { "type": "numeric", "min": 800, "max": 4500, "mean": 2025, "median": 1850 }, "category": { "type": "categorical", "distinctCount": 2, "topValues": [ { "value": "Software", "count": 6 }, { "value": "Hardware", "count": 6 } ] } } }
Use summarizeData directly if you want the summary without the hook — for server-side prompting, batch jobs, or the interrogateChart MCP tool.