2

I'm trying to product a line chart with a gradient fill beneath the line. I have searched for hours online for a solution, but none of them deal specifically with what I'm looking for.

ma = average_rate(t[0], window=900, interval=60)
fig = Figure(figsize=(8.5, 1.5), dpi=100)
canvas = FigureCanvasAgg(fig)

col = '#4f81b3'
ax = fig.add_axes([0.076, 0.11, 0.88, 0.74])

dts, vals = zip(*ma)
ax.fill(dts, vals, color=col)
fig.savefig(b, format='png')

This produces the following chart:

I have tried to use colormaps, contourf, fill_between etc with code I found online, but couldn't get it working and I'm really hoping someone has a simple solution to this problem.

With much help from @Ajean, my latest code is as follows:

    # dtseries contains a list of datetime.datetime values
    # yvalues contains a corresponding list of y-axis values
    # len(dtseries) == len(yvalues)

    import numpy as np

    # Need dpi for png generation
    fig = Figure(figsize=(8.5, 2), dpi=100)
    # Create axes directly on figure [left, bottom, width, height]
    ax = fig.add_axes([0.076, 0.11, 0.88, 0.74])

    xlims = mdates.date2num([dtseries[0], dtseries[-1]])

    # Construct an image linearly increasing in y
    xv, yv = np.meshgrid(np.linspace(0,1,50), np.linspace(0,1,50))
    zv = yv

    ax.imshow(zv, cmap='PuBu', origin='lower',
              extent=[xlims[0], xlims[1], min(yvalues), max(yvalues)])

    # Erase above the data by filling with white
    ax.fill_between(dtseries, yvalues, max(yvalues), color='w')

    # Make the line plot over the top
    colr = '#325272'
    ax.plot(dtseries, yvalues, color=colr, linewidth=0.5)

    ax.set_ylim(min(yvalues), max(yvalues))

    # Render chart as png to memory
    b = BytesIO()
    fig.savefig(b, format='png')
    return b.getvalue()

This is what I get:

grg
  • 5,023
  • 3
  • 34
  • 50
Rob
  • 301
  • 3
  • 10
  • It's hard to debug without the data ... are you getting any errors? It looks like you should be getting an error somewhere, because it's not plotting anything at all. – Ajean Jan 15 '15 at 16:08
  • @Ajean No errors - just doesn't output anything. I have gone ahead and accepted your solution and really appreciate all your help. I may try again with some fake data so that we are both using the same dataset. Once again, thank you so much for your assistance! – Rob Jan 15 '15 at 21:48
  • @Ajean: Please let me know if you are interested in helping me out or could point me in the direction of where I could get some of my charting 'plumbing' done for $$. Right now I have bigger fish to fry. Thanks! – Rob Jan 15 '15 at 23:05

1 Answers1

1

There is actually a fairly nice answer to this at this SO question, and the following borrows the main idea, but I've substituted a call to imshow rather than contourf because I think it looks smoother. I borrow the key element, which is to place the gradient over the whole image and then 'erase' above the data using fill_between.

import numpy as np
import matplotlib.pyplot as plt
import datetime
import matplotlib.dates as mdates

# Fake data using dates as requested
xdata = np.array([datetime.datetime.today()+
                 datetime.timedelta(days=1)*i for i in range(15)])
ydata = np.cumsum(np.random.uniform(size=len(xdata)))
xlims = mdates.date2num([xdata[0], xdata[-1]])

# Construct an image linearly increasing in y
xv, yv = np.meshgrid(np.linspace(0,1,50), np.linspace(0,1,50))
zv = yv

# Draw the image over the whole plot area
fig, ax = plt.subplots(figsize=(5,3))
ax.imshow(zv, cmap='YlGnBu_r', origin='lower',
          extent=[xlims[0], xlims[1], ydata.min(), ydata.max()])

# Erase above the data by filling with white
ax.fill_between(xdata, ydata, ydata.max(), color='w')

# Make the line plot over the top
ax.plot(xdata, ydata, 'b-', linewidth=2)

ax.set_ylim(ydata.min(), ydata.max())
fig.autofmt_xdate()

plt.show()

This gives me this plot:

smooth gradient fill plot

Community
  • 1
  • 1
Ajean
  • 5,528
  • 14
  • 46
  • 69
  • I really appreciate the feedback. I'll give you feedback once I can get in front of my PC again. Is it a problem if I'm using a time series on the x-axis instead of a regular numeric set? - Rob – Rob Jan 14 '15 at 21:37
  • It would just affect how you set the limits. imshow won't react correctly with pure dates, but you can take your limits and convert them to the right thing (to put in `extent=`) with `date2num` from `matplotlib.dates` – Ajean Jan 15 '15 at 02:34
  • I really appreciate the help. I'm struggling to use the meshgrid function because I'm using a time series and I'm not that familiar with numpy. Is it possible for you to update your answer with a time series on the x-axis instead of regular numeric values? This would be a great help! TIA. – Rob Jan 15 '15 at 14:53
  • You don't have to change the meshgrid part at all, that's just to make the data for the image. You have to change the *extent*. I updated the answer. – Ajean Jan 15 '15 at 15:17
  • Thanks for the feedback. I have the same extent params as you show, but I get no chart. Obviously I'm doing something silly at this point in time, which probably relates to the fact that I am not using pyplot because this code runs on an Ubuntu server and I generate and manipulate Figures directly. I will update my post above with my present code, and maybe you could see what the issue is... – Rob Jan 15 '15 at 15:51
  • Also consider using `aspect='auto'` for `imshow` method. I've got too close datetimes (few minutes difference) and my chart turned into a single line. – UnholyRaven May 08 '20 at 19:41