hamster-applet r775 - in trunk: hamster tests
- From: tbaugis svn gnome org
- To: svn-commits-list gnome org
- Subject: hamster-applet r775 - in trunk: hamster tests
- Date: Sat, 21 Feb 2009 20:55:40 +0000 (UTC)
Author: tbaugis
Date: Sat Feb 21 20:55:40 2009
New Revision: 775
URL: http://svn.gnome.org/viewvc/hamster-applet?rev=775&view=rev
Log:
instead of having central code to redo the calculations,
trust each value to separate object.
Modified:
trunk/hamster/charting.py
trunk/tests/charting_test.py
Modified: trunk/hamster/charting.py
==============================================================================
--- trunk/hamster/charting.py (original)
+++ trunk/hamster/charting.py Sat Feb 21 20:55:40 2009
@@ -50,6 +50,7 @@
import cairo, pango
import copy
import math
+from sys import maxint
# Tango colors
light = [(252, 233, 79), (252, 175, 62), (233, 185, 110),
@@ -81,26 +82,71 @@
class Integrator(object):
"""an iterator, inspired by "visualizing data" book to simplify animation"""
- def __init__(self, start_value, precision = 0):
+ def __init__(self, start_value, precision = 0, frames = 50):
"""precision determines, until which decimal number we go"""
self.value = start_value
self.target_value = start_value
- self.precision = precision
+ self.frames = float(frames)
+ self.current_frame = 0
+
+ def __repr__(self):
+ return "<Integrator %s, %s>" % (self.value, self.target_value)
def target(self, value):
"""target next value"""
+ self.current_frame = 0
self.target_value = value
def update(self):
"""goes from current to target value
if there is any action needed. returns false when done"""
- self.value += (self.target_value - self.value) / 10.0
+ moving = self.current_frame < self.frames
+ if moving:
+ self.current_frame +=1
+ self.value = self._smoothstep(self.current_frame / self.frames,
+ self.value, self.target_value)
+ return (round(self.value,4) - round(self.target_value,4) != 0) and moving
+
+ def finish(self):
+ self.current_frame = 0.0
+ self.value = self.target_value
+
+ def _smoothstep(self, v, start, end):
+ smooth = 1 - (1 - v)
+ return (end * smooth) + (start * (1-smooth))
- return round(self.value, self.precision) != round(self.target_value,
- self.precision)
+def size_list(set, target_set):
+ """turns set lenghts into target set - trim it, stretches it, but
+ keeps values for cases when lengths match
+ """
+ set = set[:min(len(set), len(target_set))] #shrink to target
+ set += target_set[len(set):] #grow to target
+
+ #nest
+ for i in range(len(set)):
+ if type(set[i]) == list:
+ set[i] = size_list(set[i], target_set[i])
+ return set
+
+def get_limits(set, stack_subfactors = True):
+ # stack_subfactors indicates whether we should sum up nested lists
+ max_value, min_value = -maxint, maxint
+ for col in set:
+ if type(col) in [int, float]:
+ max_value = max(col, max_value)
+ min_value = min(col, min_value)
+ elif stack_subfactors:
+ max_value = max(sum(col), max_value)
+ min_value = min(sum(col), min_value)
+ else:
+ for row in col:
+ max_value = max(row, max_value)
+ min_value = max(row, min_value)
+
+ return min_value, max_value
-
+
class Chart(gtk.DrawingArea):
"""Chart constructor. Optional arguments:
orient_vertical = [True|False] - Chart orientation.
@@ -160,7 +206,7 @@
self.default_grid_stride = 50
self.animation_frames = 50
- self.animation_timeout = 15 #in miliseconds
+ self.animation_timeout = 16 #in miliseconds, targetting 60fps
self.current_frame = self.animation_frames
self.freeze_animation = False
@@ -172,10 +218,9 @@
self.grid_stride = args.get("grid_stride", None)
- self.keys, self.series_keys = None, None
- self.factors = None
- self.row_max = 0
- self.current_max = 0
+ self.current_max = None
+ self.integrators = []
+ self.moving = False
def _expose(self, widget, event):
"""expose is when drawing's going on, like on _invalidate"""
@@ -203,92 +248,47 @@
return False
- def get_row_max(self, values):
- res = None
- for row in values:
- if type(row) in [int, float]:
- res = max(res, row)
- else:
- res = max(res, sum(row))
- return res
-
- def calculate_factors(self, values, max_value):
- factors = []
- max_value = float(max_value) #factors need precision
- if not values: return None
-
- if not max_value:
- if type(values[0]) in [int, float]:
- return [0] * len(values)
- else:
- return [[0] * len(values[0])] * len(values)
-
-
- for row in values:
- if type(row) in [int, float]:
- factors.append(row / max_value)
- else:
- factors.append([col / max_value for col in row])
-
- return factors
-
-
- def plot(self, keys, data, series_keys = None):
+ def plot(self, keys, data, stack_keys = None):
"""Draw chart with given data"""
- self.show()
-
- self.data = data
+ self.keys, self.data, self.stack_keys = keys, data, stack_keys
- self.prev_keys, self.prev_series_keys = copy.copy(self.keys), copy.copy(self.series_keys)
- self.prev_factors = copy.copy(self.factors)
- self.prev_row_max = self.row_max
-
- self.keys, self.series_keys = keys, series_keys
+ self.show()
if not data: #if there is no data, let's just draw blank
self._invalidate()
return
- self.row_max = self.get_row_max(data)
- self.new_factors = self.calculate_factors(data, self.row_max)
-
- #check if maybe this chart is animation enabled and we are in middle of animation
- if self.animate and self.current_frame < self.animation_frames: #something's going on here!
- self.freeze_animation = True #so we don't catch some nasty race condition
-
- #if so, let's start where we are and move to the new set inst
- self.current_frame = 0 #start the animation from beginning
- self.freeze_animation = False
- return
+ #if we are moving, freeze for moment until we recalculate things
+ self.freeze_animation = self.moving
+ min, self.max_value = get_limits(data)
+ if not self.current_max:
+ self.current_max = Integrator(0)
+ else:
+ self.current_max.target(self.max_value)
+
+ self._redo_factors()
+
if self.animate:
- """chart animation gradually moves from current data set
- to the new one. prev_factors will be the previous set, new_factors
- is what we have been asked to plot, and factors itself
- will be the moving thing"""
-
- self.current_frame = 0
-
- #if there is no previous data, set it to zero, so we get a growing animation
- if not self.prev_factors:
- if series_keys:
- #watch out of mutable arrays
- self.factors = self.prev_factors = \
- [[0] * len(series_keys) for x in range(len(keys))]
- else:
- self.factors = self.prev_factors = [0] * len(keys)
-
- self.prev_keys, self.prev_series_keys = self.keys, self.series_keys
-
-
+ #resume or call replot!
+ if self.freeze_animation:
+ self.freeze_animation = False
+ else:
+ gobject.timeout_add(self.animation_timeout, self._replot)
+ else:
+ def finish_all(integrators):
+ for i in range(len(integrators)):
+ if type(integrators[i]) == list:
+ finish_all(integrators[i])
+ else:
+ integrators[i].finish()
+
+ finish_all(self.integrators)
- gobject.timeout_add(self.animation_timeout, self._replot)
- else:
- self.factors = self.new_factors
self._invalidate()
@@ -296,6 +296,30 @@
def _smoothstep(self, v, start, end):
smooth = 1 - (1 - v) * (1 - v)
return (end * smooth) + (start * (1-smooth))
+
+ def _redo_factors(self):
+ # calculates new factors and then updates existing set
+ max_value = float(self.max_value) or 1 # avoid division by zero
+
+ self.integrators = size_list(self.integrators, self.data)
+
+ #need function to go recursive
+ def retarget(integrators, new_values):
+ for i in range(len(new_values)):
+ if type(new_values[i]) == list:
+ integrators[i] = retarget(integrators[i], new_values[i])
+ else:
+ if isinstance(integrators[i], Integrator) == False:
+ integrators[i] = Integrator(0, 1) #8 numbers after comma :)
+
+ integrators[i].target(new_values[i] / max_value)
+
+ return integrators
+
+ retarget(self.integrators, self.data)
+
+
+
def _replot(self):
"""Internal function to do the math, going from previous set to the
@@ -303,67 +327,29 @@
if self.freeze_animation:
return True #just wait until they release us!
-
- if self.current_frame == self.animation_frames:
- return False;
-
-
#this can get called before expose
if not self.window:
self._invalidate()
return False
#ok, now we are good!
- self.current_frame = self.current_frame + 1
-
+ self.current_max.update()
- frame = self.current_frame / float(self.animation_frames)
- self.current_max = self._smoothstep(frame, self.prev_row_max, self.row_max)
-
- # do some sanity checks before thinking about animation
- # are the source and target of same length?
- similar_keys = False
- for i in range(min(len(self.keys), len(self.prev_keys))):
- if self.keys[i] == self.prev_keys[i]:
- similar_keys = True
- break
-
- if not similar_keys:
- self.factors = self.new_factors
- self._invalidate()
- return True
-
- keys_len = len(self.keys)
- prev_keys_len = len(self.prev_keys)
-
- if self.series_keys:
- ser_keys_len = len(self.series_keys)
- prev_ser_keys_len = len(self.prev_series_keys)
-
- for i in range(len(self.keys)):
- if i < keys_len and i < prev_keys_len \
- and self.keys[i] == self.prev_keys[i]:
-
- if self.series_keys:
- for j in range(len(self.series_keys)):
- if j < ser_keys_len and j < prev_ser_keys_len \
- and self.series_keys[j] == self.prev_series_keys[j]:
- self.factors[i][j] = self._smoothstep(frame, self.prev_factors[i][j], self.new_factors[i][j])
- elif j>= len(self.factors[i]):
- self.factors.append(self.new_factors[i][j])
- else:
- self.factors[i][j] = self.new_factors[i][j]
+ def update_all(integrators):
+ still_moving = False
+ for z in range(len(integrators)):
+ if type(integrators[z]) == list:
+ still_moving = update_all(integrators[z]) or still_moving
else:
- self.factors[i] = self._smoothstep(frame, self.prev_factors[i], self.new_factors[i])
- elif i >= len(self.factors):
- self.factors.append(self.new_factors[i])
- else:
- self.factors[i] = self.new_factors[i]
+ still_moving = integrators[z].update() or still_moving
+ return still_moving
- self._invalidate()
+ self.moving = update_all(self.integrators)
- return self.current_frame < self.animation_frames #return if there is still work to do
+ self._invalidate()
+
+ return self.moving #return if there is still work to do
def _invalidate(self):
"""Force redrawal of chart"""
@@ -446,8 +432,9 @@
gap = self.bar_width * 0.05
bar_x = graph_x + (self.bar_width * i) + gap
- for j in range(len(self.factors[i])):
- factor = self.factors[i][j]
+ for j in range(len(self.integrators[i])):
+ factor = self.integrators[i][j].value
+
if factor > 0:
bar_size = max_bar_size * factor
@@ -472,16 +459,16 @@
self.layout.set_width(-1)
#white grid and scale values
- if self.grid_stride and self.row_max:
+ if self.grid_stride and self.max_value:
# if grid stride is less than 1 then we consider it to be percentage
if self.grid_stride < 1:
- grid_stride = int(self.row_max * self.grid_stride)
+ grid_stride = int(self.max_value * self.grid_stride)
else:
grid_stride = int(self.grid_stride)
context.set_line_width(1)
- for i in range(grid_stride, int(self.row_max), grid_stride):
- y = - max_bar_size * (i / self.row_max)
+ for i in range(grid_stride, int(self.max_value), grid_stride):
+ y = - max_bar_size * (i / self.max_value)
label = str(i)
self.layout.set_text(label)
@@ -512,9 +499,9 @@
# if labels are at end, then we need show them for the last bar!
if self.labels_at_end:
- factors = self.factors[0]
+ factors = self.integrators[0]
else:
- factors = self.factors[-1]
+ factors = self.integrators[-1]
self.layout.set_ellipsize(pango.ELLIPSIZE_END)
self.layout.set_width(legend_width * 1000)
@@ -524,11 +511,11 @@
self.layout.set_alignment(pango.ALIGN_RIGHT)
for j in range(len(factors)):
- factor = factors[j]
+ factor = factors[j].value
bar_size = factor * max_bar_size
if round(bar_size) > 0:
- label = "%s" % self.series_keys[j]
+ label = "%s" % self.stack_keys[j]
self.layout.set_text(label)
@@ -577,9 +564,9 @@
if self.show_scale:
legend_width = max(self.legend_width, 20)
- if self.series_keys and self.labels_at_end:
+ if self.stack_keys and self.labels_at_end:
graph_x = 0
- graph_width = self.width - max(legend_width, self._longest_label(self.series_keys))
+ graph_width = self.width - max(legend_width, self._longest_label(self.stack_keys))
else:
graph_x = legend_width + 8 # give some space to scale labels
graph_width = self.width - graph_x - 10
@@ -621,7 +608,7 @@
# maximal
if self.show_total:
- max_label = "%d" % self.row_max
+ max_label = "%d" % self.max_value
self.layout.set_text(max_label)
label_w, label_h = self.layout.get_pixel_size()
@@ -661,9 +648,9 @@
"""
# stripes for the case i decided that they are not annoying
- for i in range(0, round(self.current_max), 10):
- x = graph_x + (graph_width * (i / float(self.current_max)))
- w = (graph_width * (5 / float(self.current_max)))
+ for i in range(0, round(self.current_max.value), 10):
+ x = graph_x + (graph_width * (i / float(self.current_max.value)))
+ w = (graph_width * (5 / float(self.current_max.value)))
context.set_source_rgb(0.90, 0.90, 0.90)
context.rectangle(x + w, graph_y, w, graph_height)
@@ -725,8 +712,8 @@
bar_y = graph_y + (bar_width * i) + gap
- for j in range(len(self.factors[i])):
- factor = self.factors[i][j]
+ for j in range(len(self.integrators[i])):
+ factor = self.integrators[i][j].value
if factor > 0:
bar_size = max_bar_size * factor
bar_height = bar_width - (gap * 2)
@@ -750,7 +737,7 @@
if self.values_on_bars:
for i in range(rowcount):
label = self.value_format % sum(self.data[i])
- factor = sum(self.factors[i])
+ factor = sum([integrator.value for integrator in self.integrators[i]])
self.layout.set_text(label)
label_w, label_h = self.layout.get_pixel_size()
@@ -768,7 +755,7 @@
else:
# show max value
context.move_to(graph_x + graph_width - 30, graph_y + 10)
- max_label = self.value_format % self.current_max
+ max_label = self.value_format % self.current_max.value
self.layout.set_text(max_label)
context.show_layout(self.layout)
Modified: trunk/tests/charting_test.py
==============================================================================
--- trunk/tests/charting_test.py (original)
+++ trunk/tests/charting_test.py Sat Feb 21 20:55:40 2009
@@ -10,24 +10,41 @@
integrator = charting.Integrator(0, 0)
integrator.target(10)
integrator.update()
- assert integrator.value == 1
-
+ assert 0 < integrator.value < 10, "not going up as expected %f" \
+ % integrator.value
def test_target_lesser(self):
integrator = charting.Integrator(0, 0)
integrator.target(-10)
integrator.update()
- assert integrator.value == -1
-
+ assert -10 < integrator.value < 0, "not going down as expected %f" \
+ % integrator.value
def test_reaches_target(self):
integrator = charting.Integrator(0, 0)
integrator.target(10)
while integrator.update():
- print integrator.value
-
- print round(integrator.value, 0)
+ pass
assert round(integrator.value, 0) == 10
-
+
+
+class TestSizeListFunctions(unittest.TestCase):
+ def test_values_stay(self):
+ list_a = [1, [2, 3, 4], 5]
+ list_b = [6, [7, 8]]
+ res = charting.size_list(list_a, list_b)
+ assert res == [1, [2, 3]], "on shrinkage, values are kept, %s" % res
+
+ def test_grow(self):
+ list_a = [1, [2, 3], 4]
+ list_b = [5, [6, 7, 8], 9, 10]
+ res = charting.size_list(list_a, list_b)
+ assert res == [1, [2, 3, 8], 4, 10], "source table expands, %s" % res
+
+class TestGetLimits(unittest.TestCase):
+ def test_simple(self):
+ min_v, max_v = charting.get_limits([4, 7, 2, 4, 6, 12, 3, 1, 9])
+ assert min_v == 1, "wrong minimal: %d" % min_v
+ assert max_v == 12, "wrong maximal: %d" % max_v
if __name__ == '__main__':
unittest.main()
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]