[hamster-applet] moved the specialized functions out of graphics library to make it simpler to read
- From: Toms Baugis <tbaugis src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [hamster-applet] moved the specialized functions out of graphics library to make it simpler to read
- Date: Fri, 27 Nov 2009 11:03:24 +0000 (UTC)
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]