Plots.jl annotation with group colours in grouped plot

Dear Plots.jl wizards,

could someone give me advice how to best get annotations in the same colour as the lines when using the group option?

The following is a working example:

using DataFrames, Plots
gr(size=(1200, 800))

dates = [Date(2022, 10, 17), Date(2022, 10, 18), Date(2022, 10, 19)]

baln_df = DataFrame(
    date_ = dates,
    return_index = [1, 1.001407, 1.016973],
    annotation_return = ["", "", "1.7%"],
    stock = "BALN",
)

vow_df = DataFrame(
    date_ = dates,
    stock = "VOW",
    return_index = [1, 0.997758, 1.008594],
    annotation_return = ["", "", "0.86%"],
)

data_df = vcat(baln_df, vow_df)

p1 = plot(
    data_df.date_,
    data_df.return_index,
    group = data_df.stock,
    legend = :topleft,
    series_annotation = data_df.annotation_return,
)`Preformatted text`

I realize I can do the following, but maybe there is something more elegant and reliable?

anno_df = subset(data_df,
    :date_ => ByRow(d -> d == Date(2022, 10, 19))
)

p2 = plot(
    data_df.date_,
    data_df.return_index,
    group = data_df.stock,
    legend = :topleft,
)

for i in 1:length(p2.series_list)
    plot!(
        p2,
        annotations = (
            anno_df.date_[i],
            anno_df.return_index[i],
            Plots.text("$(anno_df.annotation_return[i])", color=p2[1][i][:linecolor], :center)
        )
    )
end

If I want to get colors right across multiple aspects of the plot, What I’ve been doing is grabbing a color palette I like and providing the colors myself rather than using groups.

Well, that is another workaround, for sure. I am looking for a general solution.

Thanks

An alternative, perhaps simpler, is to use StatsPlots and to preprocess the annotation colors before calling plot():

using StatsPlots

g = groupby(data_df, :stock)
n, m = length(g), nrow(first(g))
colors = permutedims(palette(:default)[1:n])
cmat = reduce(vcat, fill.(colors[1:n],m))


@df data_df plot(:date_, :return_index, group=:stock, legend=:topleft, c=colors[:,1:n],
    series_annotation=text.(:annotation_return, cmat, 11, :bottom, "Computer Moderna")
)

1 Like

Thanks a lot Rafael!

Your answer taught me a lot so thank you very much.

As far as I can tell, your solution is not really dependant on StatsPlots, i.e. I would consider this the solution:

using DataFrames, Plots
gr(size=(1200, 800))

dates = [Date(2022, 10, 17), Date(2022, 10, 18), Date(2022, 10, 19)]

baln_df = DataFrame(
    date_ = dates,
    return_index = [1, 1.001407, 1.016973],
    annotation_return = ["", "", "1.7%"],
    stock = "BALN",
)

vow_df = DataFrame(
    date_ = dates,
    stock = "VOW",
    return_index = [1, 0.997758, 1.008594],
    annotation_return = ["", "", "0.86%"],
)

data_df = vcat(baln_df, vow_df)

g = groupby(data_df, :stock)
n, m = length(g), nrow(first(g))
colors = permutedims(palette(:default)[1:n])
cmat = reduce(vcat, fill.(colors[1:n],m))

p3 = plot(
    data_df.date_,
    data_df.return_index,
    group = data_df.stock,
    legend = :topleft,
    series_annotation = text.(data_df.annotation_return, cmat, :bottom, "Computer Moderna", 11)
)

Of course not. It just provides shorter syntax and it might be the right place for a PR.