Consistent plotting attributes

I’m plotting the same stuff across multiple different plots. To make things look consistent I want the same things to have the same look, regardless of which plot they are in. What is the best way to accomplish that?

Here’s a MWE. Two different plots, in both are a path, home, and work. These are colored and shaped consistently. I’m basically trying to avoid from repeatedly setting the attributes of the plotted content. I tried using a Dict but couldn’t get to work…

plot(rand(5), color = :green, label = "path")
scatter!(rand(1), rand(1), markershape = :star5, markercolor = :red, label = "home")
scatter!(rand(1), rand(1), markershape = :pentagon, markercolor = :blue, label = "work")

plot(rand(5), proj=:polar, color = :green, label = "path")
scatter!(rand(1), rand(1), markershape = :star5, markercolor = :red, label = "home")
scatter!(rand(1), rand(1), markershape = :pentagon, markercolor = :blue, label = "work")

Any help appreciated!

The Plots ideomatic way is to create a series recipe:

@recipe function f(::Type{Val{:home}}, x,y,z)
       seriescolor := :red
       markershape := :star5
       seriestype := :scatter
       x,y
  end

plot(rand(5), st = :home)

Unfortunately it looks as if Plots is currently ignoring :seriescolor in recipes. Not sure what’s going on there.

1 Like

Oooo, that looks like exactly what I wanted. Except for the color glitch…

1 Like

Posted an issue.

1 Like

Thanks! Oh, and remember you shouldn’t define a new series recipe named :path :slight_smile:

:+1: thank you for that (I would have…)

1 Like

Not a solution, but instead hijacking this thread to get feedback on how I’m solving this in Makie:


path = Theme(color = :green, label = "path")
home = Theme(markershape = :star5, markercolor = :red, label = "home")
work = Theme(markershape = :pentagon, markercolor = :blue, label = "work")

plot(path, rand(5))
scatter!(home, rand(1), rand(1))
scatter!(work, rand(1), rand(1))

plot(path, rand(5), proj = :polar)
scatter!(home, rand(1), rand(1))
scatter!(work, rand(1), rand(1))

I also plan to offer the following, but I still need to figure out how to not make it ambigious:

path = plot(rand(5), color = :green, label = "path")
home = scatter!(rand(1), rand(1), markershape = :star5, markercolor = :red, label = "home")
work = scatter!(rand(1), rand(1), markershape = :pentagon, markercolor = :blue, label = "work")

plot(path, rand(5), proj=:polar)
# the below is ambigious with just updating the `home` plot in place, so it might need to be wrapped in
# `attributes(home)`
scatter!(home, rand(1), rand(1))
scatter!(work, rand(1), rand(1))

Alternatively, this would be the equivalent recipe solution in Makie:


@recipe(path) do scene
    Theme(color = :green, label = "path")
end
@recipe(home) do scene
    Theme(markershape = :star5, markercolor = :red, label = "home")
end
@recipe(work) do scene
    Theme(markershape = :pentagon, markercolor = :blue, label = "work")
end

path = path(rand(5))
home = home!(rand(1), rand(1))
work = work!(rand(1), rand(1))

Do those look like viable solutions as well?

4 Likes

@sdanisch I am looking forward to try Makie.jl and migrate the plot recipes defined in my packages to the Makie.jl-equivalents. What are your plans for the first release of the project?

I think it’s very important to have a Theme object that can collect all defaults for a user (say in a paper one wants to be consistent across all figures), so I think this is a great idea.

The second extra (and ambiguous) method for scatter! is probably not required, couldn’t you instead say that Theme applied to a plot object takes all its attributes? So you would have:

path = plot(rand(5), color = :green, label = "path")
home = scatter!(rand(1), rand(1), markershape = :star5, markercolor = :red, label = "home")
work = scatter!(rand(1), rand(1), markershape = :pentagon, markercolor = :blue, label = "work")

plot(Theme(path), rand(5), proj=:polar)
scatter!(Theme(home), rand(1), rand(1))
scatter!(Theme(work), rand(1), rand(1))

Yes!

@sdanisch how will you call the edge line attribute of a marker? In GMT I’m following the Matlab naming of markerfacecolor and markeredgecolor

One question on a design choice made in Plots – and possibly in Makie?

The basic data structure appears to be a matrix where each column is treated as a series to be plotted. Because of this, if I want to assign different colors to each series, I need to specify a row matrix of colors. Thus, suppose M is a matrix of 3 columns. Then I plot them with different colors by writing.

plot(M, lc=[:red :blue :green])

Alternatively, I can plot a vector of vectors, say vectors v1, v2, v3 by:

plot([v1,v2,v3], lc=[:red :blue :green])

Here, the data are given as a vector of vectors, but I still have to specify the colors as a row matrix. This takes some artificial logic to explain – essentially, that the vector of vectors is a “matrix”.

An alternative approach would be to say that it is a vector of vectors that is the primary data structure for plotting, and thus require line colors, linewidth, linestyle, etc. to be given as a vector of elements, e.g.,

plot([v1,v2,v3], lc=[:red, :blue, :green])

Of course, the artificial logic would still be there for explaining why data could alternatively be given as columns in a matrix. However:

  1. To some degree, the vector of vectors is a slightly more general way to give the data, e.g., if one wants to give the data as functionsplot([sin,cos]),
  2. A list of colors is more easily given as a vector of colors, e.g., as linspace(:red,:blue,5) which produces a vector of colors. In current plots, this must be reshaped into a row matrix before assigned to the linecolor.
  3. It is also (perhaps?) easier to give labels in a for loop as a vector of strings, say if each data series represent a unique parameter value and one wants the label to include information about the parameter value. With today’s syntax, this must be reshaped into a row matrix.

OK – I’m not a super expert on Plots, so there may be other reasons why keyword values are given as row matrices. But with my current understanding, it seems like it would both be somewhat simpler to explain the logics, and simpler to use Plots if a vector of vectors (or a vector of matrices in case of surface plots) were the standard.

Anyway, I’m curious to learn if there are some more hidden depths behind choosing matrix as the fundamental data structure.

3 Likes

@piever

That’s basically what I proposed with

might need wrapping in attributes(home)

Using Theme instead of attributes would also be a good (better?) option!

@juliohm I really hope to have first prototype ready this week. The upper bound for a release is before JuliaCon! I’m also working with a JSOC student on a full documentation for Makie, which should be finished at the end of the coding period (August 14th or so!). So that’s the date you can expect a fairly stable package.

@BLI

I sympathise with most of what you’re saying.
The beauty of Makie is, that the current behaviour of Plots.jl is ported as a recipe itself.
So we’re free to change how to do things as there is nothing in the internals of Makie that will hold as back.
We can also create some default behaviour, and have a completely different behaviour in a surface syntax package - e.g. GGPlot.jl/MakieMatplotlib.jl or whatever :wink:

4 Likes

Just wanted to let you all know that after the last update to Plots, it now works. So :seriescolor is not ignored any longer :slight_smile:

1 Like