html_url,issue_url,id,node_id,user,created_at,updated_at,author_association,body,reactions,performed_via_github_app,issue
https://github.com/pydata/xarray/issues/7014#issuecomment-1464031031,https://api.github.com/repos/pydata/xarray/issues/7014,1464031031,IC_kwDOAMm_X85XQ1c3,60435591,2023-03-10T16:07:26Z,2023-03-10T16:07:43Z,CONTRIBUTOR,"Thanks to @jklymak, there was an update in PR I created (https://github.com/pydata/xarray/pull/7553). @ghiggi with the code from this PR, your code shows identical plots (except for the first one, but that should be the case). Hopefully the PR can be merged somewhere soon.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",,1368027148
https://github.com/pydata/xarray/issues/7014#issuecomment-1448588687,https://api.github.com/repos/pydata/xarray/issues/7014,1448588687,IC_kwDOAMm_X85WV7WP,19285200,2023-02-28T17:33:46Z,2023-02-28T17:34:24Z,NONE,"@veenstrajelmer I am not sure I understand what you are saying. In the example I pass only `norm`  to the plotting function ... 
As suggested by @kmuehlbauer the solution to this issue is to specify the `extend` argument in both the plotting call and `cbar_kwargs`. 
In the example, `extend` was only defined in `cbar_kwargs` (since is an argument also of [mpl.Figure.colorbar](https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure.colorbar)). Likely we should align the arguments  somewhere in the code with something like this:

```
if extend is None and cbar_kwargs.get(""extend) is not None: 
   extend = cbar_kwargs.get(""extend) 
if extend is not None and ""extend"" not in  cbar_kwargs
   cbar_kwargs[""extend""] = extend 
```
   
  ","{""total_count"": 1, ""+1"": 1, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",,1368027148
https://github.com/pydata/xarray/issues/7014#issuecomment-1448311848,https://api.github.com/repos/pydata/xarray/issues/7014,1448311848,IC_kwDOAMm_X85WU3wo,60435591,2023-02-28T14:46:32Z,2023-02-28T14:46:32Z,CONTRIBUTOR,"@ghiggi: If I understand it correctly, your issue/examplecode covers multiple issues. Since one subissue might be using `norm` instead `levels`, I would recommend trimming down your example code do only show the remaining `vmax` issue.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",,1368027148
https://github.com/pydata/xarray/issues/7014#issuecomment-1446791387,https://api.github.com/repos/pydata/xarray/issues/7014,1446791387,IC_kwDOAMm_X85WPEjb,19285200,2023-02-27T17:58:15Z,2023-02-27T22:22:44Z,NONE,"Thanks to all the people above that have started digging into the problem ! 
@veenstrajelmer : Adding the `levels=levels` argument (together with `norm`, ... or dropping `norm`) does not correct/change the output figure.
Of course commenting `#da1.data[da1.data>=norm.vmax] = norm.vmax - 1` ""solves"" the issue, but this line of code is what enables to show up **the bug, which is occurring when the array does not contain any value equal to or higher than `norm.vmax `**","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",,1368027148
https://github.com/pydata/xarray/issues/7014#issuecomment-1446128457,https://api.github.com/repos/pydata/xarray/issues/7014,1446128457,IC_kwDOAMm_X85WMitJ,60435591,2023-02-27T11:06:40Z,2023-02-27T11:18:17Z,CONTRIBUTOR,"The related issues https://github.com/pydata/xarray/issues/4061 and https://github.com/Deltares/xugrid/issues/49 are fixed by supplying `levels=levels` instead of `norm=norm` to `ds.plot()`, as suggested by https://github.com/pydata/xarray/pull/7553#discussion_r1117264787. However, I cannot judge if this also fixes the example from this issue, since the plots still all look different. However, commenting `#da1.data[da1.data>=norm.vmax] = norm.vmax - 1` solves most of the differences. @ghiggi: could you check if this suggestion solves your issue indeed?","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",,1368027148
https://github.com/pydata/xarray/issues/7014#issuecomment-1441530710,https://api.github.com/repos/pydata/xarray/issues/7014,1441530710,IC_kwDOAMm_X85V7ANW,60435591,2023-02-23T10:36:36Z,2023-02-23T15:55:21Z,CONTRIBUTOR,"I just combined @Huite's suggestion with splitting the if-statement. This works for both solving the issue and keeping the testcases in `test_plot.py` green.
```
    if levels is not None:
        cmap, newnorm = _build_discrete_cmap(cmap, levels, extend, filled)
        norm = newnorm if norm is None else norm
    if isinstance(norm, mpl.colors.BoundaryNorm):
        cmap, norm = _build_discrete_cmap(cmap, levels, extend, filled)
```
This could replace this code:
https://github.com/pydata/xarray/blob/1de881e5f5d4d7697403f9ab7686849ab02953bd/xarray/plot/utils.py#L307

However, a bit up in the code there is a `if isinstance(norm, mpl.colors.BoundaryNorm)` statement, which I guess could be combined. https://github.com/pydata/xarray/blob/1de881e5f5d4d7697403f9ab7686849ab02953bd/xarray/plot/utils.py#L277 
```
    if isinstance(norm, mpl.colors.BoundaryNorm):
        levels = norm.boundaries
```

I think it is a potential solution nevertheless, but some help is appreciated with the last steps. Also since the case of @ghiggi seems not to be solved with this fix. It does solve https://github.com/pydata/xarray/issues/4061 though.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",,1368027148
https://github.com/pydata/xarray/issues/7014#issuecomment-1412027546,https://api.github.com/repos/pydata/xarray/issues/7014,1412027546,IC_kwDOAMm_X85UKdSa,13662783,2023-02-01T13:06:09Z,2023-02-01T13:06:45Z,CONTRIBUTOR,"Debugging this, @headtr1ck points correctly to _determine_cmap_params:

```python
    if levels is not None or isinstance(norm, mpl.colors.BoundaryNorm):
        cmap, newnorm = _build_discrete_cmap(cmap, levels, extend, filled)
        norm = newnorm if norm is None else norm
```

The problem lies in the second line. In `_build_discrete_cmap`, a new cmap is returned with a different number of levels. However, if the original `norm` is not None, you end up with a mismatch, as the old norm expects the old cmap.

What then happens, is that the norm calls into the cmap. Calling into cmap doesn't do any checks whether the value is larger than N, it just takes the highest available value. The examples in #4061 show this quite clearly, but to illustrate:

```python
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np

data = np.arange(100).reshape((10, 10))
cmap = mpl.cm.get_cmap(""viridis"")
print(cmap.N)   # 256
boundaries = [0, 25, 50, 75, 100]
norm = mpl.colors.BoundaryNorm(boundaries, cmap.N)
fig, ax = plt.subplots()
ax.imshow(data, norm=norm, cmap=cmap)

# %%

colors = [cmap(i/255) for i in np.linspace(0, cmap.N, len(boundaries) - 1)]
new_cmap, new_norm = mpl.colors.from_levels_and_colors(boundaries, colors)
print(new_cmap.N)  # 4
fig, ax = plt.subplots()
ax.imshow(data, norm=new_norm, cmap=new_cmap)

# %%
# Mismatched

fig, ax = plt.subplots()
ax.imshow(data, norm=norm, cmap=new_cmap)

# %%


```

This is avoided here by removing the conditional in the second line, or just making sure both cmap and norm are replaced by their new values:

```python
    if levels is not None or isinstance(norm, mpl.colors.BoundaryNorm):
        cmap, norm = _build_discrete_cmap(cmap, levels, extend, filled)
```

Then, the cmap and norm remain in sync.

However, when running the tests in `test_plot.py`, this gives a single questionable(?) failure:

```python
    def test_norm_sets_vmin_vmax(self) -> None:
        vmin = self.data.min()
        vmax = self.data.max()

        for norm, extend, levels in zip(
            [
                mpl.colors.Normalize(),
                mpl.colors.Normalize(),
                mpl.colors.Normalize(vmin + 0.1, vmax - 0.1),
                mpl.colors.Normalize(None, vmax - 0.1),
                mpl.colors.Normalize(vmin + 0.1, None),
            ],
            [""neither"", ""neither"", ""both"", ""max"", ""min""],
            [7, None, None, None, None],
        ):

            test_min = vmin if norm.vmin is None else norm.vmin
            test_max = vmax if norm.vmax is None else norm.vmax

            cmap_params = _determine_cmap_params(self.data, norm=norm, levels=levels)
            assert cmap_params[""vmin""] is None
            assert cmap_params[""vmax""] is None
            assert cmap_params[""norm""].vmin == test_min
            assert cmap_params[""norm""].vmax == test_max
            assert cmap_params[""extend""] == extend
>           assert cmap_params[""norm""] == norm
E           assert <matplotlib.colors.BoundaryNorm object at 0x000001C7A4CB07C0> == <matplotlib.colors.Normalize object at 0x000001C7A50C4760>
```

I don't understand why the conditional is there. At first sight, it doesn't make a lot of sense to create a new norm and cmap, but then take the original norm?
","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",,1368027148
https://github.com/pydata/xarray/issues/7014#issuecomment-1410507317,https://api.github.com/repos/pydata/xarray/issues/7014,1410507317,IC_kwDOAMm_X85UEqI1,5821660,2023-01-31T14:53:40Z,2023-01-31T14:53:40Z,MEMBER,"I'm still struggling to understand that whole workflow and it looks like this is no easy way of getting to the bottom of this.

But adding kwarg `extend=""max""` to the calls to `pcolormesh`/`imshow` fixes this. 

The docstring gives a hint:

```
extend : {'neither', 'both', 'min', 'max'}, optional
    How to draw arrows extending the colorbar beyond its limits. If not
    provided, ``extend`` is inferred from ``vmin``, ``vmax`` and the data limits.
```

So in first case everything works nice since `extend` is derived from `vmin`/`vmax` and data limits. In the second case, `extend` is obviously not set to the expected value (as in `cbar_kwargs`) as we've tampered with the maximum data values.

Not sure if aligning `extend` and `cbar_kwargs`-extend is already the solution.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",,1368027148
https://github.com/pydata/xarray/issues/7014#issuecomment-1410362988,https://api.github.com/repos/pydata/xarray/issues/7014,1410362988,IC_kwDOAMm_X85UEG5s,60435591,2023-01-31T13:32:44Z,2023-01-31T13:32:44Z,CONTRIBUTOR,Is there any update on this issue? I have been running into the same problem recently and am happy to see that this issue was already recognized by others.,"{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",,1368027148
https://github.com/pydata/xarray/issues/7014#issuecomment-1242695626,https://api.github.com/repos/pydata/xarray/issues/7014,1242695626,IC_kwDOAMm_X85KEgfK,43316012,2022-09-10T10:23:19Z,2022-09-10T10:23:19Z,COLLABORATOR,"It seems that the `cmap` gets replaced by xarray in https://github.com/pydata/xarray/blob/c0011e13b1edabcd541610d7fbef084bcb387183/xarray/plot/plot.py#L1182
which calls 
https://github.com/pydata/xarray/blob/c0011e13b1edabcd541610d7fbef084bcb387183/xarray/plot/utils.py#L854
which then calls
https://github.com/pydata/xarray/blob/c0011e13b1edabcd541610d7fbef084bcb387183/xarray/plot/utils.py#L154
and this method has some special casing of `BoundaryNorm` that will create a new `cmap`

Not sure what all this code is supposed to do, though...
But hopefully these findings can help someone with better understanding to debug this :)","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",,1368027148