Skip to content

Commit 0cbcce7

Browse files
committed
Add 'cdn' and 'directory' include_plotlyjs options in offline.plot
When 'cdn', the resulting html file/div includes a script tag reference to the plotlyjs cdn. When 'directory', the resulting html file/div includes a script tag reference to a plotly.min.js bundle in the same directory as the html file. If output_type is 'file' then this plotly.min.js bundle is created in the output directory if it doesn't already exist.
1 parent 1494538 commit 0cbcce7

File tree

2 files changed

+172
-29
lines changed

2 files changed

+172
-29
lines changed

plotly/offline/offline.py

+60-24
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import time
1313
import webbrowser
1414

15+
import six
1516
from requests.compat import json as _json
1617

1718
import plotly
@@ -448,10 +449,37 @@ def plot(figure_or_data, show_link=True, link_text='Export to plot.ly',
448449
in a standalone HTML file.
449450
Use 'div' if you are embedding these graphs in an HTML file with
450451
other graphs or HTML markup, like a HTML report or an website.
451-
include_plotlyjs (default=True) -- If True, include the plotly.js
452-
source code in the output file or string.
453-
Set as False if your HTML file already contains a copy of the plotly.js
452+
include_plotlyjs (True | False | 'cdn' | 'directory' - default=True) --
453+
Specifies how the plotly.js library is included in the output html
454+
file or div string.
455+
456+
If True, a script tag containing the plotly.js source code (~3MB)
457+
is included in the output. HTML files generated with this option are
458+
fully self-contained and can be used offline.
459+
460+
If 'cdn', a script tag that references the plotly.js CDN is included
461+
in the output. HTML files generated with this option are about 3MB
462+
smaller than those generated with include_plotlyjs=True, but they
463+
require an active internet connection in order to load the plotly.js
454464
library.
465+
466+
If 'directory', a script tag is included that references an external
467+
plotly.min.js bundle that is assumed to reside in the same
468+
directory as the HTML file. If output_type='file' then the
469+
plotly.min.js bundle is copied into the directory of the resulting
470+
HTML file. If a file named plotly.min.js already exists in the output
471+
directory then this file is left unmodified and no copy is performed.
472+
HTML files generated with this option can be used offline, but they
473+
require a copy of the plotly.min.js bundle in the same directory.
474+
This option is useful when many figures will be saved as HTML files in
475+
the same directory because the plotly.js source code will be included
476+
only once per output directory, rather than once per output file.
477+
478+
If False, no script tag referencing plotly.js is included. This is
479+
useful when output_type='div' and the resulting div string will be
480+
placed inside an HTML document that already loads plotly.js. This
481+
option is not advised when output_type='file' as it will result in
482+
a non-functional html file.
455483
filename (default='temp-plot.html') -- The local filename to save the
456484
outputted chart to. If the filename already exists, it will be
457485
overwritten. This argument only applies if `output_type` is 'file'.
@@ -494,17 +522,25 @@ def plot(figure_or_data, show_link=True, link_text='Export to plot.ly',
494522
if width == '100%' or height == '100%':
495523
resize_script = _build_resize_script(plotdivid)
496524

525+
if isinstance(include_plotlyjs, six.string_types):
526+
include_plotlyjs = include_plotlyjs.lower()
527+
528+
if include_plotlyjs == 'cdn':
529+
plotly_js_script = """\
530+
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>"""
531+
elif include_plotlyjs == 'directory':
532+
plotly_js_script = '<script src="plotly.min.js"></script>'
533+
elif include_plotlyjs:
534+
plotly_js_script = ''.join([
535+
'<script type="text/javascript">',
536+
get_plotlyjs(),
537+
'</script>',
538+
])
539+
else:
540+
plotly_js_script = ''
541+
497542
if output_type == 'file':
498543
with open(filename, 'w') as f:
499-
if include_plotlyjs:
500-
plotly_js_script = ''.join([
501-
'<script type="text/javascript">',
502-
get_plotlyjs(),
503-
'</script>',
504-
])
505-
else:
506-
plotly_js_script = ''
507-
508544
if image:
509545
if image not in __IMAGE_FORMATS:
510546
raise ValueError('The image parameter must be one of the '
@@ -532,26 +568,26 @@ def plot(figure_or_data, show_link=True, link_text='Export to plot.ly',
532568
'</body>',
533569
'</html>']))
534570

571+
# Check if we should copy plotly.min.js to output directory
572+
if include_plotlyjs == 'directory':
573+
bundle_path = os.path.join(
574+
os.path.dirname(filename), 'plotly.min.js')
575+
576+
if not os.path.exists(bundle_path):
577+
with open(bundle_path, 'w') as f:
578+
f.write(get_plotlyjs())
579+
535580
url = 'file://' + os.path.abspath(filename)
536581
if auto_open:
537582
webbrowser.open(url)
538583

539584
return url
540585

541586
elif output_type == 'div':
542-
if include_plotlyjs:
543-
return ''.join([
544-
'<div>',
545-
'<script type="text/javascript">',
546-
get_plotlyjs(),
547-
'</script>',
548-
plot_html,
549-
resize_script,
550-
'</div>',
551-
])
552-
else:
553-
return ''.join([
587+
588+
return ''.join([
554589
'<div>',
590+
plotly_js_script,
555591
plot_html,
556592
resize_script,
557593
'</div>',

plotly/tests/test_core/test_offline/test_offline.py

+112-5
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,20 @@
3030

3131
PLOTLYJS = plotly.offline.offline.get_plotlyjs()
3232

33+
cdn_script = ('<script src="https://cdn.plot.ly/plotly-latest.min.js">'
34+
'</script>')
35+
36+
directory_script = '<script src="plotly.min.js"></script>'
37+
3338

3439
class PlotlyOfflineBaseTestCase(TestCase):
3540
def tearDown(self):
3641
# Some offline tests produce an html file. Make sure we clean up :)
3742
try:
3843
os.remove('temp-plot.html')
44+
# Some tests that produce temp-plot.html]
45+
# also produce plotly.min.js
46+
os.remove('plotly.min.js')
3947
except OSError:
4048
pass
4149

@@ -71,10 +79,109 @@ def test_default_plot_generates_expected_html(self):
7179
# and it's an <html> doc
7280
self.assertTrue(html.startswith('<html>') and html.endswith('</html>'))
7381

74-
def test_including_plotlyjs(self):
75-
html = self._read_html(plotly.offline.plot(fig, include_plotlyjs=False,
76-
auto_open=False))
77-
self.assertNotIn(PLOTLYJS, html)
82+
def test_including_plotlyjs_truthy_html(self):
83+
# For backwards compatibility all truthy values that aren't otherwise
84+
# recognized are considered true
85+
for include_plotlyjs in [True, 34, 'non-empty-str']:
86+
html = self._read_html(plotly.offline.plot(
87+
fig,
88+
include_plotlyjs=include_plotlyjs,
89+
output_type='file',
90+
auto_open=False))
91+
self.assertIn(PLOTLYJS, html)
92+
self.assertNotIn(cdn_script, html)
93+
self.assertNotIn(directory_script, html)
94+
95+
def test_including_plotlyjs_truthy_div(self):
96+
# For backwards compatibility all truthy values that aren't otherwise
97+
# recognized are considered true
98+
for include_plotlyjs in [True, 34, 'non-empty-str']:
99+
html = plotly.offline.plot(
100+
fig,
101+
include_plotlyjs=include_plotlyjs,
102+
output_type='div')
103+
self.assertIn(PLOTLYJS, html)
104+
self.assertNotIn(cdn_script, html)
105+
self.assertNotIn(directory_script, html)
106+
107+
def test_including_plotlyjs_false_html(self):
108+
# For backwards compatibility all truthy values that aren't otherwise
109+
# recognized are considered true
110+
for include_plotlyjs in [False, 0, '']:
111+
html = self._read_html(plotly.offline.plot(
112+
fig,
113+
include_plotlyjs=include_plotlyjs,
114+
output_type='file',
115+
auto_open=False))
116+
self.assertNotIn(PLOTLYJS, html)
117+
self.assertNotIn(cdn_script, html)
118+
self.assertNotIn(directory_script, html)
119+
120+
def test_including_plotlyjs_false_div(self):
121+
for include_plotlyjs in [False, 0, '']:
122+
html = plotly.offline.plot(
123+
fig,
124+
include_plotlyjs=include_plotlyjs,
125+
output_type='div')
126+
self.assertNotIn(PLOTLYJS, html)
127+
self.assertNotIn(cdn_script, html)
128+
self.assertNotIn(directory_script, html)
129+
130+
def test_including_plotlyjs_cdn_html(self):
131+
for include_plotlyjs in ['cdn', 'CDN', 'Cdn']:
132+
html = self._read_html(plotly.offline.plot(
133+
fig,
134+
include_plotlyjs=include_plotlyjs,
135+
output_type='file',
136+
auto_open=False))
137+
self.assertNotIn(PLOTLYJS, html)
138+
self.assertIn(cdn_script, html)
139+
self.assertNotIn(directory_script, html)
140+
141+
def test_including_plotlyjs_cdn_div(self):
142+
for include_plotlyjs in ['cdn', 'CDN', 'Cdn']:
143+
html = plotly.offline.plot(
144+
fig,
145+
include_plotlyjs=include_plotlyjs,
146+
output_type='div')
147+
self.assertNotIn(PLOTLYJS, html)
148+
self.assertIn(cdn_script, html)
149+
self.assertNotIn(directory_script, html)
150+
151+
def test_including_plotlyjs_directory_html(self):
152+
self.assertFalse(os.path.exists('plotly.min.js'))
153+
154+
for include_plotlyjs in ['directory', 'Directory', 'DIRECTORY']:
155+
html = self._read_html(plotly.offline.plot(
156+
fig,
157+
include_plotlyjs=include_plotlyjs,
158+
auto_open=False))
159+
self.assertNotIn(PLOTLYJS, html)
160+
self.assertNotIn(cdn_script, html)
161+
self.assertIn(directory_script, html)
162+
163+
# plot creates plotly.min.js in the output directory
164+
self.assertTrue(os.path.exists('plotly.min.js'))
165+
with open('plotly.min.js', 'r') as f:
166+
self.assertEqual(f.read(), PLOTLYJS)
167+
168+
def test_including_plotlyjs_directory_div(self):
169+
self.assertFalse(os.path.exists('plotly.min.js'))
170+
171+
for include_plotlyjs in ['directory', 'Directory', 'DIRECTORY']:
172+
html = plotly.offline.plot(
173+
fig,
174+
include_plotlyjs=include_plotlyjs,
175+
output_type='div',
176+
auto_open=False)
177+
178+
self.assertNotIn(PLOTLYJS, html)
179+
self.assertNotIn(cdn_script, html)
180+
self.assertIn(directory_script, html)
181+
182+
# plot does NOT create a plotly.min.js file in the output directory
183+
# when output_type is div
184+
self.assertFalse(os.path.exists('plotly.min.js'))
78185

79186
def test_div_output(self):
80187
html = plotly.offline.plot(fig, output_type='div', auto_open=False)
@@ -103,7 +210,7 @@ def test_autoresizing(self):
103210
def test_autoresizing_div(self):
104211

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

0 commit comments

Comments
 (0)