-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Wrap Recharts Sankey chart #6708
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| --- | ||
| 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 | ||
|
|
||
| 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): | ||
| data = { | ||
| "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_padding=18, | ||
| node_width=14, | ||
| width="100%", | ||
| height=320, | ||
| ), | ||
| rx.button("Randomize flows", on_click=SankeyState.randomize_flows), | ||
| width="100%", | ||
| ) | ||
| ``` | ||
|
|
||
| ## Custom Node Types And Styles | ||
|
|
||
| Use fields on each node to describe node types and per-node styling. Pass `node` or `link` dictionaries to control shared Sankey styling. | ||
|
|
||
| ```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_styles(): | ||
| return rx.recharts.sankey_chart( | ||
| rx.recharts.graphing_tooltip(), | ||
| data=styled_sankey_data, | ||
| node={ | ||
| "stroke": rx.color("gray", 12), | ||
| "strokeWidth": 1, | ||
| }, | ||
| link={ | ||
| "stroke": rx.color("gray", 8), | ||
| "strokeOpacity": 0.35, | ||
| }, | ||
| node_padding=22, | ||
| node_width=16, | ||
| link_curvature=0.45, | ||
| 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) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,13 +3,14 @@ | |
| from __future__ import annotations | ||
|
|
||
| from collections.abc import Sequence | ||
| from typing import Any, ClassVar | ||
| from typing import Any, ClassVar, TypedDict | ||
|
|
||
| from reflex_base.components.component import Component, field | ||
| from reflex_base.constants import EventTriggers | ||
| from reflex_base.constants.colors import Color | ||
| from reflex_base.event import EventHandler, no_args_event_spec | ||
| from reflex_base.vars.base import Var | ||
| from typing_extensions import NotRequired | ||
|
|
||
| from reflex_components_recharts.general import ResponsiveContainer | ||
|
|
||
|
|
@@ -516,6 +517,88 @@ class FunnelChart(ChartBase): | |
| ] | ||
|
|
||
|
|
||
| class SankeyNode(TypedDict): | ||
| """A node in a Sankey chart.""" | ||
|
|
||
| name: str | ||
| type: NotRequired[str] | ||
| fill: NotRequired[str | Color] | ||
| stroke: NotRequired[str | Color] | ||
| strokeWidth: NotRequired[int | float] | ||
| strokeOpacity: NotRequired[int | float] | ||
|
|
||
|
|
||
| class SankeyLink(TypedDict): | ||
| """A weighted link between two Sankey chart nodes.""" | ||
|
|
||
| source: int | ||
| target: int | ||
| value: int | float | ||
| fill: NotRequired[str | Color] | ||
| fillOpacity: NotRequired[int | float] | ||
| stroke: NotRequired[str | Color] | ||
| strokeWidth: NotRequired[int | float] | ||
| strokeOpacity: NotRequired[int | float] | ||
|
|
||
|
|
||
| class SankeyData(TypedDict): | ||
| """The source data for a Sankey chart.""" | ||
|
|
||
| nodes: Sequence[SankeyNode] | ||
| links: Sequence[SankeyLink] | ||
|
|
||
|
|
||
| class SankeyChart(ChartBase): | ||
| """A Sankey chart component in Recharts.""" | ||
|
|
||
| tag = "Sankey" | ||
|
|
||
| alias = "RechartsSankeyChart" | ||
|
|
||
| name_key: Var[str] = field(doc='The key of each node name. Default: "name"') | ||
|
|
||
| data_key: Var[str | int] = field(doc='The key of each link value. Default: "value"') | ||
|
|
||
| data: Var[SankeyData] = field( | ||
| doc="The source data, including nodes and the weighted links between them." | ||
| ) | ||
|
Comment on lines
+562
to
+564
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
With Useful? React with 👍 / 👎. |
||
|
|
||
| margin: Var[dict[str, Any]] = field( | ||
| doc='The sizes of whitespace around the chart. Default: {"top": 5, "right": 5, "bottom": 5, "left": 5}' | ||
| ) | ||
|
|
||
| node: Var[Any] = field( | ||
| doc="The configuration object or custom renderer used to draw nodes." | ||
| ) | ||
|
|
||
| link: Var[Any] = field( | ||
| doc="The configuration object or custom renderer used to draw links." | ||
| ) | ||
|
|
||
| sort: Var[bool] = field( | ||
| doc="Whether to sort nodes on the y-axis or display them in data order." | ||
| ) | ||
|
|
||
| node_padding: Var[int] = field(doc="The padding between nodes.") | ||
|
|
||
| node_width: Var[int] = field(doc="The width of each node.") | ||
|
|
||
| link_width: Var[int] = field(doc="The width of each link.") | ||
|
|
||
| link_curvature: Var[float] = field(doc="The curvature of each link.") | ||
|
|
||
| iterations: Var[int] = field( | ||
| doc="The number of layout iterations used to position nodes and links." | ||
| ) | ||
|
|
||
| # Valid children components | ||
| _valid_children: ClassVar[list[str]] = [ | ||
| "Legend", | ||
| "GraphingTooltip", | ||
| "Defs", | ||
| ] | ||
|
|
||
|
|
||
| class Treemap(RechartsCharts): | ||
| """A Treemap chart component in Recharts.""" | ||
|
|
||
|
|
@@ -598,4 +681,5 @@ def create(cls, *children, **props) -> Component: | |
| radial_bar_chart = RadialBarChart.create | ||
| scatter_chart = ScatterChart.create | ||
| funnel_chart = FunnelChart.create | ||
| sankey_chart = SankeyChart.create | ||
| treemap = Treemap.create | ||
Uh oh!
There was an error while loading. Please reload this page.