2
# This program is free software; you can redistribute it and/or modify
3
# it under the terms of the GNU General Public License as published by
4
# the Free Software Foundation; either version 2 of the License, or
5
# (at your option) any later version.
7
# This program is distributed in the hope that it will be useful,
8
# but WITHOUT ANY WARRANTY; without even the implied warranty of
9
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
# GNU General Public License for more details.
12
# You should have received a copy of the GNU General Public License
13
# along with this program; if not, write to the Free Software
14
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
23
def rect_print(name, rect):
24
print ("%s: height=%d, width=%d, x=%d, y=%d" %
25
(name, rect.height, rect.width, rect.x, rect.y))
27
# For gproperties info, see:
28
# http://www.pygtk.org/docs/pygtk/class-gtkcontainer.html#function-gtk--container-class-install-child-property
30
def _line_helper(cairo_ct, cell_area, points, for_fill=False):
32
bottom_baseline = cell_area.y + cell_area.height
36
for index in range(0, len(points)):
39
# If stats value == 0, we don't want to draw a line
40
is_zero = bool(y == bottom_baseline)
42
# If the line is for filling, alter the coords so that fill covers
43
# the same area as the parent sparkline: by default, fill is one pixel
48
elif index == (len(points) - 1):
50
elif last_was_zero and is_zero:
54
cairo_ct.move_to(x, y)
55
elif last_was_zero and is_zero and not for_fill:
56
cairo_ct.move_to(x, y)
58
cairo_ct.line_to(x, y)
61
last_was_zero = is_zero
65
def draw_line(cairo_ct, cell_area, points):
69
last_point = _line_helper(cairo_ct, cell_area, points)
77
def draw_fill(cairo_ct, cell_area, points, taper=False):
81
last_point = _line_helper(cairo_ct, cell_area, points, for_fill = True)
87
baseline_y = cell_area.height + cell_area.y + 1
89
x = cell_area.width + cell_area.x
93
# Box out the area to fill
94
cairo_ct.line_to(x + 1, baseline_y)
95
cairo_ct.line_to(cell_area.x - 1, baseline_y)
101
class CellRendererSparkline(gtk.CellRenderer):
106
# 'name' : (gobject.TYPE_*,
107
# nickname, long desc, (type related args), mode)
108
# Type related args can be min, max for int (etc.), or default value
109
# for strings and bool
110
'data_array' : (gobject.TYPE_PYOBJECT, "Data Array",
111
"Array of data points for the graph",
112
gobject.PARAM_READWRITE),
113
'reversed': (gobject.TYPE_BOOLEAN, "Reverse data",
114
"Process data from back to front.",
115
0, gobject.PARAM_READWRITE),
119
gtk.CellRenderer.__init__(self)
124
self.reversed = False
127
def do_render(self, window, widget, background_area, cell_area,
129
# window : gtk.gdk.Window (not plain window)
130
# widget : Parent widget (manager treeview)
131
# background_area : GdkRectangle: entire cell area
132
# cell_area : GdkRectangle: area normally rendered by cell
133
# expose_area : GdkRectangle: area that needs updating
134
# flags : flags that affect rendering
135
# flags = gtk.CELL_RENDERER_SELECTED, gtk.CELL_RENDERER_PRELIT,
136
# gtk.CELL_RENDERER_INSENSITIVE or gtk.CELL_RENDERER_SORTED
138
# Indent of the gray border around the graph
140
# Indent of graph from border
142
GRAPH_PAD = (BORDER_PADDING + GRAPH_INDENT)
144
# We don't use yalign, since we expand to the entire height
145
#yalign = self.get_property("yalign")
146
xalign = self.get_property("xalign")
148
# Set up graphing bounds
149
graph_x = (cell_area.x + GRAPH_PAD)
150
graph_y = (cell_area.y + GRAPH_PAD)
151
graph_width = (cell_area.width - (GRAPH_PAD * 2))
152
graph_height = (cell_area.height - (GRAPH_PAD * 2))
154
# XXX: This needs to be smarter, we need to either center the graph
155
# or have some way of making it variable sized
156
pixels_per_point = (graph_width / ((len(self.data_array) or 1) - 1))
158
# Graph width needs to be some multiple of the amount of data points
160
graph_width = (pixels_per_point * ((len(self.data_array) or 1) - 1))
162
# Recalculate border width based on the amount we are graphing
163
#border_width = graph_width + GRAPH_PAD
164
border_width = graph_width + (GRAPH_INDENT * 2)
167
empty_space = cell_area.width - border_width - (BORDER_PADDING * 2)
169
xalign_space = int(empty_space * xalign)
170
cell_area.x += xalign_space
171
graph_x += xalign_space
173
cairo_ct = window.cairo_create()
174
cairo_ct.set_line_width(3)
175
cairo_ct.set_line_cap(cairo.LINE_CAP_ROUND)
177
# Draw gray graph border
178
cairo_ct.set_source_rgb(0.8828125, 0.8671875, 0.8671875)
179
cairo_ct.rectangle(cell_area.x + BORDER_PADDING,
180
cell_area.y + BORDER_PADDING,
182
cell_area.height - (BORDER_PADDING * 2))
185
# Fill in white box inside graph outline
186
cairo_ct.set_source_rgb(1, 1, 1)
187
cairo_ct.rectangle(cell_area.x + BORDER_PADDING,
188
cell_area.y + BORDER_PADDING,
190
cell_area.height - (BORDER_PADDING * 2))
194
baseline_y = graph_y + graph_height
197
n = (len(self.data_array) - index - 1)
201
val = self.data_array[n]
202
y = baseline_y - (graph_height * val)
205
y = min(graph_y + graph_height, y)
209
for index in range(0, len(self.data_array)):
210
x = int(((index * pixels_per_point) + graph_x))
211
y = int(get_y(index))
213
points.append((x, y))
216
cell_area.x = graph_x
217
cell_area.y = graph_y
218
cell_area.width = graph_width
219
cell_area.height = graph_height
221
# Set color to dark blue for the actual sparkline
222
cairo_ct.set_line_width(2)
223
cairo_ct.set_source_rgb(0.421875, 0.640625, 0.73046875)
224
draw_line(cairo_ct, cell_area, points)
226
# Set color to light blue for the fill
227
cairo_ct.set_source_rgba(0.71484375, 0.84765625, 0.89453125, .5)
228
draw_fill(cairo_ct, cell_area, points)
237
def do_get_size(self, widget, cell_area=None):
238
FIXED_WIDTH = len(self.data_array)
240
xpad = self.get_property("xpad")
241
ypad = self.get_property("ypad")
244
# XXX: What to do here?
251
width = ((xpad * 2) + FIXED_WIDTH)
252
height = ((ypad * 2) + FIXED_HEIGHT)
254
return (xoffset, yoffset, width, height)
256
def _sanitize_param_spec_name(self, name):
257
# Why this is made necessary, I have no idea
258
return name.replace("-", "_")
260
def do_get_property(self, param_spec):
261
name = self._sanitize_param_spec_name(param_spec.name)
262
return getattr(self, name)
264
def do_set_property(self, param_spec, value):
265
name = self._sanitize_param_spec_name(param_spec.name)
266
setattr(self, name, value)
268
class Sparkline(gtk.DrawingArea):
272
# 'name' : (gobject.TYPE_*,
273
# nickname, long desc, (type related args), mode)
274
# Type related args can be min, max for int (etc.), or default value
275
# for strings and bool
276
'data_array' : (gobject.TYPE_PYOBJECT, "Data Array",
277
"Array of data points for the graph",
278
gobject.PARAM_READWRITE),
279
'filled': (gobject.TYPE_BOOLEAN, 'Filled', 'the foo of the object',
281
gobject.PARAM_READWRITE),
282
'num_sets': (gobject.TYPE_INT, "Number of sets",
283
"Number of data sets to graph",
284
1, 2, 1, gobject.PARAM_READWRITE),
285
'reversed': (gobject.TYPE_BOOLEAN, "Reverse data",
286
"Process data from back to front.",
287
0, gobject.PARAM_READWRITE),
288
'rgb': (gobject.TYPE_PYOBJECT, "rgb array", "List of rgb values",
289
gobject.PARAM_READWRITE),
293
gtk.DrawingArea.__init__(self)
295
self._data_array = []
298
self.reversed = False
301
self.connect("expose-event", self.do_expose)
303
def set_data_array(self, val):
304
self._data_array = val
306
def get_data_array(self):
307
return self._data_array
308
data_array = property(get_data_array, set_data_array)
311
def do_expose(self, widget, event):
312
# widget : This widget
314
# cell_area : GdkRectangle: area normally rendered by cell
315
# window : gtk.gdk.Window (not plain window)
317
# cell_area : GdkRectangle: area normally rendered by cell
318
cell_area = widget.allocation
320
# window : gtk.gdk.Window (not plain window)
321
window = widget.window
323
points_per_set = (len(self.data_array) / self.num_sets)
324
pixels_per_point = (float(cell_area.width) /
325
(float((points_per_set - 1) or 1)))
327
# Mid-color graphics context (gtk.GC)
328
# This draws the light gray backing rectangle
329
mid_gc = widget.style.mid_gc[widget.state]
330
window.draw_rectangle(mid_gc, True, 0, 0,
332
cell_area.height - 1)
334
# Foreground-color graphics context
335
# This draws the black border
336
fg_gc = widget.style.fg_gc[widget.state]
337
window.draw_rectangle(fg_gc, False, 0, 0,
339
cell_area.height - 1)
341
# This draws the marker ticks
343
dark_gc = widget.style.dark_gc[widget.state]
344
for index in range(0, max_ticks):
345
window.draw_line(dark_gc, 1,
346
(cell_area.height / max_ticks) * index,
348
(cell_area.height / max_ticks) * index)
350
# Draw the actual sparkline
351
def get_y(dataset, index):
352
baseline_y = cell_area.height
354
n = dataset * points_per_set
356
n += (points_per_set - index - 1)
360
val = self.data_array[n]
361
return baseline_y - ((cell_area.height - 1) * val)
363
cairo_ct = window.cairo_create()
365
cairo_ct.rectangle(0, 0, cell_area.width, cell_area.height)
367
cairo_ct.set_line_width(2)
369
for dataset in range(0, self.num_sets):
370
if len(self.rgb) == (self.num_sets * 3):
371
cairo_ct.set_source_rgb(self.rgb[(dataset * 3)],
372
self.rgb[(dataset * 3) + 1],
373
self.rgb[(dataset * 1) + 2])
375
for index in range(0, points_per_set):
376
x = index * pixels_per_point
377
y = get_y(dataset, index)
379
points.append((int(x), int(y)))
382
if self.num_sets == 1:
385
draw_line(cairo_ct, cell_area, points)
387
draw_fill(cairo_ct, cell_area, points, taper=True)
394
def do_size_request(self, requisition):
395
# Requisition: a GtkRequisition instance
396
width = len(self.data_array) / self.num_sets
399
requisition.width = width
400
requisition.height = height
402
def _sanitize_param_spec_name(self, name):
403
# Why this is made necessary, I have no idea
404
return name.replace("-", "_")
406
def do_get_property(self, param_spec):
407
name = self._sanitize_param_spec_name(param_spec.name)
408
return getattr(self, name)
410
def do_set_property(self, param_spec, value):
411
name = self._sanitize_param_spec_name(param_spec.name)
412
setattr(self, name, value)
414
gobject.type_register(Sparkline)
415
gobject.type_register(CellRendererSparkline)