Dash.jl 3D plot combining line and surface

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!.

Full stack trace
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.

Code sample
using PlotlyJS

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

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

Thanks for your help.

In the surface definition:

surf = surface(x = X,
                    y = Y,
                    z = Z,
                    showscale = false)

X and Z have size (50, 100), while Y is a vector of length(100). But z=sin(x) must be defined as a parametric surface, with X, Y, Z matrices of the same size. Only for surfaces of equation z=f(x,y), x, y may be vectors and z, a Matrix.
Hence define for surface, Y=zeros(size(X)).

Thanks @empet for your answer. Unfortunately, your suggestion doesn’t solve the problem. What is somewhat disturbing is that the same code works fine outside Dash?

I have tried to plot a very basic surface z(x, y) = cos(x)*sin(y). Here is the code of the plot_3D function

Code of the function
function plot_3D() # x is a global variable
    
      surf = surface(z = @. cos(x)*sin(x)')

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

    return figure
end

If you run the code, the same error as previously is thrown by Dash.jl. This code works in the REPL.

Is it a known issue with Dash.jl or do I miss something ?

Thanks

Julia uses column major order for arrays, and plotly.js, row-major. The PlotlyJS’s author performed the transformation from one order to another, during json serialization of a plot, but maybe Dash.jl didn’t do it. I have no experience with Dash, and this is just a hypothesis.

I have seen your post on the Plotly community Can plotly.js distinguish between row major and column major order for arrays?. Transposing the matrices has no effect. I have checked that X, Y, Z have the same dimensions and the same type.

I have open an issue on the Dash.jl github repo.

I had a slightly related issue with heatmaps not showing up properly. I was passing a matrix but it needed a vector of vectors. See: [Question] Can we display heatmap ? · Issue #60 · plotly/Dash.jl · GitHub

1 Like

Your solution works @Iulian.Cioarca. For the records, the “quick & dirty” solution is:

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

Thanks again for helping me!

2 Likes