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
)
)
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
)
)
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
)
)
or
plot(
df,
y = :value,
color = :variable,
marker_colorscale = colors.viridis
)
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]
)
)
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]
)
)
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)
))
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.