---
title: "Charts: bar, line, pie and scatter over a query"
description: "The add-on @skaile/forge-editor-charts: the Block type chart, its configuration and the query it derives, the pure Vega-Lite specification, the node view with a chart editor for Vue and React, and the render target chart-svg."
canonical_url: "https://skaile-ai.github.io/forge-editor/guides/charts"
---
# Charts: bar, line, pie and scatter over a query

> The add-on @skaile/forge-editor-charts: the Block type chart, its configuration and the query it derives, the pure Vega-Lite specification, the node view with a chart editor for Vue and React, and the render target chart-svg.

The add-on `@skaile/forge-editor-charts` draws charts from the Blocks of the workspace. A **chart** is a Typed block of the Block type `chart`. It stores its **configuration** only (`meta.chart`, a [Block property](https://skaile-ai.github.io/forge-editor/guides/block-properties) that has no form field); the numbers are the answer to a [query](https://skaile-ai.github.io/forge-editor/guides/data-views) whenever the chart is drawn, so a change of a task changes the chart.

<table>
<thead>
  <tr>
    <th>
      Piece
    </th>
    
    <th>
      What it does
    </th>
    
    <th>
      Where
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        chartBlockTypes(locale)
      </code>
    </td>
    
    <td>
      the Block type <code>
        chart
      </code>
    </td>
    
    <td>
      editor and server
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        toQuery(config)
      </code>
    </td>
    
    <td>
      the <code>
        DataQuery
      </code>
      
       a chart asks: its own query plus group and aggregates
    </td>
    
    <td>
      anywhere
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        toVegaLite(config, result)
      </code>
    </td>
    
    <td>
      the pure Vega-Lite specification of the chart
    </td>
    
    <td>
      anywhere
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        chartNodeViews
      </code>
    </td>
    
    <td>
      the node view that draws the chart and carries the chart editor
    </td>
    
    <td>
      editor (<code>
        /vue
      </code>
      
      , <code>
        /react
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        chartTargets({ answer })
      </code>
    </td>
    
    <td>
      the <a href="https://skaile-ai.github.io/forge-editor/guides/render">
        render target
      </a>
      
       <code>
        chart-svg
      </code>
    </td>
    
    <td>
      server
    </td>
  </tr>
</tbody>
</table>

## The configuration

```ts
interface ChartConfig {
  type: 'bar' | 'line' | 'pie' | 'scatter'
  /** Which Blocks the chart is drawn from (Block types, filter). */
  query: DataQuery
  /** The x axis (bar, line, scatter) or the slices (pie): a property key or a pseudo-field such as `$type`. */
  x: string
  /** What is measured: an aggregate of a field, named `as`. */
  series: { field?: string, fn: AggregateFn, as: string }[]
  color?: string
  stack?: boolean
}
```

`toQuery(config)` copies `config.query` and adds `group: { field: config.x, hideEmpty: true }`, one aggregate per series and a row limit, and selects only the fields the chart reads. The Block index answers the grouping (`backend.index.query`), so a backend that groups in the database sends only the rows the chart needs. A scatter chart has no grouping: it reads the rows. Each group has one point per series: `count` counts the rows, `sum`, `avg`, `min` and `max` read the numbers of `field`. Blocks without a value in the x field are left out of the chart.

The Block's own chart in the kitchen sink counts tasks per status:

```json
{ "type": "bar", "query": { "v": 1, "source": { "kind": "blocks", "blockType": "task" } }, "x": "status", "series": [{ "fn": "count", "as": "Anzahl" }] }
```

## The Vega-Lite specification

`toVegaLite(config, result)` returns a Vega-Lite 6 specification as a plain object. It is pure: the same configuration and answer give the same specification, and it carries no size. The data are the points in long form (`x`, `series`, `value`), so any number of series share one encoding. `bar` stacks several series (or puts them side by side with `stack: false`), `line` takes a time axis when every x value is a date and a number axis for numbers, `pie` shows the first series, `scatter` plots two numeric fields. A single series takes `color`; several take the palette and a legend. Both the node view (`vega-embed`) and the server target (`vega`) draw this one specification.

## Install and wire

```ts
import { defaultBlockTypes } from '@skaile/forge-editor/composites'
import { chartBlockTypes } from '@skaile/forge-editor-charts/client'
import { chartNodeViews } from '@skaile/forge-editor-charts/vue' // or '/react'

const editor = composeEditor({
  content: 'rich',
  blockTypes: [...defaultBlockTypes(t), ...chartBlockTypes('de')],
  // The node views by Block type id: a `chart` block draws its chart, every other type keeps its own view.
  blockNodeViews: chartNodeViews,
})
```

`blockNodeViews` is an option of the composites feature (`composeEditor`, `AdvancedEditor`, `ComposedSurface`): a node view per Block type id that replaces the standard Typed block view for that type. The title, the chrome and the content of the block stay; the view adds the chart.

```ts
import { createRenderer, markdownTarget } from '@skaile/forge-editor/adapters/render'
import { answerFromDocument, chartBlockTypes, chartTargets } from '@skaile/forge-editor-charts/server'

// installEditorServer({ ports: { render }, blockTypes })
const answer = answerFromDocument({ load: async id => (await getDocument(id))?.content ?? null, blockTypes: [...serverBlockTypes] })
export const render = createRenderer([markdownTarget(), ...chartTargets({ answer })])
```

The entry `@skaile/forge-editor-charts/client` reaches neither `vega` nor the render target; `vega-embed` loads when the first chart draws. The `/server` entry reaches no editor code and needs no DOM.

## In the editor

The node view asks `backend.index.query` (the Blocks of every Document the user may read) and, for a backend without it, answers the same query over the Blocks of the open Document. It asks again while the chart is visible: after a change of the block or of the Blocks (debounced), and every few seconds, because Blocks of other Documents change without the editor hearing of it. A query the Block index refuses shows its message instead of a chart.

An editable chart has a **Diagramm einstellen** button that opens the chart editor under the chart: the chart type, the x field, the series (aggregate, field, name), the colour, stacking, and the library's [query builder](https://skaile-ai.github.io/forge-editor/guides/data-views) for the Blocks the chart is drawn from. Every change writes `meta.chart` through `setBlockProperty`, one key, so collaborators' other properties survive.

## Render target

`chartTargets({ answer })` gives `chart-svg` for a Block of the type `chart`: the query is answered by `answer` (`answerFromDocument` reads the Blocks of the Document the render is for), the specification is compiled with `vega-lite`, and `vega` renders the SVG without a DOM or a canvas. The file is named after the chart's title. A render of any other block fails with a message the client shows.

## Strings

The add-on keeps its own small catalog in German and English (`CHARTS_MESSAGES`, `createChartsTranslator(locale, overrides)`), because the library's message keys are closed over the library's own strings. `chartBlockTypes(locale)` takes the language; the node view reads it from the editor context.

## In the demo app

The charts always draw on `/kitchen-sink/databases`: the page imports the add-on's `/client` (`chartBlockTypes`) and `/vue` (`chartNodeViews`) entries itself, whatever `NUXT_PUBLIC_ADDONS_CHARTS` says, and gives its editor a backend without a Block index, so the charts and the data views beside them answer from the open document. Its „Task tracker“ sample has eight tasks, two data views and two charts over them. `NUXT_PUBLIC_ADDONS_CHARTS=1` (see `.env.example`; a runtime flag, read by the server and the browser alike) adds the server's target `chart-svg`, the SVG export; without it the page shows a hint card. The documents under `/documents` load the Block type and the node view only with the flag on. See [Kitchen sink: learning path](https://skaile-ai.github.io/forge-editor/guides/advanced-nuxt-example#databases).


## Sitemap

See the full [sitemap](https://skaile-ai.github.io/forge-editor/sitemap.md) for all pages.
