vizly
Fully themable, low-boilerplate charting over Apache ECharts. Ship production charts in a few lines of Python (DataFrame in, HTML or JSON out) without nested option builders. Built for speed (local assets, one ECharts load per page) and for native embeds in the stacks you already use.
- Easy to use: set a theme, call
vz.line/vz.bar/ ..., export withto_html()orto_option() - Highly performant: vendored JS by default; GL and plugins load only when a chart needs them
- Worldwide maps by default: bundled world atlas plus
usa; register extra GeoJSON packs when you need them - Trusted asset defaults: no Chinese CDN forced in the backend (unlike many other Python ECharts wrappers)
- Wide chart coverage: 39 registered chart types including cartesian, statistical, geo, graph, 3D, and compose
- Native integrations: Streamlit, FastAPI, Flask, Django, HTMX, and Jupyter
Install
Requires Python 3.9 or later.
pip install vizly
Framework extras (install only what you use): vizly[streamlit], vizly[fastapi], vizly[flask], vizly[django].
HTMX helpers (vizly.integrations.htmx) ship in the base package.
30-second example
import pandas as pd
import vizly as vz
vz.set_theme("corporate")
df = pd.DataFrame({"date": ["2026-01-01", "2026-01-02"], "revenue": [10, 20]})
chart = vz.line(df, x="date", y="revenue", title="Revenue")
chart.to_html() # self-contained local ECharts
chart.to_option() # plain dict for APIs / agents
Chart inventory
vz.list_chart_types()
vz.list_unavailable_chart_types() # e.g. chord (upstream-unavailable)
| Category | Chart types |
|---|---|
| Cartesian | line, bar, area, scatter, effect_scatter |
| Statistical | boxplot, heatmap, parallel, radar, polar |
| Financial | candlestick, kline, waterfall |
| Pie-like | pie, donut, funnel, treemap |
| Geographic | map, geo |
| Relational | graph, sankey, tree, sunburst |
| 3D | bar3d, line3d, scatter3d, surface3d |
| Specialized | gauge, liquid, wordcloud, pictorial_bar, theme_river |
| Compose | grid, page, tab, timeline |
| Mixed | mix / combo |
Upstream unavailable
chord: ECharts 5.x no longer ships a first-class chord series; use sankey or graph instead.
Theming
vz.set_theme("corporate")
# builtins: default, light, dark, corporate, minimal, contrast,
# ops_grafana, ops_cloudwatch, ops_kibana
vz.set_theme({"palette": ["#0B1F33", "#2F6FED"]}) # deep-merge override
vz.register_theme("acme", {"background": "#FFFFFF", "palette": ["#111111"]})
vz.load_theme("path/to/theme.json", activate=True, register=True)
vz.export_theme("acme", "acme.json")
# Per-chart override does not mutate the session theme
vz.bar(df, x="region", y="sales", theme="dark")
Ops-inspired themes
ops_grafana, ops_cloudwatch, and ops_kibana are visual inspiration only: denser grids, muted animation, and step-friendly lines so charts feel at home next to common ops UIs. They are not affiliated with Grafana Labs, Amazon Web Services, or Elastic.
Theme API
| Function | Purpose |
|---|---|
vz.set_theme(name_or_dict) | Session theme (dict deep-merges) |
vz.get_theme() / resolve_theme() | Read or resolve theme |
vz.register_theme(name, dict) | Register a custom preset |
vz.load_theme(path, activate=, register=) | Load theme from JSON file |
vz.export_theme(name, path) | Export a registered theme |
vz.list_themes() | List available theme names |
Per-chart theme= kwarg overrides without mutating session theme.
Core API
Every chart factory lives on the vizly namespace (import vizly as vz).
import vizly as vz
# Sugar constructors
chart = vz.line(df, x='date', y='revenue', title='Revenue')
chart = vz.bar(df, x='region', y='sales', title='Sales')
chart = vz.scatter(df, x='height', y='weight', title='Height vs weight')
# Universal constructor by type name
chart = vz.chart('area', df, x='date', y='revenue', title='Area')
# Wrap a raw ECharts option
chart = vz.from_option({...})
# Export
chart.to_html() # self-contained HTML with local ECharts
chart.to_option() # plain dict for APIs / SPAs
chart.to_json() # JSON string
# Escape hatches
chart.update(...)
chart.merge_option({...})
Export paths
| Export | Role |
|---|---|
chart.to_html() | Self-contained HTML with local ECharts (vendored JS inlined) |
chart.to_option() | Plain dict for APIs, SPAs, or agents |
chart.to_json() | JSON string of the option |
chart.render("file.html") | Write self-contained HTML to a file |
Base lifecycle: data standardization -> _build() -> theme apply -> user merge -> export.
Integrations
Multi-chart / dashboard (important)
Single-chart helpers can include the ECharts library so one embed works alone. For several charts on one page, load assets once. Otherwise each chart ships ~1MB of JS.
from vizly.integrations import assets_html, chart_html, dashboard_html
# Pattern A: shell page (auto GL/plugins from charts=)
head = assets_html(charts=[c1, c2])
a = chart_html(c1, fragment=True, include_assets=False)
b = chart_html(c2, fragment=True, include_assets=False)
# Pattern B: one HTML blob (assets once internally)
html = dashboard_html([c1, c2], title="Dashboard")
# same idea: vz.page(charts=[c1, c2]).to_html()
Streamlit
from vizly.integrations.streamlit import st_vizly, st_dashboard
st_vizly(chart, height=420) # one chart
st_dashboard([c1, c2], height=900) # many charts, ECharts once
st_vizly([c1, c2], height=900) # same as st_dashboard
FastAPI
from vizly.integrations.fastapi import html_response, json_response, dashboard_response
Flask
from vizly.integrations.flask import assets_html, chart_html, dashboard_response
Django templates
INSTALLED_APPS = [..., "vizly.integrations.django"]
# Template:
{% load vizly_tags %}
{% vizly_assets charts=charts %}
{% vizly_chart chart height="420px" %}
{{ chart|vizly_html }}
HTMX
from vizly.integrations.htmx import htmx_chart_fragment, htmx_or_full
Jupyter
Charts display via _repr_html_() on chart objects.
Ops metric helpers
Normalize observability JSON you already have - no live API calls.
import vizly as vz
df = vz.from_prometheus(prom_api_json) # timestamp, value, series
df = vz.from_cloudwatch(cw_datapoints) # timestamp, value [, unit|series]
df = vz.from_elasticsearch(es_search_json) # timestamp, value (alias: from_elk)
vz.set_theme("ops_grafana")
vz.line(df, x="timestamp", y="value")
Maps and geography
| Item | Detail |
|---|---|
| Default atlas | World (vz.map(df)) |
| Bundled regional | usa (vz.map(df, map="usa")) |
| Opt-in packs | register_map_pack(name, geojson_path) for custom GeoJSON you supply |
| Introspection | list_bundled_maps(), list_opt_in_maps() |
SPA / JSON note: to_option() / json_response return the ECharts option only. HTML rendering calls echarts.registerMap for you.
Trust and assets
| Rule | Detail |
|---|---|
| Default mode | assets.mode = "local" (vendored JS inlined or linked from package) |
| Optional CDN | Allowlist only: cdn.jsdelivr.net, unpkg.com |
| Banned by default | China-primary CDN hosts (bootcdn, npmmirror, assets.pyecharts.org, ...) |
| Plugin loading | GL, wordcloud, liquidfill load only when the chart needs them |
Pinned vendored assets
| File | Version |
|---|---|
echarts.min.js | 5.5.1 |
echarts-gl.min.js | 2.0.9 |
echarts-wordcloud.min.js | 2.1.0 |
echarts-liquidfill.min.js | 3.1.0 |
maps/world.json | echarts 4.9.0 map pack |
maps/usa.json | apache/echarts-examples |
Example gallery
All examples use the corporate theme. Each chart below shows the complete Python code.
See all chart types rendered live with theme switching in the Vizly Chart Gallery -- no Python needed.
area
df = pd.DataFrame({
'date': ['2026-01-01', '2026-01-02', '2026-01-03', '2026-01-04', '2026-01-05'],
'revenue': [42, 48, 45, 61, 58],
})
chart = vz.area(df, x='date', y='revenue', title='Revenue trend', theme='corporate')
bar
df = pd.DataFrame({
'region': ['Northeast', 'Southeast', 'Midwest', 'Southwest', 'West', 'Canada'],
'sales': [86, 64, 71, 58, 93, 47],
})
chart = vz.bar(df, x='region', y='sales', title='Sales by region', theme='corporate')
scatter
df = pd.DataFrame({
'height': [1.86, 1.726, 1.893, 1.829, 1.588, 1.94, 1.854],
'weight': [58.2, 74.8, 62.9, 84.2, 73.0, 92.0, 85.6],
})
chart = vz.scatter(df, x='height', y='weight', title='Height vs weight', theme='corporate')
pie
df = pd.DataFrame({
'name': ['Enterprise', 'Mid-market', 'SMB', 'Startup', 'Partner'],
'value': [34, 26, 18, 12, 10],
})
chart = vz.pie(df, names='name', values='value', title='Segment mix', theme='corporate')
map
df = pd.DataFrame({
'name': ['United States', 'Canada', 'Brazil', 'Germany', 'United Kingdom'],
'value': [92, 54, 61, 48, 57],
})
chart = vz.map(df, title='Global demand', theme='corporate')
candlestick
df = pd.DataFrame({
'date': ['2026-01-01', '2026-01-02', '2026-01-03', '2026-01-04', '2026-01-05'],
'open': [100, 104, 101, 108, 112],
'close': [104, 101, 108, 112, 109],
'low': [98, 99, 100, 106, 107],
'high': [106, 105, 110, 114, 114],
})
chart = vz.candlestick(df, x='date', title='Price action', theme='corporate')
combo
df = pd.DataFrame({
'date': ['2026-01-01', '2026-01-02', '2026-01-03', '2026-01-04', '2026-01-05'],
'revenue': [42, 48, 45, 61, 58],
'cost': [18, 20, 19, 24, 22],
})
chart = vz.combo(df, x='date', bar='cost', line='revenue', title='Revenue vs cost', theme='corporate')
page (dashboard)
vz.set_theme('corporate')
chart = vz.page(charts=[
vz.line(df_line, x='date', y=['revenue', 'cost'], title='Revenue vs cost'),
vz.pie(df_pie, names='name', values='value', title='Segment mix'),
vz.map(df_map, title='Global demand'),
], title="Dashboard")
What's New in v0.2.1
Release date: 2026-07-28
v0.2.1 is a patch release focused on packaging metadata and a safer publish path. Product API and chart behavior match v0.2.0.
Added
- Release workflow verify gate (ruff + Level 1/2 pytest) before build/publish
- GitHub Release on
v*tags (CHANGELOG body +dist/artifacts) - Packaging contract tests:
__version__sync withpyproject.toml; runtime requirespandasandnumpy
Changed
- PyPI / package summary: high-performance, low-boilerplate, fully-themable charting over Apache ECharts
- Declare runtime dependencies
pandasandnumpyin package metadata - Release workflow fails when
ENABLE_PYPI_PUBLISHis not set
v0.2.0 highlights (new since v0.1.0)
- Multi-chart dashboards: load ECharts once per page across Streamlit, Django, Flask, FastAPI, and HTMX
- Smarter asset loading:
assets_html(charts=...)auto-include GL and plugin scripts - Public map introspection:
list_bundled_maps()andlist_opt_in_maps() - Three-level test suite: contract tests, sample/golden option snapshots, Playwright browser smoke
- GitHub Actions CI and release: lint, pytest with coverage, build on every PR
- Locale and asset trust refinements: theme
localepassed toecharts.init
Known limitations
- PNG/PDF export:
vizly[export]is deferred; useto_html()/to_option()meanwhile - SPA JSON + maps: client must call
echarts.registerMapfor map/geo charts - Streamlit: each
st_vizlysingle-chart call uses a separate iframe; preferst_dashboardfor many charts
License
MIT