Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/pygtk_chart
diff options
context:
space:
mode:
authoraccayetano <almiracayetano@gmail.com>2012-01-22 21:39:34 (GMT)
committer accayetano <almiracayetano@gmail.com>2012-01-22 21:39:34 (GMT)
commit0f4bc904a9506c371efe33a056d2454d10ac7a64 (patch)
treeb65784f3be7496b1e67cd333919fe7222ec137d0 /pygtk_chart
Initial upload
Diffstat (limited to 'pygtk_chart')
-rw-r--r--pygtk_chart/__init__.py53
-rw-r--r--pygtk_chart/__init__.pycbin0 -> 1268 bytes
-rw-r--r--pygtk_chart/bar_chart.py729
-rw-r--r--pygtk_chart/bar_chart.pycbin0 -> 26455 bytes
-rw-r--r--pygtk_chart/basics.py133
-rw-r--r--pygtk_chart/basics.pycbin0 -> 4669 bytes
-rw-r--r--pygtk_chart/chart.py592
-rw-r--r--pygtk_chart/chart.pycbin0 -> 22045 bytes
-rw-r--r--pygtk_chart/chart_object.py155
-rw-r--r--pygtk_chart/chart_object.pycbin0 -> 5372 bytes
-rw-r--r--pygtk_chart/data/tango.color7
-rw-r--r--pygtk_chart/label.py744
-rw-r--r--pygtk_chart/label.pycbin0 -> 24623 bytes
-rw-r--r--pygtk_chart/multi_bar_chart.py649
-rw-r--r--pygtk_chart/multi_bar_chart.pycbin0 -> 25001 bytes
-rw-r--r--pygtk_chart/pie_chart.py474
16 files changed, 3536 insertions, 0 deletions
diff --git a/pygtk_chart/__init__.py b/pygtk_chart/__init__.py
new file mode 100644
index 0000000..891e603
--- /dev/null
+++ b/pygtk_chart/__init__.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+#
+# __init__.py
+#
+# Copyright 2008 Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+"""
+This package contains four pygtk widgets for drawing simple charts:
+ - line_chart.LineChart for line charts,
+ - pie_chart.PieChart for pie charts,
+ - bar_chart.BarChart for bar charts,
+ - bar_chart.MultiBarChart for charts with groups of bars.
+"""
+__docformat__ = "epytext"
+
+__version__ = "beta"
+__author__ = "Sven Festersen, John Dickinson"
+__license__ = "GPL"
+__url__ = "http://notmyname.github.com/pygtkChart/"
+
+import os
+from pygtk_chart.basics import gdk_color_list_from_file
+COLOR_AUTO = 0
+COLORS = gdk_color_list_from_file(os.sep.join([os.path.dirname(__file__), "data", "tango.color"]))
+
+#line style
+LINE_STYLE_SOLID = 0
+LINE_STYLE_DOTTED = 1
+LINE_STYLE_DASHED = 2
+LINE_STYLE_DASHED_ASYMMETRIC = 3
+
+#point styles
+POINT_STYLE_CIRCLE = 0
+POINT_STYLE_SQUARE = 1
+POINT_STYLE_CROSS = 2
+POINT_STYLE_TRIANGLE_UP = 3
+POINT_STYLE_TRIANGLE_DOWN = 4
+POINT_STYLE_DIAMOND = 5
+
diff --git a/pygtk_chart/__init__.pyc b/pygtk_chart/__init__.pyc
new file mode 100644
index 0000000..a4ba455
--- /dev/null
+++ b/pygtk_chart/__init__.pyc
Binary files differ
diff --git a/pygtk_chart/bar_chart.py b/pygtk_chart/bar_chart.py
new file mode 100644
index 0000000..d1e8879
--- /dev/null
+++ b/pygtk_chart/bar_chart.py
@@ -0,0 +1,729 @@
+# Copyright 2009 John Dickinson <john@johnandkaren.com>
+# Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+"""
+Contains the BarChart widget.
+
+Author: John Dickinson (john@johnandkaren.com),
+Sven Festersen (sven@sven-festersen.de)
+"""
+__docformat__ = "epytext"
+import cairo
+import gtk
+import gobject
+import os
+import math
+
+import pygtk_chart
+from pygtk_chart.basics import *
+from pygtk_chart.chart_object import ChartObject
+from pygtk_chart import chart
+from pygtk_chart import label
+
+from pygtk_chart import COLORS, COLOR_AUTO
+
+MODE_VERTICAL = 0
+MODE_HORIZONTAL = 1
+
+def draw_rounded_rectangle(context, x, y, width, height, radius=0):
+ """
+ Draws a rectangle with rounded corners to context. radius specifies
+ the corner radius in px.
+
+ @param context: the context to draw on
+ @type context: CairoContext
+ @param x: x coordinate of the upper left corner
+ @type x: float
+ @param y: y coordinate of the upper left corner
+ @type y: float
+ @param width: width of the rectangle in px
+ @type width: float
+ @param height: height of the rectangle in px
+ @type height: float
+ @param radius: corner radius in px (default: 0)
+ @type radius: float.
+ """
+ if radius == 0:
+ context.rectangle(x, y, width, height)
+ else:
+ context.move_to(x, y + radius)
+ context.arc(x + radius, y + radius, radius, math.pi, 1.5 * math.pi)
+ context.rel_line_to(width - 2 * radius, 0)
+ context.arc(x + width - radius, y + radius, radius, 1.5 * math.pi, 2 * math.pi)
+ context.rel_line_to(0, height - 2 * radius)
+ context.arc(x + width - radius, y + height - radius, radius, 0, 0.5 * math.pi)
+ context.rel_line_to(-(width - 2 * radius), 0)
+ context.arc(x + radius, y + height - radius, radius, 0.5 * math.pi, math.pi)
+ context.close_path()
+
+
+class Bar(chart.Area):
+ """
+ A class that represents a bar on a bar chart.
+
+ Properties
+ ==========
+ The Bar class inherits properties from chart.Area.
+ Additional properties:
+ - corner-radius (radius of the bar's corners, in px; type: float)
+
+ Signals
+ =======
+ The Bar class inherits signals from chart.Area.
+ """
+
+ __gproperties__ = {"corner-radius": (gobject.TYPE_INT, "bar corner radius",
+ "The radius of the bar's rounded corner.",
+ 0, 100, 0, gobject.PARAM_READWRITE)}
+
+ def __init__(self, name, value, title=""):
+ chart.Area.__init__(self, name, value, title)
+ self._label_object = label.Label((0, 0), title)
+ self._value_label_object = label.Label((0, 0), "")
+
+ self._corner_radius = 0
+
+ def do_get_property(self, property):
+ if property.name == "visible":
+ return self._show
+ elif property.name == "antialias":
+ return self._antialias
+ elif property.name == "name":
+ return self._name
+ elif property.name == "value":
+ return self._value
+ elif property.name == "color":
+ return self._color
+ elif property.name == "label":
+ return self._label
+ elif property.name == "highlighted":
+ return self._highlighted
+ elif property.name == "corner-radius":
+ return self._corner_radius
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "visible":
+ self._show = value
+ elif property.name == "antialias":
+ self._antialias = value
+ elif property.name == "value":
+ self._value = value
+ elif property.name == "color":
+ self._color = value
+ elif property.name == "label":
+ self._label = value
+ elif property.name == "highlighted":
+ self._highlighted = value
+ elif property.name == "corner-radius":
+ self._corner_radius = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def _do_draw(self, context, rect, n, i, mode, max_value, bar_padding, value_label_size, label_size, draw_labels):
+ if mode == MODE_VERTICAL:
+ self._do_draw_single_vertical(context, rect, n, i, mode, max_value, bar_padding, value_label_size, label_size, draw_labels)
+ elif mode == MODE_HORIZONTAL:
+ self._do_draw_single_horizontal(context, rect, n, i, mode, max_value, bar_padding, value_label_size, label_size, draw_labels)
+
+ def _do_draw_single_vertical(self, context, rect, n, i, mode, max_value, bar_padding, value_label_size, label_size, draw_labels):
+ bar_width = (rect.width - (n - 1) * bar_padding) / n
+ bar_height = (rect.height - value_label_size - label_size) * self._value / max_value
+ bar_x = rect.x + i * (bar_width + bar_padding)
+ bar_y = rect.y + rect.height - bar_height - label_size
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ if self._highlighted:
+ context.set_source_rgba(1, 1, 1, 0.1)
+ draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ if draw_labels:
+ #draw the value label
+ self._value_label_object.set_text(str(self._value))
+ self._value_label_object.set_color(self._color)
+ self._value_label_object.set_max_width(bar_width)
+ self._value_label_object.set_position((bar_x + bar_width / 2, bar_y - 3))
+ self._value_label_object.set_anchor(label.ANCHOR_BOTTOM_CENTER)
+ self._value_label_object.draw(context, rect)
+ context.fill()
+
+ #draw label
+ self._label_object.set_text(self._label)
+ self._label_object.set_color(self._color)
+ self._label_object.set_max_width(bar_width)
+ self._label_object.set_position((bar_x + bar_width / 2, bar_y + bar_height + 3))
+ self._label_object.set_anchor(label.ANCHOR_TOP_CENTER)
+ self._label_object.draw(context, rect)
+ context.fill()
+
+ chart.add_sensitive_area(chart.AREA_RECTANGLE, (bar_x, bar_y, bar_width, bar_height), self)
+
+ def _do_draw_single_horizontal(self, context, rect, n, i, mode, max_value, bar_padding, value_label_size, label_size, draw_labels):
+ bar_width = (rect.width - value_label_size - label_size) * self._value / max_value
+ bar_height = (rect.height - (n - 1) * bar_padding) / n
+ bar_x = rect.x + label_size
+ bar_y = rect.y + i * (bar_height + bar_padding)
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ if self._highlighted:
+ context.set_source_rgba(1, 1, 1, 0.1)
+ draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ if draw_labels:
+ #draw the value label
+ self._value_label_object.set_text(str(self._value))
+ self._value_label_object.set_color(self._color)
+ self._value_label_object.set_position((bar_x + bar_width + 3, bar_y + bar_height / 2))
+ self._value_label_object.set_anchor(label.ANCHOR_LEFT_CENTER)
+ self._value_label_object.draw(context, rect)
+ context.fill()
+
+ #draw label
+ self._label_object.set_text(self._label)
+ self._label_object.set_color(self._color)
+ self._label_object.set_max_width(0.25 * rect.width)
+ self._label_object.set_position((bar_x - 3, bar_y + bar_height / 2))
+ self._label_object.set_anchor(label.ANCHOR_RIGHT_CENTER)
+ self._label_object.draw(context, rect)
+ context.fill()
+
+ chart.add_sensitive_area(chart.AREA_RECTANGLE, (bar_x, bar_y, bar_width, bar_height), self)
+
+ def get_value_label_size(self, context, rect, mode, n, bar_padding):
+ if mode == MODE_VERTICAL:
+ bar_width = (rect.width - (n - 1) * bar_padding) / n
+ self._value_label_object.set_max_width(bar_width)
+ self._value_label_object.set_text(str(self._value))
+ return self._value_label_object.get_calculated_dimensions(context, rect)[1]
+ elif mode == MODE_HORIZONTAL:
+ self._value_label_object.set_wrap(False)
+ self._value_label_object.set_fixed(True)
+ self._value_label_object.set_text(str(self._value))
+ return self._value_label_object.get_calculated_dimensions(context, rect)[0]
+
+ def get_label_size(self, context, rect, mode, n, bar_padding):
+ if mode == MODE_VERTICAL:
+ bar_width = (rect.width - (n - 1) * bar_padding) / n
+ self._label_object.set_max_width(bar_width)
+ self._label_object.set_text(self._label)
+ return self._label_object.get_calculated_dimensions(context, rect)[1]
+ elif mode == MODE_HORIZONTAL:
+ self._label_object.set_max_width(0.25 * rect.width)
+ self._label_object.set_text(self._label)
+ return self._label_object.get_calculated_dimensions(context, rect)[0]
+
+ def set_corner_radius(self, radius):
+ """
+ Set the radius of the bar's corners in px (default: 0).
+
+ @param radius: radius of the corners
+ @type radius: int in [0, 100].
+ """
+ self.set_property("corner-radius", radius)
+ self.emit("appearance_changed")
+
+ def get_corner_radius(self):
+ """
+ Returns the current radius of the bar's corners in px.
+
+ @return: int in [0, 100]
+ """
+ return self.get_property("corner-radius")
+
+
+class Grid(ChartObject):
+ """
+ This class represents the grid on BarChart and MultiBarChart
+ widgets.
+
+ Properties
+ ==========
+ bar_chart.Grid inherits properties from ChartObject.
+ Additional properties:
+ - line-style (the style of the grid lines, type: a line style
+ constant)
+ - color (the color of the grid lines, type: gtk.gdk.Color)
+ - show-values (sets whether values should be shown at the grid
+ lines, type: boolean)
+ - padding (the grid's padding in px, type: int in [0, 100]).
+
+ Signals
+ =======
+ The Grid class inherits signal from chart_object.ChartObject.
+ """
+
+ __gproperties__ = {"show-values": (gobject.TYPE_BOOLEAN, "show values",
+ "Set whether to show grid values.",
+ True, gobject.PARAM_READWRITE),
+ "color": (gobject.TYPE_PYOBJECT, "color",
+ "The color of the grid lines.",
+ gobject.PARAM_READWRITE),
+ "line-style": (gobject.TYPE_INT, "line style",
+ "The grid's line style", 0, 3, 0,
+ gobject.PARAM_READWRITE),
+ "padding": (gobject.TYPE_INT, "padding",
+ "The grid's padding", 0, 100, 6,
+ gobject.PARAM_READWRITE)}
+
+ def __init__(self):
+ ChartObject.__init__(self)
+ #private properties:
+ self._show_values = True
+ self._color = gtk.gdk.color_parse("#dedede")
+ self._line_style = pygtk_chart.LINE_STYLE_SOLID
+ self._padding = 6
+
+ def do_get_property(self, property):
+ if property.name == "visible":
+ return self._show
+ elif property.name == "antialias":
+ return self._antialias
+ elif property.name == "show-values":
+ return self._show_values
+ elif property.name == "color":
+ return self._color
+ elif property.name == "line-style":
+ return self._line_style
+ elif property.name == "padding":
+ return self._padding
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "visible":
+ self._show = value
+ elif property.name == "antialias":
+ self._antialias = value
+ elif property.name == "show-values":
+ self._show_values = value
+ elif property.name == "color":
+ self._color = value
+ elif property.name == "line-style":
+ self._line_style = value
+ elif property.name == "padding":
+ self._padding = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def _do_draw(self, context, rect, mode, maximum_value, value_label_size, label_size):
+ n = maximum_value / (10 ** int(math.log10(maximum_value)))
+ context.set_antialias(cairo.ANTIALIAS_NONE)
+ set_context_line_style(context, self._line_style)
+ labels = []
+ if mode == MODE_VERTICAL:
+ delta = (rect.height - value_label_size - label_size) / n
+ if self._show_values:
+ max_label_size = 0
+ for i in range(0, int(n + 1)):
+ y = rect.y + rect.height - i * delta - label_size
+ value = maximum_value * float(i) / n
+ value_label = label.Label((rect.x, y), str(value))
+ max_label_size = max(max_label_size, value_label.get_calculated_dimensions(context, rect)[0])
+ labels.append(value_label)
+ max_label_size += 3
+ rect = gtk.gdk.Rectangle(int(rect.x + max_label_size), rect.y, int(rect.width - max_label_size), rect.height)
+ for i in range(0, len(labels)):
+ y = rect.y + rect.height - i * delta - label_size
+ value_label = labels[i]
+ value_label.set_position((rect.x - 3, y))
+ value_label.set_anchor(label.ANCHOR_RIGHT_CENTER)
+ value_label.draw(context, rect)
+ context.fill()
+
+ for i in range(0, int(n + 1)):
+ y = rect.y + rect.height - i * delta - label_size
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ context.move_to(rect.x, y)
+ context.rel_line_to(rect.width, 0)
+ context.stroke()
+ rect = gtk.gdk.Rectangle(rect.x + self._padding, rect.y, rect.width - 2 * self._padding, rect.height)
+ elif mode == MODE_HORIZONTAL:
+ delta = (rect.width - value_label_size - label_size) / n
+
+ if self._show_values:
+ max_label_size = 0
+ for i in range(0, int(n + 1)):
+ x = rect.x + i * delta + label_size
+ value = maximum_value * float(i) / n
+ value_label = label.Label((x, rect.y + rect.height), str(value))
+ max_label_size = max(max_label_size, value_label.get_calculated_dimensions(context, rect)[1])
+ labels.append(value_label)
+ max_label_size += 3
+ rect = gtk.gdk.Rectangle(rect.x, rect.y, rect.width, int(rect.height - max_label_size))
+ for i in range(0, len(labels)):
+ x = rect.x + i * delta + label_size
+ value_label = labels[i]
+ value_label.set_position((x, rect.y + rect.height + 3))
+ value_label.set_anchor(label.ANCHOR_TOP_CENTER)
+ value_label.draw(context, rect)
+ context.fill()
+
+ for i in range(0, int(n + 1)):
+ x = rect.x + i * delta + label_size
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ context.move_to(x, rect.y)
+ context.rel_line_to(0, rect.height)
+ context.stroke()
+ rect = gtk.gdk.Rectangle(rect.x, rect.y + self._padding, rect.width, rect.height - 2 * self._padding)
+ return rect
+
+ #set and get methods
+ def set_show_values(self, show):
+ """
+ Set whether values should be shown.
+
+ @type show: boolean.
+ """
+ self.set_property("show-values", show)
+ self.emit("appearance_changed")
+
+ def get_show_values(self):
+ """
+ Returns True if grid values are shown.
+
+ @return: boolean.
+ """
+ return self.get_property("show-values")
+
+ def set_color(self, color):
+ """
+ Set the color of the grid lines.
+
+ @param color: the grid lines' color
+ @type color: gtk.gdk.Color.
+ """
+ self.set_property("color", color)
+ self.emit("appearance_changed")
+
+ def get_color(self):
+ """
+ Returns the current color of the grid lines.
+
+ @return: gtk.gdk.Color.
+ """
+ return self.get_property("color")
+
+ def set_line_style(self, style):
+ """
+ Set the style of the grid lines. style has to be one of
+ - pygtk_chart.LINE_STYLE_SOLID (default)
+ - pygtk_chart.LINE_STYLE_DOTTED
+ - pygtk_chart.LINE_STYLE_DASHED
+ - pygtk_chart.LINE_STYLE_DASHED_ASYMMETRIC
+
+ @param style: the new line style
+ @type style: one of the constants above.
+ """
+ self.set_property("line-style", style)
+ self.emit("appearance_changed")
+
+ def get_line_style(self):
+ """
+ Returns the current grid's line style.
+
+ @return: a line style constant.
+ """
+ return self.get_property("line-style")
+
+ def set_padding(self, padding):
+ """
+ Set the grid's padding.
+
+ @type padding: int in [0, 100].
+ """
+ self.set_property("padding", padding)
+ self.emit("appearance_changed")
+
+ def get_padding(self):
+ """
+ Returns the grid's padding.
+
+ @return: int in [0, 100].
+ """
+ return self.get_property("padding")
+
+
+
+class BarChart(chart.Chart):
+ """
+ This is a widget that show a simple BarChart.
+
+ Properties
+ ==========
+ The BarChart class inherits properties from chart.Chart.
+ Additional properites:
+ - draw-labels (set wether to draw bar label, type: boolean)
+ - enable-mouseover (set whether to show a mouseover effect, type:
+ boolean)
+ - mode (the mode of the bar chart, type: one of MODE_VERTICAL,
+ MODE_HORIZONTAL)
+ - bar-padding (the sace between bars in px, type: int in [0, 100]).
+
+ Signals
+ =======
+ The BarChart class inherits signals from chart.Chart.
+ Additional signals:
+ - bar-clicked: emitted when a bar on the bar chart was clicked
+ callback signature:
+ def bar_clicked(chart, bar).
+
+ """
+
+ __gsignals__ = {"bar-clicked": (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,))}
+
+ __gproperties__ = {"bar-padding": (gobject.TYPE_INT, "bar padding",
+ "The distance between two bars.",
+ 0, 100, 16,
+ gobject.PARAM_READWRITE),
+ "mode": (gobject.TYPE_INT, "mode",
+ "The chart's mode.", 0, 1, 0,
+ gobject.PARAM_READWRITE),
+ "draw-labels": (gobject.TYPE_BOOLEAN,
+ "draw labels", "Set whether to draw labels on bars.",
+ True, gobject.PARAM_READWRITE),
+ "enable-mouseover": (gobject.TYPE_BOOLEAN, "enable mouseover",
+ "Set whether to enable mouseover effect.",
+ True, gobject.PARAM_READWRITE)}
+
+ def __init__(self):
+ super(BarChart, self).__init__()
+ #private properties:
+ self._bars = []
+ #gobject properties:
+ self._bar_padding = 16
+ self._mode = MODE_VERTICAL
+ self._draw_labels = True
+ self._mouseover = True
+ #public attributes:
+ self.grid = Grid()
+ #connect callbacks:
+ self.grid.connect("appearance_changed", self._cb_appearance_changed)
+
+ def do_get_property(self, property):
+ if property.name == "padding":
+ return self._padding
+ elif property.name == "bar-padding":
+ return self._bar_padding
+ elif property.name == "mode":
+ return self._mode
+ elif property.name == "draw-labels":
+ return self._draw_labels
+ elif property.name == "enable-mouseover":
+ return self._mouseover
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "padding":
+ self._padding = value
+ elif property.name == "bar-padding":
+ self._bar_padding = value
+ elif property.name == "mode":
+ self._mode = value
+ elif property.name == "draw-labels":
+ self._draw_labels = value
+ elif property.name == "enable-mouseover":
+ self._mouseover = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ #drawing methods
+ def draw(self, context):
+ """
+ Draw the widget. This method is called automatically. Don't call it
+ yourself. If you want to force a redrawing of the widget, call
+ the queue_draw() method.
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ """
+ label.begin_drawing()
+
+ rect = self.get_allocation()
+ rect = gtk.gdk.Rectangle(0, 0, rect.width, rect.height) #transform rect to context coordinates
+ context.set_line_width(1)
+
+ rect = self.draw_basics(context, rect)
+ maximum_value = max(bar.get_value() for bar in self._bars)
+ #find out the size of the value labels
+ value_label_size = 0
+ if self._draw_labels:
+ for bar in self._bars:
+ value_label_size = max(value_label_size, bar.get_value_label_size(context, rect, self._mode, len(self._bars), self._bar_padding))
+ value_label_size += 3
+
+ #find out the size of the labels:
+ label_size = 0
+ if self._draw_labels:
+ for bar in self._bars:
+ label_size = max(label_size, bar.get_label_size(context, rect, self._mode, len(self._bars), self._bar_padding))
+ label_size += 3
+
+ rect = self._do_draw_grid(context, rect, maximum_value, value_label_size, label_size)
+ self._do_draw_bars(context, rect, maximum_value, value_label_size, label_size)
+
+ label.finish_drawing()
+
+ if self._mode == MODE_VERTICAL:
+ n = len(self._bars)
+ minimum_width = rect.x + self._padding + (n - 1) * self._bar_padding + n * 10
+ minimum_height = 100 + self._padding + rect.y
+ elif self._mode == MODE_HORIZONTAL:
+ n = len(self._bars)
+ minimum_width = rect.x + self._bar_padding + 100
+ minimum_height = rect.y + self._padding + (n - 1) * self._bar_padding + n * 10
+ self.set_size_request(minimum_width, minimum_height)
+
+ def draw_basics(self, context, rect):
+ """
+ Draw basic things that every plot has (background, title, ...).
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ @type rect: gtk.gdk.Rectangle
+ @param rect: A rectangle representing the charts area.
+ """
+ self.background.draw(context, rect)
+ self.title.draw(context, rect, self._padding)
+
+ #calculate the rectangle that's available for drawing the chart
+ title_height = self.title.get_real_dimensions()[1]
+ rect_height = int(rect.height - 3 * self._padding - title_height)
+ rect_width = int(rect.width - 2 * self._padding)
+ rect_x = int(rect.x + self._padding)
+ rect_y = int(rect.y + title_height + 2 * self._padding)
+ return gtk.gdk.Rectangle(rect_x, rect_y, rect_width, rect_height)
+
+ def _do_draw_grid(self, context, rect, maximum_value, value_label_size, label_size):
+ if self.grid.get_visible():
+ return self.grid.draw(context, rect, self._mode, maximum_value, value_label_size, label_size)
+ else:
+ return rect
+
+ def _do_draw_bars(self, context, rect, maximum_value, value_label_size, label_size):
+ if self._bars == []:
+ return
+
+ #draw the bars
+ chart.init_sensitive_areas()
+ for i, bar in enumerate(self._bars):
+ bar.draw(context, rect, len(self._bars), i, self._mode, maximum_value, self._bar_padding, value_label_size, label_size, self._draw_labels)
+
+ #other methods
+ def add_bar(self, bar):
+ if bar.get_color() == COLOR_AUTO:
+ bar.set_color(COLORS[len(self._bars) % len(COLORS)])
+ self._bars.append(bar)
+ bar.connect("appearance_changed", self._cb_appearance_changed)
+
+ #callbacks
+ def _cb_motion_notify(self, widget, event):
+ if not self._mouseover: return
+ bars = chart.get_sensitive_areas(event.x, event.y)
+ if bars == []: return
+ for bar in self._bars:
+ bar.set_property("highlighted", bar in bars)
+ self.queue_draw()
+
+ def _cb_button_pressed(self, widget, event):
+ bars = chart.get_sensitive_areas(event.x, event.y)
+ for bar in bars:
+ self.emit("bar-clicked", bar)
+
+ #set and get methods
+ def set_bar_padding(self, padding):
+ """
+ Set the space between two bars in px.
+
+ @param padding: space between bars in px
+ @type padding: int in [0, 100].
+ """
+ self.set_property("bar-padding", padding)
+ self.queue_draw()
+
+ def get_bar_padding(self):
+ """
+ Returns the space between bars in px.
+
+ @return: int in [0, 100].
+ """
+ return self.get_property("bar-padding")
+
+ def set_mode(self, mode):
+ """
+ Set the mode (vertical or horizontal) of the BarChart. mode has
+ to be bar_chart.MODE_VERTICAL (default) or
+ bar_chart.MODE_HORIZONTAL.
+
+ @param mode: the new mode of the chart
+ @type mode: one of the mode constants above.
+ """
+ self.set_property("mode", mode)
+ self.queue_draw()
+
+ def get_mode(self):
+ """
+ Returns the current mode of the chart: bar_chart.MODE_VERTICAL
+ or bar_chart.MODE_HORIZONTAL.
+
+ @return: a mode constant.
+ """
+ return self.get_property("mode")
+
+ def set_draw_labels(self, draw):
+ """
+ Set whether labels should be drawn on bars.
+
+ @type draw: boolean.
+ """
+ self.set_property("draw-labels", draw)
+ self.queue_draw()
+
+ def get_draw_labels(self):
+ """
+ Returns True if labels are drawn on bars.
+
+ @return: boolean.
+ """
+ return self.get_property("draw-labels")
+
+ def set_enable_mouseover(self, mouseover):
+ """
+ Set whether a mouseover effect should be shown when the pointer
+ enters a bar.
+
+ @type mouseover: boolean.
+ """
+ self.set_property("enable-mouseover", mouseover)
+
+ def get_enable_mouseover(self):
+ """
+ Returns True if the mouseover effect is enabled.
+
+ @return: boolean.
+ """
+ return self.get_property("enable-mouseover")
+
diff --git a/pygtk_chart/bar_chart.pyc b/pygtk_chart/bar_chart.pyc
new file mode 100644
index 0000000..ef91f75
--- /dev/null
+++ b/pygtk_chart/bar_chart.pyc
Binary files differ
diff --git a/pygtk_chart/basics.py b/pygtk_chart/basics.py
new file mode 100644
index 0000000..5fdcd10
--- /dev/null
+++ b/pygtk_chart/basics.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+#
+# misc.py
+#
+# Copyright 2008 Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+"""
+This module contains simple functions needed by all other modules.
+
+Author: Sven Festersen (sven@sven-festersen.de)
+"""
+__docformat__ = "epytext"
+import cairo
+import gtk
+import os
+
+import pygtk_chart
+
+def is_in_range(x, (xmin, xmax)):
+ """
+ Use this method to test whether M{xmin <= x <= xmax}.
+
+ @type x: number
+ @type xmin: number
+ @type xmax: number
+ """
+ return (xmin <= x and xmax >= x)
+
+def intersect_ranges(range_a, range_b):
+ min_a, max_a = range_a
+ min_b, max_b = range_b
+ return max(min_a, min_b), min(max_a, max_b)
+
+def get_center(rect):
+ """
+ Find the center point of a rectangle.
+
+ @type rect: gtk.gdk.Rectangle
+ @param rect: The rectangle.
+ @return: A (x, y) tuple specifying the center point.
+ """
+ return rect.width / 2, rect.height / 2
+
+def color_gdk_to_cairo(color):
+ """
+ Convert a gtk.gdk.Color to cairo color.
+
+ @type color: gtk.gdk.Color
+ @param color: the color to convert
+ @return: a color in cairo format.
+ """
+ return (color.red / 65535.0, color.green / 65535.0, color.blue / 65535.0)
+
+def color_cairo_to_gdk(r, g, b):
+ return gtk.gdk.Color(int(65535 * r), int(65535 * g), int(65535 * b))
+
+def color_rgb_to_cairo(color):
+ """
+ Convert a 8 bit RGB value to cairo color.
+
+ @type color: a triple of integers between 0 and 255
+ @param color: The color to convert.
+ @return: A color in cairo format.
+ """
+ return (color[0] / 255.0, color[1] / 255.0, color[2] / 255.0)
+
+def color_html_to_cairo(color):
+ """
+ Convert a html (hex) RGB value to cairo color.
+
+ @type color: html color string
+ @param color: The color to convert.
+ @return: A color in cairo format.
+ """
+ if color[0] == '#':
+ color = color[1:]
+ (r, g, b) = (int(color[:2], 16),
+ int(color[2:4], 16),
+ int(color[4:], 16))
+ return color_rgb_to_cairo((r, g, b))
+
+def color_list_from_file(filename):
+ """
+ Read a file with one html hex color per line and return a list
+ of cairo colors.
+ """
+ result = []
+ if os.path.exists(filename):
+ f = open(filename, "r")
+ for line in f.readlines():
+ line = line.strip()
+ result.append(color_html_to_cairo(line))
+ return result
+
+def gdk_color_list_from_file(filename):
+ """
+ Read a file with one html hex color per line and return a list
+ of gdk colors.
+ """
+ result = []
+ if os.path.exists(filename):
+ f = open(filename, "r")
+ for line in f.readlines():
+ line = line.strip()
+ result.append(gtk.gdk.color_parse(line))
+ return result
+
+def set_context_line_style(context, style):
+ """
+ The the line style for a context.
+ """
+ if style == pygtk_chart.LINE_STYLE_SOLID:
+ context.set_dash([])
+ elif style == pygtk_chart.LINE_STYLE_DASHED:
+ context.set_dash([5])
+ elif style == pygtk_chart.LINE_STYLE_DASHED_ASYMMETRIC:
+ context.set_dash([6, 6, 2, 6])
+ elif style == pygtk_chart.LINE_STYLE_DOTTED:
+ context.set_dash([1])
diff --git a/pygtk_chart/basics.pyc b/pygtk_chart/basics.pyc
new file mode 100644
index 0000000..5b1d2e8
--- /dev/null
+++ b/pygtk_chart/basics.pyc
Binary files differ
diff --git a/pygtk_chart/chart.py b/pygtk_chart/chart.py
new file mode 100644
index 0000000..8b4a283
--- /dev/null
+++ b/pygtk_chart/chart.py
@@ -0,0 +1,592 @@
+#!/usr/bin/env python
+#
+# plot.py
+#
+# Copyright 2008 Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+"""
+Module Contents
+===============
+This is the main module. It contains the base classes for chart widgets.
+ - class Chart: base class for all chart widgets.
+ - class Background: background of a chart widget.
+ - class Title: title of a chart.
+
+Colors
+------
+All colors that pygtkChart uses are gtk.gdk.Colors as used by PyGTK.
+
+Author: Sven Festersen (sven@sven-festersen.de)
+"""
+__docformat__ = "epytext"
+import cairo
+import gobject
+import gtk
+import os
+import pango
+import pangocairo
+import pygtk
+
+from pygtk_chart.chart_object import ChartObject
+from pygtk_chart.basics import *
+from pygtk_chart import label
+
+COLOR_AUTO = 0
+AREA_CIRCLE = 0
+AREA_RECTANGLE = 1
+CLICK_SENSITIVE_AREAS = []
+
+
+def init_sensitive_areas():
+ global CLICK_SENSITIVE_AREAS
+ CLICK_SENSITIVE_AREAS = []
+
+def add_sensitive_area(type, coords, data):
+ global CLICK_SENSITIVE_AREAS
+ CLICK_SENSITIVE_AREAS.append((type, coords, data))
+
+def get_sensitive_areas(x, y):
+ res = []
+ global CLICK_SENSITIVE_AREAS
+ for type, coords, data in CLICK_SENSITIVE_AREAS:
+ if type == AREA_CIRCLE:
+ ax, ay, radius = coords
+ if (ax - x) ** 2 + (ay - y) ** 2 <= radius ** 2:
+ res.append(data)
+ elif type == AREA_RECTANGLE:
+ ax, ay, width, height = coords
+ if ax <= x <= ax + width and ay <= y <= ay + height:
+ res.append(data)
+ return res
+
+
+class Chart(gtk.DrawingArea):
+ """
+ This is the base class for all chart widgets.
+
+ Properties
+ ==========
+ The Chart class inherits properties from gtk.DrawingArea.
+ Additional properties:
+ - padding (the amount of free white space between the chart's
+ content and its border in px, type: int in [0, 100].
+
+ Signals
+ =======
+ The Chart class inherits signals from gtk.DrawingArea.
+ """
+
+ __gproperties__ = {"padding": (gobject.TYPE_INT, "padding",
+ "The chart's padding.", 0, 100, 16,
+ gobject.PARAM_READWRITE)}
+
+ def __init__(self):
+ gtk.DrawingArea.__init__(self)
+ #private properties:
+ self._padding = 16
+ #objects needed for every chart:
+ self.background = Background()
+ self.background.connect("appearance-changed", self._cb_appearance_changed)
+ self.title = Title()
+ self.title.connect("appearance-changed", self._cb_appearance_changed)
+
+ self.add_events(gtk.gdk.BUTTON_PRESS_MASK|gtk.gdk.SCROLL_MASK|gtk.gdk.POINTER_MOTION_MASK)
+ self.connect("expose_event", self._cb_expose_event)
+ self.connect("button_press_event", self._cb_button_pressed)
+ self.connect("motion-notify-event", self._cb_motion_notify)
+
+ def do_get_property(self, property):
+ if property.name == "padding":
+ return self._padding
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "padding":
+ self._padding = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def _cb_appearance_changed(self, object):
+ """
+ This method is called after the appearance of an object changed
+ and forces a redraw.
+ """
+ self.queue_draw()
+
+ def _cb_button_pressed(self, widget, event):
+ pass
+
+ def _cb_motion_notify(self, widget, event):
+ pass
+
+ def _cb_expose_event(self, widget, event):
+ """
+ This method is called when an instance of Chart receives
+ the gtk expose_event.
+
+ @type widget: gtk.Widget
+ @param widget: The widget that received the event.
+ @type event: gtk.Event
+ @param event: The event.
+ """
+ self.context = widget.window.cairo_create()
+ self.context.rectangle(event.area.x, event.area.y, \
+ event.area.width, event.area.height)
+ self.context.clip()
+ self.draw(self.context)
+ return False
+
+ def draw_basics(self, context, rect):
+ """
+ Draw basic things that every plot has (background, title, ...).
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ @type rect: gtk.gdk.Rectangle
+ @param rect: A rectangle representing the charts area.
+ """
+ self.background.draw(context, rect)
+ self.title.draw(context, rect)
+
+ #calculate the rectangle that's available for drawing the chart
+ title_height = self.title.get_real_dimensions()[1]
+ rect_height = int(rect.height - 3 * self._padding - title_height)
+ rect_width = int(rect.width - 2 * self._padding)
+ rect_x = int(rect.x + self._padding)
+ rect_y = int(rect.y + title_height + 2 * self._padding)
+ return gtk.gdk.Rectangle(rect_x, rect_y, rect_width, rect_height)
+
+ def draw(self, context):
+ """
+ Draw the widget. This method is called automatically. Don't call it
+ yourself. If you want to force a redrawing of the widget, call
+ the queue_draw() method.
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ """
+ rect = self.get_allocation()
+ rect = gtk.gdk.Rectangle(0, 0, rect.width, rect.height) #transform rect to context coordinates
+ context.set_line_width(1)
+ rect = self.draw_basics(context, rect)
+
+ def export_svg(self, filename, size=None):
+ """
+ Saves the contents of the widget to svg file. The size of the image
+ will be the size of the widget.
+
+ @type filename: string
+ @param filename: The path to the file where you want the chart to be saved.
+ @type size: tuple
+ @param size: Optional parameter to give the desired height and width of the image.
+ """
+ if size is None:
+ rect = self.get_allocation()
+ width = rect.width
+ height = rect.height
+ else:
+ width, height = size
+ old_alloc = self.get_allocation
+ self.get_allocation = lambda: gtk.gdk.Rectangle(0, 0, width, height)
+ surface = cairo.SVGSurface(filename, width, height)
+ ctx = cairo.Context(surface)
+ context = pangocairo.CairoContext(ctx)
+ self.draw(context)
+ surface.finish()
+ if size is not None:
+ self.get_allocation = old_alloc
+
+ def export_png(self, filename, size=None):
+ """
+ Saves the contents of the widget to png file. The size of the image
+ will be the size of the widget.
+
+ @type filename: string
+ @param filename: The path to the file where you want the chart to be saved.
+ @type size: tuple
+ @param size: Optional parameter to give the desired height and width of the image.
+ """
+ if size is None:
+ rect = self.get_allocation()
+ width = rect.width
+ height = rect.height
+ else:
+ width, height = size
+ old_alloc = self.get_allocation
+ self.get_allocation = lambda: gtk.gdk.Rectangle(0, 0, width, height)
+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+ ctx = cairo.Context(surface)
+ context = pangocairo.CairoContext(ctx)
+ self.set_size_request(width, height)
+ self.draw(context)
+ surface.write_to_png(filename)
+ if size is not None:
+ self.get_allocation = old_alloc
+
+
+ def set_padding(self, padding):
+ """
+ Set the chart's padding.
+
+ @param padding: the padding in px
+ @type padding: int in [0, 100] (default: 16).
+ """
+ self.set_property("padding", padding)
+ self.queue_draw()
+
+ def get_padding(self):
+ """
+ Returns the chart's padding.
+
+ @return: int in [0, 100].
+ """
+ return self.get_property("padding")
+
+
+class Background(ChartObject):
+ """
+ The background of a chart.
+
+ Properties
+ ==========
+ This class inherits properties from chart_object.ChartObject.
+ Additional properties:
+ - color (the background color, type: gtk.gdk.Color)
+ - gradient (the background gradient, type: a pair of gtk.gdk.Color)
+ - image (path to the background image file, type: string)
+
+ Signals
+ =======
+ The Background class inherits signals from chart_object.ChartObject.
+ """
+
+ __gproperties__ = {"color": (gobject.TYPE_PYOBJECT,
+ "background color",
+ "The color of the backround.",
+ gobject.PARAM_READWRITE),
+ "gradient": (gobject.TYPE_PYOBJECT,
+ "background gradient",
+ "A background gardient. (first_color, second_color)",
+ gobject.PARAM_READWRITE),
+ "image": (gobject.TYPE_STRING,
+ "background image file",
+ "Path to the image file to use as background.",
+ "", gobject.PARAM_READWRITE)}
+
+ def __init__(self):
+ ChartObject.__init__(self)
+ self._color = gtk.gdk.color_parse("#ffffff") #the backgound is filled white by default
+ self._gradient = None
+ self._image = ""
+ self._pixbuf = None
+
+ def do_get_property(self, property):
+ if property.name == "visible":
+ return self._show
+ elif property.name == "antialias":
+ return self._antialias
+ elif property.name == "gradient":
+ return self._gradient
+ elif property.name == "color":
+ return self._color
+ elif property.name == "image":
+ return self._image
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "visible":
+ self._show = value
+ elif property.name == "antialias":
+ self._antialias = value
+ elif property.name == "gradient":
+ self._gradient = value
+ elif property.name == "color":
+ self._color = value
+ elif property.name == "image":
+ self._image = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def _do_draw(self, context, rect):
+ """
+ Do all the drawing stuff.
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ @type rect: gtk.gdk.Rectangle
+ @param rect: A rectangle representing the charts area.
+ """
+ if self._color != None:
+ #set source color
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ elif self._gradient != None:
+ #set source gradient
+ cs = color_gdk_to_cairo(self._gradient[0])
+ ce = color_gdk_to_cairo(self._gradient[1])
+ gradient = cairo.LinearGradient(0, 0, 0, rect.height)
+ gradient.add_color_stop_rgb(0, cs[0], cs[1], cs[2])
+ gradient.add_color_stop_rgb(1, ce[0], ce[1], ce[2])
+ context.set_source(gradient)
+ elif self._pixbuf:
+ context.set_source_pixbuf(self._pixbuf, 0, 0)
+ else:
+ context.set_source_rgb(1, 1, 1) #fallback to white bg
+ #create the background rectangle and fill it:
+ context.rectangle(0, 0, rect.width, rect.height)
+ context.fill()
+
+ def set_color(self, color):
+ """
+ The set_color() method can be used to change the color of the
+ background.
+
+ @type color: gtk.gdk.Color
+ @param color: Set the background to be filles with this color.
+ """
+ self.set_property("color", color)
+ self.set_property("gradient", None)
+ self.set_property("image", "")
+ self.emit("appearance_changed")
+
+ def get_color(self):
+ """
+ Returns the background's color.
+
+ @return: gtk.gdk.Color.
+ """
+ return self.get_property("color")
+
+ def set_gradient(self, color_start, color_end):
+ """
+ Use set_gradient() to define a vertical gradient as the background.
+
+ @type color_start: gtk.gdk.Color
+ @param color_start: The starting (top) color of the gradient.
+ @type color_end: gtk.gdk.Color
+ @param color_end: The ending (bottom) color of the gradient.
+ """
+ self.set_property("color", None)
+ self.set_property("gradient", (color_start, color_end))
+ self.set_property("image", "")
+ self.emit("appearance_changed")
+
+ def get_gradient(self):
+ """
+ Returns the gradient of the background or None.
+
+ @return: A (gtk.gdk.Color, gtk.gdk.Color) tuple or None.
+ """
+ return self.get_property("gradient")
+
+ def set_image(self, filename):
+ """
+ The set_image() method sets the background to be filled with an
+ image.
+
+ @type filename: string
+ @param filename: Path to the file you want to use as background
+ image. If the file does not exists, the background is set to white.
+ """
+ try:
+ self._pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
+ except:
+ self._pixbuf = None
+
+ self.set_property("color", None)
+ self.set_property("gradient", None)
+ self.set_property("image", filename)
+ self.emit("appearance_changed")
+
+ def get_image(self):
+ return self.get_property("image")
+
+
+class Title(label.Label):
+ """
+ The title of a chart. The title will be drawn centered at the top of the
+ chart.
+
+ Properties
+ ==========
+ The Title class inherits properties from label.Label.
+
+ Signals
+ =======
+ The Title class inherits signals from label.Label.
+ """
+
+ def __init__(self, text=""):
+ label.Label.__init__(self, (0, 0), text, weight=pango.WEIGHT_BOLD, anchor=label.ANCHOR_TOP_CENTER, fixed=True)
+
+ def _do_draw(self, context, rect, top=-1):
+ if top == -1: top = rect.height / 80
+ self._size = max(8, int(rect.height / 50.0))
+ self._position = rect.width / 2, top
+ self._do_draw_label(context, rect)
+
+
+class Area(ChartObject):
+ """
+ This is a base class for classes that represent areas, e.g. the
+ pie_chart.PieArea class and the bar_chart.Bar class.
+
+ Properties
+ ==========
+ The Area class inherits properties from chart_object.ChartObject.
+ Additional properties:
+ - name (a unique name for the area, type: string, read only)
+ - value (the value of the area, type: float)
+ - color (the area's color, type: gtk.gdk.Color)
+ - label (a label for the area, type: string)
+ - highlighted (set whether the area should be highlighted,
+ type: boolean).
+
+ Signals
+ =======
+ The Area class inherits signals from chart_object.ChartObject.
+ """
+
+ __gproperties__ = {"name": (gobject.TYPE_STRING, "area name",
+ "A unique name for the area.",
+ "", gobject.PARAM_READABLE),
+ "value": (gobject.TYPE_FLOAT,
+ "value",
+ "The value.",
+ 0.0, 9999999999.0, 0.0, gobject.PARAM_READWRITE),
+ "color": (gobject.TYPE_PYOBJECT, "area color",
+ "The color of the area.",
+ gobject.PARAM_READWRITE),
+ "label": (gobject.TYPE_STRING, "area label",
+ "The label for the area.", "",
+ gobject.PARAM_READWRITE),
+ "highlighted": (gobject.TYPE_BOOLEAN, "area is higlighted",
+ "Set whether the area should be higlighted.",
+ False, gobject.PARAM_READWRITE)}
+
+ def __init__(self, name, value, title=""):
+ ChartObject.__init__(self)
+ self._name = name
+ self._value = value
+ self._label = title
+ self._color = COLOR_AUTO
+ self._highlighted = False
+
+ def do_get_property(self, property):
+ if property.name == "visible":
+ return self._show
+ elif property.name == "antialias":
+ return self._antialias
+ elif property.name == "name":
+ return self._name
+ elif property.name == "value":
+ return self._value
+ elif property.name == "color":
+ return self._color
+ elif property.name == "label":
+ return self._label
+ elif property.name == "highlighted":
+ return self._highlighted
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "visible":
+ self._show = value
+ elif property.name == "antialias":
+ self._antialias = value
+ elif property.name == "value":
+ self._value = value
+ elif property.name == "color":
+ self._color = value
+ elif property.name == "label":
+ self._label = value
+ elif property.name == "highlighted":
+ self._highlighted = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def set_value(self, value):
+ """
+ Set the value of the area.
+
+ @type value: float.
+ """
+ self.set_property("value", value)
+ self.emit("appearance_changed")
+
+ def get_value(self):
+ """
+ Returns the current value of the area.
+
+ @return: float.
+ """
+ return self.get_property("value")
+
+ def set_color(self, color):
+ """
+ Set the color of the area.
+
+ @type color: gtk.gdk.Color.
+ """
+ self.set_property("color", color)
+ self.emit("appearance_changed")
+
+ def get_color(self):
+ """
+ Returns the current color of the area or COLOR_AUTO.
+
+ @return: gtk.gdk.Color or COLOR_AUTO.
+ """
+ return self.get_property("color")
+
+ def set_label(self, label):
+ """
+ Set the label for the area.
+
+ @param label: the new label
+ @type label: string.
+ """
+ self.set_property("label", label)
+ self.emit("appearance_changed")
+
+ def get_label(self):
+ """
+ Returns the current label of the area.
+
+ @return: string.
+ """
+ return self.get_property("label")
+
+ def set_highlighted(self, highlighted):
+ """
+ Set whether the area should be highlighted.
+
+ @type highlighted: boolean.
+ """
+ self.set_property("highlighted", highlighted)
+ self.emit("appearance_changed")
+
+ def get_highlighted(self):
+ """
+ Returns True if the area is currently highlighted.
+
+ @return: boolean.
+ """
+ return self.get_property("highlighted")
diff --git a/pygtk_chart/chart.pyc b/pygtk_chart/chart.pyc
new file mode 100644
index 0000000..a098bcf
--- /dev/null
+++ b/pygtk_chart/chart.pyc
Binary files differ
diff --git a/pygtk_chart/chart_object.py b/pygtk_chart/chart_object.py
new file mode 100644
index 0000000..8e4cd7e
--- /dev/null
+++ b/pygtk_chart/chart_object.py
@@ -0,0 +1,155 @@
+#!/usr/bin/env python
+#
+# chart_object.py
+#
+# Copyright 2009 Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+"""
+This module contains the ChartObject class.
+
+Author: Sven Festersen (sven@sven-festersen.de)
+"""
+import cairo
+import gobject
+
+class ChartObject(gobject.GObject):
+ """
+ This is the base class for all things that can be drawn on a chart
+ widget.
+ It emits the signal 'appearance-changed' when it needs to be
+ redrawn.
+
+ Properties
+ ==========
+ ChartObject inherits properties from gobject.GObject.
+ Additional properties:
+ - visible (sets whether the object should be visible,
+ type: boolean)
+ - antialias (sets whether the object should be antialiased,
+ type: boolean).
+
+ Signals
+ =======
+ ChartObject inherits signals from gobject.GObject,
+ Additional signals:
+ - appearance-changed (emitted if the object needs to be redrawn).
+ """
+
+ __gsignals__ = {"appearance-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])}
+
+
+ __gproperties__ = {"visible": (gobject.TYPE_BOOLEAN,
+ "visibilty of the object",
+ "Set whether to draw the object or not.",
+ True, gobject.PARAM_READWRITE),
+ "antialias": (gobject.TYPE_BOOLEAN,
+ "use antialiasing",
+ "Set whether to use antialiasing when drawing the object.",
+ True, gobject.PARAM_READWRITE)}
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+ self._show = True
+ self._antialias = True
+
+ def do_get_property(self, property):
+ if property.name == "visible":
+ return self._show
+ elif property.name == "antialias":
+ return self._antialias
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "visible":
+ self._show = value
+ elif property.name == "antialias":
+ self._antialias = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def _do_draw(self, context, rect):
+ """
+ A derived class should override this method. The drawing stuff
+ should happen here.
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ @type rect: gtk.gdk.Rectangle
+ @param rect: A rectangle representing the charts area.
+ """
+ pass
+
+ def draw(self, context, rect, *args):
+ """
+ This method is called by the parent Chart instance. It
+ calls _do_draw.
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ @type rect: gtk.gdk.Rectangle
+ @param rect: A rectangle representing the charts area.
+ """
+ res = None
+ if self._show:
+ if not self._antialias:
+ context.set_antialias(cairo.ANTIALIAS_NONE)
+ res = self._do_draw(context, rect, *args)
+ context.set_antialias(cairo.ANTIALIAS_DEFAULT)
+ return res
+
+ def set_antialias(self, antialias):
+ """
+ This method sets the antialiasing mode of the ChartObject. Antialiasing
+ is enabled by default.
+
+ @type antialias: boolean
+ @param antialias: If False, antialiasing is disabled for this
+ ChartObject.
+ """
+ self.set_property("antialias", antialias)
+ self.emit("appearance_changed")
+
+ def get_antialias(self):
+ """
+ Returns True if antialiasing is enabled for the object.
+
+ @return: boolean.
+ """
+ return self.get_property("antialias")
+
+ def set_visible(self, visible):
+ """
+ Use this method to set whether the ChartObject should be visible or
+ not.
+
+ @type visible: boolean
+ @param visible: If False, the PlotObject won't be drawn.
+ """
+ self.set_property("visible", visible)
+ self.emit("appearance_changed")
+
+ def get_visible(self):
+ """
+ Returns True if the object is visble.
+
+ @return: boolean.
+ """
+ return self.get_property("visible")
+
+
+gobject.type_register(ChartObject)
diff --git a/pygtk_chart/chart_object.pyc b/pygtk_chart/chart_object.pyc
new file mode 100644
index 0000000..df4f462
--- /dev/null
+++ b/pygtk_chart/chart_object.pyc
Binary files differ
diff --git a/pygtk_chart/data/tango.color b/pygtk_chart/data/tango.color
new file mode 100644
index 0000000..6231528
--- /dev/null
+++ b/pygtk_chart/data/tango.color
@@ -0,0 +1,7 @@
+#cc0000
+#3465a4
+#73d216
+#f57900
+#75507b
+#c17d11
+#edd400
diff --git a/pygtk_chart/label.py b/pygtk_chart/label.py
new file mode 100644
index 0000000..75b523c
--- /dev/null
+++ b/pygtk_chart/label.py
@@ -0,0 +1,744 @@
+#!/usr/bin/env python
+#
+# text.py
+#
+# Copyright 2009 Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+"""
+Contains the Label class.
+
+Author: Sven Festersen (sven@sven-festersen.de)
+"""
+import cairo
+import gobject
+import gtk
+import math
+import pango
+import pygtk
+
+from pygtk_chart import basics
+from pygtk_chart.chart_object import ChartObject
+
+
+ANCHOR_BOTTOM_LEFT = 0
+ANCHOR_TOP_LEFT = 1
+ANCHOR_TOP_RIGHT = 2
+ANCHOR_BOTTOM_RIGHT = 4
+ANCHOR_CENTER = 5
+ANCHOR_TOP_CENTER = 6
+ANCHOR_BOTTOM_CENTER = 7
+ANCHOR_LEFT_CENTER = 8
+ANCHOR_RIGHT_CENTER = 9
+
+UNDERLINE_NONE = pango.UNDERLINE_NONE
+UNDERLINE_SINGLE = pango.UNDERLINE_SINGLE
+UNDERLINE_DOUBLE = pango.UNDERLINE_DOUBLE
+UNDERLINE_LOW = pango.UNDERLINE_LOW
+
+STYLE_NORMAL = pango.STYLE_NORMAL
+STYLE_OBLIQUE = pango.STYLE_OBLIQUE
+STYLE_ITALIC = pango.STYLE_ITALIC
+
+WEIGHT_ULTRALIGHT = pango.WEIGHT_ULTRALIGHT
+WEIGHT_LIGHT = pango.WEIGHT_LIGHT
+WEIGHT_NORMAL = pango.WEIGHT_NORMAL
+WEIGHT_BOLD = pango.WEIGHT_BOLD
+WEIGHT_ULTRABOLD = pango.WEIGHT_ULTRABOLD
+WEIGHT_HEAVY = pango.WEIGHT_HEAVY
+
+
+DRAWING_INITIALIZED = False
+REGISTERED_LABELS = []
+
+
+def begin_drawing():
+ global DRAWING_INITIALIZED
+ DRAWING_INITIALIZED = True
+
+def finish_drawing():
+ global REGISTERED_LABELS
+ global DRAWING_INITIALIZED
+ REGISTERED_LABELS = []
+ DRAWING_INITIALIZED = False
+
+def register_label(label):
+ if DRAWING_INITIALIZED:
+ REGISTERED_LABELS.append(label)
+
+def get_registered_labels():
+ if DRAWING_INITIALIZED:
+ return REGISTERED_LABELS
+ return []
+
+
+class Label(ChartObject):
+ """
+ This class is used for drawing all the text on the chart widgets.
+ It uses the pango layout engine.
+
+ Properties
+ ==========
+ The Label class inherits properties from chart_object.ChartObject.
+ Additional properties:
+ - color (the label's color, type: gtk.gdk.Color)
+ - text (text to display, type: string)
+ - position (the label's position, type: pair of float)
+ - anchor (the anchor that should be used to position the label,
+ type: an anchor constant)
+ - underline (sets the type of underline, type; an underline
+ constant)
+ - max-width (the maximum width of the label in px, type: int)
+ - rotation (angle of rotation in degrees, type: int)
+ - size (the size of the label's text in px, type: int)
+ - slant (the font slant, type: a slant style constant)
+ - weight (the font weight, type: a font weight constant)
+ - fixed (sets whether the position of the label may be changed
+ dynamicly or not, type: boolean)
+ - wrap (sets whether the label's text should be wrapped if it's
+ longer than max-width, type: boolean).
+
+ Signals
+ =======
+ The Label class inherits signals from chart_object.ChartObject.
+ """
+
+ __gproperties__ = {"color": (gobject.TYPE_PYOBJECT,
+ "label color",
+ "The color of the label (a gtk.gdkColor)",
+ gobject.PARAM_READWRITE),
+ "text": (gobject.TYPE_STRING,
+ "label text",
+ "The text to show on the label.",
+ "", gobject.PARAM_READWRITE),
+ "position": (gobject.TYPE_PYOBJECT,
+ "label position",
+ "A pair of x,y coordinates.",
+ gobject.PARAM_READWRITE),
+ "anchor": (gobject.TYPE_INT, "label anchor",
+ "The anchor of the label.", 0, 9, 0,
+ gobject.PARAM_READWRITE),
+ "underline": (gobject.TYPE_PYOBJECT,
+ "underline text",
+ "Set whether to underline the text.",
+ gobject.PARAM_READWRITE),
+ "max-width": (gobject.TYPE_INT, "maximum width",
+ "The maximum width of the label.",
+ 1, 99999, 99999,
+ gobject.PARAM_READWRITE),
+ "rotation": (gobject.TYPE_INT, "rotation of the label",
+ "The angle that the label should be rotated by in degrees.",
+ 0, 360, 0, gobject.PARAM_READWRITE),
+ "size": (gobject.TYPE_INT, "text size",
+ "The size of the text.", 0, 1000, 8,
+ gobject.PARAM_READWRITE),
+ "slant": (gobject.TYPE_PYOBJECT, "font slant",
+ "The font slant style.",
+ gobject.PARAM_READWRITE),
+ "weight": (gobject.TYPE_PYOBJECT, "font weight",
+ "The font weight.", gobject.PARAM_READWRITE),
+ "fixed": (gobject.TYPE_BOOLEAN, "fixed",
+ "Set whether the position of the label should be forced.",
+ False, gobject.PARAM_READWRITE),
+ "wrap": (gobject.TYPE_BOOLEAN, "wrap text",
+ "Set whether text should be wrapped.",
+ False, gobject.PARAM_READWRITE)}
+
+ def __init__(self, position, text, size=None,
+ slant=pango.STYLE_NORMAL,
+ weight=pango.WEIGHT_NORMAL,
+ underline=pango.UNDERLINE_NONE,
+ anchor=ANCHOR_BOTTOM_LEFT, max_width=99999,
+ fixed=False):
+ ChartObject.__init__(self)
+ self._position = position
+ self._text = text
+ self._size = size
+ self._slant = slant
+ self._weight = weight
+ self._underline = underline
+ self._anchor = anchor
+ self._rotation = 0
+ self._color = gtk.gdk.Color()
+ self._max_width = max_width
+ self._fixed = fixed
+ self._wrap = True
+
+ self._real_dimensions = (0, 0)
+ self._real_position = (0, 0)
+ self._line_count = 1
+
+ self._context = None
+ self._layout = None
+
+ def do_get_property(self, property):
+ if property.name == "visible":
+ return self._show
+ elif property.name == "antialias":
+ return self._antialias
+ elif property.name == "text":
+ return self._text
+ elif property.name == "color":
+ return self._color
+ elif property.name == "position":
+ return self._position
+ elif property.name == "anchor":
+ return self._anchor
+ elif property.name == "underline":
+ return self._underline
+ elif property.name == "max-width":
+ return self._max_width
+ elif property.name == "rotation":
+ return self._rotation
+ elif property.name == "size":
+ return self._size
+ elif property.name == "slant":
+ return self._slant
+ elif property.name == "weight":
+ return self._weight
+ elif property.name == "fixed":
+ return self._fixed
+ elif property.name == "wrap":
+ return self._wrap
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "visible":
+ self._show = value
+ elif property.name == "antialias":
+ self._antialias = value
+ elif property.name == "text":
+ self._text = value
+ elif property.name == "color":
+ self._color = value
+ elif property.name == "position":
+ self._position = value
+ elif property.name == "anchor":
+ self._anchor = value
+ elif property.name == "underline":
+ self._underline = value
+ elif property.name == "max-width":
+ self._max_width = value
+ elif property.name == "rotation":
+ self._rotation = value
+ elif property.name == "size":
+ self._size = value
+ elif property.name == "slant":
+ self._slant = value
+ elif property.name == "weight":
+ self._weight = value
+ elif property.name == "fixed":
+ self._fixed = value
+ elif property.name == "wrap":
+ self._wrap = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def _do_draw(self, context, rect):
+ self._do_draw_label(context, rect)
+
+ def _do_draw_label(self, context, rect):
+ angle = 2 * math.pi * self._rotation / 360.0
+
+ if self._context == None:
+ label = gtk.Label()
+ self._context = label.create_pango_context()
+ pango_context = self._context
+
+ attrs = pango.AttrList()
+ attrs.insert(pango.AttrWeight(self._weight, 0, len(self._text)))
+ attrs.insert(pango.AttrStyle(self._slant, 0, len(self._text)))
+ attrs.insert(pango.AttrUnderline(self._underline, 0,
+ len(self._text)))
+ if self._size != None:
+ attrs.insert(pango.AttrSize(1000 * self._size, 0,
+ len(self._text)))
+
+ if self._layout == None:
+ self._layout = pango.Layout(pango_context)
+ layout = self._layout
+ layout.set_text(self._text)
+ layout.set_attributes(attrs)
+
+ #find out where to draw the layout and calculate the maximum width
+ width = rect.width
+ if self._anchor in [ANCHOR_BOTTOM_LEFT, ANCHOR_TOP_LEFT,
+ ANCHOR_LEFT_CENTER]:
+ width = rect.width - self._position[0]
+ elif self._anchor in [ANCHOR_BOTTOM_RIGHT, ANCHOR_TOP_RIGHT,
+ ANCHOR_RIGHT_CENTER]:
+ width = self._position[0]
+
+ text_width, text_height = layout.get_pixel_size()
+ width = width * math.cos(angle)
+ width = min(width, self._max_width)
+
+ if self._wrap:
+ layout.set_wrap(pango.WRAP_WORD_CHAR)
+ layout.set_width(int(1000 * width))
+
+ x, y = get_text_pos(layout, self._position, self._anchor, angle)
+
+ if not self._fixed:
+ #Find already drawn labels that would intersect with the current one
+ #and adjust position to avoid intersection.
+ text_width, text_height = layout.get_pixel_size()
+ real_width = abs(text_width * math.cos(angle)) + abs(text_height * math.sin(angle))
+ real_height = abs(text_height * math.cos(angle)) + abs(text_width * math.sin(angle))
+
+ other_labels = get_registered_labels()
+ this_rect = gtk.gdk.Rectangle(int(x), int(y), int(real_width), int(real_height))
+ for label in other_labels:
+ label_rect = label.get_allocation()
+ intersection = this_rect.intersect(label_rect)
+ if intersection.width == 0 and intersection.height == 0:
+ continue
+
+ y_diff = 0
+ if label_rect.y <= y and label_rect.y + label_rect.height >= y:
+ y_diff = y - label_rect.y + label_rect.height
+ elif label_rect.y > y and label_rect.y < y + real_height:
+ y_diff = label_rect.y - real_height - y
+ y += y_diff
+
+ #draw layout
+ context.move_to(x, y)
+ context.rotate(angle)
+ context.set_source_rgb(*basics.color_gdk_to_cairo(self._color))
+ context.show_layout(layout)
+ context.rotate(-angle)
+ context.stroke()
+
+ #calculate the real dimensions
+ text_width, text_height = layout.get_pixel_size()
+ real_width = abs(text_width * math.cos(angle)) + abs(text_height * math.sin(angle))
+ real_height = abs(text_height * math.cos(angle)) + abs(text_width * math.sin(angle))
+ self._real_dimensions = real_width, real_height
+ self._real_position = x, y
+ self._line_count = layout.get_line_count()
+
+ register_label(self)
+
+ def get_calculated_dimensions(self, context, rect):
+ angle = 2 * math.pi * self._rotation / 360.0
+
+ if self._context == None:
+ label = gtk.Label()
+ self._context = label.create_pango_context()
+ pango_context = self._context
+
+ attrs = pango.AttrList()
+ attrs.insert(pango.AttrWeight(self._weight, 0, len(self._text)))
+ attrs.insert(pango.AttrStyle(self._slant, 0, len(self._text)))
+ attrs.insert(pango.AttrUnderline(self._underline, 0,
+ len(self._text)))
+ if self._size != None:
+ attrs.insert(pango.AttrSize(1000 * self._size, 0,
+ len(self._text)))
+
+ if self._layout == None:
+ self._layout = pango.Layout(pango_context)
+ layout = self._layout
+
+ layout.set_text(self._text)
+ layout.set_attributes(attrs)
+
+ #find out where to draw the layout and calculate the maximum width
+ width = rect.width
+ if self._anchor in [ANCHOR_BOTTOM_LEFT, ANCHOR_TOP_LEFT,
+ ANCHOR_LEFT_CENTER]:
+ width = rect.width - self._position[0]
+ elif self._anchor in [ANCHOR_BOTTOM_RIGHT, ANCHOR_TOP_RIGHT,
+ ANCHOR_RIGHT_CENTER]:
+ width = self._position[0]
+
+ text_width, text_height = layout.get_pixel_size()
+ width = width * math.cos(angle)
+ width = min(width, self._max_width)
+
+ if self._wrap:
+ layout.set_wrap(pango.WRAP_WORD_CHAR)
+ layout.set_width(int(1000 * width))
+
+ x, y = get_text_pos(layout, self._position, self._anchor, angle)
+
+ if not self._fixed:
+ #Find already drawn labels that would intersect with the current one
+ #and adjust position to avoid intersection.
+ text_width, text_height = layout.get_pixel_size()
+ real_width = abs(text_width * math.cos(angle)) + abs(text_height * math.sin(angle))
+ real_height = abs(text_height * math.cos(angle)) + abs(text_width * math.sin(angle))
+
+ other_labels = get_registered_labels()
+ this_rect = gtk.gdk.Rectangle(int(x), int(y), int(real_width), int(real_height))
+ for label in other_labels:
+ label_rect = label.get_allocation()
+ intersection = this_rect.intersect(label_rect)
+ if intersection.width == 0 and intersection.height == 0:
+ continue
+
+ y_diff = 0
+ if label_rect.y <= y and label_rect.y + label_rect.height >= y:
+ y_diff = y - label_rect.y + label_rect.height
+ elif label_rect.y > y and label_rect.y < y + real_height:
+ y_diff = label_rect.y - real_height - y
+ y += y_diff
+
+ #calculate the dimensions
+ text_width, text_height = layout.get_pixel_size()
+ real_width = abs(text_width * math.cos(angle)) + abs(text_height * math.sin(angle))
+ real_height = abs(text_height * math.cos(angle)) + abs(text_width * math.sin(angle))
+ return real_width, real_height
+
+ def set_text(self, text):
+ """
+ Use this method to set the text that should be displayed by
+ the label.
+
+ @param text: the text to display.
+ @type text: string
+ """
+ self.set_property("text", text)
+ self.emit("appearance_changed")
+
+ def get_text(self):
+ """
+ Returns the text currently displayed.
+
+ @return: string.
+ """
+ return self.get_property("text")
+
+ def set_color(self, color):
+ """
+ Set the color of the label. color has to be a gtk.gdk.Color.
+
+ @param color: the color of the label
+ @type color: gtk.gdk.Color.
+ """
+ self.set_property("color", color)
+ self.emit("appearance_changed")
+
+ def get_color(self):
+ """
+ Returns the current color of the label.
+
+ @return: gtk.gdk.Color.
+ """
+ return self.get_property("color")
+
+ def set_position(self, pos):
+ """
+ Set the position of the label. pos has to be a x,y pair of
+ absolute pixel coordinates on the widget.
+ The position is not the actual position but the position of the
+ Label's anchor point (see L{set_anchor} for details).
+
+ @param pos: new position of the label
+ @type pos: pair of (x, y).
+ """
+ self.set_property("position", pos)
+ self.emit("appearance_changed")
+
+ def get_position(self):
+ """
+ Returns the current position of the label.
+
+ @return: pair of (x, y).
+ """
+ return self.get_property("position")
+
+ def set_anchor(self, anchor):
+ """
+ Set the anchor point of the label. The anchor point is the a
+ point on the label's edge that has the position you set with
+ set_position().
+ anchor has to be one of the following constants:
+
+ - label.ANCHOR_BOTTOM_LEFT
+ - label.ANCHOR_TOP_LEFT
+ - label.ANCHOR_TOP_RIGHT
+ - label.ANCHOR_BOTTOM_RIGHT
+ - label.ANCHOR_CENTER
+ - label.ANCHOR_TOP_CENTER
+ - label.ANCHOR_BOTTOM_CENTER
+ - label.ANCHOR_LEFT_CENTER
+ - label.ANCHOR_RIGHT_CENTER
+
+ The meaning of the constants is illustrated below:::
+
+
+ ANCHOR_TOP_LEFT ANCHOR_TOP_CENTER ANCHOR_TOP_RIGHT
+ * * *
+ #####################
+ ANCHOR_LEFT_CENTER * # * # * ANCHOR_RIGHT_CENTER
+ #####################
+ * * *
+ ANCHOR_BOTTOM_LEFT ANCHOR_BOTTOM_CENTER ANCHOR_BOTTOM_RIGHT
+
+ The point in the center is of course referred to by constant
+ label.ANCHOR_CENTER.
+
+ @param anchor: the anchor point of the label
+ @type anchor: one of the constants described above.
+ """
+
+ self.set_property("anchor", anchor)
+ self.emit("appearance_changed")
+
+ def get_anchor(self):
+ """
+ Returns the current anchor point that's used to position the
+ label. See L{set_anchor} for details.
+
+ @return: one of the anchor constants described in L{set_anchor}.
+ """
+ return self.get_property("anchor")
+
+ def set_underline(self, underline):
+ """
+ Set the underline style of the label. underline has to be one
+ of the following constants:
+
+ - label.UNDERLINE_NONE: do not underline the text
+ - label.UNDERLINE_SINGLE: draw a single underline (the normal
+ underline method)
+ - label.UNDERLINE_DOUBLE: draw a double underline
+ - label.UNDERLINE_LOW; draw a single low underline.
+
+ @param underline: the underline style
+ @type underline: one of the constants above.
+ """
+ self.set_property("underline", underline)
+ self.emit("appearance_changed")
+
+ def get_underline(self):
+ """
+ Returns the current underline style. See L{set_underline} for
+ details.
+
+ @return: an underline constant (see L{set_underline}).
+ """
+ return self.get_property("underline")
+
+ def set_max_width(self, width):
+ """
+ Set the maximum width of the label in pixels.
+
+ @param width: the maximum width
+ @type width: integer.
+ """
+ self.set_property("max-width", width)
+ self.emit("appearance_changed")
+
+ def get_max_width(self):
+ """
+ Returns the maximum width of the label.
+
+ @return: integer.
+ """
+ return self.get_property("max-width")
+
+ def set_rotation(self, angle):
+ """
+ Use this method to set the rotation of the label in degrees.
+
+ @param angle: the rotation angle
+ @type angle: integer in [0, 360].
+ """
+ self.set_property("rotation", angle)
+ self.emit("appearance_changed")
+
+ def get_rotation(self):
+ """
+ Returns the current rotation angle.
+
+ @return: integer in [0, 360].
+ """
+ return self.get_property("rotation")
+
+ def set_size(self, size):
+ """
+ Set the size of the text in pixels.
+
+ @param size: size of the text
+ @type size: integer.
+ """
+ self.set_property("size", size)
+ self.emit("appearance_changed")
+
+ def get_size(self):
+ """
+ Returns the current size of the text in pixels.
+
+ @return: integer.
+ """
+ return self.get_property("size")
+
+ def set_slant(self, slant):
+ """
+ Set the font slant. slat has to be one of the following:
+
+ - label.STYLE_NORMAL
+ - label.STYLE_OBLIQUE
+ - label.STYLE_ITALIC
+
+ @param slant: the font slant style
+ @type slant: one of the constants above.
+ """
+ self.set_property("slant", slant)
+ self.emit("appearance_changed")
+
+ def get_slant(self):
+ """
+ Returns the current font slant style. See L{set_slant} for
+ details.
+
+ @return: a slant style constant.
+ """
+ return self.get_property("slant")
+
+ def set_weight(self, weight):
+ """
+ Set the font weight. weight has to be one of the following:
+
+ - label.WEIGHT_ULTRALIGHT
+ - label.WEIGHT_LIGHT
+ - label.WEIGHT_NORMAL
+ - label.WEIGHT_BOLD
+ - label.WEIGHT_ULTRABOLD
+ - label.WEIGHT_HEAVY
+
+ @param weight: the font weight
+ @type weight: one of the constants above.
+ """
+ self.set_property("weight", weight)
+ self.emit("appearance_changed")
+
+ def get_weight(self):
+ """
+ Returns the current font weight. See L{set_weight} for details.
+
+ @return: a font weight constant.
+ """
+ return self.get_property("weight")
+
+ def set_fixed(self, fixed):
+ """
+ Set whether the position of the label should be forced
+ (fixed=True) or if it should be positioned avoiding intersection
+ with other labels.
+
+ @type fixed: boolean.
+ """
+ self.set_property("fixed", fixed)
+ self.emit("appearance_changed")
+
+ def get_fixed(self):
+ """
+ Returns True if the label's position is forced.
+
+ @return: boolean
+ """
+ return self.get_property("fixed")
+
+ def set_wrap(self, wrap):
+ """
+ Set whether too long text should be wrapped.
+
+ @type wrap: boolean.
+ """
+ self.set_property("wrap", wrap)
+ self.emit("appearance_changed")
+
+ def get_wrap(self):
+ """
+ Returns True if too long text should be wrapped.
+
+ @return: boolean.
+ """
+ return self.get_property("wrap")
+
+ def get_real_dimensions(self):
+ """
+ This method returns a pair (width, height) with the dimensions
+ the label was drawn with. Call this method I{after} drawing
+ the label.
+
+ @return: a (width, height) pair.
+ """
+ return self._real_dimensions
+
+ def get_real_position(self):
+ """
+ Returns the position of the label where it was really drawn.
+
+ @return: a (x, y) pair.
+ """
+ return self._real_position
+
+ def get_allocation(self):
+ """
+ Returns an allocation rectangle.
+
+ @return: gtk.gdk.Rectangle.
+ """
+ x, y = self._real_position
+ w, h = self._real_dimensions
+ return gtk.gdk.Rectangle(int(x), int(y), int(w), int(h))
+
+ def get_line_count(self):
+ """
+ Returns the number of lines.
+
+ @return: int.
+ """
+ return self._line_count
+
+
+def get_text_pos(layout, pos, anchor, angle):
+ """
+ This function calculates the position of bottom left point of the
+ layout respecting the given anchor point.
+
+ @return: (x, y) pair
+ """
+ text_width_n, text_height_n = layout.get_pixel_size()
+ text_width = text_width_n * abs(math.cos(angle)) + text_height_n * abs(math.sin(angle))
+ text_height = text_height_n * abs(math.cos(angle)) + text_width_n * abs(math.sin(angle))
+ height_delta = text_height - text_height_n
+ x, y = pos
+ ref = (0, -text_height)
+ if anchor == ANCHOR_TOP_LEFT:
+ ref = (0, 0)
+ elif anchor == ANCHOR_TOP_RIGHT:
+ ref = (-text_width, height_delta)
+ elif anchor == ANCHOR_BOTTOM_RIGHT:
+ ref = (-text_width, -text_height)
+ elif anchor == ANCHOR_CENTER:
+ ref = (-text_width / 2, -text_height / 2)
+ elif anchor == ANCHOR_TOP_CENTER:
+ ref = (-text_width / 2, 0)
+ elif anchor == ANCHOR_BOTTOM_CENTER:
+ ref = (-text_width / 2, -text_height)
+ elif anchor == ANCHOR_LEFT_CENTER:
+ ref = (0, -text_height / 2)
+ elif anchor == ANCHOR_RIGHT_CENTER:
+ ref = (-text_width, -text_height / 2)
+ x += ref[0]
+ y += ref[1]
+ return x, y
diff --git a/pygtk_chart/label.pyc b/pygtk_chart/label.pyc
new file mode 100644
index 0000000..d0af0d8
--- /dev/null
+++ b/pygtk_chart/label.pyc
Binary files differ
diff --git a/pygtk_chart/multi_bar_chart.py b/pygtk_chart/multi_bar_chart.py
new file mode 100644
index 0000000..09e6a14
--- /dev/null
+++ b/pygtk_chart/multi_bar_chart.py
@@ -0,0 +1,649 @@
+# Copyright 2009 Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+"""
+Contains the MultiBarChart widget.
+
+Author: Sven Festersen (sven@sven-festersen.de)
+"""
+__docformat__ = "epytext"
+import cairo
+import gtk
+import gobject
+import os
+import math
+
+import pygtk_chart
+from pygtk_chart.basics import *
+from pygtk_chart import bar_chart
+from pygtk_chart.chart_object import ChartObject
+from pygtk_chart import chart
+from pygtk_chart import label
+
+MODE_VERTICAL = 0
+MODE_HORIZONTAL = 1
+
+COLOR_AUTO = 0
+COLORS = gdk_color_list_from_file(os.sep.join([os.path.dirname(__file__), "data", "tango.color"]))
+
+
+class Bar(bar_chart.Bar):
+ """
+ This is a special version of the bar_chart.Bar class that draws the
+ bars on a MultiBarChart widget.
+
+ Properties
+ ==========
+ This class inherits properties from bar_chart.Bar.
+
+ Signals
+ =======
+ This class inherits signals from bar_chart.Bar.
+ """
+
+ def __init__(self, name, value, title=""):
+ bar_chart.Bar.__init__(self, name, value, title)
+
+ #drawing methods
+ def _do_draw(self, context, rect, group, bar_count, n, i, m, j, mode, group_padding, bar_padding, maximum_value, group_end, value_label_size, label_size, label_rotation, draw_labels):
+ if mode == MODE_VERTICAL:
+ return self._do_draw_multi_vertical(context, rect, group, bar_count, n, i, m, j, mode, group_padding, bar_padding, maximum_value, group_end, value_label_size, label_size, label_rotation, draw_labels)
+ elif mode == MODE_HORIZONTAL:
+ return self._do_draw_multi_horizontal(context, rect, group, bar_count, n, i, m, j, mode, group_padding, bar_padding, maximum_value, group_end, value_label_size, label_size, label_rotation, draw_labels)
+
+ def _do_draw_multi_vertical(self, context, rect, group, bar_count, n, i, m, j, mode, group_padding, bar_padding, maximum_value, group_end, value_label_size, label_size, label_rotation, draw_labels):
+ bar_width = (rect.width - (bar_count - n) * bar_padding - (n - 1) * group_padding) / bar_count
+ bar_height = (rect.height - value_label_size - label_size) * self._value / maximum_value
+ bar_x = group_end + j * (bar_width + bar_padding)
+ bar_y = rect.y + rect.height - bar_height - label_size
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ bar_chart.draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ chart.add_sensitive_area(chart.AREA_RECTANGLE, (bar_x, bar_y, bar_width, bar_height), (group, self))
+
+ if self._highlighted:
+ context.set_source_rgba(1, 1, 1, 0.1)
+ bar_chart.draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ if draw_labels:
+ #draw the value label
+ self._value_label_object.set_max_width(bar_width)
+ self._value_label_object.set_text(str(self._value))
+ self._value_label_object.set_color(self._color)
+ self._value_label_object.set_position((bar_x + bar_width / 2, bar_y - 3))
+ self._value_label_object.set_anchor(label.ANCHOR_BOTTOM_CENTER)
+ self._value_label_object.draw(context, rect)
+ context.fill()
+
+ #draw label
+ self._label_object.set_rotation(label_rotation)
+ self._label_object.set_wrap(False)
+ self._label_object.set_color(self._color)
+ self._label_object.set_fixed(True)
+ self._label_object.set_max_width(3 * bar_width)
+ self._label_object.set_text(self._label)
+ self._label_object.set_position((bar_x + bar_width / 2 + 5, bar_y + bar_height + 8))
+ self._label_object.set_anchor(label.ANCHOR_TOP_RIGHT)
+ self._label_object.draw(context, rect)
+ context.fill()
+
+ return bar_x + bar_width
+
+ def _do_draw_multi_horizontal(self, context, rect, group, bar_count, n, i, m, j, mode, group_padding, bar_padding, maximum_value, group_end, value_label_size, label_size, label_rotation, draw_labels):
+ bar_height = (rect.height - (bar_count - n) * bar_padding - (n - 1) * group_padding) / bar_count
+ bar_width = (rect.width - value_label_size - label_size) * self._value / maximum_value
+ bar_x = rect.x + label_size
+ bar_y = group_end + j * (bar_height + bar_padding)
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ bar_chart.draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ chart.add_sensitive_area(chart.AREA_RECTANGLE, (bar_x, bar_y, bar_width, bar_height), (group, self))
+
+ if self._highlighted:
+ context.set_source_rgba(1, 1, 1, 0.1)
+ bar_chart.draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ if draw_labels:
+ #draw the value label
+ self._value_label_object.set_text(str(self._value))
+ self._value_label_object.set_wrap(False)
+ self._value_label_object.set_color(self._color)
+ self._value_label_object.set_position((bar_x + bar_width + 3, bar_y + bar_height / 2))
+ self._value_label_object.set_anchor(label.ANCHOR_LEFT_CENTER)
+ self._value_label_object.draw(context, rect)
+ context.fill()
+
+ #draw label
+ self._label_object.set_rotation(0)
+ self._label_object.set_wrap(False)
+ self._label_object.set_color(self._color)
+ self._label_object.set_fixed(True)
+ self._label_object.set_max_width(0.25 * rect.width)
+ self._label_object.set_text(self._label)
+ self._label_object.set_position((bar_x - 3, bar_y + bar_height / 2))
+ self._label_object.set_anchor(label.ANCHOR_RIGHT_CENTER)
+ self._label_object.draw(context, rect)
+ context.fill()
+
+ return bar_y + bar_height
+
+ def get_value_label_size(self, context, rect, mode, bar_count, n, group_padding, bar_padding):
+ if mode == MODE_VERTICAL:
+ bar_width = (rect.width - (bar_count - n) * bar_padding - (n - 1) * group_padding) / bar_count
+ self._value_label_object.set_max_width(bar_width)
+ self._value_label_object.set_text(str(self._value))
+ return self._value_label_object.get_calculated_dimensions(context, rect)[1]
+ elif mode == MODE_HORIZONTAL:
+ self._value_label_object.set_wrap(False)
+ self._value_label_object.set_fixed(True)
+ self._value_label_object.set_text(str(self._value))
+ return self._value_label_object.get_calculated_dimensions(context, rect)[0]
+
+ def get_label_size(self, context, rect, mode, bar_count, n, group_padding, bar_padding, label_rotation):
+ if mode == MODE_VERTICAL:
+ bar_width = (rect.width - (bar_count - n) * bar_padding - (n - 1) * group_padding) / bar_count
+ self._label_object.set_rotation(label_rotation)
+ self._label_object.set_wrap(False)
+ self._label_object.set_fixed(True)
+ self._label_object.set_max_width(3 * bar_width)
+ self._label_object.set_text(self._label)
+ return self._label_object.get_calculated_dimensions(context, rect)[1]
+ elif mode == MODE_HORIZONTAL:
+ self._label_object.set_max_width(0.25 * rect.width)
+ self._label_object.set_text(self._label)
+ return self._label_object.get_calculated_dimensions(context, rect)[0]
+
+
+
+class BarGroup(ChartObject):
+ """
+ This class represents a group of bars on the MultiBarChart widget.
+
+ Properties
+ ==========
+ This class has the following properties:
+ - name (a unique identifier for the group, type: string)
+ - title (a title for the group, type: string)
+ - bar-padding (the space between two bars of the group in px,
+ type: int in [0, 100])
+ - bars (a list of the bars in the group, read only)
+ - maximum-value (the maximum value of the bars in the group, read
+ only)
+ - bar-count (the number of bars in the group, read only).
+
+ Signals
+ =======
+ The BarGroup class inherits signals from chart_object.ChartObject.
+ """
+
+ __gproperties__ = {"name": (gobject.TYPE_STRING, "group name",
+ "A unique identifier for this group.",
+ "", gobject.PARAM_READABLE),
+ "title": (gobject.TYPE_STRING, "group title",
+ "The group's title.", "",
+ gobject.PARAM_READWRITE),
+ "bar-padding": (gobject.TYPE_INT, "bar padding",
+ "The space between bars in this group.",
+ 0, 100, 2, gobject.PARAM_READWRITE),
+ "bars": (gobject.TYPE_PYOBJECT, "bars in the group",
+ "A list of bars in this group.",
+ gobject.PARAM_READABLE),
+ "maximum-value": (gobject.TYPE_FLOAT, "max value",
+ "The maximum value of the bars in this group.",
+ 0, 9999999, 0, gobject.PARAM_READABLE),
+ "bar-count": (gobject.TYPE_INT, "bar count",
+ "The number of bars in this group.",
+ 0, 100, 0, gobject.PARAM_READWRITE)}
+
+ def __init__(self, name, title=""):
+ ChartObject.__init__(self)
+ #private properties:
+ self._group_label_object = label.Label((0, 0), title)
+ #gobject properties:
+ self._bars = []
+ self._name = name
+ self._title = title
+ self._bar_padding = 2
+
+ #gobject set_* and get_* methods
+ def do_get_property(self, property):
+ if property.name == "visible":
+ return self._show
+ elif property.name == "antialias":
+ return self._antialias
+ elif property.name == "name":
+ return self._name
+ elif property.name == "title":
+ return self._title
+ elif property.name == "bar-padding":
+ return self._bar_padding
+ elif property.name == "bars":
+ return self._bars
+ elif property.name == "maximum-value":
+ return max(bar.get_value() for bar in self._bars)
+ elif property.name == "bar-count":
+ return len(self._bars)
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "visible":
+ self._show = value
+ elif property.name == "antialias":
+ self._antialias = value
+ elif property.name == "name":
+ self._name = value
+ elif property.name == "title":
+ self._title = value
+ elif property.name == "bar-padding":
+ self._bar_padding = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def get_bar_count(self):
+ """
+ Returns the number of bars in this group.
+
+ @return: int in [0, 100].
+ """
+ return self.get_property("bar-count")
+
+ def get_maximum_value(self):
+ """
+ Returns the maximum value of the bars in this group.
+
+ @return: float.
+ """
+ return self.get_property("maximum-value")
+
+ def get_bars(self):
+ """
+ Returns a list of the bars in this group.
+
+ @return: list of multi_bar_chart.Bar.
+ """
+ return self.get_property("bars")
+
+ def get_name(self):
+ """
+ Returns the name (a unique identifier) of this group.
+
+ @return: string.
+ """
+ return self.get_property("name")
+
+ def set_title(self, title):
+ """
+ Set the title of the group.
+
+ @param title: the new title
+ @type title: string.
+ """
+ self.set_property("title", title)
+ self.emit("appearance_changed")
+
+ def get_title(self):
+ """
+ Returns the title of the group.
+
+ @return: string.
+ """
+ return self.get_property("title")
+
+ def get_label(self):
+ """
+ Alias for get_title.
+
+ @return: string.
+ """
+ return self.get_title()
+
+ def set_bar_padding(self, padding):
+ """
+ Set the distance between two bars in this group (in px).
+
+ @param padding: the padding in px
+ @type padding: int in [0, 100].
+ """
+ self.set_property("bar-padding", padding)
+ self.emit("appearance_changed")
+
+ def get_bar_padding(self):
+ """
+ Returns the distance of two bars in the group (in px).
+
+ @return: int in [0, 100].
+ """
+ return self.get_property("bar-padding")
+
+ #drawing methods
+ def _do_draw(self, context, rect, bar_count, n, i, mode, group_padding, maximum_value, group_end, value_label_size, label_size, label_rotation, draw_labels, rotate_label_horizontal):
+ end = group_end
+ for j, bar in enumerate(self._bars):
+ end = bar.draw(context, rect, self, bar_count, n, i, len(self._bars), j, mode, group_padding, self._bar_padding, maximum_value, group_end, value_label_size, label_size, label_rotation, draw_labels)
+
+ if draw_labels and mode == MODE_VERTICAL:
+ context.set_source_rgb(0, 0, 0)
+ group_width = end - group_end
+ self._group_label_object.set_text(self._title)
+ self._group_label_object.set_fixed(True)
+ self._group_label_object.set_max_width(group_width)
+ self._group_label_object.set_position((group_end + group_width / 2, rect.y + rect.height))
+ self._group_label_object.set_anchor(label.ANCHOR_BOTTOM_CENTER)
+ self._group_label_object.draw(context, rect)
+ context.fill()
+ elif draw_labels and mode == MODE_HORIZONTAL:
+ context.set_source_rgb(0, 0, 0)
+ group_height = end - group_end
+ if rotate_label_horizontal:
+ self._group_label_object.set_rotation(90)
+ offset = self.get_group_label_size(context, rect, mode, rotate_label_horizontal) #fixes postioning bug
+ else:
+ self._group_label_object.set_rotation(0)
+ offset = 0
+ self._group_label_object.set_text(self._title)
+ self._group_label_object.set_wrap(False)
+ self._group_label_object.set_fixed(True)
+ self._group_label_object.set_position((rect.x + offset, group_end + group_height / 2))
+ self._group_label_object.set_anchor(label.ANCHOR_LEFT_CENTER)
+ self._group_label_object.draw(context, rect)
+ context.fill()
+
+ return end + group_padding
+
+ #other methods
+ def add_bar(self, bar):
+ """
+ Add a bar to the group.
+
+ @param bar: the bar to add
+ @type bar: multi_bar_chart.Bar.
+ """
+ if bar.get_color() == COLOR_AUTO:
+ bar.set_color(COLORS[len(self._bars) % len(COLORS)])
+ self._bars.append(bar)
+ self.emit("appearance_changed")
+
+ def get_value_label_size(self, context, rect, mode, bar_count, n, group_padding, bar_padding):
+ value_label_size = 0
+ for bar in self._bars:
+ value_label_size = max(value_label_size, bar.get_value_label_size(context, rect, mode, bar_count, n, group_padding, bar_padding))
+ return value_label_size
+
+ def get_label_size(self, context, rect, mode, bar_count, n, group_padding, bar_padding, label_rotation):
+ label_size = 0
+ for bar in self._bars:
+ label_size = max(label_size, bar.get_label_size(context, rect, mode, bar_count, n, group_padding, bar_padding, label_rotation))
+ return label_size
+
+ def get_group_label_size(self, context, rect, mode, rotate_label_horizontal):
+ self._group_label_object.set_text(self._title)
+ if mode == MODE_VERTICAL:
+ return self._group_label_object.get_calculated_dimensions(context, rect)[1]
+ elif mode == MODE_HORIZONTAL:
+ if rotate_label_horizontal:
+ self._group_label_object.set_rotation(90)
+ else:
+ self._group_label_object.set_rotation(0)
+ self._group_label_object.set_wrap(False)
+ return self._group_label_object.get_calculated_dimensions(context, rect)[0]
+
+
+class MultiBarChart(bar_chart.BarChart):
+ """
+ The MultiBarChart widget displays groups of bars.
+ Usage: create multi_bar_chart.BarGroups and
+ add multi_bar_chart.Bars. The add the bar groups to MultiBarChart.
+
+ Properties
+ ==========
+ The MultiBarChart class inherits properties from bar_chart.BarChart
+ (except bar-padding). Additional properties:
+ - group-padding (the space between two bar groups in px, type: int
+ in [0, 100], default: 16)
+ - label-rotation (the angle (in degrees) that should be used to
+ rotate bar labels in vertical mode, type: int in [0, 360],
+ default: 300)
+ - rotate-group-labels (sets whether group labels should be roteated
+ by 90 degrees in horizontal mode, type: boolean, default: False).
+
+ Signals
+ =======
+ The MultiBarChart class inherits the signal 'bar-clicked' from
+ bar_chart.BarChart. Additional signals:
+ - group-clicked: emitted when a bar is clicked, callback signature:
+ def group_clicked(chart, group, bar).
+ """
+
+ __gsignals__ = {"group-clicked": (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))}
+
+ __gproperties__ = {"group-padding": (gobject.TYPE_INT, "group padding",
+ "The space between two bar groups.",
+ 0, 100, 16, gobject.PARAM_READWRITE),
+ "label-rotation": (gobject.TYPE_INT, "label rotation",
+ "The angle that should bar labels be rotated by in vertical mode.",
+ 0, 360, 300, gobject.PARAM_READWRITE),
+ "rotate-group-labels": (gobject.TYPE_BOOLEAN,
+ "rotate group label",
+ "Sets whether the group label should be rotated by 90 degrees in horizontal mode.",
+ False, gobject.PARAM_READWRITE),
+ "mode": (gobject.TYPE_INT, "mode",
+ "The chart's mode.", 0, 1, 0,
+ gobject.PARAM_READWRITE),
+ "draw-labels": (gobject.TYPE_BOOLEAN,
+ "draw labels", "Set whether to draw labels on bars.",
+ True, gobject.PARAM_READWRITE),
+ "enable-mouseover": (gobject.TYPE_BOOLEAN, "enable mouseover",
+ "Set whether to enable mouseover effect.",
+ True, gobject.PARAM_READWRITE)}
+
+ def __init__(self):
+ bar_chart.BarChart.__init__(self)
+ #private properties:
+ self._groups = []
+ #gobject properties:
+ self._group_padding = 16
+ self._label_rotation = 300
+ self._rotate_group_label_in_horizontal_mode = False
+
+ #gobject set_* and get_* methods
+ def do_get_property(self, property):
+ if property.name == "group-padding":
+ return self._group_padding
+ elif property.name == "label-rotation":
+ return self._label_rotation
+ elif property.name == "rotate-group-labels":
+ return self._rotate_group_label_in_horizontal_mode
+ elif property.name == "mode":
+ return self._mode
+ elif property.name == "draw-labels":
+ return self._draw_labels
+ elif property.name == "enable-mouseover":
+ return self._mouseover
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "group-padding":
+ self._group_padding = value
+ elif property.name == "label-rotation":
+ self._label_rotation = value
+ elif property.name == "rotate-group-labels":
+ self._rotate_group_label_in_horizontal_mode = value
+ elif property.name == "mode":
+ self._mode = value
+ elif property.name == "draw-labels":
+ self._draw_labels = value
+ elif property.name == "enable-mouseover":
+ self._mouseover = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def set_group_padding(self, padding):
+ """
+ Set the amount of free space between bar groups (in px,
+ default: 16).
+
+ @param padding: the padding
+ @type padding: int in [0, 100].
+ """
+ self.set_property("group-padding", padding)
+ self.queue_draw()
+
+ def get_group_padding(self):
+ """
+ Returns the amount of free space between two bar groups (in px).
+
+ @return: int in [0, 100].
+ """
+ return self.get_property("group-padding")
+
+ def set_label_rotation(self, angle):
+ """
+ Set the abgle (in degrees) that should be used to rotate the
+ bar labels in vertical mode (defualt: 300 degrees).
+
+ @type angle: int in [0, 360].
+ """
+ self.set_property("label-rotation", angle)
+ self.queue_draw()
+
+ def get_label_rotation(self):
+ """
+ Returns the angle by which bar labels are rotated in vertical
+ mode.
+
+ @return: int in [0, 350].
+ """
+ return self.get_property("label-rotation")
+
+ def set_rotate_group_labels(self, rotate):
+ """
+ Set wether the groups' labels should be rotated by 90 degrees in
+ horizontal mode (default: False).
+
+ @type rotate: boolean.
+ """
+ self.set_property("rotate-group-labels", rotate)
+ self.queue_draw()
+
+ def get_rotate_group_labels(self):
+ """
+ Returns True if group labels should be rotated by 90 degrees
+ in horizontal mode.
+
+ @return: boolean.
+ """
+ return self.get_property("rotate-group-labels")
+
+ #callbacks
+ def _cb_motion_notify(self, widget, event):
+ if not self._mouseover: return
+ active = chart.get_sensitive_areas(event.x, event.y)
+ if active == []: return
+ for group in self._groups:
+ for bar in group.get_bars():
+ bar.set_highlighted((group, bar) in active)
+ self.queue_draw()
+
+ def _cb_button_pressed(self, widget, event):
+ active = chart.get_sensitive_areas(event.x, event.y)
+ for group, bar in active:
+ self.emit("group-clicked", group, bar)
+ self.emit("bar-clicked", bar)
+
+ #drawing methods
+ def _do_draw_groups(self, context, rect, maximum_value, value_label_size, label_size, bar_count):
+ if self._groups == []: return
+
+ if self._mode == MODE_VERTICAL:
+ group_end = rect.x
+ else:
+ group_end = rect.y
+
+ for i, group in enumerate(self._groups):
+ group_end = group.draw(context, rect, bar_count, len(self._groups), i, self._mode, self._group_padding, maximum_value, group_end, value_label_size, label_size, self._label_rotation, self._draw_labels, self._rotate_group_label_in_horizontal_mode)
+
+ def draw(self, context):
+ """
+ Draw the widget. This method is called automatically. Don't call it
+ yourself. If you want to force a redrawing of the widget, call
+ the queue_draw() method.
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ """
+ label.begin_drawing()
+ chart.init_sensitive_areas()
+
+ rect = self.get_allocation()
+ rect = gtk.gdk.Rectangle(0, 0, rect.width, rect.height) #transform rect to context coordinates
+ context.set_line_width(1)
+
+ rect = self.draw_basics(context, rect)
+
+ maximum_value = max(group.get_maximum_value() for group in self._groups)
+ bar_count = 0
+ for group in self._groups: bar_count += group.get_bar_count()
+
+ value_label_size = 0
+ if self._draw_labels:
+ for group in self._groups:
+ value_label_size = max(value_label_size, group.get_value_label_size(context, rect, self._mode, bar_count, len(self._groups), self._group_padding, self._bar_padding))
+
+ label_size = 0
+ if self._draw_labels:
+ for group in self._groups:
+ label_size = max(label_size, group.get_label_size(context, rect, self._mode, bar_count, len(self._groups), self._group_padding, self._bar_padding, self._label_rotation))
+ label_size += 10
+ label_size += group.get_group_label_size(context, rect, self._mode, self._rotate_group_label_in_horizontal_mode)
+
+ rect = self._do_draw_grid(context, rect, maximum_value, value_label_size, label_size)
+ self._do_draw_groups(context, rect, maximum_value, value_label_size, label_size, bar_count)
+
+ label.finish_drawing()
+ n = len(self._groups)
+ if self._mode == MODE_VERTICAL:
+ minimum_width = rect.x + self._padding + bar_count * 10 + n * self._group_padding
+ minimum_height = rect.y + self._padding + 200
+ elif self._mode == MODE_HORIZONTAL:
+ minimum_width = rect.x + self._padding + 200
+ minimum_height = rect.y + self._padding + bar_count * 10 + n * self._group_padding
+ self.set_size_request(minimum_width, minimum_height)
+
+ #other methods
+ def add_group(self, group):
+ """
+ Add a BarGroup to the chart.
+
+ @type group: multi_bar_chart.BarGroup.
+ """
+ self._groups.append(group)
+ self.queue_draw()
+
+ def add_bar(self, bar):
+ """
+ Alias for add_group.
+ This method is deprecated. Use add_group instead.
+ """
+ print "MultiBarChart.add_bar is deprecated. Use add_group instead."
+ self.add_group(bar)
diff --git a/pygtk_chart/multi_bar_chart.pyc b/pygtk_chart/multi_bar_chart.pyc
new file mode 100644
index 0000000..bceafe1
--- /dev/null
+++ b/pygtk_chart/multi_bar_chart.pyc
Binary files differ
diff --git a/pygtk_chart/pie_chart.py b/pygtk_chart/pie_chart.py
new file mode 100644
index 0000000..3e3871d
--- /dev/null
+++ b/pygtk_chart/pie_chart.py
@@ -0,0 +1,474 @@
+#!/usr/bin/env python
+#
+# pie_chart.py
+#
+# Copyright 2008 Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+"""
+Contains the PieChart widget.
+
+Author: Sven Festersen (sven@sven-festersen.de)
+"""
+__docformat__ = "epytext"
+import cairo
+import gobject
+import gtk
+import math
+import os
+
+from pygtk_chart.basics import *
+from pygtk_chart.chart_object import ChartObject
+from pygtk_chart import chart
+from pygtk_chart import label
+from pygtk_chart import COLORS, COLOR_AUTO
+
+def draw_sector(context, cx, cy, radius, angle, angle_offset):
+ context.move_to(cx, cy)
+ context.arc(cx, cy, radius, angle_offset, angle_offset + angle)
+ context.close_path()
+ context.fill()
+
+
+class PieArea(chart.Area):
+ """
+ This class represents the sector of a pie chart.
+
+ Properties
+ ==========
+ The PieArea class inherits properties from chart.Area.
+
+ Signals
+ =======
+ The PieArea class inherits signals from chart.Area.
+ """
+
+ def __init__(self, name, value, title=""):
+ chart.Area.__init__(self, name, value, title)
+ self._label_object = label.Label((0, 0), title)
+
+ def _do_draw(self, context, rect, cx, cy, radius, angle, angle_offset, draw_label, draw_percentage, draw_value):
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ draw_sector(context, cx, cy, radius, angle, angle_offset)
+ if self._highlighted:
+ context.set_source_rgba(1, 1, 1, 0.1)
+ draw_sector(context, cx, cy, radius, angle, angle_offset)
+
+ if draw_label:
+ title = self._label
+ title_extra = ""
+ fraction = angle / (2 * math.pi)
+ if draw_percentage and not draw_value:
+ title_extra = " (%s%%)" % round(100 * fraction, 2)
+ elif not draw_percentage and draw_value:
+ title_extra = " (%s)" % self._value
+ elif draw_percentage and draw_value:
+ title_extra = " (%s, %s%%)" % (self._value, round(100 * fraction, 2))
+ title += title_extra
+
+ label_angle = angle_offset + angle / 2
+ label_angle = label_angle % (2 * math.pi)
+ x = cx + (radius + 10) * math.cos(label_angle)
+ y = cy + (radius + 10) * math.sin(label_angle)
+
+ ref = label.ANCHOR_BOTTOM_LEFT
+ if 0 <= label_angle <= math.pi / 2:
+ ref = label.ANCHOR_TOP_LEFT
+ elif math.pi / 2 <= label_angle <= math.pi:
+ ref = label.ANCHOR_TOP_RIGHT
+ elif math.pi <= label_angle <= 1.5 * math.pi:
+ ref = label.ANCHOR_BOTTOM_RIGHT
+
+ if self._highlighted:
+ self._label_object.set_underline(label.UNDERLINE_SINGLE)
+ else:
+ self._label_object.set_underline(label.UNDERLINE_NONE)
+ self._label_object.set_color(self._color)
+ self._label_object.set_text(title)
+ self._label_object.set_position((x, y))
+ self._label_object.set_anchor(ref)
+ self._label_object.draw(context, rect)
+
+
+class PieChart(chart.Chart):
+ """
+ This is the pie chart class.
+
+ Properties
+ ==========
+ The PieChart class inherits properties from chart.Chart.
+ Additional properties:
+ - rotate (the angle that the pie chart should be rotated by in
+ degrees, type: int in [0, 360])
+ - draw-shadow (sets whther to draw a shadow under the pie chart,
+ type: boolean)
+ - draw-labels (sets whether to draw area labels, type: boolean)
+ - show-percentage (sets whether to show percentage in area labels,
+ type: boolean)
+ - show-values (sets whether to show values in area labels,
+ type: boolean)
+ - enable-scroll (sets whether the pie chart can be rotated by
+ scrolling with the mouse wheel, type: boolean)
+ - enable-mouseover (sets whether a mouse over effect should be
+ added to areas, type: boolean).
+
+ Signals
+ =======
+ The PieChart class inherits signals from chart.Chart.
+ Additional signals:
+ - area-clicked (emitted when an area is clicked)
+ callback signature:
+ def callback(piechart, area).
+ """
+
+ __gproperties__ = {"rotate": (gobject.TYPE_INT,
+ "rotation",
+ "The angle to rotate the chart in degrees.",
+ 0, 360, 0, gobject.PARAM_READWRITE),
+ "draw-shadow": (gobject.TYPE_BOOLEAN,
+ "draw pie shadow",
+ "Set whether to draw pie shadow.",
+ True, gobject.PARAM_READWRITE),
+ "draw-labels": (gobject.TYPE_BOOLEAN,
+ "draw area labels",
+ "Set whether to draw area labels.",
+ True, gobject.PARAM_READWRITE),
+ "show-percentage": (gobject.TYPE_BOOLEAN,
+ "show percentage",
+ "Set whether to show percentage in the areas' labels.",
+ False, gobject.PARAM_READWRITE),
+ "show-values": (gobject.TYPE_BOOLEAN,
+ "show values",
+ "Set whether to show values in the areas' labels.",
+ True, gobject.PARAM_READWRITE),
+ "enable-scroll": (gobject.TYPE_BOOLEAN,
+ "enable scroll",
+ "If True, the pie can be rotated by scrolling with the mouse wheel.",
+ True, gobject.PARAM_READWRITE),
+ "enable-mouseover": (gobject.TYPE_BOOLEAN,
+ "enable mouseover",
+ "Set whether a mouseover effect should be visible if moving the mouse over a pie area.",
+ True, gobject.PARAM_READWRITE)}
+
+ __gsignals__ = {"area-clicked": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))}
+
+ def __init__(self):
+ chart.Chart.__init__(self)
+ self._areas = []
+ self._rotate = 0
+ self._shadow = True
+ self._labels = True
+ self._percentage = False
+ self._values = True
+ self._enable_scroll = True
+ self._enable_mouseover = True
+
+ self.add_events(gtk.gdk.BUTTON_PRESS_MASK|gtk.gdk.SCROLL_MASK|gtk.gdk.POINTER_MOTION_MASK)
+ self.connect("button_press_event", self._cb_button_pressed)
+ self.connect("scroll-event", self._cb_scroll_event)
+ self.connect("motion-notify-event", self._cb_motion_notify)
+
+ def do_get_property(self, property):
+ if property.name == "rotate":
+ return self._rotate
+ elif property.name == "draw-shadow":
+ return self._shadow
+ elif property.name == "draw-labels":
+ return self._labels
+ elif property.name == "show-percentage":
+ return self._percentage
+ elif property.name == "show-values":
+ return self._values
+ elif property.name == "enable-scroll":
+ return self._enable_scroll
+ elif property.name == "enable-mouseover":
+ return self._enable_mouseover
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "rotate":
+ self._rotate = value
+ elif property.name == "draw-shadow":
+ self._shadow = value
+ elif property.name == "draw-labels":
+ self._labels = value
+ elif property.name == "show-percentage":
+ self._percentage = value
+ elif property.name == "show-values":
+ self._values = value
+ elif property.name == "enable-scroll":
+ self._enable_scroll = value
+ elif property.name == "enable-mouseover":
+ self._enable_mouseover = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def _cb_appearance_changed(self, widget):
+ self.queue_draw()
+
+ def _cb_motion_notify(self, widget, event):
+ if not self._enable_mouseover: return
+ area = self._get_area_at_pos(event.x, event.y)
+ for a in self._areas:
+ a.set_property("highlighted", a == area)
+ self.queue_draw()
+
+ def _cb_button_pressed(self, widget, event):
+ area = self._get_area_at_pos(event.x, event.y)
+ if area:
+ self.emit("area-clicked", area)
+
+ def _get_area_at_pos(self, x, y):
+ rect = self.get_allocation()
+ center = rect.width / 2, rect.height / 2
+ x = x - center[0]
+ y = y - center[1]
+
+ #calculate angle
+ angle = math.atan2(x, -y)
+ angle -= math.pi / 2
+ angle -= 2 * math.pi * self.get_rotate() / 360.0
+ while angle < 0:
+ angle += 2 * math.pi
+
+ #calculate radius
+ radius_squared = math.pow(int(0.4 * min(rect.width, rect.height)), 2)
+ clicked_radius_squared = x*x + y*y
+
+ if clicked_radius_squared <= radius_squared:
+ #find out area that was clicked
+ sum = 0
+ for area in self._areas:
+ if area.get_visible():
+ sum += area.get_value()
+
+ current_angle_position = 0
+ for area in self._areas:
+ area_angle = 2 * math.pi * area.get_value() / sum
+
+ if current_angle_position <= angle <= current_angle_position + area_angle:
+ return area
+
+ current_angle_position += area_angle
+ return None
+
+ def _cb_scroll_event(self, widget, event):
+ if not self._enable_scroll: return
+ if event.direction in [gtk.gdk.SCROLL_UP, gtk.gdk.SCROLL_RIGHT]:
+ delta = 360.0 / 32
+ elif event.direction in [gtk.gdk.SCROLL_DOWN, gtk.gdk.SCROLL_LEFT]:
+ delta = - 360.0 / 32
+ else:
+ delta = 0
+ rotate = self.get_rotate() + delta
+ rotate = rotate % 360.0
+ if rotate < 0: rotate += 360
+ self.set_rotate(rotate)
+
+ def draw(self, context):
+ """
+ Draw the widget. This method is called automatically. Don't call it
+ yourself. If you want to force a redrawing of the widget, call
+ the queue_draw() method.
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ """
+ label.begin_drawing()
+
+ rect = self.get_allocation()
+ #initial context settings: line width & font
+ context.set_line_width(1)
+ font = gtk.Label().style.font_desc.get_family()
+ context.select_font_face(font, cairo.FONT_SLANT_NORMAL, \
+ cairo.FONT_WEIGHT_NORMAL)
+
+ self.draw_basics(context, rect)
+ self._do_draw_shadow(context, rect)
+ self._do_draw_areas(context, rect)
+
+ label.finish_drawing()
+
+ def _do_draw_areas(self, context, rect):
+ center = rect.width / 2, rect.height / 2
+ radius = int(0.4 * min(rect.width, rect.height))
+ sum = 0
+
+ for area in self._areas:
+ if area.get_visible():
+ sum += area.get_value()
+
+ current_angle_position = 2 * math.pi * self.get_rotate() / 360.0
+ for i, area in enumerate(self._areas):
+ area_angle = 2 * math.pi * area.get_value() / sum
+ area.draw(context, rect, center[0], center[1], radius, area_angle, current_angle_position, self._labels, self._percentage, self._values)
+ current_angle_position += area_angle
+
+ def _do_draw_shadow(self, context, rect):
+ if not self._shadow: return
+ center = rect.width / 2, rect.height / 2
+ radius = int(0.4 * min(rect.width, rect.height))
+
+ gradient = cairo.RadialGradient(center[0], center[1], radius, center[0], center[1], radius + 10)
+ gradient.add_color_stop_rgba(0, 0, 0, 0, 0.5)
+ gradient.add_color_stop_rgba(0.5, 0, 0, 0, 0)
+
+ context.set_source(gradient)
+ context.arc(center[0], center[1], radius + 10, 0, 2 * math.pi)
+ context.fill()
+
+ def add_area(self, area):
+ color = area.get_color()
+ if color == COLOR_AUTO: area.set_color(COLORS[len(self._areas) % len(COLORS)])
+ self._areas.append(area)
+ area.connect("appearance_changed", self._cb_appearance_changed)
+
+ def get_pie_area(self, name):
+ """
+ Returns the PieArea with the id 'name' if it exists, None
+ otherwise.
+
+ @type name: string
+ @param name: the id of a PieArea
+
+ @return: a PieArea or None.
+ """
+ for area in self._areas:
+ if area.get_name() == name:
+ return area
+ return None
+
+ def set_rotate(self, angle):
+ """
+ Set the rotation angle of the PieChart in degrees.
+
+ @param angle: angle in degrees 0 - 360
+ @type angle: integer.
+ """
+ self.set_property("rotate", angle)
+ self.queue_draw()
+
+ def get_rotate(self):
+ """
+ Get the current rotation angle in degrees.
+
+ @return: integer.
+ """
+ return self.get_property("rotate")
+
+ def set_draw_shadow(self, draw):
+ """
+ Set whether to draw the pie chart's shadow.
+
+ @type draw: boolean.
+ """
+ self.set_property("draw-shadow", draw)
+ self.queue_draw()
+
+ def get_draw_shadow(self):
+ """
+ Returns True if pie chart currently has a shadow.
+
+ @return: boolean.
+ """
+ return self.get_property("draw-shadow")
+
+ def set_draw_labels(self, draw):
+ """
+ Set whether to draw the labels of the pie areas.
+
+ @type draw: boolean.
+ """
+ self.set_property("draw-labels", draw)
+ self.queue_draw()
+
+ def get_draw_labels(self):
+ """
+ Returns True if area labels are shown.
+
+ @return: boolean.
+ """
+ return self.get_property("draw-labels")
+
+ def set_show_percentage(self, show):
+ """
+ Set whether to show the percentage an area has in its label.
+
+ @type show: boolean.
+ """
+ self.set_property("show-percentage", show)
+ self.queue_draw()
+
+ def get_show_percentage(self):
+ """
+ Returns True if percentages are shown.
+
+ @return: boolean.
+ """
+ return self.get_property("show-percentage")
+
+ def set_enable_scroll(self, scroll):
+ """
+ Set whether the pie chart can be rotated by scrolling with
+ the mouse wheel.
+
+ @type scroll: boolean.
+ """
+ self.set_property("enable-scroll", scroll)
+
+ def get_enable_scroll(self):
+ """
+ Returns True if the user can rotate the pie chart by scrolling.
+
+ @return: boolean.
+ """
+ return self.get_property("enable-scroll")
+
+ def set_enable_mouseover(self, mouseover):
+ """
+ Set whether a mouseover effect should be shown when the pointer
+ enters a pie area.
+
+ @type mouseover: boolean.
+ """
+ self.set_property("enable-mouseover", mouseover)
+
+ def get_enable_mouseover(self):
+ """
+ Returns True if the mouseover effect is enabled.
+
+ @return: boolean.
+ """
+ return self.get_property("enable-mouseover")
+
+ def set_show_values(self, show):
+ """
+ Set whether the area's value should be shown in its label.
+
+ @type show: boolean.
+ """
+ self.set_property("show-values", show)
+ self.queue_draw()
+
+ def get_show_values(self):
+ """
+ Returns True if the value of a pie area is shown in its label.
+
+ @return: boolean.
+ """
+ return self.get_property("show-values")
+