Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/api-reference/var_system.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,37 @@ def factorial(value: NumberVar):

Use `js_expression` to pass explicit JavaScript expressions; in the `multiply_array_values` example, we pass in a JavaScript expression that calculates the product of all elements in an array called `a` by using the reduce method to multiply each element with the accumulated result, starting from an initial value of 1.
Later, we leverage `rx.cond` in the' factorial' function, we instantiate an array using the `range` function, and pass this array to `multiply_array_values`.

## Hook Vars

Some values only exist on the frontend and are exposed through React hooks.
`rx.vars.use_hook_var()` binds the return value of a no-argument hook to a unique variable name and returns it as a `Var`.
The hook call and the import of the hook are automatically included in any component that uses the var, so the value reflects the context of the component it is rendered in.

```py
chart_width = rx.vars.use_hook_var(
library="recharts@3.8.1",
hook="useChartWidth",
_var_type=int | None,
)
```

A component using `chart_width` will import `useChartWidth` from `recharts` and render `const <unique_name> = useChartWidth();` in its body, so `chart_width` can be used like any other `Var[int | None]`.

For the common case of React's built-in [`useId`](https://react.dev/reference/react/useId), `rx.vars.use_id()` returns a `Var[str]` containing a stable unique id for the rendered component.
This is useful for linking SVG elements to `defs` such as gradients or filters:

```py
def gradient_rect() -> rx.Component:
gradient_id = rx.vars.use_id()
return rx.el.svg(
rx.el.svg.linear_gradient(
rx.el.svg.stop(offset="0%", stop_color="gold"),
rx.el.svg.stop(offset="100%", stop_color="tomato"),
id=gradient_id,
),
rx.el.svg.rect(fill=f"url(#{gradient_id})", width=64, height=64),
width=64,
height=64,
)
```
221 changes: 221 additions & 0 deletions docs/library/graphing/charts/sankeychart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
---
components:
- rx.recharts.SankeyChart
title: Sankey Chart
meta_description: "Create Sankey charts in Python with Reflex. Build interactive Recharts Sankey diagrams to visualize weighted flows between stages, categories, or systems."
---

# Sankey Chart

```python exec
import random
from typing import Any

import reflex as rx
```

Sankey charts in Reflex are built on [Recharts](https://recharts.org/), a React charting library, and created in pure Python. A Sankey chart visualizes weighted flows between nodes, making it useful for showing movement through stages, resource allocation, user journeys, and other source-to-target relationships.

## Simple Example

An `rx.recharts.sankey_chart()` takes a `data` dictionary with `nodes` and `links`. Links refer to nodes by zero-based index.

```python demo graphing
sankey_data = {
"nodes": [
{"name": "Website"},
{"name": "Landing Page"},
{"name": "Product Page"},
{"name": "Checkout"},
{"name": "Purchase"},
],
"links": [
{"source": 0, "target": 1, "value": 1200},
{"source": 1, "target": 2, "value": 900},
{"source": 2, "target": 3, "value": 420},
{"source": 3, "target": 4, "value": 260},
],
}


def sankey_simple():
return rx.recharts.sankey_chart(
rx.recharts.graphing_tooltip(),
data=sankey_data,
node_padding=24,
node_width=12,
link_curvature=0.55,
width="100%",
height=320,
)
```

## Stateful Example

Chart data can be tied to a State var. This example randomizes the flow values when the button is clicked.

```python demo exec
class SankeyState(rx.State):
Comment thread
harsh21234i marked this conversation as resolved.
data: dict[str, Any] = {
"nodes": [
{"name": "Marketing"},
{"name": "Trial"},
{"name": "Sales"},
{"name": "Support"},
{"name": "Retained"},
],
"links": [
{"source": 0, "target": 1, "value": 600},
{"source": 1, "target": 2, "value": 320},
{"source": 2, "target": 4, "value": 210},
{"source": 1, "target": 3, "value": 180},
{"source": 3, "target": 4, "value": 130},
],
}

@rx.event
def randomize_flows(self):
for link in self.data["links"]:
link["value"] = random.randint(80, 700)


def sankey_stateful():
return rx.vstack(
rx.recharts.sankey_chart(
rx.recharts.graphing_tooltip(),
data=SankeyState.data,
node={
"fill": rx.color("accent", 7),
"stroke": rx.color("accent", 10),
"strokeWidth": 2,
},
link={
"stroke": rx.color("gray", 7),
"strokeOpacity": 0.35,
},
node_padding=18,
node_width=14,
width="100%",
height=320,
),
rx.button("Randomize flows", on_click=SankeyState.randomize_flows),
width="100%",
)
```


## Full Node / Link Customization

For complete control over node and link rendering, pass a
`@rx.recharts.sankey_chart.node` or `@rx.recharts.sankey_chart.link` decorated
function that returns an svg-based component. The function receives a
`Var[SankeyNodeProps]` or `Var[SankeyLinkProps]` object with the node or link
data `payload`, as well as the object's position and dimensions. You can use these
properties to construct a custom node or link.

Because the component renders inside the SVG element of the chart, you can only
use `rx.el.svg` components to construct the custom node or link.

The example below also uses `rx.recharts.use_chart_width()` to read the
rendered chart width and `rx.vars.use_id()` to generate a unique id that links
each link's gradient definition to the path that references it.

```python demo graphing
styled_sankey_data = {
"nodes": [
{"name": "Sources", "type": "source", "fill": rx.color("blue", 8)},
{"name": "Direct", "type": "channel", "fill": rx.color("green", 8)},
{"name": "Search", "type": "channel", "fill": rx.color("grass", 8)},
{"name": "Paid", "type": "channel", "fill": rx.color("amber", 8)},
{"name": "Revenue", "type": "outcome", "fill": rx.color("purple", 8)},
],
"links": [
{"source": 0, "target": 1, "value": 350},
{"source": 0, "target": 2, "value": 500},
{"source": 0, "target": 3, "value": 220},
{"source": 1, "target": 4, "value": 190},
{"source": 2, "target": 4, "value": 260},
{"source": 3, "target": 4, "value": 150},
],
}


def sankey_custom_render():
@rx.recharts.sankey_chart.node
def custom_node(
node: rx.Var[rx.recharts.sankey_chart.SankeyNodeProps],
) -> rx.Component:
# Determine if the node is at the right edge of the chart to adjust the label position accordingly.
is_out = node.x + node.width + 6 > rx.recharts.use_chart_width()
return rx.fragment(
rx.el.svg.text(

@cubic-dev-ai cubic-dev-ai Bot Aug 1, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Link value labels are overpainted by the wide Sankey path, reducing or eliminating their readability for high-volume links. Rendering the path before rx.el.svg.text keeps the label on top.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/library/graphing/charts/sankeychart.md, line 151:

<comment>Link value labels are overpainted by the wide Sankey path, reducing or eliminating their readability for high-volume links. Rendering the path before `rx.el.svg.text` keeps the label on top.</comment>

<file context>
@@ -0,0 +1,221 @@
+        # Determine if the node is at the right edge of the chart to adjust the label position accordingly.
+        is_out = node.x + node.width + 6 > rx.recharts.use_chart_width()
+        return rx.fragment(
+            rx.el.svg.text(
+                node.payload.name,
+                x=rx.cond(is_out, node.x - 6, node.x + node.width + 6),
</file context>
Fix with cubic

node.payload.name,
x=rx.cond(is_out, node.x - 6, node.x + node.width + 6),
y=node.y + node.height / 2,
text_anchor=rx.cond(is_out, "end", "start"),
fill=rx.color("gray", 12),
font_size=10,
),
rx.el.svg.rect(
x=node.x,
y=node.y,
width=node.width,
height=node.height,
# Accessing custom keys in the payload needs a `dict` cast.
fill=node.payload.to(dict)["fill"],
stroke=rx.color("gray", 12),
Comment thread
harsh21234i marked this conversation as resolved.
stroke_width=1,
),
)

@rx.recharts.sankey_chart.link
def custom_link(
link: rx.Var[rx.recharts.sankey_chart.SankeyLinkProps],
) -> rx.Component:
link_id = rx.vars.use_id()
source = link.payload.source.to(dict)
target = link.payload.target.to(dict)
return rx.fragment(
rx.el.svg.linear_gradient(
rx.el.svg.stop(offset="0%", stop_color=source["fill"]),
rx.el.svg.stop(offset="100%", stop_color=target["fill"]),
id=link_id,
),
rx.el.svg.text(
link.payload.value,
x=(link.sourceX + link.targetX) / 2,
y=(link.sourceY + link.targetY) / 2,
text_anchor="middle",
fill=rx.color("gray", 12),
font_size=10,
),
rx.el.svg.path(
d=(
f"M{link.sourceX},{link.sourceY} "
f"C{link.sourceControlX},{link.sourceY} "
f"{link.targetControlX},{link.targetY} "
f"{link.targetX},{link.targetY}"
),
fill="none",
stroke=f"url(#{link_id})",
stroke_opacity=0.35,
stroke_width=link.linkWidth,
),
)

return rx.recharts.sankey_chart(
data=styled_sankey_data,
node=custom_node,
link=custom_link,
width="100%",
height=340,
)
```

## Related Charts

Explore more chart types you can build with Reflex and Recharts in pure Python:

- [Treemap](/docs/library/graphing/charts/treemap)
- [Funnel Chart](/docs/library/graphing/charts/funnelchart)
- [Pie Chart](/docs/library/graphing/charts/piechart)
17 changes: 17 additions & 0 deletions docs/wrapping-react/custom-code-and-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,20 @@ export function Div_7178f430b7b371af8a12d8265d65ab9b() {
```md alert info
# You can mix custom code and hooks in the same component. Hooks can access a variable defined in the custom code, but custom code cannot access a variable defined in a hook.
```

## Using a Hook's Return Value

`add_hooks` inserts hook statements into the component, but the values they define are not directly accessible from Python. When you need the return value of a no-argument hook, use `rx.vars.use_hook_var()`, which binds the hook call to a unique variable name and returns it as a `Var`. The hook statement and its import are automatically included in any component where the var is used, so it composes with regular props and var operations.

```python
import reflex as rx


def use_chart_width() -> rx.Var[int | None]:
"""Get the width of the enclosing recharts chart as a var."""
return rx.vars.use_hook_var(
library="recharts@3.8.1", hook="useChartWidth", _var_type=int | None
)
```

For React's built-in [`useId`](https://react.dev/reference/react/useId), `rx.vars.use_id()` returns a `Var[str]` with a stable unique id for the component being rendered, e.g. for linking SVG elements to gradient or filter definitions.
1 change: 1 addition & 0 deletions packages/reflex-base/news/6708.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `rx.vars.use_hook_var()` to create a `Var` bound to the value of a no-argument React hook imported from a given library, and `rx.vars.use_id()` to get React's stable `useId` value for the rendered component.
3 changes: 3 additions & 0 deletions packages/reflex-base/src/reflex_base/vars/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
LiteralStringVar,
StringVar,
)
from .special import use_hook_var, use_id

__all__ = [
"EMPTY_VAR_INT",
Expand Down Expand Up @@ -66,6 +67,8 @@
"number",
"object",
"sequence",
"use_hook_var",
"use_id",
"var_operation",
"var_operation_return",
]
60 changes: 60 additions & 0 deletions packages/reflex-base/src/reflex_base/vars/special.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Special Vars for rendering values from the environment."""

from types import UnionType
from typing import Any, TypeVar, cast, overload

from typing_extensions import TypeForm

from reflex_base.utils.imports import ImportVar
from reflex_base.utils.types import GenericType
from reflex_base.vars.base import Var, VarData, get_unique_variable_name

HOOK_VAR_TYPE = TypeVar("HOOK_VAR_TYPE")


@overload
def use_hook_var(library: str, hook: str) -> Var[Any]: ...


@overload
def use_hook_var(
library: str, hook: str, _var_type: TypeForm[HOOK_VAR_TYPE]
) -> Var[HOOK_VAR_TYPE]: ...


@overload
def use_hook_var(library: str, hook: str, _var_type: UnionType) -> Var: ...

@cubic-dev-ai cubic-dev-ai Bot Aug 1, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: PEP 604 calls such as _var_type=int | None are exposed as Var[Unknown] because this overload returns a raw generic. If the union members cannot currently be inferred by Pyright, declaring Var[Any] explicitly would at least prevent an unknown type from leaking through this public API.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-base/src/reflex_base/vars/special.py, line 26:

<comment>PEP 604 calls such as `_var_type=int | None` are exposed as `Var[Unknown]` because this overload returns a raw generic. If the union members cannot currently be inferred by Pyright, declaring `Var[Any]` explicitly would at least prevent an unknown type from leaking through this public API.</comment>

<file context>
@@ -0,0 +1,60 @@
+
+
+@overload
+def use_hook_var(library: str, hook: str, _var_type: UnionType) -> Var: ...
+
+
</file context>
Fix with cubic



def use_hook_var(library: str, hook: str, _var_type: Any = Any) -> Var:
"""Get a Var representing a React hook's value.

The value will depend on the context of the component in which it is used.

Args:
library: The library to import the hook from.
hook: The name of the hook.
_var_type: The type of the Var.

Returns:
A Var representing the React hook.
"""
var_name = get_unique_variable_name()
hook_alias = f"{hook}_{var_name}"
return Var(
var_name,
_var_type=cast(GenericType, _var_type),
_var_data=VarData(
imports={library: ImportVar(tag=hook, alias=hook_alias)},
hooks=(f"const {var_name} = {hook_alias}();",),
),
).guess_type()


def use_id() -> Var[str]:
"""Get the stable React useId hook value for a component.

Returns:
A Var representing the useId hook value.
"""
return use_hook_var(library="react", hook="useId", _var_type=str)
1 change: 1 addition & 0 deletions packages/reflex-components-recharts/news/6708.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added a Recharts Sankey chart wrapper (`rx.recharts.sankey_chart`) with support for custom node and link renderers, and `rx.recharts.use_chart_width()` for reading the rendered chart width as a `Var`.
13 changes: 11 additions & 2 deletions packages/reflex-components-recharts/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ readme = "README.md"
authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }]
maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }]
requires-python = ">=3.10"
dependencies = ["reflex-base >= 0.9.0"]
dependencies = [
"reflex-base >= 0.9.7",
"reflex-components-core >= 0.9.0",
]

[tool.hatch.version]
source = "uv-dynamic-versioning"
Expand All @@ -21,7 +24,13 @@ targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]

[tool.hatch.build.hooks.reflex-pyi]
dependencies = ["ruff", "reflex-base"]
dependencies = [
"ruff",
"reflex-base",
"reflex-components-core",
"reflex-components-lucide",
"reflex-components-sonner",
]

[build-system]
requires = ["hatchling", "uv-dynamic-versioning", "hatch-reflex-pyi"]
Expand Down
Loading
Loading