Skip to content

mpl_to_plotly offline #386

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 7 commits into from
Jan 17, 2016
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 4 additions & 1 deletion plotly/offline/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
download_plotlyjs,
init_notebook_mode,
iplot,
plot
iplot_mpl,
plot,
plot_mpl,
plotly_takeover
Copy link
Member

Choose a reason for hiding this comment

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

maybe plotly_mpl_takeover?

Copy link
Contributor

Choose a reason for hiding this comment

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

Any of these perhaps?

  • take_mpl_offline
  • make_mpl_offline
  • convert_mpl_to_offline

Neither plotly_takeover nor plotly_mpl_takeover makes all that much sense to me on the first pass.

Copy link
Member Author

Choose a reason for hiding this comment

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

(tbh I agree-- in case this reference got lost from the original pr the idea came from this function )

maybe enable_mpl_offline ?

)
142 changes: 139 additions & 3 deletions plotly/offline/offline.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@
from plotly.exceptions import PlotlyError


try:
import IPython
_ipython_imported = True
except ImportError:
_ipython_imported = False

try:
import matplotlib
_matplotlib_imported = True
except ImportError:
_matplotlib_imported = False


__PLOTLY_OFFLINE_INITIALIZED = False


Expand Down Expand Up @@ -191,9 +204,7 @@ def plot(figure_or_data,
from plotly.offline import plot
import plotly.graph_objs as go

plot([
go.Scatter(x=[1, 2, 3], y=[3, 2 6])
], filename='my-graph.html')
plot([go.Scatter(x=[1, 2, 3], y=[3, 2, 6])], filename='my-graph.html')
```
More examples below.

Expand Down Expand Up @@ -298,3 +309,128 @@ def plot(figure_or_data,
])
else:
return plot_html


def plot_mpl(mpl_fig, resize=False, strip_style=False,
verbose=False, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

no more **kwargs, they kill us down the line! Because we'll add a new keyword argument to plot_mpl in a future version but then silently fail on this version when the person supplies that (unsupported) keywoard argument

Copy link
Member

Choose a reason for hiding this comment

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

let's just be explicit with the rest of the kwargs like filename, output_format, etc

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

'''
Copy link
Contributor

Choose a reason for hiding this comment

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

🐄 I think we typically use """ not, '''. https://www.python.org/dev/peps/pep-0257/

Convert a matplotlib figure to a plotly graph locally as an HTML document
or string
Copy link
Contributor

Choose a reason for hiding this comment

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

🐄 Can we make the one-liner a one-liner 😸

Convert a matplotlib figure to a Plotly graph stored locally as HTML.


For more information on converting matplotlib visualizations to plotly
graphs, call help(plotly.tools.mpl_to_plotly)

For more information on creating plotly charts locally as an HTML document
or string, call help(plotly.offline.plot)

:param (matplotlib figure) mpl_fig: matplotlib figure to convert to a
Copy link
Contributor

Choose a reason for hiding this comment

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

🎎 matplotlib figure --> matplotlib.figure

plotly graph
Copy link
Contributor

Choose a reason for hiding this comment

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

🐄 super picky, but I think we should use Plotly when we talk about a Plotly graph and plotly when we talk about this module. Does that seem right? This is obviously not blocking.

:param (bool) resize: Default = False
:param (bool) strip_style: Default = False
:param (bool) verbose: Default = False
:param kwargs: kwargs passed through `plotly.offline.plot`.
For more information on valid kwargs call `help(plotly.offline.plot)`
:return (None|string): if `output_type` is 'file' (default), then the graph
Copy link
Contributor

Choose a reason for hiding this comment

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

🎎 I think this always returns a str, right? It should be :return (str):?

https://github.com/plotly/plotly.py/blob/master/plotly/offline/offline.py#L262-L300

is saved as a standalone HTML file and `plot_mpl` returns None.
Copy link
Contributor

Choose a reason for hiding this comment

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

See above for the returns None statement.

If `output_type` is 'div', then `plot` returns a string that contains
the HTML <div> that contains the graph and the script to generate the
graph. For more information about `output_type` call
`help(plotly.offline.plot)`

Example:
```
from plotly.offline import init_notebook_mode, plot_mpl
import matplotlib.pyplot as plt

init_notebook_mode()

fig = plt.figure()
x = [10, 15, 20, 25, 30]
y = [100, 250, 200, 150, 300]
plt.plot(x, y, "o")

plot_mpl(fig)
```
'''
plotly_plot = tools.mpl_to_plotly(mpl_fig, resize, strip_style, verbose)
return plot(plotly_plot, **kwargs)


def iplot_mpl(mpl_fig, resize=False, strip_style=False,
verbose=False, **kwargs):
'''
Copy link
Contributor

Choose a reason for hiding this comment

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

--> """

Convert a matplotlib figure to a plotly graph and plot inside an IPython
notebook without connecting to an external server.

To save the chart to Plotly Cloud or Plotly Enterprise, use
`plotly.tools.mpl_to_plotly`.
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

yes-thanks!


For more information on converting matplotlib visualizations to plotly
graphs call `help(plotly.tools.mpl_to_plotly)`

For more information on plotting plotly charts offline in an Ipython
notebook call `help(plotly.offline.iplot)`

:param (matplotlib figure) mpl_fig: matplotlib figure to convert to a
plotly graph
:param (bool) resize: Default = False
:param (bool) strip_style: Default = False
:param (bool) verbose: Default = False
:param kwargs: kwargs passed through `plotly.offline.iplot`.
For more information on valid kwargs call `help(plotly.offline.iplot)`
:return: draws converted plotly figure in Ipython notebook

Example:
```
from plotly.offline import init_notebook_mode, iplot_mpl
import matplotlib.pyplot as plt

init_notebook_mode()

fig = plt.figure()
x = [10, 15, 20, 25, 30]
y = [100, 250, 200, 150, 300]
plt.plot(x, y, "o")

iplot_mpl(fig)
```
'''
plotly_plot = tools.mpl_to_plotly(mpl_fig, resize, strip_style, verbose)
return iplot(plotly_plot, **kwargs)


def plotly_takeover(**kwargs):
'''
Enable the automatic matplotlib to plotly conversion and display
of figures in an IPython Notebook.
Copy link
Contributor

Choose a reason for hiding this comment

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

--> Convert mpl plots to locally hosted HTML documents?

--> Convert mpl plots to locally hosted Plotly plots?


This function should be used with the inline Matplotlib backend
Copy link
Contributor

Choose a reason for hiding this comment

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

🐄 I think Matplotlib is lowercased, almost always, right?

that ships with IPython that can be enabled with `%pylab inline`
or `%matplotlib inline`. This works by adding an HTML formatter
for Figure objects; the existing SVG/PNG formatters will remain
enabled.

(idea taken from `mpld3._display.enable_notebook`)

Example:
```
from plotly.offline import init_notebook_mode, plotly_takeover
import matplotlib.pyplot as plt

init_notebook_mode
Copy link
Contributor

Choose a reason for hiding this comment

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

init_notebook_mode()?

plotly_takeover()

fig = plt.figure()
x = [10, 15, 20, 25, 30]
y = [100, 250, 200, 150, 300]
plt.plot(x, y, "o")
fig
Copy link
Contributor

Choose a reason for hiding this comment

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

What's this fig statement doing here? Is this required?

Copy link
Member Author

Choose a reason for hiding this comment

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

as far as I could tell-yes (there weren't any use examples in the original pr)

```
'''
if not __PLOTLY_OFFLINE_INITIALIZED:
init_notebook_mode()
ip = IPython.core.getipython.get_ipython()
formatter = ip.display_formatter.formatters['text/html']
formatter.for_type(matplotlib.figure.Figure,
lambda fig, kwds=kwargs: iplot_mpl(fig, **kwds))
Copy link
Contributor

Choose a reason for hiding this comment

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

so sweet!


76 changes: 76 additions & 0 deletions plotly/tests/test_optional/test_offline/test_offline.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@

import plotly

# TODO: matplotlib-build-wip
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we make an issue for this TODO so we can track it in GH and tag this bit of code with the issue number?

Copy link
Member Author

Choose a reason for hiding this comment

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

👍 yes - on it

from plotly.tools import _matplotlylib_imported
if _matplotlylib_imported:
import matplotlib

# Force matplotlib to not use any Xwindows backend.
matplotlib.use('Agg')
import matplotlib.pyplot as plt

# Generate matplotlib plot for tests
fig = plt.figure()

x = [10, 20, 30]
y = [100, 200, 300]
plt.plot(x, y, "o")

PLOTLYJS = plotly.offline.offline.get_plotlyjs()


class PlotlyOfflineTestCase(TestCase):
def setUp(self):
Expand All @@ -22,3 +40,61 @@ def test_iplot_works_after_you_call_init_notebook_mode(self):
plotly.tools._ipython_imported = True
plotly.offline.init_notebook_mode()
plotly.offline.iplot([{}])

def test_iplot_mpl_works_after_you_call_init_notebook_mode(self):
plotly.tools._ipython_imported = True
plotly.offline.init_notebook_mode()
plotly.offline.iplot_mpl(fig)


class PlotlyOfflineMPLTestCase(TestCase):
def setUp(self):
pass

def _read_html(self, file_url):
""" Read and return the HTML contents from a file_url in the
form e.g. file:///Users/chriddyp/Repos/plotly.py/plotly-temp.html
"""
with open(file_url.replace('file://', '').replace(' ', '')) as f:
return f.read()

def test_default_mpl_plot_generates_expected_html(self):
data_json = ('[{"name": "_line0", "yaxis": "y1", "marker": {"color":' +
' "#0000FF", "opacity": 1, "line": {"color": "#000000",' +
' "width": 0.5}, "symbol": "dot", "size": 6.0}, "mode":' +
' "markers", "xaxis": "x1", "y": [100.0, 200.0, 300.0],' +
' "x": [10.0, 20.0, 30.0], "type": "scatter"}]')
layout_json = ('{"autosize": false, "width": 640, "showlegend": ' +
'false, "xaxis1": {"tickfont": {"size": 12.0}, ' +
'"domain": [0.0, 1.0], "ticks": "inside", "showgrid":' +
' false, "range": [10.0, 30.0], "mirror": "ticks", ' +
'"zeroline": false, "showline": true, "nticks": 5, ' +
'"type": "linear", "anchor": "y1", "side": "bottom"},' +
' "height": 480, "yaxis1": {"tickfont": ' +
'{"size": 12.0}, "domain": [0.0, 1.0], "ticks": ' +
'"inside", "showgrid": false, "range": [100.0, 300.0]' +
', "mirror": "ticks", "zeroline": false, "showline": ' +
'true, "nticks": 5, "type": "linear", "anchor": "x1",' +
' "side": "left"}, "hovermode": "closest", "margin":' +
' {"b": 47, "r": 63, "pad": 0, "t": 47, "l": 80}}')
Copy link
Contributor

Choose a reason for hiding this comment

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

🐄 this is super hard to read and will be hard to edit later. Can we change this to:

data = [],
layout = {}
data_json = json.dumps(data)
layout_json = json.dumps(layout)

(or something like it?)

Copy link
Contributor

Choose a reason for hiding this comment

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

At some point we should create a fixture file that has a largish/complicated plot in it which we can use over and over for testing... You probably don't need to go there now though :)

html = self._read_html(plotly.offline.plot_mpl(fig))

# just make sure a few of the parts are in here
# like PlotlyOfflineTestCase(TestCase) in test_core
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) # layout is in there too
self.assertTrue(PLOTLYJS in 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_mpl(fig, include_plotlyjs=False))
self.assertTrue(PLOTLYJS not in html)

def test_div_output(self):
html = plotly.offline.plot_mpl(fig, output_type='div')

self.assertTrue('<html>' not in html and '</html>' not in html)
self.assertTrue(html.startswith('<div>') and html.endswith('</div>'))