Clientside Callbacks #266

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' ))defupdate_graph(new_dropdown_value, new_text_input_value):
# compute a new figure based off of the new values of the dropdown# or text inputreturn {
'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

fromdash.serverlessimportSelectorasSlayout=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.

importdash.clientside.transformationsasTimportdash.clientside.selectorasSdf=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 referencedcc.Dataset(
id='my-dataset',
columns=df.columns,
rows=df.to_dict(),
),
# `Table` renders an actual table to the screendcc.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}
foriindf.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.

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

    , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
     blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    
    Skip to content

    Clientside Callbacks #266

    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' ))defupdate_graph(new_dropdown_value, new_text_input_value):
    # compute a new figure based off of the new values of the dropdown# or text inputreturn {
    '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

    fromdash.serverlessimportSelectorasSlayout=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.

    importdash.clientside.transformationsasTimportdash.clientside.selectorasSdf=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 referencedcc.Dataset(
    id='my-dataset',
    columns=df.columns,
    rows=df.to_dict(),
    ),
    # `Table` renders an actual table to the screendcc.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}
    foriindf.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.

    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

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      Clientside Callbacks #266

      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' ))defupdate_graph(new_dropdown_value, new_text_input_value):
      # compute a new figure based off of the new values of the dropdown# or text inputreturn {
      '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

      fromdash.serverlessimportSelectorasSlayout=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.

      importdash.clientside.transformationsasTimportdash.clientside.selectorasSdf=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 referencedcc.Dataset(
      id='my-dataset',
      columns=df.columns,
      rows=df.to_dict(),
      ),
      # `Table` renders an actual table to the screendcc.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}
      foriindf.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.

      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

        , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        Clientside Callbacks #266

        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' ))defupdate_graph(new_dropdown_value, new_text_input_value):
        # compute a new figure based off of the new values of the dropdown# or text inputreturn {
        '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

        fromdash.serverlessimportSelectorasSlayout=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.

        importdash.clientside.transformationsasTimportdash.clientside.selectorasSdf=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 referencedcc.Dataset(
        id='my-dataset',
        columns=df.columns,
        rows=df.to_dict(),
        ),
        # `Table` renders an actual table to the screendcc.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}
        foriindf.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.

        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

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          Clientside Callbacks #266

          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' ))defupdate_graph(new_dropdown_value, new_text_input_value):
          # compute a new figure based off of the new values of the dropdown# or text inputreturn {
          '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

          fromdash.serverlessimportSelectorasSlayout=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.

          importdash.clientside.transformationsasTimportdash.clientside.selectorasSdf=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 referencedcc.Dataset(
          id='my-dataset',
          columns=df.columns,
          rows=df.to_dict(),
          ),
          # `Table` renders an actual table to the screendcc.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}
          foriindf.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.

          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

            , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            Clientside Callbacks #266

            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' ))defupdate_graph(new_dropdown_value, new_text_input_value):
            # compute a new figure based off of the new values of the dropdown# or text inputreturn {
            '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

            fromdash.serverlessimportSelectorasSlayout=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.

            importdash.clientside.transformationsasTimportdash.clientside.selectorasSdf=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 referencedcc.Dataset(
            id='my-dataset',
            columns=df.columns,
            rows=df.to_dict(),
            ),
            # `Table` renders an actual table to the screendcc.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}
            foriindf.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.

            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

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Clientside Callbacks #266

              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' ))defupdate_graph(new_dropdown_value, new_text_input_value):
              # compute a new figure based off of the new values of the dropdown# or text inputreturn {
              '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

              fromdash.serverlessimportSelectorasSlayout=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.

              importdash.clientside.transformationsasTimportdash.clientside.selectorasSdf=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 referencedcc.Dataset(
              id='my-dataset',
              columns=df.columns,
              rows=df.to_dict(),
              ),
              # `Table` renders an actual table to the screendcc.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}
              foriindf.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.

              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

                , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                Clientside Callbacks #266

                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' ))defupdate_graph(new_dropdown_value, new_text_input_value):
                # compute a new figure based off of the new values of the dropdown# or text inputreturn {
                '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

                fromdash.serverlessimportSelectorasSlayout=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.

                importdash.clientside.transformationsasTimportdash.clientside.selectorasSdf=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 referencedcc.Dataset(
                id='my-dataset',
                columns=df.columns,
                rows=df.to_dict(),
                ),
                # `Table` renders an actual table to the screendcc.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}
                foriindf.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.

                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