2015-03-28 12 views

Trả lời

18

Đã có một số câu trả lời trước cho các câu hỏi tương tự (ví dụ: https://stackoverflow.com/a/22081678/325565), nhưng chúng đề xuất phương pháp tiếp cận phụ tối ưu.

Hầu hết các câu trả lời trước đây đều đề xuất vẽ một đa giác màu trắng trên một ô điền pcolormesh. Đây là ít hơn lý tưởng vì hai lý do:

  1. Nền của các trục không thể trong suốt, như có một đa giác điền nằm phía trên nó
  2. pcolormesh là khá chậm chạp trong việc vẽ và không được suôn sẻ suy.

Đó là một công việc liên lạc nhiều hơn, nhưng có phương pháp vẽ nhanh hơn nhiều và mang lại kết quả hình ảnh tốt hơn: Đặt đường dẫn hình ảnh của một hình vẽ với imshow.

Như một ví dụ:

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() 

enter image description here

+2

Điều này thực sự tuyệt vời! Bạn có thấy một cách để làm cho gradient theo đường cong? tức là thay vì giá trị alpha 'z' kéo dài đều từ 0 đến 1 (trong tọa độ trục), có' z' kéo dài từ 0 đến 'y' (trong tọa độ dữ liệu)? – unutbu

13

Xin lưu ý Joe Kington xứng đáng chiếm tỷ trọng lớn của tín dụng ở đây; đóng góp duy nhất của tôi là zfunc. Phương pháp của anh mở ra nhiều hiệu ứng gradient/blur/drop-shadow . Ví dụ, để làm cho các đường có một mặt dưới mờ đồng đều, bạn có thể sử dụng PIL để xây dựng một lớp alpha là 1 gần đường và 0 gần mép dưới cùng.

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() 

sản lượng

enter image description here

+0

Rất tốt! Tôi sẽ thêm một sự thay đổi hoàn toàn theo chiều dọc, nhưng tôi nghĩ rằng tôi thích sự làm mờ gaussian của bạn nhiều hơn nữa. –

+0

Tầm quan trọng của 'scale' trong phương thức'zfunc' là gì? – Jared

+0

@Jared: 'zfunc' tạo ra hình ảnh PIL nhỏ (mờ). Kích thước của hình ảnh PIL, '(w, h)', phụ thuộc vào sự khác biệt 'xmax-xmin' và' ymax-ymin'. Nếu những khác biệt này quá nhỏ, thì hình ảnh PIL sẽ có độ phân giải thấp. Nếu độ phân giải quá thấp, độ mờ sẽ không mượt mà. Vì vậy, tôi nhân các giá trị 'x' và' y' bằng 'scale' để kích thước hình ảnh PIL sẽ lớn hơn. – unutbu

Các vấn đề liên quan