0

I am trying to use the gradient fill under curves in matplotlib with datetime values on x-axis using this answer. I tried converting the datetime values to float to fix this, but that was not helpful.

The MWE and the error is given below. How to apply gradient fill under curves with datetime values on x-axis in matplotlib?

MWE

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
from datetime import datetime, timedelta

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

# x = np.linspace(0, 100, 100)
# y = np.sin(x) + x

x = np.arange(datetime(2010,7,1), datetime(2015,7,1), timedelta(hours=12)).astype(datetime)
y = np.linspace(0, 100, len(x))

fig, ax = plt.subplots()
ax.plot(x, y)
gradient_fill(x, y, ax=ax, zfunc=zfunc)
ax.grid()
fig.savefig("test.pdf")
plt.show()

Error

  File "./gradient_fill_test.py", line 95, in <module>
    gradient_fill(x, y, ax=ax, zfunc=zfunc)

  File "./radient_fill_test.py", line 69, in gradient_fill
    z = zfunc(x, y, fill_color=fill_color, alpha=alpha)

  File "./gradient_fill_test.py", line 24, in zfunc
    x = (x*scale).astype(int)

TypeError: unsupported operand type(s) for *: 'datetime.datetime' and 'int'
Tom Kurushingal
  • 6,086
  • 20
  • 54
  • 86
  • try converting the datetime values to ordinal, there are SO answer showing how to do that – Trenton McKinney Nov 11 '21 at 18:22
  • I am able to convert and plot them, but the x-axis is now in the ordinal format. – Tom Kurushingal Nov 12 '21 at 00:22
  • Now you have to get_xticks convert them to datetime, and set_xticks, and set_xticklabels. Similar to this answer https://stackoverflow.com/a/69171277/7758804. If this has been helpful, feel free to upvote that answer. – Trenton McKinney Nov 12 '21 at 00:37
  • Your answer at https://stackoverflow.com/a/69171277/7758804 works perfectly with dates; i.e., now I am able to convert dates to ordinal and vice-versa. But time values get clipped, any recommendation on converting datetime to ordinal and vice-versa? – Tom Kurushingal Nov 12 '21 at 16:10

1 Answers1

0

Try something like:

x = np.arange(datetime(2010,7,1), datetime(2015,7,1), timedelta(hours=12)).astype(datetime)
y = np.linspace(0, 100, len(x))

fig, ax = plt.subplots()
ax.plot(x, y)
gradient_fill(mdates.date2num(x), y, ax=ax, zfunc=zfunc)

I couldn't actually get that to work because your zfunc seems to have a bug in it, but if you fix that it will get you properly labeled date ticks.

Jody Klymak
  • 4,979
  • 2
  • 15
  • 31