MakieOrg / Makie.jl

Interactive data visualizations and plotting in Julia
https://docs.makie.org/stable
MIT License
2.39k stars 302 forks source link

Cannot create `Colorbar` from `contourf` of zeros #3470

Open Sbozzolo opened 9 months ago

Sbozzolo commented 9 months ago

MWE, latest version of everything:

import CairoMakie
x = [0., 1.]
y = [0., 2.]
z = zeros(2,2)

fig = CairoMakie.Figure()
CairoMakie.Axis(fig[1,1])
plot = CairoMakie.contourf!(x, y, z)
CairoMakie.Colorbar(fig[1,2], plot)
Fails with ``` ERROR: LoadError: ArgumentError: range step cannot be zero Stacktrace: [1] (::Colon)(start::Float32, step::Float32, stop::Float32) @ Base ./twiceprecision.jl:394 [2] get_minor_tickvalues(i::Makie.IntervalsBetween, scale::Function, tickvalues::Vector{Float32}, vmin::Float32, vmax::Float32) @ Makie ~/.julia/packages/Makie/RgxaV/src/makielayout/lineaxis.jl:734 [3] (::Makie.var"#1436#1450"{MakieCore.Attributes, Observables.Observable{Vector{Float32}}, Observables.Observable{Tuple{Float32, Float32}}})(tickvalues::Vector{Float32}, minorticks::Makie.IntervalsBetween) @ Makie ~/.julia/packages/Makie/RgxaV/src/makielayout/lineaxis.jl:431 [4] invokelatest(::Any, ::Any, ::Vararg{Any}; kwargs::@Kwargs{}) @ Base ./essentials.jl:887 [5] invokelatest(::Any, ::Any, ::Vararg{Any}) @ Base ./essentials.jl:884 [6] (::Observables.OnAny)(value::Any) @ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:415 [7] #invokelatest#2 @ Base ./essentials.jl:887 [inlined] [8] invokelatest @ Base ./essentials.jl:884 [inlined] [9] notify @ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:169 [inlined] [10] setindex!(observable::Observables.Observable, val::Any) @ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:86 [11] update_tickpos_string(closure_args::Tuple{Observables.Observable{Vector{Any}}, Observables.Observable{Vector{GeometryBasics.Point{2, Float32}}}, Observables.Observable{Vector{Float32}}, Observables.Observable{Tuple{Float32, Tuple{Float32, Float32}, Bool}}, Observables.Observable{Tuple{Float32, Float32}}}, tickvalues_labels_unfiltered::Tuple{Vector{Float32}, Vector{Any}}, reversed::Bool, scale::typeof(identity)) @ Makie ~/.julia/packages/Makie/RgxaV/src/makielayout/lineaxis.jl:209 [12] invokelatest(::Any, ::Any, ::Vararg{Any}; kwargs::@Kwargs{}) @ Base ./essentials.jl:887 [13] invokelatest(::Any, ::Any, ::Vararg{Any}) @ Base ./essentials.jl:884 [14] (::Observables.OnAny)(value::Any) @ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:415 [15] #invokelatest#2 @ Base ./essentials.jl:887 [inlined] [16] invokelatest @ Base ./essentials.jl:884 [inlined] [17] notify @ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:169 [inlined] [18] setindex!(observable::Observables.Observable, val::Any) @ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:86 [19] (::Observables.MapCallback)(value::Any) @ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:431 [20] #invokelatest#2 @ Base ./essentials.jl:887 [inlined] [21] invokelatest @ Base ./essentials.jl:884 [inlined] [22] notify @ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:169 [inlined] [23] Makie.LineAxis(parent::Makie.Scene, attrs::MakieCore.Attributes) @ Makie ~/.julia/packages/Makie/RgxaV/src/makielayout/lineaxis.jl:472 [24] #LineAxis#1413 @ Makie ~/.julia/packages/Makie/RgxaV/src/makielayout/lineaxis.jl:3 [inlined] [25] initialize_block!(cb::Makie.Colorbar) @ Makie ~/.julia/packages/Makie/RgxaV/src/makielayout/blocks/colorbar.jl:371 [26] _block(::Type{Makie.Colorbar}, ::Makie.Figure; bbox::Nothing, kwargs::@Kwargs{colormap::Makie.ColorMapping{1, Vector{Float32}, Vector{Float32}}}) @ Makie ~/.julia/packages/Makie/RgxaV/src/makielayout/blocks.jl:374 [27] _block(::Type{Makie.Colorbar}, ::GridLayoutBase.GridPosition; kwargs::@Kwargs{colormap::Makie.ColorMapping{1, Vector{Float32}, Vector{Float32}}}) @ Makie ~/.julia/packages/Makie/RgxaV/src/makielayout/blocks.jl:268 [28] _block @ Makie ~/.julia/packages/Makie/RgxaV/src/makielayout/blocks.jl:261 [inlined] [29] #_#1262 @ Makie ~/.julia/packages/Makie/RgxaV/src/makielayout/blocks.jl:253 [inlined] [30] Makie.Colorbar(fig_or_scene::GridLayoutBase.GridPosition, plot::MakieCore.Combined{Makie.contourf, Tuple{Vector{Float32}, Vector{Float32}, Matrix{Float32}}}; kwargs::@Kwargs{}) @ Makie ~/.julia/packages/Makie/RgxaV/src/makielayout/blocks/colorbar.jl:116 [31] Makie.Colorbar(fig_or_scene::GridLayoutBase.GridPosition, plot::MakieCore.Combined{Makie.contourf, Tuple{Vector{Float32}, Vector{Float32}, Matrix{Float32}}}) @ Makie ~/.julia/packages/Makie/RgxaV/src/makielayout/blocks/colorbar.jl:98 [32] top-level scope @ /tmp/testi/mwe.jl:8 in expression starting at /tmp/testi/mwe.jl:8 ```

Possibly related https://github.com/MakieOrg/Makie.jl/issues/3174 Precisely same issue as https://github.com/MakieOrg/Makie.jl/issues/1738

I tried working around the problem by manually specifying levels in the contour plot (didn't work), or by specifying limits in the bar (wouldn't allow me to).

jkrumbiegel commented 9 months ago

I think you cannot even create a contourf plot from all zeros because where would it draw any lines. Not sure what a good behavior here would be, maybe an early error? What do other plotting packages do here?

Sbozzolo commented 9 months ago

My expectation is that contourf of a constant is a plot filled with one single color, and the colorbar is centered around that color. If you think about the volcano example in the documentation for contourf, what we are plotting there is the elevation profile, and a plot with constant elevation still makes perfect sense and should be just filled with a single color.

This is what happens in matplotlib.

(But I agree that a contour of a constant doesn't make too much sense.)

#!/usr/bin/env python3

import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure(figsize=(6,5))
left, bottom, width, height = 0.1, 0.1, 0.8, 0.8
ax = fig.add_axes([left, bottom, width, height])

start, stop, n_values = -8, 8, 800

x_vals = np.linspace(start, stop, n_values)
y_vals = np.linspace(start, stop, n_values)
X, Y = np.meshgrid(x_vals, y_vals)

Z = 0 * np.sqrt(X**2 + Y**2)

cp = plt.contourf(X, Y, Z)
plt.colorbar(cp)

ax.set_title('Contour Plot')
ax.set_xlabel('x (cm)')
ax.set_ylabel('y (cm)')
plt.show()

image

trontrytel commented 9 months ago

We ran into this issue when plotting from a large dataset where only sometimes the data is all zeros. I think it would be nicer user experience to get a constant plot in such cases, instead of having to write your own try/catch.