Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit1b1afe1

Browse files
authored
Merge pull request#6268 from efiring/alpha_array
ENH: support alpha arrays in collections
2 parents2e82a38 +c9d0741 commit1b1afe1

File tree

9 files changed

+276
-36
lines changed

9 files changed

+276
-36
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
Transparency (alpha) can be set as an array in collections
2+
----------------------------------------------------------
3+
Previously, the alpha value controlling tranparency in collections could be
4+
specified only as a scalar applied to all elements in the collection.
5+
For example, all the markers in a `~.Axes.scatter` plot, or all the
6+
quadrilaterals in a `~.Axes.pcolormesh` plot, would have the same alpha value.
7+
8+
Now it is possible to supply alpha as an array with one value for each element
9+
(marker, quadrilateral, etc.) in a collection.
10+
11+
..plot::
12+
13+
x = np.arange(5, dtype=float)
14+
y = np.arange(5, dtype=float)
15+
# z and zalpha for demo pcolormesh
16+
z = x[1:, np.newaxis] + y[np.newaxis, 1:]
17+
zalpha = np.ones_like(z)
18+
zalpha[::2, ::2] = 0.3 # alternate patches are partly transparent
19+
# s and salpha for demo scatter
20+
s = x
21+
salpha = np.linspace(0.1, 0.9, len(x)) # just a ramp
22+
23+
fig, axs = plt.subplots(2, 2, constrained_layout=True)
24+
axs[0, 0].pcolormesh(x, y, z, alpha=zalpha)
25+
axs[0, 0].set_title("pcolormesh")
26+
axs[0, 1].scatter(x, y, c=s, alpha=salpha)
27+
axs[0, 1].set_title("color-mapped")
28+
axs[1, 0].scatter(x, y, c='k', alpha=salpha)
29+
axs[1, 0].set_title("c='k'")
30+
axs[1, 1].scatter(x, y, c=['r', 'g', 'b', 'c', 'm'], alpha=salpha)
31+
axs[1, 1].set_title("c=['r', 'g', 'b', 'c', 'm']")

‎lib/matplotlib/artist.py‎

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -954,10 +954,37 @@ def set_alpha(self, alpha):
954954
955955
Parameters
956956
----------
957-
alpha : float or None
957+
alpha : scalar or None
958+
*alpha* must be within the 0-1 range, inclusive.
958959
"""
959960
ifalphaisnotNoneandnotisinstance(alpha,Number):
960-
raiseTypeError('alpha must be a float or None')
961+
raiseTypeError(
962+
f'alpha must be numeric or None, not{type(alpha)}')
963+
ifalphaisnotNoneandnot (0<=alpha<=1):
964+
raiseValueError(f'alpha ({alpha}) is outside 0-1 range')
965+
self._alpha=alpha
966+
self.pchanged()
967+
self.stale=True
968+
969+
def_set_alpha_for_array(self,alpha):
970+
"""
971+
Set the alpha value used for blending - not supported on all backends.
972+
973+
Parameters
974+
----------
975+
alpha : array-like or scalar or None
976+
All values must be within the 0-1 range, inclusive.
977+
Masked values and nans are not supported.
978+
"""
979+
ifisinstance(alpha,str):
980+
raiseTypeError("alpha must be numeric or None, not a string")
981+
ifnotnp.iterable(alpha):
982+
Artist.set_alpha(self,alpha)
983+
return
984+
alpha=np.asarray(alpha)
985+
ifnot (0<=alpha.min()andalpha.max()<=1):
986+
raiseValueError('alpha must be between 0 and 1, inclusive, '
987+
f'but min is{alpha.min()}, max is{alpha.max()}')
961988
self._alpha=alpha
962989
self.pchanged()
963990
self.stale=True

‎lib/matplotlib/collections.py‎

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -832,12 +832,24 @@ def set_edgecolor(self, c):
832832
self._set_edgecolor(c)
833833

834834
defset_alpha(self,alpha):
835-
# docstring inherited
836-
super().set_alpha(alpha)
835+
"""
836+
Set the transparency of the collection.
837+
838+
Parameters
839+
----------
840+
alpha: float or array of float or None
841+
If not None, *alpha* values must be between 0 and 1, inclusive.
842+
If an array is provided, its length must match the number of
843+
elements in the collection. Masked values and nans are not
844+
supported.
845+
"""
846+
artist.Artist._set_alpha_for_array(self,alpha)
837847
self._update_dict['array']=True
838848
self._set_facecolor(self._original_facecolor)
839849
self._set_edgecolor(self._original_edgecolor)
840850

851+
set_alpha.__doc__=artist.Artist._set_alpha_for_array.__doc__
852+
841853
defget_linewidth(self):
842854
returnself._linewidths
843855

@@ -848,11 +860,23 @@ def update_scalarmappable(self):
848860
"""Update colors from the scalar mappable array, if it is not None."""
849861
ifself._AisNone:
850862
return
851-
# QuadMesh can map 2d arrays
863+
# QuadMesh can map 2d arrays (but pcolormesh supplies 1d array)
852864
ifself._A.ndim>1andnotisinstance(self,QuadMesh):
853865
raiseValueError('Collections can only map rank 1 arrays')
854866
ifnotself._check_update("array"):
855867
return
868+
ifnp.iterable(self._alpha):
869+
ifself._alpha.size!=self._A.size:
870+
raiseValueError(f'Data array shape,{self._A.shape} '
871+
'is incompatible with alpha array shape, '
872+
f'{self._alpha.shape}. '
873+
'This can occur with the deprecated '
874+
'behavior of the "flat" shading option, '
875+
'in which a row and/or column of the data '
876+
'array is dropped.')
877+
# pcolormesh, scatter, maybe others flatten their _A
878+
self._alpha=self._alpha.reshape(self._A.shape)
879+
856880
ifself._is_filled:
857881
self._facecolors=self.to_rgba(self._A,self._alpha)
858882
elifself._is_stroked:

‎lib/matplotlib/colors.py‎

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -289,18 +289,40 @@ def to_rgba_array(c, alpha=None):
289289
"""
290290
Convert *c* to a (n, 4) array of RGBA colors.
291291
292-
If *alpha* is not ``None``, it forces the alpha value. If *c* is
293-
``"none"`` (case-insensitive) or an empty list, an empty array is returned.
294-
If *c* is a masked array, an ndarray is returned with a (0, 0, 0, 0)
295-
row for each masked value or row in *c*.
292+
Parameters
293+
----------
294+
c : Matplotlib color or array of colors
295+
If *c* is a masked array, an ndarray is returned with a (0, 0, 0, 0)
296+
row for each masked value or row in *c*.
297+
298+
alpha : float or sequence of floats, optional
299+
If *alpha* is not ``None``, it forces the alpha value, except if *c* is
300+
``"none"`` (case-insensitive), which always maps to ``(0, 0, 0, 0)``.
301+
If *alpha* is a sequence and *c* is a single color, *c* will be
302+
repeated to match the length of *alpha*.
303+
304+
Returns
305+
-------
306+
array
307+
(n, 4) array of RGBA colors.
308+
296309
"""
297310
# Special-case inputs that are already arrays, for performance. (If the
298311
# array has the wrong kind or shape, raise the error during one-at-a-time
299312
# conversion.)
313+
ifnp.iterable(alpha):
314+
alpha=np.asarray(alpha).ravel()
300315
if (isinstance(c,np.ndarray)andc.dtype.kindin"if"
301316
andc.ndim==2andc.shape[1]in [3,4]):
302317
mask=c.mask.any(axis=1)ifnp.ma.is_masked(c)elseNone
303318
c=np.ma.getdata(c)
319+
ifnp.iterable(alpha):
320+
ifc.shape[0]==1andalpha.shape[0]>1:
321+
c=np.tile(c, (alpha.shape[0],1))
322+
elifc.shape[0]!=alpha.shape[0]:
323+
raiseValueError("The number of colors must match the number"
324+
" of alpha values if there are more than one"
325+
" of each.")
304326
ifc.shape[1]==3:
305327
result=np.column_stack([c,np.zeros(len(c))])
306328
result[:,-1]=alphaifalphaisnotNoneelse1.
@@ -320,7 +342,10 @@ def to_rgba_array(c, alpha=None):
320342
ifcbook._str_lower_equal(c,"none"):
321343
returnnp.zeros((0,4),float)
322344
try:
323-
returnnp.array([to_rgba(c,alpha)],float)
345+
ifnp.iterable(alpha):
346+
returnnp.array([to_rgba(c,a)forainalpha],float)
347+
else:
348+
returnnp.array([to_rgba(c,alpha)],float)
324349
except (ValueError,TypeError):
325350
pass
326351

@@ -332,7 +357,10 @@ def to_rgba_array(c, alpha=None):
332357
iflen(c)==0:
333358
returnnp.zeros((0,4),float)
334359
else:
335-
returnnp.array([to_rgba(cc,alpha)forccinc])
360+
ifnp.iterable(alpha):
361+
returnnp.array([to_rgba(cc,aa)forcc,aainzip(c,alpha)])
362+
else:
363+
returnnp.array([to_rgba(cc,alpha)forccinc])
336364

337365

338366
defto_rgb(c):
@@ -539,8 +567,9 @@ def __call__(self, X, alpha=None, bytes=False):
539567
return the RGBA values ``X*100`` percent along the Colormap line.
540568
For integers, X should be in the interval ``[0, Colormap.N)`` to
541569
return RGBA values *indexed* from the Colormap with index ``X``.
542-
alpha : float, None
543-
Alpha must be a scalar between 0 and 1, or None.
570+
alpha : float, array-like, None
571+
Alpha must be a scalar between 0 and 1, a sequence of such
572+
floats with shape matching X, or None.
544573
bytes : bool
545574
If False (default), the returned RGBA values will be floats in the
546575
interval ``[0, 1]`` otherwise they will be uint8s in the interval
@@ -580,23 +609,29 @@ def __call__(self, X, alpha=None, bytes=False):
580609
else:
581610
lut=self._lut.copy()# Don't let alpha modify original _lut.
582611

612+
rgba=np.empty(shape=xa.shape+ (4,),dtype=lut.dtype)
613+
lut.take(xa,axis=0,mode='clip',out=rgba)
614+
583615
ifalphaisnotNone:
616+
ifnp.iterable(alpha):
617+
alpha=np.asarray(alpha)
618+
ifalpha.shape!=xa.shape:
619+
raiseValueError("alpha is array-like but its shape"
620+
" %s doesn't match that of X %s"%
621+
(alpha.shape,xa.shape))
584622
alpha=np.clip(alpha,0,1)
585623
ifbytes:
586-
alpha=int(alpha*255)
587-
if (lut[-1]==0).all():
588-
lut[:-1,-1]=alpha
589-
# All zeros is taken as a flag for the default bad
590-
# color, which is no color--fully transparent. We
591-
# don't want to override this.
592-
else:
593-
lut[:,-1]=alpha
594-
# If the bad value is set to have a color, then we
595-
# override its alpha just as for any other value.
624+
alpha= (alpha*255).astype(np.uint8)
625+
rgba[...,-1]=alpha
626+
627+
# If the "bad" color is all zeros, then ignore alpha input.
628+
if (lut[-1]==0).all()andnp.any(mask_bad):
629+
ifnp.iterable(mask_bad)andmask_bad.shape==xa.shape:
630+
rgba[mask_bad]= (0,0,0,0)
631+
else:
632+
rgba[..., :]= (0,0,0,0)
596633

597-
rgba=lut[xa]
598634
ifnotnp.iterable(X):
599-
# Return a tuple if the input was a scalar
600635
rgba=tuple(rgba)
601636
returnrgba
602637

‎lib/matplotlib/image.py‎

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -274,16 +274,12 @@ def set_alpha(self, alpha):
274274
275275
Parameters
276276
----------
277-
alpha : float
277+
alpha : float or 2D array-like or None
278278
"""
279-
ifalphaisnotNoneandnotisinstance(alpha,Number):
280-
alpha=np.asarray(alpha)
281-
ifalpha.ndim!=2:
282-
raiseTypeError('alpha must be a float, two-dimensional '
283-
'array, or None')
284-
self._alpha=alpha
285-
self.pchanged()
286-
self.stale=True
279+
martist.Artist._set_alpha_for_array(self,alpha)
280+
ifnp.ndim(alpha)notin (0,2):
281+
raiseTypeError('alpha must be a float, two-dimensional '
282+
'array, or None')
287283
self._imcache=None
288284

289285
def_get_scalar_alpha(self):

‎lib/matplotlib/tests/test_artist.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,3 +277,29 @@ def test_artist_inspector_get_aliases():
277277
ai=martist.ArtistInspector(mlines.Line2D)
278278
aliases=ai.get_aliases()
279279
assertaliases["linewidth"]== {"lw"}
280+
281+
282+
deftest_set_alpha():
283+
art=martist.Artist()
284+
withpytest.raises(TypeError,match='^alpha must be numeric or None'):
285+
art.set_alpha('string')
286+
withpytest.raises(TypeError,match='^alpha must be numeric or None'):
287+
art.set_alpha([1,2,3])
288+
withpytest.raises(ValueError,match="outside 0-1 range"):
289+
art.set_alpha(1.1)
290+
withpytest.raises(ValueError,match="outside 0-1 range"):
291+
art.set_alpha(np.nan)
292+
293+
294+
deftest_set_alpha_for_array():
295+
art=martist.Artist()
296+
withpytest.raises(TypeError,match='^alpha must be numeric or None'):
297+
art._set_alpha_for_array('string')
298+
withpytest.raises(ValueError,match="outside 0-1 range"):
299+
art._set_alpha_for_array(1.1)
300+
withpytest.raises(ValueError,match="outside 0-1 range"):
301+
art._set_alpha_for_array(np.nan)
302+
withpytest.raises(ValueError,match="alpha must be between 0 and 1"):
303+
art._set_alpha_for_array([0.5,1.1])
304+
withpytest.raises(ValueError,match="alpha must be between 0 and 1"):
305+
art._set_alpha_for_array([0.5,np.nan])

‎lib/matplotlib/tests/test_collections.py‎

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -543,11 +543,38 @@ def test_cap_and_joinstyle_image():
543543
deftest_scatter_post_alpha():
544544
fig,ax=plt.subplots()
545545
sc=ax.scatter(range(5),range(5),c=range(5))
546-
# this needs to be here to update internal state
547-
fig.canvas.draw()
548546
sc.set_alpha(.1)
549547

550548

549+
deftest_scatter_alpha_array():
550+
x=np.arange(5)
551+
alpha=x/5
552+
# With color mapping.
553+
fig, (ax0,ax1)=plt.subplots(2)
554+
sc0=ax0.scatter(x,x,c=x,alpha=alpha)
555+
sc1=ax1.scatter(x,x,c=x)
556+
sc1.set_alpha(alpha)
557+
plt.draw()
558+
assert_array_equal(sc0.get_facecolors()[:,-1],alpha)
559+
assert_array_equal(sc1.get_facecolors()[:,-1],alpha)
560+
# Without color mapping.
561+
fig, (ax0,ax1)=plt.subplots(2)
562+
sc0=ax0.scatter(x,x,color=['r','g','b','c','m'],alpha=alpha)
563+
sc1=ax1.scatter(x,x,color='r',alpha=alpha)
564+
plt.draw()
565+
assert_array_equal(sc0.get_facecolors()[:,-1],alpha)
566+
assert_array_equal(sc1.get_facecolors()[:,-1],alpha)
567+
# Without color mapping, and set alpha afterward.
568+
fig, (ax0,ax1)=plt.subplots(2)
569+
sc0=ax0.scatter(x,x,color=['r','g','b','c','m'])
570+
sc0.set_alpha(alpha)
571+
sc1=ax1.scatter(x,x,color='r')
572+
sc1.set_alpha(alpha)
573+
plt.draw()
574+
assert_array_equal(sc0.get_facecolors()[:,-1],alpha)
575+
assert_array_equal(sc1.get_facecolors()[:,-1],alpha)
576+
577+
551578
deftest_pathcollection_legend_elements():
552579
np.random.seed(19680801)
553580
x,y=np.random.rand(2,10)
@@ -662,6 +689,39 @@ def test_quadmesh_set_array():
662689
assertnp.array_equal(coll.get_array(),np.ones(9))
663690

664691

692+
deftest_quadmesh_alpha_array():
693+
x=np.arange(4)
694+
y=np.arange(4)
695+
z=np.arange(9).reshape((3,3))
696+
alpha=z/z.max()
697+
alpha_flat=alpha.ravel()
698+
# Provide 2-D alpha:
699+
fig, (ax0,ax1)=plt.subplots(2)
700+
coll1=ax0.pcolormesh(x,y,z,alpha=alpha)
701+
coll2=ax1.pcolormesh(x,y,z)
702+
coll2.set_alpha(alpha)
703+
plt.draw()
704+
assert_array_equal(coll1.get_facecolors()[:,-1],alpha_flat)
705+
assert_array_equal(coll2.get_facecolors()[:,-1],alpha_flat)
706+
# Or provide 1-D alpha:
707+
fig, (ax0,ax1)=plt.subplots(2)
708+
coll1=ax0.pcolormesh(x,y,z,alpha=alpha_flat)
709+
coll2=ax1.pcolormesh(x,y,z)
710+
coll2.set_alpha(alpha_flat)
711+
plt.draw()
712+
assert_array_equal(coll1.get_facecolors()[:,-1],alpha_flat)
713+
assert_array_equal(coll2.get_facecolors()[:,-1],alpha_flat)
714+
715+
716+
deftest_alpha_validation():
717+
# Most of the relevant testing is in test_artist and test_colors.
718+
fig,ax=plt.subplots()
719+
pc=ax.pcolormesh(np.arange(12).reshape((3,4)))
720+
withpytest.raises(ValueError,match="^Data array shape"):
721+
pc.set_alpha([0.5,0.6])
722+
pc.update_scalarmappable()
723+
724+
665725
deftest_legend_inverse_size_label_relationship():
666726
"""
667727
Ensure legend markers scale appropriately when label and size are

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp