plotly / Dash.jl

Dash for Julia - A Julia interface to the Dash ecosystem for creating analytic web applications in Julia. No JavaScript required.
MIT License
486 stars 41 forks source link

3D plot combining line and surface #191

Closed maucejo closed 1 year ago

maucejo commented 1 year ago

Hi all, I am trying to develop a Dash app containing a 3d plot. This plot includes a 3d line and a 3d plane surface. The MWE below gives you a basic overview of the app.

using PlotlyJS
using Dash, DashBootstrapComponents

const x = range(0., 2π, 100)

function dummy_process()
    z = sin.(x)

    return z
end

function plot_3D(x, y, z)
    nx = length(x)
    nv = Int(round(nx./2.))

    vl = range(0., 1., nv)
    v = repeat(vl, 1, nx)
    X = repeat(x', nv, 1)

    zmin = minimum(z)

    traces = GenericTrace[]
    Y = y*ones(nx)

    # Line
    trace = scatter3d(x = x,
                    y = Y,
                    z = z,
                    mode = "lines",
                    line = attr(color = :blue),
                    showlegend = false)

    # Surface
    Z = zmin .+ (repeat(z', nv, 1) .- zmin).*v
    surf = surface(x = X,
                    y = Y,
                    z = Z,
                    showscale = false)

    append!(traces, [trace, surf])

    # Define the layout
    layout = Layout(scene = attr(
        xaxis = attr(autorange = "reversed")))

    p = plot(traces, layout)
    figure = (data = getfield.(p.plot.data, :fields), layout = p.plot.layout.fields)

    return figure
end

app = dash(external_stylesheets=[dbc_themes.LITERA])
app.layout = dbc_container(fluid = true) do
    html_br(),
    dbc_row(align = "center", justify = "center") do
        dbc_col(
            html_div() do
                dbc_button(
                "Compute",
                id = "compute",
                outline = true,
                size = "lg",
                color = "primary",
                className = "me-2",
                n_clicks = 0)
            end,
            md = 2)
    end,

    dbc_row(justify = "center") do
        dbc_col(width = 6) do
            dcc_graph(
                id = "3dplot",
                animate = true,
                style = Dict("width" => "100%", "height" => "600px")
            )
        end
    end,

    html_div(dcc_store(id = "save_z"))
end

callback!(
    app,
    Output("save_z", "data"),
    Input("compute", "n_clicks"),
    prevent_initial_call = true) do clicks
    if clicks == 0
        throw(PreventUpdate())
    end

    z = dummy_process()

    return z
end

# Plot graphs
callback!(
    app,
    Output("3dplot", "figure"),
    Input("save_z", "data"),
    prevent_initial_call = true
) do z

    figure = plot_3D(x, 0., z)
end

run_server(app, debug = true)

This app throws the following error cwise: Arrays do not all have the same shape!.

Error: cwise: Arrays do not all have the same shape!

    at assign_cwise_thunk (eval at e.exports (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-plotlyjs.js:2:333085), <anonymous>:8:71)

    at Object.assign_ndarrayops [as assign] (eval at o (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-plotlyjs.js:2:1676280), <anonymous>:3:43)

    at S.padField (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-plotlyjs.js:2:817643)

    at S.update (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-plotlyjs.js:2:819490)

    at p.update (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-plotlyjs.js:2:3506824)

    at e.exports [as plot] (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-plotlyjs.js:2:3507365)

    at w.plot (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-plotlyjs.js:2:2762434)

    at r.plot (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-plotlyjs.js:2:2740149)

    at r.drawData (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-plotlyjs.js:2:2538329)

    at f.syncOrAsync (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-plotlyjs.js:2:2427496)

The problem seems to be related to the definition of surface, because plotting only the 3d line works. Moreover, the function plot_3D works fine when using PlotlyJS.

using PlotlyJS

x = range(0., 2π, 100)
z = sin.(x)

fig = plot_3d(x, 0., z)
plot(fig)

Thanks for your help.

maucejo commented 1 year ago

The problem comes from the way Dash.jl process the data. Instead of considering matrices as in PlotlyJS.jl, Dash.jl needs X,Y,Z to be defined as Vectors of Vectors.

Here, the plot3D function must be as follows:

function plot_3D(x, y, z)
    nx = length(x)
    nv = Int(round(nx./2.))

    vl = range(0., 1., nv)
    v = repeat(vl, 1, nx)
    X = repeat(x', nv, 1)
    XX =  [c for c in eachcol(X)]

    zmin = minimum(z)

    traces = GenericTrace[]
    Y = y*ones(size(X))
    YY = [c for c in eachcol(Y)]

    # Line
    trace = scatter3d(x = x,
                    y = Y[1, :],
                    z = z,
                    mode = "lines",
                    line = attr(color = :blue),
                    showlegend = false)

    # Surface
    Z = zmin .+ (repeat(z', nv, 1) .- zmin).*v
    ZZ = [c for c in eachcol(Z)]
    surf = surface(x = XX,
                    y = YY,
                    z = ZZ,
                    showscale = false)

    append!(traces, [trace, surf])

    # Define the layout
    layout = Layout(scene = attr(
        xaxis = attr(autorange = "reversed")))

    p = plot(traces, layout)
    figure = (data = getfield.(p.plot.data, :fields), layout = p.plot.layout.fields)

    return figure
end
etpinard commented 1 year ago

thanks for writing down the workaround @maucejo :clap: