# API keys Source: https://docs.graphy.dev/agents/api-keys Every request to the Graphy Agents API is authenticated with an API key. You create and manage keys in the **Graphy console**, then send the key as a Bearer token on each request. ## Get your API key 1. Go to the [Graphy console](https://agents.graphy.dev/console/). 2. Sign in with Google or GitHub. 3. Open the **API keys** section. 4. Click **Create key**, give it a name (for example `production`), and confirm. 5. **Copy the key right away** — it's shown only once and cannot be retrieved later. Store it in a secrets manager or an environment variable. Keys look like `graphy_…`. You can keep up to **5 active keys** at a time — create separate keys for development and production, and revoke any you no longer use from the same page in the console. ## Use your key Send the key in the `Authorization` header as a Bearer token on every request: ``` Authorization: Bearer graphy_xxxxxxxxxxxxxxxxxxxxxxxx ``` With the TypeScript SDK, pass it as `apiKey`: ```typescript theme={null} import { GraphyAiSdk } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, // your graphy_… key baseUrl: 'https://agents.graphy.dev', }); ``` See [Authentication](/agents/rest/authentication) for the full header reference and authentication error codes. ## Keep your keys safe Never expose API keys in client-side code. Call the Agents API from your backend only. * Store keys in environment variables or a secrets manager. * Use separate keys for development and production. * Rotate keys periodically and revoke unused ones in the console. ## Next steps Generate your first chart in 5 minutes Headers, base URL, and error codes # Overview Source: https://docs.graphy.dev/agents/overview AI Agents is currently in alpha. APIs may change. Graphy AI Agents create, transform, and analyze charts using natural language. Most agents take a `GraphConfig` and a prompt and return an updated `GraphConfig` ready to render. ## Architecture Agents are stateless. Each request contains the full context needed to process it—no sessions, no conversation history. ```mermaid theme={null} flowchart LR A[Your App] -->|GraphConfig + Prompt| B[AI Agent] B -->|Updated GraphConfig| A ``` This design simplifies integration: * No session management or cleanup * Requests are idempotent and cacheable * Easy to retry on failure * Works with any architecture (serverless, edge, traditional) ## Available Agents | Agent | Endpoint | SDK Method | Description | | ----------- | -------------------------- | ----------------------- | -------------------------------------------------------- | | Chart Maker | `POST /api/v0/generate` | `generateGraph()` | Modify chart type, styling, and data transformations | | Suggestions | `POST /api/v0/suggestions` | `generateSuggestions()` | Suggest chart types and data preparations | | Mutation | `POST /api/v0/mutate` | `generateMutation()` | Transform the dataset — filter, group, derive, sort | | Annotation | `POST /api/v0/annotate` | `generateAnnotations()` | Add highlights, tooltips, and other annotations | | Narrative | `POST /api/v0/narrate` | `generateNarrative()` | Write the chart title, subtitle, and caption | | Extract | `POST /api/v0/extract` | `extractFromProse()` | Build a dataset from text, images, PDFs, or spreadsheets | ## Prerequisites * A Graphy API key — [create one in the console](/agents/api-keys) * Familiarity with `GraphConfig` ([schema reference](/sdk/reference/graph-config)) ## Integration Options **TypeScript SDK** — Full-featured client with streaming, retries, and type validation. ```typescript theme={null} import { GraphyAiSdk } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: 'your-api-key', baseUrl: 'https://agents.graphy.dev', }); const result = await ai.generateGraph({ config: myGraphConfig, userPrompt: 'Change to a stacked bar chart', }); ``` **REST API** — Direct HTTP integration for any language. Returns Server-Sent Events for streaming progress. ```bash theme={null} curl -N https://agents.graphy.dev/api/v0/generate \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"config": {...}, "userPrompt": "Add a trend line"}' ``` ## Next Steps First working call in 5 minutes Install and configure the SDK Direct HTTP integration Complete type definitions # Quickstart Source: https://docs.graphy.dev/agents/quickstart AI Agents is currently in alpha. APIs may change. Get your first AI-generated chart modification working in 5 minutes. ## Prerequisites * Node.js 18+ * A Graphy API key — [create one in the console](/agents/api-keys) * An existing `GraphConfig` (or use the example below) ## 1. Install the SDK ```bash npm theme={null} npm install @graphysdk/agents-sdk ``` ```bash yarn theme={null} yarn add @graphysdk/agents-sdk ``` ```bash pnpm theme={null} pnpm add @graphysdk/agents-sdk ``` ## 2. Create the Client ```typescript theme={null} import { GraphyAiSdk } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }); ``` ## 3. Prepare a Graph Configuration ```typescript theme={null} import type { GraphConfig } from '@graphysdk/agents-sdk'; const config: GraphConfig = { type: 'column', data: { columns: [ { key: 'quarter', label: 'Quarter' }, { key: 'revenue', label: 'Revenue ($)' }, ], rows: [ { quarter: 'Q1', revenue: 50000 }, { quarter: 'Q2', revenue: 62000 }, { quarter: 'Q3', revenue: 58000 }, { quarter: 'Q4', revenue: 71000 }, ], }, }; ``` ## 4. Call the Agent ```typescript theme={null} const result = await ai.generateGraph({ config, userPrompt: 'Change this to a line chart and add a trend line', }); console.log(result.config); // Updated GraphConfig console.log(result.response.message); // "Changed chart type to line and added a trend line" ``` ## 5. Use the Result The Agents SDK returns an updated `GraphConfig`. Log it, persist it, or pass it to your renderer. ```typescript theme={null} console.log(JSON.stringify(result.config, null, 2)); ``` Chart rendering is separate from the Agents client. Use `@graphysdk/react` for the v2 React renderer when you are ready to display the chart. ## Next Steps Timeouts, retries, and logging Real-time progress updates Retries and error types Direct HTTP integration # Type Reference Source: https://docs.graphy.dev/agents/reference/types Complete type definitions for the Graphy AI SDK. ## GraphConfig The chart configuration object. See [Graph Config Schema](/sdk/reference/graph-config) for the complete reference. ```typescript theme={null} import type { GraphConfig } from '@graphysdk/agents-sdk'; ``` *** ## GenerateGraphParams Request parameters for the Chart Maker Agent. ```typescript theme={null} interface GenerateGraphParams { config: GraphConfig; userPrompt?: string; metadata?: Metadata; storytellingOptions?: StorytellingOptions; } ``` | Field | Type | Required | Description | | --------------------- | --------------------- | -------- | -------------------------------------------------------------- | | `config` | `GraphConfig` | Yes | The current chart configuration | | `userPrompt` | `string` | No | Natural language instruction | | `metadata` | `Metadata` | No | Request tracking information | | `storytellingOptions` | `StorytellingOptions` | No | Prune storytelling from config content client-side (SDK only). | *** ## GenerateGraphResponse Response from the Chart Maker Agent. ```typescript theme={null} interface GenerateGraphResponse { config: GraphConfig; response: { message: string; steps?: string[]; }; } ``` | Field | Type | Description | | ------------------ | ------------- | --------------------------------------------------------------------------------------------------------------------------- | | `config` | `GraphConfig` | Updated chart configuration. Narrative (title, subtitle, caption) is embedded in `config.content` as TipTap JSON documents. | | `response.message` | `string` | Explanation of changes made | | `response.steps` | `string[]` | Optional breakdown of modifications | *** ## GenerateMutationParams Request parameters for the Mutation Agent. ```typescript theme={null} interface GenerateMutationParams { config: GraphConfig; userPrompt?: string; metadata?: Metadata; } ``` | Field | Type | Required | Description | | ------------ | ------------- | -------- | ------------------------------------------------------------------- | | `config` | `GraphConfig` | Yes | The current chart configuration, including the dataset to transform | | `userPrompt` | `string` | No | Natural language description of the transformation to apply | | `metadata` | `Metadata` | No | Request tracking information | *** ## GenerateMutationResponse Response from the Mutation Agent. ```typescript theme={null} interface GenerateMutationResponse { response: { message: string; steps: string[]; }; config: GraphConfig; } ``` | Field | Type | Description | | ------------------ | ------------- | -------------------------------------------------------- | | `response.message` | `string` | Explanation of the transformations applied | | `response.steps` | `string[]` | Ordered breakdown of each transformation step | | `config` | `GraphConfig` | Updated chart configuration with the transformed dataset | *** ## GenerateAnnotationsParams Request parameters for the Annotation Agent. ```typescript theme={null} interface GenerateAnnotationsParams { config: GraphConfig; userPrompt?: string; metadata?: Metadata; } ``` | Field | Type | Required | Description | | ------------ | ------------- | -------- | ---------------------------------------------------------------- | | `config` | `GraphConfig` | Yes | The current chart configuration to annotate | | `userPrompt` | `string` | No | Natural language description of the annotations to add or remove | | `metadata` | `Metadata` | No | Request tracking information | *** ## GenerateAnnotationsResponse Response from the Annotation Agent. ```typescript theme={null} interface GenerateAnnotationsResponse { response: { message: string; steps: string[]; }; config: GraphConfig; } ``` | Field | Type | Description | | ------------------ | ------------- | ---------------------------------------------------- | | `response.message` | `string` | Explanation of the annotations applied | | `response.steps` | `string[]` | Ordered breakdown of each annotation step | | `config` | `GraphConfig` | Updated chart configuration with annotations applied | *** ## GenerateNarrativeParams Request parameters for the Narrative Agent. ```typescript theme={null} interface GenerateNarrativeParams { config: GraphConfig; userPrompt: string; metadata?: Metadata; } ``` | Field | Type | Required | Description | | ------------ | ------------- | -------- | ---------------------------------------------- | | `config` | `GraphConfig` | Yes | The chart configuration to narrate | | `userPrompt` | `string` | Yes | Natural language instruction for the narrative | | `metadata` | `Metadata` | No | Request tracking information | *** ## GenerateNarrativeResponse Response from the Narrative Agent. ```typescript theme={null} interface GenerateNarrativeResponse { response: { message: string; }; config: GraphConfig; } ``` | Field | Type | Description | | ------------------ | ------------- | --------------------------------------------------------------------------------------------------------------------- | | `response.message` | `string` | Explanation of the narrative produced | | `config` | `GraphConfig` | Updated chart configuration. Title, subtitle, and caption are written into `config.content` as TipTap JSON documents. | *** ## GenerateGraphSuggestionsParams Request parameters for the Suggestions Agent. ```typescript theme={null} interface GenerateGraphSuggestionsParams { config: GraphConfig; userPrompt?: string; metadata?: Metadata; maxSuggestionCount?: 1 | 2 | 3 | 4; } ``` | Field | Type | Required | Description | | -------------------- | ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `config` | `GraphConfig` | Yes | The chart configuration containing the dataset | | `userPrompt` | `string` | No | Natural language description of what to visualize | | `metadata` | `Metadata` | No | Request tracking information | | `maxSuggestionCount` | `1` \| `2` \| `3` \| `4` | No | Optional cap (1–4) passed to the model for how many suggestions to aim for. Omitted uses the default (4). Responses are not truncated if longer. | *** ## GenerateGraphSuggestionsResponse Response from the Suggestions Agent. ```typescript theme={null} interface GenerateGraphSuggestionsResponse { suggestions: Suggestion[]; } ``` | Field | Type | Description | | ------------- | -------------- | ------------------------------ | | `suggestions` | `Suggestion[]` | List of chart type suggestions | *** ## Suggestion A single chart suggestion returned by the Suggestions Agent. ```typescript theme={null} interface Suggestion { dataPrepPrompt: string; chartType: AiChartType; summary: string; } ``` | Field | Type | Description | | ---------------- | ------------- | -------------------------------------------------------- | | `dataPrepPrompt` | `string` | Prompt describing how to prepare the data for this chart | | `chartType` | `AiChartType` | Suggested chart type | | `summary` | `string` | Short description of what the chart would show | *** ## ExtractFromProseParams Request parameters for the Extract Agent. ```typescript theme={null} interface ExtractFromProseParams { sourceText?: string; attachments?: ExtractAttachmentInput[]; images?: ExtractImageInput[]; metadata?: Metadata; } ``` | Field | Type | Required | Description | | ------------- | -------------------------- | -------- | ---------------------------------------------------------------------- | | `sourceText` | `string` | No | Raw text to extract data from. Up to 500,000 characters. | | `attachments` | `ExtractAttachmentInput[]` | No | Images, PDFs, and spreadsheets to extract data from. Up to 12 entries. | | `images` | `ExtractImageInput[]` | No | **Deprecated.** Legacy image array — use `attachments` instead. | | `metadata` | `Metadata` | No | Request tracking information | Provide `sourceText`, `attachments`, or both — at least one is required. `images` and `attachments` cannot be combined in a single request. *** ## ExtractAttachmentInput A discriminated union tagged by `kind`, used for the Extract Agent's `attachments` array. Construct entries with the `buildImageAttachment`, `buildPdfAttachment`, and `buildSpreadsheetAttachment` helpers. ```typescript theme={null} type ExtractAttachmentInput = | { kind: 'image'; mimeType: string; dataBase64: string } | { kind: 'document'; mimeType: string; dataBase64: string } | { kind: 'spreadsheet'; mimeType: string; dataBase64: string; filename?: string }; ``` | `kind` | Accepted formats | Decoded size limit | | --------------- | -------------------- | ------------------ | | `'image'` | PNG, JPEG, WebP, GIF | 2 MB each, 8 max | | `'document'` | PDF | 6 MB each, 2 max | | `'spreadsheet'` | Excel `.xlsx` | 6 MB each, 2 max | A request accepts up to 12 attachments and 12 MB combined decoded size. *** ## ExtractFromProseResponse Response from the Extract Agent. ```typescript theme={null} interface ExtractFromProseResponse { response: { message: string; }; config: GraphConfig; extractMeta: ExtractMeta | null; lastAccuracyEvaluation?: ExtractAccuracyEvaluation; } ``` | Field | Type | Description | | ------------------------ | --------------------------- | ------------------------------------------------------------------ | | `response.message` | `string` | Summary of what was extracted | | `config` | `GraphConfig` | Chart configuration containing the extracted dataset | | `extractMeta` | `ExtractMeta \| null` | Trust metadata for the extraction, or `null` when unavailable | | `lastAccuracyEvaluation` | `ExtractAccuracyEvaluation` | The agent's final self-check of the extracted data, when performed | *** ## ExtractMeta Trust metadata attached to an extraction result. ```typescript theme={null} interface ExtractMeta { confidence: ExtractConfidence; needsUserInput: boolean; warnings: string[]; cellConfidence?: Array<{ rowIndex: number; columnKey: string; confidence: ExtractConfidence; quote?: string; }>; } type ExtractConfidence = 'high' | 'medium' | 'low'; ``` | Field | Type | Description | | ---------------- | ------------------- | ----------------------------------------------------------------- | | `confidence` | `ExtractConfidence` | Overall confidence in the extraction | | `needsUserInput` | `boolean` | Whether the result should be reviewed before use | | `warnings` | `string[]` | Notes about ambiguous, conflicting, or missing source data | | `cellConfidence` | `Array<…>` | Optional per-cell confidence, each with a supporting source quote | *** ## ExtractAccuracyEvaluation The Extract Agent's self-check of an extraction result. ```typescript theme={null} interface ExtractAccuracyEvaluation { score: number; issues: string[]; sufficient: boolean; summary: string; } ``` | Field | Type | Description | | ------------ | ---------- | ------------------------------------------------ | | `score` | `number` | Accuracy score, from `0` to `1` | | `issues` | `string[]` | Specific accuracy problems the agent identified | | `sufficient` | `boolean` | Whether the accuracy is good enough to use as-is | | `summary` | `string` | Human-readable summary of the accuracy check | *** ## StorytellingOptions SDK-only options to prune storytelling fields from the config content after the API returns. For each option set to `true`, the SDK removes that field from `config.content` before returning. ```typescript theme={null} interface StorytellingOptions { excludeTitle?: boolean; excludeSubtitle?: boolean; excludeCaption?: boolean; } ``` | Field | Type | Description | | ----------------- | --------- | --------------------------------------------------- | | `excludeTitle` | `boolean` | If true, the SDK removes `config.content.title`. | | `excludeSubtitle` | `boolean` | If true, the SDK removes `config.content.subtitle`. | | `excludeCaption` | `boolean` | If true, the SDK removes `config.content.caption`. | *** ## Metadata Optional tracking information for requests. ```typescript theme={null} interface Metadata { callId?: string; locale?: string; effort?: 'low' | 'medium' | 'high'; /** @deprecated Prefer `effort`. */ storytellingEffort?: 'none' | 'low' | 'medium' | 'high'; budget?: InvocationBudget; executionMode?: 'agentic' | 'fast'; } ``` | Field | Type | Description | | -------------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `callId` | `string` | Optional. Identifier for tracking and debugging. | | `locale` | `string` | Locale for responses (e.g., `en-US`, `fr-FR`). | | `effort` | `'low' \| 'medium' \| 'high'` | Preferred invocation tier; stored on the invocation and forwarded to nested agents. Default **`medium`** when omitted. | | `storytellingEffort` | `'none' \| 'low' \| 'medium' \| 'high'` | **Deprecated** — prefer `effort`. Still accepted for chart narrative and to infer `effort` when `effort` is omitted. | | `budget` | `InvocationBudget` | Optional hard caps on time, tokens, and cost for the request. | | `executionMode` | `'agentic' \| 'fast'` | Execution strategy: `agentic` runs the full multi-step loop, `fast` runs a single-shot plan. | *** ## InvocationBudget Optional hard limits applied to a single request. The server merges any provided fields with its own per-agent defaults. ```typescript theme={null} interface InvocationBudget { maxWallClockMs?: number; maxTotalTokens?: number; maxCostUsd?: number; } ``` | Field | Type | Description | | ---------------- | -------- | -------------------------------------------------------- | | `maxWallClockMs` | `number` | Hard wall-clock timeout for the request, in milliseconds | | `maxTotalTokens` | `number` | Hard cap on total tokens consumed | | `maxCostUsd` | `number` | Hard cap on request cost, in USD | *** ## SSE Events ### ProgressEvent ```typescript theme={null} interface ProgressEvent { type: 'progress'; message: string; agentId?: string; executionId?: string; iteration?: number; metadata?: Record; } ``` ### CompleteEvent ```typescript theme={null} interface CompleteEvent { type: 'complete'; data: T; } ``` ### ErrorEvent ```typescript theme={null} interface ErrorEvent { type: 'error'; error: string; code?: string; retryable?: boolean; } ``` ### PreviewEvent An incremental preview of an agent's work-in-progress result. The payload is spread at the top level, and its shape depends on the agent. ```typescript theme={null} interface PreviewEvent { type: 'preview'; [key: string]: unknown; } ``` ### ReasoningEvent A reasoning message emitted by an agent as it works. ```typescript theme={null} interface ReasoningEvent { type: 'reasoning'; agentId: string; executionId: string; message: string; } ``` ### SSEEvent Union ```typescript theme={null} type SSEEvent = ProgressEvent | CompleteEvent | ErrorEvent | PreviewEvent | ReasoningEvent; ``` *** ## Error Types ### GraphyApiError Error thrown by the SDK when an API request fails. Covers HTTP errors, SSE stream errors, and network failures. ```typescript theme={null} import { GraphyApiError } from '@graphysdk/agents-sdk'; class GraphyApiError extends Error { readonly status: number | undefined; readonly code: string | undefined; readonly retryable: boolean; constructor(message: string, options?: GraphyApiErrorOptions); } ``` | Property | Type | Description | | ----------- | --------------------- | ---------------------------------------------- | | `status` | `number \| undefined` | HTTP status code, if from an HTTP response | | `code` | `string \| undefined` | Machine-readable error code from the API | | `retryable` | `boolean` | Whether the SDK considers this error retryable | ### GraphyApiErrorOptions ```typescript theme={null} interface GraphyApiErrorOptions { status?: number; code?: string; retryable?: boolean; } ``` ### isGraphyApiError Type guard for narrowing unknown errors to `GraphyApiError`: ```typescript theme={null} import { isGraphyApiError } from '@graphysdk/agents-sdk'; if (isGraphyApiError(error)) { console.error(error.status, error.code, error.retryable); } ``` *** ## Configuration Types ### ClientConfig ```typescript theme={null} interface ClientConfig { apiKey: string; baseUrl: string; timeout?: number; retryConfig?: RetryConfig; logger?: Logger; } ``` ### RetryConfig ```typescript theme={null} interface RetryConfig { attempts: number; delay: number; backoff: number; } ``` **Defaults:** ```typescript theme={null} { attempts: 3, delay: 1000, backoff: 2 } ``` ### Logger ```typescript theme={null} interface Logger { log: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void; debug: (...args: unknown[]) => void; } ``` *** ## AiChartType Supported chart types for AI operations. ```typescript theme={null} type AiChartType = | 'line' | 'areaStacked' | 'bar' | 'groupedBar' | 'stackedBar' | '100StackedBar' | 'column' | 'groupedColumn' | 'stackedColumn' | '100StackedColumn' | 'combo' | 'pie' | 'donut' | 'funnel' | 'heatmap' | 'scatter' | 'bubble' | 'waterfall' | 'table' | 'mekko'; ``` | Type | Description | | ------------------ | ----------------------------- | | `line` | Line chart | | `areaStacked` | Stacked area chart | | `bar` | Horizontal bar chart | | `groupedBar` | Grouped horizontal bars | | `stackedBar` | Stacked horizontal bars | | `100StackedBar` | 100% stacked horizontal bars | | `column` | Vertical column chart | | `groupedColumn` | Grouped vertical columns | | `stackedColumn` | Stacked vertical columns | | `100StackedColumn` | 100% stacked vertical columns | | `combo` | Combined line and column | | `pie` | Pie chart | | `donut` | Donut chart | | `funnel` | Funnel chart | | `heatmap` | Heatmap | | `scatter` | Scatter plot | | `bubble` | Bubble chart | | `waterfall` | Waterfall chart | | `table` | Data table | | `mekko` | Marimekko chart | *** ## Zod Schemas The SDK exports Zod schemas for runtime validation: ```typescript theme={null} import { AiChartTypeEnum, GenerateGraphResponseSchema } from '@graphysdk/agents-sdk'; // Validate a response const validated = GenerateGraphResponseSchema.parse(response); ``` # Annotation Agent Source: https://docs.graphy.dev/agents/rest/annotate POST /api/v0/annotate Add highlights, tooltips, stickers, and other annotations to a chart with natural language. The response is a Server-Sent Events (SSE) stream with progress, complete, and error events. Add highlights, tooltips, stickers, and other annotations to a chart with natural language. Send a `GraphConfig` and a prompt, receive an updated `GraphConfig` via Server-Sent Events. ## Response The response is a Server-Sent Events stream. See [SSE Format](/agents/rest/sse) for parsing details. ```text theme={null} event: progress data: {"message":"Adding annotations..."} event: complete data: {"config":{...},"response":{"message":"Highlighted the March peak","steps":["Added highlight","Added tooltip"]}} ``` **Final response data:** ```typescript theme={null} interface AnnotateResponse { config: GraphConfig; response: { message: string; steps?: string[]; }; } ``` ## HTTP Status Codes | Status | Description | | ------ | -------------------------- | | 200 | Success (stream begins) | | 400 | Invalid request body | | 401 | Invalid or missing API key | | 429 | Rate limit exceeded | | 500 | Internal server error | Errors may also arrive as SSE events within a 200 response. See [Error Codes](/agents/rest/errors). # Authentication Source: https://docs.graphy.dev/agents/rest/authentication Don't have an API key yet? [Create one in the Graphy console](https://agents.graphy.dev/console/) — see [API keys](/agents/api-keys) for the full walkthrough. ## Base URL ``` https://agents.graphy.dev ``` ## Authorization Header All requests require a Bearer token: ``` Authorization: Bearer YOUR_API_KEY ``` ## Required Headers | Header | Value | Required | | --------------- | --------------------- | -------- | | `Content-Type` | `application/json` | Yes | | `Authorization` | `Bearer YOUR_API_KEY` | Yes | ## Example call ```bash theme={null} curl -N https://agents.graphy.dev/api/v0/generate \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "config": { "type": "column", "data": { "columns": [ { "key": "quarter", "label": "Quarter" }, { "key": "revenue", "label": "Revenue ($)" } ], "rows": [ { "quarter": "Q1", "revenue": 50000 }, { "quarter": "Q2", "revenue": 62000 }, { "quarter": "Q3", "revenue": 58000 }, { "quarter": "Q4", "revenue": 71000 } ] } }, "userPrompt": "Change to a line chart" }' ``` *** ## API Key Security Never expose API keys in client-side code. Call the API from your backend. **Best practices:** * Store keys in environment variables * Use a secrets manager in production * Rotate keys periodically * Use separate keys for development and production *** ## Authentication Errors | Status | Error | Description | | ------ | ---------------------- | ------------------------------ | | 401 | `AUTHENTICATION_ERROR` | Missing or invalid API key | | 403 | `AUTHORIZATION_ERROR` | Key lacks required permissions | Authentication errors are not retryable. Check that: * The `Authorization` header is present * The header format is `Bearer YOUR_API_KEY` (note the space) * The API key is valid and active # Chart Maker Agent Source: https://docs.graphy.dev/agents/rest/chart-maker POST /api/v0/generate Modify charts using natural language. Send a GraphConfig and a prompt, receive an updated GraphConfig. The response is a Server-Sent Events (SSE) stream with progress, complete, and error events. Modify charts using natural language. Send a `GraphConfig` and a prompt, receive an updated `GraphConfig` via Server-Sent Events. ## Response The response is a Server-Sent Events stream. See [SSE Format](/agents/rest/sse) for parsing details. ```text theme={null} event: progress data: {"message":"Analyzing chart structure..."} event: progress data: {"message":"Applying changes..."} event: complete data: {"config":{...},"response":{"message":"Changed to line chart and added trend line"}} ``` **Final response data:** ```typescript theme={null} interface GenerateResponse { config: GraphConfig; response: { message: string; steps?: string[]; }; } ``` ## HTTP Status Codes | Status | Description | | ------ | -------------------------- | | 200 | Success (stream begins) | | 400 | Invalid request body | | 401 | Invalid or missing API key | | 429 | Rate limit exceeded | | 500 | Internal server error | Errors may also arrive as SSE events within a 200 response. See [Error Codes](/agents/rest/errors). # Error Codes Source: https://docs.graphy.dev/agents/rest/errors ## Error Event Format Errors arrive as SSE events: ```text theme={null} event: error data: {"error":"Invalid configuration","code":"VALIDATION_ERROR","retryable":false} ``` ```typescript theme={null} interface ErrorEvent { error: string; code?: string; retryable?: boolean; } ``` *** ## Error Codes | Code | HTTP Status | Retryable | Description | | ---------------------- | ----------- | --------- | ---------------------------------- | | `VALIDATION_ERROR` | 400 | No | Invalid request body or parameters | | `AUTHENTICATION_ERROR` | 401 | No | Invalid or missing API key | | `RATE_LIMIT_ERROR` | 429 | Yes | Too many requests | | `PROCESSING_ERROR` | 500 | Yes | Internal processing failure | | `TIMEOUT_ERROR` | 504 | Yes | Request took too long | *** ## Common Errors ### VALIDATION\_ERROR Missing required fields (`config`, `userPrompt`), invalid `GraphConfig` structure, or malformed JSON. **Resolution:** Check the request body against the schema. ### AUTHENTICATION\_ERROR Missing `Authorization` header, malformed header (missing `Bearer ` prefix), or invalid API key. **Resolution:** Verify the API key and header format. ### RATE\_LIMIT\_ERROR Too many requests in a short period. **Resolution:** Implement exponential backoff and retry. ### PROCESSING\_ERROR / TIMEOUT\_ERROR Internal failure or request took too long. **Resolution:** Retry with exponential backoff. If persistent, contact support. *** ## HTTP Status Codes Errors may also arrive as HTTP status codes before streaming begins: | Status | Description | | ------ | -------------------------------------------- | | 400 | Bad Request — Invalid JSON or missing fields | | 401 | Unauthorized — Invalid or missing API key | | 429 | Too Many Requests — Rate limit exceeded | | 500 | Internal Server Error — Processing failure | | 504 | Gateway Timeout — Upstream timeout | *** ## Retry Strategy For retryable errors, use exponential backoff: ``` delay = base_delay * (backoff ^ attempt) ``` **Recommended defaults:** * `base_delay`: 1000ms * `backoff`: 2 * `max_attempts`: 3 | Attempt | Delay | | ------- | --------------- | | 1 | 0ms (immediate) | | 2 | 1000ms | | 3 | 2000ms | **Best practices:** * Only retry if `retryable: true` * Set a maximum retry count * Treat network failures as retryable * The API is stateless, so retries are safe # Evaluation Source: https://docs.graphy.dev/agents/rest/evaluate POST /api/v0/evaluate Score how well every chart type fits a dataset. Deterministic — a rule-based scorer with no language model, so it returns a single result and uses no tokens. Only config.data is scored. The response is a Server-Sent Events (SSE) stream. Evaluation emits no progress events — the result arrives in a single complete event. Score how well every chart type fits a dataset. Evaluation is deterministic — a rule-based scorer with no language model — so it returns a single result and uses no tokens. Send a `GraphConfig`, receive a ranked list of chart types via Server-Sent Events. ## Request body Send a `GraphConfig` in `config`. Only `config.data` is scored — the chart `type`, styling, and annotations are ignored. Optionally pass a `chartFamily` hint (`comparison`, `relationship`, `distribution`, `composition`) to bias the ranking toward that family. ## Response The response is a Server-Sent Events stream. See [SSE Format](/agents/rest/sse) for parsing details. Evaluation is deterministic, so there are no `progress` events — the result arrives in a single `complete` event. ```text theme={null} event: complete data: {"ranking":[{"rank":1,"type":"bar","score":0.82,"verdict":"ok","factors":[{"kind":"pro","label":"Categorical x-axis","weight":0.3,"reason":"One categorical dimension maps cleanly to bars"}]}]} ``` **Final response data:** ```typescript theme={null} interface EvaluateResponse { ranking: ChartFitEntry[]; } interface ChartFitEntry { rank: number; type: AiChartType; score: number; verdict: 'ok' | 'disqualified'; factors: ChartFitFactor[]; } ``` See the [SDK Evaluation page](/agents/sdk/evaluate) for the full `ChartFitEntry` and `ChartFitFactor` field reference. ## HTTP Status Codes | Status | Description | | ------ | -------------------------- | | 200 | Success (stream begins) | | 400 | Invalid request body | | 401 | Invalid or missing API key | | 429 | Rate limit exceeded | | 500 | Internal server error | Errors may also arrive as SSE events within a 200 response — for example, a dataset with no plottable columns. See [Error Codes](/agents/rest/errors). # Extract Agent Source: https://docs.graphy.dev/agents/rest/extract POST /api/v0/extract Build a chart-ready dataset from unstructured text, images, PDFs, or .xlsx spreadsheets. Provide sourceText, attachments, or both. The response is a Server-Sent Events (SSE) stream with progress, complete, and error events. Build a chart-ready dataset from unstructured text, images, PDFs, or Excel spreadsheets. Returns a `GraphConfig` with the extracted data via Server-Sent Events. ## Request body Provide `sourceText`, `attachments`, or both — at least one is required. Each attachment is `{ kind: "image" | "document" | "spreadsheet", mimeType, dataBase64 }`, where `dataBase64` is RFC 4648 standard base64 (not a `data:` URL). Limits: 8 images (2 MB each), 2 PDFs (6 MB each), 2 spreadsheets (6 MB each); 12 attachments and 12 MB combined. The legacy `images` array is deprecated — use `attachments` with `kind: "image"`. `images` and `attachments` cannot be combined. ## Response The response is a Server-Sent Events stream. See [SSE Format](/agents/rest/sse) for parsing details. ```text theme={null} event: progress data: {"message":"Extracting dataset from source content..."} event: complete data: {"config":{...},"response":{"message":"Extraction complete"},"extractMeta":{"confidence":"high","needsUserInput":false,"warnings":[]}} ``` **Final response data:** ```typescript theme={null} interface ExtractResponse { config: GraphConfig; response: { message: string; }; extractMeta: ExtractMeta | null; lastAccuracyEvaluation?: ExtractAccuracyEvaluation; } ``` ## HTTP Status Codes | Status | Description | | ------ | -------------------------------- | | 200 | Success (stream begins) | | 400 | Invalid request body / no source | | 401 | Invalid or missing API key | | 429 | Rate limit exceeded | | 500 | Internal server error | Errors may also arrive as SSE events within a 200 response. See [Error Codes](/agents/rest/errors). # Mutation Agent Source: https://docs.graphy.dev/agents/rest/mutate POST /api/v0/mutate Transform a chart's dataset with natural language — filter, group, aggregate, derive, sort. The response is a Server-Sent Events (SSE) stream with progress, complete, and error events. Transform a chart's dataset with natural language — filter, group, aggregate, derive, sort. Send a `GraphConfig` and a prompt, receive an updated `GraphConfig` via Server-Sent Events. ## Response The response is a Server-Sent Events stream. See [SSE Format](/agents/rest/sse) for parsing details. ```text theme={null} event: progress data: {"message":"Mutating dataset..."} event: complete data: {"config":{...},"response":{"message":"Grouped sales by region","steps":["Grouped rows by region","Sorted by total sales descending"]}} ``` **Final response data:** ```typescript theme={null} interface MutateResponse { config: GraphConfig; response: { message: string; steps?: string[]; }; } ``` ## HTTP Status Codes | Status | Description | | ------ | -------------------------- | | 200 | Success (stream begins) | | 400 | Invalid request body | | 401 | Invalid or missing API key | | 429 | Rate limit exceeded | | 500 | Internal server error | Errors may also arrive as SSE events within a 200 response. See [Error Codes](/agents/rest/errors). # Narrative Agent Source: https://docs.graphy.dev/agents/rest/narrate POST /api/v0/narrate Generate a chart's title, subtitle, and caption. Narrative is written into config.content as TipTap JSON. Requires userPrompt. The response is a Server-Sent Events (SSE) stream with progress, complete, and error events. Generate a chart's title, subtitle, and caption with natural language. Send a `GraphConfig` and a prompt, receive an updated `GraphConfig` via Server-Sent Events. Narrative is written into `config.content` as TipTap JSON. ## Request body `userPrompt` is **required** — the narrative agent needs an instruction to write against. Which fields are produced (title only, title + caption, or all three) depends on the storytelling level derived from `metadata.effort`. ## Response The response is a Server-Sent Events stream. See [SSE Format](/agents/rest/sse) for parsing details. ```text theme={null} event: progress data: {"message":"Generating narrative..."} event: complete data: {"config":{...},"response":{"message":"Wrote a title and caption"}} ``` **Final response data:** ```typescript theme={null} interface NarrateResponse { config: GraphConfig; response: { message: string; }; } ``` ## HTTP Status Codes | Status | Description | | ------ | -------------------------- | | 200 | Success (stream begins) | | 400 | Invalid request body | | 401 | Invalid or missing API key | | 429 | Rate limit exceeded | | 500 | Internal server error | Errors may also arrive as SSE events within a 200 response. See [Error Codes](/agents/rest/errors). # SSE Format Source: https://docs.graphy.dev/agents/rest/sse All API endpoints return Server-Sent Events (SSE). This page explains the wire format and how to parse it. ## Protocol Overview SSE is a simple text-based protocol. Events are separated by blank lines, and each event has fields prefixed with `event:` and `data:`. ```text theme={null} event: progress data: {"message":"Processing..."} event: complete data: {"config":{...},"response":{...}} ``` ## Event Format Each event consists of: 1. `event:` line — The event type (`progress`, `complete`, or `error`) 2. `data:` line — JSON payload 3. Blank line — Event delimiter The blank line after each event is required. *** ## Event Types ### progress Sent periodically during processing. ```json theme={null} { "message": "Analyzing chart structure..." } ``` | Field | Type | Description | | --------- | -------- | -------------- | | `message` | `string` | Status message | Agent-emitted progress events also include `agentId`, `executionId`, and `iteration` identifiers. ### complete Sent once when processing succeeds. Contains the final result. ```json theme={null} { "config": { ... }, "response": { "message": "Changed to bar chart", "steps": ["Changed type", "Updated colors"] } } ``` ### error Sent when processing fails. ```json theme={null} { "error": "Invalid configuration", "code": "VALIDATION_ERROR", "retryable": false } ``` | Field | Type | Description | | ----------- | --------- | --------------------------------------------------- | | `error` | `string` | Error message | | `code` | `string` | Error code (see [Error Codes](/agents/rest/errors)) | | `retryable` | `boolean` | Whether the request can be retried | *** ## Python Example ```python theme={null} import requests import json response = requests.post( 'https://agents.graphy.dev/api/v0/generate', headers={ 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR_API_KEY' }, json={'config': config, 'userPrompt': prompt}, stream=True ) event_type = None for line in response.iter_lines(): if not line: continue decoded = line.decode('utf-8') if decoded.startswith('event:'): event_type = decoded[6:].strip() elif decoded.startswith('data:'): data = json.loads(decoded[5:].strip()) if event_type == 'progress': print(f"Progress: {data['message']}") elif event_type == 'complete': print(f"Done: {data['config']}") elif event_type == 'error': print(f"Error: {data['error']}") ``` *** ## Connection Tips **Disable buffering** to receive events in real-time: ```bash theme={null} curl -N https://agents.graphy.dev/api/v0/generate ... ``` ```python theme={null} response = requests.post(..., stream=True) ``` **Set appropriate timeouts.** The initial connection should be quick, but processing may take up to 60 seconds: ```python theme={null} response = requests.post(url, ..., timeout=(5, 120)) ``` # Suggestions Agent Source: https://docs.graphy.dev/agents/rest/suggestions POST /api/v0/suggestions Generate chart type and data preparation suggestions from a dataset. Send a GraphConfig and a prompt, receive suggested visualizations. The response is a Server-Sent Events (SSE) stream with progress, complete, and error events. Generate chart type and data preparation suggestions from a dataset. Send a `GraphConfig` and a prompt, receive suggested visualizations via Server-Sent Events. ## Request body Besides `config`, `userPrompt`, and optional `metadata`, you may send **`maxSuggestionCount`** (integer **1–4**). When set, it is communicated to the model as the target maximum number of suggestions. The stream is not validated or truncated to that count if the model returns more. ## Response The response is a Server-Sent Events stream. See [SSE Format](/agents/rest/sse) for parsing details. ```text theme={null} event: progress data: {"message":"Generating suggestions..."} event: complete data: {"suggestions":[{"dataPrepPrompt":"...","chartType":"line","summary":"Monthly sales trend"}]} ``` **Final response data:** ```typescript theme={null} interface SuggestionsResponse { suggestions: Suggestion[]; } interface Suggestion { dataPrepPrompt: string; chartType: AiChartType; summary: string; } ``` ## HTTP Status Codes | Status | Description | | ------ | -------------------------- | | 200 | Success (stream begins) | | 400 | Invalid request body | | 401 | Invalid or missing API key | | 429 | Rate limit exceeded | | 500 | Internal server error | Errors may also arrive as SSE events within a 200 response. See [Error Codes](/agents/rest/errors). # Annotation Agent Source: https://docs.graphy.dev/agents/sdk/annotate Add annotations to a chart with natural language. Send a `GraphConfig` and a prompt, receive a `GraphConfig` with highlights, tooltips, stickers, and other annotations applied. ## Methods ### generateAnnotations Processes the request and returns the final result. Internally handles streaming and collects the response. ```typescript theme={null} async generateAnnotations( params: GenerateAnnotationsParams, onProgress?: (event: ProgressEvent) => void, signal?: AbortSignal ): Promise ``` ### generateAnnotationsStream Returns an async iterator that yields events as they arrive. ```typescript theme={null} async generateAnnotationsStream( params: GenerateAnnotationsParams, signal?: AbortSignal ): Promise>> ``` ## Parameters ```typescript theme={null} interface GenerateAnnotationsParams { config: GraphConfig; userPrompt?: string; metadata?: Metadata; } ``` | Field | Type | Required | Description | | ------------ | ------------- | -------- | ---------------------------------------------------------------- | | `config` | `GraphConfig` | Yes | The current chart configuration to annotate | | `userPrompt` | `string` | No | Natural language description of the annotations to add or remove | | `metadata` | `Metadata` | No | Request tracking information | ## Response ```typescript theme={null} interface GenerateAnnotationsResponse { response: { message: string; steps: string[]; }; config: GraphConfig; } ``` | Field | Type | Description | | ------------------ | ------------- | ---------------------------------------------------- | | `response.message` | `string` | Explanation of the annotations applied | | `response.steps` | `string[]` | Ordered breakdown of each annotation step | | `config` | `GraphConfig` | Updated chart configuration with annotations applied | The response is validated with Zod. Invalid responses throw an error. *** ## Basic Usage ```typescript theme={null} import { GraphyAiSdk } from '@graphysdk/agents-sdk'; import type { GraphConfig } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }); const config: GraphConfig = { type: 'column', data: { columns: [ { key: 'month', label: 'Month' }, { key: 'revenue', label: 'Revenue' }, ], rows: [ { month: 'Jan', revenue: 100 }, { month: 'Feb', revenue: 140 }, { month: 'Mar', revenue: 220 }, { month: 'Apr', revenue: 130 }, ], }, }; const result = await ai.generateAnnotations({ config, userPrompt: 'Highlight the March peak and add a tooltip explaining it', }); console.log(result.config.annotations); // Annotations applied to the chart ``` *** ## With Progress Callback Use the `onProgress` callback to show real-time progress without full streaming: ```typescript theme={null} const result = await ai.generateAnnotations( { config, userPrompt: 'Add a difference arrow between the first and last month', }, (progress) => { console.log(progress.message); } ); ``` *** ## Streaming For full control over the event stream: ```typescript theme={null} import { isProgressEvent, isCompleteEvent, isErrorEvent } from '@graphysdk/agents-sdk'; const stream = await ai.generateAnnotationsStream({ config, userPrompt: 'Highlight every month above 150', }); for await (const event of stream) { if (isProgressEvent(event)) { console.log(event.message); } if (isCompleteEvent(event)) { console.log('Result:', event.data.config); } if (isErrorEvent(event)) { console.error('Error:', event.error); } } ``` See [Streaming](/agents/sdk/streaming) for cancellation and React patterns. *** ## Error Handling ```typescript theme={null} import { isGraphyApiError } from '@graphysdk/agents-sdk'; try { const result = await ai.generateAnnotations({ config, userPrompt: 'Highlight the peak value', }); } catch (error) { if (isGraphyApiError(error)) { console.error('API error:', error.message); } } ``` See [Error Handling](/agents/sdk/errors) for retry behavior and error types. # Chart Maker Agent Source: https://docs.graphy.dev/agents/sdk/chart-maker Modify charts using natural language. Send a `GraphConfig` and a prompt, receive an updated `GraphConfig`. ## Methods ### generateGraph Processes the request and returns the final result. Internally handles streaming and collects the response. ```typescript theme={null} async generateGraph( params: GenerateGraphParams, onProgress?: (event: ProgressEvent) => void, signal?: AbortSignal ): Promise ``` ### generateGraphStream Returns an async iterator that yields events as they arrive. ```typescript theme={null} async generateGraphStream( params: GenerateGraphParams, signal?: AbortSignal ): Promise>> ``` ## Parameters ```typescript theme={null} interface GenerateGraphParams { config: GraphConfig; userPrompt?: string; metadata?: Metadata; storytellingOptions?: StorytellingOptions; } ``` | Field | Type | Required | Description | | --------------------- | --------------------- | -------- | -------------------------------------------------------------- | | `config` | `GraphConfig` | Yes | The current chart configuration | | `userPrompt` | `string` | No | Natural language instruction | | `metadata` | `Metadata` | No | Request tracking information | | `storytellingOptions` | `StorytellingOptions` | No | Prune storytelling from config content client-side (SDK only). | ### Metadata ```typescript theme={null} interface Metadata { callId?: string; locale?: string; effort?: 'low' | 'medium' | 'high'; /** @deprecated Prefer `effort`. */ storytellingEffort?: 'none' | 'low' | 'medium' | 'high'; } ``` | Field | Type | Description | | -------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `callId` | `string` | Optional. Identifier for tracking and debugging. | | `locale` | `string` | Locale for responses (e.g., `en-US`, `fr-FR`). | | `effort` | `'low' \| 'medium' \| 'high'` | Preferred invocation tier; stored on the invocation and forwarded to nested agents. Default is **`medium`** when omitted (and nothing deprecated is used to infer it). See [Invocation effort and chart narrative](#invocation-effort-and-chart-narrative). | | `storytellingEffort` | `'none' \| 'low' \| 'medium' \| 'high'` | **Deprecated** — prefer `effort`. Still accepted; when only this field is set, the server infers `effort` for the rest of the pipeline. | ## Response ```typescript theme={null} interface GenerateGraphResponse { config: GraphConfig; response: { message: string; steps?: string[]; }; } ``` | Field | Type | Description | | ------------------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `config` | `GraphConfig` | Updated chart configuration. Narrative (title, subtitle, caption) is embedded in `config.content` as TipTap JSON documents; see [Invocation effort and chart narrative](#invocation-effort-and-chart-narrative). | | `response.message` | `string` | Explanation of changes made | | `response.steps` | `string[]` | Breakdown of individual modifications | The response is validated with Zod. Invalid responses throw an error. *** ## Invocation effort and chart narrative `metadata.effort` selects how heavy the invocation is: it is stored on the request, forwarded into agent invocations (including tools that run nested agents), and drives defaults when you do not send `storytellingEffort`. For **chart generation**, the chart agent still consumes a storytelling level (`none` / `low` / `medium` / `high`). The server sets that level as follows: * If you send **only** `effort`, the chart storytelling level is derived from `effort` (default `effort` is **`medium`**, which matches the narrative band you previously got from omitting metadata or using the old default). * If you send **only** `storytellingEffort` (deprecated), the chart uses that value directly and the server infers `effort` for the rest of the stack. * If you send **both**, `storytellingEffort` wins for the chart so existing explicit chart settings keep working; `effort` still applies everywhere else. There is no **`none`** value on `effort`; to drop narrative fields in the client, use **`storytellingOptions`** on `GenerateGraphParams` (see [StorytellingOptions](/agents/reference/types#storytellingoptions)). ```typescript theme={null} const result = await ai.generateGraph({ config, userPrompt: 'Show sales by region and add a clear title', metadata: { effort: 'high' }, }); // Narrative is embedded in the config content as TipTap JSON console.log(result.config.content?.title); // TipTap JSON doc with title text console.log(result.config.content?.subtitle); console.log(result.config.content?.caption); ``` *** ## Basic Usage ```typescript theme={null} import { GraphyAiSdk } from '@graphysdk/agents-sdk'; import type { GraphConfig } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }); const config: GraphConfig = { type: 'column', data: { columns: [ { key: 'month', label: 'Month' }, { key: 'sales', label: 'Sales' }, ], rows: [ { month: 'Jan', sales: 100 }, { month: 'Feb', sales: 120 }, { month: 'Mar', sales: 115 }, ], }, }; const result = await ai.generateGraph({ config, userPrompt: 'Change this to a bar chart and sort by sales descending', }); console.log(result.config); // Updated GraphConfig console.log(result.response.message); // Explanation ``` *** ## With Progress Callback Use the `onProgress` callback to show real-time progress without full streaming: ```typescript theme={null} const result = await ai.generateGraph( { config, userPrompt: 'Add a trend line and change colors to blue', }, (progress) => { console.log(progress.message); } ); ``` ### ProgressEvent ```typescript theme={null} interface ProgressEvent { type: 'progress'; message: string; agentId?: string; executionId?: string; iteration?: number; metadata?: Record; } ``` *** ## With Abort Signal Cancel a request mid-operation: ```typescript theme={null} const controller = new AbortController(); // Cancel after 5 seconds setTimeout(() => controller.abort(), 5000); try { const result = await ai.generateGraph( { config, userPrompt: 'Create a complex visualization' }, undefined, controller.signal ); } catch (error) { if (error instanceof Error && error.name === 'AbortError') { console.log('Request cancelled'); } } ``` *** ## Streaming For full control over the event stream: ```typescript theme={null} import { isProgressEvent, isCompleteEvent, isErrorEvent } from '@graphysdk/agents-sdk'; const stream = await ai.generateGraphStream({ config, userPrompt: 'Add annotations for peak values', }); for await (const event of stream) { if (isProgressEvent(event)) { console.log(event.message); } if (isCompleteEvent(event)) { console.log('Result:', event.data.config); } if (isErrorEvent(event)) { console.error('Error:', event.error); } } ``` See [Streaming](/agents/sdk/streaming) for cancellation and React patterns. *** ## Error Handling ```typescript theme={null} import { isGraphyApiError } from '@graphysdk/agents-sdk'; try { const result = await ai.generateGraph({ config, userPrompt: 'Change to a bar chart', }); } catch (error) { if (isGraphyApiError(error)) { console.error('API error:', error.message); } else if (error instanceof Error && error.name === 'AbortError') { console.log('Request cancelled'); } else { console.error('Unexpected error:', error); } } ``` See [Error Handling](/agents/sdk/errors) for retry behavior and error types. # Configuration Source: https://docs.graphy.dev/agents/sdk/configuration ## ClientConfig ```typescript theme={null} interface ClientConfig { apiKey: string; baseUrl: string; timeout?: number; retryConfig?: RetryConfig; logger?: Logger; } ``` ## Options ### apiKey **Type:** `string` (required) Your Graphy API key (starts with `graphy_`). [Create one in the console](/agents/api-keys). ```typescript theme={null} const ai = new GraphyAiSdk({ apiKey: 'graphy_...', baseUrl: 'https://agents.graphy.dev', }); ``` ### baseUrl **Type:** `string` (required) The API base URL. Use `https://agents.graphy.dev` for production. ### timeout **Type:** `number` (optional) **Default:** `60000` (60 seconds) Request timeout in milliseconds. The timeout applies to the initial connection. Once streaming begins, the timeout resets for each chunk. ```typescript theme={null} const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', timeout: 30000, // 30 seconds }); ``` ### retryConfig **Type:** `RetryConfig` (optional) ```typescript theme={null} interface RetryConfig { attempts: number; // Max retry attempts (default: 3) delay: number; // Initial delay in ms (default: 1000) backoff: number; // Backoff multiplier (default: 2) } ``` **Defaults:** * `attempts`: 3 * `delay`: 1000ms * `backoff`: 2 Retry delay formula: `delay * (backoff ^ (attempt - 1))` With defaults: * Attempt 1: immediate * Attempt 2: 1000ms delay * Attempt 3: 2000ms delay **Retryable conditions:** * HTTP 5xx errors * HTTP 429 (rate limit) * Network failures **Non-retryable conditions:** * HTTP 4xx errors (except 429) * User abort signals ```typescript theme={null} const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', retryConfig: { attempts: 5, delay: 500, backoff: 1.5, }, }); ``` ### logger **Type:** `Logger` (optional) ```typescript theme={null} interface Logger { log: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void; debug: (...args: unknown[]) => void; } ``` **Default:** Uses `console.log`, `console.warn`, `console.error`, `console.debug` Provide a custom logger to integrate with your logging infrastructure (e.g., pino, winston). ## Full Example ```typescript theme={null} import { GraphyAiSdk } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', timeout: 45000, retryConfig: { attempts: 3, delay: 1000, backoff: 2, }, logger: console, }); ``` ## Health Check Use `ping()` to verify connectivity and measure latency: ```typescript theme={null} const health = await ai.ping(); if (health.ok) { console.log(`Connected. Latency: ${health.latency}ms`); } else { console.error('Connection failed'); } ``` **Response:** ```typescript theme={null} interface PingResponse { ok: boolean; latency: number; // milliseconds } ``` # Error Handling Source: https://docs.graphy.dev/agents/sdk/errors ## GraphyApiError All API errors are wrapped in `GraphyApiError`. Each error carries structured metadata you can use to decide how to handle the failure: ```typescript theme={null} import { isGraphyApiError } from '@graphysdk/agents-sdk'; try { const result = await ai.generateGraph({ config, userPrompt }); } catch (error) { if (isGraphyApiError(error)) { console.error(`[${error.code}] ${error.message} (HTTP ${error.status})`); if (error.retryable) { // schedule retry } } } ``` | Property | Type | Description | | ----------- | --------------------- | ---------------------------------------------- | | `message` | `string` | Human-readable error description | | `status` | `number \| undefined` | HTTP status code, if from an HTTP response | | `code` | `string \| undefined` | Machine-readable error code from the API | | `retryable` | `boolean` | Whether the SDK considers this error retryable | Use the `isGraphyApiError()` type guard to safely narrow unknown errors. See the [Type Reference](/agents/reference/types#graphyapierror) for the full class definition. *** ## Retry Behavior The SDK automatically retries failed requests based on `retryConfig`. **Default configuration:** ```typescript theme={null} { attempts: 3, delay: 1000, backoff: 2 } ``` **Retry delay formula:** ``` delay * (backoff ^ (attempt - 1)) ``` With defaults: | Attempt | Delay | | ------- | --------- | | 1 | Immediate | | 2 | 1000ms | | 3 | 2000ms | ### Retryable Conditions | Condition | Retries | | --------------------- | ------- | | HTTP 5xx | Yes | | HTTP 429 (rate limit) | Yes | | Network failure | Yes | | HTTP 4xx (except 429) | No | | User abort | No | | `GraphyApiError` | No | ### Custom Retry Configuration ```typescript theme={null} const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', retryConfig: { attempts: 5, // More attempts delay: 500, // Shorter initial delay backoff: 1.5, // Gentler backoff }, }); ``` ### Disable Retries ```typescript theme={null} const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', retryConfig: { attempts: 1, // No retries delay: 0, backoff: 1, }, }); ``` *** ## Error Codes Errors from the API include a `code` field: | Code | HTTP Status | Retryable | Description | | ---------------------- | ----------- | --------- | ---------------------------------- | | `VALIDATION_ERROR` | 400 | No | Invalid request body or parameters | | `AUTHENTICATION_ERROR` | 401 | No | Invalid or missing API key | | `RATE_LIMIT_ERROR` | 429 | Yes | Too many requests | | `PROCESSING_ERROR` | 500 | Yes | Internal processing failure | | `TIMEOUT_ERROR` | 504 | Yes | Request took too long | *** ## Network Errors Network failures throw a `GraphyApiError` with `retryable: true`: ```typescript theme={null} try { const result = await ai.generateGraph({ config, userPrompt }); } catch (error) { if (isGraphyApiError(error) && error.retryable) { console.error('Transient error, consider retrying'); } } ``` *** ## Abort Errors User cancellation throws a native `AbortError`: ```typescript theme={null} const controller = new AbortController(); try { const result = await ai.generateGraph({ config, userPrompt }, undefined, controller.signal); } catch (error) { if (error instanceof Error && error.name === 'AbortError') { // User cancelled - not an error return; } throw error; } ``` *** ## Stream Errors In streaming mode, errors can arrive as events: ```typescript theme={null} import { isErrorEvent } from '@graphysdk/agents-sdk'; for await (const event of stream) { if (isErrorEvent(event)) { console.error('Error:', event.error); console.error('Code:', event.code); console.error('Retryable:', event.retryable); if (event.retryable) { // Implement retry logic } return; } } ``` Or as exceptions when the stream fails: ```typescript theme={null} import { isGraphyApiError } from '@graphysdk/agents-sdk'; try { for await (const event of stream) { // ... } } catch (error) { if (error instanceof Error && error.name === 'AbortError') { return; // User cancelled } if (isGraphyApiError(error)) { console.error('API error:', error.message); } } ``` *** ## Timeout Errors The SDK throws `AbortError` when a request exceeds the configured timeout: ```typescript theme={null} const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', timeout: 10000, // 10 seconds }); try { const result = await ai.generateGraph({ config, userPrompt }); } catch (error) { if (error instanceof Error && error.name === 'AbortError') { console.error('Request timed out'); } } ``` *** ## Best Practices ### Log errors with context ```typescript theme={null} try { const result = await ai.generateGraph({ config, userPrompt }); } catch (error) { if (error instanceof Error) { logger.error('generateGraph failed', { error: error.message, userPrompt, configType: config.type, }); } throw error; } ``` ### Show user-friendly messages ```typescript theme={null} function getErrorMessage(error: unknown): string { if (isGraphyApiError(error)) { if (error.status === 401) return 'Authentication failed. Please check your API key.'; if (error.status === 429) return 'Too many requests. Please try again later.'; if (error.retryable) return 'Temporary issue. Please try again.'; return 'Failed to generate chart. Please try again.'; } if (error instanceof Error && error.name === 'AbortError') { return 'Request was cancelled.'; } return 'An unexpected error occurred.'; } ``` # Evaluation Source: https://docs.graphy.dev/agents/sdk/evaluate Score how well every chart type fits a dataset. Unlike the other agents, `evaluate` is deterministic — it runs a rule-based scorer with no language model, so it returns in a single fast response and uses no tokens. ## Method ### evaluate Scores the dataset and returns the ranked result. There is no streaming variant — the result is computed synchronously on the server. ```typescript theme={null} async evaluate( params: EvaluateParams, signal?: AbortSignal ): Promise ``` ## Parameters ```typescript theme={null} interface EvaluateParams { config: GraphConfig; chartFamily?: ChartFamily; metadata?: Metadata; } ``` | Field | Type | Required | Description | | ------------- | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `config` | `GraphConfig` | Yes | The chart configuration. Only `config.data` is scored. | | `chartFamily` | `ChartFamily` | No | Optional family hint — one of `comparison`, `relationship`, `distribution`, `composition`. When set, chart types in that family get a fit bonus. | | `metadata` | `Metadata` | No | Request tracking information | `evaluate` is deterministic — it reads only `config.data` and the optional `chartFamily`. The chart `type`, styling, and annotations on `config` are ignored. ## Response ```typescript theme={null} interface EvaluateResponse { ranking: ChartFitEntry[]; } ``` | Field | Type | Description | | --------- | ----------------- | ----------------------------------------------------------------- | | `ranking` | `ChartFitEntry[]` | Every supported chart type, scored and ranked by fit (best first) | The response is validated with Zod. Invalid responses throw an error. ### ChartFitEntry A single chart type, scored against the dataset. ```typescript theme={null} interface ChartFitEntry { rank: number; type: AiChartType; score: number; verdict: ChartFitVerdict; factors: ChartFitFactor[]; } type ChartFitVerdict = 'ok' | 'disqualified'; ``` | Field | Type | Description | | --------- | ------------------ | ------------------------------------------------------------------------------------ | | `rank` | `number` | 1-based rank in `ranking`. Stable as scoring rules evolve — prefer it over `score`. | | `type` | `AiChartType` | The chart type being scored (see [AiChartType](/agents/reference/types#aicharttype)) | | `score` | `number` | Raw fit score. Drifts as rules change; use `rank` and `verdict` for stable checks. | | `verdict` | `ChartFitVerdict` | `ok` if the chart type is usable, `disqualified` if a rule rules it out | | `factors` | `ChartFitFactor[]` | The individual reasons that contributed to the score | ### ChartFitFactor ```typescript theme={null} interface ChartFitFactor { kind: ChartFitFactorKind; label: string; weight: number; reason: string; } type ChartFitFactorKind = | 'deal-breaker' | 'pro' | 'con' | 'family' | 'specificity' | 'continuity'; ``` | Field | Type | Description | | -------- | -------------------- | -------------------------------------- | | `kind` | `ChartFitFactorKind` | The category of the factor | | `label` | `string` | Short label for the factor | | `weight` | `number` | How much the factor moved the score | | `reason` | `string` | Why the factor applies to this dataset | *** ## Basic Usage ```typescript theme={null} import { GraphyAiSdk } from '@graphysdk/agents-sdk'; import type { GraphConfig } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }); const config: GraphConfig = { type: 'column', data: { columns: [ { key: 'product', label: 'Product' }, { key: 'revenue', label: 'Revenue' }, { key: 'units', label: 'Units Sold' }, ], rows: [ { product: 'Widget A', revenue: 4200, units: 120 }, { product: 'Widget B', revenue: 3100, units: 90 }, { product: 'Widget C', revenue: 5600, units: 60 }, ], }, }; const result = await ai.evaluate({ config }); const best = result.ranking[0]; console.log(`Best fit: ${best.type} — rank ${best.rank}, ${best.verdict}`); ``` *** ## Error Handling ```typescript theme={null} import { isGraphyApiError } from '@graphysdk/agents-sdk'; try { const result = await ai.evaluate({ config }); } catch (error) { if (isGraphyApiError(error)) { console.error('API error:', error.message); } } ``` See [Error Handling](/agents/sdk/errors) for retry behavior and error types. # Extract Agent Source: https://docs.graphy.dev/agents/sdk/extract Build a chart-ready dataset from unstructured sources. Send raw text, images, PDFs, or Excel spreadsheets, receive a `GraphConfig` with the extracted data. ## Methods ### extractFromProse Processes the request and returns the final result. Internally handles streaming and collects the response. ```typescript theme={null} async extractFromProse( params: ExtractFromProseParams, onProgress?: (event: ProgressEvent) => void, signal?: AbortSignal ): Promise ``` ### extractFromProseStream Returns an async iterator that yields events as they arrive. ```typescript theme={null} async extractFromProseStream( params: ExtractFromProseParams, signal?: AbortSignal ): Promise>> ``` ## Parameters ```typescript theme={null} interface ExtractFromProseParams { sourceText?: string; attachments?: ExtractAttachmentInput[]; images?: ExtractImageInput[]; metadata?: Metadata; } ``` | Field | Type | Required | Description | | ------------- | -------------------------- | -------- | ------------------------------------------------------------------------------------ | | `sourceText` | `string` | No | Raw text to extract data from. Up to 500,000 characters. | | `attachments` | `ExtractAttachmentInput[]` | No | Images, PDFs, and spreadsheets to extract data from. Up to 12 entries. | | `images` | `ExtractImageInput[]` | No | **Deprecated.** Legacy image array — use `attachments` with `kind: 'image'` instead. | | `metadata` | `Metadata` | No | Request tracking information | Provide `sourceText`, `attachments`, or both — at least one is required. `images` and `attachments` cannot be combined in a single request. ## Attachments `attachments` is a discriminated union tagged by `kind`. Use the builder helpers to construct entries — each accepts base64-encoded file data and returns a correctly-tagged attachment. ```typescript theme={null} import { buildImageAttachment, buildPdfAttachment, buildSpreadsheetAttachment, } from '@graphysdk/agents-sdk'; function buildImageAttachment(input: { mimeType: string; dataBase64: string }): ExtractAttachmentInput; function buildPdfAttachment(input: { dataBase64: string }): ExtractAttachmentInput; function buildSpreadsheetAttachment(input: { dataBase64: string; filename?: string }): ExtractAttachmentInput; ``` ### ExtractAttachmentInput ```typescript theme={null} type ExtractAttachmentInput = | { kind: 'image'; mimeType: string; dataBase64: string } | { kind: 'document'; mimeType: string; dataBase64: string } | { kind: 'spreadsheet'; mimeType: string; dataBase64: string; filename?: string }; ``` | `kind` | Accepted formats | Builder | | --------------- | -------------------- | ---------------------------- | | `'image'` | PNG, JPEG, WebP, GIF | `buildImageAttachment` | | `'document'` | PDF | `buildPdfAttachment` | | `'spreadsheet'` | Excel `.xlsx` | `buildSpreadsheetAttachment` | `dataBase64` must be RFC 4648 standard base64. The builders set `mimeType` for you (`buildImageAttachment` takes it as input since images have several valid types). ### Size limits | Attachment | Max count | Max size each | Max size combined | | ------------ | --------- | ------------- | ----------------- | | Images | 8 | 2 MB | 7 MB | | PDFs | 2 | 6 MB | 8 MB | | Spreadsheets | 2 | 6 MB | 8 MB | Across all kinds, a request accepts up to **12 attachments** and **12 MB** combined. Limits are measured on the decoded bytes, not the base64 string. ## Response ```typescript theme={null} interface ExtractFromProseResponse { response: { message: string; }; config: GraphConfig; extractMeta: ExtractMeta | null; lastAccuracyEvaluation?: ExtractAccuracyEvaluation; } ``` | Field | Type | Description | | ------------------------ | --------------------------- | ------------------------------------------------------------------ | | `response.message` | `string` | Summary of what was extracted | | `config` | `GraphConfig` | Chart configuration containing the extracted dataset | | `extractMeta` | `ExtractMeta \| null` | Trust metadata for the extraction, or `null` when unavailable | | `lastAccuracyEvaluation` | `ExtractAccuracyEvaluation` | The agent's final self-check of the extracted data, when performed | The response is validated with Zod. Invalid responses throw an error. ### ExtractMeta ```typescript theme={null} interface ExtractMeta { confidence: ExtractConfidence; needsUserInput: boolean; warnings: string[]; cellConfidence?: Array<{ rowIndex: number; columnKey: string; confidence: ExtractConfidence; quote?: string; }>; } type ExtractConfidence = 'high' | 'medium' | 'low'; ``` | Field | Type | Description | | ---------------- | ------------------- | ----------------------------------------------------------------- | | `confidence` | `ExtractConfidence` | Overall confidence in the extraction | | `needsUserInput` | `boolean` | Whether the result should be reviewed before use | | `warnings` | `string[]` | Notes about ambiguous, conflicting, or missing source data | | `cellConfidence` | `Array<…>` | Optional per-cell confidence, each with a supporting source quote | ### ExtractAccuracyEvaluation ```typescript theme={null} interface ExtractAccuracyEvaluation { score: number; issues: string[]; sufficient: boolean; summary: string; } ``` | Field | Type | Description | | ------------ | ---------- | ------------------------------------------------ | | `score` | `number` | Accuracy score, from `0` to `1` | | `issues` | `string[]` | Specific accuracy problems the agent identified | | `sufficient` | `boolean` | Whether the accuracy is good enough to use as-is | | `summary` | `string` | Human-readable summary of the accuracy check | *** ## Basic Usage Extract from plain text: ```typescript theme={null} import { GraphyAiSdk } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }); const result = await ai.extractFromProse({ sourceText: ` In Q3, the Direct channel brought in $1.9M, Partner $0.4M, and Marketplace $1.1M. `, }); console.log(result.config.data); // Extracted dataset console.log(result.extractMeta?.confidence); // 'high' | 'medium' | 'low' ``` *** ## Extracting from Files Read a file, base64-encode it, and wrap it with the matching builder: ```typescript theme={null} import { GraphyAiSdk, buildImageAttachment, buildPdfAttachment, buildSpreadsheetAttachment, } from '@graphysdk/agents-sdk'; import { readFile } from 'node:fs/promises'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }); // From a screenshot of a table const png = await readFile('./table.png'); const fromImage = await ai.extractFromProse({ attachments: [buildImageAttachment({ mimeType: 'image/png', dataBase64: png.toString('base64') })], }); // From a PDF report const pdf = await readFile('./q3-report.pdf'); const fromPdf = await ai.extractFromProse({ attachments: [buildPdfAttachment({ dataBase64: pdf.toString('base64') })], }); // From an Excel spreadsheet const xlsx = await readFile('./q3-report.xlsx'); const fromSpreadsheet = await ai.extractFromProse({ attachments: [buildSpreadsheetAttachment({ dataBase64: xlsx.toString('base64'), filename: 'q3-report.xlsx' })], }); ``` You can combine `sourceText` and multiple `attachments` in one request — the agent reconciles all sources into a single dataset. *** ## With Progress Callback Use the `onProgress` callback to show real-time progress without full streaming: ```typescript theme={null} const result = await ai.extractFromProse( { sourceText: longReport, }, (progress) => { console.log(progress.message); } ); ``` *** ## Streaming For full control over the event stream: ```typescript theme={null} import { isProgressEvent, isCompleteEvent, isErrorEvent } from '@graphysdk/agents-sdk'; const stream = await ai.extractFromProseStream({ sourceText: longReport, }); for await (const event of stream) { if (isProgressEvent(event)) { console.log(event.message); } if (isCompleteEvent(event)) { console.log('Result:', event.data.config); } if (isErrorEvent(event)) { console.error('Error:', event.error); } } ``` See [Streaming](/agents/sdk/streaming) for cancellation and React patterns. *** ## Error Handling ```typescript theme={null} import { isGraphyApiError } from '@graphysdk/agents-sdk'; try { const result = await ai.extractFromProse({ sourceText }); } catch (error) { if (isGraphyApiError(error)) { console.error('API error:', error.message); } } ``` See [Error Handling](/agents/sdk/errors) for retry behavior and error types. # Installation Source: https://docs.graphy.dev/agents/sdk/installation ## Package Installation ```bash npm theme={null} npm install @graphysdk/agents-sdk ``` ```bash yarn theme={null} yarn add @graphysdk/agents-sdk ``` ```bash pnpm theme={null} pnpm add @graphysdk/agents-sdk ``` No npm org token is required. The package publishes publicly. ## Import ```typescript theme={null} import { GraphyAiSdk, type GraphConfig } from '@graphysdk/agents-sdk'; ``` `GraphConfig` is the Agents wire type exported by the SDK. ## TypeScript Configuration The SDK is written in TypeScript and ships with type definitions. No additional `@types` packages are needed. Minimum TypeScript version: 4.7+ Recommended `tsconfig.json` settings: ```json theme={null} { "compilerOptions": { "moduleResolution": "bundler", "esModuleInterop": true, "strict": true } } ``` ## Environment Setup First, [create an API key in the Graphy console](/agents/api-keys). Then store it in an environment variable: ```bash theme={null} # .env GRAPHY_API_KEY=graphy_... ``` Access it in your code: ```typescript theme={null} const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }); ``` Never commit API keys to version control. Use environment variables or a secrets manager. ## Verify Installation ```typescript theme={null} import { GraphyAiSdk } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }); // Check connectivity const health = await ai.ping(); console.log(health); // { ok: true, latency: 45 } ``` # Mutation Agent Source: https://docs.graphy.dev/agents/sdk/mutate Transform a chart's dataset with natural language. Send a `GraphConfig` and a prompt, receive a `GraphConfig` with the dataset reshaped — filtered, grouped, aggregated, derived, and sorted. ## Methods ### generateMutation Processes the request and returns the final result. Internally handles streaming and collects the response. ```typescript theme={null} async generateMutation( params: GenerateMutationParams, onProgress?: (event: ProgressEvent) => void, signal?: AbortSignal ): Promise ``` ### generateMutationStream Returns an async iterator that yields events as they arrive. ```typescript theme={null} async generateMutationStream( params: GenerateMutationParams, signal?: AbortSignal ): Promise>> ``` ## Parameters ```typescript theme={null} interface GenerateMutationParams { config: GraphConfig; userPrompt?: string; metadata?: Metadata; } ``` | Field | Type | Required | Description | | ------------ | ------------- | -------- | ------------------------------------------------------------------- | | `config` | `GraphConfig` | Yes | The current chart configuration, including the dataset to transform | | `userPrompt` | `string` | No | Natural language description of the transformation to apply | | `metadata` | `Metadata` | No | Request tracking information | ## Response ```typescript theme={null} interface GenerateMutationResponse { response: { message: string; steps: string[]; }; config: GraphConfig; } ``` | Field | Type | Description | | ------------------ | ------------- | -------------------------------------------------------- | | `response.message` | `string` | Explanation of the transformations applied | | `response.steps` | `string[]` | Ordered breakdown of each transformation step | | `config` | `GraphConfig` | Updated chart configuration with the transformed dataset | The response is validated with Zod. Invalid responses throw an error. *** ## Basic Usage ```typescript theme={null} import { GraphyAiSdk } from '@graphysdk/agents-sdk'; import type { GraphConfig } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }); const config: GraphConfig = { type: 'column', data: { columns: [ { key: 'region', label: 'Region' }, { key: 'sales', label: 'Sales' }, ], rows: [ { region: 'North', sales: 100 }, { region: 'South', sales: 80 }, { region: 'North', sales: 120 }, { region: 'South', sales: 95 }, ], }, }; const result = await ai.generateMutation({ config, userPrompt: 'Total sales by region, sorted highest first', }); console.log(result.config); // GraphConfig with the aggregated dataset console.log(result.response.steps); // Ordered list of transformations applied ``` *** ## With Progress Callback Use the `onProgress` callback to show real-time progress without full streaming: ```typescript theme={null} const result = await ai.generateMutation( { config, userPrompt: 'Keep only the last 12 months and add a 3-month moving average', }, (progress) => { console.log(progress.message); } ); ``` *** ## Streaming For full control over the event stream: ```typescript theme={null} import { isProgressEvent, isCompleteEvent, isErrorEvent } from '@graphysdk/agents-sdk'; const stream = await ai.generateMutationStream({ config, userPrompt: 'Filter to the EMEA region and rank by revenue', }); for await (const event of stream) { if (isProgressEvent(event)) { console.log(event.message); } if (isCompleteEvent(event)) { console.log('Result:', event.data.config); } if (isErrorEvent(event)) { console.error('Error:', event.error); } } ``` See [Streaming](/agents/sdk/streaming) for cancellation and React patterns. *** ## Error Handling ```typescript theme={null} import { isGraphyApiError } from '@graphysdk/agents-sdk'; try { const result = await ai.generateMutation({ config, userPrompt: 'Group sales by region', }); } catch (error) { if (isGraphyApiError(error)) { console.error('API error:', error.message); } } ``` See [Error Handling](/agents/sdk/errors) for retry behavior and error types. # Narrative Agent Source: https://docs.graphy.dev/agents/sdk/narrate Generate a chart's title, subtitle, and caption with natural language. Send a `GraphConfig` and a prompt, receive a `GraphConfig` with narrative text written into `config.content`. ## Methods ### generateNarrative Processes the request and returns the final result. Internally handles streaming and collects the response. ```typescript theme={null} async generateNarrative( params: GenerateNarrativeParams, onProgress?: (event: ProgressEvent) => void, signal?: AbortSignal ): Promise ``` ### generateNarrativeStream Returns an async iterator that yields events as they arrive. ```typescript theme={null} async generateNarrativeStream( params: GenerateNarrativeParams, signal?: AbortSignal ): Promise>> ``` ## Parameters ```typescript theme={null} interface GenerateNarrativeParams { config: GraphConfig; userPrompt: string; metadata?: Metadata; } ``` | Field | Type | Required | Description | | ------------ | ------------- | -------- | ---------------------------------------------------------------------------- | | `config` | `GraphConfig` | Yes | The chart configuration to narrate | | `userPrompt` | `string` | Yes | Natural language instruction for the narrative — tone, focus, or refinements | | `metadata` | `Metadata` | No | Request tracking information | ## Response ```typescript theme={null} interface GenerateNarrativeResponse { response: { message: string; }; config: GraphConfig; } ``` | Field | Type | Description | | ------------------ | ------------- | --------------------------------------------------------------------------------------------------------------------- | | `response.message` | `string` | Explanation of the narrative produced | | `config` | `GraphConfig` | Updated chart configuration. Title, subtitle, and caption are written into `config.content` as TipTap JSON documents. | The response is validated with Zod. Invalid responses throw an error. Which fields are written — title only, title and caption, or all three — depends on the storytelling level. See [Invocation effort and chart narrative](/agents/sdk/chart-maker#invocation-effort-and-chart-narrative) for how `metadata.effort` controls this. *** ## Basic Usage ```typescript theme={null} import { GraphyAiSdk } from '@graphysdk/agents-sdk'; import type { GraphConfig } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }); const config: GraphConfig = { type: 'line', data: { columns: [ { key: 'month', label: 'Month' }, { key: 'users', label: 'Active Users' }, ], rows: [ { month: 'Jan', users: 1200 }, { month: 'Feb', users: 1800 }, { month: 'Mar', users: 3100 }, ], }, }; const result = await ai.generateNarrative({ config, userPrompt: 'Write a punchy title and a one-line caption', }); // Narrative is embedded in the config content as TipTap JSON console.log(result.config.content?.title); console.log(result.config.content?.caption); ``` *** ## With Progress Callback Use the `onProgress` callback to show real-time progress without full streaming: ```typescript theme={null} const result = await ai.generateNarrative( { config, userPrompt: 'Make the title more specific and lead with the headline number', }, (progress) => { console.log(progress.message); } ); ``` *** ## Streaming For full control over the event stream: ```typescript theme={null} import { isProgressEvent, isCompleteEvent, isErrorEvent } from '@graphysdk/agents-sdk'; const stream = await ai.generateNarrativeStream({ config, userPrompt: 'Write a title, subtitle, and caption', }); for await (const event of stream) { if (isProgressEvent(event)) { console.log(event.message); } if (isCompleteEvent(event)) { console.log('Result:', event.data.config); } if (isErrorEvent(event)) { console.error('Error:', event.error); } } ``` See [Streaming](/agents/sdk/streaming) for cancellation and React patterns. *** ## Error Handling ```typescript theme={null} import { isGraphyApiError } from '@graphysdk/agents-sdk'; try { const result = await ai.generateNarrative({ config, userPrompt: 'Write a title for this chart', }); } catch (error) { if (isGraphyApiError(error)) { console.error('API error:', error.message); } } ``` See [Error Handling](/agents/sdk/errors) for retry behavior and error types. # Streaming Source: https://docs.graphy.dev/agents/sdk/streaming AI operations take time. Streaming shows real-time progress as the agent works. ## Event Types ```typescript theme={null} type SSEEvent = ProgressEvent | CompleteEvent | ErrorEvent | PreviewEvent | ReasoningEvent; ``` Every streaming method returns the same `SSEEvent` union, so the patterns below work for all of them. ### ProgressEvent A human-readable progress update. `message` is always set; the agent-loop fields are present on events emitted from inside an agent. ```typescript theme={null} interface ProgressEvent { type: 'progress'; message: string; agentId?: string; executionId?: string; iteration?: number; metadata?: Record; } ``` ### CompleteEvent ```typescript theme={null} interface CompleteEvent { type: 'complete'; data: T; } ``` The final event on a successful stream. `data` is the response type of the method you called — for example, a `generateGraphStream()` stream completes with a [GenerateGraphResponse](/agents/reference/types#generategraphresponse). ### ErrorEvent ```typescript theme={null} interface ErrorEvent { type: 'error'; error: string; code?: string; retryable?: boolean; } ``` ### PreviewEvent An incremental preview of the agent's work-in-progress result. The payload is spread at the top level, and its shape depends on the agent — [Mutation](/agents/sdk/mutate) previews carry `config`. ```typescript theme={null} interface PreviewEvent { type: 'preview'; [key: string]: unknown; } ``` ### ReasoningEvent A reasoning message emitted by an agent as it works — useful for surfacing the agent's thinking in a UI. ```typescript theme={null} interface ReasoningEvent { type: 'reasoning'; agentId: string; executionId: string; message: string; } ``` *** ## Async Iterator Pattern `generateGraphStream()` returns an async iterator: ```typescript theme={null} import { isProgressEvent, isCompleteEvent, isErrorEvent } from '@graphysdk/agents-sdk'; const stream = await ai.generateGraphStream({ config, userPrompt: 'Add a trend line', }); for await (const event of stream) { if (isProgressEvent(event)) { console.log(event.message); } if (isCompleteEvent(event)) { return event.data.config; } if (isErrorEvent(event)) { throw new Error(event.error); } } ``` *** ## Preview Events Some agents emit `preview` events carrying partial results before the stream completes. Render them to show work-in-progress. ```typescript theme={null} import { isPreviewEvent, isCompleteEvent } from '@graphysdk/agents-sdk'; const stream = await ai.generateMutationStream({ config, userPrompt: 'Group sales by region', }); for await (const event of stream) { if (isPreviewEvent(event)) { // Payload shape depends on the agent — mutation previews carry `config` console.log('Preview:', event.config); } if (isCompleteEvent(event)) { console.log('Final:', event.data.config); } } ``` The fields on a preview event depend on the agent — see each agent's page for what it streams. Events you do not handle can be safely ignored. *** ## Cancellation Pass an `AbortSignal` to cancel mid-operation: ```typescript theme={null} import { isCompleteEvent } from '@graphysdk/agents-sdk'; const controller = new AbortController(); const stream = await ai.generateGraphStream({ config, userPrompt: 'Create a complex heatmap' }, controller.signal); // Cancel from a button click cancelButton.onclick = () => controller.abort(); try { for await (const event of stream) { if (isCompleteEvent(event)) { return event.data; } } } catch (error) { if (error instanceof Error && error.name === 'AbortError') { console.log('Cancelled by user'); return; } throw error; } ``` *** ## Progress Callback Alternative If you want progress updates without the streaming API, use the `onProgress` callback with `generateGraph()`: ```typescript theme={null} const result = await ai.generateGraph({ config, userPrompt: 'Add a trend line' }, (progress) => { setStatus(progress.message); }); // result contains the final GenerateGraphResponse ``` This collects the stream internally and returns the final result. *** ## React Pattern Store the abort controller in a ref for proper cleanup: ```tsx theme={null} import { useState, useRef, useEffect } from 'react'; import { GraphyAiSdk, isProgressEvent, isCompleteEvent, isErrorEvent } from '@graphysdk/agents-sdk'; import type { GraphConfig } from '@graphysdk/agents-sdk'; interface Props { initialConfig: GraphConfig; onUpdate: (config: GraphConfig) => void; } function ChartEditor({ initialConfig, onUpdate }: Props) { const [status, setStatus] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const abortRef = useRef(null); const ai = useRef( new GraphyAiSdk({ apiKey: process.env.NEXT_PUBLIC_GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }) ).current; const handleGenerate = async (prompt: string) => { // Cancel any in-flight request abortRef.current?.abort(); abortRef.current = new AbortController(); setIsLoading(true); setStatus(''); setError(null); try { const stream = await ai.generateGraphStream( { config: initialConfig, userPrompt: prompt }, abortRef.current.signal ); for await (const event of stream) { if (isProgressEvent(event)) { setStatus(event.message); } if (isCompleteEvent(event)) { onUpdate(event.data.config); } if (isErrorEvent(event)) { setError(event.error); } } } catch (err) { if (err instanceof Error && err.name !== 'AbortError') { setError(err.message); } } finally { setIsLoading(false); } }; // Cleanup on unmount useEffect(() => { return () => abortRef.current?.abort(); }, []); return (
{isLoading && (
{status}
)} {error &&
{error}
}
); } ``` **Key patterns:** 1. **Cancel previous requests** — Abort any in-flight request before starting a new one 2. **Store in ref** — The abort controller persists across renders 3. **Cleanup on unmount** — Cancel pending requests when the component unmounts 4. **Handle AbortError** — Don't treat user cancellation as an error `ProgressEvent` carries a `message`, not a numeric percentage — show the latest message as a status line alongside an indeterminate progress indicator. *** ## Type Guards The SDK exports `isProgressEvent()`, `isCompleteEvent()`, `isErrorEvent()`, `isPreviewEvent()`, and `isReasoningEvent()` for narrowing SSE events, and `isGraphyApiError()` for narrowing caught errors. All examples on this page use these type guards. See the [Type Reference](/agents/reference/types) for full signatures. *** ## Error Handling in Streams Errors can come from two sources: 1. **Error events** — The API returns an error during processing 2. **Exceptions** — Network failure, timeout, or abort ```typescript theme={null} import { isErrorEvent, isCompleteEvent, isGraphyApiError } from '@graphysdk/agents-sdk'; try { const stream = await ai.generateGraphStream({ config, userPrompt }); for await (const event of stream) { if (isErrorEvent(event)) { // API-level error if (event.retryable) { // Offer retry option } throw new Error(event.error); } if (isCompleteEvent(event)) { return event.data; } } } catch (error) { if (error instanceof Error && error.name === 'AbortError') { // User cancelled return; } // Network or other error console.error('Stream failed:', error); } ``` See [Error Handling](/agents/sdk/errors) for details on error codes and retry behavior. # Suggestions Agent Source: https://docs.graphy.dev/agents/sdk/suggestions Generate chart type and data preparation suggestions from a dataset. Send a `GraphConfig` and a prompt, receive a list of suggested visualizations. ## Methods ### generateSuggestions Processes the request and returns the final result. Internally handles streaming and collects the response. ```typescript theme={null} async generateSuggestions( params: GenerateGraphSuggestionsParams, onProgress?: (event: ProgressEvent) => void, signal?: AbortSignal ): Promise ``` ### generateSuggestionsStream Returns an async iterator that yields events as they arrive. ```typescript theme={null} async generateSuggestionsStream( params: GenerateGraphSuggestionsParams, signal?: AbortSignal ): Promise>> ``` ## Parameters ```typescript theme={null} interface GenerateGraphSuggestionsParams { config: GraphConfig; userPrompt?: string; metadata?: Metadata; maxSuggestionCount?: 1 | 2 | 3 | 4; } ``` | Field | Type | Required | Description | | -------------------- | ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `config` | `GraphConfig` | Yes | The chart configuration containing the dataset | | `userPrompt` | `string` | No | Natural language description of what to visualize | | `metadata` | `Metadata` | No | Request tracking information | | `maxSuggestionCount` | `1` \| `2` \| `3` \| `4` | No | Optional cap (1–4) passed to the model for how many suggestions to aim for. Omitted uses the default (4). Responses are not truncated if longer. | ## Response ```typescript theme={null} interface GenerateGraphSuggestionsResponse { suggestions: Suggestion[]; } ``` | Field | Type | Description | | ------------- | -------------- | ------------------------------ | | `suggestions` | `Suggestion[]` | List of chart type suggestions | ### Suggestion ```typescript theme={null} interface Suggestion { dataPrepPrompt: string; chartType: AiChartType; summary: string; } ``` | Field | Type | Description | | ---------------- | ------------- | ----------------------------------------------------------------------------- | | `dataPrepPrompt` | `string` | Prompt describing how to prepare the data for this chart | | `chartType` | `AiChartType` | Suggested chart type (see [AiChartType](/agents/reference/types#aicharttype)) | | `summary` | `string` | Short description of what the chart would show | The response is validated with Zod. Invalid responses throw an error. *** ## Basic Usage ```typescript theme={null} import { GraphyAiSdk } from '@graphysdk/agents-sdk'; import type { GraphConfig } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }); const config: GraphConfig = { type: 'column', data: { columns: [ { key: 'month', label: 'Month' }, { key: 'sales', label: 'Sales' }, { key: 'region', label: 'Region' }, ], rows: [ { month: 'Jan', sales: 100, region: 'North' }, { month: 'Feb', sales: 120, region: 'South' }, { month: 'Mar', sales: 115, region: 'North' }, ], }, }; const result = await ai.generateSuggestions({ config, userPrompt: 'Show me interesting trends', }); for (const suggestion of result.suggestions) { console.log(`${suggestion.chartType}: ${suggestion.summary}`); } ``` *** ## With Progress Callback Use the `onProgress` callback to show real-time progress: ```typescript theme={null} const result = await ai.generateSuggestions( { config, userPrompt: 'What charts would best show this data?', }, (progress) => { console.log(progress.message); } ); ``` *** ## Streaming For full control over the event stream: ```typescript theme={null} import { isProgressEvent, isCompleteEvent, isErrorEvent } from '@graphysdk/agents-sdk'; const stream = await ai.generateSuggestionsStream({ config, userPrompt: 'Suggest charts for sales analysis', }); for await (const event of stream) { if (isProgressEvent(event)) { console.log(event.message); } if (isCompleteEvent(event)) { console.log('Suggestions:', event.data.suggestions); } if (isErrorEvent(event)) { console.error('Error:', event.error); } } ``` See [Streaming](/agents/sdk/streaming) for cancellation and React patterns. *** ## Error Handling ```typescript theme={null} import { isGraphyApiError } from '@graphysdk/agents-sdk'; try { const result = await ai.generateSuggestions({ config, userPrompt: 'Suggest charts', }); } catch (error) { if (isGraphyApiError(error)) { console.error('API error:', error.message); } } ``` See [Error Handling](/agents/sdk/errors) for retry behavior and error types. # Version management Source: https://docs.graphy.dev/agents/versioning ## What the API version covers The API uses URL-path versioning (`/api/v0/`, `/api/v1/`, etc.). Specifically, the API version covers: * **Endpoints** - available routes and their HTTP methods * **SSE envelope** - event types (`progress`, `complete`, `error`) and their shapes * **Authentication** - how API keys are passed and validated * **Error codes** - error response structure and codes * **Request envelope** - top-level request fields (`userPrompt`, `metadata`) * **GraphConfig schema** - breaking changes to `GraphConfig` trigger a new API major version ## GraphConfig is the data contract `GraphConfig` is the central type that flows through the Agents system. The wire type ships in `@graphysdk/agents-sdk`: ```mermaid theme={null} flowchart LR A["@graphysdk/agents-sdk"] -->|defines wire| B["GraphConfig"] B -->|sent by| C["GraphyAiSdk"] C -->|over HTTP| D["Agents API"] D -->|returns| B B -->|optional render| E["@graphysdk/react"] ``` Since the Agents API **generates** `GraphConfig`, breaking changes to its shape require a new API major version. Keep the TypeScript SDK on a compatible major with the API path you call (today: SDK 1.x with API `v0`). Non-breaking additions to the wire `GraphConfig` (new optional fields, new chart types) ship as `@graphysdk/agents-sdk` minor releases and do not require an API version change. ## Current version The API is on `v0`. Breaking changes may occur. Use the TypeScript SDK for the most stable integration. | API version | Status | | ----------- | -------------------------------------------------- | | `v0` | Active, pre-stable. May change. | | `v1` | Planned. Stable, backward-compatible within major. | When `v1` ships, the same guarantees as the SDK packages apply: no breaking changes within a major version, with a deprecation cycle before removal. ## Keeping the SDK up to date Install or upgrade the Agents client alone: ```bash npm theme={null} npm install @graphysdk/agents-sdk@latest ``` ```bash yarn theme={null} yarn add @graphysdk/agents-sdk@latest ``` ```bash pnpm theme={null} pnpm add @graphysdk/agents-sdk@latest ``` Rendering is separate from the Agents client — use `@graphysdk/react` when you want to display charts. ## When does what change? | Change | What bumps | API version change? | | ----------------------------------- | ----------------------------- | ------------------- | | New chart type or config option | `@graphysdk/agents-sdk` minor | No | | New optional field on `GraphConfig` | `@graphysdk/agents-sdk` minor | No | | Breaking `GraphConfig` change | `@graphysdk/agents-sdk` major | Yes (major bump) | | New SSE event type | Backwards-compatible addition | No | | New endpoint added | Backwards-compatible addition | No | | Endpoint renamed or removed | API major | Yes (major bump) | | Auth mechanism change | API major | Yes (major bump) | ## REST API consumers If you integrate directly with the REST API (without the TypeScript SDK), pin to a specific API version in your URLs and test before upgrading: ``` https://agents.graphy.dev/api/v0/generate ``` The TypeScript SDK handles API version targeting internally. When a new API version is available, updating the SDK is enough. You do not need to change URLs in your code. # Buffer Source: https://docs.graphy.dev/data-import-utils/buffer Parse a binary buffer (XLSX, XLS, or ODS) into the `{ columns, rows }` [Data structure](/sdk/core/data-structure) used by Graphy agents and chart configs. ## fromBuffer ```typescript theme={null} import { fromBuffer } from '@graphysdk/data-import-utils/buffer'; const data = await fromBuffer(xlsxBuffer, 'xlsx'); const data = await fromBuffer(odsBuffer, 'ods', { sheet: 'Revenue' }); ``` ### Signature ```typescript theme={null} function fromBuffer( input: ArrayBuffer, format: 'xlsx' | 'xls' | 'ods', options?: SpreadsheetParseOptions ): Promise ``` ## Options The binary format: `'xlsx'`, `'xls'`, or `'ods'`. Sheet to parse — name (string) or 0-based index (number). Locale for number parsing. Determines thousand/decimal separator conventions. Maximum allowed input size in megabytes. Maximum number of data rows to process. Maximum total cells to process. ## Examples ### Parse an uploaded spreadsheet ```typescript theme={null} import { fromBuffer } from '@graphysdk/data-import-utils/buffer'; const file: File = input.files[0]; const buffer = await file.arrayBuffer(); const data = await fromBuffer(buffer, 'xlsx'); ``` ### Select a specific sheet ```typescript theme={null} import { fromBuffer } from '@graphysdk/data-import-utils/buffer'; const data = await fromBuffer(buffer, 'xlsx', { sheet: 'Revenue' }); ``` # CSV Source: https://docs.graphy.dev/data-import-utils/csv Parse CSV strings into the `{ columns, rows }` [Data structure](/sdk/core/data-structure) used by Graphy agents and chart configs. ## fromCSV ```typescript theme={null} import { fromCSV } from '@graphysdk/data-import-utils/csv'; const data = fromCSV('Name,Revenue\nAcme,1000\nGlobex,2000'); const data = fromCSV('Name;Revenue\nAcme;1000\nGlobex;2000'); // semicolon-delimited ``` ### Signature ```typescript theme={null} function fromCSV(input: string, options?: DelimitedParseOptions): Data ``` ## Options Whether the first row contains column headers. When `false`, columns are auto-named `Column 1`, `Column 2`, etc. Locale for number parsing. Determines thousand/decimal separator conventions. For example, `'PT_PT'` treats `1.000,50` as `1000.5`. Maximum allowed input size in megabytes. Throws an error if the input exceeds this limit. ## Examples ### Without Headers ```typescript theme={null} const data = fromCSV('Acme,1000\nGlobex,2000', { hasHeader: false }); // Columns auto-named: "Column 1", "Column 2" ``` ### European Number Format (semicolon-delimited) ```typescript theme={null} const csv = `Produto;Receita Widgets;1.234,56 Gadgets;7.890,12`; const data = fromCSV(csv, { locale: 'PT_PT' }); // Receita values parsed as 1234.56 and 7890.12 ``` The CSV delimiter is auto-detected (comma, semicolon, pipe, etc.). For tab-separated data, you can also use [`fromTSV`](/data-import-utils/tsv). # Examples Source: https://docs.graphy.dev/data-import-utils/examples ## With the Agents SDK Parse a data file and generate a chart with the [Agents SDK](/agents/sdk/installation). ### From a File Path (Node.js) ```typescript theme={null} import { fromFile } from '@graphysdk/data-import-utils/file'; import { GraphyAiSdk } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }); const data = await fromFile('quarterly-sales.csv'); const result = await ai.generateGraph({ config: { data }, userPrompt: 'line chart showing quarterly trends', }); ``` ### From a File Upload (Browser) ```typescript theme={null} import { fromXLSX } from '@graphysdk/data-import-utils/xlsx'; import { GraphyAiSdk } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }); const input = document.querySelector('#file-input'); input.addEventListener('change', async () => { const file = input.files?.[0]; if (!file) return; const buffer = await file.arrayBuffer(); const data = await fromXLSX(buffer); const result = await ai.generateGraph({ config: { data }, userPrompt: 'chart from uploaded spreadsheet', }); }); ``` ## With the Graph Component Parse a data file and render it with the [`` component](/sdk/reference/graph). ### From a File Upload ```tsx theme={null} import { useState } from 'react'; import { fromXLSX } from '@graphysdk/data-import-utils/xlsx'; import { GraphProvider, Graph } from '@graphysdk/core'; import type { GraphConfig } from '@graphysdk/core'; function ChartFromUpload() { const [config, setConfig] = useState(null); const handleFile = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; const buffer = await file.arrayBuffer(); const data = await fromXLSX(buffer); setConfig({ data, graphType: 'column', }); }; return (
{config && ( )}
); } ``` ### From an API Response ```tsx theme={null} import { useState, useEffect } from 'react'; import { fromCSV } from '@graphysdk/data-import-utils/csv'; import { GraphProvider, Graph } from '@graphysdk/core'; import type { GraphConfig } from '@graphysdk/core'; function ChartFromAPI() { const [config, setConfig] = useState(null); useEffect(() => { fetch('/api/report.csv') .then((res) => res.text()) .then((csv) => { const data = fromCSV(csv); setConfig({ data, graphType: 'line' }); }); }, []); if (!config) return

Loading...

; return ( ); } ``` # File Source: https://docs.graphy.dev/data-import-utils/file Read a file from disk and parse it into the `{ columns, rows }` [Data structure](/sdk/core/data-structure) used by Graphy agents and chart configs. The format is auto-detected from the file extension. ## fromFile ```typescript theme={null} import { fromFile } from '@graphysdk/data-import-utils/file'; const data = await fromFile('sales.csv'); const data = await fromFile('report.xlsx', { sheet: 'Revenue' }); ``` ### Signature ```typescript theme={null} function fromFile( filePath: string, options?: FileParseOptions ): Promise ``` `FileParseOptions` combines spreadsheet options with the `hasHeader` flag from delimited parsing. ## Options Path to the file. The extension determines the format. Whether the first row contains column headers (CSV/TSV only). When `false`, columns are auto-named `Column 1`, `Column 2`, etc. Sheet to parse (spreadsheets only). Pass a sheet name or 0-based index. Locale for number parsing. Determines thousand/decimal separator conventions. Maximum allowed input size in megabytes. Throws an error if the input exceeds this limit. Maximum number of data rows to process (spreadsheets only). Maximum total cells to process (spreadsheets only). ## Supported Extensions | Extension | Format | | -------------- | ------ | | `.csv` | CSV | | `.tsv`, `.tab` | TSV | | `.xlsx` | XLSX | | `.xls` | XLS | | `.ods` | ODS | Unsupported extensions throw an error listing the supported formats. ## Examples ### With the AI SDK ```typescript theme={null} import { fromFile } from '@graphysdk/data-import-utils/file'; import { GraphyAiSdk } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }); const data = await fromFile('quarterly-sales.csv'); const result = await ai.generateGraph({ config: { data }, userPrompt: 'line chart showing quarterly trends', }); ``` ### Error Handling ```typescript theme={null} try { const data = await fromFile('data.pdf'); } catch (error) { // Error: Unsupported file extension ".pdf". Supported: .csv, .tsv, .tab, .xlsx, .xls, .ods } ``` `fromFile` uses Node.js `fs.readFile` under the hood and is not available in the browser. For browser usage, use the format-specific parsers (`fromCSV`, `fromXLSX`, etc.) after reading the file with the File API. # Quickstart Source: https://docs.graphy.dev/data-import-utils/index `@graphysdk/data-import-utils` converts CSV, TSV, and spreadsheet files into the `{ columns, rows }` data shape used by Graphy agents and chart configs. No `@graphysdk/core` dependency or npm org token is required. ## Installation ```bash npm theme={null} npm install @graphysdk/data-import-utils ``` ```bash yarn theme={null} yarn add @graphysdk/data-import-utils ``` ```bash pnpm theme={null} pnpm add @graphysdk/data-import-utils ``` ## Parse a CSV ```typescript theme={null} import { fromCSV } from '@graphysdk/data-import-utils'; const data = fromCSV('Name,Revenue\nAcme,1000\nGlobex,2000'); // { columns: [{ key: 'c1', label: 'Name' }, ...], rows: [...] } ``` ## Parse a Spreadsheet ```typescript theme={null} import { fromXLSX } from '@graphysdk/data-import-utils/xlsx'; const buffer = await file.arrayBuffer(); const data = await fromXLSX(buffer, { sheet: 'Revenue' }); ``` ## Parse from Disk (Node.js) ```typescript theme={null} import { fromFile } from '@graphysdk/data-import-utils/file'; const data = await fromFile('sales.csv'); ``` `fromFile` auto-detects the format from the file extension. See [File](/data-import-utils/file) for details. ## Parse from URL ```typescript theme={null} import { fromURL } from '@graphysdk/data-import-utils/url'; const data = await fromURL('https://example.com/sales.csv'); ``` `fromURL` fetches a remote file and auto-detects the format from the URL path extension. See [URL](/data-import-utils/url) for details. ## Features | Feature | Details | | ----------------------- | -------------------------------------------------------------------------------------------------- | | Formats | CSV, TSV, XLSX, XLS, ODS | | SSRF protection | `fromURL` blocks private IPs and non-http(s) schemes | | Timeout support | `fromURL` supports configurable timeout and `AbortSignal` | | Byte-budget enforcement | All parsers enforce a configurable `maxFileSize` limit; `fromURL` aborts streaming downloads early | | Row / cell limits | Spreadsheet parsers enforce `maxRows` (default 100k) and `maxCells` (default 5M) | ## Entrypoints ### Sources | Source | Function | Entrypoint | | --------------------- | -------------- | ------------------------------------- | | Local file | `fromFile()` | `@graphysdk/data-import-utils/file` | | Remote URL | `fromURL()` | `@graphysdk/data-import-utils/url` | | Text (CSV/TSV) | `fromText()` | `@graphysdk/data-import-utils/text` | | Binary (XLSX/XLS/ODS) | `fromBuffer()` | `@graphysdk/data-import-utils/buffer` | ### Formats | Format | Function | Input type | Entrypoint | | ------ | ------------ | ------------- | ----------------------------------------------- | | CSV | `fromCSV()` | `string` | `@graphysdk/data-import-utils` (root) or `/csv` | | TSV | `fromTSV()` | `string` | `@graphysdk/data-import-utils` (root) or `/tsv` | | XLSX | `fromXLSX()` | `ArrayBuffer` | `@graphysdk/data-import-utils/xlsx` | | XLS | `fromXLS()` | `ArrayBuffer` | `@graphysdk/data-import-utils/xls` | | ODS | `fromODS()` | `ArrayBuffer` | `@graphysdk/data-import-utils/ods` | # ODS Source: https://docs.graphy.dev/data-import-utils/ods Parse ODS (OpenDocument Spreadsheet) files into the `{ columns, rows }` [Data structure](/sdk/core/data-structure) used by Graphy agents and chart configs. ## fromODS ```typescript theme={null} import { fromODS } from '@graphysdk/data-import-utils/ods'; const buffer = await file.arrayBuffer(); const data = await fromODS(buffer); ``` ### Signature ```typescript theme={null} function fromODS( input: ArrayBuffer, options?: SpreadsheetParseOptions ): Promise ``` ## Options Sheet to parse. Pass a sheet name (`string`) or a 0-based index (`number`). Defaults to the first sheet. Locale for number parsing. Determines thousand/decimal separator conventions. Maximum allowed input size in megabytes. Throws an error if the input exceeds this limit. Maximum number of data rows to process. Throws an error if the sheet contains more rows. Maximum total cells (rows x columns) to process. Throws an error if the limit is exceeded. ## Examples ### Select Sheet by Name ```typescript theme={null} const data = await fromODS(buffer, { sheet: 'Revenue' }); ``` ## Cell Value Handling | Cell type | Result | | --------- | --------------- | | Number | `number` | | String | `string` | | Boolean | `1` or `0` | | Date | ISO 8601 string | | Formula | Computed result | | Rich text | Plain text | | Empty | `null` | `fromODS` uses the same XLSX-based parser under the hood. It works with modern `.ods` files that are actually in XLSX format (common in LibreOffice). Genuine native ODS format is not supported. # Text Source: https://docs.graphy.dev/data-import-utils/text Parse a text string (CSV or TSV) into the `{ columns, rows }` [Data structure](/sdk/core/data-structure) used by Graphy agents and chart configs. ## fromText ```typescript theme={null} import { fromText } from '@graphysdk/data-import-utils/text'; const data = fromText(csvContent, 'csv'); const data = fromText(tsvContent, 'tsv', { hasHeader: false }); ``` ### Signature ```typescript theme={null} function fromText( input: string, format: 'csv' | 'tsv', options?: DelimitedParseOptions ): Data ``` ## Options The text format: `'csv'` or `'tsv'`. Whether the first row contains column headers. When `false`, columns are auto-named `Column 1`, `Column 2`, etc. Locale for number parsing. Determines thousand/decimal separator conventions. Maximum allowed input size in megabytes. ## Examples ### Parse CSV from memory ```typescript theme={null} import { fromText } from '@graphysdk/data-import-utils/text'; const csv = 'Name,Revenue\nAcme,1000\nGlobex,2000'; const data = fromText(csv, 'csv'); ``` ### Handle a browser text file upload ```typescript theme={null} import { fromText } from '@graphysdk/data-import-utils/text'; const file: File = input.files[0]; const text = await file.text(); const data = fromText(text, 'csv'); ``` # TSV Source: https://docs.graphy.dev/data-import-utils/tsv Parse TSV (tab-separated values) strings into the `{ columns, rows }` [Data structure](/sdk/core/data-structure) used by Graphy agents and chart configs. ## fromTSV ```typescript theme={null} import { fromTSV } from '@graphysdk/data-import-utils/tsv'; const data = fromTSV('Name\tRevenue\nAcme\t1000\nGlobex\t2000'); ``` ### Signature ```typescript theme={null} function fromTSV(input: string, options?: DelimitedParseOptions): Data ``` ## Options Whether the first row contains column headers. When `false`, columns are auto-named `Column 1`, `Column 2`, etc. Locale for number parsing. Determines thousand/decimal separator conventions. For example, `'PT_PT'` treats `1.000,50` as `1000.5`. Maximum allowed input size in megabytes. Throws an error if the input exceeds this limit. ## Examples ### Without Headers ```typescript theme={null} const data = fromTSV('Acme\t1000\nGlobex\t2000', { hasHeader: false }); // Columns auto-named: "Column 1", "Column 2" ``` Files with `.tab` extension are also treated as TSV. # URL Source: https://docs.graphy.dev/data-import-utils/url Fetch a remote file by URL and parse it into the `{ columns, rows }` [Data structure](/sdk/core/data-structure) used by Graphy agents and chart configs. The format is auto-detected from the URL path extension. ## fromURL ```typescript theme={null} import { fromURL } from '@graphysdk/data-import-utils/url'; const data = await fromURL('https://example.com/sales.csv'); const data = await fromURL('https://example.com/report.xlsx', { sheet: 'Revenue' }); ``` ### Signature ```typescript theme={null} function fromURL( url: string, options?: URLParseOptions ): Promise ``` ## Options URL to fetch. The path extension determines the format, falling back to the response `Content-Type` header. Custom headers to include in the fetch request. Useful for authenticated endpoints (e.g. `{ Authorization: 'Bearer token' }`). Fetch timeout in milliseconds. The request is aborted if it takes longer. Optional external abort signal for cancellation. Combined with the internal timeout signal via `AbortSignal.any`. Whether the first row contains column headers (CSV/TSV only). When `false`, columns are auto-named `Column 1`, `Column 2`, etc. Sheet to parse (spreadsheets only). Pass a sheet name or 0-based index. Locale for number parsing. Determines thousand/decimal separator conventions. Maximum allowed input size in megabytes. The response body is streamed with a byte budget -- the download is aborted early if the limit is exceeded. Maximum number of data rows to process (spreadsheets only). Maximum total cells to process (spreadsheets only). ## Supported Extensions | Extension | Format | | -------------- | ------ | | `.csv` | CSV | | `.tsv`, `.tab` | TSV | | `.xlsx` | XLSX | | `.xls` | XLS | | `.ods` | ODS | Unsupported extensions throw an error listing the supported formats. ## Examples ### With the AI SDK ```typescript theme={null} import { fromURL } from '@graphysdk/data-import-utils/url'; import { GraphyAiSdk } from '@graphysdk/agents-sdk'; const ai = new GraphyAiSdk({ apiKey: process.env.GRAPHY_API_KEY, baseUrl: 'https://agents.graphy.dev', }); const data = await fromURL('https://data.example.com/quarterly-sales.csv'); const result = await ai.generateGraph({ config: { data }, userPrompt: 'line chart showing quarterly trends', }); ``` ### Error Handling ```typescript theme={null} try { const data = await fromURL('https://example.com/data.pdf'); } catch (error) { // Error: Unsupported file extension ".pdf". Supported: .csv, .tsv, .tab, .xlsx, .xls, .ods } ``` ### With Custom Headers ```typescript theme={null} const data = await fromURL('https://api.example.com/data.csv', { headers: { Authorization: 'Bearer my-token' }, }); ``` ### With Timeout and Cancellation ```typescript theme={null} const controller = new AbortController(); const data = await fromURL('https://example.com/large-file.xlsx', { timeout: 10_000, signal: controller.signal, }); ``` **SSRF Protection:** `fromURL` validates URLs before fetching. Private and reserved IP addresses (e.g. `127.0.0.1`, `10.x.x.x`, `192.168.x.x`, `169.254.x.x`, `localhost`, IPv6 loopback/link-local) are blocked, and only `http:` and `https:` schemes are allowed. This prevents server-side request forgery when URLs are provided by end users. `fromURL` uses the global `fetch` API and works in Node.js 18+ and all modern browsers. For authenticated endpoints, you can either pass `headers` in the options or fetch the data yourself and use the format-specific parsers (`fromCSV`, `fromXLSX`, etc.) directly. # XLS Source: https://docs.graphy.dev/data-import-utils/xls Parse XLS spreadsheet files into the `{ columns, rows }` [Data structure](/sdk/core/data-structure) used by Graphy agents and chart configs. ## fromXLS ```typescript theme={null} import { fromXLS } from '@graphysdk/data-import-utils/xls'; const buffer = await file.arrayBuffer(); const data = await fromXLS(buffer); ``` ### Signature ```typescript theme={null} function fromXLS( input: ArrayBuffer, options?: SpreadsheetParseOptions ): Promise ``` ## Options Sheet to parse. Pass a sheet name (`string`) or a 0-based index (`number`). Defaults to the first sheet. Locale for number parsing. Determines thousand/decimal separator conventions. Maximum allowed input size in megabytes. Throws an error if the input exceeds this limit. Maximum number of data rows to process. Throws an error if the sheet contains more rows. Maximum total cells (rows x columns) to process. Throws an error if the limit is exceeded. ## Examples ### Select Sheet by Name ```typescript theme={null} const data = await fromXLS(buffer, { sheet: 'Revenue' }); ``` ## Cell Value Handling | Cell type | Result | | --------- | --------------- | | Number | `number` | | String | `string` | | Boolean | `1` or `0` | | Date | ISO 8601 string | | Formula | Computed result | | Rich text | Plain text | | Empty | `null` | `fromXLS` uses the same XLSX-based parser under the hood. It works with modern `.xls` files that are actually in XLSX format (common in Excel 2007+). Genuine legacy binary XLS (BIFF) format is not supported. # XLSX Source: https://docs.graphy.dev/data-import-utils/xlsx Parse XLSX spreadsheet files into the `{ columns, rows }` [Data structure](/sdk/core/data-structure) used by Graphy agents and chart configs. ## fromXLSX ```typescript theme={null} import { fromXLSX } from '@graphysdk/data-import-utils/xlsx'; const buffer = await file.arrayBuffer(); const data = await fromXLSX(buffer); ``` ### Signature ```typescript theme={null} function fromXLSX( input: ArrayBuffer, options?: SpreadsheetParseOptions ): Promise ``` ## Options Sheet to parse. Pass a sheet name (`string`) or a 0-based index (`number`). Defaults to the first sheet. Locale for number parsing. Determines thousand/decimal separator conventions. Maximum allowed input size in megabytes. Throws an error if the input exceeds this limit. Maximum number of data rows to process. Throws an error if the sheet contains more rows. Maximum total cells (rows x columns) to process. Throws an error if the limit is exceeded. ## Examples ### Select Sheet by Name ```typescript theme={null} const data = await fromXLSX(buffer, { sheet: 'Revenue' }); ``` ### Select Sheet by Index ```typescript theme={null} const data = await fromXLSX(buffer, { sheet: 2 }); // Third sheet (0-indexed) ``` ## Cell Value Handling | Cell type | Result | | --------- | --------------- | | Number | `number` | | String | `string` | | Boolean | `1` or `0` | | Date | ISO 8601 string | | Formula | Computed result | | Rich text | Plain text | | Empty | `null` | # Graphy Source: https://docs.graphy.dev/index Graphy gives you three ways to build and ship interactive charts. Pick the one that fits your project — or combine them. Generate and edit charts from natural language, via REST or the TypeScript SDK. Render interactive, editable charts in React with `@graphysdk/core`. Turn CSV, TSV, and spreadsheets into chart-ready datasets. ## New to Graphy? Start with the [Charting SDK quickstart](/sdk/core/quickstart) to render your first chart, or jump straight to the [Agents API](/agents/quickstart) to generate and edit charts with AI. Building with the AI agents? You'll need an API key — [create one in the console](/agents/api-keys). # Commands & history Source: https://docs.graphy.dev/sdk-next/advanced/commands-and-history A **command** is a single, undoable edit to a chart's spec — "hide the y-axis", "widen the line", "move the legend to the bottom". Instead of rebuilding the spec and passing a new `input`, you dispatch a command and the provider applies it, recompiles, and records it on an undo stack. ```tsx theme={null} import { SetAxisVisibilityCommand } from '@graphysdk/viz-engine'; import { useGraphCommands } from '@graphysdk/react-renderer'; function AxisToggle({ isVisible }: { isVisible: boolean }) { const { dispatch } = useGraphCommands(); return ( ); } ``` Every edit that reaches the chart goes through this one path — a toolbar button, an inline edit in `editable` mode, or a command streamed in from an agent — so all of them land in the same history and all of them are undoable. ## Dispatching `useGraphCommands` returns `dispatch(command, options?)` and `seal()`. It must be called from inside a ``. A command is a plain object you construct with its params: ```tsx theme={null} dispatch(new SetStyleRuleCommand({ list: 'defaults', rule: style.geom.line({ strokeWidth: 3 }) })); dispatch(new SetScaleDomainCommand({ scaledAesthetic: 'y', domainMin: 0 })); dispatch(new SetLegendPositionCommand({ position: 'bottom' })); ``` Commands that target a layer take an optional `layerId`. Omit it and the command finds the first layer of a compatible geom — enough for a single-series chart, while a combo chart should name the layer explicitly: ```tsx theme={null} dispatch(new SetLineInterpolationCommand({ layerId: 'trend', interpolate: 'catmull-rom' })); ``` A layer's id comes from the spec — set it when you build the layer, and every reference to it (commands, highlights, annotation anchors) can use that name: ```tsx theme={null} pipe(createSpec({ x: 'month' }), geom.bar({ id: 'bars' }), geom.line({ id: 'trend' })); ``` Leave `id` off and one is assigned at compile time, so a layer always has a stable identity — you just don't get to choose the name. Nothing is mutated. A command reads the current spec and returns a new one, so the provider can swap the compiled result in without touching the object you passed as `input`. ### Persisting edits The provider's `onChange` fires with the spec input a command produced. Store that if edits need to survive a reload: ```tsx theme={null} ``` Handing that same value straight back as `input` is a no-op — the provider recognises the echo and does not recompile. ### Live gestures A slider or colour picker that edits while it is held would otherwise leave one undo entry — and one `onChange` — per frame. Pass `{ transient: true }` on those frames and call `seal()` when the gesture ends: ```tsx theme={null} import { SetAppearanceCornerRadiusCommand } from '@graphysdk/viz-engine'; import { useGraphCommands } from '@graphysdk/react-renderer'; function CornerRadiusSlider({ cornerRadius }: { cornerRadius: number }) { const { dispatch, seal } = useGraphCommands(); return ( dispatch( new SetAppearanceCornerRadiusCommand({ cornerRadius: event.target.valueAsNumber, }), { transient: true } ) } onPointerUp={seal} onBlur={seal} /> ); } ``` A transient dispatch recompiles and repaints like any other, but it replaces the run's single undo entry instead of pushing a new one, and keeps the run's *oldest* revert — so one undo returns to before the gesture began, not to its penultimate frame. `onChange` fires once, when `seal()` closes the run. A run covers one thing being edited. A transient dispatch aimed at something else starts its own entry, and so does a committed dispatch, an undo, a redo, or a new `input`, `data` or theme from the host — each of those closes the open run first. Forgetting to `seal()` delays that gesture's `onChange` until one of them arrives; it does not merge two gestures into one step. Dragging something across the canvas is a different shape: move it locally in pixels and dispatch one command on drop. Transient dispatch is for gestures whose feedback genuinely needs the spec to recompile. ## Undo & redo `useGraphHistory` gives you the controls plus the state a history UI reads. It subscribes to the stack, so a component using it re-renders whenever a step becomes available. ```tsx theme={null} import { useGraphHistory } from '@graphysdk/react-renderer'; function UndoRedoToolbar() { const { undo, redo, canUndo, canRedo, undoDescription, redoDescription } = useGraphHistory(); return ( <> ); } ``` Whether a step is available in each direction. Human-readable label for the next step — `"Set line width to 3"` — for a button tooltip or a menu item. `null` when there is nothing to step. The full history, for a history panel. Each entry carries `id`, `timestamp`, `description` and `author`. Both are oldest first, and in both it is the *last* entry that is next: the end of `undoStack` is what an undo reverses, the end of `redoStack` is what a redo re-applies. ### Keyboard shortcuts Shortcuts are opt-in. `useGraphHistoryShortcuts` binds them, driving the graph through an `handleRef` so it can be called from wherever your key handling lives, including above the provider: ```tsx theme={null} import { useRef } from 'react'; import { type GraphHandle, useGraphHistoryShortcuts, } from '@graphysdk/react-renderer'; function EditableChart() { const handleRef = useRef(null); useGraphHistoryShortcuts(handleRef); return ( ); } ``` It binds ⌘/Ctrl+Z, ⌘/Ctrl+Shift+Z and Ctrl+Y, and leaves alone any chord already handled by the app or typed into a text field. Pass `{ target }` — an element, or a ref holding one — to scope it to a subtree instead of `window`, or `{ enabled: false }` to unbind. A chord is only claimed when the chart has a step to take: with an empty history the keystroke is left uncancelled, so an undo your app owns elsewhere on the page still gets it. The same `handleRef` exposes `dispatch`, `seal`, `undo` and `redo` directly, for a toolbar or menu bar mounted outside the provider where the hooks can't reach. `undo` and `redo` return whether the chart took the step — that's the signal to fall through to your own handling. A step the chart declines, either because there is nothing to step or because the older spec no longer compiles, returns `false`. ## When a command doesn't apply Two outcomes leave the chart exactly as it was: * **No-op** — the command found nothing to change: the value already matches, or the layer or scale it targets isn't in the spec. Nothing recompiles and nothing joins the history, so there's no empty step to undo past. * **Rejected** — the edit produced a spec that fails to compile. The last good chart stays on screen and the diagnostics go to the provider's `onError`. An edit the chart never took is not one you can undo, so the history is left untouched. Undo and redo are gated the same way. If restoring an older spec fails to compile — say the data loaded since no longer has a column that spec referenced — the step is refused, neither stack moves, and `undo()` returns `false` so the keystroke falls through to your app. The step stays on the stack and applies once the data supports it again, which is also to say the steps behind it are reachable only through it. ## History lifecycle The history belongs to the chart the provider is currently showing: * A **new `input`** from the host is a new baseline and clears the history, once that input compiles. The stored steps restore values from a spec that no longer exists, and may target layers or scales the new one doesn't have. This is by reference, so an `input` rebuilt on every render leaves nothing undoable — [keep it stable](/sdk-next/rendering/provider-and-renderer#keep-inputs-stable). * An **`input` that fails to compile** keeps it. The chart behind the error panel is still the last good one, so an undo is the way back to something renderable. * **New `data`** or a **theme change** keeps it. The recorded steps still address the spec they were built from. * History depth is capped at 100 steps; the oldest is dropped past that. ## Available commands Each takes its params as the first constructor argument and optional metadata as the second. Layer commands accept an optional `layerId`; scale commands take `scaledAesthetic` to pick the scale by the aesthetic it drives (`'x'`, `'y'`, `'color'`, …). Axis commands take `axis` — `'x'` or `'y'`, and `'ySecondary'` where a second y axis makes sense. **Titles & content** | Command | Params | | -------------------------------- | ------------------- | | `SetContentTitleCommand` | `title` | | `SetContentSubtitleCommand` | `subtitle` | | `SetContentCaptionCommand` | `caption` | | `SetContentSourceCommand` | `source` | | `ToggleContentVisibilityCommand` | `slot`, `isVisible` | **Axes & grid** | Command | Params | | ------------------------------- | ------------------- | | `SetAxisLabelCommand` | `axis`, `label` | | `SetAxisPositionCommand` | `axis`, `position` | | `SetAxisVisibilityCommand` | `axis`, `isVisible` | | `SetAxisTicksVisibilityCommand` | `axis`, `isVisible` | | `SetAxisTickModeCommand` | `axis`, `tickMode` | | `SetGridVisibilityCommand` | `axis`, `isVisible` | **Legend & headline** | Command | Params | | ------------------------------- | ------------- | | `SetLegendPositionCommand` | `position` | | `SetHeadlineShowCommand` | `show` | | `SetHeadlinePositionCommand` | `position` | | `SetHeadlineSizeCommand` | `size` | | `SetHeadlineCompareWithCommand` | `compareWith` | **Appearance & formatting** | Command | Params | | ------------------------------------ | -------------- | | `SetAppearanceBackgroundCommand` | `background` | | `SetAppearanceBorderCommand` | `border` | | `SetAppearanceCornerRadiusCommand` | `cornerRadius` | | `SetAppearanceTextScaleCommand` | `textScale` | | `SetNumberFormatDecimalsCommand` | `decimals` | | `SetNumberFormatAbbreviationCommand` | `abbreviation` | **Coordinates** | Command | Params | | ---------------------------- | -------------------- | | `SetCoordLimitsCommand` | `xLimits`, `yLimits` | | `SetPolarInnerRadiusCommand` | `innerRadius` | | `SetPolarStartAngleCommand` | `startAngle` | **Layers** | Command | Params | | ----------------------------------- | -------------------- | | `AddLayerCommand` | `layer`, `index` | | `RemoveLayerCommand` | `layerId` | | `SetLayerGeomCommand` | `geom` | | `SetLayerPositionCommand` | `position` | | `SetLayerStatCommand` | `stat` | | `SetLayerYScaleTypeCommand` | `yScaleType` | | `SetLineInterpolationCommand` | `interpolate` | | `SetLineMissingValuesCommand` | `missingValues` | | `ToggleLinePointsVisibilityCommand` | `showPoints` | | `SetRuleValueCommand` | `value` | | `SetRuleLabelCommand` | `label` | | `ToggleDataLabelsCommand` | `showDataLabels` | | `SetDataLabelsFormatCommand` | `format` | | `ToggleCategoryLabelsCommand` | `showCategoryLabels` | | `ToggleStackTotalsCommand` | `showStackTotals` | `AddLayerCommand` and `RemoveLayerCommand` are the two directions of every "show a trend line" or "add a series" toggle, so `AddLayerCommand` carries a whole layer rather than a per-feature flag. **Styles** | Command | Params | | --------------------- | ----------------------------- | | `SetStyleRuleCommand` | `list`, `rule`, `id`, `index` | `SetStyleRuleCommand` is the one command for geom paint (widths, dashes, opacities, colors beyond the palette): it inserts, replaces, or removes a stylesheet entry by id. `rule: null` removes. **Scales** | Command | Params | | -------------------------- | ------------------------------------------- | | `SetScaleDomainCommand` | `scaledAesthetic`, `domainMin`, `domainMax` | | `SetScalePaletteCommand` | `scaledAesthetic`, `palette` | | `SetScaleTransformCommand` | `scaledAesthetic`, `transform` | | `SetScaleReverseCommand` | `scaledAesthetic`, `reverse` | | `SetScaleZeroCommand` | `scaledAesthetic`, `zero` | **Highlights** | Command | Params | | ------------------------ | ---------------------------------------------- | | `AddHighlightCommand` | `predicate`, `scope`, `id`, `layerId`, `index` | | `RemoveHighlightCommand` | `id` | **Annotations** | Command | Params | | ------------------------- | ----------------------------- | | `AddAnnotationCommand` | `kind`, `annotation`, `index` | | `RemoveAnnotationCommand` | `id` | | `UpdateAnnotationCommand` | `kind`, `id`, `patch` | Annotations are addressed by an `id` unique across every kind, so removing and updating take the id alone. `UpdateAnnotationCommand` patches whichever fields you name — moving one and restyling it are the same command, so a drag and a colour picker share a path. ## Related * [Provider & renderer](/sdk-next/rendering/provider-and-renderer) — `onChange`, `handleRef`, `mode="editable"` * [Serializable spec](/sdk-next/concepts/serializable-spec) — the spec commands edit, and how to persist it # Statistics Source: https://docs.graphy.dev/sdk-next/advanced/statistics A **stat** summarises a layer's data before it's drawn. Instead of plotting raw rows, the layer plots a computed result — a count, a mean, or a fitted regression line. Stats are set per layer through a geom's `stat` option, and default to `identity` (draw the data as-is). ```tsx theme={null} import { stat } from '@graphysdk/viz-engine'; geom.line({ stat: stat.smooth({ method: 'linear' }) }); ``` ## Available stats | Stat | Result | | ----------------------------- | -------------------------------------------- | | `stat.identity()` *(default)* | The data unchanged | | `stat.count()` | The number of observations per group | | `stat.mean()` | The arithmetic mean per group | | `stat.smooth({ method })` | A fitted regression curve through the points | ## Trendlines with `smooth` The `smooth` stat fits a regression through a layer's points — the basis for trendlines. Add it to a second `geom.line()` layer over your raw data: ```tsx theme={null} pipe( createSpec({ x: 'date', y: 'sales' }), geom.point(), // the raw points geom.line({ stat: stat.smooth({ method: 'linear' }) }), // the trend scale.x(), scale.y() ); ``` `method` selects the regression family: The regression method to fit. Polynomial order — only used when `method: 'polynomial'`. Smoothing bandwidth — only used when `method: 'loess'`. ```tsx theme={null} stat.smooth({ method: 'polynomial', order: 4 }); stat.smooth({ method: 'loess', bandwidth: 0.5 }); ``` ## A mean reference line Pair `stat.mean()` with `geom.rule()` to draw a horizontal line at a series' average: ```tsx theme={null} pipe( createSpec({ x: 'month', y: 'revenue' }), geom.bar(), geom.rule({ stat: stat.mean() }), // a line at the mean revenue scale.x(), scale.y() ); ``` ## Next * [Transforms](/sdk-next/advanced/transforms) — reshape data before it reaches a stat * [Geoms & layers](/sdk-next/concepts/geoms) — where the `stat` option lives # Transforms Source: https://docs.graphy.dev/sdk-next/advanced/transforms A **transform** reshapes the data before it's charted — pivoting columns into rows, filtering, sorting, aggregating, or adding a constant column. Transforms run before [mappings](/sdk-next/concepts/mappings) are read, so a mapping can reference columns a transform produced. ```tsx theme={null} import { transform } from '@graphysdk/viz-engine'; pipe( createSpec(), transform.reshape({ reshape: ['revenue', 'profit'], keyName: 'metric', valueName: 'amount', }), mapping({ x: 'month', y: 'amount', color: 'metric' }), geom.line(), scale.x(), scale.y(), scale.color.palette() ); ``` ## Reshape: wide to long The most common transform. Many datasets are **wide** — one column per series: ```tsx theme={null} { month: 'Jan', revenue: 12000, profit: 3000 } ``` But a mapping wants a **long** shape — one row per series, with a category column to split on: ```tsx theme={null} { month: 'Jan', metric: 'revenue', amount: 12000 } { month: 'Jan', metric: 'profit', amount: 3000 } ``` `transform.reshape` pivots wide to long so you can map the new category column to `color`: The numeric columns to collapse into rows. Defaults to all numeric columns. Columns to carry through unchanged. Defaults to all categorical/temporal columns. Name of the output column holding the original column names. Name of the output column holding the values. ## Other transforms | Transform | Purpose | | ----------------------------------------------------- | --------------------------------------------------- | | `transform.filter({ variableName, operator, value })` | Keep only rows matching a comparison | | `transform.sort({ variableName, direction })` | Order rows by a column | | `transform.aggregate({ groupby, operations })` | Group and summarise (sum, mean, …) into new columns | | `transform.constant({ variableName, type, value })` | Add a column with the same value on every row | ```tsx theme={null} transform.filter({ variableName: 'revenue', operator: 'gt', value: 0 }); transform.sort({ variableName: 'revenue', direction: 'desc' }); transform.aggregate({ groupby: ['region'], operations: [{ op: 'sum', variableName: 'revenue', as: 'total' }], }); ``` ## Spec-level vs layer-level Piping a transform into the spec applies it to **all** layers. To reshape the data for a single geom — for example, a line overlay that needs a different shape than the bars beneath it — pass `transforms` on that geom: ```tsx theme={null} geom.line({ transforms: [ transform.aggregate({ groupby: ['month'], operations: [{ op: 'sum', variableName: 'revenue', as: 'total' }], }), ], aes: { y: 'total' }, }); ``` ## Next * [Statistics](/sdk-next/advanced/statistics) — per-layer summaries that run after transforms * [Mappings & aesthetics](/sdk-next/concepts/mappings) — reference the columns a transform produces # Coordinate systems Source: https://docs.graphy.dev/sdk-next/concepts/coordinate-systems A **coordinate system** decides the plane a geom is drawn in. The same [geom](/sdk-next/concepts/geoms) and [scales](/sdk-next/concepts/scales) produce very different charts depending on the coord: a stacked bar is a stacked column in cartesian coordinates, a set of horizontal bars when the axes are flipped, and a pie when the plane is bent into a circle. Set the coordinate system by piping a `coord.*()` part into the spec. When you don't, the chart is cartesian. ## Cartesian The standard x–y plane, and the default. ```tsx theme={null} pipe( createSpec({ x: 'month', y: 'revenue' }), geom.bar(), scale.x(), scale.y(), coord.cartesian() // optional — this is the default ); ``` ## Flip `coord.flip()` swaps the x and y axes. Its main use is turning vertical columns into horizontal bars — you keep the natural mapping (category on `x`, value on `y`) and the flip lays it on its side. ```tsx theme={null} pipe( createSpec({ x: 'country', y: 'population' }), geom.bar(), scale.x(), scale.y(), coord.flip() // horizontal bars ); ``` ## Polar `coord.polar()` bends the plane into a circle — this is how pie, donut, rose and radial charts are made. It takes: Which aesthetic maps to the angle. `theta: 'y'` sweeps the *value* around the circle (pie/donut); `theta: 'x'` sweeps the *category* around it, with the value as radius (rose / coxcomb). Inner radius as a fraction `0–1`. `0` is a full pie; a value like `0.5` cuts out the centre for a donut. Starting angle in degrees. A pie chart is a single stacked bar wrapped around `theta: 'y'`: ```tsx theme={null} pipe( createSpec({ x: '', y: 'revenue', color: 'region' }), geom.bar({ position: 'stack' }), coord.polar({ theta: 'y' }), scale.x(), scale.y(), scale.color.palette() ); ``` Give it an `innerRadius` and it becomes a donut. Map `theta: 'x'` instead and the category sweeps the angle with the value as radius — the basis for radar, rose and radial-bar charts. See [Pie & donut](/sdk-next/graph-types/pie) and [Radar & radial](/sdk-next/graph-types/radial) for worked examples. ## Axis limits Every coord accepts optional `xLimits` / `yLimits` as `[min, max]` to clip the drawing region. For most charts you'll control the visible range through [scale](/sdk-next/concepts/scales) `domainMin` / `domainMax` instead — limits operate on the coordinate space rather than the data domain. ## Next * [Chart types](/sdk-next/graph-types/index) — coords combined with geoms and positions * [Pie & donut](/sdk-next/graph-types/pie) — the `theta: 'y'` polar recipes in full * [Radar & radial](/sdk-next/graph-types/radial) — the `theta: 'x'` polar family # Geoms & layers Source: https://docs.graphy.dev/sdk-next/concepts/geoms A **geom** is what a chart draws for its data — a line, a bar, a point. Each geom is a **layer** in the spec; add several and they stack into one chart. Geoms are the visible half of the grammar: [mappings](/sdk-next/concepts/mappings) and [scales](/sdk-next/concepts/scales) decide *where* things go, geoms decide *what* is drawn there. ## The built-in geoms | Geom | Draws | Reads | | -------------- | ---------------------------------------------- | -------------------------------------------- | | `geom.point()` | A point per observation | `x`, `y`, `color`, `size`, `alpha` | | `geom.line()` | A path connecting observations | `x`, `y`, `color`, `strokeWidth`, `lineType` | | `geom.area()` | A filled band under a line | `x`, `y`, `color` | | `geom.bar()` | A rectangle (or arc, in polar) per observation | `x`, `y`, `color` | | `geom.rule()` | A single reference line across the panel | `x` or `y` | Each is a factory that returns a layer. Call it with options to configure the layer, and geom-specific `params` for its geometry and behavior knobs — paint (colors, widths, opacities) lives in the stylesheet instead: ```tsx theme={null} geom.line({ params: { interpolate: 'catmull-rom', missingValues: 'connect' } }); geom.bar({ position: 'stack', params: { width: 0.8 } }); ``` The `params` available depend on the geom — see each [chart type](/sdk-next/graph-types/index) page, or the reference for the full list. ## Layers Every `geom.*()` you pipe in adds a layer, drawn in order — later layers sit on top. Layers share the spec-level [mapping](/sdk-next/concepts/mappings) unless one overrides it. This is how decorated and combo charts are built: ```tsx theme={null} pipe( createSpec({ x: 'month', y: 'revenue' }), geom.line(), // the trend geom.point(), // a dot on every vertex, on top scale.x(), scale.y() ); ``` ## Common layer options Beyond `params`, every geom accepts these options: | Option | Purpose | | ------------- | -------------------------------------------------------------------------------------------------- | | `aes` | A [layer-local mapping](/sdk-next/concepts/mappings#where-a-mapping-lives), merged over the spec's | | `position` | How overlapping observations arrange — see below | | `stat` | A [statistical transform](/sdk-next/advanced/statistics) for this layer (count, mean, smooth) | | `yScaleType` | Bind the layer to the `'primary'` or `'secondary'` y-axis | | `dataLabels` | Show [value labels](/sdk-next/config/data-labels) on each observation | | `transforms` | [Transforms](/sdk-next/advanced/transforms) applied to this layer's view of the data | | `interactive` | Set `false` to exclude the layer from hover hit-testing | ## Position modes When several observations share the same x (because `color` or `group` splits the data), a layer's `position` decides how they arrange: | Position | Effect | | ------------------------ | ----------------------------------------------- | | `'identity'` *(default)* | Draw at the raw value; observations may overlap | | `'stack'` | Stack observations end to end | | `'fill'` | Stack, then normalise each stack to 100% | | `'dodge'` | Place observations side by side within the band | ```tsx theme={null} geom.bar({ position: 'stack' }); // stacked columns geom.bar({ position: 'dodge' }); // grouped columns geom.area({ position: 'stack' }); // stacked area ``` ## Next * [Scales](/sdk-next/concepts/scales) — map values to positions, colors and sizes * [Statistics](/sdk-next/advanced/statistics) — summarise a layer before drawing it * [Chart types](/sdk-next/graph-types/index) — geoms assembled into recipes # How a chart is built Source: https://docs.graphy.dev/sdk-next/concepts/how-a-chart-is-built Every chart in the Graphy SDK is a **spec** — a plain, immutable object you build by folding parts together. ## The builder pipeline You start a spec with `createSpec` and fold parts onto it with `pipe`. Each part is a small tagged object produced by a builder (`geom.*`, `scale.*`, `coord.*`, `config`, …): ```tsx theme={null} import { createSpec, pipe, geom, scale } from '@graphysdk/viz-engine'; const spec = pipe( createSpec({ x: 'month', y: 'revenue' }), geom.line(), scale.x(), scale.y() ); ``` `pipe(spec, ...parts)` returns a **new** spec with each part folded in, left to right — it never mutates. Parts accumulate by kind: every `geom.*` adds a layer, every `scale.*` adds a scale, `config` deep-merges, `coord` and `mapping` merge. Because a spec is a plain object, you can build it up conditionally, share fragments across charts, and [store or serialize it](/sdk-next/concepts/serializable-spec). ```tsx theme={null} // Build in stages — each call returns a new spec. let spec = createSpec({ x: 'month', y: 'revenue' }); spec = pipe(spec, geom.line(), scale.x(), scale.y()); if (showAverage) { spec = pipe(spec, geom.rule({ stat: stat.mean() })); } ``` ## The parts of a spec A spec brings together six kinds of part. Each has its own concept page: | Part | What it decides | Builder | | ----------------------------------------------------------- | -------------------------------------------------- | ----------------------- | | [Mapping](/sdk-next/concepts/mappings) | Which data columns feed which aesthetics | `mapping`, `createSpec` | | [Geoms](/sdk-next/concepts/geoms) | The shapes drawn — line, bar, point, area, rule | `geom.*` | | [Scales](/sdk-next/concepts/scales) | How data values become positions, colors and sizes | `scale.*` | | [Coordinate systems](/sdk-next/concepts/coordinate-systems) | The plane observations are drawn in | `coord.*` | | [Statistics](/sdk-next/advanced/statistics) | Per-layer summaries (count, mean, smoothing) | `stat.*` | | [Transforms](/sdk-next/advanced/transforms) | Reshaping the data before it's drawn | `transform.*` | Plus [configuration](/sdk-next/config/index) (`config`) for chart chrome — titles, axes, legend, appearance. ## Compile, then render A spec is a *description*; it isn't yet pixels. Turning it into a chart happens in two stages, one per package: ```mermaid theme={null} flowchart LR S[Spec] --> C[viz-engine: compile] D[Data] --> C C --> R[CompiledSpec] R --> P[react-renderer: paint] P --> SVG[SVG chart] ``` 1. **Compile (`@graphysdk/viz-engine`).** The engine takes your spec plus your data and produces a `CompiledSpec`: scales are resolved to concrete domains, statistics are run, every observation is placed in a normalized `[0,1]` position space, and guides (axes, legend) are worked out. This stage is pure data-in, data-out — no DOM. 2. **Render (`@graphysdk/react-renderer`).** The renderer takes the `CompiledSpec` and paints it: it lays out pixel rectangles, formats numbers and dates for the locale, applies the theme, and wires up hover and animation. In a React app you don't call the compiler yourself — `` does it for you and recompiles when the spec, data or theme change: ```tsx theme={null} import { GraphProvider, GraphRenderer } from '@graphysdk/react-renderer'; ; ``` ## Why the split matters The split between the two packages shapes how you use the SDK: * **Data lives outside the spec.** A spec references columns by name; the actual rows are passed to `` separately. One spec can draw many datasets. * **The compiler never formats.** It emits raw values and *descriptors*; the renderer turns them into locale-aware text. That's why formatting options live on the renderer side. * **Scales are resolved once, at compile time.** This is why you declare scales explicitly in the spec — the engine can't paint a position it was never told how to compute. Authoring concerns (mapping, geoms, scales, stats) live in viz-engine; presentation concerns (theme, formatting, interactivity, layout) live in react-renderer. ## Next * [Scales](/sdk-next/concepts/scales) — the one part you must always declare * [Serializable spec](/sdk-next/concepts/serializable-spec) — a spec is plain JSON you can persist and reload # Mappings & aesthetics Source: https://docs.graphy.dev/sdk-next/concepts/mappings A **mapping** binds your data columns to **aesthetics** — the visual properties a chart draws with, like horizontal position, vertical position, and color. It's the bridge between a row of data and what's drawn on screen. ```tsx theme={null} createSpec({ x: 'month', y: 'revenue', color: 'product' }); ``` This reads: put `month` on the x-axis, `revenue` on the y-axis, and give each `product` its own color. The strings are column [keys](/sdk-next/data-structure). ## Built-in aesthetics These are the built-in aesthetics you can map: | Aesthetic | Encodes | Typical column | | ------------- | ------------------------------------------------------------- | ------------------------------------ | | `x` | Horizontal position | Category, date, or number | | `y` | Vertical position | Number | | `color` | Fill / stroke color | Category (or number, for a gradient) | | `size` | Size of each observation | Number | | `alpha` | Opacity (0–1) | Number | | `strokeWidth` | Line/border thickness | Number | | `lineType` | Dash pattern (solid, dashed, dotted) | Category | | `group` | Splits observations into groups **without** a visual encoding | Category | Not every geom reads every aesthetic — a `size` mapping means something to `geom.point()` but nothing to `geom.line()`. Each [geom](/sdk-next/concepts/geoms) documents the aesthetics it uses. ### `group` vs `color` Both `color` and `group` split data into series. The difference: `color` also assigns a visual (a hue per category, with a legend); `group` only separates the observations. Use `group` when you want, say, one line per category but a single color for all of them. ## Variables and constants An aesthetic can be bound to a **variable** (a column, read per row) or a **constant** (one literal value applied to every observation): ```tsx theme={null} createSpec({ y: 'revenue', // variable — shorthand for { variable: 'revenue' } color: { value: 'red' }, // constant — every observation is red }); ``` * `'revenue'` — string shorthand for a variable mapping. * `{ variable: 'revenue' }` — the explicit variable form. * `{ value: 'red' }` — a constant. The value still flows through the aesthetic's [scale](/sdk-next/concepts/scales), so `{ value: 'Pro' }` on `color` resolves to whatever color the scale assigns the `Pro` category. ## Where a mapping lives A mapping can sit at two levels: **Spec-level** — shared by every layer. Set it with `createSpec({ ... })`: ```tsx theme={null} pipe( createSpec({ x: 'month', y: 'revenue' }), geom.line(), geom.point(), // both layers inherit x and y scale.x(), scale.y() ); ``` **Layer-level** — merged over the spec-level mapping for one geom only, via a geom's `aes` option. This is how combos give each layer a different `y`: ```tsx theme={null} pipe( createSpec({ x: 'month' }), // shared x geom.bar({ aes: { y: 'revenue' } }), // this layer's y geom.line({ aes: { y: 'profit' } }), // a different y scale.x(), scale.y(), scale.ySecondary() ); ``` Layer mappings win where they overlap; anything they don't set falls through to the spec-level mapping. ## Every mapped position needs a scale Mapping a column to `x` or `y` is only half the story — you must also declare the [scale](/sdk-next/concepts/scales) that turns those values into positions. `scale.x()` and `scale.y()` are never created automatically. A mapped position aesthetic with no matching `scale.*()` produces `NaN` positions. If a chart renders blank, a missing scale is the first thing to check. ## Next * [Scales](/sdk-next/concepts/scales) — turn mapped values into positions and colors * [Geoms & layers](/sdk-next/concepts/geoms) — which aesthetics each geom reads # Scales Source: https://docs.graphy.dev/sdk-next/concepts/scales A **scale** turns data values into visual values — a number into a pixel position, a category into a color, a magnitude into a point size. Every [aesthetic](/sdk-next/concepts/mappings) you map needs a scale to interpret it. ```tsx theme={null} scale.x(); // infer an x-axis scale from the data scale.y.continuous({ domainMin: 0 }); // an explicit continuous y-axis, pinned to zero scale.color.discrete({ range: ['#4C6EF5', '#F76707'] }); ``` ## Position scales must be declared **Position scales are never created automatically.** If you map `x` or `y`, you must add `scale.x()` / `scale.y()`. Miss one and that axis has no scale, producing `NaN` positions and a blank chart. ```tsx theme={null} pipe( createSpec({ x: 'month', y: 'revenue' }), geom.line(), scale.x(), // required scale.y() // required ); ``` Visual scales (`color`, `size`, …) are more forgiving — a mapped `color` without an explicit scale falls back to the default palette — but declaring them keeps a chart predictable. ## Inference vs explicit types Position scales are **callable** to infer their type from the mapped column's [value format](/sdk-next/data-structure#value-format-detection): * text → **discrete** (band) scale * number / percentage / currency → **continuous** scale * date → **temporal** scale ```tsx theme={null} scale.x(); // inferred scale.x({ nice: true }); // inferred, with options ``` Or name the type explicitly when you want to force a treatment: ```tsx theme={null} scale.x.continuous(); scale.x.discrete(); scale.x.datetime(); scale.x.log(); scale.x.sqrt(); ``` ## Scales by aesthetic | Aesthetic | Methods | | ------------------------------ | --------------------------------------------------------------------------------------- | | `x`, `y`, `ySecondary` | callable (inferred), `.continuous()`, `.discrete()`, `.datetime()`, `.log()`, `.sqrt()` | | `color` | `.continuous()`, `.discrete()`, `.palette()` | | `size`, `alpha`, `strokeWidth` | `.continuous()`, `.discrete()`, `.identity()` | | `lineType` | `.discrete()`, `.identity()` | ### Color scales ```tsx theme={null} scale.color.palette(); // Graphy's default palette scale.color.discrete({ domain: ['A', 'B'], range: ['#4C6EF5', '#F76707'] }); // fixed colors scale.color.continuous(); // a numeric gradient ``` ### Identity scales An identity scale passes data values straight through as visual values — `{ size: 10 }` becomes 10px, no transformation. Useful when your data already holds pixel sizes or CSS colors. ```tsx theme={null} scale.size.identity(); ``` ## Continuous scale options The continuous methods (and `.log()` / `.sqrt()`, which are continuous with a transform) accept: Force the lower bound of the domain. `domainMin: 0` makes an axis start at zero. Force the upper bound of the domain. Extend the domain to round values — `[3, 97]` becomes `[0, 100]`. Include zero in the domain. Reverse the direction of the scale. A mathematical transform applied to the scale. `scale.y.log()` is shorthand for `transform: 'log'`. ## Discrete scale options The categories, in the order they should appear. Omit to derive them from the data. Explicit output values — colors, sizes, or dash patterns — aligned to `domain`. ## Next * [Coordinate systems](/sdk-next/concepts/coordinate-systems) — the plane scales place values into * [Chart types](/sdk-next/graph-types/index) — scales at work in each recipe # Serializable spec Source: https://docs.graphy.dev/sdk-next/concepts/serializable-spec The builders you've met so far — `createSpec`, `pipe`, `geom.*`, `scale.*`, `config` — are convenience, not substance. Everything they produce is a plain, immutable object made only of objects, arrays, strings, numbers and booleans. A spec holds no functions, no class instances, no DOM references. That means it round-trips through `JSON.stringify` / `JSON.parse` unchanged: you can store a spec in a database, send it over the wire, generate it from another language, or write one by hand. ## The builder is just sugar `pipe(...)` and the `geom.*` / `scale.*` / `config` helpers just assemble and merge a plain object. This call: ```tsx theme={null} import { createSpec, pipe, geom, scale, config } from '@graphysdk/viz-engine'; const spec = pipe( createSpec({ x: 'month', y: 'revenue', color: 'product' }), geom.line(), scale.x(), scale.y.continuous({ domainMin: 0 }), scale.color.palette(), config({ legend: { position: 'bottom' } }) ); ``` produces exactly this object — and `JSON.stringify(spec)` gives you exactly this JSON (unset fields drop out): ```json theme={null} { "mapping": { "x": "month", "y": "revenue", "color": "product" }, "layers": [{ "type": "layer", "geom": "line" }], "scales": [ { "type": "scale", "scaledAesthetic": "x", "scaleType": "inferred" }, { "type": "scale", "scaledAesthetic": "y", "scaleType": "continuous", "domainMin": 0, "domainMax": null }, { "type": "scale", "scaledAesthetic": "color", "scaleType": "palette" } ], "transforms": [], "highlights": [], "config": { "legend": { "position": "bottom" } } } ``` You could paste that JSON into your source and feed it straight to the renderer with no builder in sight. What the builders add is **types, defaults and guardrails** — autocomplete for aesthetics and params, sensible fallbacks, and a compile-time check that the shape is valid. ## Next * [How a chart is built](/sdk-next/concepts/how-a-chart-is-built) — the compile → render pipeline that consumes a spec * [Data structure](/sdk-next/data-structure) — the table a spec references by column name * [Extending](/sdk-next/extending/index) — custom geoms, stats and transforms, and the `plugins` they ship in # Appearance Source: https://docs.graphy.dev/sdk-next/config/appearance The `appearance` section styles the chart frame — its background fill, its border ring, corner rounding and global text scale. ```tsx theme={null} config({ appearance: { background: { type: 'solid', color: '#ffffff' }, cornerRadius: 12, }, }); ``` ## Background Chart background fill: * `{ type: 'theme' }` — inherit the active theme's background token. * `{ type: 'solid', color }` — an explicit CSS color. Use `'transparent'` for no fill. * `{ type: 'tinted', color? }` — the theme background mixed with an anchor color; defaults to the first palette color. ```tsx theme={null} config({ appearance: { background: { type: 'solid', color: '#0B1F3A' } } }); ``` ## Border ring A border ring is painted **inside** the chart bounds — increasing its width shrinks the plot area. This is distinct from the [panel border](/sdk-next/config/axes#panel-border-and-baseline), which frames the plot area itself. * `{ type: 'none' }` — no ring. * `{ type: 'solid', color, width }` — a solid ring; pass a theme token for a theme-aware color. * `{ type: 'tinted', color?, width }` — a ring tinted for the active color scheme. * `{ type: 'gradient', color?, width }` — a gradient derived from `color`. * `{ type: 'preset', preset, width }` — a named built-in gradient. ```tsx theme={null} config({ appearance: { border: { type: 'solid', color: 'var(--graphy-grey-70)', width: 1 }, }, }); ``` ## Corner radius Corner rounding in pixels for the chart frame and its content. Use `0` for square corners. ## Text scale Multiplier applied to every text element. `1.25` makes all text 25% larger; layout adjusts to match. ## Related * [Axes › Panel border](/sdk-next/config/axes#panel-border-and-baseline) — the plot-area frame * [Layout](/sdk-next/config/layout) — padding and spacing around regions # Axes Source: https://docs.graphy.dev/sdk-next/config/axes The `axes` section controls how each position [scale](/sdk-next/concepts/scales) is drawn — its label, side, grid lines and ticks. It's keyed by axis: `x`, `y`, and `ySecondary` for [combo charts](/sdk-next/graph-types/combo). ```tsx theme={null} config({ axes: { x: { label: 'Month' }, y: { label: 'Revenue (£)', position: 'left' }, }, }); ``` ## Per-axis options Axis title text. `null` means no label. Whether the axis is drawn at all. Which side the axis sits on. Defaults to `'bottom'` for `x` and `'right'` for `y`. Grid lines for this axis — see below. Ticks for this axis — see below. ## Grid lines Each axis owns its grid. Toggle visibility and style the lines: ```tsx theme={null} config({ axes: { y: { grid: { isVisible: true, lineStyle: 'solid', lineWidth: 1 } }, x: { grid: { isVisible: false } }, }, }); ``` `true`/`false` to force visibility; `null` lets the engine decide from the geom (e.g. bar charts hide the category-axis grid by default). Dash pattern of the grid lines. Grid line width in px. `null` inherits the theme's grid line width. ## Ticks Whether tick labels are shown for this axis. `'auto'` places ticks across the axis; `'edges'` shows only the first and last. The axis controls labels, ticks and grid; the *range* of an axis comes from its [scale](/sdk-next/concepts/scales) — use `scale.y.continuous({ domainMin: 0, nice: true })` to pin bounds. ## Panel border and baseline The **panel** is the plot area. Its border is configured per edge, which is how you draw a single baseline under the bars or a full frame around the plot: ```tsx theme={null} config({ panel: { border: { bottom: { isVisible: true, lineStyle: 'solid', lineWidth: 1 }, top: { isVisible: false }, left: { isVisible: false }, right: { isVisible: false }, }, }, }); ``` Each edge (`top`, `right`, `bottom`, `left`) takes `isVisible`, `lineStyle`, `lineWidth` (px, `null` inherits the theme), and `color` (any CSS color or theme token, `null` inherits). A corner is rounded only when both edges meeting at it are visible. ## Related * [Scales](/sdk-next/concepts/scales) — the axis range and type * [Appearance](/sdk-next/config/appearance) — the border ring around the whole chart # Content Source: https://docs.graphy.dev/sdk-next/config/content The `content` section sets the text around the chart — title, subtitle, caption, your data-source attribution, and an optional **Made with Graphy** provenance badge. Each text slot has a companion visibility flag, so a value can be kept while hidden. The provenance badge is **off by default** at the low-level renderer (`@graphysdk/react` seeds it on). ```tsx theme={null} config({ content: { title: 'Monthly revenue', isTitleVisible: true, subtitle: 'By product, 2026', isSubtitleVisible: true, source: { label: 'Internal data', url: 'https://example.com' }, isSourceVisible: true, // Opt in to the Graphy provenance badge (off by default). brandMark: { enabled: true }, }, }); ``` ## Slots The chart's headline text. Pair with `isTitleVisible`. A secondary line under the title. Pair with `isSubtitleVisible`. A note shown beneath the chart. Pair with `isCaptionVisible`. Your data-source attribution shown under the caption (for example "Internal data"). Distinct from the Graphy provenance badge. Pair with `isSourceVisible`. Each text slot has a matching `isXVisible` boolean (`isTitleVisible`, `isSubtitleVisible`, `isCaptionVisible`, `isSourceVisible`). Setting text alone does not show it — the visibility flag is what renders it. ## Provenance badge ("Made with Graphy") A translucent capsule badge with the Graphy glyph and the text “Made with Graphy”. It does **not** inherit theme typography. Linked to `graphy.app` with UTM tags. Size ladder: full pill → circular mini under 200 px wide → hidden under 120 × 80. Structured badge config. Defaults: `{ enabled: false, placement: 'footer', variant: 'full' }`. Prefer this over the legacy `isBrandMarkVisible` flag. Legacy alias for `brandMark.enabled`. Kept in sync by config resolution. ```tsx theme={null} // Opt in (footer-right, full pill) config({ content: { brandMark: { enabled: true }, }, }); // Header top-right, circular mini config({ content: { brandMark: { enabled: true, placement: 'header', variant: 'mini' }, }, }); // With your own source — source stays left; badge anchors right config({ content: { source: { label: 'Internal data', url: 'https://example.com' }, isSourceVisible: true, brandMark: { enabled: true }, }, }); ``` `source` is **your** data attribution. `brandMark` controls Graphy's own provenance badge. They can appear together, either alone, or neither. ## Rich text Any text slot accepts either a plain string or a **rich-text** node (a TipTap-compatible document), which lets you mix colors, weights and headings within a title: ```tsx theme={null} config({ content: { isTitleVisible: true, title: { type: 'doc', content: [ { type: 'paragraph', content: [ { type: 'text', text: 'Revenue climbed ' }, { type: 'text', text: '24%', marks: [{ type: 'textStyle', attrs: { color: '#D4594C' } }], }, { type: 'text', text: ' this quarter.' }, ], }, ], }, }, }); ``` Recognised marks include `textStyle` (with `color`, `font`, `fontSize`), plus standard bold/italic/underline. Rich text requires the [TipTap peer packages](/sdk-next/quickstart#1-install-the-packages). ## Related * [Configuration overview](/sdk-next/config/index) — how `config` composes * [Headline numbers](/sdk-next/config/headline-numbers) — summary metrics in the header * [Slots](/sdk-next/extending/slots) — replacing the footer / header regions # Data labels Source: https://docs.graphy.dev/sdk-next/config/data-labels Data labels print each observation's value directly on the observation. Unlike the other topics in this section, they're **not** a `config` field — they're a per-layer option on a [geom](/sdk-next/concepts/geoms), because a label attaches to a specific observation. ```tsx theme={null} geom.bar({ dataLabels: { showDataLabels: true }, }); ``` ## Options Turn labels on for this layer. Show the raw value, or its share of the total. Where the label sits relative to the observation. `'auto'` lets the engine fit, flip or drop labels as space allows; `'inside'` and `'outside'` render exactly as asked. Anchor along the observation's value axis. `'end'` is the value tip whatever the orientation. Only consulted when `position` is explicit. Anchor across the observation's other axis. Only consulted when `position` is explicit. Gap in pixels between the observation's edge and the label. Defaults to 4 for bars and wedges, 12 for points and lines. On stacked bars, print the stack total at the end of each stack. On polar bars (pie/donut) prepend the category to the value ("North · 35%"). On cartesian bars, emit a second category label per bar. ## Examples Percentage labels on a pie's wedges, with the category name: ```tsx theme={null} geom.bar({ position: 'stack', dataLabels: { showDataLabels: true, format: 'percentage', showCategoryLabels: true, }, }); ``` Totals at the top of each stacked column: ```tsx theme={null} geom.bar({ position: 'stack', dataLabels: { showDataLabels: true, showStackTotals: true }, }); ``` ## Related * [Geoms & layers](/sdk-next/concepts/geoms) — where layer options live * [Number format](/sdk-next/config/number-format) — how label values are formatted # Headline numbers Source: https://docs.graphy.dev/sdk-next/config/headline-numbers Headline numbers show a summary metric — a total, an average, or the latest value — alongside the chart, optionally with a trend indicator. They're set through the `headline` section. ```tsx theme={null} config({ headline: { show: 'total', compareWith: 'previous' }, }); ``` ## Options Which aggregate to display: * `'total'` — the sum of the series' values. * `'average'` — the arithmetic mean. * `'current'` — the last value (useful for time series). * `'none'` — no headline. Reference point for the trend indicator: * `'previous'` — compare to the preceding data point. * `'first'` — compare to the first value in the series. * `'none'` — no comparison shown. Visual size. `'auto'` scales with the available space and number of series. Where the headline sits. `'above'` places it in the header region; `'center'` places it in the hole of a [donut chart](/sdk-next/graph-types/pie) — only valid when the polar coord has an inner radius. ## In a donut's centre Pair `position: 'center'` with a donut to put the total in the ring's hole: ```tsx theme={null} pipe( createSpec({ x: '', y: 'revenue', color: 'region' }), geom.bar({ position: 'stack' }), coord.polar({ theta: 'y', innerRadius: 0.6 }), scale.x(), scale.y(), scale.color.palette(), config({ headline: { show: 'total', position: 'center' } }) ); ``` ## Related * [Pie & donut](/sdk-next/graph-types/pie) — the donut's hole is the natural home for a centred headline * [Number format](/sdk-next/config/number-format) — how the headline value is formatted # Overview Source: https://docs.graphy.dev/sdk-next/config/index Everything beyond the data-to-ink mapping — titles, axes, legend, appearance, layout — is **configuration**. You add it by piping a `config(...)` part into the spec. ```tsx theme={null} import { createSpec, pipe, geom, scale, config } from '@graphysdk/viz-engine'; const spec = pipe( createSpec({ x: 'month', y: 'revenue' }), geom.line(), scale.x(), scale.y(), config({ content: { title: 'Monthly revenue', isTitleVisible: true }, axes: { y: { label: 'Revenue (£)' } }, legend: { position: 'bottom' }, }) ); ``` ## How config composes `config` is just another spec part, so it follows the same [pipe rules](/sdk-next/concepts/how-a-chart-is-built#the-builder-pipeline) as the rest — with one twist: **config deep-merges**. Piping several `config(...)` parts combines them rather than replacing, so you can layer a shared base with per-chart overrides: ```tsx theme={null} const base = config({ appearance: { background: { type: 'solid', color: '#fff' } }, }); const spec = pipe( createSpec({ x: 'month', y: 'revenue' }), geom.bar(), scale.x(), scale.y(), base, config({ content: { title: 'Q1', isTitleVisible: true } }) // merged onto base ); ``` Anything you don't set keeps its resolved default. ## Sections | Section | Controls | Page | | -------------- | ------------------------------------------------- | ----------------------------------------------------- | | `content` | Title, subtitle, caption, source, provenance mark | [Content](/sdk-next/config/content) | | `axes` | Labels, position, grid, ticks, baseline | [Axes](/sdk-next/config/axes) | | `legend` | Position and display mode | [Legend](/sdk-next/config/legend) | | `headline` | Summary numbers above or inside the chart | [Headline numbers](/sdk-next/config/headline-numbers) | | `numberFormat` | Decimals, abbreviation, separators, affixes | [Number format](/sdk-next/config/number-format) | | `appearance` | Background, border, corner radius, text scale | [Appearance](/sdk-next/config/appearance) | | `layout` | Outer padding and per-region gaps | [Layout](/sdk-next/config/layout) | Data labels sit slightly apart — they're a [per-layer geom option](/sdk-next/config/data-labels) rather than a `config` section, because labels attach to a specific observation. # Layout Source: https://docs.graphy.dev/sdk-next/config/layout The `layout` section controls the whitespace around the chart — the outer padding, and the gaps between the regions the engine arranges (header, legend, axes, plot). ```tsx theme={null} config({ layout: { padding: 32 }, }); ``` ## Padding Outer padding in pixels, applied on all four sides. `null` uses the engine default. ## Region gaps The chart is laid out as a grid of named regions. `gaps` overrides the spacing around any of them; anything left unset keeps the default. ```tsx theme={null} config({ layout: { padding: 24, gaps: { header: 40, // space below the header bottomLegend: 16, }, }, }); ``` A gap value is either a number (sets the trailing/`after` gap) or `{ before, after }` to set each edge independently. The overridable regions are: | Direction | Regions | | --------- | ------------------------------------------------------------------------------------------------------------- | | Rows | `header`, `headline`, `topLegend`, `topAxisLabel`, `topAxis`, `bottomAxis`, `bottomAxisLabel`, `bottomLegend` | | Columns | `leftLegend`, `leftAxis`, `rightAxis` | Adjacent regions share a boundary using a `max(previous.after, next.before)` rule, so a lone `after` can't shrink a boundary below the following region's `before`. ## Related * [Appearance](/sdk-next/config/appearance) — background, border and corner radius * [Legend](/sdk-next/config/legend) — legend position feeds the region layout # Legend Source: https://docs.graphy.dev/sdk-next/config/legend The `legend` section controls where the legend sits and how it presents series. A legend appears automatically when a chart maps `color` (or `group`) to more than one series. ```tsx theme={null} config({ legend: { position: 'bottom', display: 'pill' }, }); ``` ## Options Where the legend sits. `'none'` hides it; `'auto'` lets the engine choose based on the chart. How series are labelled: * `'pill'` — a boxed legend with a swatch and label per series. * `'direct'` — labels drawn next to each series' endpoint, no separate legend box. Reads well on line charts. * `'auto'` — resolved during compilation from the chart type and legend position. ## Direct labels Combine `display: 'direct'` with `position` to steer which side the labels flow toward: ```tsx theme={null} config({ legend: { position: 'right', display: 'direct' }, }); ``` ## Hiding the legend ```tsx theme={null} config({ legend: { position: 'none' } }); ``` ## Related * [Mappings & aesthetics](/sdk-next/concepts/mappings) — `color` and `group` create series * [Scales](/sdk-next/concepts/scales) — the color scale that assigns series their swatches # Number format Source: https://docs.graphy.dev/sdk-next/config/number-format The `numberFormat` section controls how numeric values are displayed across the chart — axis ticks, tooltips, data labels and headlines. It sets the defaults; the [renderer's locale](/sdk-next/quickstart#4-theme-and-locale) still governs locale-specific separators unless you override them here. ```tsx theme={null} config({ numberFormat: { decimals: 0, abbreviation: 'auto' }, }); ``` ## Options Decimal places. A number fixes them (`2` → `1234.56`); `'auto'` varies with magnitude. How large numbers are shortened: * `'none'` — `1,234,567` * `'auto'` — `1.2M`, chosen by magnitude * `'k'` / `'m'` / `'b'` — force thousands / millions / billions Text prepended to every value, e.g. `'$'`. Text appended to every value, e.g. `'%'` or `' units'`. Override the thousands separator. Defaults to the locale's. Override the decimal separator. Defaults to the locale's. ## Example ```tsx theme={null} config({ numberFormat: { decimals: 1, abbreviation: 'none', prefix: '$', }, }); ``` ## Related * [Configuration overview](/sdk-next/config/index) * [Quickstart › locale](/sdk-next/quickstart#4-theme-and-locale) — the renderer's `formattingLocale` # Data structure Source: https://docs.graphy.dev/sdk-next/data-structure In the Graphy SDK, your **data** and your **spec** are separate. Data is a plain table you pass to `GraphProvider` as the `data` prop; the spec references that table's columns by key through its [mapping](/sdk-next/quickstart#2-render-your-first-chart). Keeping them apart means the same spec can be re-used across datasets, and the same data can be drawn several ways. ## Basic structure Data is a table with explicitly defined columns and rows: ```tsx theme={null} const data = { columns: [ { key: 'category', label: 'Category' }, { key: 'value', label: 'Value' }, ], rows: [ { category: 'A', value: 100 }, { category: 'B', value: 200 }, { category: 'C', value: 150 }, ], }; ``` The `columns` array defines the shape; `rows` holds the values. ## Referencing columns from a spec A spec never contains data — it names the columns it needs through its mapping. The variable strings you pass to `mapping` (or the `createSpec` shorthand) are column `key`s: ```tsx theme={null} const spec = pipe( // 'category' and 'value' are column keys createSpec({ x: 'category', y: 'value' }), geom.bar(), scale.x(), scale.y() ); ``` Because the binding is by key, the `label` on a column is purely presentational — it's what shows in axes, legends and tooltips. If you omit it, the key is used. Row keys must exactly match a column `key`. Values under keys with no matching column definition are ignored. ## Value format detection Graphy inspects each column's values and infers a **value format** — this drives how a scale interprets the column and how the renderer formats it. The first match wins. ### Numbers Numbers and numeric strings are detected as quantitative values. Thousands separators, decimals and magnitude suffixes are supported: ```tsx theme={null} { category: 'A', value: 100 } { category: 'B', value: '1,250.50' } { category: 'C', value: '2.5m' } // k, m, b, t suffixes supported ``` ### Dates Date-like strings are parsed automatically across a wide range of formats — ISO dates, named months and locale-specific orderings: ```tsx theme={null} { date: '2024-01-15', value: 100 } // YYYY-MM-DD { date: 'January 2024', value: 200 } // month name + year { date: '15/01/2024', value: 150 } // DD/MM/YYYY (en-GB, the default locale) { date: '01/15/2024', value: 150 } // MM/DD/YYYY (en-US) { date: '2024-01-15T12:30:00Z', value: 300 } // ISO datetime { date: 'Q1 2024', value: 400 } // quarter ``` Short month names (`Jan`, `Feb`) are recognised alongside full names. Set `data._metadata.parsingLocale` to control whether ambiguous dates like `01/02/2024` are read as DD/MM (`en-GB`) or MM/DD (`en-US`). **Weekly date ranges** are also detected — values like `1 Feb – 7 Feb` or `1 Feb 2024 – 7 Feb 2024` representing 7-day spans. ### Percentages Values with a `%` suffix are detected as percentages: ```tsx theme={null} { category: 'Desktop', share: '64.5%' } { category: 'Mobile', share: '28.3%' } ``` ### Currencies Currency-formatted strings are parsed with automatic symbol recognition. Supported symbols: `$`, `€`, `£`, `¥`, `₹`, `₱`, `₩`, `₪`, `₫`, `₽`, `฿`, `₦`, `₺`, `zł`, `kr`, `Fr`, `R`, `R$`, `Rp`, `RM`, `د.إ`, `﷼`, `Ch$`, `NT$`, `HK$`, `S$`, `A$`, `C$`, `NZ$`, `MX$`. ```tsx theme={null} { product: 'A', price: '$1,250' } { product: 'B', price: '£2,450.50' } { product: 'C', price: '€3,000' } ``` Symbols can appear as prefix or suffix, and negative values are supported (`-$100`, `$-100`). ### Text Any value that doesn't match the above is treated as text (categorical data). ## How formats reach scales The inferred format feeds the scale you declare for each aesthetic. Calling a position scale bare (`scale.x()`) lets the engine choose a scale type from the column's format: * A **text** column → a discrete (band) scale. * A **numeric**, **percentage** or **currency** column → a continuous scale. * A **date** column → a temporal scale. You can always override the inference with an explicit method — `scale.x.continuous()`, `scale.x.discrete()`, `scale.x.datetime()` — when you want to force a particular treatment. See the [line chart guide](/sdk-next/graph-types/line) for worked examples. ## Missing values Use `null` for a missing cell. How a geom treats gaps is geom-specific — for lines, the `missingValues` param chooses whether to break the path, bridge it, or treat the gap as zero. ```tsx theme={null} rows: [ { month: 'Jan', revenue: 12000 }, { month: 'Feb', revenue: null }, // missing { month: 'Mar', revenue: 18000 }, ]; ``` ## Schema Array of column definitions. Each column defines the structure and metadata for a data field. Unique, stable identifier for the column. Must match the keys used in `rows` objects, and is what a spec's mapping references. Human-readable label shown in the UI (axis labels, legends, tooltips). Defaults to the `key` if not provided. Array of data rows. Each row is an object with keys matching the column keys. Values can be a `string`, `number`, `Date` or `null`. Optional metadata for parsing and advanced data processing. Locale used to infer column types and parse values (dates, numbers). Defaults to `'en-GB'`. * `'en-GB'`: DD/MM/YYYY date format * `'en-US'`: MM/DD/YYYY date format Sort configuration: `{ columnKey: string; direction: 'asc' | 'desc' }`. Time unit for grouping time-based data. Rolling date filter: `{ timeUnit: 'year' | 'quarter' | 'month' | 'week' | 'day'; value: number }` — e.g. the last 30 days is `{ timeUnit: 'day', value: 30 }`. # Annotations Source: https://docs.graphy.dev/sdk-next/emphasis/annotations Annotations layer callouts on top of the plot — a shaded region, a labelled arrow, a note pinned to a spike. They carry information the data doesn't contain, and the `annotation` builder pipes them into the spec like any other feature. Because they're a spec feature, annotations re-resolve on every compile: panel-anchored ones re-flow when the chart resizes, and data-anchored ones move with the observation they're pinned to. ## Anchoring Every annotation is positioned by an *anchor* that describes a relationship to the graph, not a fixed pixel. There are two frames: * **Panel** — a fraction of the plot rectangle, `[0, 1]` from the top-left. `{ anchorType: 'panel', x: 0.5, y: 0 }` is the top-centre. Panel anchors don't snap to data, so they hold their place as the data changes. * **Observation** — pinned to a single data point by its `anchorValue` (the x-value) and optional `groupValue` (the series). The annotation follows that observation wherever it lands. ```tsx theme={null} // floats at the top-centre of the plot { anchorType: 'panel', x: 0.5, y: 0 } // pins to the 'Jun' point of the 'North' series { anchorType: 'observation', anchorValue: 'Jun', groupValue: 'North' } ``` Region-based annotations (shapes, images) take a panel *rectangle* — `{ anchorType: 'panel', x, y, width, height }` — and point/observation annotations take a single anchor. An observation anchor also accepts an `align` (which edge or corner of the observation to attach to) and an `offset` (a nudge in panel fractions or pixels). On a combo chart, more than one layer can hold the same `(anchorValue, groupValue)` pair — bars and a trend line both have a value at `'Jun'`. Name the layer you mean and pass its id as `layerId`: ```tsx theme={null} pipe( createSpec({ x: 'month' }), geom.bar({ id: 'bars', aes: { y: 'sales' } }), geom.line({ aes: { y: 'trend' } }), annotation.pinnedNumber({ at: { layerId: 'bars', anchorValue: 'Jun' } }) ); ``` Omit `layerId` and the anchor resolves against the first layer that matches. An id no layer carries drops that annotation with a warning rather than silently moving it. ## The annotation kinds Each method on `annotation` appends one callout. Multiple calls of the same kind accumulate. | Method | What it draws | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | | `annotation.differenceArrow()` | A labelled delta between **two observations** — reads the measured gap and labels it as an absolute, relative, or proportional change | | `annotation.shape()` | A shaded rectangle over a panel region — the classic "forecast" or "recession" band | | `annotation.arrow()` | A free-standing arrow between two point anchors, each floating or pinned | | `annotation.text()` | A free rich-text label | | `annotation.image()` | An image placed in a panel region | | `annotation.sticker()` | A built-in emoji-like sticker pinned to a point | | `annotation.pinnedNumber()` | A dot on one observation; its value shows in a mini view, full tooltip on hover | | `annotation.comment()` | A dot on one observation carrying rich-text content | ### Difference arrow A difference arrow spans two observations and labels the change between them — you choose *what* the label measures: ```tsx theme={null} annotation.differenceArrow({ start: { anchorValue: 'Jan', groupValue: 'North' }, end: { anchorValue: 'Jun', groupValue: 'North' }, label: 'relative-difference', // 'absolute-difference' | 'relative-difference' | 'proportion' }); ``` Distinct from `annotation.arrow()`, which is a plain arrow between two positions you specify — it doesn't read any data. ### Shaded region ```tsx theme={null} annotation.shape({ region: { anchorType: 'panel', x: 0, y: 0.7, width: 1, height: 0.3 }, fillColor: '#e15759', fillOpacity: 0.12, zOrder: 'background', // beneath the geoms; 'foreground' draws on top }); ``` `zOrder` decides whether a shape or image sits behind the data (`'background'`, the usual choice for a wash of color) or in front of it (`'foreground'`). ### Text A text annotation takes a [rich-text node](/sdk-next/config/content) and a top-left point anchor. `width` is a fraction of the plot width; height follows the content: ```tsx theme={null} annotation.text({ content: { type: 'doc', content: [ { type: 'paragraph', content: [{ type: 'text', text: 'Product launch' }], }, ], }, at: { anchorType: 'panel', x: 0.4, y: 0.1 }, width: 0.25, }); ``` ### Image Place an image in a panel region. `fit` decides how it scales inside the box — `'fill'` stretches, `'contain'` letterboxes, `'cover'` crops to fill: ```tsx theme={null} annotation.image({ src: 'https://example.com/logo.png', // URL or data URI region: { anchorType: 'panel', x: 0.05, y: 0.08, width: 0.24, height: 0.5 }, fit: 'contain', // 'fill' | 'contain' | 'cover' }); ``` Like a shape, an image also takes an optional `zOrder` (`'background'` or `'foreground'`) and an `opacity` in `[0, 1]`. ### Sticker A sticker pins a built-in emoji-like image to a single observation. Give it a `sticker` id from the catalogue and an observation anchor — it then travels with its data point through re-sorts and filters: ```tsx theme={null} annotation.sticker({ sticker: 'rocket', // 'rocket' | 'thumbs-up' | 'thumbs-down' | 'clapping-hands' | 'grinning-face' | … at: { anchorType: 'observation', anchorValue: 'Q4' }, }); ``` ## Related * [Reference lines](/sdk-next/emphasis/reference-lines) — for marking a constant value rather than a region * [Content](/sdk-next/config/content) — the rich-text node shape used by text and comment annotations * [Mappings & aesthetics](/sdk-next/concepts/mappings) — the series and x-values an observation anchor names # Highlights Source: https://docs.graphy.dev/sdk-next/emphasis/highlights A highlight emphasises the observations that match a predicate by de-emphasising the ones that don't. You describe *what* to match — not which pixels to paint — so the highlight tracks the data through re-sorts, filters, and updates. ```tsx theme={null} import { useMemo } from 'react'; import { createSpec, pipe, geom, scale, highlight, } from '@graphysdk/viz-engine'; import { GraphProvider, GraphRenderer } from '@graphysdk/react-renderer'; const data = { columns: [{ key: 'quarter' }, { key: 'region' }, { key: 'sales' }], rows: [ { quarter: 'Q1', region: 'EU', sales: 1200 }, { quarter: 'Q1', region: 'US', sales: 1500 }, { quarter: 'Q2', region: 'EU', sales: 1800 }, { quarter: 'Q2', region: 'US', sales: 1600 }, { quarter: 'Q3', region: 'EU', sales: 2400 }, { quarter: 'Q3', region: 'US', sales: 2200 }, { quarter: 'Q4', region: 'EU', sales: 2800 }, { quarter: 'Q4', region: 'US', sales: 3100 }, ], }; export function RegionalSales() { const spec = useMemo( () => pipe( createSpec({ x: 'quarter', y: 'sales', color: 'region' }), geom.bar({ position: 'dodge' }), scale.x(), scale.y(), scale.color.palette(), highlight({ variable: 'region', eq: 'EU' }) // EU bars stay vivid, the rest fade ), [] ); return ( ); } ``` ## Predicates The first argument to `highlight()` is a predicate — a test against your post-transform columns. Field predicates name a `variable` and a comparison: | Predicate | Matches | | ---------------------------------------------- | ------------------------------------------------ | | `{ variable: 'region', eq: 'EU' }` | exact value | | `{ variable: 'quarter', oneOf: ['Q3', 'Q4'] }` | any of a set | | `{ variable: 'sales', gt: 2000 }` | `gt` / `gte` / `lt` / `lte` — ordered comparison | | `{ variable: 'sales', range: [1500, 2500] }` | inclusive numeric range | Ordered operators (`gt`, `gte`, `lt`, `lte`, `range`) need a numeric or date column. Using one against a categorical field is a compile-time validation error, surfaced as a [diagnostic](/sdk-next/concepts/how-a-chart-is-built) rather than a silent no-op. Combine predicates with the logical forms `and`, `or`, and `not`: ```tsx theme={null} highlight({ and: [ { variable: 'region', eq: 'EU' }, { variable: 'quarter', oneOf: ['Q3', 'Q4'] }, ], }); // EU, but only in the second half ``` ## Scope By default a highlight matches individual observations. The `scope` option expands each match to a larger visual unit: | Scope | Effect | | -------------------------- | ------------------------------------------------------------------------------------------------------ | | `'data-point'` *(default)* | Only the rows that satisfy the predicate; their series siblings stay dimmed | | `'series'` | Any matching row expands to its whole series — highlight an entire line, area, or bar group | | `'x-value'` | Any matching row expands to every observation sharing its x — highlight a vertical slice across series | ```tsx theme={null} highlight({ variable: 'region', eq: 'EU' }, { scope: 'series' }); ``` ## Scoping to one layer In a multi-layer chart (say bars plus a trend line), bind the highlight to a single layer: name the layer with `id` and point at it with `layerId`. Observations in other layers are never tested, so a Q4 highlight on the bars leaves the trend line untouched: ```tsx theme={null} pipe( createSpec({ x: 'month' }), geom.bar({ id: 'bars', aes: { y: 'sales' } }), geom.line({ aes: { y: 'trend' } }), // ...scales highlight( { variable: 'month', oneOf: ['Oct', 'Nov', 'Dec'] }, { layerId: 'bars' } ) ); ``` Omit `layerId` to evaluate the predicate against every layer. A layer you never name gets an id assigned at resolve time, so scoping is the one case where naming it yourself matters. ## Combining highlights Multiple `highlight()` calls accumulate, and the engine **unions** their matches — an observation matched by any highlight is emphasised. So two separate highlights behave like an `or`: ```tsx theme={null} pipe( // ... highlight({ variable: 'sales', gte: 2000 }), highlight({ variable: 'region', eq: 'US' }) // rows ≥ 2000 OR any US row ); ``` Because the union is exactly an `or`, this is equivalent to a single highlight with an `or` predicate — same matched observations, same result: ```tsx theme={null} pipe( // ... highlight({ or: [ { variable: 'sales', gte: 2000 }, { variable: 'region', eq: 'US' }, ], }) ); ``` Reach for the single `or` predicate when the two conditions are one idea; reach for two separate highlights when they're independent emphases you might toggle separately. ## Styling the de-emphasis How the *un-matched* observations recede is stylesheet paint: entries scoped to the `'dimmed'` state. The built-in stylesheet dims to `alpha: 0.4`; declare your own dimmed entry to change the look: ```tsx theme={null} styles({ defaults: [style.geom({ saturation: 0, alpha: 0.6 }, { state: 'dimmed' })], }); ``` `alpha` lowers the opacity of non-matched observations; `saturation: 0` drains their color toward grey. ## Related * [Mappings & aesthetics](/sdk-next/concepts/mappings) — the variables a predicate can name * [Transforms](/sdk-next/advanced/transforms) — predicates run against post-transform columns * [Geoms](/sdk-next/concepts/geoms) — the stylesheet the dimmed entries live in # Annotations & emphasis Source: https://docs.graphy.dev/sdk-next/emphasis/index Graphy gives you three composable tools for directing attention, all piped into the [spec](/sdk-next/concepts/how-a-chart-is-built) alongside your geoms and scales. ## Three ways to draw attention | Tool | Builder | What it does | | ------------------- | -------------- | ----------------------------------------------------------------------------------------------- | | **Highlights** | `highlight()` | De-emphasise everything that *doesn't* match a predicate, so the matched observations stand out | | **Reference lines** | `geom.rule()` | Draw a constant line — a goal, threshold, or baseline — that the scale grows to fit | | **Annotations** | `annotation.*` | Layer callouts on top of the plot: difference arrows, shaded regions, text, arrows, and more | Each is a spec feature, so it composes left-to-right in `pipe` like any geom or scale, and re-resolves on every compile — annotations track the data they're pinned to, and rules extend the axis domain. ```tsx theme={null} pipe( createSpec({ x: 'month', y: 'revenue', color: 'region' }), geom.line(), scale.x.discrete(), scale.y(), scale.color.palette(), highlight({ variable: 'region', eq: 'EU' }), // emphasis geom.rule({ aes: { y: { value: 2500 } }, params: { label: 'Target' } }), // reference line annotation.shape({ region: { anchorType: 'panel', x: 0.75, y: 0, width: 0.25, height: 1 }, fillColor: '#e15759', fillOpacity: 0.1, }) // callout ); ``` Predicate-driven emphasis: pick observations, series, or a whole x-slice. Goal lines, thresholds, and baselines with `geom.rule()`. Difference arrows, shaded regions, text, and pinned dots. ## Highlight vs. annotation vs. reference line They overlap in spirit but differ in mechanism: * Reach for a **highlight** when the emphasis is *about the data* — "show me the EU series," "dim everything below target." The predicate selects real observations, so it survives data changes and re-sorts. * Reach for a **reference line** when you're marking a *constant value* the data should be read against — a sales goal, a capacity ceiling, a zero baseline. * Reach for an **annotation** when you're adding a *layer the data doesn't contain* — a label explaining a spike, an arrow between two points, a shaded "forecast" band. # Reference lines Source: https://docs.graphy.dev/sdk-next/emphasis/reference-lines A reference line marks a constant value — a sales target, a capacity ceiling, a zero baseline — that the rest of the chart is read against. It's a geom like any other: `geom.rule()`, layered into the spec with its own value and style. ## A horizontal goal line Bind the rule to a constant `y` with `aes: { y: { value } }`. The `{ value }` form is a [constant mapping](/sdk-next/concepts/mappings#variables-and-constants) — it pins the aesthetic to a literal instead of a column: ```tsx theme={null} import { useMemo } from 'react'; import { createSpec, pipe, geom, scale } from '@graphysdk/viz-engine'; import { GraphProvider, GraphRenderer } from '@graphysdk/react-renderer'; const data = { columns: [{ key: 'month' }, { key: 'revenue' }], rows: [ { month: 'Jan', revenue: 1200 }, { month: 'Feb', revenue: 1800 }, { month: 'Mar', revenue: 2400 }, { month: 'Apr', revenue: 1600 }, { month: 'May', revenue: 3200 }, { month: 'Jun', revenue: 2800 }, ], }; export function RevenueWithTarget() { const spec = useMemo( () => pipe( createSpec({ x: 'month', y: 'revenue' }), geom.bar(), geom.rule({ aes: { y: { value: 2500 } }, params: { label: 'Target', lineType: 'dashed' }, }), scale.x(), scale.y() ), [] ); return ( ); } ``` The rule value participates in the y-scale's auto-domain — set a target above the tallest bar and the axis grows to fit it, so the line is never clipped off the top. ## Styling the line Style the rule through its `params`: Optional inline text drawn alongside the line. Omit to draw a bare line. Anchors the label at the start or end of the line. Stroke color. Falls back to the theme's reference-line token when unset. Line thickness in pixels. Stroke style. ## Vertical lines Mark a constant along the x-axis with `aes: { x: { value } }` instead: ```tsx theme={null} geom.rule({ aes: { x: { value: 40 } }, params: { label: 'Threshold' } }); ``` A vertical rule requires a **numeric** x-axis — it marks a constant x-value, which only has meaning when x is continuous. Categorical and date x-axes support horizontal rules only. ## Multiple rules Rules compose like any other layer, and paint in spec order — a rule added after `geom.bar()` draws in front of the bars. Stack as many as you need: ```tsx theme={null} pipe( createSpec({ x: 'month', y: 'revenue' }), geom.bar(), geom.rule({ aes: { y: { value: 1000 } }, params: { label: 'Floor', lineType: 'dotted' }, }), geom.rule({ aes: { y: { value: 2500 } }, params: { label: 'Target' } }), geom.rule({ aes: { y: { value: 3500 } }, params: { label: 'Ceiling', lineType: 'dotted' }, }), scale.x(), scale.y() ); ``` ## Flipped and secondary axes A rule tracks the data space, not the screen. Under [`coord.flip()`](/sdk-next/concepts/coordinate-systems) a `{ y: { value } }` rule still marks a constant y — the renderer paints it as a vertical line because the y-axis now runs horizontally. To pin a rule to a [secondary axis](/sdk-next/graph-types/combo), add `yScaleType: 'secondary'`: ```tsx theme={null} geom.rule({ aes: { y: { value: 7 } }, yScaleType: 'secondary', params: { label: 'SLA' }, }); ``` ## Related * [Mappings & aesthetics](/sdk-next/concepts/mappings#variables-and-constants) — the `{ value }` constant form * [Scales](/sdk-next/concepts/scales) — how the rule value extends the auto-domain * [Coordinate systems](/sdk-next/concepts/coordinate-systems) — rules under `coord.flip()` # Custom geoms Source: https://docs.graphy.dev/sdk-next/extending/custom-geoms A custom geom is a new kind of shape a chart can draw. It has two halves: a **compile half** — a `Geom` definition that declares the geom's aesthetics and places its observations in data space — and a **render half** that paints them. This page covers the compile half; the [geom renderers](/sdk-next/extending/geom-renderers) page covers paint. The guiding rule: **the compiler owns every position, the renderer invents none.** Your geom declares which position roles it needs, writes their values in data units, and the compiler scales them onto the axes. An observation then stays anchored through any domain change — zoom, a log scale, a flipped coordinate — because its coordinates were resolved by the same scales as every other geom. ## Anatomy of a geom Extend the `Geom` class, parameterised by its params type. Here's a lollipop — a dot on a stem dropped to the baseline: ```tsx theme={null} import { Geom, POSITION_VARIABLES } from '@graphysdk/viz-engine'; import type { CompiledGeom, GeomCompilerInput } from '@graphysdk/viz-engine'; class LollipopGeom extends Geom<{ stemWidth: number }> { readonly type = 'lollipop' as const; override readonly defaultParams = { stemWidth: 2 }; override readonly positionRoles = [ { axis: 'x', role: 'point', valueKind: 'value' }, { axis: 'y', role: 'min', valueKind: 'value' }, // the baseline { axis: 'y', role: 'max', valueKind: 'value', aes: 'y' }, // the dot height ] as const; override readonly aesthetics = [{ kind: 'visual', name: 'color' }] as const; override readonly supportedCoordTypes = ['cartesian'] as const; override readonly spatialKind = 'buckets'; compile({ data }: GeomCompilerInput): CompiledGeom { // The baseline is a position, not a render constant: write it in data units so the y-scale maps it. const withBaseline = data.hasVariable(POSITION_VARIABLES.yMin) ? data : data.addConstantVariable(POSITION_VARIABLES.yMin, 'numeric', 0); return { data: withBaseline, mapping: {} }; } } ``` ## What the definition declares The geom's name, written `as const`. It becomes the typed builder method (`kit.geom.lollipop`) and the renderer registry key. Default values for the geom's styling params. The type parameter on `Geom` types both these and the `params` the builder accepts. The positions the geom occupies, each `{ axis, role, valueKind }`. A `'point'` role is a single coordinate on its axis; `'min'` / `'max'` form an interval (like a bar or area); `'scalar'` is a magnitude. A role may name a custom positional `aes` — the lollipop's `max` role is exposed as the `y` aesthetic. Write the array `as const` so the typed builder can constrain `aes` to exactly these aesthetics. The visual and data aesthetics the geom reads beyond position — here, `color`. Each is `{ kind, name }`, with `kind` one of `'visual'` (trained through a visual scale, like `color` or `size`) or `'data'` (a raw input a layout geom hands to its algorithm). Which coordinate systems the geom compiles under. The lollipop is cartesian-only. How the compiler builds the hover hit-test index from the geom's scaled positions. `'buckets'` groups by x (lines, areas), `'rects'` uses rectangular bounds (bars), `'points'` uses point proximity, `'noop'` opts out. Use `'render-hit-test'` for a [layout geom](/sdk-next/extending/geom-renderers#hit-testing-layout-geoms) whose geometry the compiler can't see. ## The compile step `compile(input)` receives the layer's `data` (a `Dataset`) and returns a `CompiledGeom` — `{ data, mapping }`. This is where the geom writes any positions it derives rather than reads from the mapping. The lollipop's baseline isn't in the data, so `compile` adds it as a constant column in data units; the compiler then scales `yMin` and `yMax` through the shared y-scale like any other position. Do the minimum here: derive positions, and leave scaling, stacking, and axis placement to the pipeline. A geom that computes screen pixels in `compile` has crossed the [compile/render seam](/sdk-next/concepts/how-a-chart-is-built). ## Registering and using it The compile half pairs with a render half through [`defineGeomRenderer`](/sdk-next/extending/geom-renderers), and the paired result goes into the `plugins` array. From there the typed builder method appears, constrained to exactly the aesthetics and params the definition declared: ```tsx theme={null} const lollipop = defineGeomRenderer(new LollipopGeom(), lollipopRenderer); const kit = createGraphyKit({ plugins: [lollipop] }); const spec = kit.pipe( kit.createSpec({ x: 'category', y: 'revenue' }), kit.geom.lollipop({ aes: { color: 'category' }, params: { stemWidth: 3 } }), kit.scale.x(), // custom geoms declare their own position scales kit.scale.y(), kit.scale.color.palette() ); ``` A native spec doesn't auto-infer position scales the way the chart-type recipes do — declare `scale.x()` and `scale.y()` explicitly, or the positions resolve to `NaN`. See [Scales](/sdk-next/concepts/scales#position-scales-must-be-declared). ## Related * [Geom renderers](/sdk-next/extending/geom-renderers) — the render half, `defineGeomRenderer`, hit-testing, hover * [How a chart is built](/sdk-next/concepts/how-a-chart-is-built) — where `compile` sits in the pipeline * [Scales](/sdk-next/concepts/scales) — how declared positions get trained and mapped # Geom renderers Source: https://docs.graphy.dev/sdk-next/extending/geom-renderers A geom renderer owns the React paint for one `(geom, coord)` composition — the **render half** of a geom, opposite its [compile half](/sdk-next/extending/custom-geoms). Renderers paint observations and respond to hover; they don't own layout, guides, or data-label placement. The registry is keyed on the `(geom, coord)` pair, so `bar × cartesian` (a column) and `bar × polar` (a pie wedge) are independent renderers that share no code. Adding one never touches the other. ## `defineGeomRenderer` is dual-target The same function binds a renderer two ways, depending on what you pass first: ### A whole custom geom Pass a `Geom` **instance** to pair both halves. The result carries the compile definition on `.definition`, so dropping it into `plugins` registers the geom *and* derives its typed builder method: ```tsx theme={null} import { defineGeomRenderer, createGraphyKit } from '@graphysdk/react-renderer'; const lollipop = defineGeomRenderer(new LollipopGeom(), { coord: 'cartesian', render: ({ layer }) => , renderHover: ({ layer, primary }) => ( ), renderHoverCompanions: () => null, }); const kit = createGraphyKit({ plugins: [lollipop] }); // kit.geom.lollipop(...) is now typed from the definition ``` ### Render-only overrides Pass a built-in geom **name** to rebind only the paint half. The built-in compile half keeps running — positions, stacking, scales, and axes are unchanged — so only the look differs. This is how you restyle bars without reinventing bar geometry: ```tsx theme={null} import { defineGeomRenderer, GraphProvider } from '@graphysdk/react-renderer'; const sketchyBar = defineGeomRenderer('bar', { coord: 'cartesian', guideMode: 'band', render: ({ layer, coordSystem }) => { if (coordSystem.type !== 'cartesian') return null; return ; }, renderHover: ({ primary, coordSystem }) => coordSystem.type === 'cartesian' ? ( ) : null, renderHoverCompanions: () => null, }); // no kit needed — the built-in geom.bar() authors the spec, this only repaints it ; ``` The name is constrained to the built-in geom union, so overriding an unknown name is a compile-time error. To restyle a *custom* geom, rebind the definition you already hold rather than its name. ## The contract Every renderer implements a `GeomRenderContract`: The coordinate system this contract paints under — `'cartesian'`, `'flip'`, or `'polar'`. A geom binds one contract per coord. Paints the observations. Receives `{ layer, coordSystem, isAnimated, formattingLocale }`. The `{ fn, options: { overlay: true } }` form paints into a screen-aligned portal instead — see [Live geoms](#live-and-draggable-geoms). Paints the hover state for this layer's observations. Receives the hovered `primary` hit plus its `group` and `related` hits, the `coordSystem`, and the `panelRect`. Cross-layer hints — e.g. dots dropped on a line at the hovered x. Return `null` to opt out. The positional guide drawn when this is the hovered layer: `'band'` shades the hovered category (bars), `'crosshair'` draws a rule at the hovered value (lines, areas). Omit or pass `null` to draw none (scatter points). The swatch shape shown for this geom in the legend and tooltip. Optional repaint of the matched subset for the [highlight](/sdk-next/emphasis/highlights) overlay. Falls back to `render` when omitted. Override it when the plain grouped render would misrepresent a matched subset. A per-cursor spatial query for a [layout geom](#hit-testing-layout-geoms). Only consulted when the compile half declares `spatialKind: 'render-hit-test'`. ## Reading scaled positions The renderer reads coordinates off each `Observation` with the value readers — it never re-derives a position. The compiler already scaled them; the reader hands you the resolved value, and helpers convert it to the paint frame: ```tsx theme={null} import { getX, getYMin, getYMax, getColor, toPercent, toViewBoxX, toViewBoxY, } from '@graphysdk/viz-engine'; const x = getX(observation); // scaled x const yTop = getYMax(observation); // scaled dot height const yBase = getYMin(observation); // scaled baseline ; ``` A reader returns `null` for a missing or unscaled value — guard for it and skip the observation rather than painting `NaN`. For a render-only override of a built-in geom, higher-level readers like `getBarRectBounds(mainAxis, observation)` hand you the geom's normalized `[0,1]` bounds directly. ## Hover Hover has two render entry points, both in the contract: * **`renderHover`** repaints *this layer's* hovered observation — usually the same one drawn bolder or haloed. Because positions come from the compiled observation, the highlight lands exactly over the base shape. * **`renderHoverCompanions`** paints hints on *other* layers at the hovered x. Return `null` when the observation is its own highlight. ```tsx theme={null} renderHover: ({ primary }) => , renderHoverCompanions: () => null, ``` ## Hit-testing layout geoms Most geoms are hit-tested from their scaled positions — the compiler builds the index from `spatialKind`. But a **layout geom** computes its geometry with an algorithm render-side (Sankey ribbons, Treemap tiles, Voronoi cells), so the compiler can't see the shapes to index them. Such a geom declares `spatialKind: 'render-hit-test'` on its compile half and supplies a `hitTest` **factory** on the contract. The factory runs once per data change and returns a per-cursor tester; the cursor arrives in panel-local `[0,1]` (top-left origin), and the tester returns the identity key of the observation under it, or `null`: ```tsx theme={null} const treemap = defineGeomRenderer(new TreemapGeom(), { coord: 'cartesian', render: ({ layer }) => , renderHover: ({ primary }) => ( ), renderHoverCompanions: () => null, hitTest: ({ layer }) => { const tiles = buildTiles(layer.data); return ({ x, y }) => findTileKey(tiles, x, y); // → identity key or null }, }); ``` The renderer registers the tester on your behalf, so central hover, tooltips, and guides work the same as for a built-in geom. (`useGeomHitTest` is the underlying hook, but the contract's `hitTest` is the path you write against.) ## Live and draggable geoms A geom that runs its own simulation or drag interaction — a force-directed graph — must own its pointer events. For that, `render` takes the overlay-hosted form: `{ fn, options: { overlay: true } }`. Its function receives `input.overlay` with a `pushHover` to feed the central hover store and the `panelRect` for placement: ```tsx theme={null} const force = defineGeomRenderer(new ForceGeom(), { coord: 'cartesian', render: { fn: ({ layer, overlay }) => ( ), options: { overlay: true }, }, renderHover: () => null, renderHoverCompanions: () => null, }); ``` `useGeomHover(layerId)` is the lower-level push hook the overlay form wraps — reach for it only when the overlay form doesn't cover your case. ## Related * [Custom geoms](/sdk-next/extending/custom-geoms) — the compile half a renderer pairs with * [Interactivity](/sdk-next/rendering/interactivity) — the hover model renderers plug into * [Slots](/sdk-next/extending/slots) — replace a *region's* render without writing a geom # Extending Source: https://docs.graphy.dev/sdk-next/extending/index The engine ships with a fixed set of built-in geoms, stats, and transforms — but the grammar is open. You can register your own and they become first-class: a typed builder method, compiled through the same pipeline, painted through the same renderer registry. This is the advanced surface; most charts never need it. There are two ways in. **Plugins** extend the grammar itself — new geoms, stats, and transforms that compile and render like the built-ins. **Slots** are lighter: they replace how one *region* of the chart renders (a header, legend, or tooltip) without touching the grammar. Most of this section is about plugins; slots are a self-contained escape hatch on the renderer. Before writing a plugin, check whether a [render-only override](/sdk-next/extending/geom-renderers#render-only-overrides) or an existing geom composed differently gets you there. A new geom is the right tool for a genuinely new shape, not a restyle of an existing one. ## One array, both halves Everything you add is registered through a single `plugins` array. Passing it once seeds two things at once — what you can *write* (the typed builder methods) and what can *compile and render* — so the authoring surface and the runtime can never drift apart. ```tsx theme={null} import { createGraphyKit, defineGeomRenderer } from '@graphysdk/react-renderer'; const lollipop = defineGeomRenderer(new LollipopGeom(), lollipopRenderer); const kit = createGraphyKit({ plugins: [lollipop] }); // kit.geom.lollipop(...) now exists and is typed from the definition ``` ## Two entry points The ergonomic one. Returns the typed builder (`geom` / `stat` / `transform` / `scale` / `coord` / `createSpec` / `pipe`) **and** a `GraphProvider` already bound to the same plugins. Reach for this in a React app. The primitives `createGraphyKit` wraps. Use `createGraphyBuilder({ plugins })` for the headless authoring surface and pass the *same array* to ``. Choose this for framework-agnostic or advanced wiring. Both take the same `plugins` array. The kit is pure sugar — it calls `createGraphyBuilder` and pre-binds a provider, nothing more. ## What you can add | Extension | Adds | Halves | | ------------------------ | ----------------------------------------- | ---------------------------------------------------------- | | **Custom geom** | A new kind of shape | Compile half (`Geom`) + render half (`defineGeomRenderer`) | | **Render-only override** | A restyle of a built-in geom's paint | Render half only — geometry unchanged | | **Custom stat** | A per-layer data reshaping before mapping | Compile half only | | **Custom transform** | A dataset reshaping | Compile half only | A geom has two halves because it both *places* observations (compile) and *paints* them (render). Stats and transforms only touch data, so they're compile-only — no renderer. Replace a region's render — no plugin, no geom. Define a new shape with `Geom` — position roles, aesthetics, and the compile step. `defineGeomRenderer`, render-only overrides, hit-testing, and hover. A layer-scoped data reshaping that runs before mapping. Reshape the dataset before it becomes a layer. ## Related * [How a chart is built](/sdk-next/concepts/how-a-chart-is-built) — the pipeline your plugin joins * [Rendering](/sdk-next/rendering/index) — the renderer that hosts slots and geom renderers # Slots Source: https://docs.graphy.dev/sdk-next/extending/slots Slots replace how a single region of the chart renders while the [spec](/sdk-next/concepts/how-a-chart-is-built) still owns *whether* that region exists and *what* data it receives. Your override gets the same render-ready props the default would. Pass them through the `slots` prop. ```tsx theme={null} ``` ## Regions | Slot | Region | | ----------- | -------------------------------------------------------- | | `Header` | Title / subtitle area above the plot | | `Footer` | Caption, source, and provenance-mark area below the plot | | `Tooltip` | The hover tooltip | | `Grid` | The plot grid lines | | `Swatch` | The color swatch used in legend and tooltip | | `Legend` | The series legend | | `Headline` | Headline numbers | | `AxisTicks` | An axis's tick labels | | `AxisLabel` | An axis's title | Custom `Footer` / `Header` slots receive a `brandMark` visual (`'full' | 'mini' | 'hidden'`), resolved from `config.content.brandMark` and the frame size. That prop is **not** the visibility toggle; the toggle is `brandMark.enabled` (or legacy `isBrandMarkVisible`). `source` remains your data-source attribution. The default badge paints inside the matching region when placement matches. ## Two kinds of slot The distinction matters when you write an override: **Layout-safe slots** (`Header`, `Footer`, `Tooltip`, `Grid`, `Swatch`) are bare components. They paint inside a box the layout already sized, so you just provide a component: ```tsx theme={null} import type { TooltipSlotProps } from '@graphysdk/react-renderer'; function MyTooltip(props: TooltipSlotProps) { return
{/* render props.rows */}
; } ; ``` **Layout-coupled slots** (`Legend`, `Headline`, `AxisTicks`, `AxisLabel`) reserve edge space, so they're an object with both a `render` component **and** a `measure` function that reports the region's size. Paint and reserved space must agree, so the layout calls `measure` to know how much room to leave: ```tsx theme={null} ctx.measureText(axis.label, font).height + 8, }, }} /> ``` Give a layout-coupled slot's `measure` a stable reference (define it outside render or memoize it). A `measure` whose identity changes every render repaints but doesn't re-trigger the layout, so paint and reserved space drift. The `measure` receives a `SlotMeasureContext` with `measureText` (the same Canvas-backed measurer the built-in regions use) and the active `textScale`, so your reserved size matches what actually paints. ## Related * [Geom renderers](/sdk-next/extending/geom-renderers) — replace a whole geom's paint, not just a region * [Interactivity](/sdk-next/rendering/interactivity) — the `Tooltip` slot in context * [Theming](/sdk-next/rendering/theming) — restyle regions with tokens before reaching for a slot # Area Source: https://docs.graphy.dev/sdk-next/graph-types/area An area chart is a line with the region beneath it filled. Use `geom.area()` to emphasise magnitude across an ordered or continuous x-axis, and its stacked form for part-to-whole trends. ## Basic example ```tsx theme={null} import { useMemo } from 'react'; import { createSpec, pipe, geom, scale } from '@graphysdk/viz-engine'; import { GraphProvider, GraphRenderer } from '@graphysdk/react-renderer'; const data = { columns: [ { key: 'month', label: 'Month' }, { key: 'revenue', label: 'Revenue' }, ], rows: [ { month: '1 Jan', revenue: 1200 }, { month: '2 Jan', revenue: 1800 }, { month: '3 Jan', revenue: 2400 }, { month: '4 Jan', revenue: 1600 }, { month: '5 Jan', revenue: 3200 }, { month: '6 Jan', revenue: 2800 }, ], }; export function RevenueChart() { const spec = useMemo( () => pipe( createSpec({ x: 'month', y: 'revenue' }), geom.area(), scale.x(), scale.y() ), [] ); return ( ); } ``` ## Stacked area Map a category to `color` and stack the layers for a part-to-whole trend. In long format, one row per `month` × `region`: ```tsx theme={null} const data = { columns: [ { key: 'month', label: 'Month' }, { key: 'region', label: 'Region' }, { key: 'revenue', label: 'Revenue' }, ], rows: [ { month: 'Jan', region: 'North', revenue: 300 }, { month: 'Jan', region: 'South', revenue: 200 }, { month: 'Feb', region: 'North', revenue: 400 }, { month: 'Feb', region: 'South', revenue: 350 }, // ... ], }; const spec = pipe( createSpec({ x: 'month', y: 'revenue', color: 'region' }), geom.area({ position: 'stack' }), scale.x(), scale.y(), scale.color.palette() ); ``` Use `position: 'fill'` instead for a 100% stacked area, where each band shows its share of the total. ## Points on the area Add a `geom.point()` layer to mark every vertex. It shares the spec-level mapping, so both layers plot the same series. Pipe it after the area so the dots sit on top, and set `interactive: false` on the point layer: ```tsx theme={null} const spec = pipe( createSpec({ x: 'month', y: 'revenue' }), geom.area(), geom.point({ interactive: false, params: { size: 6 } }), scale.x(), scale.y() ); ``` `interactive: false` keeps the points out of hit-detection so the area owns hover — it resolves the nearest point along the x-axis and drives the tooltip. Interactive points would make hover fire only when the cursor lands directly on a dot. ## Options `geom.area()` shares the line's rendering knobs: Curve family. `'catmull-rom'` smooths the outline through every point. How the outline handles `null` values: break at the gap, span it, or treat as zero. Paint — the fill's opacity and the outline's width, dash, and opacity — is styled through the stylesheet: `style.geom.area({ alpha, strokeWidth, lineType, strokeAlpha })`. ## Related * [Line](/sdk-next/graph-types/line) — an unfilled outline, or a fading gradient fill via its `fillAlpha` style property * [Geoms & layers](/sdk-next/concepts/geoms) — stacking and position modes # Bar & column Source: https://docs.graphy.dev/sdk-next/graph-types/bar `geom.bar()` draws a rectangle per observation. In the default cartesian plane it produces **vertical columns**; add [`coord.flip()`](/sdk-next/concepts/coordinate-systems) to lay them on their side as **horizontal bars**. One geom, two orientations. Columns suit comparing categories or a value over discrete time periods; bars suit ranking and categories with long labels that wouldn't fit under a vertical axis. ## Basic example Value on `y`, category on `x` — the default cartesian form draws vertical columns: ```tsx theme={null} import { useMemo } from 'react'; import { createSpec, pipe, geom, scale } from '@graphysdk/viz-engine'; import { GraphProvider, GraphRenderer } from '@graphysdk/react-renderer'; const data = { columns: [ { key: 'category', label: 'Product' }, { key: 'revenue', label: 'Revenue' }, ], rows: [ { category: 'Product A', revenue: 1200 }, { category: 'Product B', revenue: 1800 }, { category: 'Product C', revenue: 2400 }, { category: 'Product D', revenue: 1600 }, { category: 'Product E', revenue: 3200 }, { category: 'Product F', revenue: 2800 }, ], }; export function ProductRevenueChart() { const spec = useMemo( () => pipe( createSpec({ x: 'category', y: 'revenue' }), geom.bar(), scale.x(), scale.y() ), [] ); return ( ); } ``` ## Stacked and grouped Map a category to `color`, then choose how the bars in each band arrange with `position`. In long format, one row per `quarter` × `region`: ```tsx theme={null} const data = { columns: [ { key: 'quarter', label: 'Quarter' }, { key: 'region', label: 'Region' }, { key: 'sales', label: 'Sales' }, ], rows: [ { quarter: 'Q1', region: 'North', sales: 350 }, { quarter: 'Q1', region: 'South', sales: 200 }, { quarter: 'Q1', region: 'West', sales: 500 }, // ... ], }; const spec = pipe( createSpec({ x: 'quarter', y: 'sales', color: 'region' }), geom.bar({ position: 'stack' }), // 'dodge' for grouped, 'fill' for 100% stacked scale.x(), scale.y(), scale.color.palette() ); ``` * `'stack'` — segments stacked into one column * `'dodge'` — segments side by side within the band * `'fill'` — stacked and normalised to 100% See [position modes](/sdk-next/concepts/geoms#position-modes) for the full list. ## Horizontal bars Keep the natural mapping — category on `x`, value on `y` — and add [`coord.flip()`](/sdk-next/concepts/coordinate-systems) to swap the axes. Stacking, grouping and styling all carry over unchanged; only the orientation differs: ```tsx theme={null} import { createSpec, pipe, geom, scale, coord } from '@graphysdk/viz-engine'; const spec = pipe( createSpec({ x: 'country', y: 'users' }), geom.bar(), scale.x(), scale.y(), coord.flip() // horizontal bars ); ``` ## Ranking To order bars by value, sort the data with a [transform](/sdk-next/advanced/transforms) — most useful with horizontal bars: ```tsx theme={null} import { transform } from '@graphysdk/viz-engine'; const spec = pipe( createSpec({ x: 'country', y: 'users' }), transform.sort({ variableName: 'users', direction: 'desc' }), geom.bar(), scale.x(), scale.y(), coord.flip() ); ``` ## Styling the bars `geom.bar()` accepts `params` to style the rectangle: Bar width as a fraction of the band, in `(0, 1]`. Corner rounding in pixels, or `'full'` for pill-shaped bars. Border color. The border is only drawn when this is set. Border width in pixels; only applies when `borderColor` is set. ```tsx theme={null} geom.bar({ params: { width: 0.6, borderRadius: 4 } }); ``` ## Related * [Coordinate systems](/sdk-next/concepts/coordinate-systems) — how `coord.flip()` works * [Geoms & layers](/sdk-next/concepts/geoms) — position modes and layering * [Scales](/sdk-next/concepts/scales) — control the value axis range # Combo Source: https://docs.graphy.dev/sdk-next/graph-types/combo A combo chart layers two geoms with different y-scales — typically bars for one metric and a line for another on a second axis. It's how you show two series whose units or magnitudes don't share a scale. ## Dual-axis example Give each geom its own `y` through a [layer-local mapping](/sdk-next/concepts/mappings#where-a-mapping-lives), and bind the line to the secondary axis with `yScaleType: 'secondary'`: ```tsx theme={null} import { useMemo } from 'react'; import { createSpec, pipe, geom, scale } from '@graphysdk/viz-engine'; import { GraphProvider, GraphRenderer } from '@graphysdk/react-renderer'; const data = { columns: [ { key: 'quarter', label: 'Quarter' }, { key: 'revenue', label: 'Revenue' }, { key: 'growth', label: 'Growth' }, ], rows: [ { quarter: 'Q1', revenue: 12000, growth: 5 }, { quarter: 'Q2', revenue: 15000, growth: 25 }, { quarter: 'Q3', revenue: 14000, growth: -7 }, { quarter: 'Q4', revenue: 18500, growth: 32 }, { quarter: 'Q5', revenue: 21000, growth: 14 }, { quarter: 'Q6', revenue: 19500, growth: -7 }, ], }; export function RevenueAndGrowth() { const spec = useMemo( () => pipe( createSpec({ x: 'quarter' }), // shared x geom.bar({ aes: { y: 'revenue' } }), // primary axis geom.line({ aes: { y: 'growth' }, yScaleType: 'secondary' }), // secondary axis scale.x(), scale.y(), scale.ySecondary(), ), [], ); return ( ); } ``` Each scale trains only on the layers bound to it, so the two metrics keep independent ranges. ## Related * [Mappings & aesthetics](/sdk-next/concepts/mappings) — layer-local mappings * [Scales](/sdk-next/concepts/scales) — the secondary y-scale * [Geoms & layers](/sdk-next/concepts/geoms) — how layers stack # Overview Source: https://docs.graphy.dev/sdk-next/graph-types/index A chart type is a **recipe** you compose — a geom, a coordinate system, a position mode, and the scales you declare. A vertical bar chart and a pie chart use the *same* `geom.bar()`; they differ only in coordinate system. ## The building blocks Every spec is assembled from four kinds of ingredient: | Ingredient | Builder | What it does | | ----------- | ------------------------------------------ | --------------------------------------------------------------- | | **Mapping** | `mapping({ ... })` / `createSpec({ ... })` | Binds data columns to aesthetics (`x`, `y`, `color`, `size`, …) | | **Geom** | `geom.*()` | The shape drawn for each observation | | **Scale** | `scale.*()` | Turns data values into positions, colors and sizes | | **Coord** | `coord.*()` | The coordinate system the geom is drawn in | ## Geoms There are five built-in geoms. Everything in the recipes below is one of these, sometimes layered: | Geom | Draws | Typical use | | -------------- | ---------------------------------------------- | -------------------------- | | `geom.point()` | A point per observation | Scatter, bubble, dot plots | | `geom.line()` | A path connecting observations | Line charts, trends | | `geom.area()` | A filled band under a line | Area charts | | `geom.bar()` | A rectangle (or arc, in polar) per observation | Columns, bars, pie, donut | | `geom.rule()` | A single reference line across the panel | Goal lines, thresholds | ## Position modes When a geom maps `color` (or `group`), multiple observations share an x value. The layer's `position` decides how they arrange: | Position | Effect | | ------------ | ------------------------------------------------------------------------ | | `'identity'` | Draw at the raw value; observations may overlap (default for most geoms) | | `'stack'` | Stack observations on top of each other | | `'fill'` | Stack, then normalise each stack to 100% | | `'dodge'` | Place observations side by side within the band | ```tsx theme={null} geom.bar({ position: 'stack' }); // stacked columns geom.bar({ position: 'dodge' }); // grouped columns ``` ## Coordinate systems | Coord | Effect | | ------------------------------------- | --------------------------------------------------------------- | | `coord.cartesian()` | Standard x–y plane (the default) | | `coord.flip()` | Swap the axes — turns columns into horizontal bars | | `coord.polar({ theta, innerRadius })` | Bend the plane into a circle — turns bars into pie/donut wedges | ## Common recipes Reach for these as starting points. Each is a `pipe(createSpec(...), ...)` chain; the scales are elided for brevity but always required for mapped position aesthetics. | Chart | Recipe | | ----------------------- | ------------------------------------------------------------------------------------ | | **Line** | `geom.line()` | | **Smooth line** | `geom.line({ params: { interpolate: 'catmull-rom' } })` | | **Area** | `geom.area()` | | **Stacked area** | `geom.area({ position: 'stack' })`, `color` mapped | | **Column** (vertical) | `geom.bar()` + `scale.x.discrete()` | | **Bar** (horizontal) | `geom.bar()` + `coord.flip()` | | **Stacked column** | `geom.bar({ position: 'stack' })`, `color` mapped | | **100% stacked column** | `geom.bar({ position: 'fill' })`, `color` mapped | | **Grouped column** | `geom.bar({ position: 'dodge' })`, `color` mapped | | **Pie** | `geom.bar({ position: 'stack' })` + `coord.polar({ theta: 'y' })` | | **Donut** | `geom.bar({ position: 'stack' })` + `coord.polar({ theta: 'y', innerRadius: 0.5 })` | | **Radar** | `geom.line()` + `coord.polar({ theta: 'x' })` + `scale.x.discrete()` | | **Rose / coxcomb** | `geom.bar()` + `coord.polar({ theta: 'x' })` + `scale.x.discrete()` | | **Radial bar** | `geom.bar({ position: 'stack' })` + `coord.polar({ theta: 'y', innerRadius: 0.15 })` | | **Scatter** | `geom.point()` + `scale.x.continuous()` + `scale.y.continuous()` | | **Bubble** | `geom.point()` + `size` mapped + `scale.size.continuous()` | | **Combo** | `geom.bar()` + `geom.line()` layered, the line on `ySecondary` | ## Layers A geom is a **layer**. Add several to one spec and they draw over each other, sharing the spec-level mapping unless a layer overrides it. This is how combos and decorated lines are built: ```tsx theme={null} const spec = pipe( createSpec({ x: 'month', y: 'revenue' }), geom.line(), // the trend geom.point(), // a dot on every vertex scale.x(), scale.y() ); ``` Layers are drawn in the order you pipe them — later geoms sit on top. ## Guides for each type The pages in this section walk through a specific chart type end to end — data shape, spec, and the options that matter most. * [Line](/sdk-next/graph-types/line) — trends over a continuous or ordered x-axis * [Bar & column](/sdk-next/graph-types/bar) — vertical columns for comparing categories, horizontal bars for ranking * [Area](/sdk-next/graph-types/area) — filled trends, stacked or plain * [Pie & donut](/sdk-next/graph-types/pie) — parts of a whole * [Radar & radial](/sdk-next/graph-types/radial) — categories compared around a circle * [Scatter & bubble](/sdk-next/graph-types/scatter) — correlation and three-variable plots * [Combo](/sdk-next/graph-types/combo) — two metrics on dual axes Don't see the chart you need? Because charts are composed from these ingredients, most are a small tweak to a recipe above — swap the geom, add `coord.flip()`, or change the `position`. # Line Source: https://docs.graphy.dev/sdk-next/graph-types/line Line charts connect observations with a path. They're ideal for showing trends and change over an ordered or continuous x-axis. In the Graphy SDK, a line chart is a `geom.line()` layer over `x`/`y` position scales. ## Basic example ```tsx theme={null} import { useMemo } from 'react'; import { createSpec, pipe, geom, scale } from '@graphysdk/viz-engine'; import { GraphProvider, GraphRenderer } from '@graphysdk/react-renderer'; const data = { columns: [ { key: 'month', label: 'Month' }, { key: 'revenue', label: 'Revenue' }, ], rows: [ { month: 'Jan', revenue: 1200 }, { month: 'Feb', revenue: 1800 }, { month: 'Mar', revenue: 2400 }, { month: 'Apr', revenue: 1600 }, { month: 'May', revenue: 3200 }, { month: 'Jun', revenue: 2800 }, ], }; export function RevenueChart() { const spec = useMemo(() => pipe( createSpec({ x: 'month', y: 'revenue' }), geom.line(), scale.x(), scale.y(), ), []); return ( ); } ``` `scale.x()` infers a **datetime** scale here — Graphy recognizes the abbreviated month names (`Jan`, `Feb`, …) as dates — while `scale.y()` infers a continuous scale from the numeric `revenue`. To treat the months as plain, evenly-spaced categories instead, use `scale.x.discrete()`. ## Multiple series Map a categorical column to `color` to split the data into one line per category, and add a color scale: ```tsx theme={null} const data = { columns: [ { key: 'month', label: 'Month' }, { key: 'region', label: 'Region' }, { key: 'sales', label: 'Sales' }, ], rows: [ { month: 'Jan', region: 'North', sales: 600 }, { month: 'Jan', region: 'South', sales: 900 }, { month: 'Jan', region: 'East', sales: 1400 }, { month: 'Jan', region: 'West', sales: 1800 }, { month: 'Jan', region: 'Central', sales: 2400 }, // ... ], }; const spec = pipe( createSpec({ x: 'month', y: 'sales', color: 'region' }), geom.line(), scale.x(), scale.y(), scale.color.palette() ); ``` `scale.color.palette()` draws series colors from Graphy's default palette. To pin specific colors, use `scale.color.discrete({ domain: ['North', 'South'], range: ['#4C6EF5', '#F76707'] })`. ## Smoothing The `interpolate` param sets the curve family. The default `'linear'` draws straight segments; `'catmull-rom'` draws a smooth curve through every point: ```tsx theme={null} const spec = pipe( createSpec({ x: 'month', y: 'revenue' }), geom.line({ params: { interpolate: 'catmull-rom' } }), scale.x(), scale.y() ); ``` ## Area fill Declare a `fillAlpha` style default to draw a gradient beneath the line, fading from the series color to transparent at the baseline: ```tsx theme={null} styles({ defaults: [style.geom.line({ fillAlpha: 0.15 })] }); ``` For a solid filled band rather than a fading gradient, use [`geom.area()`](/sdk-next/graph-types/index#geoms) instead. ## Missing values Use `null` for a missing cell. `missingValues` controls how the path treats the gap: ```tsx theme={null} const data = { columns: [ { key: 'month', label: 'Month' }, { key: 'revenue', label: 'Revenue' }, ], rows: [ { month: 'Jan', revenue: 1200 }, { month: 'Feb', revenue: 1800 }, { month: 'Mar', revenue: null }, { month: 'Apr', revenue: null }, { month: 'May', revenue: 3200 }, { month: 'Jun', revenue: 2800 }, ], }; const spec = pipe( createSpec({ x: 'month', y: 'revenue' }), geom.line({ params: { missingValues: 'connect' } }), scale.x(), scale.y() ); ``` ## Points on the line Add a `geom.point()` layer to mark every vertex. It shares the spec-level mapping, so both layers plot the same series. Pipe it after the line so the dots sit on top, and set `interactive: false` on the point layer: ```tsx theme={null} const spec = pipe( createSpec({ x: 'month', y: 'revenue' }), geom.line(), geom.point({ interactive: false }), scale.x(), scale.y() ); ``` `interactive: false` keeps the points out of hit-detection so the line owns hover — it resolves the nearest point along the x-axis and drives the tooltip. Interactive points would make hover fire only when the cursor lands directly on a dot. ## Line params reference Curve family. `'catmull-rom'` smooths the line through every point. How the path handles `null` values: break at the gap (`'gap'`), span it (`'connect'`), or treat as zero (`'zero'`). Paint — stroke width, dash, opacity, and the fill wash — is styled through the stylesheet: `style.geom.line({ strokeWidth, lineType, alpha, fillAlpha })`. ## Related * [Chart types overview](/sdk-next/graph-types/index) — the full recipe list * [Axes](/sdk-next/config/axes) — labels, ticks, grid and baseline * [Statistics](/sdk-next/advanced/statistics) — add a `smooth` trendline * [Data structure](/sdk-next/data-structure) — how columns feed the mapping # Pie & donut Source: https://docs.graphy.dev/sdk-next/graph-types/pie A pie chart is a single stacked-fill bar bent around a circle: `geom.bar({ position: 'fill' })` under `coord.polar({ theta: 'y' })`. Each slice's angle is proportional to its value. Add an `innerRadius` and it becomes a donut — a pie with the centre cut out. Both work best for a small number of parts of a whole. ## Pie Map the slice category to `color`, the value to `y`, and leave `x` empty so all slices share one band: ```tsx theme={null} import { useMemo } from 'react'; import { createSpec, pipe, geom, scale, coord } from '@graphysdk/viz-engine'; import { GraphProvider, GraphRenderer } from '@graphysdk/react-renderer'; const data = { columns: [ { key: 'department', label: 'Department' }, { key: 'spend', label: 'Spend' }, ], rows: [ { department: 'Engineering', spend: 420 }, { department: 'Marketing', spend: 180 }, { department: 'Sales', spend: 150 }, { department: 'Operations', spend: 95 }, { department: 'HR', spend: 80 }, { department: 'Legal', spend: 75 }, ], }; export function BudgetBreakdown() { const spec = useMemo( () => pipe( createSpec({ x: '', y: 'spend', color: 'department' }), geom.bar({ position: 'fill' }), coord.polar({ theta: 'y' }), scale.x(), scale.y(), scale.color.palette(), ), [], ); return ( ); } ``` ## Donut A donut is the same recipe with an `innerRadius` on the polar coord. The hole leaves room for a [headline number](/sdk-next/config/headline-numbers), and the thinner ring can read more cleanly than a full pie: ```tsx theme={null} const data = { columns: [ { key: 'browser', label: 'Browser' }, { key: 'share', label: 'Share' }, ], rows: [ { browser: 'Chrome', share: 65 }, { browser: 'Safari', share: 18 }, { browser: 'Firefox', share: 7 }, { browser: 'Edge', share: 5 }, { browser: 'Other', share: 5 }, ], }; const spec = pipe( createSpec({ x: '', y: 'share', color: 'browser' }), geom.bar({ position: 'fill' }), coord.polar({ theta: 'y', innerRadius: 0.55 }), scale.x(), scale.y(), scale.color.palette(), ); ``` `innerRadius` is a fraction of the outer radius — `0.55` cuts the hole just past the halfway point. Larger values give a thinner ring. ## Slice labels Turn on [data labels](/sdk-next/config/data-labels) to print each slice's value or share. On polar bars, `showCategoryLabels` prefixes the category: ```tsx theme={null} geom.bar({ position: 'fill', dataLabels: { showDataLabels: true, format: 'percentage', // 'absolute' for raw values showCategoryLabels: true, // "North · 35%" }, }); ``` ## Separated wedges A border in the chart's background color leaves a crisp gap between wedges: ```tsx theme={null} geom.bar({ position: 'fill', params: { borderColor: '#ffffff', borderWidth: 2 }, }); ``` ## Related * [Headline numbers](/sdk-next/config/headline-numbers) — a metric in the donut's hole * [Radar & radial](/sdk-next/graph-types/radial) — the `theta: 'x'` polar charts * [Coordinate systems](/sdk-next/concepts/coordinate-systems) — the polar plane, `theta`, and `innerRadius` # Radar & radial Source: https://docs.graphy.dev/sdk-next/graph-types/radial Radial charts bend the plane into a circle with `coord.polar`, just like [pie & donut](/sdk-next/graph-types/pie) — but here the *category* sweeps around the circle instead of the value. With `theta: 'x'`, each category takes a spoke and its value grows outward along the radius. The geom then sets the shape: `geom.line()` traces a radar, `geom.area()` fills it, `geom.bar()` draws a rose. With `theta: 'y'`, the value sweeps the angle instead, and each category becomes its own concentric track — a radial bar. They read best for a small, fixed set of categories compared across a few series — a skills profile, a weekly cycle, activity by hour. ## Radar A radar (or spider) chart is a `geom.line()` under `coord.polar({ theta: 'x' })`. Map the axis category to `x`, the value to `y`, and a series column to `color`. Two scale settings keep it readable: a discrete `x` scale gives each category an evenly-spaced spoke, and `zero: true` on `y` anchors the centre at zero so the radius stays proportional to the value. A non-interactive `geom.point()` layer marks each vertex: ```tsx theme={null} import { useMemo } from 'react'; import { createSpec, pipe, geom, scale, coord } from '@graphysdk/viz-engine'; import { GraphProvider, GraphRenderer } from '@graphysdk/react-renderer'; const data = { columns: [ { key: 'skill', label: 'Skill' }, { key: 'player', label: 'Player' }, { key: 'score', label: 'Score' }, ], rows: [ { skill: 'Speed', player: 'Alice', score: 8 }, { skill: 'Power', player: 'Alice', score: 6 }, { skill: 'Defense', player: 'Alice', score: 7 }, // ... { skill: 'Speed', player: 'Bob', score: 6 }, { skill: 'Power', player: 'Bob', score: 9 }, { skill: 'Defense', player: 'Bob', score: 5 }, // ... ], }; export function SkillsRadar() { const spec = useMemo( () => pipe( createSpec({ x: 'skill', y: 'score', color: 'player' }), geom.line(), geom.point({ interactive: false }), coord.polar({ theta: 'x' }), scale.x.discrete(), scale.y({ zero: true }), scale.color.palette(), ), [], ); return ( ); } ``` ## Filled radar Swap the line for `geom.area({ position: 'identity' })` to fill each series polygon. Keep `position: 'identity'` so the areas overlap at their true values rather than stacking; the vertex points still sit on top: ```tsx theme={null} const spec = pipe( createSpec({ x: 'skill', y: 'score', color: 'player' }), geom.area({ position: 'identity' }), geom.point({ interactive: false }), coord.polar({ theta: 'x' }), scale.x.discrete(), scale.y({ zero: true }), scale.color.palette(), ); ``` With more than two or three series the fills obscure each other — reach for the outline radar above, or lower the fill opacity through [appearance](/sdk-next/config/appearance). ## Rose / coxcomb A rose (or coxcomb) chart keeps `theta: 'x'` but draws bars instead of a line: each category is an angular wedge and the value sets how far the wedge reaches. Map a series to `color` and pick a [position](/sdk-next/graph-types/index#position-modes) — `dodge` splits each wedge into side-by-side petals, `stack` grows the series outward along the radius: ```tsx theme={null} const spec = pipe( createSpec({ x: 'day', y: 'signups', color: 'channel' }), geom.bar({ position: 'dodge' }), // 'stack' to stack the series along the radius coord.polar({ theta: 'x' }), scale.x.discrete(), scale.y({ zero: true }), scale.color.palette(), ); ``` ## Radial bar A radial bar (or race-track) chart flips the angle back to the value with `theta: 'y'`: each category becomes its own concentric ring and the value sweeps around it. An `innerRadius` opens up the centre so the innermost track isn't crushed into a point. Stack a series to read each ring as consecutive coloured segments: ```tsx theme={null} const spec = pipe( createSpec({ x: 'day', y: 'signups', color: 'channel' }), geom.bar({ position: 'stack' }), coord.polar({ theta: 'y', innerRadius: 0.15 }), scale.x.discrete(), scale.y({ zero: true }), scale.color.palette(), ); ``` ## Related * [Coordinate systems](/sdk-next/concepts/coordinate-systems) — the polar plane, `theta` and `innerRadius` * [Pie & donut](/sdk-next/graph-types/pie) — the `theta: 'y'` parts-of-a-whole family * [Chart types overview](/sdk-next/graph-types/index) — geoms, positions and the full recipe list # Scatter & bubble Source: https://docs.graphy.dev/sdk-next/graph-types/scatter A scatter plot draws a `geom.point()` per observation against two continuous axes — the tool for correlation and distribution. Add a `size` mapping and it becomes a bubble chart, encoding a third variable. ## Scatter Map two numeric columns to `x` and `y`, and declare continuous scales: ```tsx theme={null} import { useMemo } from 'react'; import { createSpec, pipe, geom, scale } from '@graphysdk/viz-engine'; import { GraphProvider, GraphRenderer } from '@graphysdk/react-renderer'; const data = { columns: [ { key: 'weight', label: 'Weight' }, { key: 'height', label: 'Height' }, ], rows: [ { weight: 60, height: 160 }, { weight: 65, height: 165 }, { weight: 70, height: 175 }, { weight: 75, height: 170 }, { weight: 80, height: 180 }, { weight: 85, height: 178 }, { weight: 90, height: 185 }, ], }; export function WeightVsHeight() { const spec = useMemo( () => pipe( createSpec({ x: 'weight', y: 'height' }), geom.point(), scale.x.continuous(), scale.y.continuous() ), [] ); return ( ); } ``` ## Coloring by category Map a category to `color` to distinguish groups of points: ```tsx theme={null} const spec = pipe( createSpec({ x: 'weight', y: 'height', color: 'gender' }), geom.point(), scale.x.continuous(), scale.y.continuous(), scale.color.palette() ); ``` ## Bubble Map a third numeric column to `size` and add a size scale. The default size scale uses a square-root transform, so bubble **area** is proportional to the value: ```tsx theme={null} const spec = pipe( createSpec({ x: 'gdp', y: 'lifeExpectancy', size: 'population', color: 'continent', }), geom.point(), scale.x.continuous(), scale.y.continuous(), scale.size.continuous() ); ``` ## Related * [Scales](/sdk-next/concepts/scales) — continuous and size scales * [Statistics](/sdk-next/advanced/statistics) — add a `smooth` trendline over the points # Introduction Source: https://docs.graphy.dev/sdk-next/index The Graphy SDK is a charting library for React, built on a **grammar of graphics**. Rather than picking a chart from a fixed menu, you describe a chart by composing a handful of parts — which columns map to which aesthetics, what shapes to draw, and how values become positions and colors. The same parts recombine into everything from a line chart to a polar racetrack. ## Two packages The SDK is split along a clean seam: * **`@graphysdk/viz-engine`** — a framework-agnostic engine. You author a **spec** (a small, immutable description of a chart) and the engine compiles it, resolving scales, statistics, positions and guides. No DOM, no React. * **`@graphysdk/react-renderer`** — a React renderer that paints a compiled spec to SVG. It owns layout, theming, locale-aware formatting, interactivity and animation. ```mermaid theme={null} flowchart LR A[Your data] --> C B[Spec] --> C[viz-engine
compile] C --> D[CompiledSpec] D --> E[react-renderer
paint] E --> F[SVG chart] ``` You author a spec once and hand it, with your data, to the renderer. Because the engine is framework-agnostic, the same spec can drive other renderers or run on a server — but for most apps you'll use it through React. ## Anatomy of a spec A spec is assembled from a few kinds of part, folded together with `pipe`: ```tsx theme={null} import { createSpec, pipe, geom, scale, config } from '@graphysdk/viz-engine'; const spec = pipe( createSpec({ x: 'month', y: 'revenue', color: 'product' }), // mapping — columns → aesthetics geom.line(), // geom — the shape to draw scale.x(), // scale — values → positions scale.y(), scale.color.palette(), // scale — categories → colors config({ legend: { position: 'bottom' } }) // config — chart chrome ); ``` Each part has a concept page that explains it in depth: | Part | Builder | Concept | | ----------- | ------------------------ | ----------------------------------------------------------- | | **Mapping** | `mapping` / `createSpec` | [Mappings & aesthetics](/sdk-next/concepts/mappings) | | **Geom** | `geom.*` | [Geoms & layers](/sdk-next/concepts/geoms) | | **Scale** | `scale.*` | [Scales](/sdk-next/concepts/scales) | | **Coord** | `coord.*` | [Coordinate systems](/sdk-next/concepts/coordinate-systems) | | **Config** | `config` | [Configuration](/sdk-next/config/index) | The builders are convenience — what they produce is a plain, JSON-serializable object. You can store a spec in a database and load it back to render, no builder required. See [Serializable spec](/sdk-next/concepts/serializable-spec). ## Where to start Install the packages and render your first chart. The spec pipeline and the compile → render model. The data table and how mappings reference it. Recipes for line, bar, pie, scatter and more. # Quickstart Source: https://docs.graphy.dev/sdk-next/quickstart The Graphy SDK is a grammar-of-graphics charting library, split into two packages: * **`@graphysdk/viz-engine`** — a framework-agnostic engine. You describe a chart as a **spec** — a small, immutable object built by composing mappings, geoms, scales and coordinate systems. No DOM, no React. * **`@graphysdk/react-renderer`** — a React renderer that paints a spec to SVG, and owns layout, theming, formatting and interactivity. You author a spec once and hand it, together with your data, to the renderer. ### 1. Install the packages ```shell npm theme={null} npm install @graphysdk/viz-engine @graphysdk/react-renderer ``` ```shell pnpm theme={null} pnpm add @graphysdk/viz-engine @graphysdk/react-renderer ``` ```shell yarn theme={null} yarn add @graphysdk/viz-engine @graphysdk/react-renderer ``` ```shell bun theme={null} bun add @graphysdk/viz-engine @graphysdk/react-renderer ``` Because the Graphy packages are private, configure your npm auth token. Create an `.npmrc` in your repository root (or user-level) with: ```ini .npmrc theme={null} //registry.npmjs.org/:_authToken=${NPM_TOKEN} @graphysdk:registry=https://registry.npmjs.org/ ``` Finally, import the renderer's stylesheet once, near your app's entry point: ```ts theme={null} import '@graphysdk/react-renderer/styles.css'; ``` ### 2. Render your first chart A chart needs three things: your **data**, a **spec** describing how to draw it, and the renderer to paint it. ```tsx theme={null} import { useMemo } from 'react'; import { createSpec, pipe, geom, scale } from '@graphysdk/viz-engine'; import { GraphProvider, GraphRenderer } from '@graphysdk/react-renderer'; // 1. Your data — a table of columns and rows. const data = { columns: [ { key: 'month', label: 'Month' }, { key: 'revenue', label: 'Revenue' }, ], rows: [ { month: 'Jan', revenue: 12000 }, { month: 'Feb', revenue: 15000 }, { month: 'Mar', revenue: 18000 }, { month: 'Apr', revenue: 17000 }, { month: 'May', revenue: 21000 }, ], }; export function App() { // 2. The spec — map columns to aesthetics, pick a geom, declare the scales. const spec = useMemo( () => pipe( createSpec({ x: 'month', y: 'revenue' }), geom.line(), scale.x(), scale.y() ), [] ); // 3. Render it. return ( ); } ``` ### 3. Customize the chart Everything beyond the data-to-ink mapping — titles, axes, legend, appearance — lives in `config`. Pipe a `config(...)` item onto the spec: ```tsx theme={null} import { createSpec, pipe, geom, scale, config } from '@graphysdk/viz-engine'; const spec = pipe( createSpec({ x: 'month', y: 'revenue', color: 'product' }), geom.line({ params: { interpolate: 'catmull-rom' } }), scale.x(), scale.y.continuous({ domainMin: 0, nice: true }), scale.color.palette(), config({ content: { title: 'Monthly revenue', isTitleVisible: true, subtitle: 'By product, 2026', isSubtitleVisible: true, }, axes: { y: { label: 'Revenue (£)' }, }, legend: { position: 'bottom' }, }) ); ``` ### 4. Theme and locale `GraphProvider` accepts a `theme` (`'light'` or `'dark'`) and a `formattingLocale` for number, date and currency formatting: ```tsx theme={null} ``` The `sizing` prop on `GraphRenderer` controls how the chart claims space — `responsive` fills its container, `fixed` takes explicit `width`/`height`, and `keepAspectRatio` scales to width while preserving a ratio. ### Next steps * Understand [how a chart is built](/sdk-next/concepts/how-a-chart-is-built) — the spec pipeline and the compile → render model * Learn the [data structure](/sdk-next/data-structure) and how mappings reference columns * Work through the [core concepts](/sdk-next/concepts/mappings) — mappings, geoms, scales, coords * Browse the [chart types](/sdk-next/graph-types/index) and build a [line chart](/sdk-next/graph-types/line) end to end # Formatting & locale Source: https://docs.graphy.dev/sdk-next/rendering/formatting The renderer turns raw values into display text — numbers, dates and currencies — locale-aware. The engine emits raw values; formatting happens here, at paint time. ## Two locales Two locales are in play: * **Parsing locale** — how ambiguous *input* is interpreted (is `01/02/2024` Jan 2 or Feb 1?). Set on the data via [`data._metadata.parsingLocale`](/sdk-next/data-structure#schema); defaults to `en-GB`. * **Formatting locale** — how *output* is displayed (separators, month names, currency symbols). Set with the `formattingLocale` prop on `GraphProvider`. When you don't set `formattingLocale`, display falls back to the parsing locale. ```tsx theme={null} ``` ## Supported locales `en-GB`, `en-US`, `ar`, `pt-PT`. ```tsx theme={null} import type { Locale } from '@graphysdk/viz-engine'; ``` ## Number formatting Locale governs separators and symbols; the [`numberFormat` config](/sdk-next/config/number-format) governs the rest — decimals, abbreviation, and any prefix/suffix — and can override the locale's separators when you need to: ```tsx theme={null} config({ numberFormat: { decimals: 0, abbreviation: 'auto', prefix: '$' }, }); ``` ## Currencies and dates Currency and date columns detected during [value-format inference](/sdk-next/data-structure#value-format-detection) are formatted for the active locale automatically — a `£1,250` column renders with the right symbol and grouping, a date axis with locale-appropriate month names — without extra configuration. ## Related * [Number format](/sdk-next/config/number-format) — the `numberFormat` options in full * [Data structure](/sdk-next/data-structure) — parsing locale and value formats # Overview Source: https://docs.graphy.dev/sdk-next/rendering/index `@graphysdk/react-renderer` is the React half of the SDK. It takes the [compiled spec](/sdk-next/concepts/how-a-chart-is-built#compile-then-render) and turns it into an interactive SVG chart — owning everything the framework-agnostic engine deliberately leaves out. ## What the renderer owns * **Layout** — measuring text and packing the header, legend, axes and plot into pixel rectangles. * **Formatting** — turning raw values into locale-aware numbers, dates and currencies. * **Theme** — light/dark palettes, colors, fonts and the chrome tokens. * **Interactivity** — hover, tooltips and hit-testing. * **Animation** — transitioning between compiled states. The [engine](/sdk-next/concepts/how-a-chart-is-built) owns the rest — mappings, scales, statistics, positions. ## The two components Rendering is always a `` (holds the data and spec, compiles them) wrapping a `` (paints the result): ```tsx theme={null} import { GraphProvider, GraphRenderer } from '@graphysdk/react-renderer'; import '@graphysdk/react-renderer/styles.css'; ; ``` The split exists so the compiled spec has a single owner: commands and updates operate on the provider's spec, and any renderer beneath it reflects them. [Provider & renderer](/sdk-next/rendering/provider-and-renderer) covers both in full. ## In this section The two components and their props. Responsive, fixed and aspect-ratio modes. Light/dark, token overrides and fonts. Locale-aware numbers and dates. Hover and tooltips. To replace *how* a region renders — a custom header, legend, or tooltip — see [Slots](/sdk-next/extending/slots) under Extending. # Interactivity Source: https://docs.graphy.dev/sdk-next/rendering/interactivity Charts are interactive by default. Hovering highlights the nearest observation and shows a tooltip; no configuration is needed. ```tsx theme={null} {/* hover + tooltips on by default */} ``` ## How hover works For most cartesian charts, hover is **anchored to the x-axis**: the renderer resolves the nearest position along x rather than requiring the cursor to land exactly on an observation. Everything at that x — every series in a multi-line chart, every segment in a stack — highlights together, and the tooltip lists their values in legend order. This is why a line chart responds smoothly between vertices instead of only when you touch a dot. Layers can opt out of hit-testing with `interactive: false` — useful for a decorative point overlay that shouldn't compete with the line beneath it: ```tsx theme={null} geom.point({ interactive: false, params: { size: 6 } }); ``` ## Tooltips Tooltips are on by default. Turn them off with `showTooltips`: ```tsx theme={null} ``` To change how the tooltip looks, replace it with the `Tooltip` [slot](/sdk-next/extending/slots) — you receive the same render-ready rows the default tooltip gets: ```tsx theme={null} ``` ## Animation Set `isAnimated` to transition between compiled states — for example when the data or a config value changes: ```tsx theme={null} ``` ## Related * [Slots](/sdk-next/extending/slots) — replace the tooltip or other regions * [Provider & renderer](/sdk-next/rendering/provider-and-renderer) — `showTooltips`, `isAnimated`, `mode` # Provider & renderer Source: https://docs.graphy.dev/sdk-next/rendering/provider-and-renderer Every chart is a `` wrapping a ``. The provider owns the data and spec and compiles them; the renderer paints the result. ```tsx theme={null} import { GraphProvider, GraphRenderer } from '@graphysdk/react-renderer'; ; ``` ## GraphProvider Holds the inputs and produces the compiled spec. It recompiles whenever `input`, `data` or `theme` change. The [data table](/sdk-next/data-structure) the spec draws from. The chart to compile — a [spec](/sdk-next/concepts/how-a-chart-is-built) from `createSpec`/`pipe`. The active theme. See [Theming](/sdk-next/rendering/theming). Per-token overrides layered over the base theme. See [Theming](/sdk-next/rendering/theming). Locale for formatting displayed values. See [Formatting & locale](/sdk-next/rendering/formatting). Maps font ids referenced by the spec to CSS `font-family` strings. Fires when a compile produces errors. The chart also renders an error panel in place rather than blanking. Fires with any warnings a successful compile produced. Fires when an interactive edit (in `editable` mode) mutates the spec — persist the result here. See [Commands & history](/sdk-next/advanced/commands-and-history). Filled with the graph's `dispatch`, `undo` and `redo`, for a toolbar or key handler mounted outside the provider. See [Commands & history](/sdk-next/advanced/commands-and-history). Custom geoms, stats and transforms registered for this graph. Frozen at mount. ## GraphRenderer Paints the provider's compiled spec. It must be a descendant of a ``. How the chart claims space. See [Sizing](/sdk-next/rendering/sizing). `'readonly'` displays the chart with full interactivity; `'editable'` adds inline editing of titles and labels, reported through the provider's `onChange`. Whether hover tooltips are shown. See [Interactivity](/sdk-next/rendering/interactivity). Animate transitions between compiled states. Per-region component overrides. See [Slots](/sdk-next/extending/slots). Called when the container resizes, in every sizing mode. ## Keep inputs stable The provider recompiles when `input` or `data` change **by reference**. If you build the spec or data inline in a component, memoize them so an unrelated re-render doesn't trigger a needless recompile: ```tsx theme={null} function Chart({ rows }) { const spec = useMemo( () => pipe( createSpec({ x: 'month', y: 'revenue' }), geom.line(), scale.x(), scale.y() ), [] ); const data = useMemo(() => ({ columns, rows }), [rows]); return ( ); } ``` A new `input` costs more than the recompile: it is a new baseline, so it also [clears the undo history](/sdk-next/advanced/commands-and-history#history-lifecycle). Watch the memo's dependencies as well as the memo itself — a spec knob the host drives from its own state, like a palette dropdown or a highlight-style toggle, produces a new `input` every time it moves and drops the user's undo stack with it. Drive knobs you want undoable through [commands](/sdk-next/advanced/commands-and-history) instead. ## Errors never blank the page A compile failure or a render-time throw is caught and shown as an error panel in place of the chart, so a bad spec degrades gracefully instead of taking down the surrounding UI. Use `onError` to log or surface the diagnostics yourself. ## Related * [How a chart is built](/sdk-next/concepts/how-a-chart-is-built) — the compile → render model * [Commands & history](/sdk-next/advanced/commands-and-history) — editing the spec in place, with undo/redo * [Sizing](/sdk-next/rendering/sizing) · [Theming](/sdk-next/rendering/theming) · [Slots](/sdk-next/extending/slots) # Sizing Source: https://docs.graphy.dev/sdk-next/rendering/sizing The `sizing` prop on `GraphRenderer` decides how the chart claims space in its container. It defaults to `responsive`. ```tsx theme={null} ``` ## Responsive The chart fills its parent container and re-lays out when the container resizes. Give the parent a size: ```tsx theme={null}
``` ## Fixed Explicit pixel dimensions, ignoring the container: ```tsx theme={null} ``` Useful for image export or thumbnails where the output size must be exact. ## Keep aspect ratio The chart scales to the container's width while holding a ratio — good for cards in a responsive grid. Provide an intrinsic size, or one dimension plus an `aspectRatio`: ```tsx theme={null} ``` ## Reacting to resize `onResize` fires with the new size in every mode: ```tsx theme={null} console.log(width, height)} /> ``` ## Related * [Provider & renderer](/sdk-next/rendering/provider-and-renderer) — the other renderer props * [Layout](/sdk-next/config/layout) — padding and region gaps within the chart # Theming Source: https://docs.graphy.dev/sdk-next/rendering/theming The renderer draws with a **theme** — a set of tokens for colors, fonts and chrome. Pick a base theme with the `theme` prop, then layer per-token overrides with `themeOverrides`. ```tsx theme={null} ``` ## Light and dark Selects the base token set. Drives series palettes, text colors, grid and tooltip chrome. ## Overriding tokens `themeOverrides` is a partial map of theme tokens layered over the base. Most tokens take a CSS string; the **measured font tokens** (`fontTickLabel`, `fontAxisLabel`, `fontLegendLabel`, `fontDataLabel`, and a few more) take a structured object so text measurement and paint stay in sync. ```tsx theme={null} import type { ThemeOverrides } from '@graphysdk/react-renderer'; const overrides: ThemeOverrides = { textPrimary: '#1A1A1A', textSecondary: '#8F8F8F', gridLineColor: '#E9E9E9', gridLineWidth: '1px', fontFamilyDefault: "'Inter', sans-serif", fontFamilyHeading: "'Golos Text', sans-serif", fontAxisLabel: { family: "'Inter', sans-serif", size: { value: 10.5, unit: 'px' }, weight: 500, }, }; ; ``` An omitted field keeps the base theme's value, so `{ weight: 600 }` on a font token changes only the weight. ### Font token shape A structured font override (`FontTokenOverride`) accepts any subset of: CSS `font-family`. Numeric font weight. e.g. `'normal'` or `'italic'`. Font size. Unitless multiplier; sizes HTML line boxes (canvas measurement ignores it). ## Custom fonts If your spec references fonts by id (for example in rich-text titles), map those ids to CSS `font-family` strings with `fontList`: ```tsx theme={null} ``` Make sure the fonts are actually loaded on the page (via `@font-face` or a font service) — the SDK references them, it doesn't load them. ## Background and borders Chart-level background, border ring and corner radius are part of the [spec's appearance config](/sdk-next/config/appearance), not the theme, because they travel with the chart. The theme governs the token defaults those settings fall back to. ## Related * [Appearance](/sdk-next/config/appearance) — background, border and corner radius * [Provider & renderer](/sdk-next/rendering/provider-and-renderer) — where `theme` and `themeOverrides` are set # Error handling Source: https://docs.graphy.dev/sdk/advanced/error-handling Both `GraphProvider` and `Graph` support error handling to gracefully manage failures. ## GraphProvider error handling Use the `onError` prop to track errors in data processing, configuration, or provider setup: ```tsx theme={null} import { GraphProvider, Graph } from '@graphysdk/core'; { // Log to your error tracking service console.error('Error:', error); }} > ; ``` By default, `GraphProvider` re-throws errors (allowing your app's error boundary to handle them). Provide a `fallbackComponent` to show custom UI instead: ```tsx theme={null} import type { GraphProviderErrorFallbackProps } from '@graphysdk/core'; function ErrorFallback({ error }: GraphProviderErrorFallbackProps) { return (

Something went wrong

{error.message}

); } logError(error)} fallbackComponent={ErrorFallback} > ; ``` ## Graph error handling `Graph` also accepts `onError` and `errorFallback` props for handling chart rendering errors. Unlike `GraphProvider`, `Graph` always shows a fallback UI when rendering fails. If you don't provide a custom `errorFallback`, it displays a default "Unable to render chart" message. ```tsx theme={null} import type { GraphErrorBoundaryFallbackProps } from '@graphysdk/core'; function ChartErrorFallback({ error }: GraphErrorBoundaryFallbackProps) { return (

Chart failed to load

{error.message}

); } logError(error)} errorFallback={ChartErrorFallback} /> ; ``` # Internationalization Source: https://docs.graphy.dev/sdk/advanced/i18n Graphy uses two separate locale systems: | Type | Purpose | How to set | | --------------- | -------------------------------------------- | ---------------------------------- | | **Data Locale** | Parses dates and numbers in your dataset | `data._metadata.parsingLocale` | | **UI Locale** | Renders text in the visualization and editor | `uiLocale` prop on `GraphProvider` | These are independent — you can parse data in one format while displaying the UI in another language. ## Data Locale The data locale controls how Graphy interprets dates and numbers in your dataset. | Locale | Date format | Example | | ----------------- | ----------- | ------------------------- | | `en-US` (default) | MM/DD/YYYY | `03/15/2024` → 15th March | | `en-GB` | DD/MM/YYYY | `15/03/2024` → 15th March | Set it in your data configuration: ```tsx theme={null} const config: GraphConfig = { data: { columns: [...], rows: [...], _metadata: { parsingLocale: 'en-GB' } } }; ``` ## UI Locale The UI locale controls all text displayed in the visualization and editor, including labels, tooltips, and menu items. It also determines the text direction (LTR or RTL). ### Available locales | Locale | Language | Text Direction | | ------- | ------------ | -------------- | | `en-GB` | English (UK) | LTR | | `en-US` | English (US) | LTR | | `pt-PT` | Portuguese | LTR | | `ar` | Arabic | RTL | ### Setting the UI locale ```tsx theme={null} import { GraphProvider } from '@graphy/core'; ; ``` ### Example: Different data and UI locales You might have data formatted for a UK audience but want to display the UI in Arabic: ```tsx theme={null} const config: GraphConfig = { data: { columns: [...], rows: [...], _metadata: { parsingLocale: 'en-GB' // Parse dates as DD/MM/YYYY } } }; {/* Display UI in Arabic (RTL) */} ``` ## Runtime overrides The `i18nOverrides` prop on `GraphProvider` allows you to customize translations and text direction at runtime, without switching the entire locale. ### Overriding strings Override any UI text by providing translation keys: ```tsx theme={null} ``` Some strings accept parameters. Provide a function to handle dynamic values: ```tsx theme={null} `Columna ${number}`, 'common.confirmDelete': ({ item }) => `¿Eliminar ${item}?`, }} > ``` ### Overriding text direction (RTL support) Text direction is automatically determined by the selected locale (e.g., Arabic uses RTL). You can override it manually using the `dir` property: ```tsx theme={null} ``` Valid values are `'ltr'` (left-to-right) and `'rtl'` (right-to-left). This is useful when you want to: * Force RTL layout while using an LTR locale's translations * Force LTR layout while using an RTL locale's translations * Test RTL behavior during development ### Combining overrides You can combine string overrides with text direction in a single configuration: ```tsx theme={null} ``` ## String reference ### Common | Key | Default | Parameters | | -------------------------------- | ------------------------------------- | ---------- | | `common.save` | "Save" | - | | `common.cancel` | "Cancel" | - | | `common.confirm` | "Confirm" | - | | `common.delete` | "Delete" | - | | `common.confirmDelete` | "Are you sure you want to delete \$?" | `{ item }` | | `common.abbreviations.thousands` | "k" | - | | `common.abbreviations.millions` | "m" | - | | `common.abbreviations.billions` | "b" | - | ### Graph - General | Key | Default | Parameters | | ------------------------------ | ---------------------- | ---------- | | `graph.loading` | "Loading" | - | | `graph.error` | "Something went wrong" | - | | `graph.axisLabels.placeholder` | "Add label" | - | ### Graph - Default property labels | Key | Default | Parameters | | ------------------------------------------ | ----------- | ------------ | | `graph.defaultPropertyLabels.date` | "Date" | - | | `graph.defaultPropertyLabels.year` | "Year" | - | | `graph.defaultPropertyLabels.quarter` | "Quarter" | - | | `graph.defaultPropertyLabels.month` | "Month" | - | | `graph.defaultPropertyLabels.week` | "Week" | - | | `graph.defaultPropertyLabels.series` | "Series" | - | | `graph.defaultPropertyLabels.category` | "Category" | - | | `graph.defaultPropertyLabels.columnNumber` | "Column \$" | `{ number }` | | `graph.defaultPropertyLabels.rowNumber` | "Row \$" | `{ number }` | | `graph.defaultPropertyLabels.seriesNumber` | "Series \$" | `{ number }` | ### Graph - Trend comparisons | Key | Default | Parameters | | ----------------------------------------------- | ---------------------- | ----------- | | `graph.trendComparison.fromPrevious.long` | "\$ vs previous" | `{ value }` | | `graph.trendComparison.fromPrevious.short` | "\$ vs prev" | `{ value }` | | `graph.trendComparison.fromPreviousYear.long` | "\$ vs previous year" | `{ value }` | | `graph.trendComparison.fromPreviousYear.short` | "\$ vs prev year" | `{ value }` | | `graph.trendComparison.fromPreviousMonth.long` | "\$ vs previous month" | `{ value }` | | `graph.trendComparison.fromPreviousMonth.short` | "\$ vs prev month" | `{ value }` | | `graph.trendComparison.fromPreviousWeek.long` | "\$ vs previous week" | `{ value }` | | `graph.trendComparison.fromPreviousWeek.short` | "\$ vs prev week" | `{ value }` | | `graph.trendComparison.fromPreviousDay.long` | "\$ vs previous day" | `{ value }` | | `graph.trendComparison.fromPreviousDay.short` | "\$ vs prev day" | `{ value }` | | `graph.trendComparison.fromPreviousHour.long` | "\$ vs previous hour" | `{ value }` | | `graph.trendComparison.fromPreviousHour.short` | "\$ vs prev hour" | `{ value }` | ### Graph - Headline metrics | Key | Default | Parameters | | ------------------------------------------- | -------------------------- | ----------- | | `graph.headlineMetrics.averageLong` | "Average \$" | `{ value }` | | `graph.headlineMetrics.averageShort` | "Avg. \$" | `{ value }` | | `graph.headlineMetrics.total` | "Total \$" | `{ value }` | | `graph.headlineMetrics.totalConversionRate` | "Total conversion rate \$" | `{ value }` | | `graph.headlineMetrics.current` | "\$" | `{ value }` | ### Graph - Title editor | Key | Default | | --------------------------------------- | ---------------- | | `graph.titleEditor.titlePlaceholder` | "Untitled" | | `graph.titleEditor.subtitlePlaceholder` | "Add a subtitle" | ### Graph - Text toolbar formatting | Key | Default | | ------------------------------------ | ----------- | | `graph.toolbar.formatting.bold` | "Bold" | | `graph.toolbar.formatting.italic` | "Italic" | | `graph.toolbar.formatting.underline` | "Underline" | | `graph.toolbar.formatting.link` | "Link" | ### Graph - Text toolbar link | Key | Default | | -------------------------------- | ------------------------- | | `graph.toolbar.link.ariaLabel` | "Create link" | | `graph.toolbar.link.placeholder` | "Type or paste a link..." | | `graph.toolbar.link.submit` | "Submit" | ### Graph - Text toolbar font | Key | Default | | -------------------------- | ------- | | `graph.toolbar.font.label` | "Font" | ### Graph - Text toolbar heading | Key | Default | | -------------------------------- | --------------- | | `graph.toolbar.heading.label` | "Heading level" | | `graph.toolbar.heading.text` | "Text" | | `graph.toolbar.heading.caption` | "Caption" | | `graph.toolbar.heading.heading1` | "Heading 1" | | `graph.toolbar.heading.heading2` | "Heading 2" | | `graph.toolbar.heading.heading3` | "Heading 3" | ### Graph - Text toolbar alignment | Key | Default | | -------------------------------- | -------------- | | `graph.toolbar.textAlign.left` | "Align left" | | `graph.toolbar.textAlign.center` | "Align center" | | `graph.toolbar.textAlign.right` | "Align right" | ### Graph - Text toolbar color | Key | Default | | --------------------------- | ------------ | | `graph.toolbar.color.label` | "Text color" | ### Graph - Color dropdown | Key | Default | | --------------------------------- | -------------- | | `graph.colorDropdown.colors` | "Colors" | | `graph.colorDropdown.chartColors` | "Chart colors" | | `graph.colorDropdown.custom` | "Custom" | | `graph.colorDropdown.opacity` | "Opacity" | | `graph.colorDropdown.transparent` | "Transparent" | ### Graph - Annotations base menu | Key | Default | Parameters | | ----------------------------------------------- | ------------------ | ----------- | | `graph.annotations.baseMenu.pinNumber` | "Pin number" | - | | `graph.annotations.baseMenu.annotate` | "Annotate" | - | | `graph.annotations.baseMenu.highlight` | "Highlight" | - | | `graph.annotations.baseMenu.highlightWithLabel` | "Highlight \$" | `{ label }` | | `graph.annotations.baseMenu.removeHighlight` | "Remove highlight" | - | | `graph.annotations.baseMenu.differenceArrow` | "Difference arrow" | - | | `graph.annotations.baseMenu.addSticker` | "Add sticker" | - | ### Graph - Annotations text menu | Key | Default | | ---------------------------------------- | --------------- | | `graph.annotations.textMenu.title` | "Annotate" | | `graph.annotations.textMenu.placeholder` | "Add a comment" | | `graph.annotations.textMenu.addButton` | "Add" | ### Graph - Annotations highlights | Key | Default | | --------------------------------------------------- | --------------- | | `graph.annotations.highlights.menuTitle` | "Highlight" | | `graph.annotations.highlights.labels.bar` | "Bar" | | `graph.annotations.highlights.labels.barGroup` | "Group" | | `graph.annotations.highlights.labels.barStack` | "Stack" | | `graph.annotations.highlights.labels.barSeries` | "Series" | | `graph.annotations.highlights.labels.line` | "Line" | | `graph.annotations.highlights.labels.linePoint` | "Point" | | `graph.annotations.highlights.labels.lineSeries` | "Series" | | `graph.annotations.highlights.labels.scatterPoint` | "Point" | | `graph.annotations.highlights.labels.scatterSeries` | "Series" | | `graph.annotations.highlights.labels.pieSlice` | "Slice" | | `graph.annotations.highlights.labels.pointOrBar` | "Point / bar" | | `graph.annotations.highlights.labels.lineOrSeries` | "Line / series" | ### Graph - Annotations sticker menu | Key | Default | | ------------------------------------- | ------------- | | `graph.annotations.stickerMenu.title` | "Add sticker" | ### Graph - Annotations delete | Key | Default | | -------------------------- | -------- | | `graph.annotations.delete` | "Delete" | ### Graph - Annotations arrow | Key | Default | | ---------------------------------------------- | ------------------------- | | `graph.annotations.arrow.thickness.label` | "Thickness" | | `graph.annotations.arrow.thickness.thin` | "Thin" | | `graph.annotations.arrow.thickness.medium` | "Medium" | | `graph.annotations.arrow.thickness.thick` | "Thick" | | `graph.annotations.arrow.arrowhead.startPoint` | "Start point" | | `graph.annotations.arrow.arrowhead.endPoint` | "End point" | | `graph.annotations.arrow.arrowhead.lineArrow` | "Line arrow" | | `graph.annotations.arrow.arrowhead.none` | "None" | | `graph.annotations.arrow.lineStyle.label` | "Line style" | | `graph.annotations.arrow.lineStyle.solid` | "Solid" | | `graph.annotations.arrow.lineStyle.dotted` | "Dotted" | | `graph.annotations.arrow.stickerEffect` | "Sticker effect" | | `graph.annotations.arrow.toolbarAriaLabel` | "Free-form arrow toolbar" | | `graph.annotations.arrow.editorAriaLabel` | "Arrow editor" | ### Graph - Annotations difference arrow | Key | Default | Parameters | | --------------------------------------------------------- | -------------------------- | ------------------------ | | `graph.annotations.differenceArrow.type.proportion` | "Proportion" | - | | `graph.annotations.differenceArrow.type.difference` | "Difference (\$)" | `{ absoluteDifference }` | | `graph.annotations.differenceArrow.type.increase` | "Increase (\$)" | `{ absoluteDifference }` | | `graph.annotations.differenceArrow.type.decrease` | "Decrease (\$)" | `{ absoluteDifference }` | | `graph.annotations.differenceArrow.type.percentageChange` | "Percentage change (\$)" | `{ percentageChange }` | | `graph.annotations.differenceArrow.type.percentIncrease` | "Percent increase (\$)" | `{ percentageChange }` | | `graph.annotations.differenceArrow.type.percentDecrease` | "Percent decrease (\$)" | `{ percentageChange }` | | `graph.annotations.differenceArrow.size.label` | "Size" | - | | `graph.annotations.differenceArrow.size.small` | "Small" | - | | `graph.annotations.differenceArrow.size.medium` | "Medium" | - | | `graph.annotations.differenceArrow.size.large` | "Large" | - | | `graph.annotations.differenceArrow.flipArrow` | "Flip arrow" | - | | `graph.annotations.differenceArrow.color` | "Color" | - | | `graph.annotations.differenceArrow.toolbarAriaLabel` | "Difference arrow toolbar" | - | | `graph.annotations.differenceArrow.editorAriaLabel` | "Difference arrow editor" | - | ### Graph - Annotations shape | Key | Default | Parameters | | --------------------------------------- | ------------- | ----------- | | `graph.annotations.shape.color` | "Color" | - | | `graph.annotations.shape.border.label` | "Border" | - | | `graph.annotations.shape.border.none` | "None" | - | | `graph.annotations.shape.border.thin` | "Thin (\$)" | `{ value }` | | `graph.annotations.shape.border.medium` | "Medium (\$)" | `{ value }` | | `graph.annotations.shape.border.thick` | "Thick (\$)" | `{ value }` | ### Editor - General | Key | Default | | -------------------- | ------------------------ | | `editor.title` | "Graphy Editor" | | `editor.description` | "Edit your content here" | ### Editor - Graph types | Key | Default | | ------------------------------------- | -------------- | | `editor.graphTypes.column` | "Column" | | `editor.graphTypes.columnStacked` | "Stacked" | | `editor.graphTypes.columnStackedFill` | "100% Stacked" | | `editor.graphTypes.bar` | "Bar" | | `editor.graphTypes.barStacked` | "Stacked" | | `editor.graphTypes.barStackedFill` | "100% Stacked" | | `editor.graphTypes.line` | "Line" | | `editor.graphTypes.areaStacked` | "Stacked Area" | | `editor.graphTypes.pie` | "Pie" | | `editor.graphTypes.donut` | "Donut" | | `editor.graphTypes.scatter` | "Scatter" | | `editor.graphTypes.bubble` | "Bubble" | | `editor.graphTypes.funnel` | "Funnel" | | `editor.graphTypes.combo` | "Combo" | | `editor.graphTypes.heatmap` | "Heatmap" | | `editor.graphTypes.waterfall` | "Waterfall" | | `editor.graphTypes.mekko` | "Mekko" | | `editor.graphTypes.table` | "Table" | ### Editor - Size panel | Key | Default | Parameters | | ------------------------------------------ | ---------------------------- | ---------- | | `editor.sizePanel.toolbarButton` | "Size" | - | | `editor.sizePanel.presetsSection.title` | "Presets" | - | | `editor.sizePanel.customSizeSection.title` | "Custom size" | - | | `editor.sizePanel.presets.googleSlides` | "Google Slides / PowerPoint" | - | | `editor.sizePanel.presets.webEmail` | "Web / email" | - | | `editor.sizePanel.presets.linkedIn` | "LinkedIn post" | - | | `editor.sizePanel.presets.instagram` | "Instagram post" | - | | `editor.sizePanel.presets.tiktok` | "TikTok / Instagram story" | - | | `editor.sizePanel.presets.twitter` | "X (Twitter)" | - | | `editor.sizePanel.presets.mobile` | "Mobile" | - | | `editor.sizePanel.inputs.unitLabel` | "px" | - | | `editor.sizePanel.validation.maxSizeError` | "≤ \$px" | `{ max }` | | `editor.sizePanel.validation.minSizeError` | "≥ \$px" | `{ min }` | ### Editor - Graph panel | Key | Default | | --------------------------------------------------- | ------------------ | | `editor.graphPanel.toolbarButton` | "Graph" | | `editor.graphPanel.graphTypeSection.title` | "Graph type" | | `editor.graphPanel.graphOptionsSection.title` | "Graph options" | | `editor.graphPanel.graphOptions.sortBars` | "Sort high → low" | | `editor.graphPanel.graphOptions.gridLines` | "Grid lines" | | `editor.graphPanel.graphOptions.showPoints` | "Show points" | | `editor.graphPanel.graphOptions.smoothLines` | "Smooth lines" | | `editor.graphPanel.graphOptions.stackTotals` | "Stack totals" | | `editor.graphPanel.graphOptions.dataLabels` | "Data labels" | | `editor.graphPanel.graphOptions.showPercentages` | "Show percentages" | | `editor.graphPanel.graphOptions.categoryLabels` | "Category labels" | | `editor.graphPanel.legendSection.title` | "Legend" | | `editor.graphPanel.legendSection.right` | "Right" | | `editor.graphPanel.legendSection.top` | "Top" | | `editor.graphPanel.legendSection.none` | "None" | | `editor.graphPanel.numberFormatSection.title` | "Number format" | | `editor.graphPanel.numberFormat.abbreviationLabel` | "Abbreviation" | | `editor.graphPanel.numberFormat.valueLabel` | "Value" | | `editor.graphPanel.numberFormat.decimalPlacesLabel` | "Decimal places" | | `editor.graphPanel.numberFormat.options.auto` | "Auto" | | `editor.graphPanel.numberFormat.options.custom` | "Custom" | | `editor.graphPanel.numberFormat.options.none` | "None" | | `editor.graphPanel.headlineNumberSize.sizeLabel` | "Size" | | `editor.graphPanel.headlineNumberSize.valueLabel` | "Value" | | `editor.graphPanel.headlineNumberSize.sizes.small` | "S" | | `editor.graphPanel.headlineNumberSize.sizes.medium` | "M" | | `editor.graphPanel.headlineNumberSize.sizes.large` | "L" | | `editor.graphPanel.lineThickness.label` | "Line thickness" | | `editor.graphPanel.pointSize.label` | "Point size" | | `editor.graphPanel.pointSize.options.auto` | "Auto" | | `editor.graphPanel.pointSize.options.custom` | "Custom" | ### Editor - Headline number section | Key | Default | | ----------------------------------------------------------------- | ----------------- | | `editor.graphPanel.headlineNumberSection.title` | "Headline number" | | `editor.graphPanel.headlineNumberSection.toggle` | "Visible" | | `editor.graphPanel.headlineNumberSection.metricLabel` | "Metric" | | `editor.graphPanel.headlineNumberSection.compareWithLabel` | "Compare with" | | `editor.graphPanel.headlineNumberSection.metrics.total` | "Total" | | `editor.graphPanel.headlineNumberSection.metrics.average` | "Avg." | | `editor.graphPanel.headlineNumberSection.metrics.current` | "Last" | | `editor.graphPanel.headlineNumberSection.metrics.conversion` | "Conversion" | | `editor.graphPanel.headlineNumberSection.metrics.left` | "Left" | | `editor.graphPanel.headlineNumberSection.comparison.first` | "First" | | `editor.graphPanel.headlineNumberSection.comparison.previous` | "Previous" | | `editor.graphPanel.headlineNumberSection.pieTotalPosition.left` | "Left" | | `editor.graphPanel.headlineNumberSection.pieTotalPosition.center` | "Center" | ### Editor - Treat empty values | Key | Default | | ------------------------------------------------------------ | ----------------------------------------------------------- | | `editor.graphPanel.treatEmptyValues.leaveGap.label` | "Leave gap in chart" | | `editor.graphPanel.treatEmptyValues.leaveGap.description` | "Leaves a visible break in the line where data is missing" | | `editor.graphPanel.treatEmptyValues.fillZero.label` | "Fill with zero" | | `editor.graphPanel.treatEmptyValues.fillZero.description` | "Displays missing data points as zero" | | `editor.graphPanel.treatEmptyValues.connectGaps.label` | "Connect across gaps" | | `editor.graphPanel.treatEmptyValues.connectGaps.description` | "Joins the line between available points, skipping the gap" | ### Editor - Combo chart appearance | Key | Default | | ---------------------------------------------------- | ------------------------ | | `editor.graphPanel.comboChartAppearance.title` | "Combo chart appearance" | | `editor.graphPanel.comboChartAppearance.groupedBars` | "Grouped bars" | | `editor.graphPanel.comboChartAppearance.stackedBars` | "Stacked bars" | | `editor.graphPanel.comboChartAppearance.linesOnly` | "Lines only" | ### Editor - Column mapping | Key | Default | | ----------------------------------- | ----------------- | | `editor.columnMapping.title` | "Data" | | `editor.columnMapping.xAxis` | "X-axis" | | `editor.columnMapping.yAxis` | "Y-axis" | | `editor.columnMapping.leftYAxis` | "Y-axis (left)" | | `editor.columnMapping.rightYAxis` | "Y-axis (right)" | | `editor.columnMapping.slices` | "Values (slices)" | | `editor.columnMapping.labels` | "Labels" | | `editor.columnMapping.size` | "Size" | | `editor.columnMapping.shape` | "Shape" | | `editor.columnMapping.reset` | "Reset to auto" | | `editor.columnMapping.addSeries` | "Add series" | | `editor.columnMapping.removeSeries` | "Remove series" | ### Editor - Axes panel | Key | Default | | ----------------------------------------- | ---------------- | | `editor.axesPanel.toolbarButton` | "Axes" | | `editor.axesPanel.mainAxisSection.yAxis` | "Y-axis" | | `editor.axesPanel.mainAxisSection.xAxis` | "X-axis" | | `editor.axesPanel.crossAxisSection.xAxis` | "X-axis" | | `editor.axesPanel.crossAxisSection.yAxis` | "Y-axis" | | `editor.axesPanel.controls.visible` | "Visible" | | `editor.axesPanel.controls.labels` | "Labels" | | `editor.axesPanel.controls.position` | "Position" | | `editor.axesPanel.controls.scale` | "Scale" | | `editor.axesPanel.controls.startFrom` | "Start from" | | `editor.axesPanel.controls.endAt` | "End at" | | `editor.axesPanel.controls.value` | "Value" | | `editor.axesPanel.controls.numberOfAxes` | "Number of axes" | | `editor.axesPanel.controls.reverse` | "Reverse" | | `editor.axesPanel.labelMode.auto` | "Auto" | | `editor.axesPanel.labelMode.edges` | "Edges" | | `editor.axesPanel.scale.auto` | "Auto" | | `editor.axesPanel.scale.log` | "Log" | | `editor.axesPanel.startFrom.auto` | "Auto" | | `editor.axesPanel.startFrom.zero` | "Zero" | | `editor.axesPanel.startFrom.custom` | "Custom" | | `editor.axesPanel.endAt.auto` | "Auto" | | `editor.axesPanel.endAt.custom` | "Custom" | | `editor.axesPanel.axisCount.single` | "Single" | | `editor.axesPanel.axisCount.double` | "Double" | | `editor.axesPanel.position.left` | "Left" | | `editor.axesPanel.position.right` | "Right" | | `editor.axesPanel.position.top` | "Top" | | `editor.axesPanel.position.bottom` | "Bottom" | | `editor.axesPanel.yesNo.yes` | "Yes" | | `editor.axesPanel.yesNo.no` | "No" | ### Editor - Color panel | Key | Default | | ------------------------------------------------ | ---------------- | | `editor.colorPanel.toolbarButton` | "Color" | | `editor.colorPanel.themeSection.title` | "Theme" | | `editor.colorPanel.paletteSection.title` | "Palette" | | `editor.colorPanel.paletteSection.colorScheme` | "Color scheme" | | `editor.colorPanel.paletteSection.colors` | "Colors" | | `editor.colorPanel.paletteSection.colorPalettes` | "Color palettes" | | `editor.colorPanel.paletteMode.preset` | "Preset" | | `editor.colorPanel.paletteMode.brand` | "Brand" | | `editor.colorPanel.paletteMode.freestyle` | "Freestyle" | | `editor.colorPanel.paletteThemes.colorful` | "Colorful" | | `editor.colorPanel.paletteThemes.pastel` | "Pastel" | | `editor.colorPanel.paletteThemes.neon` | "Neon" | | `editor.colorPanel.backgroundSection.title` | "Background" | | `editor.colorPanel.backgroundSection.black` | "Black" | | `editor.colorPanel.backgroundSection.white` | "White" | | `editor.colorPanel.backgroundSection.grey` | "Grey" | | `editor.colorPanel.backgroundSection.tint` | "Tint" | | `editor.colorPanel.backgroundSection.custom` | "Custom" | | `editor.colorPanel.backgroundSection.none` | "None" | | `editor.colorPanel.borderSection.title` | "Border" | | `editor.colorPanel.borderSection.borderColor` | "Border color" | | `editor.colorPanel.borderSection.thickness` | "Thickness" | | `editor.colorPanel.borderSection.cornerRadius` | "Corner radius" | | `editor.colorPanel.borderType.solid` | "Solid" | | `editor.colorPanel.borderType.gradient` | "Gradient" | | `editor.colorPanel.borderType.grey` | "Grey" | | `editor.colorPanel.borderType.preset` | "Preset" | | `editor.colorPanel.borderType.custom` | "Custom" | | `editor.colorPanel.borderType.none` | "None" | | `editor.colorPanel.presetGradients.lilac` | "Lilac" | | `editor.colorPanel.presetGradients.neonPink` | "Neon Pink" | | `editor.colorPanel.presetGradients.blackberry` | "Blackberry" | | `editor.colorPanel.presetGradients.sun` | "Sun" | | `editor.colorPanel.presetGradients.iceland` | "Iceland" | | `editor.colorPanel.presetGradients.sunset` | "Sunset" | | `editor.colorPanel.presetGradients.ultraviolet` | "Ultraviolet" | | `editor.colorPanel.presetGradients.purple` | "Purple" | | `editor.colorPanel.presetGradients.iceCream` | "Ice Cream" | | `editor.colorPanel.presetGradients.mint` | "Mint" | | `editor.colorPanel.presetGradients.cool` | "Cool" | | `editor.colorPanel.presetGradients.fresh` | "Fresh" | ### Editor - Design panel | Key | Default | | ------------------------------------ | --------- | | `editor.designPanel.toolbarButton` | "Design" | | `editor.designPanel.defaultExpanded` | "Palette" | ### Editor - Annotate panel | Key | Default | | ------------------------------------------------------ | ------------------- | | `editor.annotatePanel.toolbarButton` | "Annotate" | | `editor.annotatePanel.callOutSection.title` | "Call-out" | | `editor.annotatePanel.callOutSection.text` | "Text" | | `editor.annotatePanel.callOutSection.arrow` | "Arrow" | | `editor.annotatePanel.callOutSection.box` | "Box" | | `editor.annotatePanel.callOutSection.differenceArrows` | "Difference arrows" | | `editor.annotatePanel.highlightSection.title` | "Highlight" | | `editor.annotatePanel.highlightSection.button` | "Highlight" | | `editor.annotatePanel.highlightSection.colorLabel` | "Highlight color" | ### Editor - Annotations panel | Key | Default | | ------------------------------------------------------------- | ---------------------------------- | | `editor.annotationsPanel.toolbarButton` | "Annotate" | | `editor.annotationsPanel.freeformSection.title` | "Freeform" | | `editor.annotationsPanel.freeformSection.text` | "Text" | | `editor.annotationsPanel.freeformSection.arrow` | "Arrow" | | `editor.annotationsPanel.freeformSection.box` | "Box" | | `editor.annotationsPanel.freeformSection.difference` | "Difference" | | `editor.annotationsPanel.goalSection.title` | "Goal" | | `editor.annotationsPanel.goalSection.labelControl` | "Label" | | `editor.annotationsPanel.goalSection.labelPlaceholder` | "Goal" | | `editor.annotationsPanel.goalSection.labelAriaLabel` | "Custom goal label" | | `editor.annotationsPanel.goalSection.valueControl` | "Goal value" | | `editor.annotationsPanel.goalSection.valueAriaLabel` | "Goal value" | | `editor.annotationsPanel.goalSection.byDate` | "By date" | | `editor.annotationsPanel.goalSection.xAxisValue` | "X-axis value" | | `editor.annotationsPanel.goalSection.optional` | "(optional)" | | `editor.annotationsPanel.trendsAndAveragesSection.title` | "Trends and Averages" | | `editor.annotationsPanel.trendsAndAveragesSection.trend` | "Trend" | | `editor.annotationsPanel.trendsAndAveragesSection.average` | "Average" | | `editor.annotationsPanel.trendType.label` | "Trend type" | | `editor.annotationsPanel.trendType.placeholder` | "Trend type" | | `editor.annotationsPanel.trendType.options.linear` | "Linear" | | `editor.annotationsPanel.trendType.options.exponential` | "Exponential" | | `editor.annotationsPanel.trendType.options.quadratic` | "Quadratic" | | `editor.annotationsPanel.trendType.options.polynomial` | "Polynomial" | | `editor.annotationsPanel.trendType.options.logarithmic` | "Logarithmic" | | `editor.annotationsPanel.trendType.options.power` | "Power" | | `editor.annotationsPanel.trendType.options.loess` | "Loess" | | `editor.annotationsPanel.averageLineSeries.label` | "Average line series" | | `editor.annotationsPanel.averageLineSeries.placeholder` | "Select series" | | `editor.annotationsPanel.averageLineSeries.ariaLabel` | "Series dropdown for average line" | | `editor.annotationsPanel.highlightSection.title` | "Highlight" | | `editor.annotationsPanel.highlightSection.button` | "Highlight" | | `editor.annotationsPanel.highlightSection.fadeColorLabel` | "Fade color" | | `editor.annotationsPanel.titleAndSubtitleSection.title` | "Title & Subtitle" | | `editor.annotationsPanel.titleAndSubtitleSection.toggleTitle` | "Title" | | `editor.annotationsPanel.titleAndSubtitleSection.subtitle` | "Subtitle" | | `editor.annotationsPanel.captionAndSourceSection.title` | "Caption & Source" | | `editor.annotationsPanel.captionAndSourceSection.caption` | "Caption" | | `editor.annotationsPanel.captionAndSourceSection.source` | "Source" | | `editor.annotationsPanel.captionAndSourceSection.url` | "URL" | | `editor.annotationsPanel.captionAndSourceSection.name` | "Name" | ### Editor - Elements panel | Key | Default | Parameters | | -------------------------------------------------- | ----------- | ----------- | | `editor.elementsPanel.toolbarButton` | "Elements" | - | | `editor.elementsPanel.headerSection.title` | "Header" | - | | `editor.elementsPanel.headerSection.toggleTitle` | "Title" | - | | `editor.elementsPanel.headerSection.subtitle` | "Subtitle" | - | | `editor.elementsPanel.footerSection.title` | "Footer" | - | | `editor.elementsPanel.footerSection.caption` | "Caption" | - | | `editor.elementsPanel.footerSection.source` | "Source" | - | | `editor.elementsPanel.footerSection.url` | "URL" | - | | `editor.elementsPanel.footerSection.name` | "Name" | - | | `editor.elementsPanel.textSizeSection.title` | "Text size" | - | | `editor.elementsPanel.textSizeSection.scaleFormat` | "\$x" | `{ value }` | | `editor.elementsPanel.fontSection.title` | "Font" | - | | `editor.elementsPanel.sourceSection.title` | "Source" | - | | `editor.elementsPanel.sourceSection.url` | "URL" | - | | `editor.elementsPanel.sourceSection.name` | "Name" | - | ### Editor - Power-ups panel | Key | Default | | --------------------------------------------------------- | ---------------------------------- | | `editor.powerUpPanel.toolbarButton` | "Power-ups" | | `editor.powerUpPanel.goalSection.title` | "Goal" | | `editor.powerUpPanel.goalSection.toggle` | "Goal" | | `editor.powerUpPanel.goalSection.labelControl` | "Label" | | `editor.powerUpPanel.goalSection.labelPlaceholder` | "Goal" | | `editor.powerUpPanel.goalSection.labelAriaLabel` | "Custom goal label" | | `editor.powerUpPanel.goalSection.valueControl` | "Goal value" | | `editor.powerUpPanel.goalSection.byDate` | "By date" | | `editor.powerUpPanel.goalSection.xAxisValue` | "X-axis value" | | `editor.powerUpPanel.goalSection.optional` | " (optional)" | | `editor.powerUpPanel.goalSection.xAxisReferenceAriaLabel` | "X-axis reference value" | | `editor.powerUpPanel.goalSection.anyXAxisValue` | "Any x-axis value" | | `editor.powerUpPanel.goalSection.selectValuePlaceholder` | "Select value" | | `editor.powerUpPanel.trendSection.title` | "Trend" | | `editor.powerUpPanel.trendSection.toggle` | "Trend" | | `editor.powerUpPanel.averageSection.title` | "Average" | | `editor.powerUpPanel.averageSection.toggle` | "Average" | | `editor.powerUpPanel.averageSection.seriesLabel` | "Average line series" | | `editor.powerUpPanel.averageSection.seriesPlaceholder` | "Select series" | | `editor.powerUpPanel.averageSection.seriesAriaLabel` | "Series dropdown for average line" | | `editor.powerUpPanel.trendType.label` | "Trend type" | | `editor.powerUpPanel.trendType.placeholder` | "Trend type" | | `editor.powerUpPanel.trendType.options.linear` | "Linear" | | `editor.powerUpPanel.trendType.options.exponential` | "Exponential" | | `editor.powerUpPanel.trendType.options.quadratic` | "Quadratic" | | `editor.powerUpPanel.trendType.options.polynomial` | "Polynomial" | | `editor.powerUpPanel.trendType.options.logarithmic` | "Logarithmic" | | `editor.powerUpPanel.trendType.options.power` | "Power" | | `editor.powerUpPanel.trendType.options.loess` | "Loess" | | `editor.powerUpPanel.valueInput.percentagePlaceholder` | "Percentage" | | `editor.powerUpPanel.valueInput.numberPlaceholder` | "Number" | | `editor.powerUpPanel.valueInput.percentageSymbol` | "%" | ### Editor - Highlighting | Key | Default | | -------------------------------------------- | -------------------------------- | | `editor.highlighting.modeHelper.title` | "Highlight mode" | | `editor.highlighting.modeHelper.hover` | "Hover" | | `editor.highlighting.modeHelper.toHighlight` | "to highlight" | | `editor.highlighting.modeHelper.anyElement` | "any element" | | `editor.highlighting.modeHelper.escToExit` | "to exit" | | `editor.highlighting.modeHelper.esc` | "ESC" | | `editor.highlighting.emptyState` | "No highlight options available" | | `editor.highlighting.deleteAriaLabel` | "Delete highlight" | | `editor.highlighting.highlightStyle.tint` | "Tint" | | `editor.highlighting.highlightStyle.grey` | "Grey" | ### Editor - Fine tune panel | Key | Default | | -------------------------------------------------- | ---------------- | | `editor.fineTunePanel.toolbarButton` | "Fine tune" | | `editor.fineTunePanel.detailSection.title` | "Detail" | | `editor.fineTunePanel.detailSection.missingValues` | "Missing values" | | `editor.fineTunePanel.lineStyleSection.title` | "Line style" | | `editor.fineTunePanel.lineStyleSection.lineCurve` | "Line curve" | | `editor.fineTunePanel.lineStyleSection.sharp` | "Sharp" | | `editor.fineTunePanel.lineStyleSection.smooth` | "Smooth" | ### Editor - Custom theme editor | Key | Default | | --------------------------------------------------- | ----------------------------- | | `editor.customThemeEditor.patternDropdownAriaLabel` | "Pattern dropdown for series" | | `editor.customThemeEditor.patterns.solid` | "Solid" | | `editor.customThemeEditor.patterns.pattern` | "Pattern" | | `editor.customThemeEditor.patterns.dotted` | "Dotted" | | `editor.customThemeEditor.patterns.dashed` | "Dashed" | | `editor.customThemeEditor.patterns.hatched` | "Hatched" | | `editor.customThemeEditor.heatmapColorLabel` | "Color" | ### Editor - Accessibility | Key | Default | Parameters | | ------------------------------------ | ------------------- | ----------- | | `editor.accessibility.toggleSection` | "Toggle \$ section" | `{ title }` | ### Editor - Graphy defaults | Key | Default | | ------------------------------------------ | ------- | | `editor.graphyDefaults.themeOptions.light` | "Light" | | `editor.graphyDefaults.themeOptions.dark` | "Dark" | # Portal provider Source: https://docs.graphy.dev/sdk/advanced/portal-provider Graphy renders some elements outside of its root container using React portals. These include the annotations menu, text editor toolbar, tooltips and other overlay components. By default, these portal elements render into the document body. Use `PortalProvider` to specify a custom container instead, which is useful for modals, z-index control or CSS containment. ## Basic usage Wrap your `GraphProvider` with `PortalProvider`. All portal elements will be rendered as children of the `PortalProvider`. ```tsx theme={null} import { GraphProvider, Graph, PortalProvider } from '@graphysdk/core'; {/* portal elements will be rendered here */} ; ``` ## Props | Prop | Description | | ----------- | --------------------------------------------- | | `children` | React children to render inside the provider | | `as` | Element type to render as (defaults to `div`) | | `className` | CSS class name for styling | ## Styling By default, `PortalProvider` applies `display: contents` to avoid affecting layout. When you pass a `className`, you control the styling completely: ```tsx theme={null} ``` ## Common use cases **Modals and dialogs** – Prevent portal elements from appearing behind modal overlays: ```tsx theme={null} ``` **Custom containers** – Render portals in a specific DOM element: ```tsx theme={null} ``` # Version management Source: https://docs.graphy.dev/sdk/advanced/versioning Graphy SDK follows [Semantic Versioning](https://semver.org/) (SemVer). This page describes our release lifecycle and what you can expect as a consumer of the SDK. ## Packages The Graphy SDK is composed of three packages: * `@graphysdk/core` * `@graphysdk/i18n` * `@graphysdk/editor` All three packages are versioned together — every release bumps them to the same version number. You must ensure that all Graphy packages in your project use matching versions. Mixing different versions of Graphy packages (e.g. `@graphysdk/core@1.3.0` with `@graphysdk/editor@1.2.0`) is not supported and may cause unexpected behaviour. ## Version format Every release follows the `MAJOR.MINOR.PATCH` convention: * **Major** — contains breaking changes (aligned with Graphy customers beforehand) * **Minor** — new features, fully backwards-compatible * **Patch** — bug fixes and performance improvements ## Release lifecycle Each change merged to the main branch goes through the following stages before reaching a stable release: New features and fixes are first published as beta releases (e.g. `1.2.0-beta.20260219191134`) which are appropriate for testing in staging environments. Once a beta version has been tested and validated it is promoted to a stable release (e.g. `1.2.0`), which is the version recommended for production use. ### Installing a specific release channel ```bash Beta theme={null} npm install @graphysdk/core@beta @graphysdk/i18n@beta @graphysdk/editor@beta ``` ```bash Stable (default) theme={null} npm install @graphysdk/core@latest @graphysdk/i18n@latest @graphysdk/editor@latest ``` ## Deprecation policy We follow a deliberate deprecation cycle so that you always have time to migrate: 1. A feature is marked as **deprecated** using TypeScript's `@deprecated` tag, and noted in the release notes and documentation. 2. The deprecated feature continues to work for the remainder of the current major version. 3. The deprecated feature is **removed** in the next major release. Deprecated features are never removed in minor or patch releases. You can safely upgrade within a major version without worrying about removed functionality. # Annotations Source: https://docs.graphy.dev/sdk/config/annotations Annotations allow users to add visual elements like arrows, shapes and text to graphs to highlight important data points or provide additional context. ## Annotation types Graphy supports seven types of annotations. ### Sticker Adds an emoji sticker to a specific data point on the chart. Annotation type identifier. The sticker to display. Available options: `'rocket'`, `'clapping-hands'`, `'thumbs-up'`, `'thumbs-down'`, `'grinning-face'`. Zero-based index of the row this annotation targets. Key of the column this annotation targets. Optional categorical value to pin the annotation to. Used to remap the annotation if the dataset changes. ```tsx theme={null} const config: GraphConfig = { annotations: [ { id: 'sticker-1', type: 'sticker', sticker: 'rocket', rowIndex: 2, columnKey: 'sales', }, ], }; ``` ### Tooltip Displays a custom tooltip with rich text content at a specific data point. Annotation type identifier. Rich text (TipTap JSON) shown inside the tooltip. Zero-based index of the row this annotation targets. Key of the column this annotation targets. Optional categorical value to pin the annotation to. ```tsx theme={null} const config: GraphConfig = { annotations: [ { id: 'tooltip-1', type: 'tooltip', caption: { type: 'doc', content: [ { type: 'paragraph', content: [{ type: 'text', text: 'Peak sales period' }], }, ], }, rowIndex: 5, columnKey: 'revenue', }, ], }; ``` ### Highlight Highlights a data point, entire series or all data points at a specific x-value. Annotation type identifier. What to highlight: - `'data-point'` - Highlight a single data point - `'series'` - Highlight an entire series - `'x-value'` - Highlight all data points at a specific x-value Zero-based index of the row this annotation targets. Key of the column this annotation targets. Optional categorical value to pin the annotation to. ```tsx theme={null} const config: GraphConfig = { annotations: [ { id: 'highlight-1', type: 'highlight', highlight: 'x-value', rowIndex: 3, }, ], }; ``` ### Text Adds a text box with rich text content positioned relative to the plot area. Annotation type identifier. Rich text content (TipTap JSON). Horizontal position relative to the plot area (0 to 1). Represents the midpoint of the text box. Vertical position relative to the plot area (0 to 1). Represents the midpoint of the text box. Width relative to the plot area (0 to 1). Background color for the text box (hex color). Background style: `'fade'` for semi-transparent or `'opaque'` for solid. ```tsx theme={null} const config: GraphConfig = { annotations: [ { id: 'text-1', type: 'text', content: { type: 'doc', content: [ { type: 'paragraph', content: [{ type: 'text', text: 'Notable trend' }], }, ], }, x: 0.5, y: 0.3, width: 0.2, backgroundColor: '#ffffff', backgroundColorStyle: 'opaque', }, ], }; ``` ### Arrow Draws an arrow between two points on the chart with customizable styling. Annotation type identifier. Starting horizontal position relative to the plot area (0 to 1). Starting vertical position relative to the plot area (0 to 1). Ending horizontal position relative to the plot area (0 to 1). Ending vertical position relative to the plot area (0 to 1). Arrow color (hex color or null for default). Arrow line thickness. Arrowhead style at the start of the line. Line style. Arrowhead style at the end of the line. Whether to use a sticker outline style for the arrow. ```tsx theme={null} const config: GraphConfig = { annotations: [ { id: 'arrow-1', type: 'arrow', startX: 0.2, startY: 0.8, endX: 0.6, endY: 0.3, color: '#ef4444', thickness: 'medium', startArrowheadStyle: 'none', lineStyle: 'solid', endArrowheadStyle: 'line-arrow', hasStickerStyle: false, }, ], }; ``` ### Difference arrow Shows the difference between two data points with an automatic label displaying the calculated difference. Annotation type identifier. What to display: - `'absolute-difference'` - Show the numeric difference - `'relative-difference'` - Show the percentage change - `'proportion'` - Show the ratio between values Starting data point for the difference calculation. Zero-based row index. Column key. Optional categorical value. Ending data point for the difference calculation. Same structure as `start`. Arrow color (hex color or null for default). Arrow size. Position of the label along the arrow (0 to 1): - `0` - Label at the start (default) - `0.5` - Label in the middle - `1` - Label at the end ```tsx theme={null} const config: GraphConfig = { annotations: [ { id: 'diff-1', type: 'difference-arrow', show: 'relative-difference', start: { rowIndex: 0, columnKey: 'sales', }, end: { rowIndex: 5, columnKey: 'sales', }, color: '#10b981', size: 'medium', labelPosition: 0.5, }, ], }; ``` ### Shape Adds a rectangular shape to the chart, either below or above the plot area. Annotation type identifier. Shape type. Currently only rectangles are supported. Whether to render the shape below or above the plot area. Horizontal position relative to the plot area (0 to 1). Vertical position relative to the plot area (0 to 1). Width relative to the plot area (0 to 1). Height relative to the plot area (0 to 1). Fill color for the shape (hex color). Fill opacity (0 to 1). Border stroke width in pixels. ```tsx theme={null} const config: GraphConfig = { annotations: [ { id: 'shape-1', type: 'shape', shape: 'rectangle', layer: 'belowPlot', x: 0.1, y: 0.2, width: 0.3, height: 0.4, fillColor: '#fef3c7', fillOpacity: 0.5, strokeWidth: 2, }, ], }; ``` ## Canvas colors Canvas colors are the default options displayed in the color picker for annotations that allow the user to customize strokes, fills and text colors. Each canvas color is defined as an object with the following properties: | Property | Type | Description | | -------- | ------------------- | ----------------------------------- | | `id` | `string` | A unique identifier for the color | | `label` | `string` (optional) | A human-readable name for the color | | `value` | `string` | The hex color value | ```tsx theme={null} import { graphyLightTheme } from '@graphysdk/core'; const customTheme = { ...graphyLightTheme, canvasColors: [ { id: 'default', label: 'Black', value: '#000000' }, { id: 'blue', label: 'Blue', value: '#3b82f6' }, { id: 'green', label: 'Green', value: '#10b981' }, { id: 'red', label: 'Red', value: '#ef4444' }, ], }; ``` ### Color persistence across themes The `id` property is important for maintaining color consistency when switching between themes. When a user applies a canvas color to an annotation (such as text color or arrow stroke), the color's `id` is stored rather than the hex value itself. When the theme changes: 1. **Matching ID found**: If the new theme contains a canvas color with the same `id`, the annotation automatically uses the color value from the new theme. This allows colors to adapt appropriately—for example, a "default" color might be black in a light theme but white in a dark theme. 2. **No matching ID**: If the new theme does not contain a canvas color with the same `id`, the annotation falls back to the first canvas color in the new theme's `canvasColors` array. This behavior ensures that annotations remain visible and appropriately styled regardless of theme changes, while giving you control over how colors translate between your light and dark themes. ## Default annotation colors When users create new annotations, Graphy applies default colors automatically. You can configure these defaults via the `defaultAnnotationColorIds` property in your theme, which references the `id` values from your `canvasColors` array. See [`GraphThemeAnnotationColorIds`](/sdk/reference/graph-theme#graphthemeannotationcolorids) for the available properties. ```tsx theme={null} import { graphyLightTheme } from '@graphysdk/core'; const customTheme = { ...graphyLightTheme, canvasColors: [ { id: 'default', label: 'Black', value: '#000000' }, { id: 'blue', label: 'Blue', value: '#3b82f6' }, { id: 'accent', label: 'Accent', value: '#ef4444' }, ], defaultAnnotationColorIds: { arrowStroke: 'accent', shapeFill: 'blue', }, }; ``` ### Fallback behavior If a default annotation color is not configured or references an invalid `id`, Graphy automatically falls back to the first canvas color in the `canvasColors` array. This ensures annotations always render with a valid color, even if the configuration is incomplete. # Appearance Source: https://docs.graphy.dev/sdk/config/appearance Configure visual styling for your graph via the `appearance` field. ## Colors ID of a palette from `customPalettes` passed to `GraphProvider`. This determines which colors are used for series in your graph. See [Custom palettes](/sdk/customisation/series-colors#custom-palettes). ```tsx theme={null} // Register custom palettes with GraphProvider const myPalette = { id: 'my-palette-id', name: 'My Palette', colors: [ { id: '1', hex: '#3b82f6', name: 'Blue' }, { id: '2', hex: '#ef4444', name: 'Red' }, { id: '3', hex: '#10b981', name: 'Green' }, ], }; ; ``` Style overrides for individual series. Each series is identified by a key that starts with `series` and is followed by a number (e.g. `series1`, `series2` etc). For a waterfall chart, the keys are `waterfallStart`, `waterfallPositive`, `waterfallNegative` and `waterfallTotal`. Each `SeriesStyle` can have: * `paletteColorId` - Color ID from the active palette * `customColor` - Hex color (takes precedence over `paletteColorId`) * `fillStyle` - `'solid'` | `'hatched'` (for bars/areas) * `lineStyle` - `'solid'` | `'dashed'` | `'dotted'` (for lines) Use the same color for all bars in single-series categorical bar/column charts. ## Border Border configuration. No border by default. - `style` - `'none'` | `'custom'` | `'tinted'` | `'gradient'` | `'preset'` \| `'grey'` (default: `'none'`) - `color` - Hex color or preset name (when `style` is `'preset'`) - `width` - Stroke width in pixels, 0-64 (default: `0`) Round the corners of the border. See [Borders](/sdk/theming/borders) for more info. ## Background Set to `'tint'` to mix the background with the first palette color. ## Text styles Font and color overrides for `heading` and `body` text. Each accepts `fontId` and `color` properties. See [Fonts](/sdk/customisation/graph-fonts). Text size multiplier (0.5-5). ## Number formatting Number display settings: - `decimalPlaces` - `'auto'` or 0-10 - `abbreviation` - `'none'` | `'auto'` | `'k'` | `'m'` | `'b'` ## Interactivity How non-highlighted items appear on hover: - `'grey'` = grey out non-highlighted items - `'fade-color'` = fade the color of non-highlighted items Show tooltips on hover. Animate on initial render and data/config changes. # Axes Source: https://docs.graphy.dev/sdk/config/axes ## Overview The `axes` property provides comprehensive control over chart axes. Configure labels, ranges, scales and visibility for x, y and secondary y axes. ```tsx theme={null} const config: GraphConfig = { axes: { x: { label: 'Month', }, y: { label: 'Revenue (£)', min: 0, }, }, }; ``` Axes configuration only applies to chart types that support axes (line, bar, column, combo, scatter, etc.). Pie and donut charts don't use axes. ## X-axis The x-axis typically represents the independent variable (categories, time periods or continuous values). ### Label Custom label for the x-axis. Displayed below the axis. ```tsx theme={null} axes: { x: { label: 'Quarter'; } } ``` ### Visibility Whether to hide the x-axis. When `true`, the axis line, ticks and labels are hidden. The axis label is also hidden. ```tsx theme={null} axes: { x: { isHidden: true; } } ``` ### Orientation Flip the x-axis position (top↔bottom). ```tsx theme={null} axes: { x: { isReversed: true; // Move x-axis to opposite side } } ``` ### Numeric x-axis options When the x-axis contains numeric values (not categories), additional options are available: Scale type for numeric x-axes. Use `'logarithmic'` for data that spans several orders of magnitude. Minimum value for the x-axis. If not set, automatically calculated from the data. Maximum value for the x-axis. If not set, automatically calculated from the data. How to display ticks on numeric x-axes: - `'auto'` - Display all ticks - `'edges'` - Display only the first and last ticks ```tsx theme={null} axes: { x: { scaleType: 'linear', min: 0, max: 100, tickDisplayMode: 'auto' } } ``` ## Y-axis The y-axis typically represents the dependent variable (measurements, values, quantities). ### Label Custom label for the y-axis. Displayed beside the axis. ```tsx theme={null} axes: { y: { label: 'Sales (£000s)'; } } ``` ### Visibility Whether to hide the y-axis. When `true`, the axis line, ticks and labels are hidden. The axis label is also hidden. ```tsx theme={null} axes: { y: { isHidden: true; } } ``` ### Orientation Flip the y-axis position (left↔right). ```tsx theme={null} axes: { y: { isReversed: true; // Move y-axis to opposite side } } ``` ### Scale type Scale type for the y-axis: - `'linear'` - Standard linear scale with evenly spaced ticks - `'logarithmic'` - Logarithmic scale, useful for data spanning multiple orders of magnitude ```tsx theme={null} axes: { y: { scaleType: 'logarithmic'; // Better for data like: 1, 10, 100, 1000 } } ``` ### Range Minimum value for the y-axis. If not set, automatically calculated from the data. Setting this to `0` is common to ensure bars start from zero. Maximum value for the y-axis. If not set, automatically calculated from the data. ```tsx theme={null} axes: { y: { min: 0, // Start at zero max: 10000 // Cap at 10,000 } } ``` ### Tick display How to display ticks on the y-axis: - `'auto'` - Display all ticks at appropriate intervals - `'edges'` - Display only the minimum and maximum values ```tsx theme={null} axes: { y: { tickDisplayMode: 'edges'; // Show only min and max } } ``` ## Secondary y-axis (y2) Combo charts can display two y-axes to accommodate series with different scales or units. ### Label Custom label for the secondary y-axis. Only used when dual y-axes are enabled. ```tsx theme={null} const config: GraphConfig = { type: 'combo', axes: { y: { label: 'Revenue (£)', }, y2: { label: 'Units sold', }, hasDualYAxis: true, }, }; ``` The secondary y-axis (y2) only supports the `label` property. Other axis options (min, max, scale) are not configurable for y2. ### Dual y-axis mode Whether to show two y-axes on a combo chart. When `true`, the primary y-axis appears on the left and the secondary on the right (or reversed if `isReversed` is set). ```tsx theme={null} const config: GraphConfig = { type: 'combo', axes: { hasDualYAxis: true, // Show both y-axes }, }; ``` When `hasDualYAxis` is `false`, all series share a single y-axis. ### Swapping y-axes For combo charts with dual y-axes, use `isReversed` to swap the positions: ```tsx theme={null} axes: { y: { label: 'Revenue (£)', isReversed: true // Move primary y-axis to the right }, y2: { label: 'Units sold' // This will now appear on the left }, hasDualYAxis: true } ``` ## Grid lines Whether to show horizontal grid lines across the chart. ```tsx theme={null} axes: { showGridLines: true; // Show horizontal grid lines } ``` ## Complete example ```tsx theme={null} const config: GraphConfig = { type: 'line', data: { /* ... */ }, axes: { x: { label: 'Date', isHidden: false, }, y: { label: 'Revenue (£)', min: 0, max: 50000, scaleType: 'linear', tickDisplayMode: 'auto', isHidden: false, isReversed: false, }, showGridLines: true, }, }; ``` ## Combo chart example ```tsx theme={null} const config: GraphConfig = { type: 'combo', data: { /* ... */ }, axes: { y: { label: 'Revenue (£)', min: 0, }, y2: { label: 'Units sold', }, hasDualYAxis: true, showGridLines: true, }, }; ``` # Content Source: https://docs.graphy.dev/sdk/config/content ## Overview The `content` property controls the title, subtitle, caption, data-source attribution, and the optional Made with Graphy provenance badge for your chart. ```tsx theme={null} const config: GraphConfig = { content: { title: 'Quarterly revenue', subtitle: 'FY 2024', caption: 'Excluding one-off adjustments', source: { label: 'Finance team', url: 'https://example.com/data', }, // Opt in to the Graphy provenance mark (hidden by default). isBrandMarkHidden: false, }, }; ``` ## Title Graph title. Can be plain text or TipTap JSON content for rich text formatting. ```tsx theme={null} // Plain text content: { title: 'Monthly sales performance' } // Rich text with formatting content: { title: { type: 'doc', content: [ { type: 'paragraph', content: [ { type: 'text', text: 'Sales growth: ' }, { type: 'text', text: '+15%', marks: [{ type: 'bold' }] } ] } ] } } ``` ## Subtitle Subtitle displayed under the title. Can be plain text or TipTap JSON content for rich text formatting. ```tsx theme={null} content: { subtitle: 'Across all regions'; } ``` ## Caption Caption displayed at the bottom of the chart. Can be plain text or TipTap JSON content for rich text formatting. Use captions for notes, methodology or context about the data. ```tsx theme={null} // Plain text content: { caption: 'Data reflects confirmed transactions only' } // Rich text with link content: { caption: { type: 'doc', content: [ { type: 'paragraph', content: [ { type: 'text', text: 'Methodology: ' }, { type: 'text', text: 'See our data guide', marks: [ { type: 'link', attrs: { href: 'https://example.com/guide' } } ] } ] } ] } } ``` ## Data source Your data-source attribution shown under the caption (for example "Office for National Statistics"). This is **your** attribution of where the data came from — distinct from the Graphy provenance mark below. Text label describing the data source (e.g., "Office for National Statistics", "Internal sales data"). URL link to the original data source. Must be a valid URL. If provided, the `label` will be clickable. ```tsx theme={null} content: { source: { label: 'UK Office for National Statistics', url: 'https://www.ons.gov.uk' } } ``` ## Provenance badge ("Made with Graphy") A translucent capsule badge with the Graphy glyph and “Made with Graphy” text. Discovery signal (not a lock). Linked to `graphy.app`. Size ladder: full pill → circular mini under 200 px → hidden under 120 × 80. Structured badge config. Prefer this over `isBrandMarkHidden`. Defaults to disabled at the low-level renderer; `@graphysdk/react` seeds it on. Legacy opt-out (`true` = hide). Ignored when `brandMark.enabled` is set explicitly. ```tsx theme={null} // Show the badge (footer-right, full pill) content: { brandMark: { enabled: true }, } // Header top-right, circular mini content: { brandMark: { enabled: true, placement: 'header', variant: 'mini' }, } // Legacy opt-out content: { isBrandMarkHidden: true, } ``` `source` is **your** data attribution. `brandMark` controls Graphy's own provenance badge. They can appear together (source left, badge right), either alone, or neither. ## Content visibility Use the visibility flags (`isTitleHidden`, `isSubtitleHidden`, `isCaptionHidden`, `isSourceHidden`, `isBrandMarkHidden`) to programmatically control which content appears: ```tsx theme={null} // Show only title and source; keep the Graphy mark hidden (default) content: { title: 'Revenue trends', subtitle: 'This will be hidden', isSubtitleHidden: true, caption: 'This will also be hidden', isCaptionHidden: true, source: { label: 'Finance department' }, } // Opt in to the provenance mark as well content: { title: 'Revenue trends', source: { label: 'Finance department' }, isBrandMarkHidden: false, } ``` Hiding content with visibility flags is different from omitting it entirely. Hidden content is still part of the config but not rendered. This is useful when you want to preserve content but temporarily hide it. The provenance mark has no separate text value — only the visibility flag. # Data labels Source: https://docs.graphy.dev/sdk/config/data-labels ## Overview Data labels display numeric values directly on chart elements like bars, columns, lines and pie slices. They make it easier to read exact values without relying on tooltips or axes. ```tsx theme={null} const config: GraphConfig = { dataLabels: { showDataLabels: true, dataLabelFormat: 'absolute', }, }; ``` ## Show data labels Whether to show numeric data labels on the chart. When `true`, values appear directly on bars, columns, line points and pie slices. ```tsx theme={null} const config: GraphConfig = { type: 'column', data: { columns: [ { key: 'product', label: 'Product' }, { key: 'sales', label: 'Sales' }, ], rows: [ { product: 'A', sales: 100 }, { product: 'B', sales: 150 }, { product: 'C', sales: 120 }, ], }, dataLabels: { showDataLabels: true, // Show "100", "150", "120" on top of columns }, }; ``` Data labels respect the number formatting configured in `appearance.numberFormat`, including decimal places and abbreviations. ## Label format Format to use for the data labels: - `'absolute'` - Show the actual value from the data - `'percentage'` - Show values as percentages (calculated differently for each graph type) ### Absolute values Show the actual numeric values: ```tsx theme={null} dataLabels: { showDataLabels: true, dataLabelFormat: 'absolute' // Shows: 100, 150, 120 } ``` ### Percentage values Show values as percentages. The calculation method depends on the chart type: **For stacked charts:** * Shows each segment as a percentage of the total stack **For pie/donut charts:** * Shows each slice as a percentage of the total **For other charts:** * Shows each value as a percentage of the total sum ```tsx theme={null} const config: GraphConfig = { type: 'columnStacked', dataLabels: { showDataLabels: true, dataLabelFormat: 'percentage', // Shows each segment as % of total }, }; ``` ## Stack totals Whether to show total values above stacked bar or column stacks. Only applicable to stacked bar and column charts. ```tsx theme={null} const config: GraphConfig = { type: 'columnStacked', data: { columns: [ { key: 'quarter', label: 'Quarter' }, { key: 'online', label: 'Online' }, { key: 'retail', label: 'Retail' }, ], rows: [ { quarter: 'Q1', online: 100, retail: 50 }, { quarter: 'Q2', online: 120, retail: 60 }, { quarter: 'Q3', online: 140, retail: 55 }, ], }, dataLabels: { showStackTotals: true, // Shows "150", "180", "195" above each stack }, }; ``` `showStackTotals` and `showDataLabels` can be used together. This shows both the individual segment values and the total for each stack. ## Category labels Whether to show category names on pie and donut charts. Only applicable to pie and donut chart types. When `true`, category names appear next to each slice. ```tsx theme={null} const config: GraphConfig = { type: 'pie', data: { columns: [ { key: 'department', label: 'Department' }, { key: 'budget', label: 'Budget' }, ], rows: [ { department: 'Marketing', budget: 50000 }, { department: 'Sales', budget: 75000 }, { department: 'Engineering', budget: 120000 }, ], }, dataLabels: { showCategoryLabels: true, // Shows "Marketing", "Sales", "Engineering" }, }; ``` ## Combining data labels You can combine different data label options for comprehensive labelling: ### Stacked chart with totals and segment labels ```tsx theme={null} const config: GraphConfig = { type: 'barStacked', dataLabels: { showDataLabels: true, // Show values on each segment dataLabelFormat: 'absolute', // Show actual values (not percentages) showStackTotals: true, // Also show the total for each bar }, }; ``` ### Pie chart with categories and percentages ```tsx theme={null} const config: GraphConfig = { type: 'pie', dataLabels: { showDataLabels: true, // Show values on each slice dataLabelFormat: 'percentage', // Show as percentages showCategoryLabels: true, // Also show category names }, }; ``` ## Chart type compatibility Different data label options work with different chart types: | Option | Compatible chart types | | ------------------------------- | -------------------------------------------------------------------- | | `showDataLabels` | All chart types except table | | `dataLabelFormat: 'percentage'` | All chart types except table | | `showStackTotals` | `barStacked`, `barStackedFill`, `columnStacked`, `columnStackedFill` | | `showCategoryLabels` | `pie`, `donut` | ## Complete example ```tsx theme={null} const config: GraphConfig = { type: 'columnStacked', data: { columns: [ { key: 'month', label: 'Month' }, { key: 'new', label: 'New customers' }, { key: 'returning', label: 'Returning customers' }, ], rows: [ { month: 'Jan', new: 45, returning: 120 }, { month: 'Feb', new: 52, returning: 135 }, { month: 'Mar', new: 48, returning: 142 }, ], }, dataLabels: { showDataLabels: true, // Show segment values dataLabelFormat: 'absolute', // Show actual numbers showStackTotals: true, // Show totals above stacks }, appearance: { numberFormat: { decimalPlaces: 0, // No decimals for data labels abbreviation: 'none', // Don't abbreviate }, }, }; ``` # Headline numbers Source: https://docs.graphy.dev/sdk/config/headline-numbers ## Overview Headline numbers display key summary metrics prominently at the top of your chart. They're ideal for highlighting the most important value in your data at a glance. ```tsx theme={null} const config: GraphConfig = { headlineNumbers: { show: 'total', compareWith: 'previous', size: 'large', }, }; ``` ## Display mode How to calculate the headline number: - `'current'` - The last value in the series - `'average'` - The arithmetic mean of the series - `'total'` - The sum of all values in the series - `'conversion'` - The ratio between the first and last values (shown as a percentage) - `'none'` - Don't show any headline numbers ### Current value Show the most recent value in your data: ```tsx theme={null} const config: GraphConfig = { type: 'line', data: { columns: [ { key: 'month', label: 'Month' }, { key: 'users', label: 'Users' }, ], rows: [ { month: 'Jan', users: 1000 }, { month: 'Feb', users: 1200 }, { month: 'Mar', users: 1450 }, ], }, headlineNumbers: { show: 'current', // Shows 1,450 (the March value) }, }; ``` **Best for:** Time series showing current state, latest measurements ### Average Show the mean of all values: ```tsx theme={null} headlineNumbers: { show: 'average'; // Shows the arithmetic mean } ``` **Best for:** Understanding typical values, smoothing out volatility ### Total Show the sum of all values: ```tsx theme={null} headlineNumbers: { show: 'total'; // Shows the sum } ``` **Best for:** Cumulative data, total revenue, aggregate counts ### Conversion rate Show the percentage change from first to last value: ```tsx theme={null} const config: GraphConfig = { type: 'funnel', data: { columns: [ { key: 'stage', label: 'Stage' }, { key: 'count', label: 'Count' }, ], rows: [ { stage: 'Visitors', count: 10000 }, { stage: 'Signups', count: 2500 }, { stage: 'Purchases', count: 500 }, ], }, headlineNumbers: { show: 'conversion', // Shows 5% (500/10000) }, }; ``` Conversion rate is designed for funnel charts and calculates the ratio between the first and last values as a percentage. **Best for:** Funnel conversions, efficiency metrics ## Comparison Show a trend arrow comparing the headline value to another value: - `'previous'` - Compare to the previous value in the series - `'first'` - Compare to the first value in the series - `'none'` - Don't show a comparison ### Compare with previous Show how the headline value has changed from the previous data point: ```tsx theme={null} const config: GraphConfig = { data: { columns: [ { key: 'quarter', label: 'Quarter' }, { key: 'revenue', label: 'Revenue' }, ], rows: [ { quarter: 'Q1', revenue: 100000 }, { quarter: 'Q2', revenue: 120000 }, { quarter: 'Q3', revenue: 135000 }, ], }, headlineNumbers: { show: 'current', // Show Q3 value (135,000) compareWith: 'previous', // Compare to Q2 (120,000) // Shows: 135,000 ↑ +12.5% }, }; ``` ### Compare with first Show how the headline value has changed from the first data point: ```tsx theme={null} headlineNumbers: { show: 'current', compareWith: 'first' // Compare current value to the very first value } ``` **Best for:** Growth since start, year-over-year changes ## Size Size of the headline metric: - `'auto'` - Automatically choose the appropriate size based on chart dimensions - `'small'` - Small font size - `'medium'` - Medium font size - `'large'` - Large font size ```tsx theme={null} headlineNumbers: { show: 'total', size: 'large' // Extra prominent } ``` ## Multiple series When your chart has multiple data series, a headline number is shown for each series: ```tsx theme={null} const config: GraphConfig = { data: { columns: [ { key: 'month', label: 'Month' }, { key: 'productA', label: 'Product A' }, { key: 'productB', label: 'Product B' }, ], rows: [ { month: 'Jan', productA: 100, productB: 150 }, { month: 'Feb', productA: 120, productB: 160 }, ], }, headlineNumbers: { show: 'total', // Shows totals for both Product A and Product B }, }; ``` ## Complete example ```tsx theme={null} const config: GraphConfig = { type: 'line', data: { columns: [ { key: 'week', label: 'Week' }, { key: 'signups', label: 'Signups' }, ], rows: [ { week: 'Week 1', signups: 45 }, { week: 'Week 2', signups: 52 }, { week: 'Week 3', signups: 48 }, { week: 'Week 4', signups: 61 }, ], }, content: { title: 'Weekly signups', }, headlineNumbers: { show: 'current', // Show current week (61) compareWith: 'previous', // Compare to last week (48) size: 'large', // Make it prominent }, // Display shows: "61 ↑ +27.1%" }; ``` # Legend Source: https://docs.graphy.dev/sdk/config/legend ## Overview The `legend` property controls where the legend appears and whether it's visible. The legend shows which color or pattern represents each data series. ```tsx theme={null} const config: GraphConfig = { legend: { position: 'right', }, }; ``` ## Position Where to position the legend: - `'auto'` - Automatically determine the appropriate position based on chart size and type - `'top'` - Show the legend above the chart - `'right'` - Show the legend to the right of the chart - `'none'` - Hide the legend completely ```tsx theme={null} legend: { position: 'right'; } ``` Even when the legend is hidden with `position: 'none'`, series colors are still applied to the chart. The legend position only affects visibility and placement, not the styling. ## Legend interaction When visible, the legend is interactive: * **Click** a legend item to toggle the series visibility (in supported contexts) ## Complete example ```tsx theme={null} const config: GraphConfig = { type: 'line', data: { columns: [ { key: 'month', label: 'Month' }, { key: 'productA', label: 'Product A' }, { key: 'productB', label: 'Product B' }, { key: 'productC', label: 'Product C' }, ], rows: [ { month: 'Jan', productA: 100, productB: 150, productC: 120 }, { month: 'Feb', productA: 120, productB: 160, productC: 140 }, { month: 'Mar', productA: 140, productB: 180, productC: 130 }, ], }, legend: { position: 'right', // Show series labels on the right }, }; ``` # Reference lines Source: https://docs.graphy.dev/sdk/config/reference-lines ## Goal line A goal line displays a target value as a horizontal line across your chart. Use it to show targets, thresholds or benchmarks. Goal line configuration. Goal target value along the y-axis. This is where the horizontal line will be drawn. Optional marker position on the x-axis. Use this to indicate when the goal applies or when it was set. Custom label for the goal line. Defaults to "Goal" if not provided. ```tsx theme={null} const config: GraphConfig = { type: 'line', data: { columns: [ { key: 'month', label: 'Month' }, { key: 'revenue', label: 'Revenue' }, ], rows: [ { month: 'Jan', revenue: 850 }, { month: 'Feb', revenue: 920 }, { month: 'Mar', revenue: 1050 }, { month: 'Apr', revenue: 1100 }, ], }, referenceLines: { goalLine: { target: 1000, label: 'Monthly target', }, }, }; ``` ### Goal line with marker Add a marker to show when a goal was set or when it changes: ```tsx theme={null} referenceLines: { goalLine: { target: 1000, marker: 'Mar', // Mark the x-position where goal applies label: 'Q2 target' } } ``` ## Trendline A trendline shows the general direction and pattern in your data using statistical methods. Choose from multiple trendline types depending on your data characteristics. The type of trendline to display. Each type fits a different mathematical model to your data. ### Trendline types Straight line. Best for data that moves up or down at a steady pace. Use when: Your data shows consistent growth or decline Smooth line that gently follows the overall pattern in the data. Good for bumpy or noisy trends. Use when: You want to see the general direction without being distracted by short-term volatility Line that bends upward or downward more and more over time. Good for growth rates or compounding changes. Use when: Your data is accelerating or decelerating exponentially Line that changes quickly at first, then levels off. Good for patterns with diminishing returns. Use when: Your data grows rapidly initially then plateaus Gently curved line with one clear peak or valley. Good for data that changes direction once. Use when: Your data has a single turning point Curved line for scaling relationships where one value grows as a power of another (e.g., area vs radius). Use when: You're showing power law relationships Flexible curved line that can have several peaks and valleys. Use when: Your data has multiple turning points ### Using trendlines ```tsx theme={null} const config: GraphConfig = { type: 'scatter', data: { /* ... */ }, referenceLines: { trendline: 'linear', // Add a linear trendline }, }; ``` ## Average line An average line shows the arithmetic mean of a data series as a horizontal line. Useful for identifying values that are above or below average. Average line configuration. Column key for the series to calculate the average from. Must match a column key from your data. ```tsx theme={null} const config: GraphConfig = { type: 'column', data: { columns: [ { key: 'product', label: 'Product' }, { key: 'sales', label: 'Sales' }, ], rows: [ { product: 'A', sales: 100 }, { product: 'B', sales: 150 }, { product: 'C', sales: 120 }, { product: 'D', sales: 180 }, ], }, referenceLines: { averageLine: { columnKey: 'sales', // Show average of sales values (137.5) }, }, }; ``` # Type options Source: https://docs.graphy.dev/sdk/config/type-options ## Overview The `options` property contains chart type-specific settings. Different options are available depending on which chart type you're using. ```tsx theme={null} const config: GraphConfig = { type: 'line', options: { isSmoothLine: true, showPoints: true, }, }; ``` ## Line chart options ### Smooth lines Whether to use smooth (curved) lines for line charts. When `true`, lines are drawn with curves. When `false`, lines are straight between data points. ```tsx theme={null} const config: GraphConfig = { type: 'line', options: { isSmoothLine: true, }, }; ``` ### Line thickness Line thickness in pixels. Set to `'auto'` to automatically determine the appropriate thickness based on chart size, or provide a specific pixel value. ```tsx theme={null} const config: GraphConfig = { type: 'line', options: { lineThickness: 3, // 3px thick lines }, }; ``` ### Show points Whether to show data point markers on the line. Useful for emphasising individual data points or when you have sparse data. ```tsx theme={null} const config: GraphConfig = { type: 'line', options: { showPoints: true, }, }; ``` ### Missing values How to handle missing values (nulls) in line charts: - `'gap'` - Break the line where values are missing - `'connect'` * Connect the line across missing values - `'zero'` - Treat missing values as zero ```tsx theme={null} const config: GraphConfig = { type: 'line', data: { columns: [ { key: 'month', label: 'Month' }, { key: 'sales', label: 'Sales' }, ], rows: [ { month: 'Jan', sales: 100 }, { month: 'Feb', sales: null }, // Missing data { month: 'Mar', sales: 150 }, ], }, options: { missingValues: 'connect', // Draw line across the gap }, }; ``` ## Bar and column chart options ### Sort bars Whether to sort bars in descending order by value. Only applies to categorical bar and column charts with a single series. ```tsx theme={null} const config: GraphConfig = { type: 'bar', options: { sortBars: true, // Sort from highest to lowest }, }; ``` ## Scatter plot options ### Point size Custom point size for scatter plots in pixels. Set to `'auto'` to automatically determine the appropriate size, or provide a specific pixel value. ```tsx theme={null} const config: GraphConfig = { type: 'scatter', options: { pointSize: 8, // 8px diameter points }, }; ``` ## Combo chart options ### Combo type Determines the type of combo plot to show: - `'grouped-bars'` - Display series as grouped bars alongside each other - `'stacked-bars'` - Stack series bars on top of each other - `'lines'` - Display series as lines ```tsx theme={null} const config: GraphConfig = { type: 'combo', options: { comboType: 'lines', // Show all series as lines }, }; ``` ## Pie and donut chart options ### Pie total position Where to show the sum of all values in a pie or donut chart: - `'center'` - Show the total in the center of the chart (particularly useful for donut charts) - `'outside'` - Show the total outside the chart area ```tsx theme={null} const config: GraphConfig = { type: 'donut', options: { pieTotalPosition: 'center', // Show total in donut center }, }; ``` ## Combining options You can combine multiple options for the same chart type: ```tsx theme={null} const config: GraphConfig = { type: 'line', options: { isSmoothLine: true, showPoints: true, lineThickness: 2, missingValues: 'connect', }, }; ``` Options that don't apply to your current chart type are ignored. For example, setting `isSmoothLine` on a bar chart has no effect. # Data structure Source: https://docs.graphy.dev/sdk/core/data-structure ## Basic structure Data is structured as a table with explicitly defined columns and rows: ```tsx theme={null} const config: GraphConfig = { data: { columns: [ { key: 'category', label: 'Category' }, { key: 'value', label: 'Value' }, ], rows: [ { category: 'A', value: 100 }, { category: 'B', value: 200 }, { category: 'C', value: 150 }, ], }, }; ``` The `columns` array defines your data structure, while `rows` contains the actual values. Each row object must have keys matching the column keys. ## Automatic column mapping Graphy automatically assigns your data columns to x-axis and y-axis, and creates series based on the detected value formats. **Default mapping** for most graph types (bar, column, line, area): * The first text or date column becomes the **x-axis** * All numeric columns become the **y-axis**, with each numeric column creating a separate **series** ```tsx theme={null} const config: GraphConfig = { data: { columns: [ { key: 'month', label: 'Month' }, // date column → x-axis { key: 'revenue', label: 'Revenue' }, // numeric → series 1 { key: 'expenses', label: 'Expenses' }, // numeric → series 2 ], rows: [ { month: 'January 2026', revenue: 5000, expenses: 3200 }, { month: 'February 2026', revenue: 6200, expenses: 3800 }, { month: 'March 2026', revenue: 5800, expenses: 3500 }, ], }, }; ``` **Graph-specific behaviour:** | Graph type | X-axis | Y-axis / values | | ----------------------- | ------------------------- | ------------------------------------------- | | Bar, column, line, area | First text or date column | All numeric columns (one series each) | | Combo | First text or date column | All numeric columns (last defaults to line) | | Funnel, waterfall | First text column | First numeric column | **Pie and donut** use the first text column for slices and the first numeric column for values. Additional columns are ignored. **Scatter** requires at least 2 numeric columns: the first becomes the x-axis and the remaining become y-axis series. If a text column is present, it is used as a label for each point. **Bubble** requires at least 3 numeric columns: the first is the x-axis, the second is the y-axis and the third controls the bubble size. Like scatter, the first text column is used as a label. If your data contains only numeric columns, the first numeric column is used as the x-axis and the rest become series. ## Value format detection Graphy automatically detects the format of values in each column. The first match wins: ### Numbers Numbers and numeric strings are detected as quantitative values. Thousands separators, decimals and magnitude suffixes are supported: ```tsx theme={null} { category: 'A', value: 100 } { category: 'B', value: '1,250.50' } { category: 'C', value: '2.5m' } // k, m, b, t suffixes supported ``` ### Dates Date-like strings are automatically parsed. Graphy recognises a wide range of formats, including ISO dates, named months and locale-specific orderings: ```tsx theme={null} { date: '2024-01-15', value: 100 } // YYYY-MM-DD { date: 'January 2024', value: 200 } // month name + year { date: '15/01/2024', value: 150 } // DD/MM/YYYY (when data._metadata.parsingLocale is en-GB) { date: '01/15/2024', value: 150 } // MM/DD/YYYY (en-US, the default locale) { date: '2024-01-15T12:30:00Z', value: 300 } // ISO datetime { date: 'Q1 2024', value: 400 } // quarter ``` Short month names (`Jan`, `Feb`) are recognised alongside full names. Set `data._metadata.parsingLocale` to control whether ambiguous dates like `01/02/2024` are interpreted as MM/DD (en-US) or DD/MM (en-GB). **Weekly date ranges** are also detected — values like `1 Feb – 7 Feb` or `1 Feb 2024 – 7 Feb 2024` representing 7-day spans. ### Percentages Values with a `%` suffix are detected as percentages: ```tsx theme={null} { category: 'Desktop', share: '64.5%' } { category: 'Mobile', share: '28.3%' } ``` ### Currencies Currency-formatted strings are parsed with automatic symbol recognition. Supported symbols: `$`, `€`, `£`, `¥`, `₹`, `₱`, `₩`, `₪`, `₫`, `₽`, `฿`, `₦`, `₺`, `zł`, `kr`, `Fr`, `R`, `R$`, `Rp`, `RM`, `د.إ`, `﷼`, `Ch$`, `NT$`, `HK$`, `S$`, `A$`, `C$`, `NZ$`, `MX$`. ```tsx theme={null} { product: 'A', price: '$1,250' } { product: 'B', price: '£2,450.50' } { product: 'C', price: '€3,000' } ``` Symbols can appear as prefix or suffix, and negative values are supported (`-$100`, `$-100`). ### Text Any value that doesn't match the above formats is treated as text (categorical data). ## Schema Array of column definitions. Each column defines the structure and metadata for a data field. Unique, stable identifier for the column. Must match the keys used in `rows` objects. Human-readable label displayed in the UI (axis labels, legends, tooltips). Defaults to the `key` if not provided. Internal column metadata. Managed automatically by the editor. Whether the column is hidden from visualization. Aggregation function applied to this column. Array of data rows. Each row is an object with keys matching the column keys. Values can be strings, numbers or null. Optional metadata for advanced data processing. Locale for parsing dates and numbers. Defaults to `'en-US'`. * `'en-US'`: MM/DD/YYYY date format * `'en-GB'`: DD/MM/YYYY date format Whether the data is transposed. Used by the data table component. Whether aggregation is active. Managed automatically by the editor. Time unit for grouping time-based data. Sort configuration. Column key to sort by. Sort direction. Rolling date filter configuration. Time unit for the filter. Number of time units (e.g., last 30 days: `{ timeUnit: 'day', value: 30 }`). Row object keys must exactly match the `key` values in your columns array. Data with keys that don't have a corresponding column definition will be ignored. # Quickstart Source: https://docs.graphy.dev/sdk/core/quickstart ### 1. Install the Graphy SDK ```shell npm theme={null} npm install @graphysdk/core ``` ```shell yarn theme={null} yarn add @graphysdk/core react react-dom styled-components @tiptap/core @tiptap/extension-blockquote @tiptap/extension-bold @tiptap/extension-document @tiptap/extension-hard-break @tiptap/extension-heading @tiptap/extension-italic @tiptap/extension-link @tiptap/extension-list @tiptap/extension-paragraph @tiptap/extension-strike @tiptap/extension-text @tiptap/extension-text-align @tiptap/extension-text-style @tiptap/extension-underline @tiptap/extensions @tiptap/pm @tiptap/react ``` ```shell pnpm theme={null} pnpm add @graphysdk/core ``` ```shell bun theme={null} bun add @graphysdk/core ``` Because `@graphysdk/core` is a private package, you'll need to configure your npm auth token. Create an `.npmrc` in your repository root (or user-level) with: ```ini .npmrc theme={null} //registry.npmjs.org/:_authToken=${NPM_TOKEN} @graphysdk:registry=https://registry.npmjs.org/ ``` ### 2. Create your first graph There are two main components required to render a Graphy chart: **`GraphProvider`** is the state manager for Graphy charts and can be placed anywhere in the React tree. It provides state to the charting components as well as any components used to edit these charts. **`Graph`** is the component used to render the graph itself. ```tsx theme={null} import { GraphProvider, Graph } from '@graphysdk/core'; export function App() { const config: GraphConfig = { type: 'column', // Graph type (column, bar, line, pie, etc.) data: { columns: [ { key: 'month', label: 'Month' }, { key: 'sales', label: 'Sales' }, ], rows: [ { month: 'January', sales: 1000 }, { month: 'February', sales: 1200 }, { month: 'March', sales: 1450 }, { month: 'April', sales: 1500 }, ], }, }; return ( ); } ``` Graphy will automatically select sensible defaults for any properties that are not provided. See the [graph types reference](/sdk/graph-types/index) for all available chart types. ### 3. Customize your graph You have fine-grained control over every aspect of your chart. Here are some common customizations: ```tsx theme={null} const config: GraphConfig = { data: { columns: [ { key: 'month', label: 'Month' }, { key: 'sales', label: 'Sales' }, ], rows: [ { month: 'January', sales: 1000 }, { month: 'February', sales: 1200 }, { month: 'March', sales: 1450 }, { month: 'April', sales: 1500 }, ], }, type: 'line', options: { isSmoothLine: true, showPoints: true, }, axes: { y: { label: 'Revenue (£)', min: 0, }, }, appearance: { numberFormat: { abbreviation: 'auto', decimalPlaces: 0, }, }, content: { title: 'Monthly sales', subtitle: 'Q1 2024', source: { label: 'Internal sales data', url: 'https://example.com/data', }, }, }; ``` ### 4. Make it editable By default, graphs are read-only. Use `mode="editor"` on the `Graph` component to allow users to edit titles, add annotations and make other modifications. Use the `onChange` handler on `GraphProvider` to persist these changes: ```tsx theme={null} function MyChart() { const [config, setConfig] = useState({ ... }); return ( setConfig((current) => ({ ...current, ...update }))} > ); } ``` The `mode` prop has three options: * `readonly` (default) - Full interactivity with tooltips and hover effects, but no editing * `editor` - Same as readonly, plus the ability to edit the graph * `static` - No interactivity, useful for image exports ### Next steps * Learn about the [data structure](/sdk/core/data-structure) and how columns and rows work * Explore all available [graph types](/sdk/graph-types/index) * Discover [configuration options](/sdk/config/type-options) to customize your charts * See all [GraphProvider props](/sdk/reference/graph-provider) and [Graph props](/sdk/reference/graph) * Read the complete [schema reference](/sdk/reference/graph-config) # CSS hooks Source: https://docs.graphy.dev/sdk/customisation/css-hooks Every part of a chart you might want to style (bars, lines, grid lines, axes, the legend) has a fixed classname, and useful context like the chart type and the series index is exposed as data attributes. ARIA attributes (`role`, `aria-label`, `aria-roledescription`) remain in the DOM for accessibility, but they are not a styling API; selectors should use the hooks below. ```css theme={null} .graphy-bar { rx: 4; } .graphy-bar[data-graphy-series-index='1'] { fill: red; } ``` ## Parts Each part carries a classname; the data attributes present on a part are listed in the [next section](#data-attributes). | Classname | Element | What it is | | ------------------------- | ------------ | ------------------------------------------------------------ | | `.graphy-chart` | `div` | Figure root (`role="figure"`) | | `.graphy-bar` | `rect` | A bar, in bar/column/combo/waterfall/funnel/mekko charts | | `.graphy-line` | `path` | A line | | `.graphy-area` | `path` | The area fill under a line | | `.graphy-pie-slice` | `path` | A pie or donut slice | | `.graphy-point` | `circle` | A point: dots on lines, scatter and bubble marks | | `.graphy-tile` | `rect` | A heatmap cell | | `.graphy-grid-line` | `line` | Grid line | | `.graphy-tick-line` | `line` | Axis tick mark | | `.graphy-origin-line` | `line` | Zero/origin line | | `.graphy-plot-outline` | `rect` | Plot area outline; fill it to color the area behind the data | | `.graphy-axis` | `g` | Axis group (ticks and label) | | `.graphy-axis-label` | `text` | Axis title | | `.graphy-tick-label` | `text` | Axis tick label | | `.graphy-legend` | `div` | Legend container (`role="list"`) | | `.graphy-legend-item` | `div` | Legend item | | `.graphy-legend-swatch` | `div` | Color swatch wrapper inside an item | | `.graphy-legend-label` | `div` | Label text inside an item | | `.graphy-data-label` | `div` | Value label on a bar or slice | | `.graphy-stack-total` | `div` | Total label above a stacked bar | | `.graphy-series-label` | `text` | Inline series name next to a line | | `.graphy-headline` | `div` | Headline metrics row | | `.graphy-headline-item` | `div` | One headline metric | | `.graphy-tooltip` | `div` | Tooltip box (hover and pinned) | | `.graphy-tooltip-heading` | `div` | Tooltip heading | | `.graphy-tooltip-item` | `div` | Row inside a tooltip | | `.graphy-tooltip-swatch` | `div` | Color swatch inside a tooltip row | | `.graphy-tooltip-label` | `div`/`span` | Label text inside a tooltip | | `.graphy-tooltip-value` | `div` | Value text inside a tooltip | | `.graphy-tooltip-caption` | `div` | Tooltip caption | | `.graphy-tooltip-footer` | `div`/`span` | Tooltip footer | Tooltips render outside `.graphy-chart`, so don't nest tooltip selectors under a chart selector. Write them at the top level, and note they apply to every chart on the page. ## Data attributes | Attribute | On | Values | | -------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data-graphy-chart-type` | `.graphy-chart` | `line`, `areaStacked`, `bar`, `barStacked`, `barStackedFill`, `column`, `columnStacked`, `columnStackedFill`, `combo`, `pie`, `donut`, `funnel`, `heatmap`, `scatter`, `bubble`, `waterfall`, `table`, `mekko` | | `data-graphy-orientation` | `.graphy-chart` | `horizontal` (bar charts) or `vertical`; absent for `pie`, `donut`, `funnel`, `heatmap`, `table` | | `data-graphy-series-index` | bars, lines, areas, slices, points, legend items | 0-based series position, see below | | `data-graphy-axis-side` | axes, axis labels, tick labels, grid lines, tick lines | `top`, `bottom`, `left`, `right` | | `data-graphy-hidden` | `.graphy-legend-item` | present (value `true`) only while the series is toggled off in the legend | | `data-graphy-theme` | a wrapper element around `.graphy-chart` | `light` or `dark`; use it to scope rules to the chart's color scheme, e.g. `[data-graphy-theme='dark'] .graphy-grid-line` | Prefer `data-graphy-orientation` over enumerating bar types when a rule only cares about direction. On grid lines, the axis side tells you the line's direction: `left`/`right` grid lines run horizontally, `top`/`bottom` ones vertically. ## Series identity `data-graphy-series-index` is the 0-based position of the series in **data order**: the order series first appear in the data, honoring an explicit series order when one is configured. The same value is stamped on a series' shapes (bars, lines, areas, slices, points) and on its legend item, so match them **by attribute, not by visual position**: * Combo charts move line series to the end of the legend visually; the attribute still matches the shapes. * Hiding a series does not shift the other series' indices. * Hover highlighting reorders line z-position without touching the attribute. ```css theme={null} /* The first series (index 0) is the hero: a thicker line with a soft glow… */ .graphy-line[data-graphy-series-index='0'] { stroke-width: 3.5; filter: drop-shadow(0 2px 6px rgb(37 99 235 / 0.4)); } /* …with its legend entry bolded to match */ .graphy-legend-item[data-graphy-series-index='0'] .graphy-legend-label { font-weight: 700; } /* The second series (index 1) reads as a projection: dashed and lighter */ .graphy-line[data-graphy-series-index='1'] { stroke-dasharray: 6 4; stroke-linecap: round; opacity: 0.7; } ``` ## Examples ```css theme={null} /* Dashed grid lines, horizontal ones only */ .graphy-grid-line[data-graphy-axis-side='left'] { stroke: #dcd7fe; stroke-dasharray: 2 4; stroke-linecap: round; } /* Fade legend items for series that are toggled off */ .graphy-legend-item[data-graphy-hidden] { opacity: 0.4; text-decoration: line-through; } /* Rounder corners on horizontal bar charts only */ .graphy-chart[data-graphy-orientation='horizontal'] .graphy-bar { rx: 6; } /* Outlined bars */ .graphy-bar { stroke: #1a1523; stroke-width: 1.5; } /* Tint the plot area and hide its outline */ .graphy-plot-outline { fill: #faf9ff; stroke: none; } /* Emphasize the bottom axis labels */ .graphy-tick-label[data-graphy-axis-side='bottom'] { font-weight: 600; text-transform: uppercase; } /* Rounded, borderless tooltips (top-level rule, see the warning above) */ .graphy-tooltip { border-radius: 12px; border: none; box-shadow: 0 8px 24px rgb(0 0 0 / 0.16); } ``` ## Programmatic access If you generate CSS or selectors in code, import the names instead of hardcoding strings. `GRAPHY_PART_CLASSNAMES` maps part keys to classnames, `GRAPHY_DATA_ATTRIBUTES` maps value keys to attribute names, and `getChartOrientation(chartType)` returns the same `'horizontal' | 'vertical' | undefined` the chart stamps as `data-graphy-orientation`. That's useful when you know a chart's type and want to branch on its orientation without touching the DOM. ```ts theme={null} import { GRAPHY_PART_CLASSNAMES, GRAPHY_DATA_ATTRIBUTES, getChartOrientation } from '@graphysdk/core'; GRAPHY_PART_CLASSNAMES.bar; // 'graphy-bar' GRAPHY_DATA_ATTRIBUTES.seriesIndex; // 'data-graphy-series-index' const firstSeriesBars = `.${GRAPHY_PART_CLASSNAMES.bar}[${GRAPHY_DATA_ATTRIBUTES.seriesIndex}='0']`; // '.graphy-bar[data-graphy-series-index='0']' getChartOrientation('barStacked'); // 'horizontal' getChartOrientation('column'); // 'vertical' getChartOrientation('pie'); // undefined (orientation doesn't apply) ``` # Custom appearance Source: https://docs.graphy.dev/sdk/customisation/custom-appearance Graphy is designed to look good out of the box, but we know that a charting library embedded in your product needs to feel like it belongs there. This page explains the layers of customisation available and when to reach for each one. ## Ways to customise We think about customisation in three tiers, each offering progressively more control: 1. [**Themes**](#themes) — the right tool for the majority of cases. Adjust colours, typography, and spacing through a structured token system without touching any markup. 2. [**Config**](#config) — targeted appearance and behaviour overrides for specific chart types or elements. 3. [**Custom components**](#custom-components) — replace Graphy's internal UI components with your own when you need a completely native feel. ## Themes The `theme` prop on `GraphProvider` is the primary way to make Graphy match your brand. Themes use a layered token system: **base tokens** define raw values, **semantic tokens** map those values to purpose, and **element tokens** target specific components. The SDK includes two built-in themes: `graphyLightTheme` (default) and `graphyDarkTheme` which can be extended to match your preffered style. Override a semantic token to update many elements at once: ```tsx theme={null} import { graphyDarkTheme } from '@graphylib/core'; ; ``` Override an element token when you need surgical control over a single component without affecting anything else: ```tsx theme={null} values: { tooltipBackground: '#1A1A2E', tooltipHeadingTextColor: '#FFFFFF', } ``` Individual properties can also be overriden on `GraphConfig`. This can be useful in situations where you want to enable customisation on a per chart basis. With this approach you can simply store the whole `GraphConfig` object after a change, and restore it later without needing to reconstruct the entire theme each time. ```tsx theme={null} const config: GraphConfig = { data: { ... }, themeOverrides: { graphBackground: '#1e293b', textPrimary: '#f8fafc', gridLineColor: '#334155', }, }; ``` For a full reference of available tokens see the [`GraphTheme`](/sdk/reference/graph-theme) API reference. ## Config The `GraphConfig` object is the primary way to control the appearance and behaviour of individual charts. Where themes set the visual language across your entire application, config is chart-specific. Config covers a broad surface area. Some of the key properties to be aware of are: **`axes`**, **`legend`**, and **`options`** handle the structural and chart type specific configuration such as axis labels, tick formatting, line smoothing, bar sorting, legend position, and similar. **`appearance`** controls visual properties that persist across chart types such as series colours, border style, number formatting and tooltip styles. **`content`** controls the content and visiblility of additional elements around a chart, such as the title, subtitle and caption. **`themeOverrides`** allows specific theme tokens to be overridden at the chart level, for cases where a single chart needs to depart from the global theme (a different background colour, for instance) without requiring a separate theme definition. For a complete reference of all available properties and their types, see the [`GraphConfig`](/sdk/reference/graph-config) API reference. ## Custom components When themes and props aren't enough, you can replace individual internal components with your own implementations. ### How it works Graphy defines a typed interface for each overridable component. You implement a component that satisfies that interface, then register it at the provider level. **1. Graphy's component interface** ```tsx theme={null} export interface SwitchComponentProps { isChecked?: boolean; isDisabled?: boolean; isInvalid?: boolean; onCheckedChange?: (checked: boolean) => void; ref?: AnyRef; } export type SwitchComponent = React.ComponentType; ``` **2. Your custom implementation** ```tsx theme={null} import type { SwitchComponent } from '@graphysdk/core'; import Switch from '@mui/material/Switch'; // An example implementation using Material UI const CustomSwitch: SwitchComponent = (props) => { return ( props.onCheckedChange?.(event.target.checked)} /> ); }; ``` **3. Register at the provider level** ```tsx theme={null} ``` For a full list of components that can be customised see the [`EditorComponentRegistry`](/sdk/reference/editor-component-registry) API reference. # Graph borders Source: https://docs.graphy.dev/sdk/customisation/graph-borders The border properties in `appearance.border` allow you to customize the style, width and color of borders around your graphs. You can create subtle strokes, bold frames, gradient effects or completely custom border styles. ## Quick start Borders are controlled by three properties that work together: ```tsx theme={null} import type { GraphConfig } from '@graphysdk/core'; const config: GraphConfig = { appearance: { border: { width: 12, // Width in pixels style: 'gradient', // Visual style color: '#6366f1', // Color or preset name }, }, }; ``` ## Border width Control the thickness of your border using `width`. Set to `0` to remove the border completely. ```tsx theme={null} appearance: { border: { width: 0; // No border } } appearance: { border: { width: 12; // 12px border } } appearance: { border: { width: 1; // 1px thin border } } ``` Border width in pixels (0-64). Set to `0` for no border. ## Border styles Use `style` to control how your border appears. Each style treats `color` differently, helping frame your data without stealing the spotlight: Border style: - `'none'` - No border - `'custom'` - Apply the custom hex color from `color` as-is - `'tinted'` - Tint the `color` based on the color scheme (darkens in dark mode, lightens in light mode) - `'gradient'` - Generate a gradient starting from `color`, adjusted based on the color scheme - `'preset'` - Use a predefined border style - `'grey'` - Legacy grey border style ### Tinted A color border that's subtly tinted based on your chosen color and the graph's theme: ```tsx theme={null} appearance: { border: { width: 12, style: 'tinted', color: '#3b82f6' // Automatically tinted for contrast } } ``` ### Gradient Automatically generates a linear gradient based on your chosen color and the graph's theme: ```tsx theme={null} appearance: { border: { width: 12, style: 'gradient', color: '#8b5cf6' // Creates theme-adjusted gradient } } ``` ### Grey A neutral border that adapts to light and dark themes. Perfect for subtle definition without drawing attention: ```tsx theme={null} appearance: { border: { width: 1, style: 'grey' // color not required } } ``` ### Preset Use one of twelve predefined gradient styles. Set `color` to one of these preset names: ```tsx theme={null} appearance: { border: { width: 12, style: 'preset', color: 'sunset' // or any preset name below } } ``` **Available presets:** * `'lilac'` - Soft purple to pink * `'neon_pink'` - Vibrant pink gradient * `'blackberry'` - Purple to light blue * `'sun'` - Yellow to orange * `'iceland'` - Teal gradient * `'sunset'` - Yellow to pink * `'ultraviolet'` - Purple gradient * `'purple'` - Rich purple tones * `'ice_cream'` - Pink to peach * `'mint'` - Green to cyan * `'cool'` - Blue to cyan * `'fresh'` - Purple to cyan ### Custom Use your exact color with no adjustments. Unlike `tinted` and `gradient`, `custom` applies your color as-is with no theme-based tinting: ```tsx theme={null} appearance: { border: { width: 12, style: 'custom', color: '#ff6b6b' // Exact color used as-is } } ``` ## Border colors The color to use for the border. Accepts: - **Hex colors**: `"#3b82f6"`, `"#ff6b6b"` - **Preset names** (when `style: 'preset'`): `"lilac"`, `"sunset"`, `"ultraviolet"`, etc. - **Series references**: `"series1"`, `"series2"` (uses color from series styles) The color may be adjusted based on theme and `style` for optimal visual appearance. ### Hex colors Standard hex color values work with `tinted`, `gradient` and `custom` styles: ```tsx theme={null} appearance: { border: { style: 'tinted', color: '#3b82f6' } } ``` ### Preset names When using `style: 'preset'`, reference one of the predefined gradients: ```tsx theme={null} appearance: { border: { style: 'preset', color: 'sunset' } } ``` ### Series references Match your border to a series color by referencing the series key: ```tsx theme={null} const config: GraphConfig = { appearance: { border: { width: 12, style: 'tinted', color: 'series1', }, seriesStyles: { series1: { customColor: '#10b981' }, }, }, }; ``` ## Rounded corners Whether to round the corners of the border. Ignored if `border.width` is 0. ```tsx theme={null} appearance: { border: { width: 12, style: 'gradient', color: '#6366f1' }, hasRoundedCorners: true // Rounded corners } ``` # Graph fonts Source: https://docs.graphy.dev/sdk/customisation/graph-fonts Customize the font families in your graphs ## Setup ### 1. Load your fonts Ensure your fonts are loaded via `@font-face` or a web font service (e.g. Google Fonts). ### 2. Register fonts with GraphProvider ```tsx theme={null} {/* ... */} ``` ### 3. Apply fonts in config ```tsx theme={null} ``` ## Reference ### fontList Array of font definitions passed to `GraphProvider`. Unique identifier referenced by `fontId` in the config. Display name shown in the font picker UI. CSS font-family value with fallbacks (e.g. `'Helvetica Neue', Arial, sans-serif`). ### appearance.textStyle Font and color for titles and subtitles. Accepts `fontId` and `color`. Font and color for axis labels, legends, captions, and annotations. Accepts `fontId` and `color`. # Series colors Source: https://docs.graphy.dev/sdk/customisation/series-colors By default, series are colored using a built-in palette. There are two ways to customize series colors. ## Custom palettes Register palettes with `GraphProvider` and reference them by ID: ```tsx theme={null} const palettes = [ { id: 'brand', name: 'Brand Colors', colors: [ { id: 'blue', hex: '#1e40af' }, { id: 'green', hex: '#059669' }, { id: 'amber', hex: '#d97706' }, ], }, ]; ; ``` Palettes appear in the Editor's color picker, allowing users to switch between them. If a chart has more series than palette colors, colors cycle from the beginning. ## Series style overrides Override individual series via `appearance.seriesStyles` using keys `series1` through `series20`: ```tsx theme={null} appearance: { seriesStyles: { series1: { paletteColorId: 'blue' }, // Reference a palette color series2: { customColor: '#ef4444' }, // Or use a custom hex color series3: { customColor: '#10b981', fillStyle: 'hatched', // 'solid' | 'hatched' lineStyle: 'dashed', // 'solid' | 'dashed' | 'dotted' }, }, } ``` You can combine both approaches: use a palette as the base and override specific series. ## Waterfall chart colors Waterfall charts have special series keys for their four bar types: | Key | Description | | ------------------- | -------------------- | | `waterfallStart` | Starting value bar | | `waterfallPositive` | Positive change bars | | `waterfallNegative` | Negative change bars | | `waterfallTotal` | Final total bar | When using a custom palette without overrides, colors are assigned in order: 1st color → start, 2nd → positive, 3rd → negative, 4th → total. To customize waterfall colors explicitly: ```tsx theme={null} appearance: { seriesStyles: { waterfallStart: { customColor: '#6b7280' }, waterfallPositive: { customColor: '#10b981' }, waterfallNegative: { customColor: '#ef4444' }, waterfallTotal: { customColor: '#3b82f6' }, }, } ``` # Data table Source: https://docs.graphy.dev/sdk/editor/data-table The `DataTable` component provides a spreadsheet-like interface for viewing and editing the data in your graph. It automatically connects to `GraphProvider` and syncs all changes with your graph configuration. ## Basic usage Simply render the `DataTable` component inside a `GraphProvider`: ```tsx theme={null} import type { GraphConfig } from '@graphysdk/core'; import { GraphProvider } from '@graphysdk/core'; import { DataTable } from '@graphysdk/editor'; import { useState } from 'react'; function MyEditor() { const [config, setConfig] = useState({ data: { columns: [ { key: 'month', label: 'Month' }, { key: 'sales', label: 'Sales' }, ], rows: [ { month: 'Jan', sales: 1000 }, { month: 'Feb', sales: 1200 }, { month: 'Mar', sales: 1450 }, ], }, }); return ( setConfig({ ...config, ...update })}>
); } ``` ## Props The `DataTable` component accepts optional configuration props: | Prop | Type | Default | Description | | --------------------- | --------- | ------- | --------------------------------------------------------------- | | `minCols` | `number` | `26` | The minimum number of columns | | `minRows` | `number` | `50` | The minimum number of rows | | `additionalEmptyCols` | `number` | `1` | The number of empty columns to maintain at the end of the table | | `additionalEmptyRows` | `number` | `1` | The number of empty rows to maintain at the end of the table | | `readOnly` | `boolean` | `false` | Makes the table read only | | `inert` | `boolean` | `false` | Makes the table inert (no focus, clicks or hover events) | ## Features * **Responsive** – the table resizes to fit the container * **Cell editing** – click, press Enter or start typing to edit * **Range selection** – select multiple cells with mouse or Shift + arrows * **Keyboard navigation** – arrow keys, Tab, Enter, Escape * **Clipboard** – copy (Cmd/Ctrl+C), paste (Cmd/Ctrl+V), cut (Cmd/Ctrl+X) * **Undo/redo** – Cmd/Ctrl+Z to undo, Cmd/Ctrl+Shift+Z to redo * **Delete** – Backspace or Delete to clear cell values * **State sync** – changes automatically update the graph config ## Integration with editor components The `DataTable` works seamlessly with other editor components. You can combine it with editor panels and other controls: ```tsx theme={null} setConfig({ ...config, ...update })}>
``` ## Low-level primitive For more control, you can use `DataTablePrimitive` which doesn't connect to `GraphProvider` and requires manual state management. This is useful when you need a standalone table outside of the chart editing context. # Editor panels Source: https://docs.graphy.dev/sdk/editor/editor-panels Easy-to-use panels to change how your graph looks and works ## Overview All editor panels must be rendered inside `EditorProvider` and `GraphProvider` to function correctly: ```tsx theme={null} import { GraphProvider, graphyLightTheme } from '@graphysdk/core'; import { EditorProvider, GraphPanel } from '@graphysdk/editor'; import { useState } from 'react'; function MyEditor() { const [config, setConfig] = useState(initialConfig); return ( setConfig({ ...config, ...update })}> {/* ... or other panels ... */} ); } ``` ## Size panel Controls for setting graph size using presets or custom values. ```tsx theme={null} import { SizePanel } from '@graphysdk/editor'; ; ``` ### Props | Prop | Type | Default | Description | | ----------------- | -------- | ------- | --------------------------------------------- | | `defaultExpanded` | `string` | – | The title of the section to expand by default | ### Sections * **`SizePresetsSection`** – Predefined size options (e.g., social media formats) * **`CustomSizeSection`** – Custom width and height inputs ### Custom composition ```tsx theme={null} import { SizePanel, SizePresetsSection, CustomSizeSection } from '@graphysdk/editor'; ; ``` ## Graph panel Core graph configuration including type, legend and number formatting. ```tsx theme={null} import { GraphPanel } from '@graphysdk/editor'; ; ``` ### Props | Prop | Type | Default | Description | | ----------------- | -------- | -------------- | --------------------------------------------- | | `defaultExpanded` | `string` | `"Graph type"` | The title of the section to expand by default | ### Sections * **`GraphTypeSection`** – Choose chart type (column, bar, line, pie, etc.) * **`GraphOptionsSection`** – Chart-specific options * **`LegendPositionSection`** – Control legend visibility and position * **`HeadlineNumberSection`** – Configure headline number display * **`NumberFormatSection`** – Number formatting options (prefix, suffix, decimals) ### Custom composition ```tsx theme={null} import { GraphPanel, GraphTypeSection, GraphOptionsSection, LegendPositionSection, HeadlineNumberSection, NumberFormatSection, } from '@graphysdk/editor'; ; ``` ## Axes panel Customize the main and cross axes. ```tsx theme={null} import { AxesPanel } from '@graphysdk/editor'; ; ``` ### Props | Prop | Type | Default | Description | | ----------------- | -------- | ------- | --------------------------------------------- | | `defaultExpanded` | `string` | – | The title of the section to expand by default | ### Sections * **`MainAxisSection`** – Configure the primary axis (usually y-axis) * **`CrossAxisSection`** – Configure the secondary axis (usually x-axis) ### Custom composition ```tsx theme={null} import { AxesPanel, MainAxisSection, CrossAxisSection } from '@graphysdk/editor'; ; ``` ## Color panel Manage graph colors, backgrounds and border styles. ```tsx theme={null} import { ColorPanel } from '@graphysdk/editor'; ; ``` ### Props | Prop | Type | Default | Description | | ----------------- | -------- | ----------- | --------------------------------------------- | | `defaultExpanded` | `string` | `"Palette"` | The title of the section to expand by default | ### Sections * **`ThemeSection`** – Select from dark or light mode * **`PaletteSection`** – Pick a graph color palette * **`ChartBackgroundSection`** – Set graph background color * **`ChartBorderSection`** – Set graph border style * **`HighlightColorSection`** – Set highlight color ### Custom composition ```tsx theme={null} import { ColorPanel, ThemeSection, PaletteSection, ChartBackgroundSection, ChartBorderSection, HighlightColorSection, } from '@graphysdk/editor'; ; ``` ## Elements panel Control visibility and styling of chart text elements. ```tsx theme={null} import { ElementsPanel } from '@graphysdk/editor'; ; ``` ### Props | Prop | Type | Default | Description | | ----------------- | -------- | ------- | --------------------------------------------- | | `defaultExpanded` | `string` | – | The title of the section to expand by default | ### Sections * **`TextVisibilitySection`** – Toggle visibility of titles, labels and annotations * **`SourceSection`** – Add and edit data source attribution * **`TextSizeSection`** – Adjust font sizes for graph elements ### Custom composition ```tsx theme={null} import { ElementsPanel, TextVisibilitySection, SourceSection, TextSizeSection } from '@graphysdk/editor'; ; ``` ## Annotate panel Add annotations like call-outs and highlights to draw attention to specific data points. ```tsx theme={null} import { AnnotatePanel } from '@graphysdk/editor'; ; ``` ### Sections * **`CallOutSection`** – Text annotations, arrows, boxes and difference arrows * **`HighlightSection`** – Highlight specific data points, lines or groups of data points ### Custom composition ```tsx theme={null} import { AnnotatePanel, CallOutSection, HighlightSection } from '@graphysdk/editor'; ; ``` ### Props #### AnnotatePanel | Prop | Type | Default | Description | | ----------------- | --------------------- | ------- | ------------------------------------------------- | | `defaultExpanded` | `string` | – | The title of the section to expand by default | | `closePanel` | `() => void` | – | Optional callback invoked when panel should close | | `callOutProps` | `CallOutSectionProps` | – | Optional props to pass to the CallOutSection | #### CallOutSection The `CallOutSection` accepts an optional `hiddenButtons` prop to hide specific buttons: | Prop | Type | Description | | --------------- | ----------------- | -------------------------------------------------------------------------------------------- | | `hiddenButtons` | `CallOutButton[]` | Array of button names to hide. Options: `'text'`, `'arrow'`, `'shape'`, `'differenceArrows'` | ```tsx theme={null} import { AnnotatePanel } from '@graphysdk/editor'; // Hide the shape and difference arrows buttons ; ``` Or use `CallOutSection` directly: ```tsx theme={null} import { CallOutSection } from '@graphysdk/editor'; ; ``` ## Power-ups panel Add goals, trend lines and average lines. ```tsx theme={null} import { PowerUpPanel } from '@graphysdk/editor'; ; ``` ### Props | Prop | Type | Default | Description | | ----------------- | -------- | ------- | --------------------------------------------- | | `defaultExpanded` | `string` | – | The title of the section to expand by default | ### Sections * **`GoalPowerUpSection`** – Add goal lines to set targets * **`TrendPowerUpSection`** – Add trend lines to show the trend of a series * **`AveragePowerUpSection`** – Add average lines to show the average value of a series ### Custom composition ```tsx theme={null} import { PowerUpPanel, GoalPowerUpSection, TrendPowerUpSection, AveragePowerUpSection } from '@graphysdk/editor'; ; ``` ## Panel composition All panels support custom composition by passing children. This allows you to: * Reorder sections within a panel * Include only specific sections * Add custom controls alongside built-in sections ```tsx theme={null} import { GraphPanel, GraphTypeSection, NumberFormatSection } from '@graphysdk/editor'; // Custom "Graph" panel with only graph type and number formatting sections {/* GraphOptionsSection, LegendPositionSection and HeadlineNumberSection are excluded */} ; ``` ## Building a custom panel You can build completely custom panels and sections using the `EditorPanel` layout primitives. ### Custom panel structure A custom panel is built using `EditorPanel.Root` and `EditorPanel.Section`: ```tsx theme={null} import { EditorPanel } from '@graphysdk/editor'; export const CustomPanel = () => { return ( {/* import existing sections */} {/* or build your own custom sections from scratch */} {/* controls go here */} ); }; ``` ### EditorPanel.Root props | Prop | Type | Description | | ----------------- | -------- | --------------------------------------------------------------------------------------------------------- | | `defaultExpanded` | `string` | The title of the section to expand by default. If not provided, all collapsible sections start collapsed. | ```tsx theme={null} // Expand "Number format" section by default ``` ### Creating custom sections Sections can have three layouts: `fixed`, `collapsible` or `inline`: ```tsx theme={null} import { EditorPanel } from "@graphysdk/editor"; // Fixed section (always expanded) {/* Controls go here */} // Collapsible section (can be expanded/collapsed) {/* Controls go here */} // Inline section (controls shown inline with title) {/* Controls go here */} ``` # Graph type icon Source: https://docs.graphy.dev/sdk/editor/graph-type-icon The `GraphTypeIcon` component renders SVG icons representing different graph types. ## Basic usage ```tsx theme={null} import { GraphTypeIcon } from '@graphysdk/editor'; ; ``` ## Props | Prop | Description | | ----------- | ------------------------------------ | | `type` | The graph type to display (required) | | `className` | Optional CSS class name for styling | ## Supported icons The component supports all graph types: * `column` – Column graph * `columnStacked` – Stacked column graph * `columnStackedFill` – 100% stacked column graph * `bar` – Bar graph * `barStacked` – Stacked bar graph * `barStackedFill` – 100% stacked bar graph * `line` – Line graph * `areaStacked` – Stacked area graph * `combo` – Combo graph * `pie` – Pie graph * `donut` – Donut graph * `funnel` – Funnel * `heatmap` – Heatmap * `table` – Table * `scatter` – Scatter plot * `bubble` – Bubble plot * `mekko` – Mekko * `waterfall` – Waterfall ## Styling Icons inherit the `currentColor` for their fill, making them easy to theme. You can also pass a `className` for custom styling: ```tsx theme={null}
``` # Editor quick start Source: https://docs.graphy.dev/sdk/editor/index The Graphy Editor provides ready-to-use UI components that let users customize graphs visually. It includes panels for editing data, styling, annotations and more. ## Installation Install the editor package alongside the core SDK: ```bash npm theme={null} npm install @graphysdk/core @graphysdk/editor ``` ```bash yarn theme={null} yarn add @graphysdk/core @graphysdk/editor react react-dom styled-components @tiptap/core @tiptap/extension-blockquote @tiptap/extension-bold @tiptap/extension-document @tiptap/extension-hard-break @tiptap/extension-heading @tiptap/extension-italic @tiptap/extension-link @tiptap/extension-list @tiptap/extension-paragraph @tiptap/extension-strike @tiptap/extension-text @tiptap/extension-text-align @tiptap/extension-text-style @tiptap/extension-underline @tiptap/extensions @tiptap/pm @tiptap/react ``` ```bash pnpm theme={null} pnpm add @graphysdk/core @graphysdk/editor ``` ```bash bun theme={null} bun add @graphysdk/core @graphysdk/editor ``` ## Basic setup The editor requires two providers: `GraphProvider` from `@graphysdk/core` and `EditorProvider` from `@graphysdk/editor`. ```tsx theme={null} import { useState } from 'react'; import { GraphProvider, Graph } from '@graphysdk/core'; import { EditorProvider, GraphPanel } from '@graphysdk/editor'; function MyEditor() { const [config, setConfig] = useState({ data: { columns: [ { key: 'month', label: 'Month' }, { key: 'sales', label: 'Sales' }, ], rows: [ { month: 'Jan', sales: 1000 }, { month: 'Feb', sales: 1200 }, { month: 'Mar', sales: 1100 }, ], }, type: 'column', }); return ( { setConfig(currentValues); }} >
{/* The graph - use mode="editor" for inline editing */} {/* Editor panel */}
); } ``` ## How it works 1. **GraphProvider** wraps your entire editor and receives the `config` and `onChange` handler 2. **EditorProvider** provides context for all editor components 3. **Graph** with `mode="editor"` renders an editable chart (inline title editing, annotations etc.) 4. **Editor panels** (like `GraphPanel`) let users modify the config When users interact with editor panels, the `onChange` callback receives the updated config, which you can save to your state or database. ## Available editor panels The editor includes several pre-built panels: * **[GraphPanel](/sdk/editor/editor-panels#graph-panel)** - Chart type, legend, headline numbers, number formatting * **[AxesPanel](/sdk/editor/editor-panels#axes-panel)** - Axis labels, ranges, tick marks * **[ColorPanel](/sdk/editor/editor-panels#color-panel)** - Themes, color palettes, borders * **[ElementsPanel](/sdk/editor/editor-panels#elements-panel)** - Text visibility, source attribution, text size * **[AnnotatePanel](/sdk/editor/editor-panels#annotate-panel)** - Annotations, highlights, call-outs * **[PowerUpPanel](/sdk/editor/editor-panels#power-ups-panel)** - Goal lines, trend lines, average lines * **[SizePanel](/sdk/editor/editor-panels#size-panel)** - Graph dimensions and presets ## Complete example Here's a more complete editor with multiple panels: ```tsx theme={null} import { useState } from 'react'; import { GraphProvider, Graph } from '@graphysdk/core'; import { EditorProvider, GraphPanel, AxesPanel, ColorPanel, DataTable } from '@graphysdk/editor'; function MyEditor() { const [config, setConfig] = useState({ data: { columns: [ { key: 'category', label: 'Category' }, { key: 'value', label: 'Value' }, ], rows: [ { category: 'A', value: 100 }, { category: 'B', value: 150 }, { category: 'C', value: 120 }, ], }, type: 'column', content: { title: 'Sales by category', }, }); return ( { setConfig(currentValues); }} >
{/* Main content area */}
{/* Editor sidebar */}
); } ``` ## Next steps * See all [GraphProvider props](/sdk/reference/graph-provider) and [EditorProvider props](/sdk/reference/editor-provider) * Explore [editor panels](/sdk/editor/editor-panels) to learn about all available components * Use the [DataTable](/sdk/editor/data-table) component to let users edit data * Learn about [custom panel composition](/sdk/editor/editor-panels#panel-composition) to build custom editors # Stacked area Source: https://docs.graphy.dev/sdk/graph-types/area-stacked Stacked area charts display multiple series as filled areas stacked on top of each other, showing both individual series values and the cumulative total. ## When to use * **Part-to-whole over time** - Show how components contribute to a total * **Cumulative values** - Display running totals * **Multiple series composition** - Compare proportions across time periods ## Basic example ```tsx theme={null} const config: GraphConfig = { type: 'areaStacked', data: { columns: [ { key: 'quarter', label: 'Quarter' }, { key: 'product_a', label: 'Product A' }, { key: 'product_b', label: 'Product B' }, { key: 'product_c', label: 'Product C' }, ], rows: [ { quarter: 'Q1', product_a: 30, product_b: 20, product_c: 15 }, { quarter: 'Q2', product_a: 35, product_b: 25, product_c: 20 }, { quarter: 'Q3', product_a: 40, product_b: 30, product_c: 25 }, { quarter: 'Q4', product_a: 45, product_b: 35, product_c: 30 }, ], }, }; ``` ## Related * [Type options](/sdk/config/type-options) - Line chart options also apply * [Legend](/sdk/config/legend) - Show series labels * [Data labels](/sdk/config/data-labels) - Display values on areas # Bar Source: https://docs.graphy.dev/sdk/graph-types/bar Bar charts display data as horizontal bars. They support three variants: grouped, stacked and 100% stacked. ## When to use * **Comparing categories** - Show differences between discrete groups * **Ranking data** - Display ordered comparisons * **Long category names** - Horizontal orientation accommodates longer labels * **Large number of categories** - Easier to read than vertical columns * **Part-to-whole comparisons** - Use stacked variants to show composition ## Grouped bars Display multiple series side-by-side for easy comparison. ```tsx theme={null} const config: GraphConfig = { type: 'bar', data: { columns: [ { key: 'region', label: 'Region' }, { key: 'q1', label: 'Q1' }, { key: 'q2', label: 'Q2' }, ], rows: [ { region: 'North', q1: 150, q2: 180 }, { region: 'South', q1: 120, q2: 145 }, { region: 'East', q1: 200, q2: 220 }, { region: 'West', q1: 175, q2: 190 }, ], }, }; ``` ### Single series ```tsx theme={null} const config: GraphConfig = { type: 'bar', data: { columns: [ { key: 'country', label: 'Country' }, { key: 'population', label: 'Population' }, ], rows: [ { country: 'China', population: 1412 }, { country: 'India', population: 1408 }, { country: 'United States', population: 333 }, { country: 'Indonesia', population: 275 }, { country: 'Pakistan', population: 231 }, ], }, }; ``` ### Sorted bars Sort bars by value in descending order (single-series only): ```tsx theme={null} const config: GraphConfig = { type: 'bar', options: { sortBars: true, // Sort from highest to lowest }, data: { /* ... */ }, }; ``` ## Stacked bars Stack multiple series end-to-end to show both individual values and cumulative totals. ```tsx theme={null} const config: GraphConfig = { type: 'barStacked', data: { columns: [ { key: 'department', label: 'Department' }, { key: 'full_time', label: 'Full time' }, { key: 'part_time', label: 'Part time' }, { key: 'contract', label: 'Contract' }, ], rows: [ { department: 'Engineering', full_time: 45, part_time: 5, contract: 10 }, { department: 'Sales', full_time: 30, part_time: 15, contract: 5 }, { department: 'Marketing', full_time: 20, part_time: 8, contract: 12 }, { department: 'Support', full_time: 25, part_time: 20, contract: 5 }, ], }, }; ``` ### Stack totals Show the total value at the end of each stacked bar: ```tsx theme={null} const config: GraphConfig = { type: 'barStacked', dataLabels: { showStackTotals: true, }, data: { /* ... */ }, }; ``` ## 100 percent stacked bars Normalise each bar to 100% to show relative proportions rather than absolute values. ```tsx theme={null} const config: GraphConfig = { type: 'barStackedFill', data: { columns: [ { key: 'product', label: 'Product' }, { key: 'online', label: 'Online' }, { key: 'retail', label: 'Retail' }, { key: 'wholesale', label: 'Wholesale' }, ], rows: [ { product: 'Product A', online: 5000, retail: 3000, wholesale: 2000 }, { product: 'Product B', online: 1500, retail: 2500, wholesale: 1000 }, { product: 'Product C', online: 4000, retail: 2000, wholesale: 4000 }, ], }, }; ``` ### Show percentages Display percentage values instead of absolute values: ```tsx theme={null} const config: GraphConfig = { type: 'barStackedFill', dataLabels: { showDataLabels: true, dataLabelFormat: 'percentage', }, data: { /* ... */ }, }; ``` ## Options Sort bars in descending order by value. Only applies to single-series grouped bar charts (`type: 'bar'`). ## Related * [Column](/sdk/graph-types/column) - Vertical bars * [Type options](/sdk/config/type-options) - Bar chart options * [Data labels](/sdk/config/data-labels) - Display values and totals * [Axes](/sdk/config/axes) - Customize axes # Bubble Source: https://docs.graphy.dev/sdk/graph-types/bubble Bubble charts are scatter plots where point size represents a third dimension, allowing you to compare three variables simultaneously. ## When to use * **Three-variable comparisons** - Show relationships between three metrics * **Weighted scatter plots** - Size indicates importance or magnitude * **Market analysis** - Compare products across multiple dimensions * **Portfolio visualization** - Display risk, return and size ## Basic example ```tsx theme={null} const config: GraphConfig = { type: 'bubble', data: { columns: [ { key: 'product', label: 'Product' }, { key: 'marketing_spend', label: 'Marketing spend (£)' }, { key: 'revenue', label: 'Revenue (£)' }, { key: 'profit', label: 'Profit (£)' }, ], rows: [ { product: 'Product A', marketing_spend: 50000, revenue: 200000, profit: 75000 }, { product: 'Product B', marketing_spend: 30000, revenue: 150000, profit: 60000 }, { product: 'Product C', marketing_spend: 80000, revenue: 350000, profit: 120000 }, { product: 'Product D', marketing_spend: 40000, revenue: 180000, profit: 50000 }, ], }, }; ``` ## Data structure Bubble charts require four columns: 1. **Category column** - Labels for each bubble (optional but recommended) 2. **X-axis column** - First numeric variable (horizontal position) 3. **Y-axis column** - Second numeric variable (vertical position) 4. **Size column** - Third numeric variable (bubble size) ## Multiple series Group bubbles by adding more numeric columns: ```tsx theme={null} const config: GraphConfig = { type: 'bubble', data: { columns: [ { key: 'country', label: 'Country' }, { key: 'gdp', label: 'GDP per capita' }, { key: 'life_expectancy', label: 'Life expectancy' }, { key: 'population_europe', label: 'Europe' }, { key: 'population_asia', label: 'Asia' }, ], rows: [ // Each series (Europe, Asia) creates separate bubble groups ], }, }; ``` ## Related * [Scatter](/sdk/graph-types/scatter) - Standard scatter plot * [Type options](/sdk/config/type-options) - Bubble chart options # Column Source: https://docs.graphy.dev/sdk/graph-types/column Column charts display data as vertical bars. They support three variants: grouped, stacked and 100% stacked. ## When to use * **Comparing values across categories** - Show differences between discrete groups * **Time-based comparisons** - Track changes over time periods * **Small to moderate number of categories** - Works best with 3-12 categories * **Multiple series comparison** - Compare several series side-by-side * **Part-to-whole relationships** - Use stacked variants to show composition ## Grouped columns Display multiple series side-by-side for easy comparison. ```tsx theme={null} const config: GraphConfig = { type: 'column', data: { columns: [ { key: 'month', label: 'Month' }, { key: 'revenue', label: 'Revenue' }, { key: 'costs', label: 'Costs' }, ], rows: [ { month: 'Q1', revenue: 150000, costs: 95000 }, { month: 'Q2', revenue: 180000, costs: 105000 }, { month: 'Q3', revenue: 210000, costs: 115000 }, { month: 'Q4', revenue: 240000, costs: 125000 }, ], }, }; ``` ### Single series ```tsx theme={null} const config: GraphConfig = { type: 'column', data: { columns: [ { key: 'month', label: 'Month' }, { key: 'sales', label: 'Sales' }, ], rows: [ { month: 'Jan', sales: 12000 }, { month: 'Feb', sales: 15000 }, { month: 'Mar', sales: 18000 }, { month: 'Apr', sales: 16000 }, { month: 'May', sales: 21000 }, { month: 'Jun', sales: 24000 }, ], }, }; ``` ### Sorted columns Sort columns by value in descending order (single-series only): ```tsx theme={null} const config: GraphConfig = { type: 'column', options: { sortBars: true, // Sort from highest to lowest }, data: { /* ... */ }, }; ``` ## Stacked columns Stack multiple series on top of each other to show both individual values and cumulative totals. ```tsx theme={null} const config: GraphConfig = { type: 'columnStacked', data: { columns: [ { key: 'quarter', label: 'Quarter' }, { key: 'desktop', label: 'Desktop' }, { key: 'mobile', label: 'Mobile' }, { key: 'tablet', label: 'Tablet' }, ], rows: [ { quarter: 'Q1', desktop: 45000, mobile: 28000, tablet: 12000 }, { quarter: 'Q2', desktop: 48000, mobile: 35000, tablet: 15000 }, { quarter: 'Q3', desktop: 50000, mobile: 42000, tablet: 18000 }, { quarter: 'Q4', desktop: 52000, mobile: 48000, tablet: 20000 }, ], }, }; ``` ### Stack totals Show the total value at the top of each stacked column: ```tsx theme={null} const config: GraphConfig = { type: 'columnStacked', dataLabels: { showStackTotals: true, }, data: { /* ... */ }, }; ``` ## 100 percent stacked columns Normalise each column to 100% to show relative proportions rather than absolute values. ```tsx theme={null} const config: GraphConfig = { type: 'columnStackedFill', data: { columns: [ { key: 'year', label: 'Year' }, { key: 'renewable', label: 'Renewable' }, { key: 'natural_gas', label: 'Natural gas' }, { key: 'coal', label: 'Coal' }, ], rows: [ { year: '2020', renewable: 200, natural_gas: 500, coal: 300 }, { year: '2021', renewable: 250, natural_gas: 480, coal: 270 }, { year: '2022', renewable: 320, natural_gas: 450, coal: 230 }, { year: '2023', renewable: 400, natural_gas: 420, coal: 180 }, ], }, }; ``` ### Show percentages Display percentage values instead of absolute values: ```tsx theme={null} const config: GraphConfig = { type: 'columnStackedFill', dataLabels: { showDataLabels: true, dataLabelFormat: 'percentage', }, data: { /* ... */ }, }; ``` ## Options Sort columns in descending order by value. Only applies to single-series grouped column charts (`type: 'column'`). ## Related * [Bar](/sdk/graph-types/bar) - Horizontal bars * [Type options](/sdk/config/type-options) - Column chart options * [Data labels](/sdk/config/data-labels) - Display values and totals * [Axes](/sdk/config/axes) - Customize axes # Combo Source: https://docs.graphy.dev/sdk/graph-types/combo Combo charts combine multiple chart types with two y-axes, allowing you to compare metrics with different units or scales. ## When to use * **Different units** - Compare metrics measured in different units (e.g., revenue in £ vs conversion rate in %) * **Different scales** - Display values with vastly different magnitudes * **Correlation analysis** - Show relationships between two different metrics ## Basic example ```tsx theme={null} const config: GraphConfig = { type: 'combo', data: { columns: [ { key: 'month', label: 'Month' }, { key: 'revenue', label: 'Revenue (£)' }, { key: 'conversion', label: 'Conversion rate (%)' }, ], rows: [ { month: 'Jan', revenue: 50000, conversion: 2.5 }, { month: 'Feb', revenue: 55000, conversion: 2.8 }, { month: 'Mar', revenue: 62000, conversion: 3.1 }, { month: 'Apr', revenue: 58000, conversion: 2.9 }, { month: 'May', revenue: 68000, conversion: 3.4 }, ], }, }; ``` ## Dual y-axes By default, combo charts show two y-axes: * **Left axis** (primary) - First numeric series * **Right axis** (secondary) - Second numeric series ### Configuring axes ```tsx theme={null} const config: GraphConfig = { type: 'combo', axes: { y: { label: 'Revenue (£)', }, y2: { label: 'Conversion rate (%)', }, }, data: { /* ... */ }, }; ``` ### Single y-axis Disable the dual y-axis mode if your series share the same scale: ```tsx theme={null} const config: GraphConfig = { type: 'combo', axes: { hasDualYAxis: false, }, data: { /* ... */ }, }; ``` ## Options Determines how to display the combo chart: - `'grouped-bars'` - Show series as grouped bars side-by-side - `'stacked-bars'` - Stack bars on top of each other - `'lines'` - Display all series as lines ### Display as lines ```tsx theme={null} const config: GraphConfig = { type: 'combo', options: { comboType: 'lines', }, data: { /* ... */ }, }; ``` ## Related * [Type options](/sdk/config/type-options) - Combo chart options * [Axes](/sdk/config/axes) - Configure dual y-axes * [Line](/sdk/graph-types/line) - Line chart options apply # Donut Source: https://docs.graphy.dev/sdk/graph-types/donut Donut charts are pie charts with a hollow center, ideal for displaying a central metric alongside part-to-whole relationships. ## When to use * **Part-to-whole with central metric** - Show breakdown with total or key value in center * **2-6 categories** - Works best with a small number of slices * **Percentage comparison** - Emphasise relative proportions * **Single data series** - Show one breakdown at a time ## Basic example ```tsx theme={null} const config: GraphConfig = { type: 'donut', data: { columns: [ { key: 'category', label: 'Category' }, { key: 'sales', label: 'Sales' }, ], rows: [ { category: 'Electronics', sales: 45000 }, { category: 'Clothing', sales: 32000 }, { category: 'Home & Garden', sales: 28000 }, { category: 'Sports', sales: 18000 }, { category: 'Books', sales: 12000 }, ], }, }; ``` ## Data labels Show category labels and percentages on slices: ```tsx theme={null} const config: GraphConfig = { type: 'donut', dataLabels: { showCategoryLabels: true, showDataLabels: true, dataLabelFormat: 'percentage', }, data: { /* ... */ }, }; ``` ## Related * [Pie](/sdk/graph-types/pie) - Standard pie chart * [Type options](/sdk/config/type-options) - Donut chart options * [Data labels](/sdk/config/data-labels) - Display values and percentages # Funnel Source: https://docs.graphy.dev/sdk/graph-types/funnel Funnel charts display progressively decreasing values across stages, ideal for visualising conversion processes and multi-stage workflows. ## When to use * **Conversion funnels** - Track user journey from awareness to conversion * **Process stages** - Show drop-off at each step * **Progressive reduction** - Display sequential filtering or narrowing ## Basic example ```tsx theme={null} const config: GraphConfig = { type: 'funnel', data: { columns: [ { key: 'stage', label: 'Stage' }, { key: 'users', label: 'Users' }, ], rows: [ { stage: 'Website visits', users: 10000 }, { stage: 'Product views', users: 5000 }, { stage: 'Add to cart', users: 2000 }, { stage: 'Checkout', users: 800 }, { stage: 'Purchase', users: 500 }, ], }, }; ``` ## Conversion rate Show the conversion rate between the first and last stages as a headline number: ```tsx theme={null} const config: GraphConfig = { type: 'funnel', headlineNumbers: { show: 'conversion', }, data: { /* ... */ }, }; ``` The `'conversion'` headline number type is specifically designed for funnel charts. It calculates the ratio between the first and last values. ## Stage data The funnel automatically calculates drop-off between stages. Your data should include: * **First column**: Stage names (categorical) * **Second column**: Values (numeric), ordered from highest to lowest ## Related * [Headline numbers](/sdk/config/headline-numbers) - Show conversion rate * [Data labels](/sdk/config/data-labels) - Display stage values # Heatmap Source: https://docs.graphy.dev/sdk/graph-types/heatmap Heatmaps display data as a grid of colored cells where color intensity represents values, making patterns and correlations easy to spot. ## When to use * **Patterns across two dimensions** - Show relationships between two categorical variables * **Correlation matrices** - Display strength of relationships * **Time-based patterns** - Reveal trends across days, weeks or months * **Large datasets** - Summarise many data points visually ## Basic example ```tsx theme={null} const config: GraphConfig = { type: 'heatmap', data: { columns: [ { key: 'day', label: 'Day' }, { key: 'hour_0', label: '00:00' }, { key: 'hour_6', label: '06:00' }, { key: 'hour_12', label: '12:00' }, { key: 'hour_18', label: '18:00' }, ], rows: [ { day: 'Monday', hour_0: 45, hour_6: 120, hour_12: 350, hour_18: 280 }, { day: 'Tuesday', hour_0: 52, hour_6: 135, hour_12: 380, hour_18: 295 }, { day: 'Wednesday', hour_0: 48, hour_6: 140, hour_12: 420, hour_18: 310 }, { day: 'Thursday', hour_0: 55, hour_6: 145, hour_12: 390, hour_18: 285 }, { day: 'Friday', hour_0: 60, hour_6: 150, hour_12: 450, hour_18: 380 }, ], }, }; ``` ## Data structure Heatmaps require: * **First column**: Row labels (categorical) * **Remaining columns**: Numeric values for each cell Each data column becomes a column in the heatmap grid. ## Data labels Show values in each cell: ```tsx theme={null} const config: GraphConfig = { type: 'heatmap', dataLabels: { showDataLabels: true, }, data: { /* ... */ }, }; ``` ## Related * [Appearance](/sdk/config/appearance) - Customize color palette * [Data labels](/sdk/config/data-labels) - Display cell values # Overview Source: https://docs.graphy.dev/sdk/graph-types/index Set the `type` property on your `GraphConfig`: ```tsx theme={null} const config: GraphConfig = { type: 'line', data: { /* ... */ }, }; ``` ## All types | Type | Description | Best for | | ------------------- | ---------------------------- | --------------------------------- | | `line` | Line chart | Time series, trends | | `areaStacked` | Stacked area chart | Part-to-whole over time | | `bar` | Horizontal bar chart | Comparing categories, ranking | | `barStacked` | Stacked horizontal bars | Composition across categories | | `barStackedFill` | 100% stacked horizontal bars | Percentage breakdowns | | `column` | Vertical column chart | Comparisons, time series | | `columnStacked` | Stacked columns | Composition over time | | `columnStackedFill` | 100% stacked columns | Proportions over time | | `combo` | Dual y-axis chart | Different units/scales | | `pie` | Pie chart | Simple part-to-whole (2-6 slices) | | `donut` | Donut chart | Part-to-whole with central metric | | `funnel` | Funnel chart | Conversion funnels, stages | | `heatmap` | Heatmap | Patterns across two dimensions | | `scatter` | Scatter plot | Correlation, distribution | | `bubble` | Bubble chart | Three-variable comparisons | | `waterfall` | Waterfall chart | Financial analysis, variance | | `mekko` | Mekko chart | Market share analysis | | `table` | Data table | Precise values | ## TypeScript ```tsx theme={null} type Type = | 'line' | 'areaStacked' | 'bar' | 'barStacked' | 'barStackedFill' | 'column' | 'columnStacked' | 'columnStackedFill' | 'combo' | 'pie' | 'donut' | 'funnel' | 'heatmap' | 'scatter' | 'bubble' | 'waterfall' | 'mekko' | 'table'; ``` See [type options](/sdk/config/type-options) for chart-specific configuration. # Line Source: https://docs.graphy.dev/sdk/graph-types/line Line charts display data as a series of points connected by straight or curved lines. They're ideal for showing trends and changes over time. ## When to use * **Time series data** - Track changes over time periods * **Trends** - Show upward or downward patterns * **Continuous measurements** - Display data with no gaps * **Multiple series comparison** - Compare several trends on the same chart ## Basic example ```tsx theme={null} const config: GraphConfig = { type: 'line', data: { columns: [ { key: 'month', label: 'Month' }, { key: 'revenue', label: 'Revenue' }, ], rows: [ { month: 'Jan', revenue: 12000 }, { month: 'Feb', revenue: 15000 }, { month: 'Mar', revenue: 18000 }, { month: 'Apr', revenue: 17000 }, { month: 'May', revenue: 21000 }, ], }, }; ``` ## Multiple series ```tsx theme={null} const config: GraphConfig = { type: 'line', data: { columns: [ { key: 'month', label: 'Month' }, { key: 'revenue', label: 'Revenue' }, { key: 'profit', label: 'Profit' }, ], rows: [ { month: 'Jan', revenue: 12000, profit: 3000 }, { month: 'Feb', revenue: 15000, profit: 4500 }, { month: 'Mar', revenue: 18000, profit: 5400 }, ], }, }; ``` ## Related * [Type options](/sdk/config/type-options) - All line chart options * [Axes](/sdk/config/axes) - Customize axes * [Reference lines](/sdk/config/reference-lines) - Add trendlines and goal lines # Mekko Source: https://docs.graphy.dev/sdk/graph-types/mekko Mekko charts (also called marimekko or market map charts) display stacked bars with variable widths, showing both size and composition simultaneously. ## When to use * **Market share analysis** - Show market size and segment composition * **Portfolio breakdown** - Display size and composition of groups * **Two-dimensional part-to-whole** - Compare both totals and proportions * **Strategic positioning** - Visualise competitive landscape ## Basic example ```tsx theme={null} const config: GraphConfig = { type: 'mekko', data: { columns: [ { key: 'region', label: 'Region' }, { key: 'product_a', label: 'Product A' }, { key: 'product_b', label: 'Product B' }, { key: 'product_c', label: 'Product C' }, ], rows: [ { region: 'North America', product_a: 120, product_b: 80, product_c: 50 }, { region: 'Europe', product_a: 90, product_b: 110, product_c: 40 }, { region: 'Asia Pacific', product_a: 200, product_b: 150, product_c: 100 }, { region: 'Latin America', product_a: 40, product_b: 30, product_c: 20 }, ], }, }; ``` ## Data structure Mekko charts require: * **First column**: Categories (becomes variable-width bars) * **Remaining columns**: Numeric values (stacked segments within each bar) ## Related * [Bar](/sdk/graph-types/bar) - Fixed-width stacked bars * [Column](/sdk/graph-types/column) - Percentage composition # Pie Source: https://docs.graphy.dev/sdk/graph-types/pie Pie charts display data as slices of a circle, showing parts of a whole as proportions. ## When to use * **Simple part-to-whole relationships** - Show how categories contribute to a total * **2-6 categories** - Works best with a small number of slices * **Percentage comparison** - Emphasise relative proportions * **Single data series** - Show one breakdown at a time ## Basic example ```tsx theme={null} const config: GraphConfig = { type: 'pie', data: { columns: [ { key: 'browser', label: 'Browser' }, { key: 'users', label: 'Users' }, ], rows: [ { browser: 'Chrome', users: 12500 }, { browser: 'Safari', users: 8200 }, { browser: 'Firefox', users: 3400 }, { browser: 'Edge', users: 2100 }, { browser: 'Other', users: 1800 }, ], }, }; ``` ## Data labels Show category labels and percentages on slices: ```tsx theme={null} const config: GraphConfig = { type: 'pie', dataLabels: { showCategoryLabels: true, showDataLabels: true, dataLabelFormat: 'percentage', }, data: { /* ... */ }, }; ``` ## Related * [Donut](/sdk/graph-types/donut) - Donut chart with hollow center * [Type options](/sdk/config/type-options) - Pie chart options * [Data labels](/sdk/config/data-labels) - Display values and percentages # Scatter Source: https://docs.graphy.dev/sdk/graph-types/scatter Scatter plots display individual data points on x and y axes, revealing correlations, distributions and outliers. ## When to use * **Correlation analysis** - Identify relationships between two variables * **Distribution patterns** - See how data is spread across ranges * **Outlier detection** - Spot unusual data points * **Large datasets** - Display hundreds or thousands of points ## Basic example ```tsx theme={null} const config: GraphConfig = { type: 'scatter', data: { columns: [ { key: 'hours_studied', label: 'Hours studied' }, { key: 'exam_score', label: 'Exam score' }, ], rows: [ { hours_studied: 2, exam_score: 55 }, { hours_studied: 3, exam_score: 62 }, { hours_studied: 4, exam_score: 68 }, { hours_studied: 5, exam_score: 74 }, { hours_studied: 6, exam_score: 80 }, { hours_studied: 7, exam_score: 85 }, { hours_studied: 8, exam_score: 90 }, ], }, }; ``` ## Multiple series Compare correlations across different groups: ```tsx theme={null} const config: GraphConfig = { type: 'scatter', data: { columns: [ { key: 'age', label: 'Age' }, { key: 'income_male', label: 'Male income' }, { key: 'income_female', label: 'Female income' }, ], rows: [ { age: 25, income_male: 35000, income_female: 33000 }, { age: 30, income_male: 45000, income_female: 43000 }, { age: 35, income_male: 55000, income_female: 54000 }, // ... more data points ], }, }; ``` ## Options Point size in pixels. Set to `'auto'` to automatically determine appropriate size. ### Custom point size ```tsx theme={null} const config: GraphConfig = { type: 'scatter', options: { pointSize: 8, // 8px diameter points }, data: { /* ... */ }, }; ``` ## Trendlines Add a trendline to show the overall correlation: ```tsx theme={null} const config: GraphConfig = { type: 'scatter', referenceLines: { trendline: 'linear', }, data: { /* ... */ }, }; ``` ## Related * [Bubble](/sdk/graph-types/bubble) - Scatter plot with sized points * [Reference lines](/sdk/config/reference-lines) - Add trendlines * [Type options](/sdk/config/type-options) - Scatter plot options # Table Source: https://docs.graphy.dev/sdk/graph-types/table Tables display data in a traditional tabular format with rows and columns, ideal for precise value lookup and detailed data exploration. ## When to use * **Precise values** - When exact numbers are important * **Detailed data exploration** - Allow users to scan specific values * **Supporting charts** - Provide detailed breakdown alongside visual charts * **Many columns** - Display data with numerous variables * **Reference material** - Create data that can be easily copied or referenced ## Basic example ```tsx theme={null} const config: GraphConfig = { type: 'table', data: { columns: [ { key: 'product', label: 'Product' }, { key: 'units_sold', label: 'Units sold' }, { key: 'revenue', label: 'Revenue' }, { key: 'profit_margin', label: 'Profit margin' }, ], rows: [ { product: 'Laptop Pro', units_sold: 1245, revenue: 1867500, profit_margin: 0.32 }, { product: 'Phone X', units_sold: 3521, revenue: 2816800, profit_margin: 0.41 }, { product: 'Tablet Mini', units_sold: 892, revenue: 267600, profit_margin: 0.28 }, { product: 'Watch Smart', units_sold: 2104, revenue: 841600, profit_margin: 0.45 }, ], }, }; ``` ## Formatting Tables automatically format values based on data type: * **Numbers** - Formatted with thousands separators * **Currency** - Detected from symbols (£, \$, €) * **Percentages** - Displayed with % symbol * **Dates** - Formatted according to locale ### Number formatting Control how numbers are displayed: ```tsx theme={null} const config: GraphConfig = { type: 'table', appearance: { numberFormat: { decimalPlaces: 2, abbreviation: 'k', }, }, data: { /* ... */ }, }; ``` ## Column visibility Hide specific columns by setting `_metadata.isHidden`: ```tsx theme={null} const config: GraphConfig = { type: 'table', data: { columns: [ { key: 'id', label: 'ID', _metadata: { isHidden: true } }, { key: 'product', label: 'Product' }, { key: 'sales', label: 'Sales' }, ], rows: [ { id: 1, product: 'Product A', sales: 1000 }, { id: 2, product: 'Product B', sales: 1500 }, ], }, }; ``` ## Combining with charts Tables work well alongside visual charts: ```tsx theme={null} const config: GraphConfig = { type: 'column', // Main visualization // ... chart configuration }; // Render both chart and table <> ; ``` ## Related * [Data structure](/sdk/core/data-structure) - Learn about columns and rows * [Appearance](/sdk/config/appearance) - Number formatting options # Waterfall Source: https://docs.graphy.dev/sdk/graph-types/waterfall Waterfall charts show running totals with up and down bars, ideal for visualising how sequential positive and negative values contribute to a cumulative result. ## When to use * **Financial analysis** - Show income, expenses and profit * **Explaining variance** - Break down differences between starting and ending values * **Incremental changes** - Display step-by-step contributions * **Budget vs actual** - Analyze deviations ## Basic example ```tsx theme={null} const config: GraphConfig = { type: 'waterfall', data: { columns: [ { key: 'category', label: 'Category' }, { key: 'amount', label: 'Amount' }, ], rows: [ { category: 'Starting balance', amount: 10000 }, { category: 'Revenue', amount: 25000 }, { category: 'Operating costs', amount: -8000 }, { category: 'Marketing', amount: -3000 }, { category: 'Tax', amount: -4000 }, { category: 'Ending balance', amount: 20000 }, ], }, }; ``` ## How it works Waterfall charts automatically: * Start with the first value as the base * Show positive values as upward bars (increases) * Show negative values as downward bars (decreases) * Connect bars to show the running total * Display the final cumulative value ## Data structure Provide: * **First column**: Category labels * **Second column**: Numeric values (positive for increases, negative for decreases) The chart calculates running totals automatically. ## Data labels Show values on each bar: ```tsx theme={null} const config: GraphConfig = { type: 'waterfall', dataLabels: { showDataLabels: true, }, data: { /* ... */ }, }; ``` ## Related * [Column](/sdk/graph-types/column) - Standard column chart * [Data labels](/sdk/config/data-labels) - Display bar values # EditorComponentRegistry Source: https://docs.graphy.dev/sdk/reference/editor-component-registry `EditorComponentRegistry` is a list of custom components that can be passed to the `components` prop of [`EditorProvider`](/sdk/reference/editor-provider) ## Example ```tsx theme={null} ``` ## Properties | Key | Interface | | -------- | ------------------------------------------------------------- | | `Select` | [SelectComponent](/sdk/reference/components/select-component) | | `Switch` | [SwitchComponent](/sdk/reference/components/switch-component) | > The set of overridable components will grow over time. If you need a component that isn't listed here, [let us know](mailto:support@graphy.dev). # EditorProvider Source: https://docs.graphy.dev/sdk/reference/editor-provider `EditorProvider` is the context provider for editor components. It manages editor-specific state and must wrap all editor panels, sections and other controls. It should be placed inside a `GraphProvider`. ## Basic usage ```tsx theme={null} import { GraphProvider } from '@graphysdk/core'; import { EditorProvider } from '@graphysdk/editor'; {/* Editor panels / sections / controls go here */} ; ``` ## Props Child components to be wrapped by the provider. List of overrides for internal components. See [`EditorComponentRegistry`](/sdk/reference/editor-component-registry) Custom color configuration for series styling in the editor. By default, the editor automatically generates color inputs based on the data series. Use this prop to override that behavior with custom configurations. **Type:** `Array<{ text: string, seriesConfigKey: string, symbol?: LegendSymbolType, color?: string }>` ```tsx theme={null} const customColorInputs = [ { text: 'Revenue', seriesConfigKey: 'revenue' }, { text: 'Profit', seriesConfigKey: 'profit', symbol: 'bar' }, ]; {/* Editor panels */}; ``` Available theme options to display in the editor theme selector. Each option has a `label` (string or function receiving a translate function), a `value` (`GraphTheme`), and an optional `renderIcon` callback. You can use the pre-built `defaultThemeOptions` which provides light and dark options with icons. ```tsx theme={null} import { graphyLightTheme, graphyDarkTheme, SunIcon, MoonIcon } from '@graphysdk/core'; const themeOptions = [ { label: 'Light', value: graphyLightTheme, renderIcon: () => }, { label: 'Dark', value: graphyDarkTheme, renderIcon: () => }, { label: 'Custom', value: myCustomTheme }, ]; {/* Editor panels */}; ``` Or use the built-in defaults: ```tsx theme={null} import { defaultThemeOptions } from '@graphysdk/editor'; {/* Editor panels */}; ``` Callback invoked when the user changes the graph theme in the editor. ```tsx theme={null} { console.log('Theme changed to:', theme.id); }} > {/* Editor panels */} ``` Customize the editor UI theme (typography, colors, font weights, etc.). Defaults to `editorLightTheme`. You can use the pre-built `editorLightTheme` or `editorDarkTheme`, or create a custom theme. See [EditorTheme](/sdk/reference/editor-theme) for the full type definition and available font tokens. ```tsx theme={null} import { editorLightTheme } from '@graphysdk/editor'; // or editorDarkTheme const customEditorTheme = { ...editorLightTheme, values: { ...editorLightTheme.values, fontSectionTitle: '600 14px Inter', }, }; {/* Editor panels with custom fonts */}; ``` ## Complete example ```tsx theme={null} import { useState } from 'react'; import { GraphProvider, Graph, graphyLightTheme, graphyDarkTheme } from '@graphysdk/core'; import { EditorProvider, GraphPanel, defaultThemeOptions, editorLightTheme } from '@graphysdk/editor'; import type { GraphConfig, GraphTheme } from '@graphysdk/core'; function MyEditor() { const [config, setConfig] = useState({ data: { columns: [ { key: 'category', label: 'Category' }, { key: 'value', label: 'Value' }, ], rows: [ { category: 'A', value: 100 }, { category: 'B', value: 200 }, ], }, type: 'column', }); return ( setConfig({ ...config, ...update })}> { console.log('User selected theme:', theme.id); }} theme={editorLightTheme} >
); } ``` ## Type definitions ```tsx theme={null} interface EditorProviderProps { children: React.ReactNode; customColorInputs?: CustomColorInputConfig[]; themeOptions?: GraphThemeOption[]; onThemeChange?: GraphThemeChangeHandler; theme?: EditorTheme; } interface CustomColorInputConfig { text: string; seriesConfigKey: SeriesConfigKey; symbol?: LegendSymbolType; color?: string; } interface GraphThemeOption { label: string | ((t: TranslateFunction) => string); value: GraphTheme; renderIcon?: () => React.ReactNode; } type GraphThemeChangeHandler = (value: GraphTheme) => void; ``` ## Related * [GraphProvider](/sdk/reference/graph-provider) - State management and configuration * [EditorTheme](/sdk/reference/editor-theme) - Editor theme type and font tokens * [Editor panels](/sdk/editor/editor-panels) - Available editor components * [Editor quick start](/sdk/editor/index) - Getting started with the editor # EditorTheme Source: https://docs.graphy.dev/sdk/reference/editor-theme ## Structure ```tsx theme={null} interface EditorTheme { values: EditorThemeValues; } ``` | Property | Type | Description | | -------- | ------------------- | ------------------------- | | `values` | `EditorThemeValues` | Design tokens (see below) | ## EditorThemeValues All tokens are strings resolved to CSS values at render time. Any valid CSS value is supported, including CSS variables (e.g. `var(--my-brand-color)`). `EditorTheme` inherits the base and semantic tokens from [`GraphTheme`](/sdk/reference/graph-theme#semantic-font-tokens). Modify these tokens in the graph theme to update multiple elements at once. ## Color tokens | Token | Purpose | | --------------------- | ----------------------------------------------- | | `graphTypeButtonIcon` | Icon color for buttons in the graph type picker | ## Font tokens Font tokens are passed to the CSS `font` property and use the CSS `font` [shorthand syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/font), which sets multiple font properties in a single declaration. | Token | Purpose | | --------------------------- | --------------------------------- | | `fontInputUnit` | Unit suffix in numeric inputs | | `fontInlineError` | Inline validation error messages | | `fontToolbarButton` | Toolbar button labels | | `fontGridButtonLabel` | Grid/layout button labels | | `fontTextScalePreview` | Text scale preview labels | | `fontSectionTitle` | Editor panel section titles | | `fontSectionBody` | Editor panel section body text | | `fontControlLabel` | Control and field labels | | `fontAccordionTitle` | Accordion section titles | | `fontAnnotationButton` | Annotation action button text | | `fontPaletteLabel` | Color palette item labels | | `fontHighlightSelected` | Selected highlight mode label | | `fontDataTableAccessory` | Data table accessory text | | `fontNumberControlValue` | Numeric control value display | | `fontTabButtonLabel` | Tab button labels | | `fontSelectedPropertyLabel` | Selected property indicator label | | `fontSwitchLabel` | Toggle switch labels | | `fontDataTableCell` | Data table cell text | | `fontDataTableHeaderCell` | Data table header cell text | # Graph Source: https://docs.graphy.dev/sdk/reference/graph `Graph` is the component that renders the chart. It must be used inside a `GraphProvider`. ## Basic usage ```tsx theme={null} import { GraphProvider, Graph } from '@graphysdk/core'; ; ``` ## Props All props are optional. ### Mode Shortcut prop for setting sensible defaults for the interactivity and behavior of the graph for common use cases. | Mode | `isEditable` | `showTooltips` | `showHoverEffects` | `animateTransitions` | `isExplorable` | | ---------- | ------------ | -------------- | ------------------ | -------------------- | -------------- | | `readonly` | ✗ | ✓ | ✓ | ✓ | ✓ | | `editor` | ✓ | ✓ | ✓ | ✓ | ✓ | | `static` | ✗ | ✗ | ✗ | ✗ | ✗ | When no `mode` is provided, the graph behaves like `readonly` by default. ```tsx theme={null} // Readonly graph with full interactivity (default) // Editable graph // Useful for thumbnails or exports ``` ### Sizing Controls how the graph responds to its container size. - `mode: 'fixed'` - graph has a fixed width and height (default) - `mode: 'responsive'` - graph automatically resizes to fit its parent container - `mode: 'keepAspectRatio'` * graph scales to fit container while maintaining aspect ratio (requires either intrinsic dimensions or aspect ratio) ```tsx theme={null} // Fixed sizing: specific dimensions // Responsive sizing: fills container // Keep aspect ratio: with intrinsic dimensions // Keep aspect ratio: with intrinsic width and aspect ratio // Keep aspect ratio: with intrinsic height and aspect ratio ``` ### Error handling Callback invoked when the graph encounters an error. Useful for logging or analytics. **Signature:** `(info: { error: Error, errorInfo: React.ErrorInfo }) => void` Component to render when the graph encounters an error. Receives the error as a prop. ```tsx theme={null} { console.error('Chart error:', error); }} errorFallback={({ error }) =>
Something went wrong: {error.message}
} /> ``` ### Callbacks Callback invoked when the chart container is resized. **Signature:** `(entries: ResizeObserverEntry[]) => void` ```tsx theme={null} { console.log('Chart resized:', entries[0].contentRect); }} /> ``` ### Custom rendering If `false`, no border will be shown around the graph (this overrides the border styling set in the `GraphConfig`). ```tsx theme={null} // Without border ``` Custom content to display in the chart footer area. ```tsx theme={null} Custom footer content} /> ``` Custom render function for the chart title area. **Signature:** `(props: { isEditable: boolean, titleDocument: JSONContent, onChange: (newTitle: JSONContent) => void }) => React.ReactNode` ```tsx theme={null} } /> ``` Ref to the graph container element. ```tsx theme={null} const graphRef = useRef(null); ; ``` ### Advanced props These props allow fine-grained control over individual behaviors. In most cases, you should use the `mode` prop instead. **Precedence order:** explicit props → `mode` → `GraphConfig` settings → defaults. For example, `showTooltips={false}` will override both the `mode` setting and `appearance.showTooltips` in your config. Whether the graph is editable. Changes are handled by the `GraphProvider.onChange` handler. Whether to show tooltips on hover. Overrides the `appearance.showTooltips` setting in `GraphConfig`. Whether to show hover effects (line markers, bar highlighting, etc.). Whether to animate the graph on initial render and when data changes. Overrides the `appearance.animateTransitions` setting in `GraphConfig`. Whether to allow transient, in-memory interactions for exploring the data. These interactions do not modify the `GraphConfig`. Currently supports: - **Legend toggling** — click legend items to temporarily hide/show series ```tsx theme={null} // Fine-grained control example: editable with no animations // Readonly mode but without tooltips ``` ## Examples ### Responsive dashboard chart ```tsx theme={null} import { GraphProvider, Graph } from '@graphysdk/core'; import type { GraphConfig } from '@graphysdk/core'; const config: GraphConfig = { data: { columns: [ { key: 'month', label: 'Month' }, { key: 'revenue', label: 'Revenue' }, ], rows: [ { month: 'Jan', revenue: 12000 }, { month: 'Feb', revenue: 15000 }, { month: 'Mar', revenue: 18000 }, ], }, type: 'column', }; function DashboardChart() { return (
); } ``` ### Exportable chart with fixed dimensions ```tsx theme={null} function ExportableChart({ config }: { config: GraphConfig }) { const graphRef = useRef(null); const handleExport = async () => { // Use graphRef.current to capture the chart as an image }; return ( ); } ``` ## Type definitions ```tsx theme={null} type GraphSizing = | { mode: 'responsive' } | { mode: 'fixed'; width: number; height: number } | { mode: 'keepAspectRatio'; intrinsicWidth: number; intrinsicHeight: number } | { mode: 'keepAspectRatio'; intrinsicWidth: number; aspectRatio: number } | { mode: 'keepAspectRatio'; intrinsicHeight: number; aspectRatio: number }; ``` ```tsx theme={null} interface GraphTitleRenderProps { /** Whether the graph is in edit mode */ isEditable: boolean; /** The current title as a TipTap JSONContent document */ titleDocument: JSONContent; /** Callback to update the title */ onChange: (newTitle: JSONContent) => void; } ``` ## Migrating from older versions ### `isInteractive` prop (removed in `0.0.62`) The `isInteractive` prop has been replaced by the `mode` prop. | Before | After | | --------------------------------- | ------------------------------- | | `` (no prop) | `` — no changes needed | | `` | `` — remove the prop | | `` | `` | ### `disableAnimation` prop (removed in `0.0.62`) The `disableAnimation` prop has been replaced by the `mode` prop or `animateTransitions`. | Before | After | | ------------------------------------ | ------------------------------------------------------------------- | | `` (no prop) | `` — no changes needed | | `` | `` — remove the prop | | `` | `` or `` | ### `isEditable` prop The `isEditable` prop is still supported but we recommend using `mode` instead for clarity. | Before | After | | ------------------------------ | --------------------------- | | `` | `` | | `` | `` | ## Related * [GraphProvider](/sdk/reference/graph-provider) - State management and configuration * [Error Handling](/sdk/advanced/error-handling) - Error handling patterns # GraphConfig Source: https://docs.graphy.dev/sdk/reference/graph-config ## Overview `GraphConfig` is the main configuration object for creating and customising graphs in the Graphy SDK. It combines data, visual styling, text content and interactive features into a single, strongly-typed configuration. ```tsx theme={null} import type { GraphConfig } from '@graphysdk/core'; const config: GraphConfig = { data: { /* ... */ }, type: 'column', // ... other properties }; ``` ## Schema structure ### Required properties Data to visualise. Contains columns and rows. See [data structure](/sdk/core/data-structure) for detailed documentation. ### Optional properties Graph type to render. Defaults to `'column'` if not specified. See [graph types](/sdk/graph-types/index) for all available types. Chart type-specific options (line thickness, smooth lines, bar sorting, etc.). See [type options](/sdk/config/type-options) for details. Axis configuration for x, y and secondary y axes. See [axes configuration](/sdk/config/axes) for details. Legend position and visibility. See [legend configuration](/sdk/config/legend) for details. Visual styling including colors, borders, backgrounds, text styles and number formatting. See [appearance configuration](/sdk/config/appearance) for details. Theme-level customization. Overrides specific theme values like `graphBackground`, `textColor`, etc. See [GraphTheme schema](/sdk/reference/graph-theme) for available properties. Text content for title, subtitle, caption, data source, and the optional Made with Graphy provenance mark. See [content configuration](/sdk/config/content) for details — notably `isBrandMarkHidden` (hidden by default; set `false` to opt in). Summary metrics displayed prominently on the chart. See [headline numbers](/sdk/config/headline-numbers) for details. Configuration for value labels displayed on chart elements. See [data labels](/sdk/config/data-labels) for details. Visual annotations layered on top of the chart (stickers, tooltips, highlights, text, arrows, shapes). See [annotations](/sdk/config/annotations) for details. Goal lines, trendlines and average lines. See [reference lines](/sdk/config/reference-lines) for details. ## Complete example ```tsx theme={null} import type { GraphConfig } from '@graphysdk/core'; const config: GraphConfig = { // Data (required) data: { columns: [ { key: 'quarter', label: 'Quarter' }, { key: 'revenue', label: 'Revenue' }, { key: 'profit', label: 'Profit' }, ], rows: [ { quarter: 'Q1', revenue: 100000, profit: 25000 }, { quarter: 'Q2', revenue: 120000, profit: 30000 }, { quarter: 'Q3', revenue: 135000, profit: 35000 }, { quarter: 'Q4', revenue: 150000, profit: 42000 }, ], }, // Graph type type: 'line', // Type-specific options options: { isSmoothLine: true, showPoints: true, lineThickness: 2, }, // Axes configuration axes: { x: { label: 'Fiscal quarter', }, y: { label: 'Amount (£)', min: 0, }, showGridLines: true, }, // Legend legend: { position: 'right', }, // Appearance appearance: { paletteId: 'corporate', seriesStyles: { series1: { customColor: '#1e40af', lineStyle: 'solid', }, series2: { customColor: '#059669', lineStyle: 'solid', }, }, border: { style: 'tinted', color: '#1e40af', width: 8, }, hasRoundedCorners: true, textScale: 1, numberFormat: { decimalPlaces: 0, abbreviation: 'k', }, showTooltips: true, animateTransitions: true, }, // Text content content: { title: 'Quarterly financial performance', subtitle: 'FY 2024', caption: 'All figures are preliminary', source: { label: 'Finance department', url: 'https://company.com/finance', }, // Opt in to the Made with Graphy badge (hidden by default at low level). isBrandMarkHidden: false, }, // Headline numbers headlineNumbers: { show: 'current', compareWith: 'previous', size: 'large', }, // Data labels dataLabels: { showDataLabels: false, }, // Reference lines referenceLines: { goalLine: { target: 140000, label: 'Target', }, trendline: 'linear', }, }; ``` ## Type definitions The `GraphConfig` type is defined using Zod for runtime validation: ```tsx theme={null} export const GraphConfig = z.object({ data: Data, type: Type.optional(), options: Options.optional(), axes: Axes.optional(), legend: Legend.optional(), appearance: Appearance.optional(), themeOverrides: GraphThemeOverrides.optional(), content: Content.optional(), headlineNumbers: HeadlineNumbers.optional(), dataLabels: DataLabels.optional(), annotations: Annotations.optional(), referenceLines: ReferenceLines.optional(), }); export type GraphConfig = z.infer; ``` ## Using with GraphProvider Pass your config to `GraphProvider`: ```tsx theme={null} import { GraphProvider, Graph } from '@graphysdk/core'; function App() { return ( ); } ``` # GraphProvider Source: https://docs.graphy.dev/sdk/reference/graph-provider `GraphProvider` is the root component that wraps your graphs and provides configuration, theming and state management. ## Basic usage ```tsx theme={null} import { useState } from 'react'; import { GraphProvider, Graph } from '@graphysdk/core'; import type { GraphConfig } from '@graphysdk/core'; function MyGraph() { const [config, setConfig] = useState(); return ( { setConfig((currentValues) => ({ ...currentValues, ...update })); }} > ); } ``` ## Props Components to render within the provider. Typically includes `Graph` and optionally editor components. The graph configuration object. See [GraphConfig schema](/sdk/reference/graph-config) for all available options. ```tsx theme={null} const config: GraphConfig = { data: { columns: [ { key: 'month', label: 'Month' }, { key: 'sales', label: 'Sales' }, ], rows: [ { month: 'Jan', sales: 1000 }, { month: 'Feb', sales: 1200 }, ], }, type: 'column', content: { title: 'Monthly sales', }, }; ; ``` Callback invoked when the graph configuration changes (typically from editor interactions). **Signature:** `(changedValues: Partial, currentValues: GraphConfig) => void` * `changedValues` - Only the properties that changed * `currentValues` - The complete updated configuration ```tsx theme={null} { console.log('Changed:', changedValues); console.log('Current:', currentValues); setConfig(currentValues); }} > ``` Custom theme for the graph. See [GraphTheme reference](/sdk/reference/graph-theme) for all available properties. Defaults to `graphyLightTheme` if not provided. ```tsx theme={null} import { graphyDarkTheme } from '@graphysdk/core'; ; ``` Array of custom fonts available for use in graphs. Each font definition includes an `id`, `label` and `fontFamily`. See [Fonts](/sdk/customisation/graph-fonts) for usage details. ```tsx theme={null} const fontList = [ { id: 'custom-serif', label: 'Custom Serif', fontFamily: 'Garamond, Baskerville, serif', }, ]; ; ``` Array of custom color palettes available for graphs. Each palette includes an `id`, `name` and array of `colors`. See [Custom color palettes](/sdk/customisation/series-colors#custom-palettes) for usage details. ```tsx theme={null} const customPalettes = [ { id: 'brand', name: 'Brand Palette', colors: [ { id: '1', hex: '#3b82f6', name: 'Blue' }, { id: '2', hex: '#ef4444', name: 'Red' }, ], }, ]; ; ``` Locale for UI text (editor labels, buttons, etc.). Accepts `'en-GB'` or `'en-US'`. Defaults to `'en-US'`. Locale for formatting numbers and dates in the UI. Accepts `'en-GB'` or `'en-US'`. Defaults to `'en-US'`. This is different from `data._metadata.parsingLocale`, which controls how data is parsed. This prop controls how values are displayed in the editor UI. ```tsx theme={null} ``` Advanced: Maps canvas color IDs to CSS variable names for custom styling. Advanced: Override specific translation strings in the UI. Use this to customize text labels in the editor. **Type:** `Partial>>` ## Complete example ```tsx theme={null} import { useState } from 'react'; import { GraphProvider, Graph, graphyDarkTheme } from '@graphysdk/core'; import type { GraphConfig } from '@graphysdk/core'; const fontList = [ { id: 'custom-sans', label: 'Custom Sans', fontFamily: 'Helvetica Neue, Arial, sans-serif', }, ]; const customPalettes = [ { id: 'brand', name: 'Brand Palette', colors: [ { id: '1', hex: '#3b82f6', name: 'Blue' }, { id: '2', hex: '#10b981', name: 'Green' }, ], }, ]; function MyGraph() { const [config, setConfig] = useState({ data: { columns: [ { key: 'category', label: 'Category' }, { key: 'value', label: 'Value' }, ], rows: [ { category: 'A', value: 100 }, { category: 'B', value: 150 }, ], }, type: 'column', appearance: { paletteId: 'brand', textStyle: { body: { fontId: 'custom-sans', }, }, }, }); return ( { setConfig(currentValues); }} theme={graphyDarkTheme} fontList={fontList} customPalettes={customPalettes} uiLocale="en-GB" formattingLocale="en-GB" > ); } ``` ## Type definitions ```tsx theme={null} type Locale = 'en-GB' | 'en-US'; interface GraphProviderProps { children?: React.ReactNode; config?: GraphConfig; onChange?: (changedValues: Partial, currentValues: GraphConfig) => void; uiLocale?: Locale; formattingLocale?: Locale; fontList?: FontList; customPalettes?: CustomPaletteCatalog; canvasColorToVariableName?: CanvasColorToVariableName; theme?: GraphTheme; i18nOverrides?: Partial>>; } ``` ## Related * [Graph](/sdk/reference/graph) - Chart rendering component * [EditorProvider](/sdk/reference/editor-provider) - Editor state management # GraphTheme Source: https://docs.graphy.dev/sdk/reference/graph-theme ## Structure ```tsx theme={null} interface GraphTheme { id: string; colorScheme: 'light' | 'dark'; values: GraphThemeValues; canvasColors: GraphThemeCanvasColor[]; defaultAnnotationColorIds: GraphThemeAnnotationColorIds; } ``` | Property | Type | Description | | --------------------------- | ------------------------------ | ------------------------------------------------------ | | `id` | `string` | Unique identifier for this theme | | `colorScheme` | `'light'` \| `'dark'` | Hint for deriving colors (tinted backgrounds, borders) | | `values` | `GraphThemeValues` | Design tokens (see below) | | `canvasColors` | `GraphThemeCanvasColor[]` | Colors available in annotation color picker | | `defaultAnnotationColorIds` | `GraphThemeAnnotationColorIds` | Default colors for new annotations | ## GraphThemeCanvasColor ```tsx theme={null} interface GraphThemeCanvasColor { id: string; // Unique ID, persists across theme changes label?: string; // Display name in color picker value: string; // Hex color } ``` ## GraphThemeAnnotationColorIds ```tsx theme={null} interface GraphThemeAnnotationColorIds { arrowStroke?: string; // Canvas color ID for arrow strokes shapeFill?: string; // Canvas color ID for shape fills } ``` ## GraphThemeValues All tokens are strings resolved to CSS values at render time. Any valid CSS value is supported, including CSS variables (e.g. `var(--my-brand-color)`). Tokens follow an inheritance hierarchy: **Base Tokens** define foundational primitives, **Semantic Tokens** map those primitives to contextual meaning and can be used to update multiple elements at once. **Element Tokens** target specific UI components for more fine-grained control. ## Colors ### Base colors | Token | | | | | ---------- | ---------------- | ------------- | --------- | | `white` | `black` | `transparent` | | | `grey100` | `grey95` | `grey90` | | | `grey85` | `grey80` | `grey75` | | | `grey70` | `grey60` | `grey50` | | | `grey0` | `greyGradient80` | | | | `green60` | `green50` | | | | `red60` | `red50` | | | | `amber70` | `amber50` | `amber40` | `amber30` | | `blue80` | `blue60` | | | | `purple50` | `purple30` | | | ### Semantic color tokens | Token | Purpose | | ----------------------- | ------------------------------------------------------ | | `brand` | Brand accent color | | `success` | Success states | | `warning` | Warning states | | `alert` | Error/alert states | | `textPrimary` | Primary text | | `textSecondary` | Secondary text | | `textDisabled` | Disabled text | | `iconPrimary` | Primary icon color | | `iconSecondary` | Secondary icon color | | `iconStickerBackground` | Background color for icons with a "sticker" appearance | | `sunkenBackground` | Recessed surfaces | | `defaultBackground` | Default surfaces | | `raisedBackground` | Elevated surfaces | | `overlayBackground` | Overlay/modal backgrounds | | `overlayBorderGradient` | Overlay border gradient | | `border100` | Full opacity borders | | `border10` | Low opacity borders | ### Element color tokens | Token | Purpose | | -------------------------------- | ------------------------ | | **Graph** | | | `graphBackground` | Chart background | | **Grid** | | | `gridLineColor` | Grid lines | | `hoverGuideLineColor` | Hover guide lines | | `originLineColor` | Origin/zero line | | `targetLineColor` | Goal/target line | | `targetLineMarkerColor` | Goal line marker | | **Legend** | | | `legendBackground` | Legend background | | `legendBorderColor` | Legend border | | `legendTextColor` | Legend text | | `dimmedSeriesLabelTextColor` | Dimmed series label text | | `dimmedSeriesLabelLineColor` | Dimmed series label line | | **Trends** | | | `trendPositiveColor` | Positive trend indicator | | `trendNegativeColor` | Negative trend indicator | | **Tooltips** | | | `tooltipBackground` | Tooltip background | | `tooltipBorderColor` | Tooltip border | | `tooltipHeadingTextColor` | Tooltip heading | | `tooltipLabelTextColor` | Tooltip labels | | `tooltipValueTextColor` | Tooltip values | | **Annotations** | | | `defaultArrowAnnotationColor` | Default arrow color | | `annotationFrameBorderColor` | Annotation frame border | | `annotationMenuTriggerIconColor` | Annotation menu icon | | **Chart-specific** | | | `heatmapEmptyTileBackground` | Heatmap empty cells | ## Fonts Font tokens are passed to the CSS `font` property and use the CSS `font` [shorthand syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/font), which sets multiple font properties in a single declaration. ### Base font tokens Foundational font properties. Combine these to build higher-level tokens. | Token | | -------------------- | | **Font families** | | `fontFamilyDefault` | | `fontFamilyHeading` | | **Font weights** | | `fontWeightRegular` | | `fontWeightMedium` | | `fontWeightSemibold` | | `fontWeightBold` | | **Font sizes** | | `fontXxs` | | `fontXs` | | `fontSm` | | `fontMd` | | `fontLg` | | `fontXl` | ### Semantic font tokens Named text styles built from base tokens. Override these to update the font style across all elements that use them. | Token | Purpose | | ---------------- | --------------------------- | | `fontEditorBody` | Body text for editable text | | `fontHeadingSm` | Smallest heading size | | `fontHeadingMd` | Standard heading size | | `fontHeadingLg` | Large heading size | ### Element font tokens Per-component font tokens that reference semantic tokens. Override these to adjust typography for a specific element without affecting others. | Token | Purpose | | ---------------------------- | ----------------------------- | | **Axes** | | | `fontTickLabel` | Axis tick labels | | `fontAxisLabel` | Axis title labels | | **Data labels** | | | `fontDataLabel` | In-chart data labels | | `fontStackTotal` | Stacked bar total labels | | **Legend** | | | `fontLegendLabel` | Legend item labels | | `fontSeriesLabel` | Series labels | | **Chart tooltips** | | | `fontTooltipHeading` | Tooltip heading | | `fontTooltipLabel` | Tooltip row labels | | `fontTooltipFooter` | Tooltip footer text | | `fontJumboTooltipLabel` | Jumbo tooltip label | | `fontJumboTooltip` | Jumbo tooltip value | | `fontMiniTooltipLabel` | Mini tooltip label | | `fontMiniTooltipFooter` | Mini tooltip footer | | `fontTooltipCaption` | Tooltip caption | | `fontTooltipCaptionSmall` | Small tooltip caption | | **Trends** | | | `fontTrendTag` | Trend tag text | | `fontTrendTagSmall` | Small trend tag text | | **Goal lines** | | | `fontGoalLineLabel` | Goal/target line labels | | **Pie charts** | | | `fontPieLabel` | Pie segment labels | | `fontPieChartTotal` | Pie chart total text | | **Difference arrows** | | | `fontDifferenceArrowSmall` | Small difference arrow label | | `fontDifferenceArrowMedium` | Medium difference arrow label | | `fontDifferenceArrowLarge` | Large difference arrow label | | **Button** | | | `fontButton` | Button text | | **Forms** | | | `fontInput` | Input field text | | `fontInputLabel` | Input field labels | | `fontSelectLabel` | Select dropdown labels | | `fontSelectDescription` | Select dropdown descriptions | | `fontColorSelectLabel` | Color picker labels | | **Menus** | | | `fontMenuTitle` | Menu title | | `fontMenuGroupTitle` | Menu group heading | | `fontMenuItemLabel` | Menu item primary label | | `fontMenuItemLabelSecondary` | Menu item secondary label | | **UI tooltips** | | | `fontUITooltip` | UI tooltip text | | `fontUITooltipSecondary` | UI tooltip secondary text | | **Error boundary** | | | `fontErrorBoundaryTitle` | Error boundary title | | `fontErrorBoundaryMessage` | Error boundary message | | **Tables** | | | `fontTableCell` | Table cell text | | `fontTableHeaderCell` | Table header cell text | | **Footer** | | | `fontSourceLabel` | Source label text | | `fontSourceLink` | Source link text | | **Text editor** | | | `fontTextEditorH1` | Text editor H1 heading | | `fontTextEditorH2` | Text editor H2 heading | | `fontTextEditorH3` | Text editor H3 heading | | `fontTextEditorH6` | Text editor H6 heading | | `fontTextEditorBody` | Text editor body text | | `fontTextEditorLink` | Text editor link text | | **Highlight mode** | | | `fontHighlightModeTitle` | Highlight mode title | | `fontHighlightModeSubtitle` | Highlight mode subtitle | | **Editor panel** | | | `fontEditorSectionTitle` | Editor panel section title | | `fontEditorControlLabel` | Editor panel setting label | | `fontEditorControlValue` | Editor panel control's value | | `fontEditorCaption` | Editor panel unit or caption |