Code Monkey home page Code Monkey logo

Comments (12)

ned2 avatar ned2 commented on May 16, 2024 10

I've also been trying to work out the best way to encapsulate callbacks with corresponding component trees to make reusable components. I came up with the following class-based solution.

class BaseBlock:
    def __init__(self, app=None):
        self.app = app

        if self.app is not None and hasattr(self, 'callbacks'):
            self.callbacks(self.app)

class MyBlock(BaseBlock):
    layout = html.Div('layout for this "block".')

    def callbacks(self, app):

        @app.callback(Output('foo', 'figure'), [Input('bar')])
        def do_things(bar):
            return SOME_DATA

        @app.callback(Output('baz', 'figure'), [Input('boop')])
        def do_things(boop):
            return OTHER_DATA

# creating a new MyBlock will register all callbacks
block = MyBlock(app=app)

# now insert this component into the app's layout 
app.layout['slot'] = block.layout

The idea being that supplying a 'callback' method is optional if the layout doesn't require any callbacks. I think the main advantage here is if you also associated some data and corresponding logic with the subclass, otherwise the function-based approach that @chriddyp suggests would do the job fine.

from dash.

jamestwebber avatar jamestwebber commented on May 16, 2024 4

So is this a working solution right now or in need of a PR? We are having similar questions as we try to refactor an increasingly-complex Dash app into readable components. How to wire up the callbacks and have everything still work is a bit confusing to figure out.

This issue dovetails with #38 in that Flask apps are already pretty modular. If we could have a structure like:

main_site/
     __init__.py  <- main site code, flask initialization
    config.py
    manage.py <- CLI tools for server management
    dash_app/
         __init__.py
        plotting_code.py <- callbacks
        layout_code.py <- Div tree
    some_other_app/
        ...who knows

That would be very nice!

from dash.

chriddyp avatar chriddyp commented on May 16, 2024 3

@jamestwebber - another way is to organize modular sets of layout and their callbacks in the same file and create a central app.py file that imports the app to bind the callbacks.

Here's an example that's used for multiple pages, but it could just as well be a single page:
https://community.plot.ly/t/multiple-dashboards/4656/16?u=chriddyp

Repeating from that link:

File structure:

- app.py
- index.py
- apps
   |-- __init__.py
   |-- app1.py
   |-- app2.py

app.py

import dash

app = dash.Dash()
server = app.server
app.config.supress_callback_exceptions = True

apps/app1.py

from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc

from app import app

layout = html.Div([
    html.H3('App 1'),
    dcc.Dropdown(
        id='app-1-dropdown',
        options=[
            {'label': 'App 1 - {}'.format(i), 'value': i} for i in [
                'NYC', 'MTL', 'LA'
            ]
        ]
    ),
    html.Div(id='app-1-display-value'),
    dcc.Link('Go to App 2', href='/apps/app2')
])


@app.callback(
    Output('app-1-display-value', 'children'),
    [Input('app-1-dropdown', 'value')])
def display_value(value):
    return 'You have selected "{}"'.format(value)

And similarly for other apps


index.py
index.py could load different apps on different urls like this:

from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html

from app import app
from apps import app1, app2


app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content')
])


@app.callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    if pathname == '/apps/app1':
         return app1.layout
    elif pathname == '/apps/app2':
         return app2.layout
    else:
        return '404'

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

or it could just organize all of the layouts into one page like this:

from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html

from app import app
from apps import app1, app2


app.layout = html.Div([
     html.H1('My App'),
     app1.layout,
     app2.layout
])


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

from dash.

olgabot avatar olgabot commented on May 16, 2024 1

What if the apps need to share data between each other? The flat files seem non-ideal for sharing data, compared to creating objects. For example, our layouts require the underlying data to create dropdowns, etc. Here is a gist using @ned2's suggestion but this gives me an "Error loading layout"

from dash.

chriddyp avatar chriddyp commented on May 16, 2024

Great question and great idea here. The class-oriented approach looks good. Here are a couple other ideas to contribute:

1 - Reusable App Templates

I see this being really useful for teams that are working in Dash and want their apps to have the same look and feel. In this case, there might note be any callbacks, you're just returning a component tree.

There are two ways to parameterize the layout. The first would be to expose all of the children as input arguments:

def two_column_layout(left_child, right_child):
    return html.Div([
        html.Div(left_child, className="six columns"),            
        html.Div(right_child, className="six columns")
    ], className="row")
from my_custom_layouts import two_column_layout
[...]

app.layout = two_column_layout(dcc.Graph(...), dcc.Graph(...))

The second would be to set IDs in the component and require the user to index them:

def two_column_layout(left_child, right_child):
    return html.Div([
        html.Div(id='left-child'),            
        html.Div(id='right-child')
    ], className="row")
from my_custom_layouts import two_column_layout
[...]

app.layout = two_column_layout()
app.layout['left-child'] = dcc.Graph(...)
app.layout['right-child'] = dcc.Graph(...)

Note how indexing in Dash component trees works: the keys are IDs inside the tree. Here's a quick Jupyter notebook exploring the dash component tree a little bit further: https://plot.ly/~chris/18410

Component Trees with Callbacks

In the Plotly-Python graphing library we came up with this idea called "FigureFactory" which "generates" commonly used figures for you. I could see similar things being applied here. For example:

def enhanced_graph(df, x_column, y_column, app):
    graph_id = 'enhanced-graph-' + randomstring()
    div_id = 'enhanced-div-' + randomstring()
    figure = {'data': [{'x': df[x_column], 'y': df[y_column]}]}
    component = html.div(
        html.Div(id=div_id),
        dcc.Graph(id=id, figure=figure)
    )
    app.callback(Output(id, 'figure'), [Input(id, 'hoverData')])
    def update_div_with_hover_info(hoverData):
       return return hoverData['points']['y']

and a user would use this like:

import custom_components

[...]

app.layout = html.Div(
    [...],
    enhanced_graph(df, 'x', 'y', app)
)

from dash.

pyenthu avatar pyenthu commented on May 16, 2024

Chris,

This works. I tried it already. Slightly different.
However I have gone a step further.
What I want to be able to do is to assign callbacks real-time. The divisions or modules we develop should have interactivity. I want to make an app which allows you to customize properties real time.
For this I need to be able to assign the callbacks real time. It seems this is not possible within a class.
Code below.
https://github.com/pyenthu/DASH_TRIAL/blob/master/dash_callback.py

The callbacks assigned outside the event triggered (button click) function are working
new_div_0 = custom_div(app, 'new_div_0')
new_div_1 = custom_div(app, 'new_div_1')
new_div_2 = custom_div(app, 'new_div_2')

new_div_1.assign_callback()
new_div_2.assign_callback()

However the callback assigned on clicking another button (after all the components have been loaded) for some reason are not working. The new component is created by the callback for the width button does not work.
This callback does not get assigned. I was trying to capture the event and trying to run an unrelated function without an output. The output gets assigned but the callback does not get assigned for some reason. Actually I believe the callback does get assigned (since I get an error for it being assigned already) but it does not change the width as per the other two divisions.

@app.callback( Output('done', 'children'), events=[Event('add-div', 'click')] ) def add_new_div(): new_div_0.assign_callback() return 'DONE CLICK'
It seems maybe I am stretching the applicability of your work but if this would be possible then you can really extend the scope to be a leading pythonic way of building browser gui. I mean I could eliminate 80% of javascript code.

from dash.

pyenthu avatar pyenthu commented on May 16, 2024

Chris,
It seems that if I add the new component at the first time during assigning the layout it works.
But what I want to be able to do is to add or delete the "enhanced graph". That does not seem to work with the callback function. The internal callback function does not seem to work if an instance of enhanced graph is added at a later stage dynamically.

app.layout = html.Div(
[...],
enhanced_graph(df, 'x', 'y', app)
)

from dash.

pyenthu avatar pyenthu commented on May 16, 2024

Chris, this is what I am trying to achieve. But as part of a class inside the class.
https://www.brython.info/static_doc/en/cookbook/bind_unbind.html

from dash.

olgabot avatar olgabot commented on May 16, 2024

BTW, the "Error loading layout" browser console gives me:

screen shot 2017-08-25 at 3 23 23 pm

For the above example, while this is only one panel, but we have many divs and panels that we would like to modularize so it's easier to find what's getting run where.

from dash.

adm78 avatar adm78 commented on May 16, 2024

I've managed to use @ned2 's method to successfully organize modular sets of layouts and their callbacks. The only thing that is missing is the css. It would be good if the layout, callbacks, css and potentially js for a particular module could be kept together, ensuring that the module could be used across multiple dash apps with a consistant appearance. At the moment, I have to copy the module's css files into the 'assets' directory of each app that uses that module (following https://dash.plot.ly/external-resources). Any ideas on how this could be more elegantly handled?

from dash.

dantp-ai avatar dantp-ai commented on May 16, 2024

@ned2 Your encapsulation is great, but how can you use MyBlock twice?

You'd have to pass an unique id for the callbacks, no?

from dash.

ned2 avatar ned2 commented on May 16, 2024

That's true. You could enhance the class to take a prefix argument that namesapces the IDs:

class BaseBlock:
    def __init__(self, app=None, prefix=''):
        self.app = app
        self.prefix = prefix

        if self.app is not None and hasattr(self, 'callbacks'):
            self.callbacks(self.app)

And then apply that to all IDs in callback definitions, eg: Output(f'{self.prefix}foo', 'figure') and call the class like:

block = MyBlock(app, prefix='page1_')

I'm hoping that Dash might get builtin support at some point for namespacing IDs at some point, as manually prefixing every ID is a little cumbersome.

from dash.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.