Adding annotations with custom font and color in plot recipes

I have created a plot recipe for one of my types in GeoStats.jl:

@recipe function f(γ::EmpiricalVariogram; bincounts=true)
  # get the data
  x, y, n = values(γ)

  # discard empty bins
  x = x[n .> 0]; y = y[n .> 0]; n = n[n .> 0]

  # draw bin counts as a measure of confidence
  if bincounts
    @series begin
      # plot a "frequency" instead of raw counts
      f = n*(maximum(y) / maximum(n)) / 10.

      seriestype := :bar
      fillalpha := .5
      color := :blue
      label := "bin counts"

      x, f
    end

    # annotate the bins
    @series begin
      seriestype := :scatter
      primary := false
      markersize := 0
      series_annotations := string.(n) # how to set the font size and color?

      x, zeros(x) + .001
    end
  end

  seriestype := :scatter
  xlim := (0, γ.maxlag)
  color --> :orange
  xlabel --> "Lag h"
  ylabel --> "Variogram(h)"
  label --> "variogram"

  x, y
end

It is working nicely, except that I don’t know how to set the font size and color for the annotations in the bins. Could someone help? Is it possible to control font and color without depending on Plots.jl? I would like to have RecipesBase.jl as my only dependency.

The problem is that with too many bins, the annotations overlap. If you know a better approach to annotating in this case, I’d love to hear.

@mkborregaard could you please give a hand? Sorry pinging you, I couldn’t find another resource online to solve this issue by myself, and you are the only knowledgeable Plots.jl adviser I know of :slight_smile:

Hi Julio,

I’m not really an expert with plot recipes, but I’ll give this a shot to try and help out @mkborregaard

It appears that series[:series_annotations] is supposed to be a sort of iterable (collection) called Plots.SeriesAnnotations.

It also appears that the best way to construct this object is by using the series_annotations() function. I would therefore try replacing:

#Old code:
series_annotations := string.(n)

#New code:
series_annotations := Plots.series_annotations(string.(n), Plots.font("Sans", 12))
#NOTE: You can add more parameters to better control "font" (including a "Colorant")

Note that Plots would then use the same font for all annotations.

It also appears that if you want different fonts for each entry of n, then the first argument of series_annotations() must be a vector of PlotText (instead of a vector of String).

If you want to see how to construct a PlotText object (simple structure storing String, Font), than take a look at the components.jl file. The “constructor” function is called text() (defined just below definition of PlotText).

I would give you executable code… but like I said, I have never figured out how to build my own plot recipes… sorry.

1 Like

Thank you very much @MA_Laforge, that is very helpful :slight_smile:

So we can say that in order to control font size and color we have to depend on Plots.jl, correct?

If you qualify the references like this:

series_annotations := Main.Plots.series_annotations(string.(n), Main.Plots.font("Sans", 12))

you don’t need to have using Plots in your module, so it wouldn’t be a formal package dependency.

I sometimes use this trick to speed up development of my projects (no wait for Plots to load when I am working on computational code), but I don’t know if it violates any packaging rules.

3 Likes

@juliohm: So we can say that in order to control font size and color we have to depend on Plots.jl, correct?

I was not aware of this (Still a bit ignorant of how recipes work).

My best guess:

Unless RecipesBase (or some other module) defines another similar structure to what I described above (that supposedly gets converted back to Plots.series_annotations for Plots.jl), then my guess is: yes… you technically should depend on Plots.jl.

However:

To me, this would seem to indicate that the definition of series_annotations should be moved to RecipesBase in order to avoid overly complex dependency trees.

Update request:

@juliohm: could you please inform me whether this solution actually works. I am quite curious to know if there is not something else that needs to be addressed to get this working.

@Ralph_Smith: Very interesting comment. I am quite surprised to learn this makes a difference.

I personally get the feeling it is violating a packaging rule of some sort… but I am not certain either.

That is a good trick @Ralph_Smith :slight_smile:

@tkelman, could you share your thoughts on this? Does the trick violate the packaging rules? Having control over fonts and size would be very useful in my plot recipes.

Will the code ever execute without Plots having been loaded? Note that the fact that all packages load into Main is likely to change in the future.

1 Like

The code would only execute with Plots.jl installed, it is written inside of a plot recipe. I think the issue here is in the Plots/RecipesBase separation. Currently, font and size settings are not part of RecipesBase.

I will open an issue to make sure this is the case.

This is just a current Plots.jl limitation due to it using the Font type which is not in RecipesBase.jl. Honestly, the true answer is to get rid of that type to make font choices more similar to other arguments.

https://github.com/JuliaPlots/Plots.jl/issues/555

2 Likes

Nice idea, @ChrisRackauckas . Given your recent refactor it makes sense to try to put this functionality into RecipesBase too, if possible. I’d be happy to consider a PR.

If it’s all standard attributes and no font object, then there shouldn’t be anything in RecipesBase needed for this to work.

Hmm OK I’ll give it some thought.