charts.py 19 KB

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