> ## Documentation Index
> Fetch the complete documentation index at: https://docs.profy.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# Visualize

> profy-visualize — render data, 3D scenes, and structured UI directly in the conversation, with a self-check tool to verify what actually rendered

# Visualize (profy-visualize)

## One-line definition

`profy-visualize` lets an expert **draw the result directly into the conversation** — charts, 3D exhibits, geographic maps, interactive interfaces — instead of handing you code or a static image. The output is interactive (hover, rotate, click), and the plugin ships a companion inspection tool so the model can verify the render before showing it to you.

## Activation

| Field             | Value                                                            |
| ----------------- | ---------------------------------------------------------------- |
| Plugin ID         | `profy-visualize`                                                |
| `user_selectable` | `true` — **tick it manually in the plugin panel**                |
| `conditions`      | `{ "sandbox_mode": "!none" }` (requires a sandbox)               |
| Tools injected    | `visualize` / `inspect_3d` / `render_ui` / `browser`             |
| Prompts injected  | `VISUALIZE.md` / `3D.md` / `DATA_VIZ.md` / `A2UI.md`             |
| Declared skills   | 8 (14 more sit in the directory, loadable on demand — see below) |

<Note>
  The `browser` tool is **deliberately bound by this plugin**: the visual verification loop calls `browser(action='screenshot')`. If you ticked Visualize without browser capability, the tool referenced by the prompt would not exist and the entire self-check step would silently fail. That is why ticking Visualize alone also lights up the browser tool.
</Note>

## Choosing among the three rendering tools

This is the only judgement you need to make; the rest is the model's job.

| Tool         | Output                            | Use when                                                                       |
| ------------ | --------------------------------- | ------------------------------------------------------------------------------ |
| `visualize`  | HTML/CSS/JS in a sandboxed iframe | Bespoke visuals: charts, 3D, animation, simulation                             |
| `render_ui`  | Native React components (A2UI)    | Layout + data + actions: cards, tables, pricing comparisons, status dashboards |
| `inspect_3d` | Structured diagnostic JSON        | Produces no visual; checks Three.js scene health                               |

The rule of thumb is written into the prompt: **if the output is "layout + data + actions" use `render_ui`; if it is a "custom visual" use `visualize`.**

The practical difference is visual consistency. `render_ui` output uses the same design system as Profy itself (literally the same React components), while `visualize` is an independent world inside an iframe whose appearance depends entirely on the CSS the model wrote. So for something like a comparison table, `render_ui` is far more reliable than asking the model to hand-write an HTML table.

***

## visualize: inline HTML visualization

### Parameters

| Parameter | Type   | Required | Notes                                                      |
| --------- | ------ | -------- | ---------------------------------------------------------- |
| `title`   | string | Yes      | Short title for the visualization                          |
| `html`    | string | Yes      | HTML fragment; may include inline `<script>` and `<style>` |
| `css`     | string | No       | Additional CSS to inject                                   |

### Sandbox and network

Rendering happens inside a **sandboxed iframe** with no network access by default. CDNs are the one exception, because virtually every visualization library is loaded that way. Verified sources:

```
https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js
https://cdn.jsdelivr.net/npm/d3@7/+esm
https://cdn.jsdelivr.net/npm/vega-lite@5/+esm
https://cdn.jsdelivr.net/npm/vega-embed@6/+esm
https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6/+esm
```

jsdelivr, unpkg, and cdnjs are allowed; other outbound requests are blocked by the iframe sandbox. **This means a visualization cannot pull live data from your API** — the data must be inlined into the HTML by the model. For a few hundred rows that is fine; for tens of thousands, aggregate in the sandbox first, then inline the result.

### Design rules (enforced at the prompt layer)

The model is held to six:

1. **Self-contained** — everything in one HTML fragment, no external file references
2. **Responsive** — `width: 100%`, typical heights 400–600px
3. **Light/dark adaptive** — `color-scheme: light dark` or `prefers-color-scheme`
4. **Interactive** — hover tooltips, click handlers, zoom/pan where appropriate
5. **Accessible** — ARIA labels on key elements
6. **Performant** — keep DOM elements under 1,000; use canvas/WebGL for large datasets

Rule 6 is the one most often violated in practice: a scatter plot of ten thousand points drawn with `<div>` elements will lock up the page. Canvas is mandatory at that scale.

### The visual self-check loop (the most valuable part of this plugin)

The prompt hard-codes a "Vision in the Loop" protocol — the model must look at its own output **before** showing it to you:

<Steps>
  <Step title="Generate or modify code">
    Write the HTML fragment.
  </Step>

  <Step title="Capture">
    `browser(action="screenshot", html=<your_html>)`
  </Step>

  <Step title="Read the image">
    `image(action="read", image_urls=[...], prompt="Check rendering, composition, labels, and visible errors")`
  </Step>

  <Step title="Diagnose by symptom">
    Blank canvas → WebGL context error or script error. Missing elements → geometry/material/lighting. Wrong layout → camera, position, or scale. Missing axes → scale domain or append order. No data rendered → data binding or parse error.
  </Step>

  <Step title="Loop on any issue">
    Fix and re-capture until it looks right, only then present it.
  </Step>
</Steps>

This protocol is the core difference between this plugin and simply asking a model to write a chart. Without it, what you receive is **what the model believes the output should look like**. With it, what you receive is **what it confirmed the output actually looks like**.

***

## render\_ui: declarative native UI (A2UI)

### Parameters

| Parameter    | Type   | Required | Notes                                                      |
| ------------ | ------ | -------- | ---------------------------------------------------------- |
| `components` | array  | Yes      | A2UI component definitions; each needs a `type`            |
| `surface_id` | string | No       | Stable surface identifier; auto-generated uuid4 if omitted |

Protocol version is pinned at `0.9.1`.

### Component types

`Card` / `Row` / `Column` / `Text` / `Button` / `Image` / `Table` / `PricingTable`, nestable through a `children` array.

### Validation precedes rendering

Every component — at every nesting level — is recursively validated for a `type` field. On failure you get error detail rather than a surface:

```json theme={null}
{
  "error": "Invalid component structure",
  "details": ["components[0].children[2]: missing required `type` field"]
}
```

The error carries a full path (`components[0].children[2]`), so the model can locate the problem exactly. A non-array `children` is also called out (`.children: must be an array`).

<Info>
  `render_ui` renders **native React components**, not an iframe. It therefore has none of `visualize`'s network constraints — but it also cannot run arbitrary JS. You are limited to the component types the platform provides. That is the safety/flexibility trade: a lower ceiling, but output that necessarily matches the product's design system.
</Info>

***

## inspect\_3d: Three.js scene diagnostics

This tool produces no visual. It answers "is the 3D scene I just rendered actually healthy?"

### Parameters

| Parameter            | Default | Notes                                                                                              |
| -------------------- | ------- | -------------------------------------------------------------------------------------------------- |
| `url`                | none    | Navigate here before inspecting; omit to inspect the current page (pairs with `browser(set_html)`) |
| `max_depth`          | 5       | Max scene tree traversal depth                                                                     |
| `include_materials`  | true    | Include the material inventory                                                                     |
| `include_animations` | true    | Include animation clip list                                                                        |
| `performance_audit`  | true    | Include performance report and warnings                                                            |

### Prerequisite: the scene must be exposed

The tool injects JS via CDP to traverse the scene graph, so the page must expose the scene on `window.__THREE_SCENE__` or `window.scene`. Without it you get:

```
Three.js scene not found. Expose via window.__THREE_SCENE__ or window.scene
```

This is a contract, not a bug — adding one line, `window.__THREE_SCENE__ = scene;`, unlocks the whole diagnostic chain.

### What it reports

| Warning                               | Trigger                   | Meaning                                       |
| ------------------------------------- | ------------------------- | --------------------------------------------- |
| `WebGL not available`                 | No WebGL context          | Environment problem; the canvas will be blank |
| `WebGL error on active canvas: <err>` | WebGL error on the canvas | Shader, texture, or buffer failure            |
| `Three.js scene not found...`         | Scene not exposed         | Add the global assignment                     |
| `High triangle count: <n>`            | Triangles > 1,000,000     | Decimate or add LOD                           |
| `High draw calls: <n>`                | Draw calls > 100          | Merge geometry or use instancing              |

"Blank canvas" is an extremely common 3D symptom and impossible to diagnose from a screenshot alone — WebGL may have died, the camera may be pointing the wrong way, or the scene may genuinely be empty. Distinguishing those three is exactly what `inspect_3d` is for.

### Dual mode

Sandbox mode goes through CDP; desktop mode routes via the browser tool. Behavior is identical. When the injected result cannot be parsed, the tool returns `{"raw_output": ..., "warnings": ["Could not parse inspection result as JSON"]}` rather than raising — so a `raw_output` payload means injection ran but the output shape was unexpected.

## Skills

The manifest declares 8 skills whose **full text is injected into the prompt**:

`visualize` / `threejs-showcase` / `molecular-visualization` / `data-visualization` / `d3-data-visualization` / `geospatial-visualization` / `statistical-visualization` / `a2ui-patterns`

The plugin directory, however, contains **22 skill directories**. The platform's skill registry recursively scans every `SKILL.md` under `plugins/builtin/*/skills/`, so the other 14 are equally loadable by name through the `skill` tool — they just do not occupy context by default:

`3d-data-visualization` / `accessibility-visualization` / `canvas2d-data-visualization` / `dashboards-realtime` / `gantt-chart` / `grammar-of-graphics` / `interactive-3d-atlas` / `node-link-diagram` / `react-nextjs-visualization` / `reports-pdf-slides` / `scrollytelling` / `threejs-data-visualization` / `uml-architecture` / `visualization-strategy`

<Note>
  This pattern is general, not specific to Visualize: **the manifest's `skills` list is what gets injected by default**, while **every `SKILL.md` in the directory is loadable on demand**. The former spends context to buy certainty; the latter saves context but requires the model to fetch it. So when you ask for "a Gantt chart of the schedule" and the model loads `gantt-chart` before doing anything, that intermediate step is not stalling.
</Note>

## Executable examples

<AccordionGroup>
  <Accordion title="Data chart">
    ```
    Here's our revenue and cost for the last 12 months (table pasted below).
    Draw a dual-axis chart — revenue as bars, cost as a line — with hover showing
    the exact values and that month's margin.
    ```

    The model picks `visualize` with D3 or Vega-Lite, screenshots its own output, confirms the axes and hover work, and only then presents it.
  </Accordion>

  <Accordion title="3D product showcase">
    ```
    Build a drag-to-rotate product stage showing a rounded cube, with ambient light
    plus one key light and a shadow underneath. The material should read clearly.
    ```

    This triggers the `3D.md` prompt and the `threejs-showcase` skill, then calls `inspect_3d` to confirm draw calls and triangle count are sane.
  </Accordion>

  <Accordion title="Structured comparison UI">
    ```
    Turn these three options into a comparison card set — price, target size, and
    three core capabilities each, with a "choose this" button at the bottom of each.
    ```

    Classic `render_ui` territory: layout plus data plus actions, no bespoke visual needed.
  </Accordion>

  <Accordion title="Geographic data">
    ```
    Draw a choropleth of China from this per-province sales data, five color bands
    by revenue, hover showing province name and amount.
    ```

    Loads the `geospatial-visualization` skill.
  </Accordion>
</AccordionGroup>

## Boundaries and failure modes

| Symptom                                 | Cause                                                | Fix                                                                                            |
| --------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| Visualization area is blank             | Script error inside the iframe, or a blocked CDN     | Ask the model to screenshot and self-check; confirm it used one of the three allowed CDN hosts |
| 3D scene is entirely black              | WebGL context failure or wrong camera orientation    | Have the model run `inspect_3d` to distinguish the two                                         |
| `Three.js scene not found`              | Page does not expose `window.__THREE_SCENE__`        | Add the global assignment and re-inspect                                                       |
| `Invalid component structure`           | A `render_ui` component is missing `type`            | The error detail carries an exact path                                                         |
| Chart stutters, browser heats up        | Over 1,000 DOM elements — large data drawn with divs | Explicitly ask for canvas rendering                                                            |
| The data in the chart is invented       | You supplied none, so the model fabricated samples   | Paste real data, or have it read from a file first                                             |
| Ticked in the panel but nothing happens | `sandbox_mode` is `none`                             | This plugin needs a sandbox                                                                    |

<Warning>
  **Visualizations cannot connect to your live data source.** The iframe has no network access beyond CDNs, so the data in a chart is a snapshot inlined at generation time. A genuinely live dashboard needs a real site published through Sites, not an inline visualization.
</Warning>

## Verify

<Steps>
  <Step title="Confirm activation">
    Ask "can you draw charts directly in this conversation?" When active, the expert distinguishes `visualize` from `render_ui` output; when inactive it offers to give you code.
  </Step>

  <Step title="Minimal chart">
    "Draw a bar chart of \[1,3,2,5,4]." You should see an interactive chart, not a code block.
  </Step>

  <Step title="Confirm self-check runs">
    Give it a moderately complex 3D request and watch for a screenshot and `inspect_3d` call before delivery. Skipping straight to delivery usually means the browser tool was not bound.
  </Step>
</Steps>

## Related pages

<CardGroup cols={2}>
  <Card title="Rendering surfaces" icon="layout" href="/en/documentation/plugins/rendering-surfaces">
    Overview of the in-chat rendering surfaces and how they differ
  </Card>

  <Card title="A2UI" icon="component" href="/en/documentation/plugins/a2ui">
    Components and patterns of the declarative UI protocol
  </Card>

  <Card title="3D development" icon="box" href="/en/documentation/plugins/3d-development">
    Blender / Godot bridges and the 3D asset pipeline
  </Card>

  <Card title="Canvas" icon="pen-tool" href="/en/documentation/capabilities/canvas">
    The free-form creation canvas (orthogonal to inline visualization)
  </Card>
</CardGroup>

<Note>
  Verified 2026-08-11. Sources: `services/agent-runtime/src/plugins/builtin/visualize/plugin.json`, `tools/{visualize,render_ui,inspect_3d,_inspect_runner}.py`, `prompts/VISUALIZE.md`, `services/agent-runtime/src/skills/registry.py`.
</Note>
