🌐 AI搜索 & 代理 主页
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/api/next_api_changes/behavior/30824-AYS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Bivariate colormaps now fully span the intended range of colors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Bivariate colormaps generated by ``SegmentedBivarColormap`` (e.g., ``BiOrangeBlue``)
from a set of input colors now fully span that range of colors. There had been a bug
with the numerical interpolation such that the colormap did not actually include the
first or last colors.
34 changes: 5 additions & 29 deletions lib/matplotlib/_cm_bivar.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# auto-generated by https://github.com/trygvrad/multivariate_colormaps
# date: 2024-05-24

import numpy as np
from matplotlib.colors import SegmentedBivarColormap

# auto-generated by https://github.com/trygvrad/multivariate_colormaps
# date: 2024-05-24
BiPeak = np.array(
[0.000, 0.674, 0.931, 0.000, 0.680, 0.922, 0.000, 0.685, 0.914, 0.000,
0.691, 0.906, 0.000, 0.696, 0.898, 0.000, 0.701, 0.890, 0.000, 0.706,
Expand Down Expand Up @@ -1276,32 +1275,9 @@
]).reshape((65, 65, 3))

BiOrangeBlue = np.array(
[0.000, 0.000, 0.000, 0.000, 0.062, 0.125, 0.000, 0.125, 0.250, 0.000,
0.188, 0.375, 0.000, 0.250, 0.500, 0.000, 0.312, 0.625, 0.000, 0.375,
0.750, 0.000, 0.438, 0.875, 0.000, 0.500, 1.000, 0.125, 0.062, 0.000,
0.125, 0.125, 0.125, 0.125, 0.188, 0.250, 0.125, 0.250, 0.375, 0.125,
0.312, 0.500, 0.125, 0.375, 0.625, 0.125, 0.438, 0.750, 0.125, 0.500,
0.875, 0.125, 0.562, 1.000, 0.250, 0.125, 0.000, 0.250, 0.188, 0.125,
0.250, 0.250, 0.250, 0.250, 0.312, 0.375, 0.250, 0.375, 0.500, 0.250,
0.438, 0.625, 0.250, 0.500, 0.750, 0.250, 0.562, 0.875, 0.250, 0.625,
1.000, 0.375, 0.188, 0.000, 0.375, 0.250, 0.125, 0.375, 0.312, 0.250,
0.375, 0.375, 0.375, 0.375, 0.438, 0.500, 0.375, 0.500, 0.625, 0.375,
0.562, 0.750, 0.375, 0.625, 0.875, 0.375, 0.688, 1.000, 0.500, 0.250,
0.000, 0.500, 0.312, 0.125, 0.500, 0.375, 0.250, 0.500, 0.438, 0.375,
0.500, 0.500, 0.500, 0.500, 0.562, 0.625, 0.500, 0.625, 0.750, 0.500,
0.688, 0.875, 0.500, 0.750, 1.000, 0.625, 0.312, 0.000, 0.625, 0.375,
0.125, 0.625, 0.438, 0.250, 0.625, 0.500, 0.375, 0.625, 0.562, 0.500,
0.625, 0.625, 0.625, 0.625, 0.688, 0.750, 0.625, 0.750, 0.875, 0.625,
0.812, 1.000, 0.750, 0.375, 0.000, 0.750, 0.438, 0.125, 0.750, 0.500,
0.250, 0.750, 0.562, 0.375, 0.750, 0.625, 0.500, 0.750, 0.688, 0.625,
0.750, 0.750, 0.750, 0.750, 0.812, 0.875, 0.750, 0.875, 1.000, 0.875,
0.438, 0.000, 0.875, 0.500, 0.125, 0.875, 0.562, 0.250, 0.875, 0.625,
0.375, 0.875, 0.688, 0.500, 0.875, 0.750, 0.625, 0.875, 0.812, 0.750,
0.875, 0.875, 0.875, 0.875, 0.938, 1.000, 1.000, 0.500, 0.000, 1.000,
0.562, 0.125, 1.000, 0.625, 0.250, 1.000, 0.688, 0.375, 1.000, 0.750,
0.500, 1.000, 0.812, 0.625, 1.000, 0.875, 0.750, 1.000, 0.938, 0.875,
1.000, 1.000, 1.000,
]).reshape((9, 9, 3))
[0.0, 0.0, 0.0, 0.0, 0.5, 1.0,
1.0, 0.5, 0.0, 1.0, 1.0, 1.0,
]).reshape((2, 2, 3))

cmaps = {
"BiPeak": SegmentedBivarColormap(
Expand Down
41 changes: 31 additions & 10 deletions lib/matplotlib/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@

import matplotlib as mpl
import numpy as np
from matplotlib import _api, _cm, cbook, scale, _image
from matplotlib import _api, _cm, cbook, scale
from ._color_data import BASE_COLORS, TABLEAU_COLORS, CSS4_COLORS, XKCD_COLORS


Expand Down Expand Up @@ -2211,16 +2211,37 @@ def __init__(self, patch, N=256, shape='square', origin=(0, 0),
super().__init__(N, N, shape, origin, name=name)

def _init(self):
# Perform bilinear interpolation

s = self.patch.shape
_patch = np.empty((s[0], s[1], 4))
_patch[:, :, :3] = self.patch
_patch[:, :, 3] = 1
transform = mpl.transforms.Affine2D().translate(-0.5, -0.5)\
.scale(self.N / (s[1] - 1), self.N / (s[0] - 1))
self._lut = np.empty((self.N, self.N, 4))

_image.resample(_patch, self._lut, transform, _image.BILINEAR,
resample=False, alpha=1)

# Indices (whole and fraction) of the new grid points
row = np.linspace(0, s[0] - 1, self.N)[:, np.newaxis]
col = np.linspace(0, s[1] - 1, self.N)[np.newaxis, :]
left = row.astype(int) # floor not needed because all values are nonnegative
top = col.astype(int) # floor not needed because all values are nonnegative
row_frac = (row - left)[:, :, np.newaxis]
col_frac = (col - top)[:, :, np.newaxis]

# Indices of the next edges, clipping where needed
right = np.clip(left + 1, 0, s[0] - 1)
bottom = np.clip(top + 1, 0, s[1] - 1)

# Values at the corners
tl = self.patch[left, top, :]
tr = self.patch[right, top, :]
bl = self.patch[left, bottom, :]
br = self.patch[right, bottom, :]

# Interpolate between the corners
lut = (tl * (1 - row_frac) * (1 - col_frac) +
tr * row_frac * (1 - col_frac) +
bl * (1 - row_frac) * col_frac +
br * row_frac * col_frac)

# Add the alpha channel
self._lut = np.concatenate([lut, np.ones((self.N, self.N, 1))], axis=2)

self._isinit = True


Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 16 additions & 16 deletions lib/matplotlib/tests/test_colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -2127,30 +2127,30 @@ def test_colorizer_multinorm_implicit():

# test call with two single values
data = [0.1, 0.2]
res = (0.10009765625, 0.1510859375, 0.20166015625, 1.0)
res = (0.098039, 0.149020, 0.2, 1.0)
assert_array_almost_equal(ca.to_rgba(data), res)

# test call with two 1d arrays
data = [[0.1, 0.2], [0.3, 0.4]]
res = [[0.10009766, 0.19998877, 0.29931641, 1.],
[0.20166016, 0.30098633, 0.40087891, 1.]]
res = [[0.09803922, 0.19803922, 0.29803922, 1.],
[0.2, 0.3, 0.4, 1.]]
assert_array_almost_equal(ca.to_rgba(data), res)

# test call with two 2d arrays
data = [np.linspace(0, 1, 12).reshape(3, 4),
np.linspace(1, 0, 12).reshape(3, 4)]
res = np.array([[[0.00244141, 0.50048437, 0.99853516, 1.],
[0.09228516, 0.50048437, 0.90869141, 1.],
[0.18212891, 0.50048437, 0.81884766, 1.],
[0.27197266, 0.50048437, 0.72900391, 1.]],
[[0.36572266, 0.50048437, 0.63525391, 1.],
[0.45556641, 0.50048438, 0.54541016, 1.],
[0.54541016, 0.50048438, 0.45556641, 1.],
[0.63525391, 0.50048437, 0.36572266, 1.]],
[[0.72900391, 0.50048437, 0.27197266, 1.],
[0.81884766, 0.50048437, 0.18212891, 1.],
[0.90869141, 0.50048437, 0.09228516, 1.],
[0.99853516, 0.50048437, 0.00244141, 1.]]])
res = np.array([[[0., 0.5, 1., 1.],
[0.09019608, 0.5, 0.90980392, 1.],
[0.18039216, 0.5, 0.81960784, 1.],
[0.27058824, 0.5, 0.72941176, 1.]],
[[0.36470588, 0.5, 0.63529412, 1.],
[0.45490196, 0.5, 0.54509804, 1.],
[0.54509804, 0.5, 0.45490196, 1.],
[0.63529412, 0.5, 0.36470588, 1.]],
[[0.72941176, 0.5, 0.27058824, 1.],
[0.81960784, 0.5, 0.18039216, 1.],
[0.90980392, 0.5, 0.09019608, 1.],
[1., 0.5, 0., 1.]]])
assert_array_almost_equal(ca.to_rgba(data), res)

with pytest.raises(ValueError, match=("This MultiNorm has 2 components, "
Expand Down Expand Up @@ -2191,7 +2191,7 @@ def test_colorizer_multinorm_explicit():

# test call with two single values
data = [0.1, 0.2]
res = (0.100098, 0.375492, 0.650879, 1.)
res = (0.098039, 0.374510, 0.65098, 1.)
assert_array_almost_equal(ca.to_rgba(data), res)


Expand Down
85 changes: 57 additions & 28 deletions lib/matplotlib/tests/test_multivariate_colormaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,26 @@ def test_multivar_resample():

def test_bivar_cmap_call_tuple():
cmap = mpl.bivar_colormaps['BiOrangeBlue']
assert_allclose(cmap((1.0, 1.0)), (1, 1, 1, 1), atol=0.01)
assert_allclose(cmap((0.0, 0.0)), (0, 0, 0, 1), atol=0.1)
assert_allclose(cmap((0.0, 0.0), alpha=0.1), (0, 0, 0, 0.1), atol=0.1)
assert_allclose(cmap((1.0, 1.0)), (1, 1, 1, 1))
assert_allclose(cmap((0.0, 0.0)), (0, 0, 0, 1))
assert_allclose(cmap((0.2, 0.8)), (0.2, 0.5, 0.8, 1))
assert_allclose(cmap((0.0, 0.0), alpha=0.1), (0, 0, 0, 0.1))


def test_bivar_cmap_lut_smooth():
cmap = mpl.bivar_colormaps['BiOrangeBlue']

assert_allclose(cmap.lut[:, 0, 0], np.linspace(0, 1, 256))
assert_allclose(cmap.lut[:, 255, 0], np.linspace(0, 1, 256))
assert_allclose(cmap.lut[:, 0, 1], np.linspace(0, 0.5, 256))
assert_allclose(cmap.lut[:, 153, 1], np.linspace(0.3, 0.8, 256))
assert_allclose(cmap.lut[:, 255, 1], np.linspace(0.5, 1, 256))

assert_allclose(cmap.lut[0, :, 1], np.linspace(0, 0.5, 256))
assert_allclose(cmap.lut[102, :, 1], np.linspace(0.2, 0.7, 256))
assert_allclose(cmap.lut[255, :, 1], np.linspace(0.5, 1, 256))
assert_allclose(cmap.lut[0, :, 2], np.linspace(0, 1, 256))
assert_allclose(cmap.lut[255, :, 2], np.linspace(0, 1, 256))


def test_bivar_cmap_call():
Expand Down Expand Up @@ -312,20 +329,36 @@ def test_bivar_cmap_call():
match="only implemented for use with with floats"):
cs = cmap([(0, 5, 9, 0, 0, 9), (0, 0, 0, 5, 11, 11)])

# test origin
cmap = mpl.bivar_colormaps['BiOrangeBlue'].with_extremes(origin=(0.5, 0.5))
assert_allclose(cmap[0](0.5),
(0.50244140625, 0.5024222412109375, 0.50244140625, 1))
assert_allclose(cmap[1](0.5),
(0.50244140625, 0.5024222412109375, 0.50244140625, 1))
cmap = mpl.bivar_colormaps['BiOrangeBlue'].with_extremes(origin=(1, 1))
assert_allclose(cmap[0](1.),
(0.99853515625, 0.9985467529296875, 0.99853515625, 1.0))
assert_allclose(cmap[1](1.),
(0.99853515625, 0.9985467529296875, 0.99853515625, 1.0))

def test_bivar_cmap_1d_origin():
"""
Test getting 1D colormaps with different origins
"""
cmap0 = mpl.bivar_colormaps['BiOrangeBlue']
assert_allclose(cmap0[0].colors[:, 0], np.linspace(0, 1, 256))
assert_allclose(cmap0[0].colors[:, 1], np.linspace(0, 0.5, 256))
assert_allclose(cmap0[0].colors[:, 2], 0)
assert_allclose(cmap0[1].colors[:, 0], 0)
assert_allclose(cmap0[1].colors[:, 1], np.linspace(0, 0.5, 256))
assert_allclose(cmap0[1].colors[:, 2], np.linspace(0, 1, 256))

cmap1 = cmap0.with_extremes(origin=(0, 1))
assert_allclose(cmap1[0].colors[:, 0], np.linspace(0, 1, 256))
assert_allclose(cmap1[0].colors[:, 1], np.linspace(0.5, 1, 256))
assert_allclose(cmap1[0].colors[:, 2], 1)
assert_allclose(cmap1[1].colors, cmap0[1].colors)

cmap2 = cmap0.with_extremes(origin=(0.2, 0.4))
assert_allclose(cmap2[0].colors[:, 0], np.linspace(0, 1, 256))
assert_allclose(cmap2[0].colors[:, 1], np.linspace(0.2, 0.7, 256))
assert_allclose(cmap2[0].colors[:, 2], 0.4)
assert_allclose(cmap2[1].colors[:, 0], 0.2)
assert_allclose(cmap2[1].colors[:, 1], np.linspace(0.1, 0.6, 256))
assert_allclose(cmap2[1].colors[:, 2], np.linspace(0, 1, 256))

with pytest.raises(KeyError,
match="only 0 or 1 are valid keys"):
cs = cmap[2]
cs = cmap0[2]


def test_bivar_getitem():
Expand Down Expand Up @@ -433,22 +466,18 @@ def test_bivar_cmap_from_image():


def test_bivar_resample():
cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((2, 2))
assert_allclose(cmap((0.25, 0.25)), (0, 0, 0, 1), atol=1e-2)

cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((-2, 2))
assert_allclose(cmap((0.25, 0.25)), (1., 0.5, 0., 1.), atol=1e-2)
cmap = mpl.bivar_colormaps['BiOrangeBlue']

cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((2, -2))
assert_allclose(cmap((0.25, 0.25)), (0., 0.5, 1., 1.), atol=1e-2)
assert_allclose(cmap.resampled((2, 2))((0.25, 0.25)), (0, 0, 0, 1))
assert_allclose(cmap.resampled((-2, 2))((0.25, 0.25)), (1., 0.5, 0., 1.))
assert_allclose(cmap.resampled((2, -2))((0.25, 0.25)), (0., 0.5, 1., 1.))
assert_allclose(cmap.resampled((-2, -2))((0.25, 0.25)), (1, 1, 1, 1))

cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((-2, -2))
assert_allclose(cmap((0.25, 0.25)), (1, 1, 1, 1), atol=1e-2)
assert_allclose(cmap((0.8, 0.4)), (0.8, 0.6, 0.4, 1.))
assert_allclose(cmap.reversed()((1 - 0.8, 1 - 0.4)), (0.8, 0.6, 0.4, 1.))

cmap = mpl.bivar_colormaps['BiOrangeBlue'].reversed()
assert_allclose(cmap((0.25, 0.25)), (0.748535, 0.748547, 0.748535, 1.), atol=1e-2)
cmap = mpl.bivar_colormaps['BiOrangeBlue'].transposed()
assert_allclose(cmap((0.25, 0.25)), (0.252441, 0.252422, 0.252441, 1.), atol=1e-2)
assert_allclose(cmap((0.6, 0.2)), (0.6, 0.4, 0.2, 1.))
assert_allclose(cmap.transposed()((0.2, 0.6)), (0.6, 0.4, 0.2, 1.))

with pytest.raises(ValueError, match="lutshape must be of length"):
cmap = cmap.resampled(4)
Expand Down
Loading