Adding Plotly Traces to an Array

I am attempting to plot multiple traces using the PlotlyJS backend and there are too many to do manually. My idea is to do something of the sort:

for idx in 1:size(data)[2]
    traces[idx] = scatter(x=r_range,
                         y=data[:,idx],
                        mode="lines",
                        name = "$idx")
end

However, I need to allocate the traces array to be able to use it. I tried doing traces = Array{Any,1}() and using push! to populate it, which worked, but when I plot, I get the following error:

ERROR: LoadError: MethodError: no method matching Plot(::Vector{Any}, ::Layout{Dict{Symbol, Any}})
Closest candidates are:
  Plot(::AbstractArray{T}, ::Layout; kwargs...) where T<:Union{Dates.Date, Dates.DateTime, AbstractString, Number, Symbol} at C:\Users\Paul\.julia\packages\PlotlyBase\xb3Du\src\convenience_api.jl:78
  Plot(::TT, ::TL, ::TF, ::Base.UUID, ::PlotConfig) where {TT<:(AbstractVector{<:AbstractTrace}), TL<:AbstractLayout, TF<:(AbstractVector{<:PlotlyFrame})} at C:\Users\Paul\.julia\packages\PlotlyBase\xb3Du\src\PlotlyBase.jl:79
  Plot(::AbstractVector{<:AbstractTrace}, ::Any) at C:\Users\Paul\.julia\packages\PlotlyBase\xb3Du\src\PlotlyBase.jl:105

I tried playing around with AbstractArrays to no avail.

Here’s an overview of my code:

using PlotlyJS

# Measurement Generation
data = rand(Float64, (100, 5))
r_range = 10:10:90


for idx in 1:size(data)[2]
    push!(traces, scatter(x=r_range,
                         y=data[:,idx],
                        mode="lines",
                        name = "$idx")
end

plot_size = 800

layout = Layout(
    xaxis_label = "x",
    yaxis_label = "y",
    font = attr(size=16),
    width=plot_size, height=plot_size,
    template = "plotly_white"
)

analysis_plot = plot(traces, layout)

My goal would be to automate the process via a for loop and get a similar result as THIS tutorial.
I would appreciate any insight as to the best way to allocate the array which works with Plotly. Thanks

1 Like

Why not using the builtin function: add_trace!()

You can pre-allocate a vector of traces:

nr_of_traces = size(data, 2)
traces = Vector{GenericTrace}(undef, nr_of_traces)
for idx in 1:nr_of_traces
    traces[idx] = scatter(x=r_range,  y=data[:,idx],
                        mode="lines",
                        name = "$idx")
end

If you don’t know in advance how many traces will be needed, start with

traces = GenericTrace[]

then push!(traces, scatter(...))

4 Likes

I have tried both in my example below: GenericTrace as well as AbstractTrace and I experimented with push!() as well as with a predefined vector of traces of specified length of n elements.
The strange thing is:
In the single plots that are produced inside the for loop every single trace is displayed correctly, but in the plot of all n traces, the curves are all plotted on top of each other, as if they were identical. Any ideas? …

using PlotlyJS

begin
    n_ = 5
    y_vec = 1.0 .* collect(1:n_)
    y_vec_hlp = 1.0 .* collect(1:n_)
    traces_ = Vector{PlotlyJS.AbstractTrace}(undef, n_)
    # traces2_ = PlotlyJS.GenericTrace[]
    for _i in eachindex(y_vec)
        global y_vec_hlp, traces_
        y_vec_hlp[2] = (1 + _i * 0.05) * y_vec[2]
        traces_[_i] = PlotlyJS.scatter(; y= y_vec_hlp , name = string("#: ", _i));
        # push!(traces2_, PlotlyJS.scatter(; y= y_vec_hlp , name = string("#: ", _i)))
        display(PlotlyJS.Plot(PlotlyJS.scatter(; y= y_vec_hlp , name = string("#: ", _i))))
    end
    display(PlotlyJS.Plot(traces_))
    # display(PlotlyJS.Plot(traces2_))
end

Yes, you are right, add_trace!() is a reliable alternative to push!(traces, new_trace), the new example with add_trace!() works fine :slight_smile:

using PlotlyJS

begin
    n_ = 5
    y_vec = 1.0 .* collect(1:n_)
    y_vec_hlp = 1.0 .* collect(1:n_)
    # traces_ = PlotlyJS.GenericTrace[] # does not work
    # traces_ = PlotlyJS.AbstractTrace[] # does not work
    traces_ = PlotlyJS.Plot();
    for _i in eachindex(y_vec)
        global y_vec_hlp
        y_vec_hlp[2] = (1 + _i * 0.05) * y_vec[2]
        PlotlyJS.add_trace!(traces_, PlotlyJS.scatter(; y= y_vec_hlp , name = string("#: ", _i)))
    end
    display(PlotlyJS.Plot(traces_))
end

push!(traces, new_trace) does add new_trace to the array of traces. traces, (which literally was the question) while add_trace!(plot, new_trace) adds new_trace to the PlotlyJS plot. A difference is that a (generic) trace has only the x, y etc. arrays, the elements can still be changed or the variables get reassigned. So your array of n_=5 traces had after the loop all the same single global y_vec_hlp array and therefore all traces got displayed on top of each other with only the last number 5 visible.

When adding traces (and other stuff) to the plot, with add_trace!…, everything including the arrays gets copied to the plot, and a later change of the traces has no effect. This is “safer” and also needed for interactive displaying. On the other hand, when using arrays of traces, one has to be aware that a trace has only “references” to the arrays to be displayed. However, this can be also be an advantage. For example, a mass production of scatter plots with many different data becomes more efficient with this trick.

Thanks for the details from the PlotlJS-ecosystem! And what would be the approach, to make the other plots visible in the example with a vector of traces?