charts.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. """A module that defines the chart components in Recharts."""
  2. from __future__ import annotations
  3. from typing import Any, Dict, List, Union
  4. from reflex.components.component import Component
  5. from reflex.components.recharts.general import ResponsiveContainer
  6. from reflex.constants import EventTriggers
  7. from reflex.event import EventHandler
  8. from reflex.vars import Var
  9. from .recharts import (
  10. LiteralAnimationEasing,
  11. LiteralComposedChartBaseValue,
  12. LiteralLayout,
  13. LiteralStackOffset,
  14. LiteralSyncMethod,
  15. RechartsCharts,
  16. )
  17. class ChartBase(RechartsCharts):
  18. """A component that wraps a Recharts charts."""
  19. # The width of chart container. String or Integer
  20. width: Var[Union[str, int]] = "100%" # type: ignore
  21. # The height of chart container.
  22. height: Var[Union[str, int]] = "100%" # type: ignore
  23. # The customized event handler of click on the component in this chart
  24. on_click: EventHandler[lambda: []]
  25. # The customized event handler of mouseenter on the component in this chart
  26. on_mouse_enter: EventHandler[lambda: []]
  27. # The customized event handler of mousemove on the component in this chart
  28. on_mouse_move: EventHandler[lambda: []]
  29. # The customized event handler of mouseleave on the component in this chart
  30. on_mouse_leave: EventHandler[lambda: []]
  31. @staticmethod
  32. def _ensure_valid_dimension(name: str, value: Any) -> None:
  33. """Ensure that the value is an int type or str percentage.
  34. Unfortunately str Vars cannot be checked and are implicitly not allowed.
  35. Args:
  36. name: The name of the prop.
  37. value: The value to check.
  38. Raises:
  39. ValueError: If the value is not an int type or str percentage.
  40. """
  41. if value is None:
  42. return
  43. if isinstance(value, int):
  44. return
  45. if isinstance(value, str) and value.endswith("%"):
  46. return
  47. if isinstance(value, Var) and issubclass(value._var_type, int):
  48. return
  49. raise ValueError(
  50. f"Chart {name} must be specified as int pixels or percentage, not {value!r}. "
  51. "CSS unit dimensions are allowed on parent container."
  52. )
  53. @classmethod
  54. def create(cls, *children, **props) -> Component:
  55. """Create a chart component.
  56. Args:
  57. *children: The children of the chart component.
  58. **props: The properties of the chart component.
  59. Returns:
  60. The chart component wrapped in a responsive container.
  61. """
  62. width = props.pop("width", None)
  63. height = props.pop("height", None)
  64. cls._ensure_valid_dimension("width", width)
  65. cls._ensure_valid_dimension("height", height)
  66. dim_props = dict(
  67. width=width or "100%",
  68. height=height or "100%",
  69. )
  70. # Provide min dimensions so the graph always appears, even if the outer container is zero-size.
  71. if width is None:
  72. dim_props["min_width"] = 200
  73. if height is None:
  74. dim_props["min_height"] = 100
  75. return ResponsiveContainer.create(
  76. super().create(*children, **props),
  77. **dim_props, # type: ignore
  78. )
  79. class CategoricalChartBase(ChartBase):
  80. """A component that wraps a Categorical Recharts charts."""
  81. # The source data, in which each element is an object.
  82. data: Var[List[Dict[str, Any]]]
  83. # The sizes of whitespace around the chart.
  84. margin: Var[Dict[str, Any]]
  85. # If any two categorical charts(rx.line_chart, rx.area_chart, rx.bar_chart, rx.composed_chart) have the same sync_id, these two charts can sync the position GraphingTooltip, and the start_index, end_index of Brush.
  86. sync_id: Var[str]
  87. # When sync_id is provided, allows customisation of how the charts will synchronize GraphingTooltips and brushes. Using 'index' (default setting), other charts will reuse current datum's index within the data array. In cases where data does not have the same length, this might yield unexpected results. In that case use 'value' which will try to match other charts values, or a fully custom function which will receive tick, data as argument and should return an index. 'index' | 'value' | function
  88. sync_method: Var[LiteralSyncMethod]
  89. # The layout of area in the chart. 'horizontal' | 'vertical'
  90. layout: Var[LiteralLayout]
  91. # The type of offset function used to generate the lower and upper values in the series array. The four types are built-in offsets in d3-shape. 'expand' | 'none' | 'wiggle' | 'silhouette'
  92. stack_offset: Var[LiteralStackOffset]
  93. class AreaChart(CategoricalChartBase):
  94. """An Area chart component in Recharts."""
  95. tag = "AreaChart"
  96. alias = "RechartsAreaChart"
  97. # The base value of area. Number | 'dataMin' | 'dataMax' | 'auto'
  98. base_value: Var[Union[int, LiteralComposedChartBaseValue]]
  99. # Valid children components
  100. _valid_children: List[str] = [
  101. "XAxis",
  102. "YAxis",
  103. "ReferenceArea",
  104. "ReferenceDot",
  105. "ReferenceLine",
  106. "Brush",
  107. "CartesianGrid",
  108. "Legend",
  109. "GraphingTooltip",
  110. "Area",
  111. "Defs",
  112. ]
  113. class BarChart(CategoricalChartBase):
  114. """A Bar chart component in Recharts."""
  115. tag = "BarChart"
  116. alias = "RechartsBarChart"
  117. # The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number
  118. bar_category_gap: Var[Union[str, int]] = Var.create_safe("10%", _var_is_string=True) # type: ignore
  119. # The gap between two bars in the same category, which can be a percent value or a fixed value. Percentage | Number
  120. bar_gap: Var[Union[str, int]] = Var.create_safe(4) # type: ignore
  121. # The width of all the bars in the chart. Number
  122. bar_size: Var[int]
  123. # The maximum width of all the bars in a horizontal BarChart, or maximum height in a vertical BarChart.
  124. max_bar_size: Var[int]
  125. # The type of offset function used to generate the lower and upper values in the series array. The four types are built-in offsets in d3-shape.
  126. stack_offset: Var[LiteralStackOffset]
  127. # If false set, stacked items will be rendered left to right. If true set, stacked items will be rendered right to left. (Render direction affects SVG layering, not x position.)
  128. reverse_stack_order: Var[bool]
  129. # Valid children components
  130. _valid_children: List[str] = [
  131. "XAxis",
  132. "YAxis",
  133. "ReferenceArea",
  134. "ReferenceDot",
  135. "ReferenceLine",
  136. "Brush",
  137. "CartesianGrid",
  138. "Legend",
  139. "GraphingTooltip",
  140. "Bar",
  141. ]
  142. class LineChart(CategoricalChartBase):
  143. """A Line chart component in Recharts."""
  144. tag = "LineChart"
  145. alias = "RechartsLineChart"
  146. # Valid children components
  147. _valid_children: List[str] = [
  148. "XAxis",
  149. "YAxis",
  150. "ReferenceArea",
  151. "ReferenceDot",
  152. "ReferenceLine",
  153. "Brush",
  154. "CartesianGrid",
  155. "Legend",
  156. "GraphingTooltip",
  157. "Line",
  158. ]
  159. class ComposedChart(CategoricalChartBase):
  160. """A Composed chart component in Recharts."""
  161. tag = "ComposedChart"
  162. alias = "RechartsComposedChart"
  163. # The base value of area. Number | 'dataMin' | 'dataMax' | 'auto'
  164. base_value: Var[Union[int, LiteralComposedChartBaseValue]]
  165. # The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number
  166. bar_category_gap: Var[Union[str, int]] # type: ignore
  167. # The gap between two bars in the same category, which can be a percent value or a fixed value. Percentage | Number
  168. bar_gap: Var[Union[str, int]] # type: ignore
  169. # The width of all the bars in the chart. Number
  170. bar_size: Var[int]
  171. # If false set, stacked items will be rendered left to right. If true set, stacked items will be rendered right to left. (Render direction affects SVG layering, not x position.)
  172. reverse_stack_order: Var[bool]
  173. # Valid children components
  174. _valid_children: List[str] = [
  175. "XAxis",
  176. "YAxis",
  177. "ReferenceArea",
  178. "ReferenceDot",
  179. "ReferenceLine",
  180. "Brush",
  181. "CartesianGrid",
  182. "Legend",
  183. "GraphingTooltip",
  184. "Area",
  185. "Line",
  186. "Bar",
  187. ]
  188. class PieChart(ChartBase):
  189. """A Pie chart component in Recharts."""
  190. tag = "PieChart"
  191. alias = "RechartsPieChart"
  192. # The sizes of whitespace around the chart.
  193. margin: Var[Dict[str, Any]]
  194. # Valid children components
  195. _valid_children: List[str] = [
  196. "PolarAngleAxis",
  197. "PolarRadiusAxis",
  198. "PolarGrid",
  199. "Legend",
  200. "GraphingTooltip",
  201. "Pie",
  202. ]
  203. # The customized event handler of mousedown on the sectors in this group
  204. on_mouse_down: EventHandler[lambda: []]
  205. # The customized event handler of mouseup on the sectors in this group
  206. on_mouse_up: EventHandler[lambda: []]
  207. # The customized event handler of mouseover on the sectors in this group
  208. on_mouse_over: EventHandler[lambda: []]
  209. # The customized event handler of mouseout on the sectors in this group
  210. on_mouse_out: EventHandler[lambda: []]
  211. class RadarChart(ChartBase):
  212. """A Radar chart component in Recharts."""
  213. tag = "RadarChart"
  214. alias = "RechartsRadarChart"
  215. # The source data, in which each element is an object.
  216. data: Var[List[Dict[str, Any]]]
  217. # The sizes of whitespace around the chart.
  218. margin: Var[Dict[str, Any]]
  219. # The The x-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of width. Number | Percentage
  220. cx: Var[Union[int, str]]
  221. # The The y-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of height. Number | Percentage
  222. cy: Var[Union[int, str]]
  223. # The angle of first radial direction line.
  224. start_angle: Var[int]
  225. # The angle of last point in the circle which should be startAngle - 360 or startAngle + 360. We'll calculate the direction of chart by 'startAngle' and 'endAngle'.
  226. end_angle: Var[int]
  227. # The inner radius of first circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage
  228. inner_radius: Var[Union[int, str]]
  229. # The outer radius of last circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage
  230. outer_radius: Var[Union[int, str]]
  231. # Valid children components
  232. _valid_children: List[str] = [
  233. "PolarAngleAxis",
  234. "PolarRadiusAxis",
  235. "PolarGrid",
  236. "Legend",
  237. "GraphingTooltip",
  238. "Radar",
  239. ]
  240. def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
  241. """Get the event triggers that pass the component's value to the handler.
  242. Returns:
  243. A dict mapping the event trigger to the var that is passed to the handler.
  244. """
  245. return {
  246. EventTriggers.ON_CLICK: lambda: [],
  247. EventTriggers.ON_MOUSE_ENTER: lambda: [],
  248. EventTriggers.ON_MOUSE_LEAVE: lambda: [],
  249. }
  250. class RadialBarChart(ChartBase):
  251. """A RadialBar chart component in Recharts."""
  252. tag = "RadialBarChart"
  253. alias = "RechartsRadialBarChart"
  254. # The source data which each element is an object.
  255. data: Var[List[Dict[str, Any]]]
  256. # The sizes of whitespace around the chart.
  257. margin: Var[Dict[str, Any]]
  258. # The The x-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of width. Number | Percentage
  259. cx: Var[Union[int, str]]
  260. # The The y-coordinate of center. If set a percentage, the final value is obtained by multiplying the percentage of height. Number | Percentage
  261. cy: Var[Union[int, str]]
  262. # The angle of first radial direction line.
  263. start_angle: Var[int]
  264. # The angle of last point in the circle which should be startAngle - 360 or startAngle + 360. We'll calculate the direction of chart by 'startAngle' and 'endAngle'.
  265. end_angle: Var[int]
  266. # The inner radius of first circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage
  267. inner_radius: Var[Union[int, str]]
  268. # The outer radius of last circle grid. If set a percentage, the final value is obtained by multiplying the percentage of maxRadius which is calculated by the width, height, cx, cy. Number | Percentage
  269. outer_radius: Var[Union[int, str]]
  270. # The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number
  271. bar_category_gap: Var[Union[int, str]]
  272. # The gap between two bars in the same category, which can be a percent value or a fixed value. Percentage | Number
  273. bar_gap: Var[str]
  274. # The size of each bar. If the barSize is not specified, the size of bar will be calculated by the barCategoryGap, barGap and the quantity of bar groups.
  275. bar_size: Var[int]
  276. # Valid children components
  277. _valid_children: List[str] = [
  278. "PolarAngleAxis",
  279. "PolarRadiusAxis",
  280. "PolarGrid",
  281. "Legend",
  282. "GraphingTooltip",
  283. "RadialBar",
  284. ]
  285. class ScatterChart(ChartBase):
  286. """A Scatter chart component in Recharts."""
  287. tag = "ScatterChart"
  288. alias = "RechartsScatterChart"
  289. # The sizes of whitespace around the chart.
  290. margin: Var[Dict[str, Any]]
  291. # Valid children components
  292. _valid_children: List[str] = [
  293. "XAxis",
  294. "YAxis",
  295. "ZAxis",
  296. "ReferenceArea",
  297. "ReferenceDot",
  298. "ReferenceLine",
  299. "Brush",
  300. "CartesianGrid",
  301. "Legend",
  302. "GraphingTooltip",
  303. "Scatter",
  304. ]
  305. def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
  306. """Get the event triggers that pass the component's value to the handler.
  307. Returns:
  308. A dict mapping the event trigger to the var that is passed to the handler.
  309. """
  310. return {
  311. EventTriggers.ON_CLICK: lambda: [],
  312. EventTriggers.ON_MOUSE_DOWN: lambda: [],
  313. EventTriggers.ON_MOUSE_UP: lambda: [],
  314. EventTriggers.ON_MOUSE_MOVE: lambda: [],
  315. EventTriggers.ON_MOUSE_OVER: lambda: [],
  316. EventTriggers.ON_MOUSE_OUT: lambda: [],
  317. EventTriggers.ON_MOUSE_ENTER: lambda: [],
  318. EventTriggers.ON_MOUSE_LEAVE: lambda: [],
  319. }
  320. class FunnelChart(ChartBase):
  321. """A Funnel chart component in Recharts."""
  322. tag = "FunnelChart"
  323. alias = "RechartsFunnelChart"
  324. # The layout of bars in the chart. centeric
  325. layout: Var[str]
  326. # The sizes of whitespace around the chart.
  327. margin: Var[Dict[str, Any]]
  328. # Valid children components
  329. _valid_children: List[str] = ["Legend", "GraphingTooltip", "Funnel"]
  330. class Treemap(RechartsCharts):
  331. """A Treemap chart component in Recharts."""
  332. tag = "Treemap"
  333. alias = "RechartsTreemap"
  334. # The width of chart container. String or Integer
  335. width: Var[Union[str, int]] = "100%" # type: ignore
  336. # The height of chart container.
  337. height: Var[Union[str, int]] = "100%" # type: ignore
  338. # data of treemap. Array
  339. data: Var[List[Dict[str, Any]]]
  340. # The key of a group of data which should be unique in a treemap. String | Number | Function
  341. data_key: Var[Union[str, int]]
  342. # The treemap will try to keep every single rectangle's aspect ratio near the aspectRatio given. Number
  343. aspect_ratio: Var[int]
  344. # If set false, animation of area will be disabled.
  345. is_animation_active: Var[bool]
  346. # Specifies when the animation should begin, the unit of this option is ms.
  347. animation_begin: Var[int]
  348. # Specifies the duration of animation, the unit of this option is ms.
  349. animation_duration: Var[int]
  350. # The type of easing function. 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'linear'
  351. animation_easing: Var[LiteralAnimationEasing]
  352. # The customized event handler of animation start
  353. on_animation_start: EventHandler[lambda: []]
  354. # The customized event handler of animation end
  355. on_animation_end: EventHandler[lambda: []]
  356. @classmethod
  357. def create(cls, *children, **props) -> Component:
  358. """Create a chart component.
  359. Args:
  360. *children: The children of the chart component.
  361. **props: The properties of the chart component.
  362. Returns:
  363. The Treemap component wrapped in a responsive container.
  364. """
  365. return ResponsiveContainer.create(
  366. super().create(*children, **props),
  367. width=props.pop("width", "100%"),
  368. height=props.pop("height", "100%"),
  369. )
  370. area_chart = AreaChart.create
  371. bar_chart = BarChart.create
  372. line_chart = LineChart.create
  373. composed_chart = ComposedChart.create
  374. pie_chart = PieChart.create
  375. radar_chart = RadarChart.create
  376. radial_bar_chart = RadialBarChart.create
  377. scatter_chart = ScatterChart.create
  378. funnel_chart = FunnelChart.create
  379. treemap = Treemap.create