plotnine icon indicating copy to clipboard operation
plotnine copied to clipboard

Plotnine export of interactive plots like Plotly?

Open heoa opened this issue 6 years ago • 10 comments

In R, Plotly has added ggplot2 bindings to export to interactive plotly objects, more here. In Python, I want to use ggplot2 -style plotting so I chose Plotnine but I cannot find a way to generate interactive Plotly plots from Plotnine.

In Python, how can I generate interactive plots in Plotnine (interactive like in Plotly so easy to export to HTML for visual investigation)?

heoa avatar Feb 18 '19 07:02 heoa

I believe that is not supported in plotnine. You can of course asked the plotly people to add a feature like that. I guess it would be a cool addition. Meanwhile you might want to have a look at altair, which also has a grammar of graphics approach and directly renders to interactive HTML plots.

JarnoRFB avatar Mar 01 '19 13:03 JarnoRFB

Just discovered that plotly even supports conversion from matplotlib figures to plotly objects, see https://plot.ly/matplotlib/getting-started/. However, that does not seem to work with the figures created by plotnine.

JarnoRFB avatar Mar 01 '19 14:03 JarnoRFB

... However, that does not seem to work with the figures created by plotnine.

I have not used plotly but I would have expected it to work (may be not perfectly) with figures created in plotnine, since it is all matplotlib.

has2k1 avatar Mar 01 '19 22:03 has2k1

FWIW - here are examples of a working matplotlib graph and a failing plotnine graph. I do get a graph but no data. The key message appears to be:

Dang! That path collection is out of this world. I totally don't know what to do with it yet! Plotly can only import path collections linked to 'data' coordinates

The working matplotlib graph:

import dash_core_components as dcc
import dash_html_components as html
from plotly.tools import mpl_to_plotly

import matplotlib.pyplot as plt
import matplotlib.mlab as mlab
import numpy as np

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

n = 50
x, y, z, s, ew = np.random.rand(5, n)
c, ec = np.random.rand(2, n, 4)
area_scale, width_scale = 500, 5

fig, ax = plt.subplots()
sc = ax.scatter(x, y, c=c,
                s=np.square(s)*area_scale,
                edgecolor=ec,
                linewidth=ew*width_scale)
ax.grid()

plotly_fig = mpl_to_plotly(plt.gcf())
graph = dcc.Graph(id='myGraph', figure=plotly_fig)

app.layout = html.Div([graph])

if __name__ == '__main__':
    app.run_server(debug=True)

The failing plotnine graph:

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd

from plotly.tools import mpl_to_plotly
from plotnine import *

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

# plotnine example from gallery
# https://plotnine.readthedocs.io/en/stable/generated/plotnine.geoms.geom_col.html#two-variable-bar-plot

df = pd.DataFrame({
    'variable': ['gender', 'gender', 'age', 'age', 'age', 'income', 'income', 'income', 'income'],
    'category': ['Female', 'Male', '1-24', '25-54', '55+', 'Lo', 'Lo-Med', 'Med', 'High'],
    'value': [60, 40, 50, 30, 20, 10, 25, 25, 40],
})
df['variable'] = pd.Categorical(df['variable'], categories=['gender', 'age', 'income'])

graph = ggplot(df, aes(x='variable', y='value', fill='category')) + geom_col()

# get matplotlib figure

fig = graph.draw()

# dash graph

plotly_fig = mpl_to_plotly(fig)
graph = dcc.Graph(id='myGraph', figure=plotly_fig)
app.layout = html.Div([graph])

if __name__ == '__main__':
    app.run_server(debug=True)

This is the error from running the dash app:

/home/mneilly/PycharmProjects/plotly/venv/bin/python /home/mneilly/PycharmProjects/plotly/plotlyplotnineexample.py
/home/mneilly/PycharmProjects/plotly/venv/lib/python3.6/site-packages/plotly/matplotlylib/renderer.py:451: UserWarning:

Dang! That path collection is out of this world. I totally don't know what to do with it yet! Plotly can only import path collections linked to 'data' coordinates

Running on http://127.0.0.1:8050/
Debugger PIN: 573-582-988
 * Serving Flask app "plotlyplotnineexample" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: on
Running on http://127.0.0.1:8050/
Debugger PIN: 075-361-477
/home/mneilly/PycharmProjects/plotly/venv/lib/python3.6/site-packages/plotly/matplotlylib/renderer.py:451: UserWarning:

Dang! That path collection is out of this world. I totally don't know what to do with it yet! Plotly can only import path collections linked to 'data' coordinates

Which results in a blank graph:

image

mneilly avatar Mar 15 '19 00:03 mneilly

Actually, this example appears to work:

import dash
import dash_core_components as dcc
import dash_html_components as html

from plotnine import *
from plotnine.data import *
from plotly.tools import mpl_to_plotly

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

p = ggplot(aes(x='displ', y='cty'), mpg)
p += geom_point()

# get matplotlib figure

fig = p.draw()

# dash graph

plotly_fig = mpl_to_plotly(fig)
graph = dcc.Graph(id='myGraph', figure=plotly_fig)
app.layout = html.Div([graph])

if __name__ == '__main__':
    app.run_server(debug=True)

image

mneilly avatar Mar 15 '19 00:03 mneilly

@mneilly-et, thanks for that link. Then I think best solution would be to have Plotly as a separate backend. It would be a lot of work, but it is worth thinking about; including the non-technical issues like copyright and licensing.

has2k1 avatar May 21 '20 17:05 has2k1

@has2k1 it would be a really awesome feature for plotnine, but for sure there is a lot of work involved

GitHunter0 avatar Dec 04 '20 02:12 GitHunter0

FYI you can easily export plotnine to interactive HTML plots using mpld3.

krassowski avatar Dec 18 '20 01:12 krassowski

@krassowski have you tried rendering with mpld3 in dash? I have attempted the following (within a callback to fill a div child target):

@app.callback(Output('graph1','children'), [Input("tenor1", "value")])
def update_graph(value):
    Tbase = pd.to_datetime("2010-1-1")
    dates = [Tbase + pd.DateOffset(days=10*i) for i in range(0,230)]
    s1 = pd.Series(np.random.normal(0.2, 1.0, 230)).cumsum()
    s2 = pd.Series(np.random.normal(0.0, 1.0, 230)).cumsum()

    df = pd.concat([
        pd.DataFrame({'date': dates, 'value': s1, 'what': 'strategy'}),
        pd.DataFrame({'date': dates, 'value': s2, 'what': 'spy'}),
    ])

    v = (ggplot() +
         geom_line(aes(x='date', y='value', color='what'), data=df))

    # use mpld3 to render the graph to html
    fhtml = fig_to_html(v.draw())
    return dash_dangerously_set_inner_html.DangerouslySetInnerHTML(fhtml)

Where the target is just:

html.Div(id='graph1')

I can see that the

tr8dr avatar Jan 18 '21 00:01 tr8dr