Repository: plotly/plotly.py
Pull request #714 (2017-03-16 12:57:08)

Make offline.plot/iplot accept a plot config dict
Make `plotly.offline.plot()` (`iplot()`) accept a `config` dictionary so the various [configuration options](https://plot.ly/javascript/configuration-options/) can be passed. Includes unit tests.

Addresses https://community.plot.ly/t/removing-hover-toolbar-and-other-configuration-options-with-the-python-api/374.

Also took liberty to slightly improve surrounding `offline` tests.

If you find the proposed feature something acceptable, I'd be happy to also amend the docs.

Kully: @kernc taking a look right now
Kully: @kernc I've reviewed and just have a couple suggestions for improving clarity of code. It's a 💃 from me after that.

Patch:
diff --git a/plotly/offline/offline.py b/plotly/offline/offline.py
index 09985a1a0..a390fcb24 100644
--- a/plotly/offline/offline.py
+++ b/plotly/offline/offline.py
@@ -275,7 +275,7 @@ def _plot_html(figure_or_data, config, validate, default_width,
 
 def iplot(figure_or_data, show_link=True, link_text='Export to plot.ly',
           validate=True, image=None, filename='plot_image', image_width=800,
-          image_height=600):
+          image_height=600, config=None):
     """
     Draw plotly graphs inside an IPython or Jupyter notebook without
     connecting to an external server.
@@ -308,6 +308,9 @@ def iplot(figure_or_data, show_link=True, link_text='Export to plot.ly',
         will be saved to. The extension should not be included.
     image_height (default=600) -- Specifies the height of the image in `px`.
     image_width (default=800) -- Specifies the width of the image in `px`.
+    config (default=None) -- Plot view options dictionary. Keyword arguments
+        `show_link` and `link_text` set the associated options in this
+        dictionary if it doesn't contain them already.
 
     Example:
     ```
@@ -331,9 +334,9 @@ def iplot(figure_or_data, show_link=True, link_text='Export to plot.ly',
     if not ipython:
         raise ImportError('`iplot` can only run inside an IPython Notebook.')
 
-    config = {}
-    config['showLink'] = show_link
-    config['linkText'] = link_text
+    config = dict(config) if config else {}
+    config.setdefault('showLink', show_link)
+    config.setdefault('linkText', link_text)
 
     plot_html, plotdivid, width, height = _plot_html(
         figure_or_data, config, validate, '100%', 525, True
@@ -375,7 +378,8 @@ def iplot(figure_or_data, show_link=True, link_text='Export to plot.ly',
 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,
-         image_filename='plot_image', image_width=800, image_height=600):
+         image_filename='plot_image', image_width=800, image_height=600,
+         config=None):
     """ Create a plotly graph locally as an HTML document or string.
 
     Example:
@@ -435,6 +439,9 @@ def plot(figure_or_data, show_link=True, link_text='Export to plot.ly',
         image will be saved to. The extension should not be included.
     image_height (default=600) -- Specifies the height of the image in `px`.
     image_width (default=800) -- Specifies the width of the image in `px`.
+    config (default=None) -- Plot view options dictionary. Keyword arguments
+        `show_link` and `link_text` set the associated options in this
+        dictionary if it doesn't contain them already.
     """
     if output_type not in ['div', 'file']:
         raise ValueError(
@@ -446,9 +453,9 @@ def plot(figure_or_data, show_link=True, link_text='Export to plot.ly',
             "Adding .html to the end of your file.")
         filename += '.html'
 
-    config = {}
-    config['showLink'] = show_link
-    config['linkText'] = link_text
+    config = dict(config) if config else {}
+    config.setdefault('showLink', show_link)
+    config.setdefault('linkText', link_text)
 
     plot_html, plotdivid, width, height = _plot_html(
         figure_or_data, config, validate,


Test patch:
diff --git a/plotly/tests/test_core/test_offline/test_offline.py b/plotly/tests/test_core/test_offline/test_offline.py
index bf5a51b12..b78293731 100644
--- a/plotly/tests/test_core/test_offline/test_offline.py
+++ b/plotly/tests/test_core/test_offline/test_offline.py
@@ -56,22 +56,23 @@ class PlotlyOfflineTestCase(PlotlyOfflineBaseTestCase):
 
         # I don't really want to test the entire script output, so
         # instead just make sure a few of the parts are in here?
-        self.assertTrue('Plotly.newPlot' in html) # plot command is in there
-        self.assertTrue(data_json in html)        # data is in there
-        self.assertTrue(layout_json in html)      # so is layout
-        self.assertTrue(PLOTLYJS in html)         # and the source code
+        self.assertIn('Plotly.newPlot', html)  # plot command is in there
+        self.assertIn(data_json, html)         # data is in there
+        self.assertIn(layout_json, html)       # so is layout
+        self.assertIn(PLOTLYJS, html)          # and the source code
         # 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.assertTrue(PLOTLYJS not in html)
+        self.assertNotIn(PLOTLYJS, html)
 
     def test_div_output(self):
         html = plotly.offline.plot(fig, output_type='div', auto_open=False)
 
-        self.assertTrue('<html>' not in html and '</html>' not in html)
+        self.assertNotIn('<html>', html)
+        self.assertNotIn('</html>', html)
         self.assertTrue(html.startswith('<div>') and html.endswith('</div>'))
 
     def test_autoresizing(self):
@@ -82,17 +83,26 @@ class PlotlyOfflineTestCase(PlotlyOfflineBaseTestCase):
         # 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.assertTrue(resize_code_string in html)
+            self.assertIn(resize_code_string, html)
 
         # If width or height was specified, then we don't resize
-        html = plotly.offline.plot({
+        html = self._read_html(plotly.offline.plot({
             'data': fig['data'],
             'layout': {
                 'width': 500, 'height': 500
             }
-        }, auto_open=False)
+        }, auto_open=False))
         for resize_code_string in resize_code_strings:
-            self.assertTrue(resize_code_string not in html)
+            self.assertNotIn(resize_code_string, html)
+
+    def test_config(self):
+        config = dict(linkText='Plotly rocks!',
+                      editable=True)
+        html = self._read_html(plotly.offline.plot(fig, config=config,
+                                                   auto_open=False))
+        self.assertIn('"linkText": "Plotly rocks!"', html)
+        self.assertIn('"showLink": true', html)
+        self.assertIn('"editable": true', html)
 
 
 class PlotlyOfflineOtherDomainTestCase(PlotlyOfflineBaseTestCase):
@@ -106,7 +116,7 @@ class PlotlyOfflineOtherDomainTestCase(PlotlyOfflineBaseTestCase):
         html = plotly.offline.plot(fig, output_type='div')
 
         # test that 'Export to stage.plot.ly' is in the html
-        self.assertTrue('Export to stage.plot.ly' in html)
+        self.assertIn('Export to stage.plot.ly', html)
 
     def tearDown(self):
         plotly.tools.set_config_file(plotly_domain='https://plot.ly',