[hamster-applet] moved the specialized functions out of graphics library to make it simpler to read



commit acdc6363bab1cef17764f04532e63857bdf66bb8
Author: Toms Bauģis <toms baugis gmail com>
Date:   Fri Nov 27 11:03:16 2009 +0000

    moved the specialized functions out of graphics library to make it simpler to read

 hamster/charting.py         |   20 ++-
 hamster/graphics.py         |  293 +++++++++++--------------------------------
 hamster/widgets/dayline.py  |  151 ++++++++++++++++++++++-
 hamster/widgets/timeline.py |   90 +++++++++++++-
 4 files changed, 327 insertions(+), 227 deletions(-)
---
diff --git a/hamster/charting.py b/hamster/charting.py
index c7557e4..8b95636 100644
--- a/hamster/charting.py
+++ b/hamster/charting.py
@@ -241,6 +241,17 @@ class Chart(graphics.Area):
             return bars
     
         retarget(self.bars, self.data)
+
+
+    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
     
     def draw(self):
         logging.error("OMG OMG, not implemented!!!")
@@ -287,9 +298,6 @@ class BarChart(Chart):
                                                              self.max_bar_width)
         gap = bar_width * 0.05
         
-        # flip hamster.graphics matrix so we don't think upside down
-        self.set_value_range(y_max = 0, y_min = self.graph_height)
-
         # bars and keys
         max_bar_size = self.graph_height
         #make sure bars don't hit the ceiling
@@ -308,7 +316,7 @@ class BarChart(Chart):
             intended_x = (bar_width * i) + (bar_width - label_w) / 2.0
             
             if not prev_label_end or intended_x > prev_label_end:
-                self.move_to(intended_x, -4)
+                self.context.move_to(intended_x, self.graph_height - 4)
                 context.show_layout(self.layout)
             
                 prev_label_end = intended_x + label_w + 3
@@ -363,7 +371,7 @@ class BarChart(Chart):
                 context.move_to(self.graph_x + (bar_width * i) + (bar_width - label_w) / 2.0, label_y)
 
                 # we are in the bar so make sure that the font color is distinguishable
-                if colorsys.rgb_to_hls(*self.rgb(last_color))[1] < 150:
+                if colorsys.rgb_to_hls(*graphics.Colors.rgb(last_color))[1] < 150:
                     self.set_color(graphics.Colors.almost_white)
                 else:
                     self.set_color(graphics.Colors.aluminium[5])        
@@ -580,7 +588,7 @@ class HorizontalBarChart(Chart):
                 self.set_color(graphics.Colors.aluminium[5])        
             else:
                 # we are in the bar so make sure that the font color is distinguishable
-                if colorsys.rgb_to_hls(*self.rgb(last_color))[1] < 150:
+                if colorsys.rgb_to_hls(*graphics.Colors.rgb(last_color))[1] < 150:
                     self.set_color(graphics.Colors.almost_white)
                 else:
                     self.set_color(graphics.Colors.aluminium[5])        
diff --git a/hamster/graphics.py b/hamster/graphics.py
index 226cf1f..15107ce 100644
--- a/hamster/graphics.py
+++ b/hamster/graphics.py
@@ -8,6 +8,20 @@ class Colors(object):
                  (136, 138, 133), (85, 87, 83), (46, 52, 54)]
     almost_white = (250, 250, 250)
 
+    @staticmethod
+    def normalize_rgb(color):
+        # turns your average rgb into values with components in range 0..1
+        # if none of the componets are over 1 - will return what it got
+        if color[0] > 1 or color[1] > 0 or color[2] > 0:
+            color = [c / 255.0 for c in color]
+        return color
+    
+    @staticmethod
+    def rgb(color):
+        #return color that has each component in 0..255 range
+        return [c*255 for c in Colors.normalize_rgb(color)]
+        
+
 class Area(gtk.DrawingArea):
     """Abstraction on top of DrawingArea to work specifically with cairo"""
     __gsignals__ = {
@@ -17,38 +31,14 @@ class Area(gtk.DrawingArea):
         "button-release": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, )),
     }
 
-    def do_configure_event ( self, event ):
-        (self.__width, self.__height) = self.window.get_size()
-        self.queue_draw()
-                    
-    def do_expose_event ( self, event ):
-        self.width, self.height = self.window.get_size()
-        self.context = self.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(self.font_size * 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
-        
-        self.mouse_regions = [] #reset since these can move in each redraw
-        self._render()
-
     def __init__(self):
         gtk.DrawingArea.__init__(self)
         self.set_events(gtk.gdk.EXPOSURE_MASK
-                                 | gtk.gdk.LEAVE_NOTIFY_MASK
-                                 | gtk.gdk.BUTTON_PRESS_MASK
-                                 | gtk.gdk.BUTTON_RELEASE_MASK
-                                 | gtk.gdk.POINTER_MOTION_MASK
-                                 | gtk.gdk.POINTER_MOTION_HINT_MASK)
+                        | gtk.gdk.LEAVE_NOTIFY_MASK
+                        | gtk.gdk.BUTTON_PRESS_MASK
+                        | gtk.gdk.BUTTON_RELEASE_MASK
+                        | gtk.gdk.POINTER_MOTION_MASK
+                        | gtk.gdk.POINTER_MOTION_HINT_MASK)
         self.connect("button_release_event", self.__on_button_release)
         self.connect("motion_notify_event", self.__on_mouse_move)
         self.connect("leave_notify_event", self.__on_mouse_out)
@@ -71,24 +61,40 @@ class Area(gtk.DrawingArea):
         self.mouse_regions = [] #regions of drawing that respond to hovering/clicking
         self.__prev_mouse_regions = None
 
+
+    def __rectangle(self, x, y, w, h, color, opacity = 0):
+        if color[0] > 1: color = [c / 256.0 for c in color]
+
+        if opacity:
+            self.context.set_source_rgba(color[0], color[1], color[2], opacity)
+        elif len(color) == 3:
+            self.context.set_source_rgb(*color)
+        else:
+            self.context.set_source_rgba(*color)
+            
+        self.context.rectangle(x, y, w, h)
+    
+    def fill_area(self, x, y, w, h, color, opacity = 0):
+        self.context.save()
+        self.__rectangle(x, y, w, h, color, opacity)
+        self.context.fill()
+        self.context.restore()
+        
+    def fill_rectangle(self, x, y, w, h, color, opacity = 0):
+        self.context.save()
+        self.__rectangle(x, y, w, h, color, opacity)
+        self.context.fill_preserve()
+        self.set_color(color)
+        self.context.stroke()
+        self.context.restore()
+
     def set_text(self, text):
         # sets text and returns width and height of the layout
         self.layout.set_text(text)
-        w, h = self.layout.get_pixel_size()
-        return w, h
-        
-    def normalize_rgb(self, color):
-        #if any of values is over 1 - that means color has been entered in 0-255 range and we should normalize it
-        if color[0] > 1 or color[1] > 0 or color[2] > 0:
-            color = [c / 255.0 for c in color]
-        return color
-    
-    def rgb(self, color):
-        #return color that has each component in 0..255 range
-        return [c*255 for c in self.normalize_rgb(color)]
+        return self.layout.get_pixel_size()
         
     def set_color(self, color, opacity = None):
-        color = self.normalize_rgb(color)
+        color = Colors.normalize_rgb(color)
 
         if opacity:
             self.context.set_source_rgba(color[0], color[1], color[2], opacity)
@@ -111,131 +117,34 @@ class Area(gtk.DrawingArea):
     def _render(self):
         raise NotImplementedError
 
-    def set_value_range(self, x_min = None, x_max = None, y_min = None, y_max = None):
-        """sets up our internal conversion matrix, because cairo one will
-        scale also fonts and we need something in between!"""
-        
-        #store given params, we might redo the math later
-        if not self.value_boundaries:
-            self.value_boundaries = [x_min, x_max, y_min, y_max]
-        else:
-            if x_min != None:
-                self.value_boundaries[0] = x_min
-            if x_max != None:
-                self.value_boundaries[1] = x_max
-            if y_min != None:
-                self.value_boundaries[2] = y_min
-            if y_max != None:
-                self.value_boundaries[3] = y_max 
-        self.x_factor, self.y_factor = None, None
-        self._get_factors()
-
-    def _get_factors(self):
-        if not self.x_factor:
-            self.x_factor = 1
-            if self.value_boundaries and self.value_boundaries[0] != None and self.value_boundaries[1] != None:
-                self.x_factor = float(self.graph_width or self.width) / abs(self.value_boundaries[1] - self.value_boundaries[0])
-                
-        if not self.y_factor:            
-            self.y_factor = 1
-            if self.value_boundaries and self.value_boundaries[2] != None and self.value_boundaries[3] != None:
-                self.y_factor = float(self.graph_height or self.height) / abs(self.value_boundaries[3] - self.value_boundaries[2])
-
-        return self.x_factor, self.y_factor        
-
-
-    def get_pixel(self, x_value = None, y_value = None):
-        """returns screen pixel position for value x and y. Useful to
-        get and then pad something
-
-        x = min1 + (max1 - min1) * (x / abs(max2-min2))  
-            => min1 + const1 * x / const2
-            => const3 = const1 / const2
-            => min + x * const3
-        """
-        x_factor, y_factor = self._get_factors()
-
-        if x_value != None:
-            if self.value_boundaries and self.value_boundaries[0] != None:
-                if self.value_boundaries[1] > self.value_boundaries[0]:
-                    x_value = self.value_boundaries[0] + x_value * x_factor
-                else: #case when min is larger than max (flipped)
-                    x_value = self.value_boundaries[1] - x_value * x_factor
-            if y_value is None:
-                return x_value + self.graph_x
-
-        if y_value != None:
-            if self.value_boundaries and self.value_boundaries[2] != None:
-                if self.value_boundaries[3] > self.value_boundaries[2]:
-                    y_value = self.value_boundaries[2] + y_value * y_factor
-                else: #case when min is larger than max (flipped)
-                    y_value = self.value_boundaries[2] - y_value * y_factor
-            if x_value is None:
-                return y_value + self.graph_y
-            
-        return x_value + self.graph_x, y_value + self.graph_y
 
-    def get_value_at_pos(self, x = None, y = None):
-        """returns mapped value at the coordinates x,y"""
-        x_factor, y_factor = self._get_factors()
-        
-        if x != None:
-            x = (x - self.graph_x)  / x_factor
-            if y is None:
-                return x
-        if y != None:
-            y = (y - self.graph_x) / y_factor
-            if x is None:
-                return y
-        return x, y            
-        
-    def __rectangle(self, x, y, w, h, color, opacity = 0):
-        if color[0] > 1: color = [c / 256.0 for c in color]
+    """ exposure events """
+    def do_configure_event(self, event):
+        (self.__width, self.__height) = self.window.get_size()
+        self.queue_draw()
+                    
+    def do_expose_event(self, event):
+        self.width, self.height = self.window.get_size()
+        self.context = self.window.cairo_create()
 
-        if opacity:
-            self.context.set_source_rgba(color[0], color[1], color[2], opacity)
-        elif len(color) == 3:
-            self.context.set_source_rgb(*color)
-        else:
-            self.context.set_source_rgba(*color)
-            
-        self.context.rectangle(x, y, w, h)
-    
-    def fill_area(self, x, y, w, h, color, opacity = 0):
-        self.context.save()
-        self.__rectangle(x, y, w, h, color, opacity)
-        self.context.fill()
-        self.context.restore()
-        
-    def fill_rectangle(self, x, y, w, h, color, opacity = 0):
-        self.context.save()
-        self.__rectangle(x, y, w, h, color, opacity)
-        self.context.fill()
-        self.__rectangle(x, y, w, h, color, 0)
-        self.context.stroke()
-        self.context.restore()
 
-    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
-    
-    def move_to(self, x, y):
-        """our copy of moveto that takes into account our transformations"""
-        self.context.move_to(*self.get_pixel(x, y))
+        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()
 
-    def line_to(self, x, y):
-        self.context.line_to(*self.get_pixel(x, y))
+        self.layout = self.context.create_layout()
+        default_font = pango.FontDescription(gtk.Style().font_desc.to_string())
+        default_font.set_size(self.font_size * 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 __on_mouse_out(self, area, event):
-        self.__prev_mouse_regions = None
-        self.emit("mouse-over", [])
+        self.mouse_regions = [] #reset since these can move in each redraw
+        self._render()
 
+
+    """ mouse events """
     def __on_mouse_move(self, area, event):
         if not self.mouse_regions:
             return
@@ -262,6 +171,10 @@ class Area(gtk.DrawingArea):
 
         self.__prev_mouse_regions = mouse_regions
 
+    def __on_mouse_out(self, area, event):
+        self.__prev_mouse_regions = None
+        self.emit("mouse-over", [])
+
     def __on_button_release(self, area, event):
         if not self.mouse_regions:
             return
@@ -278,62 +191,4 @@ class Area(gtk.DrawingArea):
         if mouse_regions:
             self.emit("button-release", mouse_regions)
 
-
-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 isinstance(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 isinstance(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
  
diff --git a/hamster/widgets/dayline.py b/hamster/widgets/dayline.py
index 993a7f9..c648f8e 100644
--- a/hamster/widgets/dayline.py
+++ b/hamster/widgets/dayline.py
@@ -23,11 +23,101 @@ import gobject
 from hamster import stuff
 from hamster import graphics
 
+import time
 import datetime as dt
 import colorsys
 
 
 class DayLine(graphics.Area):
+    #TODO remove these obsolete functions with in-house transformations
+    def set_value_range(self, x_min = None, x_max = None, y_min = None, y_max = None):
+        """sets up our internal conversion matrix, because cairo one will
+        scale also fonts and we need something in between!"""
+        
+        #store given params, we might redo the math later
+        if not self.value_boundaries:
+            self.value_boundaries = [x_min, x_max, y_min, y_max]
+        else:
+            if x_min != None:
+                self.value_boundaries[0] = x_min
+            if x_max != None:
+                self.value_boundaries[1] = x_max
+            if y_min != None:
+                self.value_boundaries[2] = y_min
+            if y_max != None:
+                self.value_boundaries[3] = y_max 
+        self.x_factor, self.y_factor = None, None
+        self._get_factors()
+
+
+    def move_to(self, x, y):
+        """our copy of moveto that takes into account our transformations"""
+        self.context.move_to(*self.get_pixel(x, y))
+
+    def line_to(self, x, y):
+        self.context.line_to(*self.get_pixel(x, y))
+
+    def _get_factors(self):
+        if not self.x_factor:
+            self.x_factor = 1
+            if self.value_boundaries and self.value_boundaries[0] != None and self.value_boundaries[1] != None:
+                self.x_factor = float(self.graph_width or self.width) / abs(self.value_boundaries[1] - self.value_boundaries[0])
+                
+        if not self.y_factor:            
+            self.y_factor = 1
+            if self.value_boundaries and self.value_boundaries[2] != None and self.value_boundaries[3] != None:
+                self.y_factor = float(self.graph_height or self.height) / abs(self.value_boundaries[3] - self.value_boundaries[2])
+
+        return self.x_factor, self.y_factor        
+
+
+    def get_pixel(self, x_value = None, y_value = None):
+        """returns screen pixel position for value x and y. Useful to
+        get and then pad something
+
+        x = min1 + (max1 - min1) * (x / abs(max2-min2))  
+            => min1 + const1 * x / const2
+            => const3 = const1 / const2
+            => min + x * const3
+        """
+        x_factor, y_factor = self._get_factors()
+
+        if x_value != None:
+            if self.value_boundaries and self.value_boundaries[0] != None:
+                if self.value_boundaries[1] > self.value_boundaries[0]:
+                    x_value = self.value_boundaries[0] + x_value * x_factor
+                else: #case when min is larger than max (flipped)
+                    x_value = self.value_boundaries[1] - x_value * x_factor
+            if y_value is None:
+                return x_value + self.graph_x
+
+        if y_value != None:
+            if self.value_boundaries and self.value_boundaries[2] != None:
+                if self.value_boundaries[3] > self.value_boundaries[2]:
+                    y_value = self.value_boundaries[2] + y_value * y_factor
+                else: #case when min is larger than max (flipped)
+                    y_value = self.value_boundaries[2] - y_value * y_factor
+            if x_value is None:
+                return y_value + self.graph_y
+            
+        return x_value + self.graph_x, y_value + self.graph_y
+
+    def get_value_at_pos(self, x = None, y = None):
+        """returns mapped value at the coordinates x,y"""
+        x_factor, y_factor = self._get_factors()
+        
+        if x != None:
+            x = (x - self.graph_x)  / x_factor
+            if y is None:
+                return x
+        if y != None:
+            y = (y - self.graph_x) / y_factor
+            if x is None:
+                return y
+        return x, y            
+    
+    
+    #normal stuff
     def __init__(self):
         graphics.Area.__init__(self)
 
@@ -63,7 +153,7 @@ class DayLine(graphics.Area):
             self.range_start.target(start_time)
             self.scroll_to_range_start()
         else:
-            self.range_start = graphics.Integrator(start_time, damping = 0.35, attraction = 0.5)
+            self.range_start = Integrator(start_time, damping = 0.35, attraction = 0.5)
 
         self.highlight = highlight
         
@@ -345,3 +435,62 @@ class DayLine(graphics.Area):
             self.scroll_to_range_start()
 
 
+
+# TODO - should remove this and replace with standard tweening instead!
+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 isinstance(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 isinstance(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
diff --git a/hamster/widgets/timeline.py b/hamster/widgets/timeline.py
index c3fc508..74dd694 100644
--- a/hamster/widgets/timeline.py
+++ b/hamster/widgets/timeline.py
@@ -31,7 +31,95 @@ class TimeLine(graphics.Area):
         self.draw_mode = None
         self.max_hours = None
         
-    
+    #TODO remove these obsolete functions with in-house transformations
+    def set_value_range(self, x_min = None, x_max = None, y_min = None, y_max = None):
+        """sets up our internal conversion matrix, because cairo one will
+        scale also fonts and we need something in between!"""
+        
+        #store given params, we might redo the math later
+        if not self.value_boundaries:
+            self.value_boundaries = [x_min, x_max, y_min, y_max]
+        else:
+            if x_min != None:
+                self.value_boundaries[0] = x_min
+            if x_max != None:
+                self.value_boundaries[1] = x_max
+            if y_min != None:
+                self.value_boundaries[2] = y_min
+            if y_max != None:
+                self.value_boundaries[3] = y_max 
+        self.x_factor, self.y_factor = None, None
+        self._get_factors()
+
+
+    def move_to(self, x, y):
+        """our copy of moveto that takes into account our transformations"""
+        self.context.move_to(*self.get_pixel(x, y))
+
+    def line_to(self, x, y):
+        self.context.line_to(*self.get_pixel(x, y))
+
+    def _get_factors(self):
+        if not self.x_factor:
+            self.x_factor = 1
+            if self.value_boundaries and self.value_boundaries[0] != None and self.value_boundaries[1] != None:
+                self.x_factor = float(self.graph_width or self.width) / abs(self.value_boundaries[1] - self.value_boundaries[0])
+                
+        if not self.y_factor:            
+            self.y_factor = 1
+            if self.value_boundaries and self.value_boundaries[2] != None and self.value_boundaries[3] != None:
+                self.y_factor = float(self.graph_height or self.height) / abs(self.value_boundaries[3] - self.value_boundaries[2])
+
+        return self.x_factor, self.y_factor        
+
+
+    def get_pixel(self, x_value = None, y_value = None):
+        """returns screen pixel position for value x and y. Useful to
+        get and then pad something
+
+        x = min1 + (max1 - min1) * (x / abs(max2-min2))  
+            => min1 + const1 * x / const2
+            => const3 = const1 / const2
+            => min + x * const3
+        """
+        x_factor, y_factor = self._get_factors()
+
+        if x_value != None:
+            if self.value_boundaries and self.value_boundaries[0] != None:
+                if self.value_boundaries[1] > self.value_boundaries[0]:
+                    x_value = self.value_boundaries[0] + x_value * x_factor
+                else: #case when min is larger than max (flipped)
+                    x_value = self.value_boundaries[1] - x_value * x_factor
+            if y_value is None:
+                return x_value + self.graph_x
+
+        if y_value != None:
+            if self.value_boundaries and self.value_boundaries[2] != None:
+                if self.value_boundaries[3] > self.value_boundaries[2]:
+                    y_value = self.value_boundaries[2] + y_value * y_factor
+                else: #case when min is larger than max (flipped)
+                    y_value = self.value_boundaries[2] - y_value * y_factor
+            if x_value is None:
+                return y_value + self.graph_y
+            
+        return x_value + self.graph_x, y_value + self.graph_y
+
+    def get_value_at_pos(self, x = None, y = None):
+        """returns mapped value at the coordinates x,y"""
+        x_factor, y_factor = self._get_factors()
+        
+        if x != None:
+            x = (x - self.graph_x)  / x_factor
+            if y is None:
+                return x
+        if y != None:
+            y = (y - self.graph_x) / y_factor
+            if x is None:
+                return y
+        return x, y            
+
+
+    # Normal stuff    
     def draw(self, facts):
         import itertools
         self.facts = {}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]