Skip to content

Commit 19fe788

Browse files
authored
Merge pull request matplotlib#28351 from meeseeksmachine/auto-backport-of-pr-28307-on-v3.9.x
Backport PR matplotlib#28307 on branch v3.9.x (DOC: New color line by value example)
2 parents fd834d8 + 7278380 commit 19fe788

File tree

1 file changed

+173
-32
lines changed

1 file changed

+173
-32
lines changed

galleries/examples/lines_bars_and_markers/multicolored_line.py

+173-32
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,188 @@
33
Multicolored lines
44
==================
55
6-
This example shows how to make a multicolored line. In this example, the line
7-
is colored based on its derivative.
6+
The example shows two ways to plot a line with the a varying color defined by
7+
a third value. The first example defines the color at each (x, y) point.
8+
The second example defines the color between pairs of points, so the length
9+
of the color value list is one less than the length of the x and y lists.
10+
11+
Color values at points
12+
----------------------
13+
814
"""
915

16+
import warnings
17+
1018
import matplotlib.pyplot as plt
1119
import numpy as np
1220

1321
from matplotlib.collections import LineCollection
14-
from matplotlib.colors import BoundaryNorm, ListedColormap
1522

23+
24+
def colored_line(x, y, c, ax, **lc_kwargs):
25+
"""
26+
Plot a line with a color specified along the line by a third value.
27+
28+
It does this by creating a collection of line segments. Each line segment is
29+
made up of two straight lines each connecting the current (x, y) point to the
30+
midpoints of the lines connecting the current point with its two neighbors.
31+
This creates a smooth line with no gaps between the line segments.
32+
33+
Parameters
34+
----------
35+
x, y : array-like
36+
The horizontal and vertical coordinates of the data points.
37+
c : array-like
38+
The color values, which should be the same size as x and y.
39+
ax : Axes
40+
Axis object on which to plot the colored line.
41+
**lc_kwargs
42+
Any additional arguments to pass to matplotlib.collections.LineCollection
43+
constructor. This should not include the array keyword argument because
44+
that is set to the color argument. If provided, it will be overridden.
45+
46+
Returns
47+
-------
48+
matplotlib.collections.LineCollection
49+
The generated line collection representing the colored line.
50+
"""
51+
if "array" in lc_kwargs:
52+
warnings.warn('The provided "array" keyword argument will be overridden')
53+
54+
# Default the capstyle to butt so that the line segments smoothly line up
55+
default_kwargs = {"capstyle": "butt"}
56+
default_kwargs.update(lc_kwargs)
57+
58+
# Compute the midpoints of the line segments. Include the first and last points
59+
# twice so we don't need any special syntax later to handle them.
60+
x = np.asarray(x)
61+
y = np.asarray(y)
62+
x_midpts = np.hstack((x[0], 0.5 * (x[1:] + x[:-1]), x[-1]))
63+
y_midpts = np.hstack((y[0], 0.5 * (y[1:] + y[:-1]), y[-1]))
64+
65+
# Determine the start, middle, and end coordinate pair of each line segment.
66+
# Use the reshape to add an extra dimension so each pair of points is in its
67+
# own list. Then concatenate them to create:
68+
# [
69+
# [(x1_start, y1_start), (x1_mid, y1_mid), (x1_end, y1_end)],
70+
# [(x2_start, y2_start), (x2_mid, y2_mid), (x2_end, y2_end)],
71+
# ...
72+
# ]
73+
coord_start = np.column_stack((x_midpts[:-1], y_midpts[:-1]))[:, np.newaxis, :]
74+
coord_mid = np.column_stack((x, y))[:, np.newaxis, :]
75+
coord_end = np.column_stack((x_midpts[1:], y_midpts[1:]))[:, np.newaxis, :]
76+
segments = np.concatenate((coord_start, coord_mid, coord_end), axis=1)
77+
78+
lc = LineCollection(segments, **default_kwargs)
79+
lc.set_array(c) # set the colors of each segment
80+
81+
return ax.add_collection(lc)
82+
83+
84+
# -------------- Create and show plot --------------
85+
# Some arbitrary function that gives x, y, and color values
86+
t = np.linspace(-7.4, -0.5, 200)
87+
x = 0.9 * np.sin(t)
88+
y = 0.9 * np.cos(1.6 * t)
89+
color = np.linspace(0, 2, t.size)
90+
91+
# Create a figure and plot the line on it
92+
fig1, ax1 = plt.subplots()
93+
lines = colored_line(x, y, color, ax1, linewidth=10, cmap="plasma")
94+
fig1.colorbar(lines) # add a color legend
95+
96+
# Set the axis limits and tick positions
97+
ax1.set_xlim(-1, 1)
98+
ax1.set_ylim(-1, 1)
99+
ax1.set_xticks((-1, 0, 1))
100+
ax1.set_yticks((-1, 0, 1))
101+
ax1.set_title("Color at each point")
102+
103+
plt.show()
104+
105+
####################################################################
106+
# This method is designed to give a smooth impression when distances and color
107+
# differences between adjacent points are not too large. The following example
108+
# does not meet this criteria and by that serves to illustrate the segmentation
109+
# and coloring mechanism.
110+
x = [0, 1, 2, 3, 4]
111+
y = [0, 1, 2, 1, 1]
112+
c = [1, 2, 3, 4, 5]
113+
fig, ax = plt.subplots()
114+
ax.scatter(x, y, c=c, cmap='rainbow')
115+
colored_line(x, y, c=c, ax=ax, cmap='rainbow')
116+
117+
plt.show()
118+
119+
####################################################################
120+
# Color values between points
121+
# ---------------------------
122+
#
123+
124+
125+
def colored_line_between_pts(x, y, c, ax, **lc_kwargs):
126+
"""
127+
Plot a line with a color specified between (x, y) points by a third value.
128+
129+
It does this by creating a collection of line segments between each pair of
130+
neighboring points. The color of each segment is determined by the
131+
made up of two straight lines each connecting the current (x, y) point to the
132+
midpoints of the lines connecting the current point with its two neighbors.
133+
This creates a smooth line with no gaps between the line segments.
134+
135+
Parameters
136+
----------
137+
x, y : array-like
138+
The horizontal and vertical coordinates of the data points.
139+
c : array-like
140+
The color values, which should have a size one less than that of x and y.
141+
ax : Axes
142+
Axis object on which to plot the colored line.
143+
**lc_kwargs
144+
Any additional arguments to pass to matplotlib.collections.LineCollection
145+
constructor. This should not include the array keyword argument because
146+
that is set to the color argument. If provided, it will be overridden.
147+
148+
Returns
149+
-------
150+
matplotlib.collections.LineCollection
151+
The generated line collection representing the colored line.
152+
"""
153+
if "array" in lc_kwargs:
154+
warnings.warn('The provided "array" keyword argument will be overridden')
155+
156+
# Check color array size (LineCollection still works, but values are unused)
157+
if len(c) != len(x) - 1:
158+
warnings.warn(
159+
"The c argument should have a length one less than the length of x and y. "
160+
"If it has the same length, use the colored_line function instead."
161+
)
162+
163+
# Create a set of line segments so that we can color them individually
164+
# This creates the points as an N x 1 x 2 array so that we can stack points
165+
# together easily to get the segments. The segments array for line collection
166+
# needs to be (numlines) x (points per line) x 2 (for x and y)
167+
points = np.array([x, y]).T.reshape(-1, 1, 2)
168+
segments = np.concatenate([points[:-1], points[1:]], axis=1)
169+
lc = LineCollection(segments, **lc_kwargs)
170+
171+
# Set the values used for colormapping
172+
lc.set_array(c)
173+
174+
return ax.add_collection(lc)
175+
176+
177+
# -------------- Create and show plot --------------
16178
x = np.linspace(0, 3 * np.pi, 500)
17179
y = np.sin(x)
18180
dydx = np.cos(0.5 * (x[:-1] + x[1:])) # first derivative
19181

20-
# Create a set of line segments so that we can color them individually
21-
# This creates the points as an N x 1 x 2 array so that we can stack points
22-
# together easily to get the segments. The segments array for line collection
23-
# needs to be (numlines) x (points per line) x 2 (for x and y)
24-
points = np.array([x, y]).T.reshape(-1, 1, 2)
25-
segments = np.concatenate([points[:-1], points[1:]], axis=1)
26-
27-
fig, axs = plt.subplots(2, 1, sharex=True, sharey=True)
28-
29-
# Create a continuous norm to map from data points to colors
30-
norm = plt.Normalize(dydx.min(), dydx.max())
31-
lc = LineCollection(segments, cmap='viridis', norm=norm)
32-
# Set the values used for colormapping
33-
lc.set_array(dydx)
34-
lc.set_linewidth(2)
35-
line = axs[0].add_collection(lc)
36-
fig.colorbar(line, ax=axs[0])
37-
38-
# Use a boundary norm instead
39-
cmap = ListedColormap(['r', 'g', 'b'])
40-
norm = BoundaryNorm([-1, -0.5, 0.5, 1], cmap.N)
41-
lc = LineCollection(segments, cmap=cmap, norm=norm)
42-
lc.set_array(dydx)
43-
lc.set_linewidth(2)
44-
line = axs[1].add_collection(lc)
45-
fig.colorbar(line, ax=axs[1])
46-
47-
axs[0].set_xlim(x.min(), x.max())
48-
axs[0].set_ylim(-1.1, 1.1)
182+
fig2, ax2 = plt.subplots()
183+
line = colored_line_between_pts(x, y, dydx, ax2, linewidth=2, cmap="viridis")
184+
fig2.colorbar(line, ax=ax2, label="dy/dx")
185+
186+
ax2.set_xlim(x.min(), x.max())
187+
ax2.set_ylim(-1.1, 1.1)
188+
ax2.set_title("Color between points")
189+
49190
plt.show()

0 commit comments

Comments
 (0)