简体   繁体   English

是否可以在 matplotlib 中获得曲线下的颜色渐变?

[英]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?是否有可能在 matplotlib 中获得这样的颜色渐变?

There have been a handful of previous answers to similar questions (eg https://stackoverflow.com/a/22081678/325565 ), but they recommend a sub-optimal approach.之前有一些类似问题的答案(例如https://stackoverflow.com/a/22081678/325565 ),但他们推荐了一种次优方法。

Most of the previous answers recommend plotting a white polygon over a pcolormesh fill.以前的大多数答案都建议在pcolormesh填充上绘制一个白色多边形。 This is less than ideal for two reasons:这不太理想,原因有两个:

  1. The background of the axes can't be transparent, as there's a filled polygon overlying it轴的背景不能是透明的,因为上面有一个填充的多边形
  2. pcolormesh is fairly slow to draw and isn't smoothly interpolated. pcolormesh绘制速度相当慢,并且不能顺利插入。

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 .这是更多的工作,但有一种方法可以更快地绘制并提供更好的视觉效果:设置使用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;请注意, Joe Kington 在此应得的大部分功劳; my sole contribution is zfunc .我唯一的贡献是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.例如,要使线条具有均匀模糊的底面,您可以使用 PIL 构建一个 alpha 图层,该图层在线条附近为 1,在底部边缘附近为 0。

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(map(tuple, xy.tolist()), 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.当然,通过改变feel_between函数的范围,梯度可以降到0。

just reopening this thread, so just re-opening this thread, ive used a vast majority of joe's code however i cant for the life of me figure out how to shade using 0 at the X axis instead of this upward sloping limit.只是重新打开这个线程,所以只是重新打开这个线程,我使用了绝大多数 joe 的代码,但是我一生都无法弄清楚如何在 X 轴上使用 0 而不是这个向上倾斜的限制来进行着色。

ideally in the belly of the data it should be weak colours and at the heavy positive and negative numbers the extreme colours, i would like the Line for where the shading starts/stops to be the horizontal (0) instead im getting this weird sloping axis which i just dont understand how to rectify.理想情况下,在数据的腹部,它应该是弱颜色,在大量的正负数处是极端颜色,我希望阴影开始/停止的线为水平(0)而不是我得到这个奇怪的倾斜轴我只是不明白如何纠正。

i have a previous chart which you can see (non gradient) what im trying to achieve.我有一个以前的图表,您可以看到(非渐变)我试图实现的目标。 and the red gradient chart im messing up.和红色渐变图表我搞砸了。

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

# Variables
AUM = df['#AHD_AUM'].head(104)
MM = df['#AHD_Managed_Money_Net'].head(104)
PRICE = df['#AHD_Price'].head(104)
DATES = df['DATES'].head(104)

# Date Friendly Variables for Plot
List_AUM = df['#AHD_AUM'].head(104).to_list()
List_MM = df['#AHD_Managed_Money_Net'].head(104).to_list()
List_DATES = df['DATES'].head(104).to_list()
X = 0 * df['#AHD_AUM'].head(104)


# Make a date list changing dates with numbers to avoid the issue with the plot 
interpreting dates
for i in range(len(df['DATES'].head(104))):
count = i
df['count'][i] = 120 - i


# X and Y data variables changed to arrays as when i had these set as dates 
matplotlib hates it    
x = df['count'].head(104).to_numpy()
y = df['#AHD_Managed_Money_Net'].head(104).to_numpy()

#DD = AUM.to_numpy()
#MMM = MM.to_numpy()

def main():
for _ in range(len(DD)):
    gradient_fill(x,y)
plt.show()


def gradient_fill(x,y, fill_color=None, ax=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

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]]) ### i dont 
need this so i have just commented it out
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()

previous version test one im messing up以前的版本测试一个我搞砸了

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM