proplot-dev / proplot

🎨 A succinct matplotlib wrapper for making beautiful, publication-quality graphics
https://proplot.readthedocs.io
MIT License
1.11k stars 102 forks source link

Colorbar level gone when too small #372

Open mothersup opened 2 years ago

mothersup commented 2 years ago

Description

I was plotting a colorbar using a custom colormap and levels using ax.colorbar. However, sometimes I observed that the plot would miss out one level (and its color). After trying with different values I found that when the length of the level is too small, this level would be gone. I tried using values, norm=DiscreteNorm and using DiscreteNorm and DiscreteColormap, but all these methods failed to make the correct plot.

In the following I will demonstrate this with a colorbar consisting of colors 'blue', 'orange' and 'green', set at levels $5$, $60$ and $130$ respectively. In the three plots presented, one of the colors is missing.

As for why I am plotting a colorbar instead of using ScalerMappable, I am plotting a categorical map using geopandas. The ScalarMappable generated cannot be used to add a colorbar, so I have to build it from colors and values.

Steps to reproduce

Method 1: Using values

import proplot as pplt

fig, ax = pplt.subplots()
ax.colorbar(
    ['blue', 'orange', 'green'],
    values=[0, 5, 60, 130],
    spacing='proportional',
    ticks=10
)

Method 2: Using norm = pplt.DiscreteNorm

import proplot as pplt

fig, ax = pplt.subplots()
ax.colorbar(
    ['blue', 'orange', 'green'],
    norm=pplt.DiscreteNorm([0, 5, 60, 130]),
    spacing='proportional',
    ticks=10
)

Method 3: Using pplt.DiscreteNorm and pplt.DiscreteColormap in cm.ScalarMappable

from matplotlib.cm import ScalarMappable
import proplot as pplt

fig, ax = pplt.subplots()
ax.colorbar(
    ScalarMappable(
        pplt.DiscreteNorm([0, 5, 60, 130]),
        pplt.DiscreteColormap(['blue', 'orange', 'green']),
    ),
    spacing='proportional',
    ticks=10
)

Expected behavior: fig_1

Actual behavior:

Method 1

fig_m1

Method 2

fig_m2

Method 3

fig_m3

Equivalent steps in matplotlib

I do not fully use matplotlib to reproduce. Instead, I can simply use colors.BoundaryNorm and colors.ListedColormap to build the ScalarMappable to obtain the expected hebavior.

import proplot as pplt
from matplotlib import cm, colors

fig, ax = pplt.subplots()
ax.colorbar(
    cm.ScalarMappable(
        colors.BoundaryNorm([0, 5, 60, 130], ncolors=3), 
        colors.ListedColormap(['blue', 'orange', 'green']),
    ),
    spacing='proportional',
    ticks=10
)

Extra Info

If I reduce the maximum value from 130 to 120, then Method 2 gives the expected plot.

fig_e1

Method 2 (reduced maximum)

import proplot as pplt

fig, ax = pplt.subplots()
ax.colorbar(
    ['blue', 'orange', 'green'],
    norm=pplt.DiscreteNorm([0, 5, 60, 120]),
    spacing='proportional',
    ticks=10
)

Proplot version