reflex-nivo 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. reflex_nivo-0.1.0/LICENSE +21 -0
  2. reflex_nivo-0.1.0/PKG-INFO +283 -0
  3. reflex_nivo-0.1.0/README.md +247 -0
  4. reflex_nivo-0.1.0/custom_components/reflex_nivo/__init__.py +41 -0
  5. reflex_nivo-0.1.0/custom_components/reflex_nivo/_css.py +808 -0
  6. reflex_nivo-0.1.0/custom_components/reflex_nivo/base.py +374 -0
  7. reflex_nivo-0.1.0/custom_components/reflex_nivo/base.pyi +94 -0
  8. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/__init__.py +160 -0
  9. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/bar.py +294 -0
  10. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/bar.pyi +400 -0
  11. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/boxplot.py +168 -0
  12. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/boxplot.pyi +223 -0
  13. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/bullet.py +110 -0
  14. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/bullet.pyi +175 -0
  15. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/bump.py +249 -0
  16. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/bump.pyi +334 -0
  17. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/calendar.py +297 -0
  18. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/calendar.pyi +478 -0
  19. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/chord.py +230 -0
  20. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/chord.pyi +344 -0
  21. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/circle_packing.py +281 -0
  22. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/circle_packing.pyi +424 -0
  23. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/funnel.py +119 -0
  24. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/funnel.pyi +163 -0
  25. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/geo.py +321 -0
  26. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/geo.pyi +625 -0
  27. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/heatmap.py +239 -0
  28. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/heatmap.pyi +334 -0
  29. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/icicle.py +310 -0
  30. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/icicle.pyi +393 -0
  31. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/line.py +282 -0
  32. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/line.pyi +373 -0
  33. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/marimekko.py +111 -0
  34. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/marimekko.pyi +155 -0
  35. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/network.py +203 -0
  36. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/network.pyi +294 -0
  37. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/parallel_coordinates.py +168 -0
  38. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/parallel_coordinates.pyi +329 -0
  39. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/pie.py +270 -0
  40. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/pie.pyi +392 -0
  41. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/polar_bar.py +129 -0
  42. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/polar_bar.pyi +197 -0
  43. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/radar.py +128 -0
  44. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/radar.pyi +179 -0
  45. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/radial_bar.py +133 -0
  46. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/radial_bar.pyi +201 -0
  47. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/sankey.py +130 -0
  48. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/sankey.pyi +181 -0
  49. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/scatterplot.py +218 -0
  50. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/scatterplot.pyi +300 -0
  51. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/stream.py +133 -0
  52. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/stream.pyi +181 -0
  53. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/sunburst.py +109 -0
  54. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/sunburst.pyi +173 -0
  55. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/swarmplot.py +236 -0
  56. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/swarmplot.pyi +324 -0
  57. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/tree.py +269 -0
  58. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/tree.pyi +392 -0
  59. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/treemap.py +325 -0
  60. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/treemap.pyi +478 -0
  61. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/voronoi.py +77 -0
  62. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/voronoi.pyi +123 -0
  63. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/waffle.py +287 -0
  64. reflex_nivo-0.1.0/custom_components/reflex_nivo/charts/waffle.pyi +430 -0
  65. reflex_nivo-0.1.0/custom_components/reflex_nivo/constants.py +103 -0
  66. reflex_nivo-0.1.0/custom_components/reflex_nivo/helpers.py +451 -0
  67. reflex_nivo-0.1.0/custom_components/reflex_nivo/py.typed +0 -0
  68. reflex_nivo-0.1.0/custom_components/reflex_nivo/themes.py +167 -0
  69. reflex_nivo-0.1.0/custom_components/reflex_nivo.egg-info/PKG-INFO +283 -0
  70. reflex_nivo-0.1.0/custom_components/reflex_nivo.egg-info/SOURCES.txt +74 -0
  71. reflex_nivo-0.1.0/custom_components/reflex_nivo.egg-info/dependency_links.txt +1 -0
  72. reflex_nivo-0.1.0/custom_components/reflex_nivo.egg-info/requires.txt +13 -0
  73. reflex_nivo-0.1.0/custom_components/reflex_nivo.egg-info/top_level.txt +1 -0
  74. reflex_nivo-0.1.0/pyproject.toml +79 -0
  75. reflex_nivo-0.1.0/setup.cfg +4 -0
  76. reflex_nivo-0.1.0/tests/test_components.py +258 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ernesto Crespo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,283 @@
1
+ Metadata-Version: 2.4
2
+ Name: reflex-nivo
3
+ Version: 0.1.0
4
+ Summary: Every nivo chart (bar, line, pie, sankey, treemap, choropleth... 51 components) as a Reflex component.
5
+ Author-email: Ernesto Crespo <ecrespo@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ecrespo/reflex-nivo
8
+ Project-URL: Source, https://github.com/ecrespo/reflex-nivo
9
+ Project-URL: Issues, https://github.com/ecrespo/reflex-nivo/issues
10
+ Project-URL: Changelog, https://github.com/ecrespo/reflex-nivo/blob/main/CHANGELOG.md
11
+ Project-URL: nivo, https://nivo.rocks
12
+ Keywords: reflex,reflex-custom-components,nivo,d3,charts,dataviz,visualization
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Visualization
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: reflex>=0.9.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: build; extra == "dev"
28
+ Requires-Dist: twine; extra == "dev"
29
+ Requires-Dist: pytest; extra == "dev"
30
+ Provides-Extra: lint
31
+ Requires-Dist: ruff==0.16.8; extra == "lint"
32
+ Provides-Extra: security
33
+ Requires-Dist: bandit[toml]==1.9.4; extra == "security"
34
+ Requires-Dist: pip-audit==2.10.1; extra == "security"
35
+ Dynamic: license-file
36
+
37
+ # reflex-nivo
38
+
39
+ Every chart of [nivo](https://nivo.rocks) — the React data-visualization library built on d3 — as a
40
+ [Reflex](https://reflex.dev) custom component.
41
+
42
+ - **51 components** from **28 nivo packages**, SVG, Canvas and HTML renderers (nivo `0.99.0`).
43
+ - **snake_case props** generated from nivo's TypeScript declarations, with types and nivo docs links.
44
+ - **Events land in Python**: `on_click`, `on_mouse_enter`, `on_node_click`, `on_arc_click`… receive the
45
+ datum as a JSON-safe `dict` (cycles, DOM nodes and React events are stripped in the browser).
46
+ - **Follows Reflex's light/dark mode** out of the box.
47
+ - Helpers for axes, legends, scales, color configs, patterns/gradients, markers and annotations,
48
+ plus `nivo.tooltip("<b>{id}</b>: {formattedValue}")` templates and a `nivo.js(...)` escape hatch.
49
+
50
+ ```bash
51
+ pip install reflex-nivo
52
+ ```
53
+
54
+ ## Quick start
55
+
56
+ ```python
57
+ import reflex as rx
58
+ import reflex_nivo as nivo
59
+
60
+
61
+ class State(rx.State):
62
+ rows: list[dict] = [
63
+ {"country": "AD", "burger": 120, "fries": 80},
64
+ {"country": "AE", "burger": 95, "fries": 130},
65
+ ]
66
+ selected: str = ""
67
+
68
+ @rx.event
69
+ def on_bar_click(self, datum: dict):
70
+ self.selected = f"{datum['indexValue']} · {datum['id']} = {datum['value']}"
71
+
72
+
73
+ def index():
74
+ return rx.vstack(
75
+ nivo.bar(
76
+ data=State.rows,
77
+ keys=["burger", "fries"],
78
+ index_by="country",
79
+ group_mode="grouped",
80
+ colors=nivo.scheme("nivo"),
81
+ margin=nivo.margin(top=20, right=110, bottom=50, left=60),
82
+ axis_bottom=nivo.axis(legend="country", legend_position="middle", legend_offset=36),
83
+ legends=[nivo.legend(data_from="keys", anchor="bottom-right", translate_x=100)],
84
+ tooltip=nivo.tooltip("<b>{indexValue}</b> · {id}: {formattedValue}"),
85
+ on_click=State.on_bar_click,
86
+ height="400px",
87
+ ),
88
+ rx.text(State.selected),
89
+ )
90
+
91
+
92
+ app = rx.App()
93
+ app.add_page(index)
94
+ ```
95
+
96
+ ## Components
97
+
98
+ Each factory wraps nivo's `Responsive*` export. Classes use the nivo name (`nivo.Bar`, `nivo.HeatMapCanvas`…).
99
+
100
+ | Family | SVG | Canvas | HTML |
101
+ | --- | --- | --- | --- |
102
+ | Bar | `bar` | `bar_canvas` | |
103
+ | Line | `line` | `line_canvas` | |
104
+ | Pie | `pie` | `pie_canvas` | |
105
+ | Radar · RadialBar · PolarBar | `radar` · `radial_bar` · `polar_bar` | | |
106
+ | Waffle | `waffle` | `waffle_canvas` | `waffle_html` |
107
+ | ScatterPlot | `scatterplot` | `scatterplot_canvas` | |
108
+ | SwarmPlot | `swarmplot` | `swarmplot_canvas` | |
109
+ | BoxPlot · Voronoi | `boxplot` · `voronoi` | | |
110
+ | HeatMap | `heatmap` | `heatmap_canvas` | |
111
+ | TreeMap | `treemap` | `treemap_canvas` | `treemap_html` |
112
+ | CirclePacking | `circle_packing` | `circle_packing_canvas` | `circle_packing_html` |
113
+ | Sunburst | `sunburst` | | |
114
+ | Icicle | `icicle` | | `icicle_html` |
115
+ | Tree | `tree` | `tree_canvas` | |
116
+ | Sankey · Funnel · Marimekko | `sankey` · `funnel` · `marimekko` | | |
117
+ | Chord | `chord` | `chord_canvas` | |
118
+ | Network | `network` | `network_canvas` | |
119
+ | ParallelCoordinates | `parallel_coordinates` | `parallel_coordinates_canvas` | |
120
+ | Calendar | `calendar` | `calendar_canvas` | |
121
+ | TimeRange · Stream · Bump · AreaBump · Bullet | `time_range` · `stream` · `bump` · `area_bump` · `bullet` | | |
122
+ | GeoMap | `geo_map` | `geo_map_canvas` | |
123
+ | Choropleth | `choropleth` | `choropleth_canvas` | |
124
+
125
+ The data shape and every prop are documented on nivo.rocks (each class docstring links to its page).
126
+ Convert prop names to snake_case: `indexBy` → `index_by`, `enableGridX` → `enable_grid_x`.
127
+
128
+ ### Renamed props
129
+
130
+ | nivo | reflex-nivo | why |
131
+ | --- | --- | --- |
132
+ | `id` (Pie, Sunburst, CirclePacking, SwarmPlot, Marimekko) | `id_by` | `id` is the HTML id of the container |
133
+ | `from` / `to` (Calendar, TimeRange) | `from_date` / `to_date` | `from` is a Python keyword |
134
+
135
+ Plain `YYYY-MM-DD` values for `from_date`/`to_date` are sent as local midnight, so calendars do not
136
+ shift one day (and one year) back west of Greenwich.
137
+
138
+ ## Sizing
139
+
140
+ nivo's responsive charts fill their parent, so every factory returns the chart inside a `<div>`.
141
+ `width` (default `"100%"`), `height` (default `"400px"`) and any other CSS prop, `class_name`, `id`
142
+ or `style` go to that container. Anything that is neither a nivo prop nor CSS raises a `TypeError`
143
+ with a suggestion, so typos like `enable_gridx` do not fail silently.
144
+
145
+ ```python
146
+ nivo.pie(data=State.pie, height="320px", max_width="600px", margin_x="auto")
147
+ ```
148
+
149
+ (`margin` itself is nivo's chart margin: use `nivo.margin(top=.., right=.., bottom=.., left=..)`.)
150
+
151
+ ## Events
152
+
153
+ All nivo callbacks are exposed as event triggers. The handler receives nivo's first callback argument
154
+ (datum, point, slice, node, cell, serie, link, feature, dimensions, active id…), serialized in the
155
+ browser: functions, DOM nodes and React events are dropped, repeated/cyclic objects are replaced by
156
+ their `id`, and depth is bounded.
157
+
158
+ ```python
159
+ class State(rx.State):
160
+ @rx.event
161
+ def drill_down(self, node: dict):
162
+ if node["data"].get("children"):
163
+ self.sunburst = node["data"] # the raw datum travels with the event
164
+
165
+
166
+ nivo.sunburst(data=State.sunburst, id_by="name", value="loc", on_click=State.drill_down)
167
+ ```
168
+
169
+ Only the callbacks a chart actually declares carry a datum, and they differ per chart: `@nivo/bar`
170
+ exposes `on_click`, `on_mouse_enter` and `on_mouse_leave`, while `@nivo/stream` exposes none of them.
171
+ Any other `on_*` is attached to the wrapping `<div>` as a plain DOM event, so it fires for the whole
172
+ chart area but receives no datum (write the handler without arguments). An `on_*` that is neither
173
+ raises a `TypeError` listing the chart's nivo callbacks.
174
+
175
+ `on_mouse_move` fires very often; on the charts that expose it, prefer click/enter/leave or throttle
176
+ it: `on_mouse_move=State.hover.throttle(100)`.
177
+
178
+ ### Hiding a prop
179
+
180
+ Passing `None` sends JavaScript `null`, which turns the feature off instead of falling back to nivo's
181
+ default — that is how you hide an axis:
182
+
183
+ ```python
184
+ nivo.bar(data=State.rows, axis_bottom=None, axis_left=None) # no axes
185
+ nivo.pie(data=State.pie, theme=None) # nivo's own theme instead of the Reflex one
186
+ ```
187
+
188
+ ## Helpers
189
+
190
+ ```python
191
+ nivo.margin(top=20, right=20, bottom=40, left=50)
192
+ nivo.axis(legend="price", legend_position="middle", legend_offset=-40, tick_rotation=-45, format=" >-$.2f")
193
+ nivo.legend(
194
+ anchor="bottom-right", direction="column", translate_x=100, effects=[nivo.hover_effect(item_opacity=1)]
195
+ )
196
+ nivo.scale("time", format="%Y-%m-%d", precision="day")
197
+ nivo.scheme("category10") # {"scheme": "category10"}
198
+ nivo.inherit("color", ("darker", 1.6)) # inherited color
199
+ nivo.from_theme("background") # color from theme
200
+ nivo.marker("y", 100, legend="target")
201
+ nivo.annotation({"id": "fries"}, type_="circle", note="max", note_x=40, note_y=-30)
202
+ nivo.pattern_dots_def("dots", background="inherit", color="#38bcb2")
203
+ nivo.pattern_lines_def("lines", rotation=-45) / nivo.pattern_squares_def(...) / nivo.linear_gradient_def(...)
204
+ nivo.fill_rule("dots", {"id": "fries"})
205
+ nivo.props(any_snake_case="option") # generic camelCase dict builder
206
+ ```
207
+
208
+ Constants: `nivo.COLOR_SCHEMES` (categorical/diverging/sequential), `nivo.CURVES`, `nivo.MOTION_CONFIGS`.
209
+
210
+ ### Functions: templates, tooltips and raw JS
211
+
212
+ nivo accepts functions for labels, formatters, accessors, tooltips and custom layers.
213
+
214
+ ```python
215
+ # Formatter/label from a {path} template, evaluated in the browser:
216
+ label = nivo.template("{id}: {formattedValue}")
217
+
218
+ # Tooltip component from a template. It may use attribute-less <b>, <strong>, <i>, <em>, <u>,
219
+ # <s>, <small>, <code>, <span>, <div>, <p> and <br>; anything else, and every interpolated
220
+ # value, is rendered as text, so template and values are safe to build from a state Var.
221
+ tooltip = nivo.tooltip("<b>{datum.label}</b><br/>{datum.formattedValue}", style={"min_width": "120px"})
222
+
223
+ # Anything else: a raw JavaScript expression (never build it from user input).
224
+ value_format = nivo.js("v => `${v.toLocaleString()} €`")
225
+ node_size = nivo.js("n => n.size")
226
+ ```
227
+
228
+ Most formatters also accept d3-format / d3-time-format strings directly (`value_format=".2s"`,
229
+ `axis(format="%b %d")`).
230
+
231
+ ## Themes
232
+
233
+ Charts use `nivo.themes.auto()` unless you pass `theme=`: a light/dark pair built on the Radix gray
234
+ scale that switches with `rx.color_mode`. Customize or pin it:
235
+
236
+ ```python
237
+ theme = nivo.themes.auto({"grid": {"line": {"strokeDasharray": "4 4"}}}) # both modes
238
+ theme = nivo.themes.auto(light={"text": {"fill": "#333"}}, dark={"text": {"fill": "#eee"}})
239
+ theme = nivo.themes.DARK # fixed
240
+ theme = State.computed_theme # any nivo theme dict
241
+ theme = {} # nivo's own default
242
+ ```
243
+
244
+ ## Demo
245
+
246
+ `nivo_demo/` is a 10-page app that uses all 51 components with live state controls, an event
247
+ inspector, drill-down/zoom interactions, geo projections, canvas variants with thousands of marks and
248
+ customization examples.
249
+
250
+ ```bash
251
+ uv venv && uv pip install -e . && cd nivo_demo && uv pip install -r requirements.txt
252
+ uv run reflex run # http://localhost:3010 (backend on 8010)
253
+ ```
254
+
255
+ World country shapes in the demo come from the nivo website dataset (Natural Earth).
256
+
257
+ ## Regenerating the wrappers
258
+
259
+ The chart modules in `custom_components/reflex_nivo/charts/` are generated from nivo's typings:
260
+
261
+ ```bash
262
+ # in a scratch directory
263
+ npm i typescript@5 @types/react@19 react@19 react-dom@19 @nivo/{bar,line,pie,...}@0.99.0
264
+ node /path/to/reflex-nivo/scripts/extract_nivo_props.cjs bar boxplot bullet bump calendar chord \
265
+ circle-packing funnel heatmap icicle line marimekko network pie polar-bar radar radial-bar sankey \
266
+ scatterplot stream sunburst swarmplot tree treemap voronoi waffle > /path/to/reflex-nivo/scripts/nivo_props.json
267
+ # back in the repo (geo and parallel-coordinates ship no usable typings: see scripts/nivo_manual_props.json)
268
+ python scripts/generate_components.py
269
+ ```
270
+
271
+ Bump `NIVO_VERSION` in `custom_components/reflex_nivo/constants.py` together with the extraction.
272
+
273
+ ## Development
274
+
275
+ ```bash
276
+ uv pip install -e ".[dev]"
277
+ uv run pytest
278
+ uv run reflex component build # generates .pyi stubs, then sdist + wheel in dist/
279
+ ```
280
+
281
+ ## License
282
+
283
+ MIT © Ernesto Crespo. nivo is MIT © Raphaël Benitte.
@@ -0,0 +1,247 @@
1
+ # reflex-nivo
2
+
3
+ Every chart of [nivo](https://nivo.rocks) — the React data-visualization library built on d3 — as a
4
+ [Reflex](https://reflex.dev) custom component.
5
+
6
+ - **51 components** from **28 nivo packages**, SVG, Canvas and HTML renderers (nivo `0.99.0`).
7
+ - **snake_case props** generated from nivo's TypeScript declarations, with types and nivo docs links.
8
+ - **Events land in Python**: `on_click`, `on_mouse_enter`, `on_node_click`, `on_arc_click`… receive the
9
+ datum as a JSON-safe `dict` (cycles, DOM nodes and React events are stripped in the browser).
10
+ - **Follows Reflex's light/dark mode** out of the box.
11
+ - Helpers for axes, legends, scales, color configs, patterns/gradients, markers and annotations,
12
+ plus `nivo.tooltip("<b>{id}</b>: {formattedValue}")` templates and a `nivo.js(...)` escape hatch.
13
+
14
+ ```bash
15
+ pip install reflex-nivo
16
+ ```
17
+
18
+ ## Quick start
19
+
20
+ ```python
21
+ import reflex as rx
22
+ import reflex_nivo as nivo
23
+
24
+
25
+ class State(rx.State):
26
+ rows: list[dict] = [
27
+ {"country": "AD", "burger": 120, "fries": 80},
28
+ {"country": "AE", "burger": 95, "fries": 130},
29
+ ]
30
+ selected: str = ""
31
+
32
+ @rx.event
33
+ def on_bar_click(self, datum: dict):
34
+ self.selected = f"{datum['indexValue']} · {datum['id']} = {datum['value']}"
35
+
36
+
37
+ def index():
38
+ return rx.vstack(
39
+ nivo.bar(
40
+ data=State.rows,
41
+ keys=["burger", "fries"],
42
+ index_by="country",
43
+ group_mode="grouped",
44
+ colors=nivo.scheme("nivo"),
45
+ margin=nivo.margin(top=20, right=110, bottom=50, left=60),
46
+ axis_bottom=nivo.axis(legend="country", legend_position="middle", legend_offset=36),
47
+ legends=[nivo.legend(data_from="keys", anchor="bottom-right", translate_x=100)],
48
+ tooltip=nivo.tooltip("<b>{indexValue}</b> · {id}: {formattedValue}"),
49
+ on_click=State.on_bar_click,
50
+ height="400px",
51
+ ),
52
+ rx.text(State.selected),
53
+ )
54
+
55
+
56
+ app = rx.App()
57
+ app.add_page(index)
58
+ ```
59
+
60
+ ## Components
61
+
62
+ Each factory wraps nivo's `Responsive*` export. Classes use the nivo name (`nivo.Bar`, `nivo.HeatMapCanvas`…).
63
+
64
+ | Family | SVG | Canvas | HTML |
65
+ | --- | --- | --- | --- |
66
+ | Bar | `bar` | `bar_canvas` | |
67
+ | Line | `line` | `line_canvas` | |
68
+ | Pie | `pie` | `pie_canvas` | |
69
+ | Radar · RadialBar · PolarBar | `radar` · `radial_bar` · `polar_bar` | | |
70
+ | Waffle | `waffle` | `waffle_canvas` | `waffle_html` |
71
+ | ScatterPlot | `scatterplot` | `scatterplot_canvas` | |
72
+ | SwarmPlot | `swarmplot` | `swarmplot_canvas` | |
73
+ | BoxPlot · Voronoi | `boxplot` · `voronoi` | | |
74
+ | HeatMap | `heatmap` | `heatmap_canvas` | |
75
+ | TreeMap | `treemap` | `treemap_canvas` | `treemap_html` |
76
+ | CirclePacking | `circle_packing` | `circle_packing_canvas` | `circle_packing_html` |
77
+ | Sunburst | `sunburst` | | |
78
+ | Icicle | `icicle` | | `icicle_html` |
79
+ | Tree | `tree` | `tree_canvas` | |
80
+ | Sankey · Funnel · Marimekko | `sankey` · `funnel` · `marimekko` | | |
81
+ | Chord | `chord` | `chord_canvas` | |
82
+ | Network | `network` | `network_canvas` | |
83
+ | ParallelCoordinates | `parallel_coordinates` | `parallel_coordinates_canvas` | |
84
+ | Calendar | `calendar` | `calendar_canvas` | |
85
+ | TimeRange · Stream · Bump · AreaBump · Bullet | `time_range` · `stream` · `bump` · `area_bump` · `bullet` | | |
86
+ | GeoMap | `geo_map` | `geo_map_canvas` | |
87
+ | Choropleth | `choropleth` | `choropleth_canvas` | |
88
+
89
+ The data shape and every prop are documented on nivo.rocks (each class docstring links to its page).
90
+ Convert prop names to snake_case: `indexBy` → `index_by`, `enableGridX` → `enable_grid_x`.
91
+
92
+ ### Renamed props
93
+
94
+ | nivo | reflex-nivo | why |
95
+ | --- | --- | --- |
96
+ | `id` (Pie, Sunburst, CirclePacking, SwarmPlot, Marimekko) | `id_by` | `id` is the HTML id of the container |
97
+ | `from` / `to` (Calendar, TimeRange) | `from_date` / `to_date` | `from` is a Python keyword |
98
+
99
+ Plain `YYYY-MM-DD` values for `from_date`/`to_date` are sent as local midnight, so calendars do not
100
+ shift one day (and one year) back west of Greenwich.
101
+
102
+ ## Sizing
103
+
104
+ nivo's responsive charts fill their parent, so every factory returns the chart inside a `<div>`.
105
+ `width` (default `"100%"`), `height` (default `"400px"`) and any other CSS prop, `class_name`, `id`
106
+ or `style` go to that container. Anything that is neither a nivo prop nor CSS raises a `TypeError`
107
+ with a suggestion, so typos like `enable_gridx` do not fail silently.
108
+
109
+ ```python
110
+ nivo.pie(data=State.pie, height="320px", max_width="600px", margin_x="auto")
111
+ ```
112
+
113
+ (`margin` itself is nivo's chart margin: use `nivo.margin(top=.., right=.., bottom=.., left=..)`.)
114
+
115
+ ## Events
116
+
117
+ All nivo callbacks are exposed as event triggers. The handler receives nivo's first callback argument
118
+ (datum, point, slice, node, cell, serie, link, feature, dimensions, active id…), serialized in the
119
+ browser: functions, DOM nodes and React events are dropped, repeated/cyclic objects are replaced by
120
+ their `id`, and depth is bounded.
121
+
122
+ ```python
123
+ class State(rx.State):
124
+ @rx.event
125
+ def drill_down(self, node: dict):
126
+ if node["data"].get("children"):
127
+ self.sunburst = node["data"] # the raw datum travels with the event
128
+
129
+
130
+ nivo.sunburst(data=State.sunburst, id_by="name", value="loc", on_click=State.drill_down)
131
+ ```
132
+
133
+ Only the callbacks a chart actually declares carry a datum, and they differ per chart: `@nivo/bar`
134
+ exposes `on_click`, `on_mouse_enter` and `on_mouse_leave`, while `@nivo/stream` exposes none of them.
135
+ Any other `on_*` is attached to the wrapping `<div>` as a plain DOM event, so it fires for the whole
136
+ chart area but receives no datum (write the handler without arguments). An `on_*` that is neither
137
+ raises a `TypeError` listing the chart's nivo callbacks.
138
+
139
+ `on_mouse_move` fires very often; on the charts that expose it, prefer click/enter/leave or throttle
140
+ it: `on_mouse_move=State.hover.throttle(100)`.
141
+
142
+ ### Hiding a prop
143
+
144
+ Passing `None` sends JavaScript `null`, which turns the feature off instead of falling back to nivo's
145
+ default — that is how you hide an axis:
146
+
147
+ ```python
148
+ nivo.bar(data=State.rows, axis_bottom=None, axis_left=None) # no axes
149
+ nivo.pie(data=State.pie, theme=None) # nivo's own theme instead of the Reflex one
150
+ ```
151
+
152
+ ## Helpers
153
+
154
+ ```python
155
+ nivo.margin(top=20, right=20, bottom=40, left=50)
156
+ nivo.axis(legend="price", legend_position="middle", legend_offset=-40, tick_rotation=-45, format=" >-$.2f")
157
+ nivo.legend(
158
+ anchor="bottom-right", direction="column", translate_x=100, effects=[nivo.hover_effect(item_opacity=1)]
159
+ )
160
+ nivo.scale("time", format="%Y-%m-%d", precision="day")
161
+ nivo.scheme("category10") # {"scheme": "category10"}
162
+ nivo.inherit("color", ("darker", 1.6)) # inherited color
163
+ nivo.from_theme("background") # color from theme
164
+ nivo.marker("y", 100, legend="target")
165
+ nivo.annotation({"id": "fries"}, type_="circle", note="max", note_x=40, note_y=-30)
166
+ nivo.pattern_dots_def("dots", background="inherit", color="#38bcb2")
167
+ nivo.pattern_lines_def("lines", rotation=-45) / nivo.pattern_squares_def(...) / nivo.linear_gradient_def(...)
168
+ nivo.fill_rule("dots", {"id": "fries"})
169
+ nivo.props(any_snake_case="option") # generic camelCase dict builder
170
+ ```
171
+
172
+ Constants: `nivo.COLOR_SCHEMES` (categorical/diverging/sequential), `nivo.CURVES`, `nivo.MOTION_CONFIGS`.
173
+
174
+ ### Functions: templates, tooltips and raw JS
175
+
176
+ nivo accepts functions for labels, formatters, accessors, tooltips and custom layers.
177
+
178
+ ```python
179
+ # Formatter/label from a {path} template, evaluated in the browser:
180
+ label = nivo.template("{id}: {formattedValue}")
181
+
182
+ # Tooltip component from a template. It may use attribute-less <b>, <strong>, <i>, <em>, <u>,
183
+ # <s>, <small>, <code>, <span>, <div>, <p> and <br>; anything else, and every interpolated
184
+ # value, is rendered as text, so template and values are safe to build from a state Var.
185
+ tooltip = nivo.tooltip("<b>{datum.label}</b><br/>{datum.formattedValue}", style={"min_width": "120px"})
186
+
187
+ # Anything else: a raw JavaScript expression (never build it from user input).
188
+ value_format = nivo.js("v => `${v.toLocaleString()} €`")
189
+ node_size = nivo.js("n => n.size")
190
+ ```
191
+
192
+ Most formatters also accept d3-format / d3-time-format strings directly (`value_format=".2s"`,
193
+ `axis(format="%b %d")`).
194
+
195
+ ## Themes
196
+
197
+ Charts use `nivo.themes.auto()` unless you pass `theme=`: a light/dark pair built on the Radix gray
198
+ scale that switches with `rx.color_mode`. Customize or pin it:
199
+
200
+ ```python
201
+ theme = nivo.themes.auto({"grid": {"line": {"strokeDasharray": "4 4"}}}) # both modes
202
+ theme = nivo.themes.auto(light={"text": {"fill": "#333"}}, dark={"text": {"fill": "#eee"}})
203
+ theme = nivo.themes.DARK # fixed
204
+ theme = State.computed_theme # any nivo theme dict
205
+ theme = {} # nivo's own default
206
+ ```
207
+
208
+ ## Demo
209
+
210
+ `nivo_demo/` is a 10-page app that uses all 51 components with live state controls, an event
211
+ inspector, drill-down/zoom interactions, geo projections, canvas variants with thousands of marks and
212
+ customization examples.
213
+
214
+ ```bash
215
+ uv venv && uv pip install -e . && cd nivo_demo && uv pip install -r requirements.txt
216
+ uv run reflex run # http://localhost:3010 (backend on 8010)
217
+ ```
218
+
219
+ World country shapes in the demo come from the nivo website dataset (Natural Earth).
220
+
221
+ ## Regenerating the wrappers
222
+
223
+ The chart modules in `custom_components/reflex_nivo/charts/` are generated from nivo's typings:
224
+
225
+ ```bash
226
+ # in a scratch directory
227
+ npm i typescript@5 @types/react@19 react@19 react-dom@19 @nivo/{bar,line,pie,...}@0.99.0
228
+ node /path/to/reflex-nivo/scripts/extract_nivo_props.cjs bar boxplot bullet bump calendar chord \
229
+ circle-packing funnel heatmap icicle line marimekko network pie polar-bar radar radial-bar sankey \
230
+ scatterplot stream sunburst swarmplot tree treemap voronoi waffle > /path/to/reflex-nivo/scripts/nivo_props.json
231
+ # back in the repo (geo and parallel-coordinates ship no usable typings: see scripts/nivo_manual_props.json)
232
+ python scripts/generate_components.py
233
+ ```
234
+
235
+ Bump `NIVO_VERSION` in `custom_components/reflex_nivo/constants.py` together with the extraction.
236
+
237
+ ## Development
238
+
239
+ ```bash
240
+ uv pip install -e ".[dev]"
241
+ uv run pytest
242
+ uv run reflex component build # generates .pyi stubs, then sdist + wheel in dist/
243
+ ```
244
+
245
+ ## License
246
+
247
+ MIT © Ernesto Crespo. nivo is MIT © Raphaël Benitte.
@@ -0,0 +1,41 @@
1
+ """reflex-nivo: every nivo (https://nivo.rocks) chart as a Reflex component.
2
+
3
+ Usage::
4
+
5
+ import reflex_nivo as nivo
6
+
7
+ nivo.bar(data=State.rows, keys=["a", "b"], index_by="country", height="420px")
8
+ """
9
+
10
+ from . import themes
11
+ from .base import NivoComponent, nivo_event_spec
12
+ from .charts import * # noqa: F403
13
+ from .charts import __all__ as _charts_all
14
+ from .constants import (
15
+ CATEGORICAL_COLOR_SCHEMES,
16
+ COLOR_SCHEMES,
17
+ CURVES,
18
+ DIVERGING_COLOR_SCHEMES,
19
+ MOTION_CONFIGS,
20
+ NIVO_VERSION,
21
+ SEQUENTIAL_COLOR_SCHEMES,
22
+ )
23
+ from .helpers import * # noqa: F403
24
+ from .helpers import __all__ as _helpers_all
25
+
26
+ __version__ = "0.1.0"
27
+
28
+ __all__ = [
29
+ "CATEGORICAL_COLOR_SCHEMES",
30
+ "COLOR_SCHEMES",
31
+ "CURVES",
32
+ "DIVERGING_COLOR_SCHEMES",
33
+ "MOTION_CONFIGS",
34
+ "NIVO_VERSION",
35
+ "SEQUENTIAL_COLOR_SCHEMES",
36
+ "NivoComponent",
37
+ "nivo_event_spec",
38
+ "themes",
39
+ *_charts_all,
40
+ *_helpers_all,
41
+ ]