# 06 · Chart Builder The Chart Builder is a visual, no-JSON way to compose a Vega-Lite chart from a selected dataset. The user picks a mark type and maps the dataset's columns to encoding channels; the builder produces a complete Vega-Lite spec and saves it as a new snippet that references the dataset. It is intended for users who want to start a chart quickly without hand-writing JSON in the _Spec Editor & Draft/Published Workflow_. > **Design level — "smart + guarded" (Tier B).** The builder is mark-first and stays within the inputs below, but it is not a dumb composer: it picks a sensible default mark for the data shape, offers only field types valid for each column, keeps unsuitable channel mappings out of reach, and surfaces non-blocking guidance for encodings that render poorly. These behaviors are derived from cross-source chart-choice research recorded in [`docs/chart-builder-research.md`](../chart-builder-research.md) (the convergence of Draco, Voyager, the FT Visual Vocabulary, and Datawrapper). The richer "intent-first" front door (ask _what do you want to show?_ and recommend a chart) is explicitly out of scope for now and noted there as a future tier. ## Opening - Launched from a selected dataset in the _Datasets_ manager via that dataset's "build chart" action. - Opens as a modal dialog over the application; the URL reflects the dataset's "build" action so the open builder is shareable/restorable (see _Application Shell & Navigation_). - On open, the builder loads the selected dataset, displays its name, and pre-populates sensible defaults (see below). If no dataset is available, it shows a "No dataset loaded" message and offers no controls. ## Layout A two-pane modal: - **Left — configuration:** dataset name, mark type selector, one row per encoding channel, optional width/height inputs, and a "Create Snippet" action. - **Right — live preview:** a rendered chart that updates as the configuration changes, with a placeholder/error area. ## Inputs and Controls ### Mark type - Single selection from an exact set of five mark types: **Bar, Line, Point, Area, Circle**. - On open, the mark **defaults to the type that best fits the pre-populated X/Y field-type shape** (Tier B smart default): a temporal axis against a measure → **Line**; two measures → **Point**; a category against a measure → **Bar**; two categories → **Point**; and **Bar** as the fallback when only one axis (or none) is mapped. The user can switch to any of the five afterward. - Exactly one mark type is active at any time; selecting one updates the preview. ### Encoding channels - Exactly four channels are offered, in this order: **X, Y, Color, Size**. - For each channel the user: - Picks a dataset column from a dropdown of the dataset's detected columns (see _Datasets_ for column detection). A "None" option leaves the channel unmapped. Each column option shows a small type indicator alongside the column name. - Optionally overrides the channel's **field type**. The override appears only once a column is selected, and offers only the **types valid for that column** (Tier B valid-type locking) — a string/boolean column never offers Quantitative, and only a date column offers Temporal. Concretely: number → {Quantitative (default), Ordinal, Nominal}; date → {Temporal}; text → {Nominal (default), Ordinal}; boolean → {Nominal}. When a column admits only one valid type, no override control is shown. - When a column is chosen, its field type defaults from the dataset's inferred column type (numeric → Quantitative, date → Temporal, otherwise Nominal); the user may change it within the valid set above. - **Size discipline:** the **Size** channel accepts only Quantitative or Ordinal columns — size implies an ordered magnitude, so categorical (Nominal) and Temporal columns are not offered for Size (they remain available on X/Y/Color). A column that can't go on Size is shown disabled there with a brief reason. - Clearing a channel back to "None" leaves it out of the produced spec. - A **Swap X/Y** control exchanges the X and Y mappings (field and type) in one click, for quickly flipping the axes of the pre-populated default without re-selecting both columns. ### Default pre-population - On open, the first detected column is assigned to **X** and the second (if any) to **Y**, each with its derived field type. Remaining channels start unmapped. The mark starts at the smart default for that X/Y shape (see _Mark type_), not unconditionally Bar. ### Guidance (non-blocking) The builder surfaces short, plain-language hints for configurations that render but read poorly — advisory only, never blocking the **Create Snippet** action (validation below is the sole gate). These follow the chart-choice research ([`docs/chart-builder-research.md`](../chart-builder-research.md)) and include, for example: - A **Line** or **Area** mark with only one axis mapped (both axes are needed to draw it). - A **Bar/Line/Area** whose X and Y are both categories (nothing to measure). - **Two measures** on a non-scatter mark (a scatter — Point/Circle — usually reads better). - An **Area** chart split into multiple colour series (per-series change is hard to see). A clean configuration shows no hints. ### Dimensions (optional) - Optional numeric **Width** and **Height** inputs in pixels. - When left empty, the chart uses default/responsive sizing (consistent with _Live Preview_); when provided, the values are written into the spec. ## Live Preview - The right pane renders the chart described by the current mark, encodings, and dimensions, resolving the dataset reference to its actual data (same rendering behavior as _Live Preview_). - Updates are debounced: changes to mark, encodings, or dimensions trigger a re-render after a short pause rather than on every keystroke. - While no encoding is mapped, the pane shows a placeholder instructing the user to configure at least one encoding. - If the spec fails to render, the pane shows an inline error message describing the problem instead of a chart. ## Validation - A chart requires **at least one** channel mapped to a column. - While no channel is mapped, the "Create Snippet" action is disabled and the preview shows the configuration prompt. ## Output / Create Selecting "Create Snippet" produces the final artifact: - Builds a complete Vega-Lite spec containing: the schema reference, a named data reference to the dataset, the chosen mark (with tooltips enabled), the mapped encodings (each with its field and field type), and any explicit width/height. - Channels left unmapped are omitted; if no encodings exist the spec omits the encoding block entirely (prevented by validation here). - Creates a new snippet from that spec with an auto-generated descriptive name, adds it to the snippet library, and records that it was built from the dataset. - Links the snippet to the dataset by recording the dataset reference, so the bidirectional snippet↔dataset relationship is established (see _Datasets_). - Raises a success toast naming the created snippet. - Closes the builder; the newly created snippet becomes the active snippet in the library/editor. ## Closing - The builder can be dismissed without creating anything (close control / modal dismissal). - Closing resets all builder state (dataset, mark type, encodings, dimensions, preview) so a later open starts fresh, and any pending preview render is cancelled.