Skip to main content

visualize

Render rich interactive visuals — SVG diagrams, HTML widgets, Chart.js charts, and interactive explainers — directly inline in chat using visualize(). Use only when the user explicitly asks for a visualization, diagram, chart, graph, drawing, map, dashboard, or similar visual artifact. Do not use for ordinary markdown, code blocks, file previews, or answer formatting

الانتقال إلى التثبيت

معلومات المصدر

المستودع
Classic298/open-webui-plugins
آخر نشاط في المصدر
١٤ أغسطس ٢٠٢٦ في ١٨:٢٤
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٥٣٣
التفرعات
٢٦

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

The default accent is purple. Switch to one of the other ramps via the data-accent attribute. The chosen color drives --accent and --accent-foreground, which in turn power focus rings, checkbox/radio fills, and any var(--accent) reference you write yourself. The same nine names match the chart color ramps, so a teal-accented form sits naturally next to a teal-accented chart.

Available values: purple (default), teal, coral, pink, gray, blue, green, amber, red

To apply an accent color globally to the whole visualization: wrap the entire content in a single root

. Every supported element inside inherits the chosen accent.

/* CSS */ …all focus rings, checkboxes, and var(--accent) consumers go teal…

To apply an accent color to a specific section: set data-accent on any inner container to recolor just its subtree:

Save
Cancel

To apply an accent color to a single element: set directly on an element to recolor just it:

Approve Reject

Both light and dark themes are handled — accent values track per-theme ramp stops automatically, and foreground text color flips for legibility in dark mode. No manual override needed.

Pick an accent that matches the topic: green for finance/positive, red for warnings/critical actions, blue for informational dashboards, amber for attention/caution, etc. Default to purple for neutral or multi-purpose visualizations.

Output rules

These rules keep visuals clean, accessible, and consistent with the host UI:


Design system

CSS variables (auto-injected — prefer these so light/dark mode just works)

The tool injects theme-aware CSS variables that adapt to light/dark mode automatically. Use them by default for text, surface, and border colors; reach for a specific hex only when the design genuinely calls for a fixed color (a brand mark, a deliberate accent that shouldn't track the theme).

TokenPurpose
--color-text-primaryMain text
--color-text-secondaryLabels, muted text
--color-text-tertiaryHints, placeholders
--color-text-info/success/warning/dangerSemantic text
--color-bg-primaryMain background
--color-bg-secondaryCards, surfaces
--color-bg-tertiaryPage background
--color-border-tertiaryDefault borders (0.15 alpha)
--color-border-secondaryHover borders (0.3 alpha)
--font-sansDefault font
--font-monoCode font
--radius-md / --radius-lg / --radius-xl8px / 12px / 16px

Color ramps (9 ramps, auto light/dark)

Each ramp provides fill, stroke, and text variants that adapt to the theme automatically via CSS classes.

Ramp50 (light fill)200400600 (light stroke)800 (light title)
purple#EEEDFE#AFA9EC#7F77DD#534AB7#3C3489
teal#E1F5EE#5DCAA5#1D9E75#0F6E56#085041
coral#FAECE7#F0997B#D85A30#993C1D#712B13
pink#FBEAF0#ED93B1#D4537E#993556#72243E
gray#F1EFE8#B4B2A9#888780#5F5E5A#444441
blue#E6F1FB#85B7EB#378ADD#185FA5#0C447C
green#EAF3DE#97C459#639922#3B6D11#27500A
amber#FAEEDA#EF9F27#BA7517#854F0B#633806
red#FCEBEB#F09595#E24B4A#A32D2D#791F1F

Chart dataset colors (use 400 stops)

SeriesColorHex
1teal-400#1D9E75
2purple-400#7F77DD
3coral-400#D85A30
4blue-400#378ADD
5amber-400#BA7517

For area/line fills, use same color at 20% opacity.


SVG setup

If you want to build a beautiful SVG to be rendered inside the chat, follow these rules too: Always use this SVG boilerplate:

SVG classes (auto-injected)

Drop these on SVG elements instead of writing inline fill, stroke, or font-size. They track the theme automatically.

ClassWhat it isWhen to use
.t14px primary-color textDefault for any visible label inside a node, axis tick, or callout.
.ts12px secondary-color textSubtitles, captions, units (e.g. "users", "ms"), supporting text under a .t label.
.th14px primary text, 500 weightNode titles, KPI numbers, anything that needs to read as "the headline" of a small region.
.boxNeutral rect — secondary bg, tertiary borderDefault container for a labeled region. Use whenever you need a neutral chip / panel and don't have a semantic color.
.nodeCursor-pointer + hover opacity on a Mark a as clickable. Pair with onclick="sendPrompt(...)" so a user can drill into the topic.
.arr1.5px stroke matching theme bordersArrow lines and connectors. Combine with marker-end="url(#arrow)".
.leader0.5px dashed guide linePulling a label to a part of an illustration when the label can't sit on top of it.
.c-{ramp}Sets fill/stroke + text colors on a whole from one of the 9 color rampsColor a node by category — apply .c-teal (etc.) to a and every shape and text inside picks up the matching ramp. Un-classed, un-filled / children (pie wedges, areas) take the ramp's series color; on a classed or filled mark, fill="currentColor" opts back in.

Sizing text inside boxes

Browsers don't auto-size SVG boxes to text. To pick a width, estimate the rendered glyph width per character and size the box from the longest line.

Centering text in boxes

defaults to dominant-baseline="alphabetic" — y is the text's baseline, not its center, so a label placed at the vertical midpoint of a box actually sits ~4 px too high. For text inside a node, callout, or any rounded rect, add dominant-baseline="central" and put y at the box midpoint.

Keep the default (no dominant-baseline) for text that's meant to sit on a baseline: axis tick labels (resting on the axis line), legend labels (aligned to the swatch baseline), and anything where the bottom edge of the glyphs is the visual anchor. Setting central on those will make them look ~4 px low instead.


Diagram types

Flowchart — sequential steps, decisions

Single-line node:

Label

Two-line node:

Title Subtitle

Architecture — nested regions, layered systems

For diagrams that show what contains what: services inside zones, modules inside layers, components inside subsystems. The nesting itself is the information — outer regions are the system, inner regions are the parts.

Illustrative — explain a mechanism by drawing it

For "how does this actually work" topics where the answer is spatial: how light refracts through a prism, how a transformer attention head weighs tokens, how a heat pump moves heat against a gradient. Draw the thing itself, not a labeled diagram about it.


Charts (Chart.js)

Load Chart.js in your HTML fragment:

Setup pattern:

Chart rules:

Chart type selection:

Data shapeTypeNotes
Categories + values (a few items, comparable magnitudes)BarDefault for "compare values across labels". Switch to a horizontal bar (indexAxis: 'y') when labels are long, when there are 8+ categories, or when ranking is the point.
Time series, anything sampled at regular intervalsLinetension: 0.3 for a natural curve. Stack multiple datasets when you're comparing trends, not when each line wanders independently — overlap gets unreadable past 4 lines.
Parts of a whole, ≤5 slicesDoughnutUse cutout: '60%' so the empty middle can hold a total or label. Skip if the segments are very uneven (one slice >70%) — the small slices vanish; show a stacked bar instead.
Two continuous variables, looking for correlationScatterAdd a trend line if the relationship is the takeaway. For dense clouds, drop point opacity to 0.3–0.5 so density reads.
Stacked / cumulative composition over timeStacked bar / stacked areaBar when the buckets are discrete (months, segments); area when the underlying signal is continuous.
Single-value vs target / thresholdBar with reference line or KPI cardA whole chart is overkill for one number — consider a metric card with a sparkline instead.
Multi-dimensional comparison (3–6 axes)RadarOnly when the axes are genuinely commensurate — otherwise a small-multiples bar grid is clearer.

Inline SVG charts (no library)

Reach for inline SVG when the data is small, the shape is simple, or you want the chart to share design with surrounding diagrams (matching corner radii, palette, type). No script, no CDN — just shapes and text. Reach for Chart.js when you need axes, tooltips, hover, animation, or many series.

Good fits for inline SVG:

Theme consistency for inline SVG:

Math hints for the less obvious shapes:


Component patterns

Metric cards — KPI strip

Revenue
$3,870
▲ 12.4%

Pair with a chart below for a compact dashboard. Add a tiny inline-SVG sparkline under each value if the trend matters.

Comparison layout — two paths side by side

Monolith
Deploy unit
1 service
Latency
Low (in-process)
Scaling
Vertical
Microservices
Deploy unit
N services
Latency
Higher (network)
Scaling
Horizontal per service

Interactive explainer — slider drives output

Interest 5.0%

The pattern generalises: every interactive element binds an input listener, recomputes a value, and writes it to a result node. Pair with an inline SVG that re-draws on every input change for a "live diagram".

Tabs — a piece of UI users already know

Overview Details Source
.tab { background:none; border:none; padding:8px 12px; cursor:pointer; border-bottom:2px solid transparent; } .tab.active { border-bottom-color: var(--accent); color: var(--color-text-primary); } .tab-panel { padding:12px 0; }

Persist the active tab with saveState/loadState so it survives reloads.

Charts in inactive tabs render at 0×0. Plotly, ECharts, and vis-network all measure their container at init time. If that container is inside a hidden / display:none panel, they paint into a zero-size canvas and stay blank even after the tab becomes visible. Two workarounds, pick one:

  1. Lazy-init: only call Plotly.newPlot / echarts.init / new vis.Network the first time its tab is shown (track a tabInit[id] flag in the handler).

  2. Resize on show: init everything up front (so data is ready), then in showTab call the right resize hook for whichever lib is in that tab. Note the API differs per library — c.resize() does not work for all of them:

    // ECharts: instance.resize() echartsInstance.resize(); // Plotly: pass the container element, no .resize() on the chart Plotly.Plots.resize(document.getElementById('plotly-container')); // vis-network: redraw + fit — the instance has no .resize() networkInstance.redraw(); networkInstance.fit(); // Chart.js: instance.resize() — but Chart.js auto-resizes on // container size change so usually nothing needed.

    Skip the resize call for D3 / Vega-Lite / inline SVG — they paint declaratively into the SVG namespace and aren't bothered by hidden parents.

Step-through walkthrough — guided narrative

A "Next ▶" button advances through a sequence of stages, each with its own caption and (optionally) a different highlighted region of the same diagram. Useful for explaining algorithms, processes, or any topic where the order matters more than the totals.

Click Next to begin.
Next ▶

sendPrompt bridge — conversational diagrams

sendPrompt(text) is the function that makes visualizations conversational. When called, it injects the given text into the chat input field and submits it — exactly as if the user had typed and sent it themselves. The model then receives that message and responds normally, creating a feedback loop between the visual and the conversation.

This is what separates a static diagram from an exploration interface. A user sees a system architecture diagram, clicks on the "Load Balancer" node, and the model receives "Tell me more about the load balancer — how does it distribute traffic across the backend services?" as a user message. The model then responds with details, and could even generate a new sub-diagram showing the load balancer internals. The user never had to type anything — they just clicked.

Why this matters

Without sendPrompt, interactive elements inside the iframe are isolated — they can toggle visibility, animate, or filter data, but they can never talk back to the model. The user sees a cool diagram but has to manually type follow-up questions. With sendPrompt, every clickable element becomes a conversation starter. The diagram itself becomes a navigation interface for the topic.

Writing good sendPrompt text

The text you pass to sendPrompt becomes the user's message to the model. Write it as a natural follow-up question — conversational, specific, and referencing the context of the diagram:

Good prompt text (specific, contextual, references the diagram):

Usage patterns

Simple patterns — single-click sendPrompt on a node or button:

Form / preference collector — gather multiple user selections, then send them all at once. Use local JS to track choices (button highlights, state object) and a submit button that composes a sendPrompt from the collected answers:

What's your style?

Pace

Relaxed Moderate Intensive

Focus

Culture Nature Food

Get my recommendation →

This pattern is powerful because the model receives a structured summary of all user preferences in one message. Use local JS for the selection UI (instant feedback), then sendPrompt only on final submit.

When to use sendPrompt vs local JS:

User actionUseWhy
Learn more about a componentsendPromptModel gives a contextual explanation
Explore a stage / drill downsendPromptModel can generate a sub-diagram
Submit answers or preferencessendPromptModel evaluates or personalizes
Toggle views, adjust slidersLocal JSInstant feedback, no reasoning needed
Filter/sort dataLocal JSInstant response, no model needed

Interactivity by default

Build dashboards, charts, graphs, interactive functions, animated sections, moving objects, expandable detail sections, cards, copyable text elements and more. If the topic allows and it makes sense for the topic, build complex and visually stunning elements.

Visualizations should feel alive and polished — not static images dumped into chat. Build interfaces that invite interaction:

The goal is to build something that feels like a real app component embedded in chat with reactivity, sections and extra elements — not a screenshot. If the visualization has multiple facets, give the user controls to explore them. If it has hierarchical information, let them expand and collapse. If it has data, let them sort or filter.


openLink bridge — opening URLs from visualizations

openLink(url) opens a URL in a new browser tab from within the visualization iframe. Normal links inside an iframe can behave unpredictably (opening inside the iframe, being blocked by sandbox restrictions, etc.). This function handles that by opening the link in the parent window instead.

Open API docs ↗

Or in SVG:

View source ↗

Use openLink for external references, documentation links, or source code links. Unlike sendPrompt, this navigates away from the chat — use it when the user needs to access an external resource, not when they need the model to explain something.


copyText + toast bridges — feedback on user actions

copyText(text) copies text to the system clipboard and automatically shows a localized "Copied" toast in the top-right corner of the iframe. Works from HTTPS and HTTP origins (falls back to execCommand('copy') if the async Clipboard API is blocked). Use this on "Copy" buttons inside interactive visualizations — data tables, code snippets, shareable values.

Copy JSON

toast(message, kind) shows a small auto-dismissing banner inside the iframe. kind is optional and controls the text color: 'success' (green, default), 'info' (blue), 'warn' (amber), 'error' (red). Use it for status notifications inside long-running interactive tools — "Calculation done", "Invalid input", etc.

Recompute

Toasts auto-dismiss after ~2.2 s and stack vertically if fired in quick succession.


saveState + loadState bridges — persistent interactive state

saveState(key, value) and loadState(key, fallback) proxy parent.localStorage with a key prefix scoped to this assistant message. State survives page reloads and tab switches, but two different chats (or different messages in the same chat) each get their own independent state — no cross-contamination.

Use it for: selected tabs, picked chart range, hidden/shown layers, theme overrides, collapsed sections — anything the user would expect to be remembered when they re-open the chat.

Values are JSON-serialized. If localStorage is blocked (private browsing, sandboxed), both functions silently no-op and loadState returns fallback.


CDN libraries

Strict-mode CSP allowlists three CDN hosts. Anything served from them loads — no plugin tweaking needed, even in strict security mode.

Allowed hosts:

Common picks:

LibraryWhy reach for itExample loader
Chart.jsBar / line / doughnut / scatter with animation out of the box
D3.jsCustom data-driven SVG (force graphs, arcs, maps, non-standard charts)