Problem guide
How to Build a Custom D3 Visualization With AI
Give the AI a clear question, a small sample of the data, and a few non-negotiable behaviors. Keep data preparation separate from rendering, check D3’s keyed joins and scales, test ugly edge cases, and keep security, accessibility, dependencies, and versioning in the review. AI should remove setup work, not hide the implementation.
Written for: Data visualization developers, technical analysts, and advanced data storytellers
Give the AI a clear question, a small sample of the data, and a few non-negotiable behaviors. Keep data preparation separate from rendering, check D3’s keyed joins and scales, test ugly edge cases, and keep security, accessibility, dependencies, and versioning in the review. AI should remove setup work, not hide the implementation.
Give the AI a clear engineering brief
D3 can express almost any web-based visualization, which is both its strength and its risk. A vague request such as “make an impressive chart” forces the AI to invent the data shape, interaction, visual hierarchy, and code structure all at once. Start with a clear brief.
Specify:
- what one row represents and which fields are required;
- the audience and principal analytical question;
- the default view and visual encoding;
- interactions and persistent state;
- dimensions that must respond to container width;
- accessibility and reduced-motion behavior;
- null, empty, and invalid-record rules;
- libraries and versions that are allowed;
- the publication and security environment.
Include a small representative data fixture. A ten-row fixture with difficult cases is often more useful than attaching a million-row source to the first request.
Separate data shaping from rendering
D3 code should not be responsible for discovering and repairing an entire data warehouse. Create a reviewed query or transformation that returns a compact, documented structure. Use stable IDs, typed numeric fields, ISO-style dates, and explicit status flags.
For repeated measures, prefer a long structure. For a network, provide node and edge tables. For geography, join by stable codes. For a survey, calculate weights and denominators before the browser when the methodology is complex.
This separation reduces code size, makes analytical review possible, and limits public data exposure. It also lets the same visualization accept refreshed data without changing its rendering logic.
Agree on the structure before generating all the code
Before the assistant writes hundreds of lines, ask it to outline the components, state, and update cycle. A reasonable D3 structure often includes:
- parseData to validate and normalize input;
- a state object containing active filters and selection;
- computeLayout or scale functions;
- an initial DOM structure created once;
- an update function that joins data and changes marks;
- event handlers that update state and call update;
- resize handling;
- cleanup for observers or listeners.
Review that plan. Ask where totals are calculated, how keys are assigned in data joins, and what happens when the container becomes narrow. It is cheaper to correct the architecture before hundreds of lines are generated.
Build one complete small version first
Start with one complete but narrow version: one dataset, one view, one interaction, and one small-screen state. That is enough to prove that the data shape and rendering approach work before you add everything else.
For example, begin a custom survey explorer with one question and one comparison variable. Verify weighted percentages and label placement. Then add question switching, sorting, locked selection, and a detail panel one at a time.
After each change, preserve established behavior and run the same fixture. Large “rewrite everything” prompts can produce attractive output while dropping keyboard support, mobile behavior, or prior controls.
Inspect the D3 data join
A stable D3 visualization depends on correct joins. Marks should use keys based on persistent IDs rather than array position or display labels. Updates should enter, change, and exit cleanly without duplicating SVG elements or event listeners.
Look for code shaped around a single rendering lifecycle. Repeatedly appending a new SVG on every filter change is a warning sign. So is mutating the source data in place until later updates depend on hidden prior state.
Ask the assistant to explain the join in plain language. Confirm that sorting changes visual position without changing identity. Test adding and removing categories between updates.
Make scales and formats explicit
Review domain calculations. A numeric scale should ignore null and nonfinite values, handle a constant domain, and use a meaningful baseline. A time scale should parse dates once and sort them chronologically. An ordinal color scale should have a stable domain so colors do not shift when a category is temporarily filtered out.
Centralize formatting for percentages, currency, counts, and dates. Tooltips and axis labels should use the same definitions. Guard divisions by zero and avoid displaying NaN, Infinity, or floating-point noise.
If a scale changes with a filter, decide whether that helps local detail or damages comparison. Make the behavior visible in the title or legend when necessary.
Review security and dependency behavior
Generated browser code must treat text fields as data, not trusted HTML. Prefer textContent or D3’s .text() for labels. Sanitize any rich content. Never place database credentials or private API keys in the visualization.
Pin D3 and any plugins to approved versions. Review external fetches, imported modules, fonts, images, and map assets. Ensure they comply with the application’s Content Security Policy. Avoid adding a new dependency for a small utility that can be implemented clearly with the existing stack.
If links are created from data, validate their protocols. If the visualization accepts user-entered values, constrain and encode them before using them in selectors, URLs, or HTML.
Design accessibility in from the start
SVG does not become accessible automatically. Give the visualization a descriptive title and summary. Use native controls where you can, label them properly, and make sure keyboard users can reach the same information that mouse users get from hover or click.
For a dense graphic, a separate control such as a searchable list may be more usable than making every mark tabbable. Announce selection changes in a live region. Use color with position, label, shape, or pattern. Respect prefers-reduced-motion and make transitions interruptible.
Provide a table, downloadable data, or structured textual alternative when the visual relationship cannot be fully conveyed to every reader.
Test rendering and analysis separately
Create two test groups.
Analytical fixtures verify sums, rates, denominators, classifications, and flow conservation. These can often run without a browser. Rendering fixtures verify that the page creates one SVG, labels appear, controls update state, no-data messages render, and resize logic works.
Include awkward test records: nulls, duplicate labels with distinct IDs, zero denominators, very long text, a single value, all-equal values, negative values where allowed, and an empty array. Change controls quickly and resize the page repeatedly to catch duplicated listeners or stale transitions.
Keep a screenshot or expected DOM snapshot for major states, but do not rely on pixel comparison alone. A visually similar chart can contain incorrect data.
Use Rhubarb as the development and publication environment
Rhubarb’s assistant can be both an analyst and a visualization developer. You can ask what the data shows, let it write or revise the query, and then ask for the custom D3 behavior that best answers the question. The generated D3 and JavaScript stay visible in the editor rather than becoming an opaque image or one-off chat response.
Give the assistant one bounded change at a time and ask it not to remove existing controls or behavior. Inspect the proposed result, reconcile values, and save a version. Directly edit the code when a precise technical change is faster than another conversational revision.
This is especially useful for technical people in a hurry: you can get to a credible first implementation conversationally, inspect the architecture when it matters, and edit the code directly when that is faster. Rhubarb does not remove analytical responsibility; it removes a lot of the mechanical work between the question and the first reviewable result.
Before you call it finished
A production-ready AI-assisted D3 visualization has a documented input contract, stable keyed joins, explicit scales and formats, resilient null handling, one rendering lifecycle, responsive layout, keyboard and touch behavior, safe text handling, pinned dependencies, analytical reconciliation, a no-data state, source and methodology notes, a versioned release, and a defined refresh owner. The goal is not merely generated code. It is generated code that another person can inspect, operate, and maintain.
Frequently asked questions
What should an AI know before generating D3 code?
Provide the row meaning, required fields, audience question, default encoding, interactions, responsive behavior, accessibility rules, null handling, allowed dependencies, and a difficult sample fixture.
What is the most important D3 code review check?
Verify stable keyed data joins and a single update lifecycle. Repeatedly appending a new SVG or using array position as identity often causes duplicated or incorrect marks.
Can AI-generated D3 be production-ready?
It can be a strong implementation draft, but production readiness requires analytical reconciliation, security and dependency review, responsive and accessible testing, versions, and an owner.