Skip to content

offline.plot/iplot improvements #1234

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 6 commits into from
Oct 23, 2018
Merged
Show file tree
Hide file tree
Changes from 5 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
114 changes: 86 additions & 28 deletions plotly/offline/offline.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import time
import webbrowser

import six
from requests.compat import json as _json

import plotly
Expand Down Expand Up @@ -358,8 +359,12 @@ def iplot(figure_or_data, show_link=True, link_text='Export to plot.ly',
plot_html, plotdivid, width, height = _plot_html(
figure_or_data, config, validate, '100%', 525, True
)
display_bundle['text/html'] = plot_html
display_bundle['text/vnd.plotly.v1+html'] = plot_html
resize_script = ''
if width == '100%' or height == '100%':
resize_script = _build_resize_script(plotdivid)

display_bundle['text/html'] = plot_html + resize_script
display_bundle['text/vnd.plotly.v1+html'] = plot_html + resize_script

ipython_display.display(display_bundle, raw=True)

Expand Down Expand Up @@ -389,6 +394,17 @@ def iplot(figure_or_data, show_link=True, link_text='Export to plot.ly',
ipython_display.display(ipython_display.HTML(script))


def _build_resize_script(plotdivid):
resize_script = (
''
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove empty string here

'<script type="text/javascript">'
'window.addEventListener("resize", function(){{'
'Plotly.Plots.resize(document.getElementById("{id}"));}});'
'</script>'
).format(id=plotdivid)
return resize_script


def plot(figure_or_data, show_link=True, link_text='Export to plot.ly',
validate=True, output_type='file', include_plotlyjs=True,
filename='temp-plot.html', auto_open=True, image=None,
Expand Down Expand Up @@ -433,10 +449,41 @@ def plot(figure_or_data, show_link=True, link_text='Export to plot.ly',
in a standalone HTML file.
Use 'div' if you are embedding these graphs in an HTML file with
other graphs or HTML markup, like a HTML report or an website.
include_plotlyjs (default=True) -- If True, include the plotly.js
source code in the output file or string.
Set as False if your HTML file already contains a copy of the plotly.js
include_plotlyjs (True | False | 'cdn' | 'directory' | path - default=True)
Specifies how the plotly.js library is included in the output html
file or div string.

If True, a script tag containing the plotly.js source code (~3MB)
is included in the output. HTML files generated with this option are
fully self-contained and can be used offline.

If 'cdn', a script tag that references the plotly.js CDN is included
in the output. HTML files generated with this option are about 3MB
smaller than those generated with include_plotlyjs=True, but they
require an active internet connection in order to load the plotly.js
library.

If 'directory', a script tag is included that references an external
plotly.min.js bundle that is assumed to reside in the same
directory as the HTML file. If output_type='file' then the
plotly.min.js bundle is copied into the directory of the resulting
HTML file. If a file named plotly.min.js already exists in the output
directory then this file is left unmodified and no copy is performed.
HTML files generated with this option can be used offline, but they
require a copy of the plotly.min.js bundle in the same directory.
This option is useful when many figures will be saved as HTML files in
the same directory because the plotly.js source code will be included
only once per output directory, rather than once per output file.

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 file to an alternative CDN.

If False, no script tag referencing plotly.js is included. This is
useful when output_type='div' and the resulting div string will be
placed inside an HTML document that already loads plotly.js. This
option is not advised when output_type='file' as it will result in
a non-functional html file.
filename (default='temp-plot.html') -- The local filename to save the
outputted chart to. If the filename already exists, it will be
overwritten. This argument only applies if `output_type` is 'file'.
Expand Down Expand Up @@ -477,25 +524,31 @@ def plot(figure_or_data, show_link=True, link_text='Export to plot.ly',

resize_script = ''
if width == '100%' or height == '100%':
resize_script = (
''
'<script type="text/javascript">'
'window.addEventListener("resize", function(){{'
'Plotly.Plots.resize(document.getElementById("{id}"));}});'
'</script>'
).format(id=plotdivid)
resize_script = _build_resize_script(plotdivid)

if isinstance(include_plotlyjs, six.string_types):
include_plotlyjs = include_plotlyjs.lower()

if include_plotlyjs == 'cdn':
plotly_js_script = """\
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>"""
elif include_plotlyjs == 'directory':
plotly_js_script = '<script src="plotly.min.js"></script>'
elif (isinstance(include_plotlyjs, six.string_types) and
include_plotlyjs.endswith('.js')):
plotly_js_script = '<script src="{url}"></script>'.format(
url=include_plotlyjs)
elif include_plotlyjs:
plotly_js_script = ''.join([
'<script type="text/javascript">',
get_plotlyjs(),
'</script>',
])
else:
plotly_js_script = ''

if output_type == 'file':
with open(filename, 'w') as f:
if include_plotlyjs:
plotly_js_script = ''.join([
'<script type="text/javascript">',
get_plotlyjs(),
'</script>',
])
else:
plotly_js_script = ''

if image:
if image not in __IMAGE_FORMATS:
raise ValueError('The image parameter must be one of the '
Expand Down Expand Up @@ -523,25 +576,30 @@ def plot(figure_or_data, show_link=True, link_text='Export to plot.ly',
'</body>',
'</html>']))

# Check if we should copy plotly.min.js to output directory
if include_plotlyjs == 'directory':
bundle_path = os.path.join(
os.path.dirname(filename), 'plotly.min.js')

if not os.path.exists(bundle_path):
with open(bundle_path, 'w') as f:
f.write(get_plotlyjs())

url = 'file://' + os.path.abspath(filename)
if auto_open:
webbrowser.open(url)

return url

elif output_type == 'div':
if include_plotlyjs:
return ''.join([

return ''.join([
'<div>',
'<script type="text/javascript">',
get_plotlyjs(),
'</script>',
plotly_js_script,
plot_html,
resize_script,
'</div>',
])
else:
return plot_html


def plot_mpl(mpl_fig, resize=False, strip_style=False,
Expand Down
182 changes: 174 additions & 8 deletions plotly/tests/test_core/test_offline/test_offline.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,29 @@
)
}


resize_code_strings = [
'window.addEventListener("resize", ',
'Plotly.Plots.resize('
]


PLOTLYJS = plotly.offline.offline.get_plotlyjs()

cdn_script = ('<script src="https://cdn.plot.ly/plotly-latest.min.js">'
'</script>')

directory_script = '<script src="plotly.min.js"></script>'


class PlotlyOfflineBaseTestCase(TestCase):
def tearDown(self):
# Some offline tests produce an html file. Make sure we clean up :)
try:
os.remove('temp-plot.html')
# Some tests that produce temp-plot.html]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove ']' character

# also produce plotly.min.js
os.remove('plotly.min.js')
except OSError:
pass

Expand Down Expand Up @@ -64,10 +79,142 @@ def test_default_plot_generates_expected_html(self):
# and it's an <html> doc
self.assertTrue(html.startswith('<html>') and html.endswith('</html>'))

def test_including_plotlyjs(self):
html = self._read_html(plotly.offline.plot(fig, include_plotlyjs=False,
auto_open=False))
self.assertNotIn(PLOTLYJS, html)
def test_including_plotlyjs_truthy_html(self):
# For backwards compatibility all truthy values that aren't otherwise
# recognized are considered true
for include_plotlyjs in [True, 34, 'non-empty-str']:
html = self._read_html(plotly.offline.plot(
fig,
include_plotlyjs=include_plotlyjs,
output_type='file',
auto_open=False))
self.assertIn(PLOTLYJS, html)
self.assertNotIn(cdn_script, html)
self.assertNotIn(directory_script, html)

def test_including_plotlyjs_truthy_div(self):
# For backwards compatibility all truthy values that aren't otherwise
# recognized are considered true
for include_plotlyjs in [True, 34, 'non-empty-str']:
html = plotly.offline.plot(
fig,
include_plotlyjs=include_plotlyjs,
output_type='div')
self.assertIn(PLOTLYJS, html)
self.assertNotIn(cdn_script, html)
self.assertNotIn(directory_script, html)

def test_including_plotlyjs_false_html(self):
# For backwards compatibility all truthy values that aren't otherwise
# recognized are considered true
for include_plotlyjs in [False, 0, '']:
html = self._read_html(plotly.offline.plot(
fig,
include_plotlyjs=include_plotlyjs,
output_type='file',
auto_open=False))
self.assertNotIn(PLOTLYJS, html)
self.assertNotIn(cdn_script, html)
self.assertNotIn(directory_script, html)

def test_including_plotlyjs_false_div(self):
for include_plotlyjs in [False, 0, '']:
html = plotly.offline.plot(
fig,
include_plotlyjs=include_plotlyjs,
output_type='div')
self.assertNotIn(PLOTLYJS, html)
self.assertNotIn(cdn_script, html)
self.assertNotIn(directory_script, html)

def test_including_plotlyjs_cdn_html(self):
for include_plotlyjs in ['cdn', 'CDN', 'Cdn']:
html = self._read_html(plotly.offline.plot(
fig,
include_plotlyjs=include_plotlyjs,
output_type='file',
auto_open=False))
self.assertNotIn(PLOTLYJS, html)
self.assertIn(cdn_script, html)
self.assertNotIn(directory_script, html)

def test_including_plotlyjs_cdn_div(self):
for include_plotlyjs in ['cdn', 'CDN', 'Cdn']:
html = plotly.offline.plot(
fig,
include_plotlyjs=include_plotlyjs,
output_type='div')
self.assertNotIn(PLOTLYJS, html)
self.assertIn(cdn_script, html)
self.assertNotIn(directory_script, html)

def test_including_plotlyjs_directory_html(self):
self.assertFalse(os.path.exists('plotly.min.js'))

for include_plotlyjs in ['directory', 'Directory', 'DIRECTORY']:
html = self._read_html(plotly.offline.plot(
fig,
include_plotlyjs=include_plotlyjs,
auto_open=False))
self.assertNotIn(PLOTLYJS, html)
self.assertNotIn(cdn_script, html)
self.assertIn(directory_script, html)

# plot creates plotly.min.js in the output directory
self.assertTrue(os.path.exists('plotly.min.js'))
with open('plotly.min.js', 'r') as f:
self.assertEqual(f.read(), PLOTLYJS)

def test_including_plotlyjs_directory_div(self):
self.assertFalse(os.path.exists('plotly.min.js'))

for include_plotlyjs in ['directory', 'Directory', 'DIRECTORY']:
html = plotly.offline.plot(
fig,
include_plotlyjs=include_plotlyjs,
output_type='div',
auto_open=False)

self.assertNotIn(PLOTLYJS, html)
self.assertNotIn(cdn_script, html)
self.assertIn(directory_script, html)

# plot does NOT create a plotly.min.js file in the output directory
# when output_type is div
self.assertFalse(os.path.exists('plotly.min.js'))

def test_including_plotlyjs_path_html(self):
for include_plotlyjs in [
('https://cdnjs.cloudflare.com/ajax/libs/plotly.js/1.40.1/'
'plotly.min.js'),
'subpath/to/plotly.min.js',
'something.js']:

html = self._read_html(plotly.offline.plot(
fig,
include_plotlyjs=include_plotlyjs,
output_type='file',
auto_open=False))
self.assertNotIn(PLOTLYJS, html)
self.assertNotIn(cdn_script, html)
self.assertNotIn(directory_script, html)
self.assertIn(include_plotlyjs, html)

def test_including_plotlyjs_path_div(self):
for include_plotlyjs in [
('https://cdnjs.cloudflare.com/ajax/libs/plotly.js/1.40.1/'
'plotly.min.js'),
'subpath/to/plotly.min.js',
'something.js']:

html = plotly.offline.plot(
fig,
include_plotlyjs=include_plotlyjs,
output_type='div')
self.assertNotIn(PLOTLYJS, html)
self.assertNotIn(cdn_script, html)
self.assertNotIn(directory_script, html)
self.assertIn(include_plotlyjs, html)

def test_div_output(self):
html = plotly.offline.plot(fig, output_type='div', auto_open=False)
Expand All @@ -77,10 +224,7 @@ def test_div_output(self):
self.assertTrue(html.startswith('<div>') and html.endswith('</div>'))

def test_autoresizing(self):
resize_code_strings = [
'window.addEventListener("resize", ',
'Plotly.Plots.resize('
]

# 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:
Expand All @@ -96,6 +240,28 @@ def test_autoresizing(self):
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!',
editable=True)
Expand Down