root/sparkplot.py

Revision 2, 12.7 kB (checked in by grig, 6 years ago)

Applied patch sent by Mark Kuba:
1) altered the axes sizing. Plot points would be cut off if the
maximum or minimum of a data set was a negative number (line 226)
2) altered the new figure creation call so that multiple calls to
plot_sparkline() do not add plots to the same figure. (line 183)

Added LICENSE (PSF).

  • Property svn:executable set to
Line 
1 #!/usr/bin/env python
2
3 import os, sys, re
4 from optparse import OptionParser
5 import matplotlib
6 # If you want to use a different backend, replace Agg with
7 # Cairo, PS, SVG, GD, Paint etc.
8 # Agg stands for "antigrain rendering" and produces PNG files
9 matplotlib.use('Agg')
10 from pylab import *
11
12 # Avoid name collisions with min and max functions from numarray module
13 min = __builtins__.min
14 max = __builtins__.max
15
16 class Sparkplot:
17     """
18     Creates sparkline graphics, as described by Edward Tufte. Uses the matplotlib library.
19
20     The 2 styles of plots implemented so far are: 'line' and 'bars'
21     """
22     def __init__(self, type='line', data=[], input_file="data.txt", output_file="",
23                  plot_first=True, plot_last=True,
24                  label_first_value=False, label_last_value=False,
25                  plot_min=False, plot_max=False,
26                  label_min=False, label_max=False,                 
27                  draw_hspan=False, hspan_min=-1, hspan_max=0,
28                  label_format="", currency='$', transparency=False, verbose=0):
29
30         self.type = type
31         self.data = data
32         self.input_file = input_file
33         self.output_file = output_file
34         self.plot_first = plot_first
35         self.plot_last = plot_last
36         self.label_first_value = label_first_value
37         self.label_last_value = label_last_value
38         self.plot_min = plot_min
39         self.plot_max = plot_max
40         self.label_min = label_min
41         self.label_max = label_max
42         self.draw_hspan = draw_hspan
43         self.hspan_min = hspan_min
44         self.hspan_max = hspan_max
45         self.label_format = label_format
46         self.currency = currency
47         self.transparency = transparency
48         self.verbose = verbose
49        
50     def process_args(self):
51         parser = OptionParser()
52         parser.add_option("-m", "--type", dest="type",
53                           default="line", help="graphic type (can be 'line' [default], 'bars')")
54         parser.add_option("-i", "--input", dest="input_file",
55                           default="data.txt", help="input data file (default is data.txt)")
56         parser.add_option("-o", "--output", dest="output_file",
57                           default="", help="output data file (default is data.png)")
58         parser.add_option("--noplot_first", action="store_false", dest="plot_first",
59                           default=True, help="do not plot first data point in different color")
60         parser.add_option("--noplot_last", action="store_false", dest="plot_last",
61                           default=True, help="do not plot last data point in different color")
62         parser.add_option("--label_first", action="store_true", dest="label_first_value",
63                           default=False, help="label first data value (default=False)")
64         parser.add_option("--label_last", action="store_true", dest="label_last_value",
65                           default=False, help="label last data value (default=False)")
66         parser.add_option("--plot_min", action="store_true", dest="plot_min",
67                           default=False, help="plot min data point in different color (default=False)")
68         parser.add_option("--plot_max", action="store_true", dest="plot_max",
69                           default=False, help="plot max data point in different color (default=False)")
70         parser.add_option("--label_min", action="store_true", dest="label_min",
71                           default=False, help="label min data value (default=False)")
72         parser.add_option("--label_max", action="store_true", dest="label_max",
73                           default=False, help="label max data value (default=False)")
74         parser.add_option("--draw_hspan", action="store_true", dest="draw_hspan",
75                           default=False, help="draw a horizontal band along the x axis (default=False)")
76         parser.add_option("--hspan_min", dest="hspan_min", type="int",
77                           default=-1, help="specify the min y value for the hspan (default=-1)")
78         parser.add_option("--hspan_max", dest="hspan_max", type="int",
79                           default=0, help="specify the max y value for the hspan (default=0)")
80         parser.add_option("--format", dest="label_format", metavar="FORMAT",
81                           default="", help="format for the value labels (can be empty [default], 'comma', 'currency')")
82         parser.add_option("--currency", dest="currency",
83                           default="$", help="currency symbol (default='$')")
84         parser.add_option("-t", "--transparency", action="store_true", dest="transparency",
85                           default=False, help="set transparency for the image background (default=False)")
86         parser.add_option("--verbose", action="store_true", dest="verbose",
87                           default=False, help="show diagnostic messages (default=False)")
88        
89         (options, args) = parser.parse_args()
90
91         self.type = options.type
92         self.input_file = options.input_file
93         self.output_file = options.output_file
94         self.plot_first = options.plot_first
95         self.plot_last = options.plot_last
96         self.label_first_value = options.label_first_value
97         self.label_last_value = options.label_last_value
98         self.plot_min = options.plot_min
99         self.plot_max = options.plot_max
100         self.label_min = options.label_min
101         self.label_max = options.label_max
102         self.draw_hspan = options.draw_hspan
103         self.hspan_min = options.hspan_min
104         self.hspan_max = options.hspan_max
105         self.label_format = options.label_format
106         self.verbose = options.verbose
107         self.currency = options.currency
108         self.transparency = options.transparency
109
110     def get_input_data(self):
111         """
112         Read input file and fill data list.
113
114         Data file is assumed to contain one column of numbers which will
115         be plotted as a timeseries.
116         """
117         try:
118             f = open(self.input_file)
119         except:
120             print "Input file %s could not be opened" % self.input_file
121             sys.exit(1)
122         data = [float(line.rstrip('\n')) for line in f.readlines() if re.search('\d+', line)]
123         f.close()
124         return data
125        
126     def plot_sparkline(self):
127         """
128         Plot sparkline graphic by using various matplotlib functions.
129         """
130
131         if len(self.data) == 0:
132             self.data = self.get_input_data()
133         num_points = len(self.data)
134         min_data = min(self.data)
135         max_data = max(self.data)
136         sum_data = sum(self.data)
137         avg_data = sum(self.data) / num_points
138         min_index = self.data.index(min_data)
139         max_index = self.data.index(max_data)
140         if self.verbose:
141             print "Plotting %d data points" % num_points
142             print "Min", min_index, min_data
143             print "Max", max_index, max_data
144             print "Avg", avg_data
145             print "Sum", sum_data
146
147         # last_value_len is used for dynamically adjusting the width of the axes
148         # in the axes_position list
149         if self.label_last_value:
150             last_value_len = len(self.format_text(self.data[num_points-1]))
151         elif self.label_max:
152             last_value_len = len(self.format_text(max_data))
153         else:
154             last_value_len = 1
155
156         # delta_height is used for dynamically adjusting the height of the axes
157         # in the axes_position list
158         if self.plot_max or self.label_max or self.label_last_value:
159             delta_height = 0.32
160         else:
161             delta_height = 0.1
162
163         axes_position = [0.02,0.02,1-0.035*last_value_len,1-delta_height]
164
165         # Width of the figure is dynamically adjusted depending on num_points
166         fig_width = min(5, max(1.5, 0.03 * num_points))
167
168         # Height of the figure is set differently depending on plot type
169         if self.type.startswith('line'):
170             fig_height = 0.3
171         elif self.type.startswith('bar'):
172             if self.label_max:
173                 fig_height = 0.5
174             else:
175                 fig_height = 0.1
176
177         if self.verbose:
178             print "Figure width:", fig_width
179             print "Figure height:", fig_height
180             print "Axes position:", axes_position
181
182         # Create a figure with the given width, height and dpi
183         fig = figure(figsize=(fig_width, fig_height), dpi=150)
184
185         if self.type.startswith('line'):
186             # For 'line' plots, simply plot the line
187             plot(range(num_points), self.data, color='gray')
188         elif self.type.startswith('bar'):
189             # For 'bars' plots, simulate bars by plotting vertical lines
190             for i in range(num_points):
191                 if self.data[i] < 0:
192                     color = 'r'
193                 else:
194                     color = 'b' # Use color = '#003163' for a dark blue
195                 plot((i, i), (0, self.data[i]), color=color, linewidth=1.25)
196
197
198         if self.draw_hspan:
199             axhspan(ymin=self.hspan_min, ymax=self.hspan_max, xmin=0, xmax=1, linewidth=0.5, edgecolor='gray', facecolor='gray')
200
201         if self.type == 'line':
202             # Plotting the first, last, min and max data points in a different color only makes sense for 'line' plots
203             if self.plot_first:
204                 plot([0,0], [self.data[0], self.data[0]], 'r.')
205             if self.plot_last:
206                 plot([num_points-1, num_points-1], [self.data[num_points-1], self.data[num_points-1]], 'r.')
207             if self.plot_min:
208                 plot([min_index, min_index], [self.data[min_index], self.data[min_index]], 'b.')
209             if self.plot_max:
210                 plot([max_index, max_index], [self.data[max_index], self.data[max_index]], 'b.')
211
212         if self.label_first_value:
213             text(0, self.data[0], self.format_text(self.data[0]), size=6)
214         if self.label_last_value:
215             text(num_points-1, self.data[num_points-1], self.format_text(self.data[num_points-1]), size=6)
216         if self.label_min:
217             text(min_index, self.data[min_index], self.format_text(min_data), size=6)
218         if self.label_max:
219             text(max_index, self.data[max_index], self.format_text(max_data), size=6)
220
221         # IMPORTANT: commands affecting the axes need to be issued AFTER the plot commands
222
223         # Set the axis limits instead of letting them be computed automatically by matplotlib
224         # We leave some space around the data points so that the plot points for
225         # the first/last/min/max points are displayed
226         axis([-1, num_points, min_data - (abs(min_data)*0.1), max_data + (abs(max_data)*0.1) ])               
227
228         # Turn off all axis display elements (frame, ticks, tick labels)
229         axis('off')
230         # Note that these elements can also be turned off via the following calls,
231         # but I had problems setting the axis limits AND settings the ticks to empty lists
232         #a.set_xticks([])
233         #a.set_yticks([])
234         #a.set_frame_on(False)
235
236         # Set the position for the current axis so that the data labels fit in the figure
237         a = gca()
238         a.set_position(axes_position)
239
240         if self.transparency:
241             fig.figurePatch.set_alpha(0.5)
242             a.axesPatch.set_alpha(0.5)
243
244         # Save the plotted figure to a data file
245         self.generate_output_file()
246
247     def generate_output_file(self):
248         """
249         Save plotted figure to output file.
250
251         The AGG backend will automatically append .PNG to the file name
252         """
253         if not self.output_file:
254             self.output_file = os.path.splitext(self.input_file)[0]
255         if self.verbose:
256             print "Generating output file " + self.output_file + '.png'
257         savefig(self.output_file)
258
259     def format_text(self, data):
260         """
261         Format text for displaying data values.
262
263         The only 2 formats implemented so far are:
264         'currency' (e.g. $12,249)
265         'comma' (e.g. 34,256,798)
266         """
267         if self.label_format == 'currency' or self.label_format == 'comma':
268             t = str(int(data))
269             text = ""
270             if self.label_format == 'currency':
271                 text += self.currency
272             l = len(t)
273             if l > 3:
274                 quot = l / 3
275                 rem = l % 3
276                 text += t[:rem]
277                 for i in range(quot):
278                     text += ',' + t[rem:rem+3]
279                     rem += 3
280             else:
281                 text += t
282         else:
283             text = str(data)
284         return text
285    
286 if __name__ == '__main__':
287     sparkplot = Sparkplot()
288     sparkplot.process_args()
289     sparkplot.plot_sparkline()
290
Note: See TracBrowser for help on using the browser.