Skip to content

Clientside Callbacks #266

Closed
Closed

Description

@chriddyp

In Dash, changes to component properties in the front-end trigger user-supplied Python functions (@app.callback). This framework is very flexible: users have full control over the Python code that they write in their app.callback functions.

However, app.callback functions are simple. They filter some data or change the color of the chart or display some text. These data transformations, although simple, currently need to be processed entirely over the network in the Python backend. This makes the apps slower than they could be (because of the network delay) and less portable than they could be (because they require a running Python server).

Clientside Callbacks will introduce an interface for describing data transformation relationships between components in JavaScript. These data transformations will happen entirely in the client-side without passing data over the network. The Clientside callbacks Dash framework will enable developers to swap out their python-driven @app.callback functions with javascript-executed data transformations, enabling more performant apps.


As a quick background, here is what Dash Apps currently look like in Python:

The first part describes what the app looks like.
These classes just declaratively describe the names and the props of the React
components that they generate. These objects get serialized as JSON.

image

app.layout = html.Div([

    html.H1('Example'),

    comonents.TextInput(
        id='my-text-input',
        value='Initial value'
    ),
    components.Dropdown(
        id='my-dropdown',
        options=[
            'Option A', 'Option B', 'Option C'
        ]
        value='Option A'
    ),

    components.Graph(
        id='my-graph'
        figure={             # Just a plotly figure
            'data': [...],
            'layout': {...}
        }
    )
])

The second part of dash apps describe the relationship between graphs.
This sets up "sources" ("inputs") and "sinks" ("outputs")
in the Dash front-end. Whenever any of the Input properties change, the
AJAX call gets made.

It's Reactive like a spreadsheet: as inputs change, the new values propagate
down the dependency graph, updating components in the correct order:

1_basic_reactive

@callback(
    Input(
        id='my-dropdown',
        property='value'
    ),
    Input(
        id='my-text-input',
        property='value'
    ),
    Output(
        id='my-graph',
        property='figure'
    )
)
def update_graph(new_dropdown_value, new_text_input_value):
    # compute a new figure based off of the new values of the dropdown
    # or text input
    return {
        'data': [{
            'x': [1, 2, 3],
            'y': ({
                'Option A': [3, 1, 2],
                'Option B': [4, 3, 5],
                'Option C': [1, 2, 4]
            })[dropdown]
        }],
        'layout': {'title': text}
    }

These reactive updates happen entirely server-side through HTTP requests.
(This allows dash developers to do complicated updates or analytics through
their python context).

I think that we could extend this framework to work client-side as well.
Instead of a custom function defining how Inputs ("sources")
update Outputs ("sinks"), we could define a library of transformations components and a syntax for relating input properties to output properties. These transformations could be just be functional React components.

Here are some conceptual examples:
2_input_updates_text

from dash.serverless import Selector as S

layout = Div([
    Input(id='my-input', value='initial-value'),
    H3(children=S('my-input', 'value'))
])

In this example, we're setting the "children" property of the HTML H3 element
to just be the "value" of the "my-input" component. When "value" changes,
the content of the H3 element updates to match the new value.

I'm wrapping the ID and property with S to denote that the string represents a "reactive"
property corresponding to the component with the specified ID and that component's
property. (The actual API might be different, just using s for conceptual purposes.)

Now, consider a "Dataset" component and a "Graph":

3_dataset_graph

layout = Div([
    Dataset(
        id='my-dataset'
        columns={
            'column-1': [1, 2, 3],
            'column-2': [3, 1, 4]
        },
        column_names={
            'column-1': 'My first column',
            'column-2': 'My second column'
        }
    ),

    Graph(
        figure={
            'data': [{
                'x': S('my-dataset', 'column-1'),
                'y': S('my-dataset', 'column-2')
            }]
        }
    )
])

Note that not all components actually get rendered in the DOM. In this case,
the Dataset component isn't actually visible. It's just included as state.
If you wanted to view it as a table, it would look like:

image

layout = Div([
    Dataset(
        id='my-dataset'
        columns={
            'column-1': [1, 2, 3],
            'column-2': [3, 1, 4]
        },
        column_names={
            'column-1': 'My first column',
            'column-2': 'My second column'
        }
    ),

    Table(data='::my-dataset.columns'),

    Graph(
        figure={
            'data': [{
                'x': S('my-dataset', 'columns', 'column-1'),
                'y': S('my-dataset', 'columns', 'column-2')
            }]
        }
    )
])

You can imagine how there might be several datasets and several graphs in one
(like a dashboard or a report).

layout = Div([
    Dataset(id='dataset-1', columns={...}),
    Dataset(id='dataset-2', columns={...}),
    Dataset(id='dataset-3', columns={...}),
    Graph(id='graph-1',
          data=[{'x': S('dataset-1', 'columns', 'column-1'), ...}]
    ),
    Graph(id='graph-2',
          data=[{'x': S('dataset-2', 'columns', 'column-1'), ...}]
    ),
    Graph(id='graph-3',
          data=[{'x': S('dataset-3, 'columns', 'column-1'), ...}]
    )
])

Now, we would also need a library for lightweight data transformations. I'm thinking something like Ramda.

import dash.clientside.transformations as T
import dash.clientside.selector as S

df = pd.DataFrame([
    {'col-1': 1, 'col-2': 5, 'col-3': 10},
    {'col-1': 2, 'col-2': 6, 'col-3': 11},
    {'col-1': 3, 'col-2': 7, 'col-3': 12},
    # ...
])

app.layout = html.Div([

    # "Virtual" component that doesn't render anything
    # to the screen, it just contains the data for other
    # components to reference
    dcc.Dataset(
        id='my-dataset',
        columns=df.columns,
        rows=df.to_dict(),
    ),

    # `Table` renders an actual table to the screen
    dcc.Table(
        rows=S('my-dataset', 'rows'),
        columns=S('my-dataset', 'rows')
    ),

    dcc.Graph(
        figure={

            # T.pluck('col-1', [{'col-1': 1}, {'col-1': 2}]) -> [1, 2]

            'x': T.pluck(
                'col-1',
                S('my-dataset', 'rows')
            ),
            'y': T.pluck(
                'col-2',
                S('my-dataset', 'rows')
            )
        }
    )
])

Or, extending this with dynamic components:

app.layout = html.Div([
    dcc.Dataset(
        id='my-dataset',
        columns=df.columns,
        rows=df.to_dict(),
    ),

    dcc.Dropdown(
        id='my-dropdown',
        options=[
            {'option': i, 'label': i}
            for i in df.columns
        ],
        value=df.columns[0]
    ),

    dcc.Graph(
        figure={
            'x': T.pluck(
                'col-1',
                S('my-dataset', 'rows')
            ),
            'y': T.pluck(
                S('my-dropdown', 'value'),
                S('my-dataset', 'rows')
            )
        }
    )
])

5_simple_declarative_dropdown


Here are some high-level architectural requirements and goals for this work item:

  • The Dash Apps will still be created with Python
  • The clientside callbacks will be executed in JavaScript
  • The existing set of Dash components will be available (with the exception of network-connected components like the mapbox charts)
  • We will introduce a new syntax or language ("data transformation language") for declaratively describing the relationships and simple operations between component properties. This language will have a Python interface, will be serialized as JSON, and executed in JavaScript.
  • This client-side data transformation will be available in server-connected Dash apps as well, enabling certain simple updates to happen quickly
  • The app will not run arbitrary JavaScript, it will be designed in a way to be safe from XSS injections
  • The "data transformation language" will operations like filtering, plucking values from nested objects, sorting, and arithmetic. It will draw inspiration from functional programming libraries and languages that enable concise, chainable, and immutable data transformations. See Ramda (http://ramdajs.com/) for an example.

Activity

  1. self-assigned this
    on Jun 8, 2018
  2. ned2 commented on Jun 17, 2018

    @ned2
    Contributor

    This is super exciting, as it will greatly increase the range of contexts where Dash can be used. The plan to have serverless components be available within a server-connected app is a great idea. I've often found myself swapping bits of pre-computed layout in and out using callbacks. Having to do an entire round-trip just to change the page layout is not ideal. This will allow for much more responsive single-page apps.

  3. valentijnnieman commented on Jun 18, 2018

    @valentijnnieman
    Contributor

    In theory, this would open up Dash apps to be used with different React renderers, such as these https://github.com/chentsulin/awesome-react-renderer right? Very exciting!

  4. Tasselmi commented on Jul 16, 2018

    @Tasselmi

    hey , guys. I think you make problems complicated.
    Just look at plotly.offline.plot() . I think you should not create any new language at all . Just create a function or a package that can generate offline html files ,ok ? For example, we use dash as now and we view the app by visiting https://127.0.0.0:8000 or some address like this , a function u provide can download all the data and web UI and interactive features in the page and generate a offline html file . Studying a new language costs a lot time and the offline mode is quite different from now . I am afraid.

  5. chriddyp commented on Jul 16, 2018

    @chriddyp
    MemberAuthor

    Thanks for chiming in @Tasselmi !

    a function u provide can download all the data and web UI and interactive features in the page and generate a offline html file

    This will be possible if you want to just export the app.layout of your app. Nothing new to learn here.

    However, if you want to convert your Python callbacks to a standalone file, we need some way to run those callbacks without a Python server running. So, that's what this "new language" is proposing: a way to write your Dash callbacks in a way that can be run without a Python server.

  6. davidolmo commented on Jul 26, 2018

    @davidolmo

    I was looking for this feature before trying out Dash. This would be great

  7. Jonnyb36 commented on Aug 6, 2018

    @Jonnyb36

    This will be possible if you want to just export the app.layout of your app. Nothing new to learn here.

    This would also be a really useful feature by itself as often I would just like to take a snapshot of the html with the existing widget states that have generated that html.

  8. eruisi commented on Aug 15, 2018

    @eruisi

    This is a feature missing from Dash compared to Bokeh autload_static mode. Very excited to see it will come to Dash too!

    Do you have an ETA for the feature?

  9. chriddyp commented on Aug 15, 2018

    @chriddyp
    MemberAuthor

    Do you have an ETA for the feature?

    Shooting for late fall, but I'll keep this post updated with progress.

    Glad you're excited, it's going to be sweet :)

  10. Tasselmi commented on Aug 26, 2018

    @Tasselmi

    Hey guys. How is all of this going? I think offline mode is historic. As a data analyst, i will never use Microsoft office any more.

  11. chriddyp commented on Aug 26, 2018

    @chriddyp
    MemberAuthor

    I think offline mode is historic. As a data analyst, i will never use Microsoft office any more.

    That's what I'm talking about!

    It's looking good. I promise to keep this thread updated with developments. All I can say now is that we're shooting for late fall.

  12. Tasselmi commented on Aug 26, 2018

    @Tasselmi

    Thank u for ur response.

  13. Immortalin commented on Oct 1, 2018

    @Immortalin

    @chriddyp Easy way: https://github.com/QQuick/Transcrypt

    Throw in pyodide if you want the Scipy kitchen sink. Having a custom DSL is nice yes, but having the full power of Python is even nicer. :D

  14. 14 remaining items

  15. Btibert3 commented on Apr 11, 2019

    @Btibert3

    Any update on this?

  16. chriddyp commented on Apr 11, 2019

    @chriddyp
    MemberAuthor

    Done in #672

  17. mungojam commented on Apr 12, 2019

    @mungojam
    Contributor

    If anybody puts together a fully serverless dash app using this, it would be great to see an example. (I may try but can't guarantee that I'll get time for it)

  18. vlizanae commented on Apr 14, 2019

    @vlizanae

    is there any kind of documentation for this already?

  19. davidolmo commented on Apr 26, 2019

    @davidolmo

    I've seen the examples and it looks promising. If all callbacks are performed clientside, do you think there would be any way to export the Dash app to html+.js in order to allow offline usage?

  20. louisdecharson commented on May 20, 2019

    @louisdecharson

    Documentation

    For those of you looking for an example, one can be found here:
    https://community.plot.ly/t/dash-0-41-0-released/22131

  21. mungojam commented on May 21, 2019

    @mungojam
    Contributor

    Documentation

    For those of you looking for an example, one can be found here:
    https://community.plot.ly/t/dash-0-41-0-released/22131

    I don't know about others, but I was asking for an example (if it's possible) of how to use this to generate a fully clientside app. I suspect it isn't possible at this stage, but would be good for the future as it enables things like s3 hosting of some basic dash apps. Though maybe we would gain very little in using dash over pure react at that point.

  22. alexcjohnson commented on May 21, 2019

    @alexcjohnson
    Collaborator

    how to use this to generate a fully clientside app. I suspect it isn't possible at this stage

    Right, for now this feature is focused on performance, and does not provide a pathway to a fully serverless architecture.

  23. mneira10 commented on Jun 12, 2019

    @mneira10

    Does this let me get the corresponding html of a dash plot? I haven't been able to get the raw html yet and it is not clear to me how it can be done with this feature.

    Has anyone been able to do this?

  24. giiyms commented on Jul 25, 2019

    @giiyms

    Documentation

    For those of you looking for an example, one can be found here:
    https://community.plot.ly/t/dash-0-41-0-released/22131

    From this example did anyone figure out how to output to a single .html file?
    Would love to pass Dash boards to colleagues without requiring server.

  25. vantaka2 commented on Aug 3, 2019

    @vantaka2

    @chriddyp - sorry for tagging you on a closed issue! I have the same question as @mneira10 & @GuyMS above, seems like a few of us are looking for a way to export the html.

    Higher up in this thread you mentioned this is possible

    This will be possible if you want to just export the app.layout of your app. Nothing new to learn here.

    would you mind sharing a example of how to do this? Apologize if this is documented somewhere, I went though a lot of the documentation and have not found it yet.

    Thanks again for all your great work!

    I asked the same question on the dash community page: https://community.plot.ly/t/export-html-of-dash-app/26754 - I'll be sure to update everyone, if it gets answered or I figure it out.

  26. chriddyp commented on Aug 4, 2019

    @chriddyp
    MemberAuthor

    As mentioned above in #266 (comment), exporting to standalone HTML is not officially supported right now, this issue ended up focusing on the clientside callback architecture.

  27. locked and limited conversation to collaborators on Aug 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions