Add documentation pages and whatsnew entry · matplotlib/matplotlib@fcef182 · GitHub
Skip to content

Commit fcef182

Browse files
committed
Add documentation pages and whatsnew entry
1 parent ced4c64 commit fcef182

9 files changed

Lines changed: 361 additions & 8 deletions

File tree

Lines changed: 13 additions & 0 deletions

galleries/users_explain/colors/GALLERY_HEADER.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
Colors
66
------
77

8-
Matplotlib has support for visualizing information with a wide array
9-
of colors and colormaps. These tutorials cover the basics of how
10-
these colormaps look, how you can create your own, and how you can
11-
customize colormaps for your use case.
8+
Matplotlib has support for visualizing information with a wide array of colors
9+
and colormaps. The color tutorials cover the basics of specifying colors, as
10+
well as the range of options for blending the colors of overlapping artists.
11+
The colormap tutorials cover the basics of how colormaps look, how you can
12+
create your own, and how you can customize colormaps for your use case.
1213

1314
For even more information see the :ref:`examples page <color_examples>`.
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""
2+
.. _blend-groups:
3+
4+
==========================================
5+
Blending and compositing groups of artists
6+
==========================================
7+
8+
An advanced technique of blending artists (see :ref:`blend-modes`) is to use a
9+
blend group, also known as a transparency group. Blend groups can be isolated,
10+
knockout, or both:
11+
12+
* An **isolated** group has the artists within the group rendered into a
13+
separate buffer, and the result is subsequently blended into the primary
14+
buffer.
15+
* A **knockout** group has each of the artists within the group individually
16+
blended onto the initial backdrop, with each successive artist ignoring any
17+
modifications underneath it by preceding artists in the group.
18+
19+
The methods to open and close groups are found on the backend renderer, but
20+
user code does not typically directly access the renderer. The convenience
21+
class below (``ArtistGroup``) makes it straightforward to form a blend group
22+
from a list of artists. Setting ``group_blend_mode`` to a blend mode (see
23+
:ref:`blend-modes` for the allowed options) makes the blend group an isolated
24+
group using that blend mode, whereas specifying ``group_blend_mode=None`` makes
25+
the blend group a non-isolated group. Specifying ``knockout=True`` makes the
26+
blend group a knockout group.
27+
28+
The first example below shows:
29+
30+
* The left panel shows the behavior of a blend group that is neither isolated
31+
nor knockout. The result is the same as not using a blend group at all,
32+
except that the elements will all be drawn at the zorder of the group. A cyan
33+
circle and a magenta circle are successively blended with the "multiply" blend
34+
mode into the backdrop.
35+
* The middle panel shows how the behavior changes when the two circles are in an
36+
isolated blend group. The cyan circle is rendered into an isolated buffer, so
37+
its "multiply" blend mode has no visible effect. The magenta circle is then
38+
blended with the cyan circle using "multiply". Finally, the isolated buffer
39+
is blended into the primary buffer using "normal". Thus, the "multiply" blend
40+
mode affects only the overlap between the two circles, and does not interact
41+
with the backdrop at all due to the isolation.
42+
* The right panel shows how the behavior changes when the blend group is both
43+
isolated and knockout. The magenta circle knocks out the portion of the cyan
44+
circle that is overlapped. Since there is no longer any overlapping elements
45+
in the isolated buffer, the blend modes within the group have no visible
46+
effect. As before, the isolated buffer is then blended into the primary
47+
buffer using "normal".
48+
49+
Support for the different types of blend groups depends on the backend. See the
50+
table below for details.
51+
"""
52+
from operator import attrgetter
53+
54+
import matplotlib.pyplot as plt
55+
import numpy as np
56+
57+
from matplotlib.artist import Artist
58+
from matplotlib.patches import Circle
59+
60+
61+
class ArtistGroup(Artist):
62+
def __init__(self, artists, *,
63+
group_blend_mode=None, group_alpha=1, knockout=False):
64+
self._artists = artists
65+
self._group_blend_mode = group_blend_mode
66+
self._group_alpha = group_alpha
67+
self._knockout = knockout
68+
super().__init__()
69+
70+
def draw(self, renderer):
71+
renderer.open_blend_group(self._group_blend_mode, alpha=self._group_alpha,
72+
knockout=self._knockout)
73+
for a in sorted(self._artists, key=attrgetter('zorder')):
74+
if not a.is_transform_set():
75+
a.set_transform(self.get_transform())
76+
if getattr(a, 'axes', None) is None:
77+
a.axes = self.axes
78+
a.draw(renderer)
79+
renderer.close_blend_group()
80+
81+
82+
fig, axs = plt.subplots(1, 3, figsize=(9, 3), layout='constrained')
83+
84+
for i, (group_blend_mode, knockout) in enumerate([(None, False),
85+
('normal', False),
86+
('normal', True)]):
87+
axs[i].set_xlim(-1, 1)
88+
axs[i].set_ylim(-1, 1)
89+
axs[i].set_aspect('equal')
90+
axs[i].set_axis_off()
91+
92+
axs[i].imshow(np.arange(20*20).reshape((20, 20)) % 19,
93+
cmap='Spectral', extent=[-1, 1, -1, 1])
94+
95+
left = Circle((-0.25, 0), 0.6, fc='c', alpha=0.75, blend_mode='multiply')
96+
right = Circle((0.25, 0), 0.6, fc='m', alpha=0.75, blend_mode='multiply')
97+
98+
both = ArtistGroup([left, right],
99+
group_blend_mode=group_blend_mode, knockout=knockout)
100+
axs[i].add_artist(both)
101+
102+
axs[0].set_title('neither isolated nor knockout')
103+
axs[1].set_title('isolated only')
104+
axs[2].set_title('isolated and knockout')
105+
106+
107+
# %%
108+
#
109+
# This table shows which types of blend groups are supported by each
110+
# backend type (✅ = supported, 🟡 = supported through rasterization,
111+
# ❌ = not supported).
112+
#
113+
# +--------------------+-----------+-----------+-----+-----+-----+---------+
114+
# | Option | Agg | Cairo | SVG | PDF | PGF | PS |
115+
# +====================+===========+===========+=====+=====+=====+=========+
116+
# | neither isolated | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ [#]_ |
117+
# | nor knockout | | | | | | |
118+
# +--------------------+-----------+-----------+-----+-----+-----+---------+
119+
# | isolated only | ✅ | ✅ | ✅ | ✅ | ✅ | 🟡 |
120+
# +--------------------+-----------+-----------+-----+-----+-----+---------+
121+
# | isolated and | ✅ | ✅ | 🟡 | ✅ | ✅ | 🟡 |
122+
# | knockout | | | | | | |
123+
# +--------------------+-----------+-----------+-----+-----+-----+---------+
124+
# | knockout only [#]_ | ❌ [#f3]_ | ❌ [#f3]_ | ❌ | ✅ | ✅ | ❌ |
125+
# +--------------------+-----------+-----------+-----+-----+-----+---------+
126+
#
127+
# .. [#] groups are not supported, but it is equivalent to instead draw artists
128+
# without using a group
129+
# .. [#] not depicted above
130+
# .. [#f3] see the workaround below
131+
#
132+
# As indicated in the table above, the Agg and Cairo renderers do not natively
133+
# support non-isolated knockout groups. If all of the artists in the group use
134+
# the same blend mode, an alternative approach that produces the desired result
135+
# is to use a group that is both isolated and knockout, with the group blend
136+
# mode set to that common blend mode. This workaround can also be used to
137+
# achieve non-isolated knockout groups for the SVG and PS backends if
138+
# rasterization is enabled. This workaround allows us to show the result of a
139+
# non-isolated knockout group in the HTML documentation.
140+
141+
142+
fig, ax = plt.subplots(figsize=(3, 3), layout='constrained')
143+
144+
ax.set_xlim(-1, 1)
145+
ax.set_ylim(-1, 1)
146+
ax.set_aspect('equal')
147+
ax.set_axis_off()
148+
149+
ax.imshow(np.arange(20*20).reshape((20, 20)) % 19,
150+
cmap='Spectral', extent=[-1, 1, -1, 1])
151+
152+
left = Circle((-0.25, 0), 0.6, fc='c', alpha=0.75)
153+
right = Circle((0.25, 0), 0.6, fc='m', alpha=0.75)
154+
155+
both = ArtistGroup([left, right], group_blend_mode='multiply', knockout=True)
156+
ax.add_artist(both)
157+
158+
ax.set_title('knockout only\n(using workaround)')
159+
160+
plt.show()
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
"""
2+
.. _blend-modes:
3+
4+
================================
5+
Blending and compositing artists
6+
================================
7+
8+
When an artist is drawn on top of existing elements, the default behavior is for
9+
the artist's colors to be blended with the colors underneath the artist using
10+
:ref:`alpha-based transparency <colors_transparency>`. An *alpha* value of 1
11+
normally means that the underlying colors are completely hidden.
12+
13+
An example of an alternative to normal alpha blending is the
14+
`"multiply" blend mode <https://en.wikipedia.org/wiki/Blend_modes#Multiply>`__,
15+
where the RGB channel values (in the range [0, 1]) of the artist colors and the
16+
underlying colors are multiplied together. For this blend mode, the underlying
17+
colors can still affect the final color even when the *alpha* value is 1.
18+
19+
"""
20+
21+
import matplotlib.pyplot as plt
22+
from matplotlib.patches import Circle
23+
24+
fig, ax = plt.subplots(figsize=(6, 3), layout='constrained')
25+
26+
ax.text(1.5, 1.2, 'default behavior\n(a.k.a. "normal" blend mode)', ha='center')
27+
ax.add_patch(Circle((1, 0), 1, color='c', ec='none'))
28+
ax.add_patch(Circle((2, 0), 1, color='m', ec='none'))
29+
ax.add_patch(Circle((1.5, -0.87), 1, color='y', ec='none'))
30+
31+
ax.text(5.5, 1.2, '"multiply" blend mode', ha='center')
32+
ax.add_patch(Circle((5, 0), 1, color='c', ec='none'))
33+
ax.add_patch(Circle((6, 0), 1, color='m', ec='none', blend_mode='multiply'))
34+
ax.add_patch(Circle((5.5, -0.87), 1, color='y', ec='none', blend_mode='multiply'))
35+
36+
ax.set_xlim(-0.2, 7.2)
37+
ax.set_ylim(-1.9, 1.5)
38+
ax.set_aspect('equal')
39+
ax.axis('off')
40+
41+
42+
# %%
43+
#
44+
# Matplotlib provides a wide range of alternative behaviors to the default
45+
# ("normal") behavior:
46+
#
47+
# * 15 `blend modes`_
48+
# * 6 `Porter-Duff compositing operators`_
49+
#
50+
# (See also :ref:`blend-groups` for the additional capability of blending groups
51+
# of artists.)
52+
#
53+
# These behaviors are specified via the artist's ``blend_mode`` property. You
54+
# can set the property when creating a new artist, or you can call
55+
# `.Artist.set_blend_mode` on an existing artist. You can specify the behavior
56+
# either by string or by member of the `.BlendMode` enumeration.
57+
#
58+
# Below is a gallery illustrating the effect of each ``blend_mode`` option for a
59+
# variety of artists. Although each panel in the gallery has all of its artists
60+
# using the same blend mode, artists in the same axes can have different blend
61+
# modes from each other. Be aware that the background of the axes and the
62+
# background of the figure are artists as well, so their respective colors may
63+
# affect the blending result.
64+
#
65+
# Backends using the Agg renderer (the default) or the Cairo renderer natively
66+
# support all of these ``blend_mode`` options. The vector backends do not
67+
# natively support some of the options, but one can use rasterization (see
68+
# :doc:`/gallery/misc/rasterization_demo`) to achieve the blending effect if the
69+
# fixed resolution of the result is acceptable.
70+
#
71+
# .. _blend modes: https://en.wikipedia.org/wiki/Blend_modes
72+
# .. _Porter-Duff compositing operators: https://www.w3.org/TR/compositing-1/#advancedcompositing
73+
74+
75+
import matplotlib.pyplot as plt
76+
import numpy as np
77+
78+
from matplotlib.patches import Circle, Rectangle
79+
80+
N = 10
81+
data = np.arange(N**2).reshape((N, N)) % (N-1)
82+
83+
fig, axs = plt.subplots(3, 8, figsize=(10, 6), layout='tight')
84+
axs = axs.flatten()
85+
fig.set_facecolor('none')
86+
87+
blend_modes = ['normal',
88+
89+
# Blend modes
90+
'multiply', 'screen', 'overlay', 'darken', 'lighten',
91+
'color dodge', 'color burn', 'hard light', 'soft light',
92+
'difference', 'exclusion',
93+
'hue', 'saturation', 'color', 'luminosity',
94+
95+
# Porter-Duff compositing operators
96+
'knockout', 'erase', 'clear', 'atop', 'xor', 'plus']
97+
98+
for ax in axs:
99+
ax.set_facecolor('none')
100+
ax.set_xlim(0, 1)
101+
ax.set_ylim(0, 1.2)
102+
ax.set_axis_off()
103+
104+
for i, blend_mode in enumerate(blend_modes):
105+
axs[i].imshow(data, cmap='Reds', alpha=0.75, extent=(0, 0.8, 0, 0.8))
106+
107+
# Four different artist types drawn using this blend_mode setting
108+
axs[i].imshow(data[::-1, :], cmap='Blues', alpha=0.75, extent=(0.2, 1, 0.4, 1.2),
109+
blend_mode=blend_mode)
110+
axs[i].text(0.05, 0.15, 'Test', weight='bold', color='c',
111+
blend_mode=blend_mode)
112+
axs[i].plot([0, 1], [1.2, 0], color='y',
113+
blend_mode=blend_mode)
114+
circ = Circle((.65, 0.5), .3, facecolor='g', alpha=0.5, zorder=2,
115+
blend_mode=blend_mode)
116+
axs[i].add_artist(circ)
117+
118+
rect = Rectangle((0, 1.2), 1, .3, facecolor='lightgray', clip_on=False)
119+
axs[i].add_artist(rect)
120+
axs[i].set_title(blend_mode)
121+
122+
plt.show()
123+
124+
125+
# %%
126+
#
127+
# This table shows by backend which options for ``blend_mode`` are supported
128+
# natively (✅) versus supported only through rasterization (🟡).
129+
#
130+
# +----------------+-----+-------+-----+-----+-----+----+
131+
# | Option | Agg | Cairo | SVG | PDF | PGF | PS |
132+
# +================+=====+=======+=====+=====+=====+====+
133+
# | normal [#]_ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
134+
# +----------------+-----+-------+-----+-----+-----+----+
135+
# | multiply, | ✅ | ✅ | ✅ | ✅ | ✅ | 🟡 |
136+
# | screen, | | | | | | |
137+
# | overlay, | | | | | | |
138+
# | darken, | | | | | | |
139+
# | lighten, | | | | | | |
140+
# | color dodge, | | | | | | |
141+
# | color burn, | | | | | | |
142+
# | hard light, | | | | | | |
143+
# | soft light, | | | | | | |
144+
# | difference, | | | | | | |
145+
# | exclusion, | | | | | | |
146+
# | hue, | | | | | | |
147+
# | saturation, | | | | | | |
148+
# | color, | | | | | | |
149+
# | luminosity | | | | | | |
150+
# +----------------+-----+-------+-----+-----+-----+----+
151+
# | knockout [#]_, | ✅ | ✅ | 🟡 | 🟡 | 🟡 | 🟡 |
152+
# | erase [#]_, | | | | | | |
153+
# | clear, | | | | | | |
154+
# | atop, | | | | | | |
155+
# | xor, | | | | | | |
156+
# | plus | | | | | | |
157+
# +----------------+-----+-------+-----+-----+-----+----+
158+
#
159+
# .. [#] also known as "over"
160+
# .. [#] also known as "source"
161+
# .. [#] also known as "destination out"

galleries/users_explain/colors/colors.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,21 +95,24 @@
9595
"Red", "Green", and "Blue" are the intensities of those colors. In combination,
9696
they represent the colorspace.
9797
98+
.. _colors_transparency:
99+
98100
Transparency
99101
============
100102
101103
The *alpha* value of a color specifies its transparency, where 0 is fully
102104
transparent and 1 is fully opaque. When a color is semi-transparent, the
103105
background color will show through.
104106
105-
The *alpha* value determines the resulting color by blending the
107+
By default, the *alpha* value determines the resulting color by blending the
106108
foreground color with the background color according to the formula
107109
108110
.. math::
109111
110112
RGB_{result} = RGB_{background} * (1 - \\alpha) + RGB_{foreground} * \\alpha
111113
112-
The following plot illustrates the effect of transparency.
114+
See :ref:`blend-modes` for alternative blending options. The following plot
115+
illustrates the effect of transparency.
113116
"""
114117

115118
import matplotlib.pyplot as plt
Lines changed: 5 additions & 0 deletions

0 commit comments

Comments
 (0)