hamster-applet r822 - in trunk: data hamster tests
- From: tbaugis svn gnome org
- To: svn-commits-list gnome org
- Subject: hamster-applet r822 - in trunk: data hamster tests
- Date: Sat, 28 Feb 2009 22:44:34 +0000 (UTC)
Author: tbaugis
Date: Sat Feb 28 22:44:34 2009
New Revision: 822
URL: http://svn.gnome.org/viewvc/hamster-applet?rev=822&view=rev
Log:
moving drawing out to "graphics" to reduce code redundancy
Added:
trunk/hamster/graphics.py
Modified:
trunk/data/edit_activity.glade
trunk/hamster/Makefile.am
trunk/hamster/charting.py
trunk/hamster/edit_activity.py
trunk/tests/charting_test.py
Modified: trunk/data/edit_activity.glade
==============================================================================
--- trunk/data/edit_activity.glade (original)
+++ trunk/data/edit_activity.glade Sat Feb 28 22:44:34 2009
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
-<!--Generated with glade3 3.4.5 on Thu Feb 26 09:07:19 2009 -->
+<!--Generated with glade3 3.4.5 on Sat Feb 28 22:37:09 2009 -->
<glade-interface>
<widget class="GtkWindow" id="custom_fact_window">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
@@ -22,75 +22,59 @@
<property name="column_spacing">4</property>
<property name="row_spacing">8</property>
<child>
- <widget class="GtkScrolledWindow" id="scrolledwindow2">
+ <widget class="GtkEventBox" id="day_preview">
+ <property name="height_request">56</property>
<property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
- <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
- <property name="shadow_type">GTK_SHADOW_IN</property>
<child>
- <widget class="GtkTextView" id="description">
- <property name="height_request">50</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="wrap_mode">GTK_WRAP_WORD_CHAR</property>
- </widget>
+ <placeholder/>
</child>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
- <property name="top_attach">2</property>
- <property name="bottom_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
</packing>
</child>
<child>
- <widget class="GtkAlignment" id="alignment5">
+ <widget class="GtkAlignment" id="alignment6">
<property name="visible">True</property>
<property name="xalign">1</property>
+ <property name="yalign">0</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<child>
- <widget class="GtkLabel" id="label1">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Activity:</property>
- </widget>
- </child>
- </widget>
- </child>
- <child>
- <widget class="GtkAlignment" id="alignment4">
- <property name="visible">True</property>
- <property name="xalign">1</property>
- <property name="xscale">0</property>
- <child>
- <widget class="GtkLabel" id="label2">
+ <widget class="GtkLabel" id="label4">
<property name="visible">True</property>
- <property name="label" translatable="yes">Time:</property>
+ <property name="label" translatable="yes">Preview:</property>
</widget>
</child>
</widget>
<packing>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
</packing>
</child>
<child>
- <widget class="GtkAlignment" id="alignment3">
+ <widget class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
- <property name="xalign">1</property>
- <property name="yalign">0</property>
- <property name="yscale">0</property>
+ <property name="xalign">0</property>
<child>
- <widget class="GtkLabel" id="Description:">
+ <widget class="GtkComboBoxEntry" id="activity_combo">
<property name="visible">True</property>
- <property name="label" translatable="yes">Description:</property>
+ <signal name="changed" handler="on_activity_combo_changed"/>
+ <child internal-child="entry">
+ <widget class="GtkEntry" id="activity_text">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </widget>
+ </child>
</widget>
</child>
</widget>
<packing>
- <property name="top_attach">2</property>
- <property name="bottom_attach">3</property>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
</packing>
</child>
<child>
@@ -199,59 +183,75 @@
</packing>
</child>
<child>
- <widget class="GtkAlignment" id="alignment2">
+ <widget class="GtkAlignment" id="alignment3">
<property name="visible">True</property>
- <property name="xalign">0</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0</property>
+ <property name="yscale">0</property>
<child>
- <widget class="GtkComboBoxEntry" id="activity_combo">
+ <widget class="GtkLabel" id="Description:">
<property name="visible">True</property>
- <signal name="changed" handler="on_activity_combo_changed"/>
- <child internal-child="entry">
- <widget class="GtkEntry" id="activity_text">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- </widget>
- </child>
+ <property name="label" translatable="yes">Description:</property>
</widget>
</child>
</widget>
<packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
</packing>
</child>
<child>
- <widget class="GtkAlignment" id="alignment6">
+ <widget class="GtkAlignment" id="alignment4">
<property name="visible">True</property>
<property name="xalign">1</property>
- <property name="yalign">0</property>
<property name="xscale">0</property>
- <property name="yscale">0</property>
<child>
- <widget class="GtkLabel" id="label4">
+ <widget class="GtkLabel" id="label2">
<property name="visible">True</property>
- <property name="label" translatable="yes">Preview:</property>
+ <property name="label" translatable="yes">Time:</property>
</widget>
</child>
</widget>
<packing>
- <property name="top_attach">3</property>
- <property name="bottom_attach">4</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
</packing>
</child>
<child>
- <widget class="GtkEventBox" id="day_preview">
- <property name="height_request">40</property>
+ <widget class="GtkAlignment" id="alignment5">
<property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
<child>
- <placeholder/>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Activity:</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <child>
+ <widget class="GtkTextView" id="description">
+ <property name="height_request">50</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="wrap_mode">GTK_WRAP_WORD_CHAR</property>
+ </widget>
</child>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
- <property name="top_attach">3</property>
- <property name="bottom_attach">4</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
</packing>
</child>
</widget>
Modified: trunk/hamster/Makefile.am
==============================================================================
--- trunk/hamster/Makefile.am (original)
+++ trunk/hamster/Makefile.am Sat Feb 28 22:44:34 2009
@@ -20,6 +20,7 @@
stats.py \
reports.py \
charting.py \
+ graphics.py \
Configuration.py \
KeyBinder.py \
preferences.py \
Modified: trunk/hamster/charting.py
==============================================================================
--- trunk/hamster/charting.py (original)
+++ trunk/hamster/charting.py Sat Feb 28 22:44:34 2009
@@ -53,6 +53,7 @@
from sys import maxint
import datetime as dt
import time
+from hamster import graphics
# Tango colors
light = [(252, 233, 79), (252, 175, 62), (233, 185, 110),
@@ -81,68 +82,6 @@
r,g,b = color.red / 65536.0, color.green / 65536.0, color.blue / 65536.0
context.set_source_rgb(r, g, b)
-class Integrator(object):
- """an iterator, inspired by "visualizing data" book to simplify animation"""
- def __init__(self, start_value, damping = 0.5, attraction = 0.2):
- #if we got datetime, convert it to unix time, so we operate with numbers again
- self.current_value = start_value
- if type(start_value) == dt.datetime:
- self.current_value = int(time.mktime(start_value.timetuple()))
-
- self.value_type = type(start_value)
-
- self.target_value = start_value
- self.current_frame = 0
-
- self.targeting = False
- self.vel, self.accel, self.force = 0, 0, 0
- self.mass = 1
- self.damping = damping
- self.attraction = attraction
-
-
-
- def __repr__(self):
- current, target = self.current_value, self.target_value
- if self.value_type == dt.datetime:
- current = dt.datetime.fromtimestamp(current)
- target = dt.datetime.fromtimestamp(target)
- return "<Integrator %s, %s>" % (current, target)
-
- def target(self, value):
- """target next value"""
- self.targeting = True
- self.target_value = value
- if type(value) == dt.datetime:
- self.target_value = int(time.mktime(value.timetuple()))
-
- def update(self):
- """goes from current to target value
- if there is any action needed. returns velocity, which is synonym from
- delta. Use it to determine when animation is done (experiment to find
- value that fits you!"""
-
- if self.targeting:
- self.force += self.attraction * (self.target_value - self.current_value)
-
- self.accel = self.force / self.mass
- self.vel = (self.vel + self.accel) * self.damping
- self.current_value += self.vel
- self.force = 0
- return abs(self.vel)
-
- def finish(self):
- self.current_value = self.target_value
-
- @property
- def value(self):
- if self.value_type == dt.datetime:
- return dt.datetime.fromtimestamp(self.current_value)
- else:
- return self.current_value
-
-
-
def size_list(set, target_set):
"""turns set lenghts into target set - trim it, stretches it, but
keeps values for cases when lengths match
@@ -174,7 +113,7 @@
return min_value, max_value
-class Chart(gtk.DrawingArea):
+class Chart(graphics.Area):
"""Chart constructor. Optional arguments:
self.max_bar_width = pixels. Maximal width of bar. If not specified,
bars will stretch to fill whole area
@@ -205,9 +144,7 @@
show them at right end of graph.
"""
def __init__(self, **args):
- gtk.DrawingArea.__init__(self)
- self.context = None
- self.layout = None
+ graphics.Area.__init__(self)
self.connect("expose_event", self._expose)
self.max_bar_width = args.get("max_bar_width", 0)
@@ -235,6 +172,26 @@
self.moving = False
+ def draw_bar(self, x, y, w, h, color = None):
+ """ draws a simple bar"""
+ context = self.context
+
+ base_color = color or self.bar_base_color or (220, 220, 220)
+
+ if self.bars_beveled:
+ self.fill_area(x, y, w, h,
+ [b - 30 for b in base_color])
+
+ if w > 2 and h > 2:
+ self.fill_area(x + 1, y + 1, w - 2, h - 2,
+ [b + 20 for b in base_color])
+
+ if w > 3 and h > 3:
+ self.fill_area(x + 2, y + 2, w - 4, h - 4, base_color)
+ else:
+ self.fill_area(x, y, w, h, base_color)
+
+
def plot(self, keys, data, stack_keys = None):
"""Draw chart with given data"""
self.keys, self.data, self.stack_keys = keys, data, stack_keys
@@ -242,14 +199,14 @@
self.show()
if not data: #if there is no data, let's just draw blank
- self._invalidate()
+ self.redraw_canvas()
return
min, self.max_value = get_limits(data)
if not self.current_max:
- self.current_max = Integrator(0)
+ self.current_max = graphics.Integrator(0)
self.current_max.target(self.max_value)
self._update_targets()
@@ -268,7 +225,7 @@
finish_all(self.integrators)
- self._invalidate()
+ self.redraw_canvas()
def _interpolate(self):
@@ -276,7 +233,7 @@
new one, and redraw graph"""
#this can get called before expose
if not self.window:
- self._invalidate()
+ self.redraw_canvas()
return False
#ok, now we are good!
@@ -294,38 +251,12 @@
self.moving = update_all(self.integrators)
- self._invalidate()
+ self.redraw_canvas()
return self.moving #return if there is still work to do
-
- def _invalidate(self):
- """Force graph redraw"""
- if self.window: #this can get called before expose
- alloc = self.get_allocation()
- rect = gtk.gdk.Rectangle(alloc.x, alloc.y, alloc.width, alloc.height)
- self.window.invalidate_rect(rect, True)
- self.window.process_updates(True)
-
-
def _expose(self, widget, event):
- """expose is when drawing's going on, like on _invalidate"""
- self.context = widget.window.cairo_create()
- self.context.set_antialias(cairo.ANTIALIAS_NONE)
-
- self.context.rectangle(event.area.x, event.area.y,
- event.area.width, event.area.height)
- self.context.clip()
-
- self.layout = self.context.create_layout()
-
- default_font = pango.FontDescription(gtk.Style().font_desc.to_string())
- default_font.set_size(8 * pango.SCALE)
- self.layout.set_font_description(default_font)
-
- alloc = self.get_allocation() #x, y, width, height
- self.width, self.height = alloc[2], alloc[3]
-
+ """expose is when drawing's going on, like on invalidate"""
# fill whole area
if self.background:
self.context.rectangle(0, 0, self.width, self.height)
@@ -351,8 +282,8 @@
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)
+ if isinstance(integrators[i], graphics.Integrator) == False:
+ integrators[i] = graphics.Integrator(0)
integrators[i].target(new_values[i] / max_value)
@@ -360,40 +291,6 @@
retarget(self.integrators, self.data)
- def _fill_area(self, x, y, w, h, color):
- self.context.rectangle(x, y, w, h)
- self.context.set_source_rgb(*[c / 256.0 for c in color])
- self.context.fill()
-
- def _draw_bar(self, x, y, w, h, color = None):
- """ draws a nice bar"""
- context = self.context
-
- base_color = color or self.bar_base_color or (220, 220, 220)
-
- if self.bars_beveled:
- self._fill_area(x, y, w, h,
- [b - 30 for b in base_color])
-
- if w > 2 and h > 2:
- self._fill_area(x + 1, y + 1, w - 2, h - 2,
- [b + 20 for b in base_color])
-
- if w > 3 and h > 3:
- self._fill_area(x + 2, y + 2, w - 4, h - 4, base_color)
- else:
- self._fill_area(x, y, w, h, base_color)
-
-
- def _longest_label(self, labels):
- max_extent = 0
- for label in labels:
- self.layout.set_text(label)
- label_w, label_h = self.layout.get_pixel_size()
- max_extent = max(label_w + 5, max_extent)
-
- return max_extent
-
def draw(self):
print "OMG OMG, not implemented!!!"
@@ -403,7 +300,7 @@
# graph box dimensions
if self.show_stack_labels:
- legend_width = self.legend_width or self._longest_label(self.keys)
+ legend_width = self.legend_width or self.longest_label(self.keys)
elif self.show_scale:
if self.grid_stride < 1:
grid_stride = int(self.max_value * self.grid_stride)
@@ -412,7 +309,7 @@
scale_labels = [self.value_format % i
for i in range(grid_stride, int(self.max_value), grid_stride)]
- legend_width = self.legend_width or self._longest_label(scale_labels)
+ legend_width = self.legend_width or self.longest_label(scale_labels)
else:
legend_width = self.legend_width
@@ -434,7 +331,7 @@
self.context.set_line_width(1)
if self.chart_background:
- self._fill_area(graph_x - 1, graph_y, graph_width, graph_height,
+ self.fill_area(graph_x - 1, graph_y, graph_width, graph_height,
self.chart_background)
@@ -493,11 +390,11 @@
bar_size = max_bar_size * factor
bar_start += bar_size
- self._draw_bar(bar_x,
- graph_height - bar_start,
- self.bar_width - (gap * 2),
- bar_size,
- [col - (j * 22) for col in base_color])
+ self.draw_bar(bar_x,
+ graph_height - bar_start,
+ self.bar_width - (gap * 2),
+ bar_size,
+ [col - (j * 22) for col in base_color])
#flip the matrix back, so text doesn't come upside down
@@ -599,7 +496,7 @@
rowcount, keys = len(self.keys), self.keys
#push graph to the right, so it doesn't overlap, and add little padding aswell
- legend_width = self.legend_width or self._longest_label(keys)
+ legend_width = self.legend_width or self.longest_label(keys)
graph_x = legend_width
graph_x += 8 #add another 8 pixes of padding
@@ -609,7 +506,7 @@
if self.chart_background:
- self._fill_area(graph_x, graph_y, graph_width, graph_height, self.chart_background)
+ self.fill_area(graph_x, graph_y, graph_width, graph_height, self.chart_background)
"""
# stripes for the case i decided that they are not annoying
@@ -685,11 +582,11 @@
bar_size = max_bar_size * factor
bar_height = bar_width - (gap * 2)
- self._draw_bar(graph_x + bar_start,
- bar_y,
- bar_size,
- bar_height,
- [col - (j * 22) for col in base_color])
+ self.draw_bar(graph_x + bar_start,
+ bar_y,
+ bar_size,
+ bar_height,
+ [col - (j * 22) for col in base_color])
bar_start += bar_size
Modified: trunk/hamster/edit_activity.py
==============================================================================
--- trunk/hamster/edit_activity.py (original)
+++ trunk/hamster/edit_activity.py Sat Feb 28 22:44:34 2009
@@ -27,7 +27,7 @@
import re
from hamster import dispatcher, storage, SHARED_DATA_DIR, stuff
-from hamster import charting
+from hamster import graphics
import hamster.eds
import time
@@ -43,12 +43,10 @@
""" TODO:
* hook into notifications and refresh our days if some evil neighbour edit
fact window has dared to edit facts
- * sort out animation (move stuff from charting.py and this place into
- some hamster.Area!
"""
-class Dayline(gtk.DrawingArea):
- def __init__(self, **args):
- gtk.DrawingArea.__init__(self)
+class Dayline(graphics.Area):
+ def __init__(self):
+ graphics.Area.__init__(self)
self.context = None
self.layout = None
self.connect("expose_event", self._expose)
@@ -116,7 +114,7 @@
self.days.append(date_plus)
- self._invalidate()
+ self.redraw_canvas()
if moving:
return True
else:
@@ -185,7 +183,7 @@
if end - start > 1:
self.highlight_start = start
self.highlight_end = end
- self._invalidate()
+ self.redraw_canvas()
if self.move_type == "scale_drag":
self.range_start.target(self.drag_start_time + dt.timedelta(minutes = ((self.drag_start - x) / self.minute_pixel)))
@@ -211,45 +209,17 @@
start_time = highlight[0] - dt.timedelta(minutes = highlight[0].minute) - dt.timedelta(hours = 10)
- self.range_start = charting.Integrator(start_time, damping = 0.5, attraction = 0.7)
+ self.range_start = graphics.Integrator(start_time, damping = 0.5, attraction = 0.7)
self.highlight = highlight
self.show()
- self._invalidate()
-
-
-
- def _invalidate(self):
- """Force graph redraw"""
- if self.window: #this can get called before expose
- alloc = self.get_allocation()
- rect = gtk.gdk.Rectangle(alloc.x, alloc.y, alloc.width, alloc.height)
- self.window.invalidate_rect(rect, True)
- self.window.process_updates(True)
+ self.redraw_canvas()
def _expose(self, widget, event):
"""expose is when drawing's going on, like on _invalidate"""
- self.context = widget.window.cairo_create()
- self.context.set_antialias(cairo.ANTIALIAS_NONE)
-
- self.context.rectangle(event.area.x, event.area.y,
- event.area.width, event.area.height)
- self.context.clip()
-
- self.layout = self.context.create_layout()
- font = pango.FontDescription(gtk.Style().font_desc.to_string())
- font.set_size(8 * pango.SCALE)
- self.layout.set_font_description(font)
-
-
- alloc = self.get_allocation() #x, y, width, height
- self.width = alloc.width
-
-
- self.height = alloc.height
self._draw(self.context)
return False
@@ -273,14 +243,13 @@
self.graph_x = -minute_pixel * 4 * 60
- graph_y = 1
- graph_height = self.height - 15
+ graph_y = 4
+ graph_height = self.height - 25
+ graph_y2 = graph_y + graph_height
# graph area
- context.set_source_rgb(1, 1, 1)
- context.rectangle(0, graph_y - 1, self.width, graph_height)
- context.fill()
+ self.fill_area(0, graph_y - 1, self.width, graph_height, (1,1,1))
context.set_source_rgb(0.7, 0.7, 0.7)
context.rectangle(0, graph_y-1, self.width - 1, graph_height)
context.stroke()
@@ -294,8 +263,8 @@
if label_time.minute == 0 and label_time.hour % 2 == 0:
if label_time.hour == 0:
context.set_source_rgb(0.8, 0.8, 0.8)
- context.move_to(self.graph_x + minute_pixel * i, 0)
- context.line_to(self.graph_x + minute_pixel * i, graph_height)
+ context.move_to(self.graph_x + minute_pixel * i, graph_y)
+ context.line_to(self.graph_x + minute_pixel * i, graph_y2)
label_minutes = label_time.strftime("%b %d.")
else:
label_minutes = label_time.strftime("%H:%M")
@@ -305,7 +274,7 @@
label_w, label_h = self.layout.get_pixel_size()
context.move_to(self.graph_x + minute_pixel * i - label_w/2,
- graph_height + 2)
+ graph_y2 + 6)
context.show_layout(self.layout)
context.stroke()
@@ -340,8 +309,8 @@
rgb = colorsys.hls_to_rgb(.6, .7, .5)
context.set_source_rgba(rgb[0], rgb[1], rgb[2], 0.5)
- context.rectangle(self.highlight_start, graph_y-1,
- self.highlight_end - self.highlight_start, graph_height)
+ context.rectangle(self.highlight_start, graph_y-3,
+ self.highlight_end - self.highlight_start, graph_height + 4)
context.fill_preserve()
context.set_source_rgb(*rgb)
context.stroke()
Added: trunk/hamster/graphics.py
==============================================================================
--- (empty file)
+++ trunk/hamster/graphics.py Sat Feb 28 22:44:34 2009
@@ -0,0 +1,116 @@
+import time, datetime as dt
+import gtk
+
+import pango, cairo
+
+class Area(gtk.DrawingArea):
+ """Abstraction on top of DrawingArea to work specifically with cairo"""
+ def __init__(self):
+ gtk.DrawingArea.__init__(self)
+ self.context = None
+ self.layout = None
+ self.connect("expose_event", self._on_expose)
+ self.width = None
+ self.height = None
+
+ def redraw_canvas(self):
+ """Force graph redraw"""
+ if self.window: #this can get called before expose
+ alloc = self.get_allocation()
+ self.queue_draw_area(alloc.x, alloc.y, alloc.width, alloc.height)
+ self.window.process_updates(True)
+
+
+ def _on_expose(self, widget, event):
+ """expose is when drawing's going on, like on _invalidate"""
+ self.context = widget.window.cairo_create()
+ self.context.set_antialias(cairo.ANTIALIAS_NONE)
+ self.context.rectangle(event.area.x, event.area.y,
+ event.area.width, event.area.height)
+ self.context.clip()
+
+ self.layout = self.context.create_layout()
+ default_font = pango.FontDescription(gtk.Style().font_desc.to_string())
+ default_font.set_size(8 * pango.SCALE)
+ self.layout.set_font_description(default_font)
+
+
+ alloc = self.get_allocation() #x, y, width, height
+ self.width, self.height = alloc.width, alloc.height
+
+ def fill_area(self, x, y, w, h, color):
+ if color[0] > 1: color = [c / 256.0 for c in color]
+
+ self.context.set_source_rgb(*color)
+ self.context.rectangle(x, y, w, h)
+ self.context.fill()
+
+ def longest_label(self, labels):
+ """returns width of the longest label"""
+ max_extent = 0
+ for label in labels:
+ self.layout.set_text(label)
+ label_w, label_h = self.layout.get_pixel_size()
+ max_extent = max(label_w + 5, max_extent)
+
+ return max_extent
+
+
+class Integrator(object):
+ """an iterator, inspired by "visualizing data" book to simplify animation"""
+ def __init__(self, start_value, damping = 0.5, attraction = 0.2):
+ #if we got datetime, convert it to unix time, so we operate with numbers again
+ self.current_value = start_value
+ if type(start_value) == dt.datetime:
+ self.current_value = int(time.mktime(start_value.timetuple()))
+
+ self.value_type = type(start_value)
+
+ self.target_value = start_value
+ self.current_frame = 0
+
+ self.targeting = False
+ self.vel, self.accel, self.force = 0, 0, 0
+ self.mass = 1
+ self.damping = damping
+ self.attraction = attraction
+
+ def __repr__(self):
+ current, target = self.current_value, self.target_value
+ if self.value_type == dt.datetime:
+ current = dt.datetime.fromtimestamp(current)
+ target = dt.datetime.fromtimestamp(target)
+ return "<Integrator %s, %s>" % (current, target)
+
+ def target(self, value):
+ """target next value"""
+ self.targeting = True
+ self.target_value = value
+ if type(value) == dt.datetime:
+ self.target_value = int(time.mktime(value.timetuple()))
+
+ def update(self):
+ """goes from current to target value
+ if there is any action needed. returns velocity, which is synonym from
+ delta. Use it to determine when animation is done (experiment to find
+ value that fits you!"""
+
+ if self.targeting:
+ self.force += self.attraction * (self.target_value - self.current_value)
+
+ self.accel = self.force / self.mass
+ self.vel = (self.vel + self.accel) * self.damping
+ self.current_value += self.vel
+ self.force = 0
+ return abs(self.vel)
+
+ def finish(self):
+ self.current_value = self.target_value
+
+ @property
+ def value(self):
+ if self.value_type == dt.datetime:
+ return dt.datetime.fromtimestamp(self.current_value)
+ else:
+ return self.current_value
+
\ No newline at end of file
Modified: trunk/tests/charting_test.py
==============================================================================
--- trunk/tests/charting_test.py (original)
+++ trunk/tests/charting_test.py Sat Feb 28 22:44:34 2009
@@ -3,23 +3,23 @@
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
import unittest
-from hamster import charting
+from hamster import graphics
class TestIteratorFunctions(unittest.TestCase):
def test_target_bigger(self):
- integrator = charting.Integrator(0, 0)
+ integrator = graphics.Integrator(0, 0)
integrator.target(10)
integrator.update()
assert 0 < integrator.value < 10, "not going up as expected %f" \
% integrator.value
def test_target_lesser(self):
- integrator = charting.Integrator(0, 0)
+ integrator = graphics.Integrator(0, 0)
integrator.target(-10)
integrator.update()
assert -10 < integrator.value < 0, "not going down as expected %f" \
% integrator.value
def test_reaches_target(self):
- integrator = charting.Integrator(0, 0)
+ integrator = graphics.Integrator(0, 0)
integrator.target(10)
while integrator.update():
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]