Skip to content
Development
Skill

/data_visualization

Best practices for creating charts, tables, and live data displays.

From plugin
pywry
9918 skills1 agent2 commands1 MCP

Claude Code

$ npx -y skills add deeleeramone/PyWry --skill data_visualization --agent claude-code

SKILL.md

data_visualization.SKILL.md

Data Visualization

> Best practices for creating charts, tables, and live data displays.

Plotly Charts

Creating a Chart

import plotly.express as px

# Create figure
df = pd.DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
fig = px.line(df, x="x", y="y", title="My Chart")

# Show as widget
show_plotly(figure_json=fig.to_json(), title="Line Chart")

Updating a Chart

# Full update
new_fig = px.bar(df, x="category", y="value")
update_plotly(widget_id, figure_json=new_fig.to_json())

# Layout-only update (faster - doesn't re-render data)
layout_update = {"title": {"text": "Updated Title"}}
update_plotly(widget_id, figure_json=json.dumps({"layout": layout_update}), layout_only=True)

Theme Consistency

PyWry automatically switches Plotly charts between `plotly_dark` and `plotly_white` templates when the theme toggles. To customize per-theme styles while preserving automatic switching, use `template_dark` and `template_light` on `PlotlyConfig`:

from pywry import PlotlyConfig

config = PlotlyConfig(
    template_dark={"layout": {"paper_bgcolor": "#1a1a2e", "plot_bgcolor": "#16213e"}},
    template_light={"layout": {"paper_bgcolor": "#ffffff", "plot_bgcolor": "#f0f0f0"}},
)

show_plotly(figure_json=fig.to_json(), title="Themed Chart", config=config)

User values are **deep-merged** on top of the built-in base template — your overrides always win, and anything you don't set is inherited from the base.

Chart Sizing

# Let chart fill container
fig.update_layout(
    autosize=True,
    margin=dict(l=40, r=40, t=50, b=40),
)

# Or set explicit size
fig.update_layout(
    width=600,
    height=400,
)

AG Grid Tables

Creating a Table

import json

# Data as list of dicts
data = [
    {"name": "Alice", "age": 30, "city": "NYC"},
    {"name": "Bob", "age": 25, "city": "LA"},
]

# Show as AG Grid widget
show_dataframe(data_json=json.dumps(data), title="Users")

Theme Consistency

# AG Grid themes
# - ag-theme-quartz-dark (dark mode)
# - ag-theme-quartz (light mode)
# Widget auto-applies based on current theme

Large Datasets

# AG Grid handles large datasets efficiently via virtualization
# Pagination is built-in

data = [{"id": i, "value": random.random()} for i in range(10000)]
show_dataframe(data_json=json.dumps(data), title="Large Dataset")
# Grid virtualizes - only visible rows are rendered

Marquee for Live Data

Building Ticker Items

# Build individual ticker items
items = [
    build_ticker_item(ticker="AAPL", text="AAPL $185.50"),
    build_ticker_item(ticker="GOOGL", text="GOOGL $142.20"),
    build_ticker_item(ticker="MSFT", text="MSFT $378.90"),
]

Creating Marquee

create_widget(
    html="<div id='content'>Dashboard Content</div>",
    toolbars=[{
        "position": "top",
        "items": [{
            "type": "marquee",
            "ticker_items": [
                {"ticker": "AAPL", "text": "AAPL $185.50"},
                {"ticker": "GOOGL", "text": "GOOGL $142.20"},
            ],
            "speed": 50,  # pixels per second
            "direction": "left",  # left or right
        }]
    }]
)

Updating Individual Tickers

# Update one ticker without affecting others
update_ticker_item(
    widget_id,
    ticker="AAPL",
    text="AAPL $186.25 ▲",
    styles={"color": "#22c55e"}  # Green for up
)

update_ticker_item(
    widget_id,
    ticker="GOOGL",
    text="GOOGL $141.50 ▼",
    styles={"color": "#ef4444"}  # Red for down
)

Updating All Tickers

# Replace all ticker items
update_marquee(widget_id, ticker_items=[
    {"ticker": "AAPL", "text": "AAPL $186.25", "styles": {"color": "#22c55e"}},
    {"ticker": "GOOGL", "text": "GOOGL $141.50", "styles": {"color": "#ef4444"}},
    {"ticker": "NEW", "text": "NEW $100.00"},  # Add new ticker
])

Live Data Patterns

Pattern 1: Polling

import time

widget = create_widget(html=initial_html, toolbars=[...])

while True:
    # Fetch new data
    data = fetch_latest_data()

    # Update chart
    fig = create_chart(data)
    update_plotly(widget.id, figure_json=fig.to_json())

    # Update tickers
    for ticker, price in data["prices"].items():
        update_ticker_item(widget.id, ticker=ticker, text=f"{ticker} ${price:.2f}")

    time.sleep(5)  # Poll every 5 seconds

Pattern 2: Event-Driven

widget = create_widget(html=initial_html, toolbars=[
    {
        "position": "top",
        "items": [
            {"type": "button", "label": "Refresh", "event": "refresh"},
            {"type": "select", "label": "Timeframe", "event": "timeframe",
             "options": [
                 {"label": "1D", "value": "1d"},
                 {"label": "1W", "value": "1w"},
                 {"label": "1M", "value": "1m"},
             ]},
        ]
    }
])

while True:
    events = get_events(widget.id, clear=True)
    for e in events:
        if e["event_type"] == "refresh":
            data = fetch_data()
            update_chart(widget.id, data)
        elif e["event_type"] == "timeframe":
            timeframe = e["data"]["value"]
            data = fetch_data(timeframe=timeframe)
            update_chart(widget.id, data)
    time.sleep(0.1)

Pattern 3: Streaming via Marquee

# Marquee is ideal for streaming price data
# - Continuous scrolling
- Individual ticker updates
- Visual indication of changes

def stream_prices():
    while True:
        for ticker, price, change in get_price_updates():
            color = "#22c55e" if change >= 0 else "#ef4444"
            arrow = "▲" if change >= 0 else "▼"
            update_ticker_item(
                widget_id,
                ticker=ticker,
                text=f"{ticker} ${price:.2f} {arrow}",
                styles={"color": color}
            )
        time.sleep(0.1)  # High-frequency updates OK for ticker

Dashboard Layout

Multi-Chart

Read more
@deeleeramone
Ships withpywry

PyWry is a cross-platform app factory, rendering engine and UI toolkit for Python that produces native desktop, web, and notebook experiences from a single API.

9918 skills1 agent1 MCP
Get the whole plugin

Stars on GitHub

Momentum

stars in total
99
Star history of deeleeramone/PyWry on GitHub, a chart by star-history.com

Other skills on pywry.

pywry-orientation

pywry-orientation

When to reach for PyWry MCP tools (native webview rendering, Plotly, TradingView, AgGrid, chat) instead of writing…

@deeleeramone@deeleeramoneInstall Skill
chat_agent

chat_agent

How an agent operates inside a PyWry chat widget — reading user messages, attachments, @-context, tool-call result cards,…

@deeleeramone@deeleeramoneInstall Skill
component_reference

component_reference

**STOP. THIS IS THE AUTHORITATIVE REFERENCE FOR ALL COMPONENTS.** **YOU MUST USE THE EXACT EVENT SIGNATURES DOCUMENTED HERE.**…

@deeleeramone@deeleeramoneInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills10d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc12d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react1d252k
@facebook@facebookInstall Skill

Questions about data_visualization.

Which coding agents can use data_visualization?
data_visualization installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.