Skip to content

Fix 3.8.0 responsive resizing behavior #1525

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

Merged
merged 1 commit into from
Apr 19, 2019
Merged
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
@@ -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:
@@ -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}

@@ -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()
@@ -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}"
@@ -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
@@ -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.
@@ -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)
@@ -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.
@@ -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 = ''
@@ -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 || {{}};
@@ -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,
@@ -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
@@ -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)
@@ -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.
@@ -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)
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
@@ -35,10 +35,6 @@
]
}


resize_code_strings = ['"responsive": true']


PLOTLYJS = plotly.offline.get_plotlyjs()

plotly_config_script = """\
@@ -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,