Visualizations (@view)
Panda contracts can produce rich visualizations using the @view decorator. Views render natively in the block explorer, playground, and notebooks -- charts, SVGs, HTML widgets, and images display inline without any external tools.
Overview
The @view decorator marks a query method that returns visual content instead of raw data. The explorer detects the content type and renders it appropriately. Unlike @query, @view accepts a content_type parameter that tells the renderer what format to expect.
Here is a complete Vega-Lite scatter plot contract:
<!-- Source: contracts/views/vega_scatter.py -->from panda import contract, call, view
@contract
class VegaScatter:
class State:
points: list = []
title: str = "Scatter Plot"
@call
def add_point(self, ctx, x: float, y: float, label: str = "default"):
self.state.points = self.state.points + [{"x": x, "y": y, "label": label}]
@call
def set_points(self, ctx, points: list):
self.state.points = points
@view(content_type="vega")
def scatter(self):
return {
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"title": self.state.title,
"data": {"values": self.state.points},
"mark": {"type": "point", "filled": True, "size": 60},
"encoding": {
"x": {"field": "x", "type": "quantitative"},
"y": {"field": "y", "type": "quantitative"},
"color": {"field": "label", "type": "nominal"},
},
"width": 400,
"height": 300,
}
Supported Content Types
| Type | Content-Type | Description |
|---|---|---|
| Vega-Lite | application/vnd.vegalite+json | Interactive charts (bar, line, scatter, heatmap, etc.) |
| SVG | image/svg+xml | Vector graphics |
| HTML | text/html | Arbitrary HTML content |
| JSON | application/json | Structured data |
| PNG/JPEG | image/png, image/jpeg | Base64-encoded images |
| Plain text | text/plain | Rendered as preformatted text |
The explorer auto-detects the content type from the return value:
- JSON objects with a
$schemafield containing "vega" are treated as Vega-Lite specs - Strings starting with
<svgare treated as SVG - Strings starting with
<!or<htmlare treated as HTML - Otherwise, the content type header from the RPC response is used
Vega-Lite Charts
Vega-Lite is the recommended way to create interactive charts. The VegaScatter contract above shows the pattern: return a Vega-Lite JSON spec from a @view(content_type="vega") method. The spec is a Python dict -- the renderer serializes it automatically.
Vega-Lite charts are interactive by default -- hover for tooltips, click to select, drag to zoom (depending on the spec).
SVG Graphics
Return raw SVG markup for custom vector graphics. Here is the BarChart contract that renders an SVG bar chart:
from panda import contract, call, query, view
@contract
class BarChart:
class State:
data: list = [10, 25, 40, 30, 50]
title: str = "Bar Chart"
bar_color: str = "#2dd4bf"
@call
def set_data(self, ctx, data: list):
self.state.data = data
@call
def set_title(self, ctx, title: str):
self.state.title = title
@view(content_type="svg")
def chart(self):
data = self.state.data
max_val = max(data) if data else 1
bar_w = 50
gap = 10
chart_w = len(data) * (bar_w + gap) + gap
chart_h = 150
color = self.state.bar_color
bars = ""
for i, val in enumerate(data):
h = int((val / max_val) * (chart_h - 30))
x = gap + i * (bar_w + gap)
y = chart_h - h - 20
bars += f'<rect x="{x}" y="{y}" width="{bar_w}" height="{h}" fill="{color}" rx="4"/>'
bars += f'<text x="{x + bar_w // 2}" y="{chart_h - 5}" text-anchor="middle" font-size="12" fill="#888">{val}</text>'
title_el = f'<text x="{chart_w // 2}" y="15" text-anchor="middle" font-size="14" font-weight="bold" fill="#fff">{self.state.title}</text>'
return f'<svg width="{chart_w}" height="{chart_h}" xmlns="http://www.w3.org/2000/svg" style="background:#111">{title_el}{bars}</svg>'
@query
def get_data(self):
return self.state.data
SVG content is sanitized before rendering -- script tags and event handlers are stripped for security.
HTML Widgets
Return HTML for rich interactive content. The Dashboard contract renders metric cards using styled HTML:
from panda import contract, call, view
@contract
class Dashboard:
class State:
metrics: dict = {"users": 0, "transactions": 0, "contracts": 0, "gas_used": 0}
title: str = "Panda Dashboard"
@call
def update_metrics(self, ctx, **kwargs):
metrics = dict(self.state.metrics)
for k, v in kwargs.items():
if k in metrics:
metrics[k] = v
self.state.metrics = metrics
@view(content_type="html")
def render(self):
m = self.state.metrics
cards = ""
for key, val in m.items():
label = key.replace("_", " ").title()
cards += f"""
<div style="background:#1a1a2e;border-radius:12px;padding:20px;min-width:150px;text-align:center">
<div style="font-size:28px;font-weight:bold;color:#2dd4bf">{val:,}</div>
<div style="font-size:13px;color:#888;margin-top:4px">{label}</div>
</div>"""
return f"""<!DOCTYPE html>
<html><head><style>
body {{ background:#0b0b0d; color:#fff; font-family:system-ui; margin:0; padding:24px }}
.grid {{ display:flex; gap:16px; flex-wrap:wrap; justify-content:center }}
h1 {{ text-align:center; font-size:22px; margin-bottom:24px; color:#2dd4bf }}
</style></head><body>
<h1>{self.state.title}</h1>
<div class="grid">{cards}</div>
</body></html>"""
HTML is rendered inside a sandboxed iframe for security.
Data Tables
The DataTable contract supports both HTML and JSON views of tabular data:
from panda import contract, call, view
@contract
class DataTable:
class State:
columns: list = ["Name", "Value", "Type"]
rows: list = []
@call
def add_row(self, ctx, row: list):
self.state.rows = self.state.rows + [row]
@call
def set_data(self, ctx, columns: list, rows: list):
self.state.columns = columns
self.state.rows = rows
@view(content_type="html")
def table(self):
cols = self.state.columns
rows = self.state.rows
headers = "".join(
f"<th style='padding:8px 16px;text-align:left;border-bottom:2px solid #2dd4bf'>{c}</th>"
for c in cols
)
body = ""
for row in rows:
cells = "".join(
f"<td style='padding:8px 16px;border-bottom:1px solid #222'>{v}</td>" for v in row
)
body += f"<tr>{cells}</tr>"
return f"""<!DOCTYPE html>
<html><head><style>
body {{ background:#0b0b0d; color:#e0e0e0; font-family:monospace; margin:0; padding:16px }}
table {{ border-collapse:collapse; width:100% }}
</style></head><body>
<table><thead><tr>{headers}</tr></thead><tbody>{body}</tbody></table>
</body></html>"""
@view(content_type="json")
def as_json(self):
return {
"columns": self.state.columns,
"rows": self.state.rows,
"total_rows": len(self.state.rows),
}
Note how DataTable exposes two views: table returns styled HTML, while as_json returns structured JSON for programmatic consumption.
Viewing Visualizations
From the Block Explorer
Navigate to any contract page and look for methods marked with the @view badge. Click Render to see the visualization inline.
From the Playground
In the Contract Playground, @view methods appear in the Interact panel with a render button. The visualization displays directly below the method form.
From Notebooks
There are three ways to view visualizations in Notebooks:
1. Using the Contract class:
dashboard = Contract("0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18")
await dashboard.view("render")
2. Using magic commands:
%panda view 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18 scatter
%panda view 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18 render
3. Building charts locally from contract data:
You can query contract state and create visualizations entirely in-browser with matplotlib:
import micropip
await micropip.install("matplotlib")
import matplotlib
matplotlib.use("AGG")
import matplotlib.pyplot as plt
import io, base64
# Fetch data from a contract
chart = Contract("0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18")
state = await chart.get_state()
# Create chart locally
fig, ax = plt.subplots(figsize=(8, 4))
ax.bar(range(len(state["data"])), state["data"], color="#2dd4bf")
ax.set_title("Bar Chart from Contract State")
# Render as inline image
buf = io.BytesIO()
fig.savefig(buf, format="png", dpi=100, bbox_inches="tight")
buf.seek(0)
print(f"<img src='data:image/png;base64,{base64.b64encode(buf.read()).decode()}'/>")
plt.close(fig)
Complete Workflow Example
Here is a full example using the real VegaScatter contract: deploy it, add data, then visualize.
Step 1: Deploy from a Notebook
%panda deploy 0 {"title": "Training Clusters"}
Note the contract address from the output (e.g., 0xABC...123).
Step 2: Add Some Data Points
%panda call 0xABC...123 add_point {"x": 1.0, "y": 2.5, "label": "cluster_a"}
%panda call 0xABC...123 add_point {"x": 3.0, "y": 1.2, "label": "cluster_b"}
%panda call 0xABC...123 add_point {"x": 2.0, "y": 3.8, "label": "cluster_a"}
%panda call 0xABC...123 add_point {"x": 4.5, "y": 0.9, "label": "cluster_b"}
%panda call 0xABC...123 add_point {"x": 1.5, "y": 3.1, "label": "cluster_a"}
Step 3: Render the Chart
%panda view 0xABC...123 scatter
The Vega-Lite scatter plot renders inline with colored points grouped by label.
Step 4: Analyze in Python
scatter = Contract("0xABC...123")
# The VegaScatter contract doesn't have a query for raw points,
# but you can query state directly
state = await scatter.get_state()
points = state["points"]
print(f"Total points: {len(points)}")
for p in points:
print(f" ({p['x']}, {p['y']}) -> {p['label']}")
Troubleshooting
"address X is not a Panda contract"
This means no Panda contract is deployed at the given address. This can happen if:
- The address is wrong -- double-check it
- The contract was deployed on a different network (e.g., local devnet vs mainnet)
- The deploy transaction failed silently (check the tx receipt status)
Verify a contract exists with %panda state <address> in a notebook.
View returns empty or plain text
Make sure the @view method returns a properly formatted value:
- For Vega-Lite: use
@view(content_type="vega")and return a dict with a$schemafield - For SVG: use
@view(content_type="svg")and return a string starting with<svg - For HTML: use
@view(content_type="html")and return a string starting with<!DOCTYPEor<html
Chart not interactive
Vega-Lite interactivity depends on the spec. Add selection or params to your spec for click/hover/drag interactions. See the Vega-Lite docs.
Next Steps
- Try the Contract Playground to deploy a contract with
@viewmethods - Use Notebooks to query and visualize contract data interactively
- See the Contract Guide for the full decorator reference