Labels for levels in contour plot (Plots.jl)

I would like to add custom labels to a contour plot, replacing the levels. Is this possible? MWE:

using Plots; pyplot() # other backend OK if necessary
using StatsBase
using Distributions
using StatsFuns

N = 100000
z = rand(MvNormal([1.0, 0.0], [1.0 0.5; 0.5 0.5]), N);
x = logistic.(z[1, :]);
y = logistic.(z[2, :]);
h = fit(Histogram, (x, y), closed = :left, nbins = (50, 50));

gap = trunc(Int, maximum(h.weights) / 9)
levels = 0:gap:(10*gap)
level_labels = map(x -> "$(x*10)%", 0:10)

contourf(midpoints(h.edges[1]),
         midpoints(h.edges[2]),
         h.weights';
         levels = levels)       # how to put level_labels in?
1 Like

I realized that for printed output, I don’t need contourf, contour would do just fine. But how can I

  1. make all the lines black, and
  2. put labels on them?

Eg continuing the above code,

contour(midpoints(h.edges[1]),
        midpoints(h.edges[2]),
        h.weights';
        color = "black",        # how to change contour lines to black? this does not work?
        colorbar = false,
        levels = levels)        # how to put level_labels in?

I realized that I can go to the backend as a workaround:

using PyPlot
using StatsBase
using Distributions
using StatsFuns

N = 100000
z = rand(MvNormal([1.0, 0.0], [1.0 0.5; 0.5 0.5]), N);
x = logistic.(z[1, :]);
y = logistic.(z[2, :]);
h = fit(Histogram, (x, y), closed = :left, nbins = (20, 20));

gap = trunc(Int, maximum(h.weights) / 9)
levels = collect(0:gap:(10*gap))
level_labels = map(x -> "$(x*10)%", 0:10)

PyPlot.svg(true)
cs = contour(midpoints(h.edges[1]),
             midpoints(h.edges[2]),
             h.weights';
             colors = "black",
             levels = levels)
clabel(cs, fmt = Dict(zip(levels, level_labels)), inline = 1)

x

2 Likes

A related (?) problem… I have tried to use Plots to do a 3d surface plot with contours (useful, e.g., in illustrating optimization problems). However, I was not able to freely choose the z-position of the contours – this seems to be hard-coded to z=0. If the minimum value of f(x,y) is “small” (close to zero or lower), the surface plot will often hide the contours. When I tried to learn Matplotlib some years ago, I found it was relatively straightforward to set the z-position of the contours, and I’d think this is very useful also in Plots.

[OK – perhaps this is possible in Plots – I just haven’t found any documentation of it.]

Part solutions:
define constant color map:
color = cgrad([:black,:black,:black])
this seems to also work (though I’ve seen glitches with this)
color = :black

show contour labels
contour_labels = true

These usages seem to require the latest master.

I haven’t come across a way to use custom labels for the contour levels so far.

1 Like

Bumping up since the title is the same I wanted. Tried something similar to what @sschelm suggested. I am using Julia 1.1 and Plots 0.26.2. Full working example is below. Works fine without contour_labels = true and used to work with ‘clabels = true’ but now it doesn’t.

Any ideas on how to get that sorted?

using Plots, LaTeXStrings
pyplot()

n = 500

x1 = range(-1, stop=10, length=n);
x2 = range(-1, stop=10, length=n);

f(x) = (x[1])^2 + (x[2])^2
z = [f([x1[i], x2[j]]) for j = 1:n, i = 1:n];

contour(x1,x2,z,
      levels = [4, 8, 12, 16],
      xaxis = (L"$x_1$", (0,4), (0:1:10)),
      yaxis = (L"$x_2$", (0,4)),
      clims = (1,15),
      contour_labels = true,
      aspect_ratio = :equal
)

I get an error that looks like this:

MethodError: no method matching getindex(::Base.Generator{Tuple{Int64,Int64},getfield(Plots, Symbol(“##280#282”)){Float64}}, ::Int64)
(::getfield(Plots, Symbol(“##279#281”)){Base.Generator{Tuple{Int64,Int64},getfield(Plots, Symbol(“##280#282”)){Float64}},Array{Int64,1}})(::Int64) at none:0
iterate at generator.jl:47 [inlined]
mapfoldl_impl(::Function, ::Function, ::NamedTuple{(),Tuple{}}, ::Base.Generator{UnitRange{Int64},getfield(Plots, Symbol(“##279#281”)){Base.Generator{Tuple{Int64,Int64},getfield(Plots, Symbol(“##280#282”)){Float64}},Array{Int64,1}}}) at reduce.jl:55
#mapfoldl#187 at reduce.jl:72 [inlined]
mapfoldl at reduce.jl:72 [inlined]
#mapreduce#191 at reduce.jl:205 [inlined]
mapreduce(::Function, ::Function, ::Base.Generator{UnitRange{Int64},getfield(Plots, Symbol(“##279#281”)){Base.Generator{Tuple{Int64,Int64},getfield(Plots, Symbol(“##280#282”)){Float64}},Array{Int64,1}}}) at reduce.jl:205
minimum(::Base.Generator{UnitRange{Int64},getfield(Plots, Symbol(“##279#281”)){Base.Generator{Tuple{Int64,Int64},getfield(Plots, Symbol(“##280#282”)){Float64}},Array{Int64,1}}}) at reduce.jl:503
showjuno(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::MIME{Symbol(“image/svg+xml”)}, ::Plots.Plot{Plots.PyPlotBackend}) at output.jl:238
show(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::MIME{Symbol(“image/svg+xml”)}, ::Plots.Plot{Plots.PyPlotBackend}) at output.jl:195
__binrepr at multimedia.jl:129 [inlined]
_textrepr at multimedia.jl:119 [inlined]
#stringmime#6 at Base64.jl:38 [inlined]
(::getfield(Base64, Symbol(“#kw##stringmime”)))(::NamedTuple{(:context,),Tuple{IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}}}, ::typeof(Base64.stringmime), ::MIME{Symbol(“image/svg+xml”)}, ::Plots.Plot{Plots.PyPlotBackend}) at none:0
#stringmime#7(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::Function, ::String, ::Plots.Plot{Plots.PyPlotBackend}) at Base64.jl:39
#stringmime at none:0 [inlined]
displayinplotpane(::Plots.Plot{Plots.PyPlotBackend}) at showdisplay.jl:55
displayandrender(::Plots.Plot{Plots.PyPlotBackend}) at showdisplay.jl:115
(::getfield(Atom, Symbol(“##129#134”)))() at eval.jl:142
#invokelatest#1 at essentials.jl:742 [inlined]
invokelatest at essentials.jl:741 [inlined]
(::getfield(Atom, Symbol(“##128#133”)){String,String,Module})() at eval.jl:141
withpath(::getfield(Atom, Symbol(“##128#133”)){String,String,Module}, ::String) at utils.jl:30
withpath at eval.jl:46 [inlined]
#127 at eval.jl:122 [inlined]
with_logstate(::getfield(Atom, Symbol(“##127#132”)){String,String,Module}, ::Base.CoreLogging.LogState) at logging.jl:395
with_logger at logging.jl:491 [inlined]
#126 at eval.jl:121 [inlined]
hideprompt(::getfield(Atom, Symbol(“##126#131”)){String,String,Module}) at repl.jl:75
macro expansion at eval.jl:120 [inlined]
macro expansion at dynamic.jl:24 [inlined]
(::getfield(Atom, Symbol(“##125#130”)))(::Dict{String,Any}) at eval.jl:109
handlemsg(::Dict{String,Any}, ::Dict{String,Any}) at comm.jl:164
(::getfield(Atom, Symbol(“##19#21”)){Array{Any,1}})() at task.jl:259

Bumping this again to see if there is a solution now. The older solutions above do not yield working work-arounds.

I understand from this discourse that there is a (complicated) way to do this in Makie.jl right now, but I’d prefer to use GR or pyplot.

I would be satisfied with even a simpler solution than the above, to just add a legend with the levels as items and custom labels, but I’m not even sure how to do this. Any ideas?

I’m working on adding a page to the Plots.jl docs for contour plots and I’m wondering if this is still possible. It’d be nice to have the features illustrated on this page. @Tamas_Papp is the workaround that you wrote for having percentages still working? I’ve filed an issue here.

Not sure, I no longer use PyPlot. I use PGFPlotsX.jl where this is possible.

I think modifying contour_labels to take a boolean or a Vector{String} would be the easiest (and non breaking).

It is only a matter of someone submitting a PR.

Using the Contour.jl package, we can annotate individual contours to get: