Matplotlib Fill Between Multiple Lines

Matplotlib fill between multiple lines

If you start the plot in point (0, 0), and therefore do not need to consider the area of the polygon not in the first quadrant, then this should do the trick in this particular situation:

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(0,10,0.1)

# The lines to plot
y1 = 4 - 2*x
y2 = 3 - 0.5*x
y3 = 1 -x

# The upper edge of polygon (min of lines y1 & y2)
y4 = np.minimum(y1, y2)

# Set y-limit, making neg y-values not show in plot
plt.ylim(0, 5)

# Plotting of lines
plt.plot(x, y1,
x, y2,
x, y3)

# Filling between line y3 and line y4
plt.fill_between(x, y3, y4, color='grey', alpha='0.5')
plt.show()

Sample Image

fill between more than two curves matplotlib

You could fill between zero and the minimum of the three curves:

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 20, 2000)
y1 = (36 - 2 * x) / 6
y2 = (30 - 5 * x) / 3
y3 = (40 - 8 * x) / 2

plt.plot(x, y1, label=r'$2 x_{1} + 6 x_{2}\leq 36$')
plt.plot(x, y2, label=r'$x_{1} + 3 x_{2}\leq 30$')
plt.plot(x, y3, label=r'$x_{1} + 2 x_{2}\leq 40$')
plt.xlim((0, 16))
plt.ylim((0, 11))
plt.xlabel(r'$x_1$')
plt.ylabel(r'$x_2$')

plt.fill_between(x, y1, y2, color='grey', alpha=0.5,
interpolate=True)
plt.fill_between(x, 0, np.min([y1, y2, y3], axis=0), color='red', alpha=0.5, hatch='//',
interpolate=True, label='$intersection$')
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
plt.tight_layout()
plt.show()

resulting plot

How to fill between two lines with different x and y?

Here is an approach creating a "polygon" by concatenating the reverse of one curve to the other curve. ax.fill() can be used to fill the polygon. Note that fill_between() can look strange when the x-values aren't nicely ordered (as is the case here after the rotation). Also, the mirror function fill_betweenx() wouldn't be adequate in this case.

import matplotlib.pyplot as plt
import numpy as np

def g(y):
amp = 0.6
return amp * np.exp(-2.5 * y) * np.sin(9.8 * y)

def g_e(y):
amp = 0.66
return amp * np.exp(-2.5 * y_e) * np.sin(8.1 * y_e)

y = np.linspace(0, 0.83, 501)
y_e = np.linspace(0, 1.08, 501)
values = g(y)
values_e = g_e(y)

theta = np.radians(-65.9)
c, s = np.cos(theta), np.sin(theta)
rot_matrix = np.array(((c, s), (-s, c)))
xy = np.array([y, values]).T @ rot_matrix

theta_e = np.radians(-60)
c_e, s_e = np.cos(theta_e), np.sin(theta_e)
rot_matrix_e = np.array(((c_e, s_e), (-s_e, c_e)))
xy_e = np.array([y, values_e]).T @ rot_matrix_e

fig, ax = plt.subplots(figsize=(5, 5))
ax.axis('equal')

x_shift = 0.59
y_shift = 0.813
x_shift_e = 0.54
y_shift_e = 0.83

xf = np.concatenate([xy[:, 0] + x_shift, xy_e[::-1, 0] + x_shift_e])
yf = np.concatenate([xy[:, 1] + y_shift, xy_e[::-1, 1] + y_shift_e])

ax.plot(xy[:, 0] + x_shift, xy[:, 1] + y_shift, c='red')
ax.plot(xy_e[:, 0] + x_shift_e, xy_e[:, 1] + y_shift_e, c='black')
ax.fill(xf, yf, color='dodgerblue', alpha=0.3)
plt.show()

resulting plot

Matplotlib fill_between() Multiple lines

A new top curve can be defined as the minimum between the two existing top curves. And a new bottom curve as the maximum between the existing bottom curves.

The code below is somewhat simplified, using one common x-axis ('e'). The naming is the same as from the question, where bottom and top seem to be switched.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

#global values
sigma_ct_inf = 0
sigma_ct_0 = 0
sigma_c_inf = 30
sigma_c_0 = 12
beta = 0.8

#values T 70
A = 359000
Wb = 40830202.33
Wt = 72079066.94
Mmin = 701.17
Mmax = 978.52

#Magnel Diagram
e = np.arange(0, 1001)

t70_top_0 = pd.Series({'y': ((e - (Wt / A)) / ((Mmin * 10 ** 6) + sigma_ct_0 * Wt)) * 10 ** 6})
t70_bot_0 = pd.Series({'y': ((e + (Wb / A)) / ((Mmin * 10 ** 6) + sigma_c_0 * Wb)) * 10 ** 6})
t70_top_inf = pd.Series({'y': (((e - (Wt / A)) * beta) / ((Mmax * 10 ** 6) - sigma_c_inf * Wt)) * 10 ** 6})
t70_bot_inf = pd.Series({'y': (((e + (Wb / A)) * beta) / ((Mmax * 10 ** 6) - sigma_ct_inf * Wb)) * 10 ** 6})

bot = np.min([t70_bot_0['y'], t70_bot_inf['y']], axis=0)
top = np.max([t70_top_0['y'], t70_top_inf['y']], axis=0)

fig, ax = plt.subplots()
ax.set_title('Magnel Diagram, T-70')
ax.plot(e, t70_top_0['y'], lw=0.5, label='Top, t = 0')
ax.plot(e, t70_bot_0['y'], lw=0.5, label='Bottom, t = 0')
ax.plot(e, t70_top_inf['y'], lw=0.5, label='Top, t = \u221E')
ax.plot(e, t70_bot_inf['y'], lw=0.5, label='Bottom, t = \u221E')

# ax.fill_between(e, t70_top_inf['y'], t70_bot_inf['y'], where=t70_top_inf['y'] < t70_bot_inf['y'], color='r', alpha=0.1)
# ax.fill_between(e, t70_top_0['y'], t70_bot_0['y'], where=t70_top_0['y'] < t70_bot_0['y'], color='b', alpha=0.1)
ax.fill_between(e, bot, top, where=top < bot, color='dodgerblue', alpha=0.4)

ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.ylabel('1/P0 [1/MN]')
plt.xlabel('Eccentricity [mm]')
plt.legend()
plt.show()

resulting plot

Filling In the Area Between Two Lines with a Custom Color Gradient

You could draw a colored rectangle covering the curves. And use the polygon created by fill_between to clip that rectangle:

import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
import numpy as np

x = np.linspace(0, 10, 200)
y1 = np.random.normal(0.02, 1, 200).cumsum() + 20
y2 = np.random.normal(0.05, 1, 200).cumsum() + 50

cm1 = LinearSegmentedColormap.from_list('Temperature Map', ['blue', 'red'])

polygon = plt.fill_between(x, y1, y2, lw=0, color='none')
xlim = (x.min(), x.max())
ylim = plt.ylim()
verts = np.vstack([p.vertices for p in polygon.get_paths()])
gradient = plt.imshow(np.linspace(0, 1, 256).reshape(-1, 1), cmap=cm1, aspect='auto', origin='lower',
extent=[verts[:, 0].min(), verts[:, 0].max(), verts[:, 1].min(), verts[:, 1].max()])
gradient.set_clip_path(polygon.get_paths()[0], transform=plt.gca().transData)
plt.xlim(xlim)
plt.ylim(ylim)
plt.show()

clipping imshow by fill_between polygon

A more complicated alternative, would color such that the upper curve corresponds to red and the lower curve to blue:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 200)
y1 = np.random.normal(0.02, 1, 200).cumsum() + 20
y2 = np.random.normal(0.05, 1, 200).cumsum() + 50

polygon = plt.fill_between(x, y1, y2, lw=0, color='none')
ylim = plt.ylim()
verts = np.vstack([p.vertices for p in polygon.get_paths()])
ymin, ymax = verts[:, 1].min(), verts[:, 1].max()
gradient = plt.imshow(np.array([np.interp(np.linspace(ymin, ymax, 200), [y1i, y2i], np.arange(2))
for y1i, y2i in zip(y1, y2)]).T,
cmap='turbo', aspect='auto', origin='lower', extent=[x.min(), x.max(), ymin, ymax])
gradient.set_clip_path(polygon.get_paths()[0], transform=plt.gca().transData)
plt.ylim(ylim)
plt.show()

gradient between two curves

A variant could be to smooth out the color values in the horizontal direction (but still clip using the original curves):


from scipy.ndimage import gaussian_filter

gradient = plt.imshow(np.array([np.interp(np.linspace(ymin, ymax, 200), [y1i, y2i], np.arange(2))
for y1i, y2i in zip(gaussian_filter(y1, 4, mode='nearest'),
gaussian_filter(y2, 4, mode='nearest'))]).T,
cmap='turbo', aspect='auto', origin='lower', extent=[x.min(), x.max(), ymin, ymax])

gradient between two curves, smoothed

Fill area between a curve and diagonal lines

The fill_between() method is designed especially for that:

# Create empty figure 
_,ax = plt.subplots()
# Plot the two lines
ax.plot(x,x,color='C0')
ax.plot(x,y,color='C3')
# Plot the area with two conditions (x>y) and (x<=y)
ax.fill_between(x, x, y, where=(x > y), color='C0', alpha=0.2,
interpolate=True)
ax.fill_between(x, x, y, where=(x <= y), color='C3', alpha=0.2,
interpolate=True)

And we obtain:

Sample Image

python | matplotlib | plt.fill_between two lines in a Circle

There is similar function fill_betweenx(y, x1, x2)

You can also use plt.fill(x, y) to fill polygon.

Doc: fill_betweenx(), fill()

import matplotlib.pyplot as plt

fig,ax = plt.subplots(figsize=[10,10])

ax.plot([-1/2,0],[-1,0],'k--')
ax.plot([1/2,0],[-1,0],'k--')
ax.plot([1/2,0],[1,0],'k--')
ax.plot([-1/2,0],[1,0],'k--')

ax.plot([-1,0],[-1/2,0],'k--')
ax.plot([1,0],[1/2,0],'k--')
ax.plot([-1,0],[1/2,0],'k--')
ax.plot([1,0],[-1/2,0],'k--')

plt.fill_between([0,-1], [0,-1/2],[0,1/2],color='gray',alpha=0.5)
plt.fill_between([0,1], [0,-1/2],[0,1/2],color='gray',alpha=0.5)

plt.fill_betweenx([0,-1],[0,-1/2],[0,1/2],color='green',alpha=0.5)
plt.fill_betweenx([0,1],[0,-1/2],[0,1/2],color='green',alpha=0.5)

plt.fill([-1, 0, -1/2], [-1/2, 0, -1], color='red',alpha=0.5)
plt.fill([1, 0, 1/2], [1/2, 0, 1], color='red',alpha=0.5)

plt.show()

Sample Image

matplotlib: fill circular sector between two curves in a polar plot

One option is to make a Polygon of the circular sector

import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import numpy as np

fig, ax = plt.subplots(subplot_kw=dict(projection="polar"))

n = 330
r = np.arange(330)

# auto correlate random numbers (see comment below)
a = np.zeros(n) + np.add.accumulate(np.random.normal(0, np.pi / 180, size=n))
b = np.ones(n) * np.pi / 2 + np.add.accumulate(np.random.normal(0, np.pi / 180, size=n))

ax.plot(a, r)
ax.plot(b, r)

arc_theta = np.linspace(a[-1], b[-1], 101)
arc_r = np.ones_like(arc_theta) * 330

start = np.c_[a, r]
arc = np.c_[arc_theta, arc_r]
end = np.c_[b, r][::-1]

verts = np.concatenate([start, arc, end])

p = Polygon(verts, fc="purple", alpha=0.5)
ax.add_artist(p)

plt.show()

gives

Sample Image

How to fill area between two sets of data connected by line?

You have to use the ydata as arguments for your fill_between, not the curves.

Either use ydata directly, or get them from your curve1/2 objects like ydata=curve1.get_ydata().

Here is an example adapted from the docs:

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(-5, 5, 0.01)
y1 = -5*x*x + x + 10
y2 = 5*x*x + x

c1, = plt.plot(x, y1, color='black')
c2, = plt.plot(x, y2, color='black')

# If you want/have to get the data form the plots
# x = c1.get_xdata()
# y1 = c1.get_ydata()
# y2 = c2.get_ydata()

plt.fill_between(x, y1, y2, where=y2 >y1, facecolor='yellow', alpha=0.5)
plt.fill_between(x, y1, y2, where=y2 <=y1, facecolor='red', alpha=0.5)
plt.title('Fill Between')

plt.show()

In the end you get:

Sample Image



Related Topics



Leave a reply



Submit