Shiny-like syntax

At my workplace we are looking into consolidation of tools. At the moment, some people are writing dashboards in Dash and some are doing it in Shiny. Personally, I have a python background, so i never tried Shiny - but to participate in the discussion, i figured i should at least try it out. Even though I am new to R, I couldn’t helt but notice the simplicity of their syntax, in particular their reactive expressions.

As an example, consider this this small Dash app that calculates an intermediate result (x to some power, i know it is not necessary to do this; but the point is to illustrate more complicated workflows where this is necessary) and displays the result in two different views (text and graph),

import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
from dash.dependencies import Output, Input
from dash_extensions.enrich import DashProxy

app = DashProxy()
app.layout = html.Div([dcc.Input(value=1, id='x', type='number'),
                       dcc.Input(value=1, id='power', type='number'),
                       html.Div(id='result'), dcc.Graph(id='graph'),
                       dcc.Store(id='z')])

@app.callback(Output('z', 'data'), Input('x', 'value'), Input('power', 'value'))
def calculate_z(x, y):
    return x ** y if (x and y) else None

@app.callback(Output('result', 'children'), Input('x', 'value'), Input('power', 'value'), Input('z', 'data'))
def display_result(x, y, z):
    return f"{x}^{y} is {z}"

@app.callback(Output('graph', 'figure'), Input('x', 'value'), Input('power', 'value'), Input('z', 'data'))
def plot_result(x, y, z):
    return go.Figure([go.Bar(x=['x', 'y', 'x**y'], y=[x, y, z])])

if __name__ == "__main__":
    app.run_server()

Looking at the code, there is a lot of boilerplate code. I have written a small transpiler of what a more Shiny-like syntax could look like in Dash. Here is the code for the same app,

import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
from dash_extensions.enrich import DashProxy

app = DashProxy()
app.layout = html.Div([dcc.Input(value=1, id='x', type='number'),
                       dcc.Input(value=1, id='power', type='number'),
                       html.Div(id='result'), dcc.Graph(id='graph')])
app.reactive('z', lambda x='x', y='power': x ** y if (x and y) else None)  # reactive variable, no need for Store
app.output('result', lambda x='x', y='power', z='z': f"{x}^{y} is {z}") # default prop for input is value, default for output is children
app.output(('graph', 'figure'), lambda x='x', y='power', z='z': go.Figure([go.Bar(x=['x', 'y', 'x**y'], y=[x, y, z])]))

if __name__ == "__main__":
    app.run_server(port=8056)

The Shiny-like syntax is about half (!) the length of the original Dash code. I find the Shiny-like syntax just as readable, maybe even more so. Especially for beginners, as you don’t need to understand decorators, imports from dash dependencies (i always get confused about the Input import) and deal with Store objects. What do you think, @chriddyp ? If anyone else have been playing with similar projects and/or have comments, please chip in :slight_smile:

Hey @Emil

Have you checked out Dash Labs?

Here is one way to make the same app, There is a lot less boilerplate, plus you can use lots of nice output templates:

import dash
import dash_core_components as dcc
import dash_labs as dl
import plotly.graph_objects as go

app = dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl = dl.templates.HtmlCard(app, title="Dash Labs App", width="500px")


@app.callback(
    dl.Input(dcc.Input(value=1, type="number"), label="Number"),
    dl.Input(dcc.Input(value=1, type="number"), label="Power"),
    template=tpl,
)
def callback(x, y):
    z = x ** y if (x and y) else None
    text = f"{x}^{y} is {z}"
    fig = dcc.Graph(figure=go.Figure([go.Bar(x=["x", "y", "x**y"], y=[x, y, z])]))
    return [text, fig]


app.layout = tpl.children

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

Hi @AnnMarieW,

I haven’t played much with Dash Labs yet, but as I understand that is mainly for layout, right? One of the main points of the previous example was how to use an intermediate (reactive) variable to share state between callbacks. I know it is not necessary for the simple calculation in the example, but it was intended to illustrate a use case that i often encounter “in real life”. I’ll try to see if i can figure out how to do it using Dash Labs syntax :slight_smile:

Hey @Emil

Dash Labs has a lot of new features and the templates are just my favorite :wink:. The examples for the long running callbacks show some new functionality without the use of templates. The callback enhancements will go a long way towards making things more concise (and shiny?) My understanding is that all of this will be part of Dash 2.0.

For the intermediate values, you can still use dcc.Store. I think it would take a separate callback though. I really like your syntax for the “reactive” variable - that’s really slick. Maybe that could be added to Dash Labs?

Yes, the reactive variable is also the main concept that i like from Shiny. It’s a lot simpler than manually creating a Store and setting up callback(s). The compact syntax is a bit more nit picking and personal taste :smile:

1 Like

I guess this would be the Dash labs approach of the same app (my first Dash labs app, wuhuu!),

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_labs as dl
import plotly.graph_objects as go
from dash.dependencies import Output, Input

app = dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl = dl.templates.HtmlCard(app, title="Dash Labs App", width="500px")


@app.callback(
    dl.Output(html.Div(), 'children', label="Display"),
    dl.Input(dcc.Input(value=1, type="number", id="x"), label="Number"),
    dl.Input(dcc.Input(value=1, type="number", id="power"), label="Power"),
    dl.Input(dcc.Store(id='z'), 'data', label=''),
    template=tpl,
)
def display_result(x, y, z):
    return f"{x}^{y} is {z}"


@app.callback(
    dl.Output(dcc.Graph(), 'figure', label="Graph"),
    Input('x', 'value'), Input('power', 'value'), Input('z', 'data'),
    template=tpl,
)
def update_graph(x, y, z):
    return go.Figure([go.Bar(x=['x', 'y', 'x**y'], y=[x, y, z])])


@app.callback(Output('z', 'data'), Input('x', 'value'), Input('power', 'value'))
def calculate_z(x, y):
    return x ** y if (x and y) else None


app.layout = tpl.children

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

I guess the template system doesn’t fit this usecase particularly well, but i am starting to see the potential. It’s actually a bit like the Shiny layout system :smile:

EDIT: I was able to make it a little nicer (now without tpl.add_component call)

Yippie! Here’s one with only 2 callbacks, but I’m not sure if it’s better. :slight_smile:

import dash
import dash_core_components as dcc
import dash_labs as dl
import plotly.graph_objects as go


app = dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl = dl.templates.HtmlCard(app, title="Dash Labs App", width="500px")


@app.callback(
    dl.Input(dcc.Input(value=1, id="x", type="number"), label="Number"),
    dl.Input(dcc.Input(value=1, id="power", type="number"), label="Power"),
    dl.Input("z", "data"),
    template=tpl,
)
def update_output(x, y, z):
    text = f"{x}^{y} is {z}"
    fig = dcc.Graph(figure=go.Figure([go.Bar(x=["x", "y", "x**y"], y=[x, y, z])]))
    return [text, fig]


@app.callback(
    dl.Output(dcc.Store(id="z"), "data"),
    dl.Input("x", "value"), dl.Input("power", "value"),
    template=tpl,
)
def update_store(x, y):
    return x ** y if (x and y) else None


app.layout = tpl.children

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