在Matplotlib图下的渐变填充

时间:2015-11-10 17:11:47

标签: python matplotlib pillow

我从these two posts on SO获得了很多关于在matplotlib中将渐变填充放在曲线下方的信息。我尝试了同样的事情,在一个轴上绘制多个图并按照它们的顺序和它们的alpha来确保它们可见。我使用此代码输出此图表时出现PIL错误:enter image description here

是否可以填充'在情节下面进一步下降,并修复右下角的错误?我通过将原始数据放在bpaste上来包含我在本例中使用的数据,所以尽管它很长,但这个例子完全是自包含的。

它可能与后端使用有关吗?

谢谢,Jared

import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
from matplotlib.patches import Polygon
from matplotlib.ticker import Formatter, FuncFormatter
import matplotlib
import numpy as np
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFilter


df = pd.read_csv('https://bpaste.net/raw/87cbf69259ae')
df = df.set_index('Date', drop=True)
df.index = pd.to_datetime(df.index)


df1 = pd.read_csv('https://bpaste.net/raw/bc06b26b0b8b')
df1 = df1.set_index('Date', drop=True)
df1.index = pd.to_datetime(df1.index)

def zfunc(x, y, fill_color='k', alpha=1.0, xmin=None, xmax=None, ymin=None, ymax=None):

    if xmax is not None:
        xmax = int(xmax)

    if xmin is not None:
        xmin = int(xmin)

    if ymax is not None:
        ymax = int(ymax)

    if ymin is not None:
        ymin = int(ymin)

    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=25))

    # 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 = int(zalpha.shape[0] / 4)

    zalpha[:n] *= np.linspace(0, 10, n)[:, None]
    z[:,:,-1] = zalpha
    return z


def gradient_fill(x, y, fill_color=None, ax=None, ylabel=None, zfunc=None, **kwargs):

    if ax is None:
        ax = plt.gca()

    if ylabel is not None:
        ax.set_ylabel(ylabel, weight='bold', color='white')

    class DateFormatter(Formatter):
        def __init__(self, dates, fmt='%b \'%y'):
            self.dates = dates
            self.fmt = fmt

        def __call__(self, x, pos=0):
            'Return the label for time x at position pos'
            ind = int(round(x))
            if ind>=len(self.dates) or ind<0: return ''

            return self.dates[ind].strftime(self.fmt)

    def millions(x, pos):
        return '$%d' % x

    dollar_formatter = FuncFormatter(millions)     
    formatter = DateFormatter(df.index)
    ax.yaxis.grid(linestyle='-', alpha=0.5, color='white', zorder=-1) 

    line, = ax.plot(x, y, linewidth=2.0, c=fill_color, **kwargs)

    if fill_color is None:
        fill_color = line.get_color()

    zorder = line.get_zorder()
    if 'alpha' in kwargs:
        alpha = kwargs['alpha']
    else:
        alpha = line.get_alpha()
        alpha = 1.0 if alpha is None else alpha

    xmin, xmax, ymin, ymax = x.min(), x.max(), y.min(), y.max()
    diff = ymax - ymin
    ymin = ymin - diff*0.15
    ymax = diff*0.05 + ymax

    if zfunc is None:
        ## Grab an array of length (cols,rows,spacing) but don't initialize values
        z = np.empty((110, 1, 4), dtype=float)
        ## get color to fill for current axix line
        rgb = mcolors.colorConverter.to_rgb(fill_color)
        z[:,:,:3] = rgb
        z[:,:,-1] = np.linspace(0, alpha, 110)[:,None]
    else:
        z = zfunc(x, y, fill_color=fill_color, alpha=alpha, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax)

    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)
    ax.patch.set_facecolor('black')
    im.set_clip_path(clip_path)


    ax.xaxis.set_major_formatter(formatter)
    ax.yaxis.set_major_formatter(dollar_formatter)

    for tick in ax.get_yticklabels():
        tick.set_color('white')

    for tick in ax.get_xticklabels():
        tick.set_color('white')

    w = 17.5 * 1.5  # approximate size in inches of 1280
    h = 7.5 * 1.5  # approximate size in inches of 720
    fig =  plt.gcf()
    fig.set_size_inches(w, h)
#     fig.autofmt_xdate()
    plt.rcParams['xtick.major.pad']='20'
    matplotlib.rcParams['ytick.major.pad']='20'
    matplotlib.rcParams.update({'font.size': 22})

    ax.set_ylim((ymin, ymax))
    #ax.autoscale(True)
    return line, im, ax

line, im, ax = gradient_fill(np.arange(len(df1.index)), df1['/CL_Close'], fill_color='#fdbf6f', ylabel='Crude Oil', alpha=1.0, zfunc=zfunc)
ax2 = ax.twinx()
gradient_fill(np.arange(len(df.index)), df['/ES_Close'], ax=ax2, fill_color='#cab2d6', ylabel='S&P', alpha=0.75, zfunc=zfunc)
ax2.yaxis.grid(False)

2 个答案:

答案 0 :(得分:4)

问题出在zfunc。 你说你想通过将它们与np.linspace(0,10,n)相乘来淡化你的alphas。

尝试:

zalpha[:n] *= np.linspace(0, 1, n)[:, None]

然后它对我有用......

答案 1 :(得分:1)

这是一种不同于你所采用的方法,但也许你可以使用不同强度的图像和使用像这样的alpha值的色彩图:

{{1}}

code