Coverage for src/ipyvizzu/chart.py: 100%
82 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-25 15:04 +0100
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-25 15:04 +0100
1"""A module for working with Vizzu charts."""
3import pkgutil
4import uuid
5from typing import List, Optional, Union, Tuple
7from IPython.display import display_javascript # type: ignore
8from IPython import get_ipython # type: ignore
10from ipyvizzu.animation import Animation, Snapshot, AnimationMerger
11from ipyvizzu.method import Animate, Feature, Store, EventOn, EventOff, Log
12from ipyvizzu.template import ChartProperty, DisplayTarget, DisplayTemplate
13from ipyvizzu.event import EventHandler
16class Chart:
17 """A class for representing a wrapper over Vizzu chart."""
19 VIZZU: str = "https://cdn.jsdelivr.net/npm/vizzu@0.7/dist/vizzu.min.js"
20 """A variable for storing the default url of vizzu package."""
22 def __init__(
23 self,
24 vizzu: Optional[str] = VIZZU,
25 width: Optional[str] = "800px",
26 height: Optional[str] = "480px",
27 display: Optional[Union[DisplayTarget, str]] = DisplayTarget.ACTUAL,
28 ):
29 """
30 Chart constructor.
32 Args:
33 vizzu: The url of Vizzu JavaScript package.
34 width: The width of the chart.
35 height: The height of the chart.
36 display: The display behaviour of the chart.
37 """
39 self._chart_id = uuid.uuid4().hex[:7]
41 self._display_target = DisplayTarget(display)
42 self._calls: List[str] = []
43 self._showed = False
45 self._scroll_into_view = False
47 ipyvizzurawjs = pkgutil.get_data(__name__, "templates/ipyvizzu.js")
48 ipyvizzujs = ipyvizzurawjs.decode("utf-8") # type: ignore
49 self._display(DisplayTemplate.IPYVIZZUJS.format(ipyvizzujs=ipyvizzujs))
51 self._display(
52 DisplayTemplate.INIT.format(
53 chart_id=self._chart_id,
54 vizzu=vizzu,
55 div_width=width,
56 div_height=height,
57 )
58 )
60 if self._display_target != DisplayTarget.MANUAL:
61 self._register_events()
63 @staticmethod
64 def _register_events() -> None:
65 ipy = get_ipython()
66 if ipy is not None:
67 ipy.events.register("pre_run_cell", Chart._register_pre_run_cell)
69 @staticmethod
70 def _register_pre_run_cell() -> None:
71 display_javascript(DisplayTemplate.CLEAR_INHIBITSCROLL, raw=True)
73 @property
74 def scroll_into_view(self) -> bool:
75 """
76 A property for turning on/off the scroll into view feature.
78 Returns:
79 The value of the property (default `False`).
80 """
82 return self._scroll_into_view
84 @scroll_into_view.setter
85 def scroll_into_view(self, scroll_into_view: Optional[bool]):
86 self._scroll_into_view = bool(scroll_into_view)
88 def animate(
89 self, *animations: Animation, **options: Optional[Union[str, int, float, dict]]
90 ) -> None:
91 """
92 A method for changing the state of the chart.
94 Args:
95 *animations:
96 List of Animation objects such as [Data][ipyvizzu.animation.Data],
97 [Config][ipyvizzu.animation.Config] and [Style][ipyvizzu.animation.Style].
98 **options: Dictionary of animation options for example `duration=1`.
99 For information on all available animation options see the
100 [Vizzu Code reference](https://lib.vizzuhq.com/latest/reference/interfaces/vizzu.Anim.Options/#properties).
102 Raises:
103 ValueError: If `animations` is not set.
105 Example:
106 Reset the chart styles:
108 chart.animate(Style(None))
109 """ # pylint: disable=line-too-long
111 if not animations:
112 raise ValueError("No animation was set.")
114 animation = self._merge_animations(animations)
115 animate = Animate(animation, options)
117 self._display(
118 DisplayTemplate.ANIMATE.format(
119 display_target=self._display_target.value,
120 chart_id=self._chart_id,
121 scroll=str(self._scroll_into_view).lower(),
122 **animate.dump(),
123 )
124 )
126 @staticmethod
127 def _merge_animations(
128 animations: Tuple[Animation, ...],
129 ) -> Union[Animation, AnimationMerger]:
130 if len(animations) == 1:
131 return animations[0]
133 merger = AnimationMerger()
134 for animation in animations:
135 merger.merge(animation)
137 return merger
139 def feature(self, name: str, enabled: bool) -> None:
140 """
141 A method for turning on/off features of the chart.
143 Args:
144 name:
145 The name of the chart feature.
146 For information on all available features see the
147 [Vizzu Code reference](https://lib.vizzuhq.com/latest/reference/modules/vizzu/#feature).
148 enabled: The new state of the chart feature.
150 Example:
151 Turn on `tooltip` of the chart:
153 chart.feature("tooltip", True)
154 """ # pylint: disable=line-too-long
156 self._display(
157 DisplayTemplate.FEATURE.format(
158 chart_id=self._chart_id,
159 **Feature(name, enabled).dump(),
160 )
161 )
163 def store(self) -> Snapshot:
164 """
165 A method for saving and storing the actual state of the chart.
167 Returns:
168 A snapshot animation object wich stores the actual state of the chart.
170 Example:
171 Save and restore the actual state of the chart:
173 snapshot = chart.store()
174 ...
175 chart.animate(snapshot)
176 """
178 snapshot_id = uuid.uuid4().hex[:7]
179 self._display(
180 DisplayTemplate.STORE.format(
181 chart_id=self._chart_id, **Store(snapshot_id).dump()
182 )
183 )
184 return Snapshot(snapshot_id)
186 def on( # pylint: disable=invalid-name
187 self, event: str, handler: str
188 ) -> EventHandler:
189 """
190 A method for creating and turning on an event handler.
192 Args:
193 event:
194 The type of the event.
195 For information on all available events see the
196 [Vizzu Code reference](https://lib.vizzuhq.com/latest/reference/modules/vizzu.Event/#type).
197 handler: The JavaScript method of the event.
199 Returns:
200 The turned on event handler object.
202 Example:
203 Turn on an event handler which prints an alert message
204 when someone clicks on the chart:
206 handler = chart.on("click", "alert(JSON.stringify(event.data));")
207 """ # pylint: disable=line-too-long
209 event_handler = EventHandler(event, handler)
210 self._display(
211 DisplayTemplate.SET_EVENT.format(
212 chart_id=self._chart_id,
213 **EventOn(event_handler).dump(),
214 )
215 )
216 return event_handler
218 def off(self, event_handler: EventHandler) -> None:
219 """
220 A method for turning off an event handler.
222 Args:
223 event_handler: A previously created event handler object.
225 Example:
226 Turn off a previously created event handler:
228 chart.off(handler)
229 """
231 self._display(
232 DisplayTemplate.CLEAR_EVENT.format(
233 chart_id=self._chart_id,
234 **EventOff(event_handler).dump(),
235 )
236 )
238 def log(self, chart_property: ChartProperty) -> None:
239 """
240 A method for printing chart properties to the browser console.
242 Args:
243 chart_property:
244 A chart property such as
245 [CONFIG][ipyvizzu.template.ChartProperty] and
246 [STYLE][ipyvizzu.template.ChartProperty].
248 Example:
249 Log the actual style of the chart to the browser console:
251 chart.log(ChartProperty.STYLE)
252 """
254 self._display(
255 DisplayTemplate.LOG.format(
256 chart_id=self._chart_id, **Log(chart_property).dump()
257 )
258 )
260 def _repr_html_(self) -> str:
261 assert (
262 self._display_target == DisplayTarget.MANUAL
263 ), "chart._repr_html_() can be used with display=DisplayTarget.MANUAL only"
264 assert not self._showed, "cannot be used after chart displayed."
265 self._showed = True
266 html_id = uuid.uuid4().hex[:7]
267 script = (
268 self._calls[0]
269 + "\n"
270 + "\n".join(self._calls[1:]).replace(
271 "element", f'document.getElementById("{html_id}")'
272 )
273 )
274 return f'<div id="{html_id}"><script>{script}</script></div>'
276 def show(self) -> None:
277 """
278 A method for displaying the assembled JavaScript code.
280 Raises:
281 AssertionError: If [display][ipyvizzu.Chart.__init__]
282 is not [DisplayTarget.MANUAL][ipyvizzu.template.DisplayTarget].
283 AssertionError: If chart already has been displayed.
284 """
286 assert (
287 self._display_target == DisplayTarget.MANUAL
288 ), "chart.show() can be used with display=DisplayTarget.MANUAL only"
289 assert not self._showed, "cannot be used after chart displayed"
290 display_javascript(
291 "\n".join(self._calls),
292 raw=True,
293 )
294 self._showed = True
296 def _display(self, javascript: str) -> None:
297 if self._display_target != DisplayTarget.MANUAL:
298 display_javascript(
299 javascript,
300 raw=True,
301 )
302 else:
303 assert not self._showed, "cannot be used after chart displayed"
304 self._calls.append(javascript)