hamster-applet r822 - in trunk: data hamster tests



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]