Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix 3.8.0 responsive resizing behavior #1525

Merged
merged 1 commit into from
Apr 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 58 additions & 19 deletions plotly/io/_base_renderers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import absolute_import
import base64
import json
import webbrowser
Expand Down Expand Up @@ -238,8 +239,8 @@ def __init__(self,
self.global_init = global_init
self.requirejs = requirejs
self.full_html = full_html
self.post_script = post_script
self.animation_opts = animation_opts
self.post_script = post_script

def activate(self):
if self.global_init:
Expand Down Expand Up @@ -310,15 +311,50 @@ def to_mimebundle(self, fig_dict):
include_plotlyjs = True
include_mathjax = 'cdn'

# build post script
post_script = ["""
var gd = document.getElementById('{plot_id}');
var x = new MutationObserver(function (mutations, observer) {{
var display = window.getComputedStyle(gd).display;
if (!display || display === 'none') {{
console.log([gd, 'removed!']);
Plotly.purge(gd);
observer.disconnect();
}}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
x.observe(outputEl, {childList: true});
}}
"""]

# Add user defined post script
if self.post_script:
if not isinstance(self.post_script, (list, tuple)):
post_script.append(self.post_script)
else:
post_script.extend(self.post_script)

html = to_html(
fig_dict,
config=self.config,
auto_play=self.auto_play,
include_plotlyjs=include_plotlyjs,
include_mathjax=include_mathjax,
post_script=self.post_script,
post_script=post_script,
full_html=self.full_html,
animation_opts=self.animation_opts)
animation_opts=self.animation_opts,
default_width='100%',
default_height=525,
)

return {'text/html': html}

Expand Down Expand Up @@ -446,17 +482,16 @@ def to_mimebundle(self, fig_dict):
# having iframe have its own scroll bar.
iframe_buffer = 20
layout = fig_dict.get('layout', {})
if 'width' in layout:
iframe_width = layout['width'] + iframe_buffer

if layout.get('width', False):
iframe_width = str(layout['width'] + iframe_buffer) + 'px'
else:
layout['width'] = 700
iframe_width = layout['width'] + iframe_buffer
iframe_width = '100%'

if 'height' in layout:
if layout.get('height', False):
iframe_height = layout['height'] + iframe_buffer
else:
layout['height'] = 450
iframe_height = layout['height'] + iframe_buffer
iframe_height = str(525 + iframe_buffer) + 'px'

# Build filename using ipython cell number
ip = IPython.get_ipython()
Expand All @@ -477,11 +512,14 @@ def to_mimebundle(self, fig_dict):
auto_open=False,
post_script=self.post_script,
animation_opts=self.animation_opts,
default_width='100%',
default_height=525,
validate=False)

# Build IFrame
iframe_html = """\
<iframe
scrolling="no"
width="{width}"
height="{height}"
src="{src}"
Expand Down Expand Up @@ -579,16 +617,17 @@ def __init__(self,
self.animation_opts = animation_opts

def render(self, fig_dict):
renderer = HtmlRenderer(
connected=False,
full_html=True,
requirejs=False,
global_init=False,
from plotly.io import to_html
html = to_html(
fig_dict,
config=self.config,
auto_play=self.auto_play,
include_plotlyjs=True,
include_mathjax='cdn',
post_script=self.post_script,
animation_opts=self.animation_opts)

bundle = renderer.to_mimebundle(fig_dict)
html = bundle['text/html']
full_html=True,
animation_opts=self.animation_opts,
default_width='100%',
default_height='100%',
)
open_html_in_browser(html, self.using, self.new, self.autoraise)
78 changes: 60 additions & 18 deletions plotly/io/_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def to_html(fig,
post_script=None,
full_html=True,
animation_opts=None,
default_width='100%',
default_height='100%',
validate=True):
"""
Convert a figure to an HTML string representation.
Expand Down Expand Up @@ -93,10 +95,10 @@ def to_html(fig,
If a string that ends in '.js', a script tag is included that
references the specified path. This approach can be used to point the
resulting HTML div string to an alternative CDN.
post_script: str or None (default None)
JavaScript snippet to be included in the resulting div just after
plot creation. The string may include '{plot_id}' placeholders that
will then be replaced by the `id` of the div element that the
post_script: str or list or None (default None)
JavaScript snippet(s) to be included in the resulting div just after
plot creation. The string(s) may include '{plot_id}' placeholders
that will then be replaced by the `id` of the div element that the
plotly.js figure is associated with. One application for this script
is to install custom plotly.js event handlers.
full_html: bool (default True)
Expand All @@ -109,6 +111,11 @@ def to_html(fig,
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.
default_width, default_height: number or str (default '100%')
The default figure width/height to use if the provided figure does not
specify its own layout.width/layout.height property. May be
specified in pixels as an integer (e.g. 500), or as a css width style
string (e.g. '500px', '100%').
validate: bool (default True)
True if the figure should be validated before being converted to
JSON, False otherwise.
Expand Down Expand Up @@ -143,25 +150,47 @@ def to_html(fig,
# ## Serialize figure config ##
config = _get_jconfig(config)

# Check whether we should add responsive
layout_dict = fig_dict.get('layout', {})
if layout_dict.get('width', None) is None:
config.setdefault('responsive', True)
# Set responsive
config.setdefault('responsive', True)

jconfig = json.dumps(config)

# Get div width/height
layout_dict = fig_dict.get('layout', {})
div_width = layout_dict.get('width', default_width)
div_height = layout_dict.get('height', default_height)

# Add 'px' suffix to numeric widths
try:
float(div_width)
except (ValueError, TypeError):
pass
else:
div_width = str(div_width) + 'px'

try:
float(div_height)
except (ValueError, TypeError):
pass
else:
div_height = str(div_height) + 'px'

# ## Get platform URL ##
plotly_platform_url = config.get('plotlyServerURL', 'https://plot.ly')

# ## Build script body ##
# This is the part that actually calls Plotly.js

# build post script snippet(s)
then_post_script = ''
if post_script:
then_post_script = """.then(function(){{
if not isinstance(post_script, (list, tuple)):
post_script = [post_script]
for ps in post_script:
then_post_script += """.then(function(){{
{post_script}
}})""".format(
post_script=post_script.replace('{plot_id}', plotdivid))
else:
then_post_script = ''
post_script=ps.replace('{plot_id}', plotdivid))

then_addframes = ''
then_animate = ''
Expand Down Expand Up @@ -274,7 +303,8 @@ def to_html(fig,
<div>
{mathjax_script}
{load_plotlyjs}
<div id="{id}" class="plotly-graph-div"></div>
<div id="{id}" class="plotly-graph-div" \
style="height:{height}; width:{width};"></div>
<script type="text/javascript">
{require_start}
window.PLOTLYENV=window.PLOTLYENV || {{}};
Expand All @@ -286,6 +316,8 @@ def to_html(fig,
mathjax_script=mathjax_script,
load_plotlyjs=load_plotlyjs,
id=plotdivid,
width=div_width,
height=div_height,
plotly_platform_url=plotly_platform_url,
require_start=require_start,
script=script,
Expand Down Expand Up @@ -313,6 +345,8 @@ def write_html(fig,
full_html=True,
animation_opts=None,
validate=True,
default_width='100%',
default_height='100%',
auto_open=False):
"""
Write a figure to an HTML file representation
Expand Down Expand Up @@ -393,10 +427,10 @@ def write_html(fig,
If a string that ends in '.js', a script tag is included that
references the specified path. This approach can be used to point the
resulting HTML div string to an alternative CDN.
post_script: str or None (default None)
JavaScript snippet to be included in the resulting div just after
plot creation. The string may include '{plot_id}' placeholders that
will then be replaced by the `id` of the div element that the
post_script: str or list or None (default None)
JavaScript snippet(s) to be included in the resulting div just after
plot creation. The string(s) may include '{plot_id}' placeholders
that will then be replaced by the `id` of the div element that the
plotly.js figure is associated with. One application for this script
is to install custom plotly.js event handlers.
full_html: bool (default True)
Expand All @@ -409,6 +443,11 @@ def write_html(fig,
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.
default_width, default_height: number or str (default '100%')
The default figure width/height to use if the provided figure does not
specify its own layout.width/layout.height property. May be
specified in pixels as an integer (e.g. 500), or as a css width style
string (e.g. '500px', '100%').
validate: bool (default True)
True if the figure should be validated before being converted to
JSON, False otherwise.
Expand All @@ -430,8 +469,11 @@ def write_html(fig,
include_mathjax=include_mathjax,
post_script=post_script,
full_html=full_html,
animation_opts=animation_opts,
default_width=default_width,
default_height=default_height,
validate=validate,
animation_opts=animation_opts)
)

# Check if file is a string
file_is_str = isinstance(file, six.string_types)
Expand Down
43 changes: 0 additions & 43 deletions plotly/tests/test_core/test_offline/test_offline.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@
]
}


resize_code_strings = ['"responsive": true']


PLOTLYJS = plotly.offline.get_plotlyjs()

plotly_config_script = """\
Expand Down Expand Up @@ -266,45 +262,6 @@ def test_div_output(self):
self.assertNotIn('</html>', html)
self.assertTrue(html.startswith('<div>') and html.endswith('</div>'))

def test_autoresizing(self):

# If width or height wasn't specified, then we add a window resizer
html = self._read_html(plotly.offline.plot(fig, auto_open=False))
for resize_code_string in resize_code_strings:
self.assertIn(resize_code_string, html)

# If width or height was specified, then we don't resize
html = self._read_html(plotly.offline.plot({
'data': fig['data'],
'layout': {
'width': 500, 'height': 500
}
}, auto_open=False))
for resize_code_string in resize_code_strings:
self.assertNotIn(resize_code_string, html)

def test_autoresizing_div(self):

# If width or height wasn't specified, then we add a window resizer
for include_plotlyjs in [True, False, 'cdn', 'directory']:
html = plotly.offline.plot(fig,
output_type='div',
include_plotlyjs=include_plotlyjs)

for resize_code_string in resize_code_strings:
self.assertIn(resize_code_string, html)

# If width or height was specified, then we don't resize
html = plotly.offline.plot({
'data': fig['data'],
'layout': {
'width': 500, 'height': 500
}
}, output_type='div')

for resize_code_string in resize_code_strings:
self.assertNotIn(resize_code_string, html)

def test_config(self):
config = dict(linkText='Plotly rocks!',
showLink=True,
Expand Down