Coverage for src/ipyvizzu/chart.py: 100%
93 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-10 09:30 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-10 09:30 +0000
1"""A module for working with Vizzu charts."""
3import pkgutil
4import uuid
5from typing import List, Optional, Union
7from IPython.display import display_javascript # type: ignore
8from IPython import get_ipython # type: ignore
10from ipyvizzu.animation import AbstractAnimation, Snapshot, AnimationMerger
11from ipyvizzu.animationcontrol import AnimationControl
12from ipyvizzu.method import Animate, Feature, Store, EventOn, EventOff, Log
13from ipyvizzu.template import ChartProperty, DisplayTarget, DisplayTemplate
14from ipyvizzu.event import EventHandler
17class Chart:
18 """A class for representing a wrapper over Vizzu chart."""
20 # pylint: disable=too-many-instance-attributes
22 VIZZU: str = "https://cdn.jsdelivr.net/npm/vizzu@0.7/dist/vizzu.min.js"
23 """A variable for storing the default url of vizzu package."""
25 def __init__(
26 self,
27 vizzu: str = VIZZU,
28 width: str = "800px",
29 height: str = "480px",
30 display: Union[DisplayTarget, str] = DisplayTarget.ACTUAL,
31 ):
32 """
33 Chart constructor.
35 Args:
36 vizzu: The url of Vizzu JavaScript package.
37 width: The width of the chart.
38 height: The height of the chart.
39 display: The display behaviour of the chart.
40 """
42 self._chart_id: str = uuid.uuid4().hex[:7]
44 self._vizzu: str = vizzu
45 self._width: str = width
46 self._height: str = height
48 self._display_target: DisplayTarget = DisplayTarget(display)
49 self._calls: List[str] = []
50 self._last_anim: Optional[str] = None
51 self._showed: bool = False
53 self._initialized: bool = False
54 self._scroll_into_view: bool = False
56 @staticmethod
57 def _register_events() -> None:
58 ipy = get_ipython()
59 if ipy is not None:
60 ipy.events.register("pre_run_cell", Chart._register_pre_run_cell)
62 @staticmethod
63 def _register_pre_run_cell() -> None:
64 display_javascript(DisplayTemplate.CLEAR_INHIBITSCROLL, raw=True)
66 @property
67 def scroll_into_view(self) -> bool:
68 """
69 A property for turning on/off the scroll into view feature.
71 Returns:
72 The value of the property (default `False`).
73 """
75 return self._scroll_into_view
77 @scroll_into_view.setter
78 def scroll_into_view(self, scroll_into_view: Optional[bool]):
79 self._scroll_into_view = bool(scroll_into_view)
81 @property
82 def control(self) -> AnimationControl:
83 """
84 A property for returning a control object of the last animation.
86 Raises:
87 AssertionError: If called before any animation plays.
89 Returns:
90 The control object of the last animation.
91 """
92 assert self._last_anim, "must be used after an animation."
93 return AnimationControl(self._chart_id, self._last_anim, self._display)
95 def initializing(self) -> None:
96 """A method for initializing the chart."""
98 if not self._initialized:
99 self._initialized = True
100 ipyvizzurawjs = pkgutil.get_data(__name__, "templates/ipyvizzu.js")
101 ipyvizzujs = ipyvizzurawjs.decode("utf-8") # type: ignore
102 self._display(DisplayTemplate.IPYVIZZUJS.format(ipyvizzujs=ipyvizzujs))
104 if self._display_target != DisplayTarget.MANUAL:
105 Chart._register_events()
107 self._display(
108 DisplayTemplate.INIT.format(
109 chart_id=self._chart_id,
110 vizzu=self._vizzu,
111 div_width=self._width,
112 div_height=self._height,
113 )
114 )
116 def animate(
117 self,
118 *animations: AbstractAnimation,
119 **options: Optional[Union[str, int, float, dict]],
120 ) -> None:
121 """
122 A method for changing the state of the chart.
124 Args:
125 *animations:
126 List of AbstractAnimation inherited objects such as [Data][ipyvizzu.animation.Data],
127 [Config][ipyvizzu.animation.Config] and [Style][ipyvizzu.animation.Style].
128 **options: Dictionary of animation options for example `duration=1`.
129 For information on all available animation options see the
130 [Vizzu Code reference](https://lib.vizzuhq.com/latest/reference/interfaces/vizzu.Anim.Options/#properties).
132 Raises:
133 ValueError: If `animations` is not set.
135 Example:
136 Reset the chart styles:
138 chart.animate(Style(None))
139 """ # pylint: disable=line-too-long
141 if not animations:
142 raise ValueError("No animation was set.")
144 animation = AnimationMerger.merge_animations(animations)
145 animate = Animate(animation, options)
147 self._last_anim = uuid.uuid4().hex[:7]
148 self._display(
149 DisplayTemplate.ANIMATE.format(
150 display_target=self._display_target.value,
151 chart_id=self._chart_id,
152 anim_id=self._last_anim,
153 scroll=str(self._scroll_into_view).lower(),
154 **animate.dump(),
155 )
156 )
158 def feature(self, name: str, enabled: bool) -> None:
159 """
160 A method for turning on/off features of the chart.
162 Args:
163 name:
164 The name of the chart feature.
165 For information on all available features see the
166 [Vizzu Code reference](https://lib.vizzuhq.com/latest/reference/modules/vizzu/#feature).
167 enabled: The new state of the chart feature.
169 Example:
170 Turn on `tooltip` of the chart:
172 chart.feature("tooltip", True)
173 """ # pylint: disable=line-too-long
175 self._display(
176 DisplayTemplate.FEATURE.format(
177 chart_id=self._chart_id,
178 **Feature(name, enabled).dump(),
179 )
180 )
182 def store(self) -> Snapshot:
183 """
184 A method for saving and storing the actual state of the chart.
186 Returns:
187 A Snapshot object wich stores the actual state of the chart.
189 Example:
190 Save and restore the actual state of the chart:
192 snapshot = chart.store()
193 ...
194 chart.animate(snapshot)
195 """
197 snapshot_id = uuid.uuid4().hex[:7]
198 self._display(
199 DisplayTemplate.STORE.format(
200 chart_id=self._chart_id, **Store(snapshot_id).dump()
201 )
202 )
203 return Snapshot(snapshot_id)
205 def on( # pylint: disable=invalid-name
206 self, event: str, handler: str
207 ) -> EventHandler:
208 """
209 A method for creating and turning on an event handler.
211 Args:
212 event:
213 The type of the event.
214 For information on all available events see the
215 [Vizzu Code reference](https://lib.vizzuhq.com/latest/reference/modules/vizzu.Event/#type).
216 handler: The JavaScript method of the event.
218 Returns:
219 The turned on event handler object.
221 Example:
222 Turn on an event handler which prints an alert message
223 when someone clicks on the chart:
225 handler = chart.on("click", "alert(JSON.stringify(event.data));")
226 """ # pylint: disable=line-too-long
228 event_handler = EventHandler(event, handler)
229 self._display(
230 DisplayTemplate.SET_EVENT.format(
231 chart_id=self._chart_id,
232 **EventOn(event_handler).dump(),
233 )
234 )
235 return event_handler
237 def off(self, event_handler: EventHandler) -> None:
238 """
239 A method for turning off an event handler.
241 Args:
242 event_handler: A previously created event handler object.
244 Example:
245 Turn off a previously created event handler:
247 chart.off(handler)
248 """
250 self._display(
251 DisplayTemplate.CLEAR_EVENT.format(
252 chart_id=self._chart_id,
253 **EventOff(event_handler).dump(),
254 )
255 )
257 def log(self, chart_property: ChartProperty) -> None:
258 """
259 A method for printing chart properties to the browser console.
261 Args:
262 chart_property:
263 A chart property such as
264 [CONFIG][ipyvizzu.template.ChartProperty] and
265 [STYLE][ipyvizzu.template.ChartProperty].
267 Example:
268 Log the actual style of the chart to the browser console:
270 chart.log(ChartProperty.STYLE)
271 """
273 self._display(
274 DisplayTemplate.LOG.format(
275 chart_id=self._chart_id, **Log(chart_property).dump()
276 )
277 )
279 def _repr_html_(self) -> str:
280 assert (
281 self._display_target == DisplayTarget.MANUAL
282 ), "chart._repr_html_() can be used with display=DisplayTarget.MANUAL only"
283 assert not self._showed, "cannot be used after chart displayed."
284 self._showed = True
285 if not self._initialized:
286 return ""
287 html_id = uuid.uuid4().hex[:7]
288 script = (
289 self._calls[0]
290 + "\n"
291 + "\n".join(self._calls[1:]).replace(
292 "element", f'document.getElementById("{html_id}")'
293 )
294 )
295 return f'<div id="{html_id}"><script>{script}</script></div>'
297 def show(self) -> None:
298 """
299 A method for displaying the assembled JavaScript code.
301 Raises:
302 AssertionError: If [display][ipyvizzu.Chart.__init__]
303 is not [DisplayTarget.MANUAL][ipyvizzu.template.DisplayTarget].
304 AssertionError: If chart already has been displayed.
305 """
307 assert (
308 self._display_target == DisplayTarget.MANUAL
309 ), "chart.show() can be used with display=DisplayTarget.MANUAL only"
310 assert not self._showed, "cannot be used after chart displayed"
311 display_javascript(
312 "\n".join(self._calls),
313 raw=True,
314 )
315 self._showed = True
317 def _display(self, javascript: str) -> None:
318 if not self._initialized:
319 self.initializing()
320 if self._display_target != DisplayTarget.MANUAL:
321 display_javascript(
322 javascript,
323 raw=True,
324 )
325 else:
326 assert not self._showed, "cannot be used after chart displayed"
327 self._calls.append(javascript)