From 82a62ff086c2fbd39a8c58b6c3ec7dbb1bec7e31 Mon Sep 17 00:00:00 2001 From: TakodaS <49280242+TakodaS@users.noreply.github.com> Date: Wed, 10 Apr 2019 22:36:32 +0100 Subject: [PATCH] Adding auto start animation_opts argument to html export and html renderers (#1503) --- plotly/io/_base_renderers.py | 45 +++++++++++++---- plotly/io/_html.py | 26 ++++++++-- plotly/offline/offline.py | 50 +++++++++++++++++-- .../test_core/test_offline/test_offline.py | 23 ++++++++- 4 files changed, 124 insertions(+), 20 deletions(-) diff --git a/plotly/io/_base_renderers.py b/plotly/io/_base_renderers.py index 5f82d098f4..b8a684feb0 100644 --- a/plotly/io/_base_renderers.py +++ b/plotly/io/_base_renderers.py @@ -229,7 +229,8 @@ def __init__(self, global_init=False, config=None, auto_play=False, - post_script=None): + post_script=None, + animation_opts=None): self.config = dict(config) if config else {} self.auto_play = auto_play @@ -238,6 +239,7 @@ def __init__(self, self.requirejs = requirejs self.full_html = full_html self.post_script = post_script + self.animation_opts = animation_opts def activate(self): if self.global_init: @@ -315,7 +317,8 @@ def to_mimebundle(self, fig_dict): include_plotlyjs=include_plotlyjs, include_mathjax=include_mathjax, post_script=self.post_script, - full_html=self.full_html) + full_html=self.full_html, + animation_opts=self.animation_opts) return {'text/html': html} @@ -336,7 +339,8 @@ def __init__(self, connected=False, config=None, auto_play=False, - post_script=None): + post_script=None, + animation_opts=None): super(NotebookRenderer, self).__init__( connected=connected, full_html=False, @@ -344,7 +348,8 @@ def __init__(self, global_init=True, config=config, auto_play=auto_play, - post_script=post_script) + post_script=post_script, + animation_opts=animation_opts) class KaggleRenderer(HtmlRenderer): @@ -358,7 +363,12 @@ class KaggleRenderer(HtmlRenderer): mime type: 'text/html' """ - def __init__(self, config=None, auto_play=False, post_script=None): + def __init__(self, + config=None, + auto_play=False, + post_script=None, + animation_opts=None): + super(KaggleRenderer, self).__init__( connected=True, full_html=False, @@ -366,7 +376,8 @@ def __init__(self, config=None, auto_play=False, post_script=None): global_init=True, config=config, auto_play=auto_play, - post_script=post_script) + post_script=post_script, + animation_opts=animation_opts) class ColabRenderer(HtmlRenderer): @@ -377,7 +388,12 @@ class ColabRenderer(HtmlRenderer): mime type: 'text/html' """ - def __init__(self, config=None, auto_play=False, post_script=None): + def __init__(self, + config=None, + auto_play=False, + post_script=None, + animation_opts=None): + super(ColabRenderer, self).__init__( connected=True, full_html=True, @@ -385,7 +401,8 @@ def __init__(self, config=None, auto_play=False, post_script=None): global_init=False, config=config, auto_play=auto_play, - post_script=post_script) + post_script=post_script, + animation_opts=animation_opts) class IFrameRenderer(MimetypeRenderer): @@ -414,11 +431,13 @@ class IFrameRenderer(MimetypeRenderer): def __init__(self, config=None, auto_play=False, - post_script=None): + post_script=None, + animation_opts=None): self.config = config self.auto_play = auto_play self.post_script = post_script + self.animation_opts = animation_opts def to_mimebundle(self, fig_dict): from plotly.io import write_html @@ -457,6 +476,7 @@ def to_mimebundle(self, fig_dict): include_mathjax='cdn', auto_open=False, post_script=self.post_script, + animation_opts=self.animation_opts, validate=False) # Build IFrame @@ -547,7 +567,8 @@ def __init__(self, using=None, new=0, autoraise=True, - post_script=None): + post_script=None, + animation_opts=None): self.config = config self.auto_play = auto_play @@ -555,6 +576,7 @@ def __init__(self, self.new = new self.autoraise = autoraise self.post_script = post_script + self.animation_opts = animation_opts def render(self, fig_dict): renderer = HtmlRenderer( @@ -564,7 +586,8 @@ def render(self, fig_dict): global_init=False, config=self.config, auto_play=self.auto_play, - post_script=self.post_script) + post_script=self.post_script, + animation_opts=self.animation_opts) bundle = renderer.to_mimebundle(fig_dict) html = bundle['text/html'] diff --git a/plotly/io/_html.py b/plotly/io/_html.py index f70d269f7f..62596ae98f 100644 --- a/plotly/io/_html.py +++ b/plotly/io/_html.py @@ -30,6 +30,7 @@ def to_html(fig, include_mathjax=False, post_script=None, full_html=True, + animation_opts=None, validate=True): """ Convert a figure to an HTML string representation. @@ -102,6 +103,12 @@ def to_html(fig, If True, produce a string containing a complete HTML document starting with an tag. If False, produce a string containing a single
element. + animation_opts: dict or None (default None) + dict of custom animation parameters to be passed to the function + Plotly.animate in Plotly.js. See + https://github.com/plotly/plotly.js/blob/master/src/plots/animation_attributes.js + for available options. Has no effect if the figure does not contain + frames, or auto_play is False. validate: bool (default True) True if the figure should be validated before being converted to JSON, False otherwise. @@ -164,9 +171,14 @@ def to_html(fig, }})""".format(id=plotdivid, frames=jframes) if auto_play: + if animation_opts: + animation_opts_arg = ', ' + json.dumps(animation_opts) + else: + animation_opts_arg = '' then_animate = """.then(function(){{ - Plotly.animate('{id}'); - }})""".format(id=plotdivid) + Plotly.animate('{id}', null{animation_opts}); + }})""".format(id=plotdivid, + animation_opts=animation_opts_arg) script = """ if (document.getElementById("{id}")) {{ @@ -299,6 +311,7 @@ def write_html(fig, include_mathjax=False, post_script=None, full_html=True, + animation_opts=None, validate=True, auto_open=False): """ @@ -390,6 +403,12 @@ def write_html(fig, If True, produce a string containing a complete HTML document starting with an tag. If False, produce a string containing a single
element. + animation_opts: dict or None (default None) + dict of custom animation parameters to be passed to the function + Plotly.animate in Plotly.js. See + https://github.com/plotly/plotly.js/blob/master/src/plots/animation_attributes.js + for available options. Has no effect if the figure does not contain + frames, or auto_play is False. validate: bool (default True) True if the figure should be validated before being converted to JSON, False otherwise. @@ -411,7 +430,8 @@ def write_html(fig, include_mathjax=include_mathjax, post_script=post_script, full_html=full_html, - validate=validate) + validate=validate, + animation_opts=animation_opts) # Check if file is a string file_is_str = isinstance(file, six.string_types) diff --git a/plotly/offline/offline.py b/plotly/offline/offline.py index 5cad091f01..94a7fd07a2 100644 --- a/plotly/offline/offline.py +++ b/plotly/offline/offline.py @@ -294,7 +294,7 @@ def init_notebook_mode(connected=False): def iplot(figure_or_data, show_link=False, link_text='Export to plot.ly', validate=True, image=None, filename='plot_image', image_width=800, - image_height=600, config=None, auto_play=True): + image_height=600, config=None, auto_play=True, animation_opts=None): """ Draw plotly graphs inside an IPython or Jupyter notebook @@ -329,6 +329,11 @@ def iplot(figure_or_data, show_link=False, link_text='Export to plot.ly', auto_play (default=True) -- Whether to automatically start the animation sequence if the figure contains frames. Has no effect if the figure does not contain frames. + animation_opts (default=None) -- dict of custom animation parameters to be + passed to the function Plotly.animate in Plotly.js. See + https://github.com/plotly/plotly.js/blob/master/src/plots/animation_attributes.js + for available options. Has no effect if the figure + does not contain frames, or auto_play is False. Example: ``` @@ -339,6 +344,20 @@ def iplot(figure_or_data, show_link=False, link_text='Export to plot.ly', format you want. e.g. `image='png'` iplot([{'x': [1, 2, 3], 'y': [5, 2, 7]}], image='png') ``` + + animation_opts Example: + ``` + from plotly.offline import iplot + figure = {'data': [{'x': [0, 1], 'y': [0, 1]}], + 'layout': {'xaxis': {'range': [0, 5], 'autorange': False}, + 'yaxis': {'range': [0, 5], 'autorange': False}, + 'title': 'Start Title'}, + 'frames': [{'data': [{'x': [1, 2], 'y': [1, 2]}]}, + {'data': [{'x': [1, 4], 'y': [1, 4]}]}, + {'data': [{'x': [3, 4], 'y': [3, 4]}], + 'layout': {'title': 'End Title'}}]} + iplot(figure,animation_opts="{frame: {duration: 1}}") + ``` """ import plotly.io as pio @@ -361,14 +380,16 @@ def iplot(figure_or_data, show_link=False, link_text='Export to plot.ly', validate=validate, config=config, auto_play=auto_play, - post_script=post_script) + post_script=post_script, + animation_opts=animation_opts) def plot(figure_or_data, show_link=False, link_text='Export to plot.ly', validate=True, output_type='file', include_plotlyjs=True, filename='temp-plot.html', auto_open=True, image=None, image_filename='plot_image', image_width=800, image_height=600, - config=None, include_mathjax=False, auto_play=True): + config=None, include_mathjax=False, auto_play=True, + animation_opts=None): """ Create a plotly graph locally as an HTML document or string. Example: @@ -481,6 +502,25 @@ def plot(figure_or_data, show_link=False, link_text='Export to plot.ly', auto_play (default=True) -- Whether to automatically start the animation sequence on page load if the figure contains frames. Has no effect if the figure does not contain frames. + animation_opts (default=None) -- dict of custom animation parameters to be + passed to the function Plotly.animate in Plotly.js. See + https://github.com/plotly/plotly.js/blob/master/src/plots/animation_attributes.js + for available options. Has no effect if the figure does not contain + frames, or auto_play is False. + + Example: + ``` + from plotly.offline import plot + figure = {'data': [{'x': [0, 1], 'y': [0, 1]}], + 'layout': {'xaxis': {'range': [0, 5], 'autorange': False}, + 'yaxis': {'range': [0, 5], 'autorange': False}, + 'title': 'Start Title'}, + 'frames': [{'data': [{'x': [1, 2], 'y': [1, 2]}]}, + {'data': [{'x': [1, 4], 'y': [1, 4]}]}, + {'data': [{'x': [3, 4], 'y': [3, 4]}], + 'layout': {'title': 'End Title'}}]} + plot(figure,animation_opts="{frame: {duration: 1}}") + ``` """ import plotly.io as pio @@ -522,6 +562,7 @@ def plot(figure_or_data, show_link=False, link_text='Export to plot.ly', post_script=post_script, full_html=True, validate=validate, + animation_opts=animation_opts, auto_open=auto_open) return filename else: @@ -533,7 +574,8 @@ def plot(figure_or_data, show_link=False, link_text='Export to plot.ly', include_mathjax=include_mathjax, post_script=post_script, full_html=False, - validate=validate) + validate=validate, + animation_opts=animation_opts) def plot_mpl(mpl_fig, resize=False, strip_style=False, diff --git a/plotly/tests/test_core/test_offline/test_offline.py b/plotly/tests/test_core/test_offline/test_offline.py index da1a93e21a..b7d543a6b3 100644 --- a/plotly/tests/test_core/test_offline/test_offline.py +++ b/plotly/tests/test_core/test_offline/test_offline.py @@ -418,9 +418,28 @@ def test_auto_play(self): def test_no_auto_play(self): html = plotly.offline.plot( - fig_frames, output_type='div',auto_play=False) + fig_frames, output_type='div', auto_play=False) + self.assertIn(add_frames, html) + self.assertNotIn(do_auto_play, html) + + def test_animation_opts(self): + animation_opts = {'frame': {'duration': 5000}} + expected_opts_str = json.dumps(animation_opts) + + # When auto_play is False, animation options are skipped + html = plotly.offline.plot( + fig_frames, output_type='div', auto_play=False, animation_opts=animation_opts) self.assertIn(add_frames, html) self.assertNotIn(do_auto_play, html) + self.assertNotIn(expected_opts_str, html) + + # When auto_play is True, animation options are included + html = plotly.offline.plot( + fig_frames, output_type='div', auto_play=True, + animation_opts=animation_opts) + self.assertIn(add_frames, html) + self.assertIn(do_auto_play, html) + self.assertIn(expected_opts_str, html) def test_download_image(self): # Not download image by default @@ -431,4 +450,4 @@ def test_download_image(self): # Request download image html = plotly.offline.plot( fig_frames, output_type='div', auto_play=False, image='png') - self.assertIn(download_image, html) \ No newline at end of file + self.assertIn(download_image, html)