reflex-nivo
Every chart of nivo — the React data-visualization library built on d3 — as a Reflex custom component.
- 51 components from 28 nivo packages, SVG, Canvas and HTML renderers (nivo
0.99.0). - snake_case props generated from nivo's TypeScript declarations, with types and nivo docs links.
- Events land in Python:
on_click,on_mouse_enter,on_node_click,on_arc_click… receive the datum as a JSON-safedict(cycles, DOM nodes and React events are stripped in the browser). - Follows Reflex's light/dark mode out of the box.
- Helpers for axes, legends, scales, color configs, patterns/gradients, markers and annotations,
plus
nivo.tooltip("<b>{id}</b>: {formattedValue}")templates and anivo.js(...)escape hatch.
pip install reflex-nivo
Requires Python 3.11+ and Reflex 0.10+ (reflex-nivo 0.1.x is the line for Reflex 0.9 and Python 3.10).
Quick start
import reflex as rx
import reflex_nivo as nivo
class State(rx.State):
rows: list[dict] = [
{"country": "AD", "burger": 120, "fries": 80},
{"country": "AE", "burger": 95, "fries": 130},
]
selected: str = ""
@rx.event
def on_bar_click(self, datum: dict):
self.selected = f"{datum['indexValue']} · {datum['id']} = {datum['value']}"
def index():
return rx.vstack(
nivo.bar(
data=State.rows,
keys=["burger", "fries"],
index_by="country",
group_mode="grouped",
colors=nivo.scheme("nivo"),
margin=nivo.margin(top=20, right=110, bottom=50, left=60),
axis_bottom=nivo.axis(legend="country", legend_position="middle", legend_offset=36),
legends=[nivo.legend(data_from="keys", anchor="bottom-right", translate_x=100)],
tooltip=nivo.tooltip("<b>{indexValue}</b> · {id}: {formattedValue}"),
on_click=State.on_bar_click,
height="400px",
),
rx.text(State.selected),
)
app = rx.App()
app.add_page(index)
Components
Each factory wraps nivo's Responsive* export. Classes use the nivo name (nivo.Bar, nivo.HeatMapCanvas…).
| Family | SVG | Canvas | HTML |
|---|---|---|---|
| Bar | bar |
bar_canvas |
|
| Line | line |
line_canvas |
|
| Pie | pie |
pie_canvas |
|
| Radar · RadialBar · PolarBar | radar · radial_bar · polar_bar |
||
| Waffle | waffle |
waffle_canvas |
waffle_html |
| ScatterPlot | scatterplot |
scatterplot_canvas |
|
| SwarmPlot | swarmplot |
swarmplot_canvas |
|
| BoxPlot · Voronoi | boxplot · voronoi |
||
| HeatMap | heatmap |
heatmap_canvas |
|
| TreeMap | treemap |
treemap_canvas |
treemap_html |
| CirclePacking | circle_packing |
circle_packing_canvas |
circle_packing_html |
| Sunburst | sunburst |
||
| Icicle | icicle |
icicle_html |
|
| Tree | tree |
tree_canvas |
|
| Sankey · Funnel · Marimekko | sankey · funnel · marimekko |
||
| Chord | chord |
chord_canvas |
|
| Network | network |
network_canvas |
|
| ParallelCoordinates | parallel_coordinates |
parallel_coordinates_canvas |
|
| Calendar | calendar |
calendar_canvas |
|
| TimeRange · Stream · Bump · AreaBump · Bullet | time_range · stream · bump · area_bump · bullet |
||
| GeoMap | geo_map |
geo_map_canvas |
|
| Choropleth | choropleth |
choropleth_canvas |
The data shape and every prop are documented on nivo.rocks (each class docstring links to its page).
Convert prop names to snake_case: indexBy → index_by, enableGridX → enable_grid_x.
Renamed props
| nivo | reflex-nivo | why |
|---|---|---|
id (Pie, Sunburst, CirclePacking, SwarmPlot, Marimekko) |
id_by |
id is the HTML id of the container |
from / to (Calendar, TimeRange) |
from_date / to_date |
from is a Python keyword |
Plain YYYY-MM-DD values for from_date/to_date are sent as local midnight, so calendars do not
shift one day (and one year) back west of Greenwich.
Sizing
nivo's responsive charts fill their parent, so every factory returns the chart inside a <div>.
width (default "100%"), height (default "400px") and any other CSS prop, class_name, id
or style go to that container. Anything that is neither a nivo prop nor CSS raises a TypeError
with a suggestion, so typos like enable_gridx do not fail silently.
nivo.pie(data=State.pie, height="320px", max_width="600px", margin_x="auto")
(margin itself is nivo's chart margin: use nivo.margin(top=.., right=.., bottom=.., left=..).)
Events
All nivo callbacks are exposed as event triggers. The handler receives nivo's first callback argument
(datum, point, slice, node, cell, serie, link, feature, dimensions, active id…), serialized in the
browser: functions, DOM nodes and React events are dropped, repeated/cyclic objects are replaced by
their id, and depth is bounded.
class State(rx.State):
@rx.event
def drill_down(self, node: dict):
if node["data"].get("children"):
self.sunburst = node["data"] # the raw datum travels with the event
nivo.sunburst(data=State.sunburst, id_by="name", value="loc", on_click=State.drill_down)
Only the callbacks a chart actually declares carry a datum, and they differ per chart: @nivo/bar
exposes on_click, on_mouse_enter and on_mouse_leave, while @nivo/stream exposes none of them.
Any other on_* is attached to the wrapping <div> as a plain DOM event, so it fires for the whole
chart area but receives no datum (write the handler without arguments). An on_* that is neither
raises a TypeError listing the chart's nivo callbacks.
on_mouse_move fires very often; on the charts that expose it, prefer click/enter/leave or throttle
it: on_mouse_move=State.hover.throttle(100).
Hiding a prop
Passing None sends JavaScript null, which turns the feature off instead of falling back to nivo's
default — that is how you hide an axis:
nivo.bar(data=State.rows, axis_bottom=None, axis_left=None) # no axes
nivo.pie(data=State.pie, theme=None) # nivo's own theme instead of the Reflex one
Helpers
nivo.margin(top=20, right=20, bottom=40, left=50)
nivo.axis(legend="price", legend_position="middle", legend_offset=-40, tick_rotation=-45, format=" >-$.2f")
nivo.legend(
anchor="bottom-right", direction="column", translate_x=100, effects=[nivo.hover_effect(item_opacity=1)]
)
nivo.scale("time", format="%Y-%m-%d", precision="day")
nivo.scheme("category10") # {"scheme": "category10"}
nivo.inherit("color", ("darker", 1.6)) # inherited color
nivo.from_theme("background") # color from theme
nivo.marker("y", 100, legend="target")
nivo.annotation({"id": "fries"}, type_="circle", note="max", note_x=40, note_y=-30)
nivo.pattern_dots_def("dots", background="inherit", color="#38bcb2")
nivo.pattern_lines_def("lines", rotation=-45) / nivo.pattern_squares_def(...) / nivo.linear_gradient_def(...)
nivo.fill_rule("dots", {"id": "fries"})
nivo.props(any_snake_case="option") # generic camelCase dict builder
Constants: nivo.COLOR_SCHEMES (categorical/diverging/sequential), nivo.CURVES, nivo.MOTION_CONFIGS.
Functions: templates, tooltips and raw JS
nivo accepts functions for labels, formatters, accessors, tooltips and custom layers.
# Formatter/label from a {path} template, evaluated in the browser:
label = nivo.template("{id}: {formattedValue}")
# Tooltip component from a template. It may use attribute-less <b>, <strong>, <i>, <em>, <u>,
# <s>, <small>, <code>, <span>, <div>, <p> and <br>; anything else, and every interpolated
# value, is rendered as text, so template and values are safe to build from a state Var.
tooltip = nivo.tooltip("<b>{datum.label}</b><br/>{datum.formattedValue}", style={"min_width": "120px"})
# Anything else: a raw JavaScript expression (never build it from user input).
value_format = nivo.js("v => `${v.toLocaleString()} €`")
node_size = nivo.js("n => n.size")
Most formatters also accept d3-format / d3-time-format strings directly (value_format=".2s",
axis(format="%b %d")).
Themes
Charts use nivo.themes.auto() unless you pass theme=: a light/dark pair built on the Radix gray
scale that switches with rx.color_mode. Customize or pin it:
theme = nivo.themes.auto({"grid": {"line": {"strokeDasharray": "4 4"}}}) # both modes
theme = nivo.themes.auto(light={"text": {"fill": "#333"}}, dark={"text": {"fill": "#eee"}})
theme = nivo.themes.DARK # fixed
theme = State.computed_theme # any nivo theme dict
theme = {} # nivo's own default
Demo
nivo_demo/ is a 10-page app that uses all 51 components with live state controls, an event
inspector, drill-down/zoom interactions, geo projections, canvas variants with thousands of marks and
customization examples.
uv venv && uv pip install -e . && cd nivo_demo && uv pip install -r requirements.txt
uv run reflex run # http://localhost:3000
If a tab from another Reflex app is still open on the same ports, its frontend reconnects to this
backend and the console reports no dispatch function for substate(s): close or reload that tab, or
give one of the apps its own frontend_port/backend_port.
World country shapes in the demo come from the nivo website dataset (Natural Earth).
Regenerating the wrappers
The chart modules in custom_components/reflex_nivo/charts/ are generated from nivo's typings:
# in a scratch directory
npm i typescript@5 @types/react@19 react@19 react-dom@19 @nivo/{bar,line,pie,...}@0.99.0
node /path/to/reflex-nivo/scripts/extract_nivo_props.cjs bar boxplot bullet bump calendar chord \
circle-packing funnel heatmap icicle line marimekko network pie polar-bar radar radial-bar sankey \
scatterplot stream sunburst swarmplot tree treemap voronoi waffle > /path/to/reflex-nivo/scripts/nivo_props.json
# back in the repo (geo and parallel-coordinates ship no usable typings: see scripts/nivo_manual_props.json)
python scripts/generate_components.py
Bump NIVO_VERSION in custom_components/reflex_nivo/constants.py together with the extraction.
Development
uv pip install -e ".[dev]"
uv run pytest
uv run reflex component build # generates .pyi stubs, then sdist + wheel in dist/
CI (.github/workflows/ci.yml) runs ruff, the tests on Python 3.11–3.13, the generator drift check, the
distribution checks and a production build of the demo on Node 24.
Releasing
- Bump
versioninpyproject.tomland__version__incustom_components/reflex_nivo/__init__.py, and add a## X.Y.Z — datesection toCHANGELOG.md. - Merge into
main, then tag the merge commit and push the tag:git checkout main && git pull git tag vX.Y.Z && git push origin vX.Y.Z
.github/workflows/release.ymlchecks that the tag matches the version, builds the sdist and wheel, publishes them to PyPI through trusted publishing (thepypienvironment) and creates the GitHub release with the changelog section as notes.
License
MIT © Ernesto Crespo. nivo is MIT © Raphaël Benitte.
Metadata
Release files for reflex-nivo 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| reflex_nivo-0.2.0.tar.gz | 77.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| reflex_nivo-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 215.7 kB
Release files / reflex_nivo-0.2.0.tar.gz
| Download URL | reflex_nivo-0.2.0.tar.gz |
|---|---|
| Size | 77.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
847c24873db2b9c1725ed96d7a257a5f3952c69e180ce05d5cbbe26bc7d4f710
|
|
BLAKE2b-256 checksum How to use checksums |
d74b7ca72de233534300e464f0999c0111034d69200631837e3615a4994c7132
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logRelease files / reflex_nivo-0.2.0-py3-none-any.whl
| Download URL | reflex_nivo-0.2.0-py3-none-any.whl |
|---|---|
| Size | 138.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d9603f93ffe5ee21650a14d34905dd4ce3e0a7fd6012eda46eb71b8f1ca6b260
|
|
BLAKE2b-256 checksum How to use checksums |
4729e17ddd6219bd2b752d90d44854e110747d9f7f599129f03de48acc17bee9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logSecurity for reflex-nivo 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for reflex_nivo-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.