plotly / dash

Data Apps & Dashboards for Python. No JavaScript Required.
https://plotly.com/dash
MIT License
21.11k stars 2.04k forks source link

Should Dash be a Flask Extension? #38

Closed mylbp2ps3 closed 3 weeks ago

mylbp2ps3 commented 7 years ago

Extensions such as flask_sqlalchemy or whatever?

hclent commented 7 years ago

Somewhat related, is it possible to use Dash from inside my Flask app?

I've been wanting to use the Python dendrogram+heatmap visualization in my Flask app for a long time. But sadly there is no Javascript dendrogram+heatmap, so I've had no choice but to use something else :'(

mylbp2ps3 commented 7 years ago

Yeah I've been thinking about learning a little bit of Javascript to use one of it's libraries for making graphs. But Dash sounds like it would work fantastic with what I want to do, if it was more like a Flask extension.

chriddyp commented 7 years ago

I've been wanting to use the Python dendrogram+heatmap visualization in my Flask app for a long time.

For this example, you can render these dendrograms as a Graph object since they use the same underlying graphing library:

import plotly.figure_factory as ff
import numpy as np
import dash
import dash_core_components as dcc

X = np.random.rand(15, 15)
dendro = ff.create_dendrogram(X)

app = dash.Dash()
app.layout = dcc.Graph(figure=dendro)

All of the plotly.figure_factory functions return a figure object that can be used directly as the figure attribute in the dcc.Graph

chriddyp commented 7 years ago

Could folks explain a little bit more why they would like Dash as a flask extension? Some leading questions:

Thank you!

mylbp2ps3 commented 7 years ago

It just seems a bit too limiting to have Dash stand on its own. It would create more consistent to make it an extension. Embedding it in an <iframe> just sounds needlessly complicated. I'd want to embed a Dash app in an existing page, a template for Flask, and I'd like to be able to just pass the graph into Flask's render_template function. Passing the flask instance into Dash, doesn't give Dash the same functionality as Flask, it just leaves me with two different objects to render a page with. Although I haven't seen any real examples on what passing the Flask instance into Dash and using them both together would look like.

Forgive me if any of my points are wrong, this is the way I understand Dash to be working from reading a bit into it and experimenting with it a bit.

hclent commented 7 years ago

@chriddyp Firstly, thank you for your reply to my post! I'm very excited to try this out! My remaining concern is, I'm not sure how I will pass data from my Flask app to the Dash app? (More on that below).

Secondly to answer your questions,

  1. Yes, I would like Dash as a Flask extension so I can use Dash in an app I've already written. (For the purpose of getting that dendrogram+heatmap!!)
  2. Perhaps, embedding a Dash app as an <iframe/> is doable in theory, but I think this would really only work if you're not providing buttons/options for a user to update or change the vis. I'm not sure how I could get that Dash app iframe to "talk" to my Flask app, when there's a request to update a visualization per user specifications. (Currently I use Jinja to pass data from my Flask app into the Plotly Javascript vis). I'm not sure what a solution is here, that could be robust enough for a Flask app in production.
  3. Right now my Plotly Javascript visualizations live in <iframe/>s, and I pass data with Jinja to update the visualizations on demand. So I would want to do the same thing, but again... how to pass the data?
  4. I'm interested to try how it currently works. If I would need to change a lot of things about my current Flask app to get Dash to work, it wouldn't be worth it (since I've already built the Flask app). I will give it a try and report back!

Edit - Silly me! I don't use Django. I use Jinja!

havok2063 commented 7 years ago

I would second most of what has been said here. I think it would be great to have Dash integrable into existing Flask apps. My biggest problem with using things like Plotly or Bokeh was that it always seemed like they had to be used as standalone products.

  1. I would like to able to integrate Dash into Flask apps I've already written and are quite developed. I want to be able to use the Plotly and Dash functionality without relying on its internal system. I like Dash instead of standard Plotly because it makes building callbacks and interactivity into the visualizations quite easy.

  2. iframes no longer follow best web practices for modern development. AJAX was designed to replace iframes and iframes makes debugging quite difficult. I don't think iframes fits into the MVC framework of Flask apps either.

  3. Primarily I'd like to add it into existing pages. I have existing pages that I don't want to completely convert. I'd like to drop in a new Dash app that is a mini-vis or two. For new pages, I would prefer to build them with Jinja2 templating for consistency, and standardization, and just push any Dash apps through the render_template Flask function. I can see either pushing data through to the front-end, pushing just the dcc.Graphs into an already built div, or building the complete dash app on the backend and pushing the final html div to the front.

  4. What does pushing the server instance into Dash actually do? I couldn't find any documentation that explains that functionality. This is the only help I can find. https://plot.ly/dash/

fcollman commented 6 years ago

I think what I'm saying is covered by the more general case statements that others have made in this issue, but sometimes simpler more concrete examples make the point more strongly.

I have written a number of flask apps that have complex navigation and information display to get to help the user navigate to a particular dataset. I would like to have a dash app that lets me display that particular dataset to the user after they navigate to it, and give the user a fixed URL to get that dataset back at a later date, or email as a link. In such a case I need the flask endpoint to be able to convey information to the dash app in order to load the appropriate dataset and generate the interactive visualization. I could redo the whole dataset navigation in dash, but it seems overly complex in many instances as the layout's for selection procedures often vary dramatically from level to level, and the dataset selection parameters wouldn't be stored in the URL. Having different flask endpoints point to dash apps would then let you easily parameterize dash visualizations. I understand that 'snapshots' is an enterprise feature now for capturing the state of the visualization, but I'm talking about parameters that naturally live outside the UI visualization loop.

yk-tanigawa commented 6 years ago

I would also love to have the dash-app as a flask extension so that we can pass dataset (path to the data file on the server) as an argument to Flask's render_template to generate interactive plots. I am thinking to create separate pages without using iframe. I am thinking bioinformatics/genomics as an application area, where we have roughly 20,000 datasets for different genes. Although it is possible to have input box (like drop-down) to select a particular dataset, it would be great if we can assign a fixed URL to an interactive plot for a particular dataset.

sladkovm commented 6 years ago

@yk-tanigawa flask extension would be awesome, but until we've gotten there here is how I implement the functionality you describe:

  1. Add location element to the app layout

    # This block adds location identifier to the page
    import dash_html_components as html
    app.layout = html.Div(
        id='app-layout',
        children=[
            dcc.Location(id='url', refresh=False),
            html.Div(id='page-content', className = 'container')
        ]
    )
  2. Use the value of the location element (referenced by id='url') in the callback

    # This call back reads the url of the page and passes it as a pathname to the router function
    @app.callback(Output('page-content', 'children'), [Input('url', 'pathname')])
    def display_page(pathname):
    
    return router(pathname)
  3. The router function does... well... routing, e.g. based on the value of location it renders the page you want - it will get static url with unique id.

    def router(pathname, **kwargs):
    
    # Split pathname into resource and resource_id
    _ = pathname.split('/')
    resource = _[1]
    resource_id = _[2]
    
    if resource == 'event':
    
        return render_event_page(resource_id)
    
    else:
    
        return create_index_page()
  4. To navigate to these urls use:

    import dash_core_components as dcc
    dcc.Link('A link to the static url', href='/event/{}'.format(resource_id))

To see it in action - http://velometria.com/events/marmotte-alpes-2017

Aso1977 commented 6 years ago

Dear @sladkovm I am trying to embed a dash plot into a route in flask using your above code. I linked the two as:

app = Flask()
dapp = Dash(__name__, server=app, url_base_pathname='/plot')

I want to generate the dash layout based on variables defined as flask config parameter, e.g. app.Config['plot_type'] in the dash app:

if app.Config['plot_type'] == 'scatter':
        self.dapp.layout = html.Div(id='plot', children=[
            dcc.Location(id='url', refresh=False),
            dcc.Graph( id='fig',
                figure={'''})
            ])
else:
  ...

And I use the following callback in the flask app:

@dapp.callback(Output('fig', 'children'), [Input('url', 'pathname')])
def display_page(self, pathname):
       return router(pathname) 

I have a template 'plot.html' with jinja2 div of 'plot', which I want to be replaced by the dash graph id=fig. I don't know how to embed the fig. If I replace 'plot' container in the template with the generated dash layout, it doesn't get rendered. It would be great if you point me to the right direction. Thanks, Aso

sladkovm commented 6 years ago

Hi @resenganalytics,

I wrote you a very long answer, but than I've noticed that you might have a different problem.

I have a template 'plot.html' with jinja2 div of 'plot', which I want to be replaced by the dash graph id=fig. I don't know how to embed the fig. If I replace 'plot' container in the template with the generated dash layout, it doesn't get rendered.

For your particular case, apart from the fact that you are populating the non-existent children property of the dcc.Graph (see below), the solution must be in defining dcc.Location element outside of the template scope... but I've never tried it and, in general, I'm not sure how jinja templates should be working with Dash. I would guess these two approaches are fundamentally incompatible.

My long answer:

I would not be able to answer all your questions since some of them (jinja2 etc.) are something I've never worked with, but let me guide you through the thinking process.

  1. You define in the app.layout the element, which you want to populate dynamically based on the value of the location. In your case, this element is dcc.Graph and you refer to it by id='fig'. Location you read from the element dcc.Location and you refer to this element by it's id='url'

In your code this step is:

self.dapp.layout = html.Div(id='plot', children=[
            dcc.Location(id='url', refresh=False),
            dcc.Graph( id='fig',
                figure={'''})
            ])
  1. The actual logic behind dynamically populating the dcc.Graph(id='fig') is handled by the callback:
@dapp.callback(Output('fig', 'children'), [Input('url', 'pathname')])
def display_page(self, pathname):
       return router(pathname) 

What it says in plain english is something like this: "find the element with id='url', read it's property-value 'pathname' and pass it to the element with id='fig' to the property ... in your case it is 'children', but... the dcc.Graph element does not have property 'children'. What it has instead is a property 'figure'.

Your code should look like this:

@dapp.callback(Output('fig', 'figure'), [Input('url', 'pathname')])
def display_page(self, pathname):
       return router(pathname) 
  1. What should the router() function return? Easy - the figure object. Example:
def router(pathname):

    if pathname=='path1':
        figure={
            'data': [
                {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
                {'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},
            ],
            'layout': {
                'title': 'Dash Data Visualization for Path1'
            }
        }

    else:
        figure={
            'data': [
                {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
                {'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},
            ],
            'layout': {
                'title': 'Dash Data Visualization for Any Other Path'
            }
        }

   return figure

That will work if you know how to define different figure objects based on the path. The more general way of doing it however is not to populate the dcc.Graph object dynamically, but return and actual new page object e.g. html.div' - that is what I proposed in my prev. example. in case ofdiv' you populate the 'children' properties.

MLPythoner commented 6 years ago

Hi @sladkovm Would you please give a simple implement demo? I almost unstood what you said but still don't know how to do it. thank you.

sladkovm commented 6 years ago

@MLPythoner

Hi,

I quickly implemented a very simple routing function at the branch: https://github.com/sladkovm/docker-flask-gunicorn-nginx/tree/routing

If you are not familiar with docker, just simply run the app from dash_app folder as dash docs do recommend - in this case the app will run at http://127.0.0.1:8050/

What you expect to see is that the header of the plot will display the pathname of the url. Examples:

http://127.0.0.1:8050/ - / http://127.0.0.1:8050/about - about http://127.0.0.1:8050/blah - blah

For more practical cases, the pathname could be a key to the dict that will return an object you want to display - figure, div etc (see how I refere to the dict figures['fig1'] to fetch the actual figure object).

alien-analyst commented 5 years ago

Could folks explain a little bit more why they would like Dash as a flask extension? Some leading questions:

  • Is it just to use Dash in apps that they have already written?
  • Does embedding a Dash app as an <iframe/> in an existing app work instead?
  • How do you want to embed Dash in your existing application? As a separate page or as a Dash app as part of a particular page?
  • You can pass in your own server instance into the Dash constructor. In which ways would making Dash a flask extension improve the functionality over passing in your own Flask instance? Here's how it currently works:
server = flask.Flask(__name__)ks
app = dash.Dash(__name__, server=server) a few apps 

Thank you!

HI @chriddyp Below are my comments for your points

  1. Yes, we have quite few apps using Django and Flask and many of them have a page which is only for dashboard. if we have dash as a Flask extension then we can quikly port them into dash
  2. Cool idea, i have even tried using iframe within jinja2, but may different graphs or charts as iframes will be cumbersome on a dashboard page which has cross filtering
  3. It can be a separate page as well as a dash app becoz we have stand alone dash application for dashboard alone which we want to integrate as single app
  4. we can use the flask app as server variable but I would want to use jinja templating and user flow from flask app to dashboard and vice-versa
dre2004 commented 4 years ago

I would also like to add my support for having this request implemented. Visualising data is very much a function that most applications have to be able to do now days. Having the ability to build an application in Flask and using Dash to provide some of the visualisation capability would make a lot of sense, it would also mean we could keep our applications almost entirely as Python code (with a mix of some css / html as Jinja templates).

I've had to deal with this requirement a number of different ways over the past couple of years, both of which have felt somewhat hacky.

If I could build the components of my dash app and then just insert them into general layout template using Jinja2 tags that would be ideal.

                <html>
                    <head>
                        <title></title>
                    </head>
                    <body>
                        <nav>
                          {% dash_dropdown('product_dropdown') %}
                        </nav>
                        {% dash_visual('product_graph') %}
                        <footer>
                        </footer>
                    </body>
                </html>

The dash_dropdown being just a standard html_component used in a callback and the dash_visual being the graph where I displayed the data. The rest of the layout and html types of things I would just do in my main application template. This way I would need to try and replicate how my Flask templates look like into another layout which I use just for Dash which I have to maintain separately.

I appreciate this is probably a very hard thing to do but adding another perspective as to why it would be useful.

gvwilson commented 3 weeks ago

closing as the project has gone in other directions