What do you guys use canvas for? 🤔

I made this dash-svm-2022.herokuapp.com. And this is a 2022 copy of the one of official demo. I’ve tried those new features, those new components. Amazing! The experience is so much better than when I was new to dash. Thank you Plotly for your efforts over the past few years.

svm12

And could someone teach me how to write a nice client-side callback? I want to add grids and axes to the canvas, as well as a ‘one-click clear’ button. And I have to figure out a way to prevent the user from drawing lines. Please help me, thanks a lot!

Ah, this is my MRE.

from dash import Dash, html, dcc, dash_table, Input, Output, State, no_update, get_asset_url
from dash.exceptions import PreventUpdate
from dash_canvas import DashCanvas
import pandas as pd
import json

app = Dash(__name__)

canvas_width = 800

columns = ['stroke', 'path', 'pathOffset']

app.layout = html.Div(
    [
        html.H6('---------------------------------'),
        DashCanvas(
            id='annot-canvas',
            #filename=get_asset_url('canvas_bg.png'),
            lineWidth=5,
            goButtonTitle='Generate',
            lineColor='#509188',
            hide_buttons=[
                "zoom", "pan", "line", "pencil", "rectangle", "select"
            ]),
        btn := html.Button('Toggle'),
        btn2 := html.Button('Clear'),
        btn3 := html.Button('Grids'),
        dash_table.DataTable(id='canvas-table',
                             style_cell={'textAlign': 'left'},
                             columns=[{
                                 "name": i,
                                 "id": i
                             } for i in columns]),
        div := html.Div(),
    ],
    style={'height': '1000px'})


@app.callback(Output('canvas-table', 'data'), Input('annot-canvas',
                                                    'json_data'))
def update_data(string):
    if string:
        df = pd.DataFrame(json.loads(string)['objects'])
    else:
        raise PreventUpdate

    dff = df[columns]
    dff['path'] = dff['path'].apply(lambda x: str(x))
    dff['pathOffset'] = dff['pathOffset'].apply(lambda x: str(x))

    return dff.to_dict('records')


@app.callback(Output(div, 'children'), Input('annot-canvas', 'json_data'))
def showme(string):
    return string


app.clientside_callback(
    """
    function(n, c) {
    if (c === '#509188') {
        return '#FF7070'
    } else {
        return '#509188'
    }

}
    """, Output('annot-canvas', 'lineColor'), [Input(btn, 'n_clicks')],
    [State('annot-canvas', 'lineColor')])

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

Very impressive App, @stu
I just used the canvas and was delighted to see the data just auto-generate :slight_smile:

Did you get a chance to read the Dash docs on the clientside_callback, or watch the video I made (around minute 07:54 is where I show it, if I recall)?

The question is that what I want to achieve is a bit complicated for my JS level. :joy:

I see. I recommend you start with a small clientside_callback example written out, and let us know where you get stuck or what error you are getting.
If it’s really complicated, it might be better to practice more simple clientside_callbacks and improve your JS skillset and then try the more complicated callbacks.

2 Likes
from pydoc import classname
from dash import Dash, html, dcc, dash_table, Input, Output, State, no_update, get_asset_url
from dash.exceptions import PreventUpdate
from dash_canvas import DashCanvas
import pandas as pd
import json

app = Dash(__name__)

canvas_width = 800

columns = ['stroke', 'path', 'pathOffset']

app.layout = html.Div([
    html.H6('---------------------------------'),
    html.Div(DashCanvas(
        id={
            'type': 'canvas',
            'index': 'c1'
        },
        filename=get_asset_url('canvas_bg.png'),
        lineWidth=5,
        goButtonTitle='Generate',
        lineColor='#509188',
        hide_buttons=["zoom", "pan", "line", "pencil", "rectangle", "select"]),
             className='mycanvas'), btn := html.Button('Toggle'), btn2 :=
    html.Button('Clear'), btn3 := html.Button('Grids'),
    dash_table.DataTable(
        id='canvas-table',
        style_cell={'textAlign': 'left'},
        columns=[{
            "name": i,
            "id": i
        } for i in columns]), div := html.Div(), blank_output := html.Div()
],
                      style={'height': '1000px'})


@app.callback(Output('canvas-table', 'data'),
              Input({
                  'type': 'canvas',
                  'index': 'c1'
              }, 'json_data'))
def update_data(string):
    if string:
        df = pd.DataFrame(json.loads(string)['objects'])
    else:
        raise PreventUpdate

    dff = df[columns]
    dff['path'] = dff['path'].apply(lambda x: str(x))
    dff['pathOffset'] = dff['pathOffset'].apply(lambda x: str(x))

    return dff.to_dict('records')


@app.callback(Output(div, 'children'),
              Input({
                  'type': 'canvas',
                  'index': 'c1'
              }, 'json_data'))
def showme(string):
    return string


app.clientside_callback(
    """
    function(n, c) {
    if (c === '#509188') {
        return '#FF7070'
    } else {
        return '#509188'
    }

}
    """, Output({
        'type': 'canvas',
        'index': 'c1'
    }, 'lineColor'), [Input(btn, 'n_clicks')],
    [State({
        'type': 'canvas',
        'index': 'c1'
    }, 'lineColor')])

app.clientside_callback(
    """
    function(n) {
        let cavs = document.querySelector('.canvas-container').firstChild
        let ctx = cavs.getContext("2d");
        ctx.clearRect(0, 0, cavs.width, cavs.height);

    }              
    """, Output(blank_output, 'children'), Input(btn2, 'n_clicks'))

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

When I tried to write a callback to the clear button, I encountered an error report of document.querySelector(...) is null. Can’t the selector be used in client side callbacks?

------update-------
I changed it to an external JS file. Although there is still that error, the button responds. But I made the whole canvas disappear, and it reappeared when I clicked on it, and I wasn’t able to clear the content.

app.clientside_callback(
    ClientsideFunction(namespace='clientside',
                       function_name='clear_function'),
    Output(blank_output, 'children'), Input(btn2, 'n_clicks'))
window.dash_clientside = Object.assign({}, window.dash_clientside, {
    clientside: {
        clear_function: function (n) {
            let cavs = document.querySelector('.canvas-container').firstChild
            let ctx = cavs.getContext("2d");
            ctx.clearRect(0, 0, cavs.width, cavs.height);

        }
    }
});

Any suggestions?

Hi Adam,
Although you told me, I should start by practicing simple client callbacks and then complicated. I haven’t actually seen any complex use case until now. I don’t know where to start to implement my idea. And dash-canvas is a totally new component, I went to GitHub to see it, but still have no clue. What’s the most complex client side callback you’ve ever seen?
You know what? I just now had an idea that I was going to submit a pull request to plotly/dash-sample-apps to hand over my app. If the administrator is willing to merge it, that means there may be someone to help enhance it. I’ll probably be able to learn something new. :laughing:

Hi @stu :point_up: that’s a good idea. I’ll send you a message to continue the conversation.