JuliaPlots / PlotlyJS.jl

Julia library for plotting with plotly.js

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unable to use ColorSchemes.jl to set a the colorscale when plotting with a dataframe

JontySinai opened this issue · comments

It is extremely useful to be able to set the colorscale when using PlotlyJS for a dataframe with discrete groups, eg:

plot(df, x = :x, y = :y, color = :group)

Specifically I would like to be able to choose one of the colorscales from ColorSchemes.jl for the different groups in the dataframe.

In the documentation for Plotly in Julia, there are few examples showing usage of the marker.colorscale property, and my usage of the property is proving ineffective. I cannot find any examples help me in this case. The closest I could find to using ColorSchemes.jl with PlotlyJS.jl is the example in issue #292, but this doesn't translate to the dataframes case.

The following works, as per the example:

data = rand(20) .+ reshape(1:10, 1, :)

plot(
    data,
    Layout(
        colorway=ColorSchemes.tab20.colors
    )
)

image

But the following doesn't work, reverting to the default colorscale:

df = DataFrame(data, :auto) |> stack

plot(
    df,
    y = :value,
    color = :variable,
    marker = attr(
        colorscale = ColorSchemes.tab20.colors
    )
)

image

How can I recreate the first picture using the dataframes syntax?

In getting started, https://plotly.com/julia/getting-started/ with PlotlyJS.jl, paragraph ColorSchemes.jl, we can read:
They are all exposed under the colors object that is imported when you run using PlotlyJS. To access the vidiris colorscheme you would use colors.viridis.
As such, setting in the two plots:

colorway=colors.tab20

respectively:

 marker_colorscale = colors.tab20

you'll plot the same colors and color order.

@empet I am unable to produce the expected behaviour using your solution. Using a more familiar palette (viridis), either method still renders with the default color scheme:

plot(
    data,
    Layout(
        colorway=colors.viridis
    )
)

image

or

plot(
    df,
    y = :value,
    color = :variable,
    marker_colorscale = colors.viridis
)

image

colors.viridis is a continuous colorscale that has effect only in a plot with a single trace, like this one:

using PlotlyJS
fig= Plot(scatter(x=-2 .+ 3*rand(15), y=3*rand(15), mode="markers",
          marker_size=18, marker_color=rand(1:7, 15), marker_colorscale=colors.viridis, marker_showscale=true),
          Layout(width=600, height=400))

In your examples there are 10 traces, and it's impossible to map the color values from a trace to a single color from the
continuous colorscale.
For the below plot (one of your examples) just name the returned figure by fig as, follows:

df = DataFrame(data, :auto) |> stack

fig = plot(
           df,
           y = :value,
           color = :variable,
           marker_colorscale= colors.tab20
              )

Now you can retrieve the number of traces as follows:

length(fig.plot.data)

and inspect the trace definitions by printing their json version:

print(json(fig.plot.data, 2))

No matter what colorscale are you setting or commenting out the line

marker_colorscale= colors.tab20

the lines in the above fig will be plotted by the colors in the colorway
https://github.com/sglyon/PlotlyBase.jl/blob/b706d0042162a0b6a9b5b8ac836e1d3fc025d21d/src/api.jl#L512
of the default plotly template.
Unfortunately I did not check how the discrete Colorscheme, colors.tab20, is defined and what colors is contains
when I initially answered your question. I thought that the displayed colors are the tab20 colors.

Thanks for this. So it seems that based on this function, the colorway will always be fixed to the default, regardless of what is specified in the layout, when using a DataFrame:

plot(
    df,
    y = :value,
    color = :variable,
    Layout(
        colorway=ColorSchemes.viridis.colors[1:20:end]
    )
)

image

Is it possible to change the behaviour so that the above code will produce an equivalent output to the below following (taken from #292 which successfully uses regularly spaced discrete samples from viridis to colour each trace):

plot(
    data, 
    Layout(
        colorway=ColorSchemes.viridis.colors[1:20:end]
    )
)

image

P.S. I changed to viridis because I realised that tab20 might not be as recognizable.

Meanwhile I succeeded with a workaround, consisting in two functions: one that defines a colorway of 10 colors from a continuous colorscale, and another that replaces the default colorway, by the new one

using  PlotlyJS,  Colors
#get 10 equally spaced colors from colors.some_sequential_colorscale 
function colorscale_colorway(clrsc)
    dcolorscale=[Colors.hex(get(clrsc, k*0.09)) for k in 1:10]
    return ["#"*c for c in dcolorscale] #colorway consisting from 10 colors of the colorscale clrsc
end  

function clrwtemplate(;colorway=String[])
    template = templates["plotly"]
    template.layout[:margin]=Dict(:t=>125,:r=>80, :b=>80, :l=>80)
    if length(colorway) > 0
        template.layout[:colorway]=colorway 
    end
    return template
end  
vir_colorway = colorscale_colorway(colors.viridis)
data = rand(20) .+ reshape(1:10, 1, :)
fig=plot(
    data,
    Layout(
        colorway=vir_colorway, width=650,  height=400, template=clrwtemplate(;colorway=vir_colorway)
    ))

viridis_colorway

and the same plot with:

using DataFrames
df = DataFrame(data, :auto) |> stack
fig=plot(
    df,
    y = :value,
    color = :variable
 )
relayout!(fig,  width=650, height=400,
        template=clrwtemplate(;colorway=vir_colorway))
display(fig)

The last code must be run two times because after the first run it displays the default colorway.

A better function, that creates a colorway of equally "spaced" n colors, from a colorscale, is this one:

function colorscale_colorway(clrsc; n=10)
    return ["#"*Colors.hex(get(clrsc, k/(n-1))) for k in 0:n-1]
end 

By your definition:

ColorSchemes.viridis.colors[1:20:end]

the colorway contains more than 10 colors, but not the lighter colors from viridis, and so your first from those 10 colors are almost indistinguishable.

Setting in the function above, the exact number of needed colors, the colorway contains distinguishable colors, from the darkest to the lightest one.