Overlay rotated Images on plot with Matplotlib - python

I've currently constructed a plot using rectangle Patches to display a sequence of positions.
EDIT: Code used to generate this (built off of the RLPy library)-
def visualize_trajectory(self, trajectory=[[0,0,0,0], [0.1,0.1,0,0]]):
domain_fig = plt.figure()
for i, s in enumerate(trajectory):
x, y, speed, heading = s[:4]
car_xmin = x - self.REAR_WHEEL_RELATIVE_LOC
car_ymin = y - self.CAR_WIDTH / 2.
car_fig = matplotlib.patches.Rectangle(
[car_xmin,
car_ymin],
self.CAR_LENGTH,
self.CAR_WIDTH,
alpha=(0.8 * i) / len(trajectory) )
rotation = Affine2D().rotate_deg_around(
x, y, heading * 180 / np.pi) + plt.gca().transData
car_fig.set_transform(rotation)
plt.gca().add_patch(car_fig)
Is there any way to overlay each of these patches with images? Ideally, there would be a car image instead of a rectangle at each of the positions.
I've played around with AnnotationBbox and TransformedBbox, but both seem to be inflexible when dealing with rotations.

Take a look at
demo_affine_image
from the matplotlib gallery. It shows how
to rotate an image.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms as mtransforms
import matplotlib.cbook as cbook
def get_image():
fn = cbook.get_sample_data("necked_tensile_specimen.png")
arr = plt.imread(fn)
# make background transparent
# you won't have to do this if your car image already has a transparent background
mask = (arr == (1,1,1,1)).all(axis=-1)
arr[mask] = 0
return arr
def imshow_affine(ax, z, *args, **kwargs):
im = ax.imshow(z, *args, **kwargs)
x1, x2, y1, y2 = im.get_extent()
im._image_skew_coordinate = (x2, y1)
return im
N = 7
x = np.linspace(0, 1, N)
y = x**1.1
heading = np.linspace(10, 90, N)
trajectory = list(zip(x, y, heading))
width, height = 0.3, 0.3
car = get_image()
fig, ax = plt.subplots()
for i, t in enumerate(trajectory, start=1):
xi, yi, deg = t
im = imshow_affine(ax, car, interpolation='none',
extent=[0, width, 0, height], clip_on=True,
alpha=0.8*i/len(trajectory))
center_x, center_y = width//2, height//2
im_trans = (mtransforms.Affine2D()
.rotate_deg_around(center_x, center_y, deg)
.translate(xi, yi)
+ ax.transData)
im.set_transform(im_trans)
ax.set_xlim(-0.5, 1.5)
ax.set_ylim(-0.5, 1.7)
plt.show()

Related

How to identify certain coordinates (x,y) for its color after matplotlib.pyplot fill function?

Is there any way to input a certain coordinates (x,y) to pyplot and output its filling color in return?
ex. (0,0) -> Red , (0.75,0) -> blue , (1,1) ->white
import numpy as np
import matplotlib.pyplot as plt
plt.figure(figsize=(15,15))
x,y,x2,y2=[],[],[],[]
for i in np.linspace(0,2,300):
x.append(np.cos(np.pi*i))
y.append(np.sin(np.pi*i))
x2.append(0.5*np.cos(np.pi*i))
y2.append(0.5*np.sin(np.pi*i))
plt.fill(x,y,'b')
plt.fill(x2,y2,'r')
Color Image
Here is one possible method. Let's begin with a modified code (based on yours):
# Modified code
import numpy as np
import matplotlib.pyplot as plt
from shapely.geometry import Point, Polygon # additional code
plt.figure(figsize=(15,15))
x,y,x2,y2=[],[],[],[]
for i in np.linspace(0,2,300):
x.append(np.cos(np.pi*i))
y.append(np.sin(np.pi*i))
x2.append(0.5*np.cos(np.pi*i))
y2.append(0.5*np.sin(np.pi*i))
# grab the fill objects for use later
# .fill return: matplotlib.patches.Polygon object
blue_pgn = plt.fill(x,y,'b') #Plot filled polygons
red_pgn = plt.fill(x2,y2,'r')
plt.show()
It produces the same plot as your code does, but also exposes 2 useful objects, blue_pgn and red_pgn .
This is the second part:
# Create geometries from objects in the plot
blue_geom = Polygon(blue_pgn[0].get_xy())
red_geom = Polygon(red_pgn[0].get_xy())
# create a function
def from_xy_to_color(x,y):
point_xy = Point(x,y)
if red_geom.contains(point_xy):
print("xy:",x,y,", color:","Red")
elif blue_geom.contains(point_xy):
print("xy:",x,y,", color:","Blue")
else:
print("xy:",x,y,", color:","White")
# test all the points
xys = [[0,0], [.75,0], [1,1]]
for xy in xys:
#print(xy)
from_xy_to_color(*xy)
The output from this part:
xy: 0 0 , color: Red
xy: 0.75 0 , color: Blue
xy: 1 1 , color: White
from matplotlib.backends.backend_agg import FigureCanvasAgg
import numpy as np
import matplotlib.pyplot as plt
def circle_coords():
t = np.linspace(0, 2, 300) * np.pi
x = np.cos(t)
y = np.sin(t)
return x, y
def plot(fig):
ax = fig.gca()
x, y = circle_coords()
ax.fill(x, y, 'b')
ax.fill(x / 2, y / 2, 'r')
def capture():
plt.Figure((4, 4), dpi=20)
fig = plt.gcf()
canvas = FigureCanvasAgg(fig)
plot(fig)
canvas.draw()
r = canvas_to_numpy(canvas)
plt.close()
return r
def canvas_to_numpy(canvas):
s, (width, height) = canvas.print_to_buffer()
x = np.frombuffer(s, np.uint8)
return x.reshape((height, width, 4))
def random_points(shape, n_points=4):
height, width = shape
x = np.random.uniform(0, width, size=n_points)
y = np.random.uniform(0, height, size=n_points)
return np.vstack([x, y]).T
def main():
arr = capture()
p = [[360, 274],
[379, 48],
[117, 216]]
fig = plt.gcf()
ax = fig.gca()
np.set_printoptions(precision=2)
print(p)
for x, y in p:
r, g, b, a = arr[y, x] / 255
c = f"{r, g, b}"
print(arr[y, x])
ax.text(x, y, c)
ax.scatter(x, y, c="yellow")
plt.imshow(arr)
plt.show()
plt.close()
main()
See: Matplotlib figure to image as a numpy array

Rotating Rectangles around point with matplotlib

I wanted to rotate Rectangles with matplotlib, but the normal patch always rotates around the lower left corner of the Rectangle. Is there a way to describe more general transformations? For Example I want to rotate about the mid point on the shorter side of the rectangle, so that the result looks like the motion of a clock hand.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
height = 0.1
width = 1
fig, ax = plt.subplots()
ax.set_aspect('equal')
ax.set_xlim([-width * 1.2, width * 1.2])
ax.set_ylim([-width * 1.2, width * 1.2])
ax.plot(0, 0, color='r', marker='o', markersize=10)
for deg in range(0, 360, 45):
rec = Rectangle((0, 0), width=width, height=height,
angle=deg, color=str(deg / 360), alpha=0.9)
ax.add_patch(rec)
Same as the other answer, just without subclassing and using private attributes.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.transforms import Affine2D
height = 0.1
width = 1
fig, ax = plt.subplots()
ax.set_aspect('equal')
ax.set_xlim([-width * 1.2, width * 1.2])
ax.set_ylim([-width * 1.2, width * 1.2])
ax.plot(0, 0, color='r', marker='o', markersize=10)
point_of_rotation = np.array([0, height/2]) # A
# point_of_rotation = np.array([width/2, height/2]) # B
# point_of_rotation = np.array([width/3, height/2]) # C
# point_of_rotation = np.array([width/3, 2*height]) # D
for deg in range(0, 360, 45):
rec = plt.Rectangle(-point_of_rotation, width=width, height=height,
color=str(deg / 360), alpha=0.9,
transform=Affine2D().rotate_deg_around(*(0,0), deg)+ax.transData)
ax.add_patch(rec)
plt.show()
I created a custom Rectangle class which has as additional argument the relative point of rotation - relative means measured in the frame defined by the original lower left coordinates of the rectangle (x0, y0) and the angle.
rec = RotatingRectangle((0, 0), width=1, height=0.1, rel_point_of_rot=(0, 0.05), angle=45)
creates an rectangle where the the relative point (0, 0.05) [the midpoint of the left side] is shifted to the center (0, 0) and than a rotation of 45 deg is performed around this point.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
class RotatingRectangle(Rectangle):
def __init__(self, xy, width, height, rel_point_of_rot, **kwargs):
super().__init__(xy, width, height, **kwargs)
self.rel_point_of_rot = rel_point_of_rot
self.xy_center = self.get_xy()
self.set_angle(self.angle)
def _apply_rotation(self):
angle_rad = self.angle * np.pi / 180
m_trans = np.array([[np.cos(angle_rad), -np.sin(angle_rad)],
[np.sin(angle_rad), np.cos(angle_rad)]])
shift = -m_trans # self.rel_point_of_rot
self.set_xy(self.xy_center + shift)
def set_angle(self, angle):
self.angle = angle
self._apply_rotation()
def set_rel_point_of_rot(self, rel_point_of_rot):
self.rel_point_of_rot = rel_point_of_rot
self._apply_rotation()
def set_xy_center(self, xy):
self.xy_center = xy
self._apply_rotation()
height = 0.1
width = 1
fig, ax = plt.subplots()
ax.set_aspect('equal')
ax.set_xlim([-width * 1.2, width * 1.2])
ax.set_ylim([-width * 1.2, width * 1.2])
ax.plot(0, 0, color='r', marker='o', markersize=10)
point_of_rotation = np.array([0, height/2]) # A
# point_of_rotation = np.array([width/2, height/2]) # B
# point_of_rotation = np.array([width/3, height/2]) # C
# point_of_rotation = np.array([width/3, 2*height]) # D
for deg in range(0, 360, 45):
rec = RotatingRectangle((0, 0), width=width, height=height,
rel_point_of_rot=point_of_rotation,
angle=deg, color=str(deg / 360), alpha=0.9)
ax.add_patch(rec)

Does the fill_between function in matplotlib have a gradient feature? [duplicate]

I happened to see a beautiful graph on this page which is shown below:
Is it possible to get such color gradients in matplotlib?
There have been a handful of previous answers to similar questions (e.g. https://stackoverflow.com/a/22081678/325565), but they recommend a sub-optimal approach.
Most of the previous answers recommend plotting a white polygon over a pcolormesh fill. This is less than ideal for two reasons:
The background of the axes can't be transparent, as there's a filled polygon overlying it
pcolormesh is fairly slow to draw and isn't smoothly interpolated.
It's a touch more work, but there's a method that draws much faster and gives a better visual result: Set the clip path of an image plotted with imshow.
As an example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
from matplotlib.patches import Polygon
np.random.seed(1977)
def main():
for _ in range(5):
gradient_fill(*generate_data(100))
plt.show()
def generate_data(num):
x = np.linspace(0, 100, num)
y = np.random.normal(0, 1, num).cumsum()
return x, y
def gradient_fill(x, y, fill_color=None, ax=None, **kwargs):
"""
Plot a line with a linear alpha gradient filled beneath it.
Parameters
----------
x, y : array-like
The data values of the line.
fill_color : a matplotlib color specifier (string, tuple) or None
The color for the fill. If None, the color of the line will be used.
ax : a matplotlib Axes instance
The axes to plot on. If None, the current pyplot axes will be used.
Additional arguments are passed on to matplotlib's ``plot`` function.
Returns
-------
line : a Line2D instance
The line plotted.
im : an AxesImage instance
The transparent gradient clipped to just the area beneath the curve.
"""
if ax is None:
ax = plt.gca()
line, = ax.plot(x, y, **kwargs)
if fill_color is None:
fill_color = line.get_color()
zorder = line.get_zorder()
alpha = line.get_alpha()
alpha = 1.0 if alpha is None else alpha
z = np.empty((100, 1, 4), dtype=float)
rgb = mcolors.colorConverter.to_rgb(fill_color)
z[:,:,:3] = rgb
z[:,:,-1] = np.linspace(0, alpha, 100)[:,None]
xmin, xmax, ymin, ymax = x.min(), x.max(), y.min(), y.max()
im = ax.imshow(z, aspect='auto', extent=[xmin, xmax, ymin, ymax],
origin='lower', zorder=zorder)
xy = np.column_stack([x, y])
xy = np.vstack([[xmin, ymin], xy, [xmax, ymin], [xmin, ymin]])
clip_path = Polygon(xy, facecolor='none', edgecolor='none', closed=True)
ax.add_patch(clip_path)
im.set_clip_path(clip_path)
ax.autoscale(True)
return line, im
main()
Please note Joe Kington deserves the lion's share of the credit here; my sole contribution is zfunc.
His method opens to door to many gradient/blur/drop-shadow
effects. For example, to make the lines have an evenly blurred underside, you
could use PIL to build an alpha layer which is 1 near the line and 0 near the bottom edge.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import matplotlib.patches as patches
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFilter
np.random.seed(1977)
def demo_blur_underside():
for _ in range(5):
# gradient_fill(*generate_data(100), zfunc=None) # original
gradient_fill(*generate_data(100), zfunc=zfunc)
plt.show()
def generate_data(num):
x = np.linspace(0, 100, num)
y = np.random.normal(0, 1, num).cumsum()
return x, y
def zfunc(x, y, fill_color='k', alpha=1.0):
scale = 10
x = (x*scale).astype(int)
y = (y*scale).astype(int)
xmin, xmax, ymin, ymax = x.min(), x.max(), y.min(), y.max()
w, h = xmax-xmin, ymax-ymin
z = np.empty((h, w, 4), dtype=float)
rgb = mcolors.colorConverter.to_rgb(fill_color)
z[:,:,:3] = rgb
# Build a z-alpha array which is 1 near the line and 0 at the bottom.
img = Image.new('L', (w, h), 0)
draw = ImageDraw.Draw(img)
xy = np.column_stack([x, y])
xy -= xmin, ymin
# Draw a blurred line using PIL
draw.line(list(map(tuple, xy)), fill=255, width=15)
img = img.filter(ImageFilter.GaussianBlur(radius=100))
# Convert the PIL image to an array
zalpha = np.asarray(img).astype(float)
zalpha *= alpha/zalpha.max()
# make the alphas melt to zero at the bottom
n = zalpha.shape[0] // 4
zalpha[:n] *= np.linspace(0, 1, n)[:, None]
z[:,:,-1] = zalpha
return z
def gradient_fill(x, y, fill_color=None, ax=None, zfunc=None, **kwargs):
if ax is None:
ax = plt.gca()
line, = ax.plot(x, y, **kwargs)
if fill_color is None:
fill_color = line.get_color()
zorder = line.get_zorder()
alpha = line.get_alpha()
alpha = 1.0 if alpha is None else alpha
if zfunc is None:
h, w = 100, 1
z = np.empty((h, w, 4), dtype=float)
rgb = mcolors.colorConverter.to_rgb(fill_color)
z[:,:,:3] = rgb
z[:,:,-1] = np.linspace(0, alpha, h)[:,None]
else:
z = zfunc(x, y, fill_color=fill_color, alpha=alpha)
xmin, xmax, ymin, ymax = x.min(), x.max(), y.min(), y.max()
im = ax.imshow(z, aspect='auto', extent=[xmin, xmax, ymin, ymax],
origin='lower', zorder=zorder)
xy = np.column_stack([x, y])
xy = np.vstack([[xmin, ymin], xy, [xmax, ymin], [xmin, ymin]])
clip_path = patches.Polygon(xy, facecolor='none', edgecolor='none', closed=True)
ax.add_patch(clip_path)
im.set_clip_path(clip_path)
ax.autoscale(True)
return line, im
demo_blur_underside()
yields
I've tried something :
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
xData = range(100)
yData = range(100)
plt.plot(xData, yData)
NbData = len(xData)
MaxBL = [[MaxBL] * NbData for MaxBL in range(100)]
Max = [np.asarray(MaxBL[x]) for x in range(100)]
for x in range (50, 100):
plt.fill_between(xData, Max[x], yData, where=yData >Max[x], facecolor='red', alpha=0.02)
for x in range (0, 50):
plt.fill_between(xData, yData, Max[x], where=yData <Max[x], facecolor='green', alpha=0.02)
plt.fill_between([], [], [], facecolor='red', label="x > 50")
plt.fill_between([], [], [], facecolor='green', label="x < 50")
plt.legend(loc=4, fontsize=12)
plt.show()
fig.savefig('graph.png')
.. and the result:
Of course the gradient could go down to 0 by changing the range of feel_between function.

combining patches to obtain single transparency in matplotlib

I calculate ellipse-like polygons which are drawn over another image. The polygons together form one shape and must have a single transparency. Unfortunately, if I draw one polygon after another, the stack gets different transparencies. In the example below I have 3 polygons, creating 3 transparencies, resulting in different grey areas instead of one. Does anyone know how to obtain a single transparency?
Thank you
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon, Rectangle
def get_ellipse_coords(a=0.0, b=0.0, x=0.0, y=0.0, angle=0.0, k=2):
pts = np.zeros((360*k+1, 2))
beta = -angle * np.pi/180.0
sin_beta = np.sin(beta)
cos_beta = np.cos(beta)
alpha = np.radians(np.r_[0.:360.:1j*(360*k+1)])
sin_alpha = np.sin(alpha)
cos_alpha = np.cos(alpha)
pts[:, 0] = x + (a * cos_alpha * cos_beta - b * sin_alpha * sin_beta)
pts[:, 1] = y + (a * cos_alpha * sin_beta + b * sin_alpha * cos_beta)
return pts
if __name__ == '__main__':
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal')
plt.xlim([-5,5])
plt.ylim([-5,5])
c='0.5'
alp=0.5
ax.add_patch(Rectangle((1.5, -4.0),0.5,8.0,
color='lightblue', zorder=1))
pts = get_ellipse_coords(a=4.0, b=1.0)
ax.add_patch(Polygon(pts, closed=True, lw=0.5,
color=c, alpha=alp,zorder=2))
pts = get_ellipse_coords(a=4.0, b=1.0, x=1.0, angle=30)
ax.add_patch(Polygon(pts, closed=True, lw=0.5,
color=c, alpha=alp, zorder=2))
pts = get_ellipse_coords(a=2.0, b=0.25, x=2.0, y=-2.0, angle=250)
ax.add_patch(Polygon(pts, closed=True, lw=0.5,
color=c, alpha=alp, zorder=2))
plt.show()
np.concatenate, suggested in the first answer, does the trick I need:
[...]
ax.add_patch(Rectangle((1.5, -4.0),0.5,8.0,
color='lightblue', zorder=1))
pts1 = get_ellipse_coords(a=4.0, b=1.0)
pts2 = get_ellipse_coords(a=4.0, b=1.0, x=1.0, angle=30)
pts3 = get_ellipse_coords(a=2.0, b=0.25, x=2.0, y=-2.0, angle=250)
stoppoint = np.array([[np.nan,np.nan]])
pts = np.concatenate((pts1, stoppoint, pts2))
pts = np.concatenate((pts, stoppoint, pts3))
ax.add_patch(Polygon(pts, closed=True, lw=0.0,
color=c, alpha=alp, zorder=2))
Like this:
pts1 = get_ellipse_coords(a=4.0, b=1.0)
pts2 = get_ellipse_coords(a=4.0, b=1.0, x=1.0, angle=30)
stoppoint = np.array([[nan,nan]])
pts = np.concatenate((pts1, stoppoint, pts2))
ax.add_patch(Polygon(pts, closed=True, lw=0.5,
color=c, alpha=alp,zorder=2))
The stoppoint prevents a line connecting the ellipses.
The accepted solution did not work for me, I had to build my compound path with matplotlib.path.Path.make_compound_path_from_polys and then pass its vertices to my matplotlib.patches.Polygon class instance.
The gist of it as documented is as follows:
data = np.random.randn(1000)
n, bins = np.histogram(data, 50)
# get the corners of the rectangles for the histogram
left = np.array(bins[:-1])
right = np.array(bins[1:])
bottom = np.zeros(len(left))
top = bottom + n
# we need a (numrects x numsides x 2) numpy array for the path helper
# function to build a compound path
XY = np.array([[left, left, right, right], [bottom, top, top, bottom]]).T
# get the Path object
barpath = path.Path.make_compound_path_from_polys(XY)
# make a patch out of it
patch = patches.PathPatch(barpath)
ax.add_patch(patch)

Is it possible to get color gradients under curve in matplotlib?

I happened to see a beautiful graph on this page which is shown below:
Is it possible to get such color gradients in matplotlib?
There have been a handful of previous answers to similar questions (e.g. https://stackoverflow.com/a/22081678/325565), but they recommend a sub-optimal approach.
Most of the previous answers recommend plotting a white polygon over a pcolormesh fill. This is less than ideal for two reasons:
The background of the axes can't be transparent, as there's a filled polygon overlying it
pcolormesh is fairly slow to draw and isn't smoothly interpolated.
It's a touch more work, but there's a method that draws much faster and gives a better visual result: Set the clip path of an image plotted with imshow.
As an example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
from matplotlib.patches import Polygon
np.random.seed(1977)
def main():
for _ in range(5):
gradient_fill(*generate_data(100))
plt.show()
def generate_data(num):
x = np.linspace(0, 100, num)
y = np.random.normal(0, 1, num).cumsum()
return x, y
def gradient_fill(x, y, fill_color=None, ax=None, **kwargs):
"""
Plot a line with a linear alpha gradient filled beneath it.
Parameters
----------
x, y : array-like
The data values of the line.
fill_color : a matplotlib color specifier (string, tuple) or None
The color for the fill. If None, the color of the line will be used.
ax : a matplotlib Axes instance
The axes to plot on. If None, the current pyplot axes will be used.
Additional arguments are passed on to matplotlib's ``plot`` function.
Returns
-------
line : a Line2D instance
The line plotted.
im : an AxesImage instance
The transparent gradient clipped to just the area beneath the curve.
"""
if ax is None:
ax = plt.gca()
line, = ax.plot(x, y, **kwargs)
if fill_color is None:
fill_color = line.get_color()
zorder = line.get_zorder()
alpha = line.get_alpha()
alpha = 1.0 if alpha is None else alpha
z = np.empty((100, 1, 4), dtype=float)
rgb = mcolors.colorConverter.to_rgb(fill_color)
z[:,:,:3] = rgb
z[:,:,-1] = np.linspace(0, alpha, 100)[:,None]
xmin, xmax, ymin, ymax = x.min(), x.max(), y.min(), y.max()
im = ax.imshow(z, aspect='auto', extent=[xmin, xmax, ymin, ymax],
origin='lower', zorder=zorder)
xy = np.column_stack([x, y])
xy = np.vstack([[xmin, ymin], xy, [xmax, ymin], [xmin, ymin]])
clip_path = Polygon(xy, facecolor='none', edgecolor='none', closed=True)
ax.add_patch(clip_path)
im.set_clip_path(clip_path)
ax.autoscale(True)
return line, im
main()
Please note Joe Kington deserves the lion's share of the credit here; my sole contribution is zfunc.
His method opens to door to many gradient/blur/drop-shadow
effects. For example, to make the lines have an evenly blurred underside, you
could use PIL to build an alpha layer which is 1 near the line and 0 near the bottom edge.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import matplotlib.patches as patches
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFilter
np.random.seed(1977)
def demo_blur_underside():
for _ in range(5):
# gradient_fill(*generate_data(100), zfunc=None) # original
gradient_fill(*generate_data(100), zfunc=zfunc)
plt.show()
def generate_data(num):
x = np.linspace(0, 100, num)
y = np.random.normal(0, 1, num).cumsum()
return x, y
def zfunc(x, y, fill_color='k', alpha=1.0):
scale = 10
x = (x*scale).astype(int)
y = (y*scale).astype(int)
xmin, xmax, ymin, ymax = x.min(), x.max(), y.min(), y.max()
w, h = xmax-xmin, ymax-ymin
z = np.empty((h, w, 4), dtype=float)
rgb = mcolors.colorConverter.to_rgb(fill_color)
z[:,:,:3] = rgb
# Build a z-alpha array which is 1 near the line and 0 at the bottom.
img = Image.new('L', (w, h), 0)
draw = ImageDraw.Draw(img)
xy = np.column_stack([x, y])
xy -= xmin, ymin
# Draw a blurred line using PIL
draw.line(list(map(tuple, xy)), fill=255, width=15)
img = img.filter(ImageFilter.GaussianBlur(radius=100))
# Convert the PIL image to an array
zalpha = np.asarray(img).astype(float)
zalpha *= alpha/zalpha.max()
# make the alphas melt to zero at the bottom
n = zalpha.shape[0] // 4
zalpha[:n] *= np.linspace(0, 1, n)[:, None]
z[:,:,-1] = zalpha
return z
def gradient_fill(x, y, fill_color=None, ax=None, zfunc=None, **kwargs):
if ax is None:
ax = plt.gca()
line, = ax.plot(x, y, **kwargs)
if fill_color is None:
fill_color = line.get_color()
zorder = line.get_zorder()
alpha = line.get_alpha()
alpha = 1.0 if alpha is None else alpha
if zfunc is None:
h, w = 100, 1
z = np.empty((h, w, 4), dtype=float)
rgb = mcolors.colorConverter.to_rgb(fill_color)
z[:,:,:3] = rgb
z[:,:,-1] = np.linspace(0, alpha, h)[:,None]
else:
z = zfunc(x, y, fill_color=fill_color, alpha=alpha)
xmin, xmax, ymin, ymax = x.min(), x.max(), y.min(), y.max()
im = ax.imshow(z, aspect='auto', extent=[xmin, xmax, ymin, ymax],
origin='lower', zorder=zorder)
xy = np.column_stack([x, y])
xy = np.vstack([[xmin, ymin], xy, [xmax, ymin], [xmin, ymin]])
clip_path = patches.Polygon(xy, facecolor='none', edgecolor='none', closed=True)
ax.add_patch(clip_path)
im.set_clip_path(clip_path)
ax.autoscale(True)
return line, im
demo_blur_underside()
yields
I've tried something :
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
xData = range(100)
yData = range(100)
plt.plot(xData, yData)
NbData = len(xData)
MaxBL = [[MaxBL] * NbData for MaxBL in range(100)]
Max = [np.asarray(MaxBL[x]) for x in range(100)]
for x in range (50, 100):
plt.fill_between(xData, Max[x], yData, where=yData >Max[x], facecolor='red', alpha=0.02)
for x in range (0, 50):
plt.fill_between(xData, yData, Max[x], where=yData <Max[x], facecolor='green', alpha=0.02)
plt.fill_between([], [], [], facecolor='red', label="x > 50")
plt.fill_between([], [], [], facecolor='green', label="x < 50")
plt.legend(loc=4, fontsize=12)
plt.show()
fig.savefig('graph.png')
.. and the result:
Of course the gradient could go down to 0 by changing the range of feel_between function.

Categories

Resources