November 8, 2012 at 10:56 PM by Dr. Drang
Shortly after my post on combining the Dark Sky API with the Python matplotlib library to create rain intensity plots with confidence intervals, Jay Hickey and Barron Bichon both decided they wanted plots like that on their Desktops, so each of them adapted my script and turned it into GeekTool geeklet. Since this was a much better use of the script than I had ever put it to, I stole some of their ideas and made my own geeklet.
For years I’ve been putting textual weather information and a radar image on my Desktop via NerdTool, a GeekTool workalike that I switched to when I found GeekTool eating up a lot of RAM. Recently, though, I learned that NerdTool can’t display a PNG image as is—it always scales the image, even when you tell it not to. This was not something I wanted for my Dark Sky plot, so I switched back. I think Tynsoe has fixed GeekTool’s memory problem, but I’ll be keeping an eye on it.
Here’s what the plot looks like on my Desktop, which is the Solid Aqua Dark Blue color that OS X has had for years as one of its standard solid Desktop colors.
The horizontal gradations represent increasing rain intensity, the solid white line is the mean predicted intensity, and the faint band around it is the interquartile range (IQR).
Here’s the script that produces the plot:
python: 1: #!/usr/bin/python 2: 3: from __future__ import division 4: import json 5: import urllib 6: from os import environ 7: from sys import exit, argv 8: import matplotlib.pyplot as plt 9: from datetime import datetime, timedelta 10: 11: # Where to save the plot. 12: plotfile = environ['HOME'] + '/Pictures/ds-rain.png' 13: 14: # The list of times on the x axis, 10 minutes apart. 15: checktime = datetime.now() 16: plotminutes = [10, 20, 30, 40, 50] 17: plotlabels = [ (checktime + timedelta(minutes=m)).strftime('%-I:%M') 18: for m in plotminutes ] 19: 20: # The probability and number of standard deviations 21: # associated with the upper and lower bounds. 22: nUpper = .6745 # 75th percentile 23: nLower = -.6745 # 25th percentile 24: 25: # Get the latitude and longitude from the command line 26: # or use default values from downtown Naperville. 27: try: 28: lat = argv 29: lon = argv 30: except IndexError: 31: lat = 41.772903 32: lon = -88.150392 33: 34: # Get my API key and construct the URL 35: try: 36: with open(environ['HOME'] + '/.darksky') as rcfile: 37: for line in rcfile: 38: k, v = line.split(':') 39: if k.strip() == 'APIkey': 40: APIkey = v.strip() 41: dsURL = 'https://api.darkskyapp.com/v1/forecast/%s/%s,%s' % 42: (APIkey, lat, lon) 43: except (IOError, NameError): 44: print "Failed to get API key" 45: exit() 46: 47: # Get the data from Dark Sky. 48: try: 49: jsonString = urllib.urlopen(dsURL).read() 50: weather = json.loads(jsonString) 51: except (IOError, ValueError): 52: print "Connection failure to %s" % dsURL 53: exit() 54: 55: # Pluck out the hourly rain forecast information. 56: startTime = weather['hourPrecipitation']['time'] 57: intensity = [ x['intensity'] for x in weather['hourPrecipitation'] ] 58: upper = [ min(x['intensity'] + x['error']/3*nUpper, 75) for x in weather['hourPrecipitation'] ] 59: lower = [ max(x['intensity'] + x['error']/3*nLower, 0) for x in weather['hourPrecipitation'] ] 60: time = [ (x['time'] - startTime)/60 for x in weather['hourPrecipitation'] ] 61: 62: # Plot the intensity ranges. 63: plt.fill_between([0, 59], [15, 15], [0, 0], color='#ffffff', alpha=.01, linewidth=0) 64: plt.fill_between([0, 59], [30, 30], [15, 15], color='#ffffff', alpha=.02, linewidth=0) 65: plt.fill_between([0, 59], [45, 45], [30, 30], color='#ffffff', alpha=.04, linewidth=0) 66: plt.fill_between([0, 59], [75, 75], [45, 45], color='#ffffff', alpha=.08, linewidth=0) 67: 68: # Plot the values. 69: plt.plot(time, intensity, color='#ffffff', linewidth=3) 70: plt.fill_between(time, upper, lower, color='#ffffff', alpha=.05, linewidth=0) 71: plt.box() 72: plt.axis([0, 59, 0, 65]) 73: plt.xticks(plotminutes, plotlabels, color='#ffffff') 74: plt.yticks() 75: plt.tick_params('y', length=0, color='#ffffff') 76: plt.tick_params('x', color='#ffffff') 77: 78: plt.savefig(plotfile, dpi=50, transparent=True, bbox_inches='tight')
Very little of the script has changed from the original. The main differences are:
- The confidence interval has change from a 95% to the IQR.
- The limits aren’t calculated via the SciPy stats library. The number of standard deviations for the IQR are precalculated and hard-coded into the script.
- The plot has a transparent background, and all the colors have been changed to white with varying alpha values. This matches the style of my other geeklets and means the plot will work on any dark Desktop color or pattern.
- I got rid of the title, the x-axis label, and the y-axis interval descriptors. They seemed too busy for a Desktop graphic and unnecessary for a plot I’ll be looking at every day.
- The x-axis is marked with clock times instead of “minutes from now.”
- The plot is saved to my Pictures folder, instead of wherever directory the script happens to be run from.
Two geeklets are needed: one that runs the script and another that displays the resulting plot. I have them set to refresh every five minutes; by starting them on a minute that’s a multiple of 5, I get nice round numbers in the times along the x-axis.
One thing I didn’t steal was Barron’s spline smoothing code. I don’t use smoothing in the plotting I do for work, so I wasn’t inclined to dig into his code to see how the smoothing worked. I may change my mind if my plots look too jagged.
One last thing: If you try to use my script, or Jay’s or Barron’s, you may find that a little rocket icon appears momentarily in your Dock whenever the script that produces the plot runs.
If you’re annoyed by that (as I am), you can stop it from happening by following this tip to edit the Info.plist file of the Python app associated with that icon. Be aware that you’ll have to make the edit with administrator privileges and a future update by Apple may wipe out your changes, forcing you to do it again. To me, it’s worth it—I hate that little rocket.