Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWalter Bender <walter.bender@gmail.com>2012-10-03 11:42:54 (GMT)
committer Walter Bender <walter.bender@gmail.com>2012-10-03 11:42:54 (GMT)
commitc711bd0a4392ff8bb66d1b9a9ffe6e48ba5c8c6b (patch)
tree0a0fab376cda03882ca0d95ac877776d54251282
parentf0b6e0d1d3ca19acd760de3c95acde290e136cfa (diff)
rebase to Chart code; add new charts for journal data
-rw-r--r--NEWS6
-rw-r--r--activity.py666
-rw-r--r--activity/activity.info5
-rw-r--r--charthelp.py30
-rw-r--r--charts.py34
-rw-r--r--help.py17
-rw-r--r--helpbutton.py10
-rw-r--r--icons/hbar.svg66
-rw-r--r--icons/import-freespace.svg114
-rw-r--r--icons/import-journal.svg147
-rw-r--r--icons/import-turtle.svg158
-rw-r--r--icons/line.svg34
-rw-r--r--icons/pie.svg48
-rw-r--r--icons/save-as-image.svg116
-rw-r--r--icons/vbar.svg66
-rw-r--r--readers.py340
-rw-r--r--sugarpycha/__init__.py (renamed from pycha/__init__.py)0
-rw-r--r--sugarpycha/bar.py (renamed from pycha/bar.py)15
-rw-r--r--sugarpycha/chart.py (renamed from pycha/chart.py)45
-rw-r--r--sugarpycha/color.py (renamed from pycha/color.py)8
-rw-r--r--sugarpycha/line.py (renamed from pycha/line.py)5
-rw-r--r--sugarpycha/pie.py (renamed from pycha/pie.py)26
-rw-r--r--sugarpycha/polygonal.py (renamed from pycha/polygonal.py)8
-rw-r--r--sugarpycha/radial.py (renamed from pycha/radial.py)8
-rw-r--r--sugarpycha/scatter.py (renamed from pycha/scatter.py)2
-rw-r--r--sugarpycha/stackedbar.py (renamed from pycha/stackedbar.py)8
-rw-r--r--sugarpycha/utils.py (renamed from pycha/utils.py)1
-rw-r--r--utils.py103
28 files changed, 1864 insertions, 222 deletions
diff --git a/NEWS b/NEWS
index 9e4050d..b4673aa 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,9 @@
+2
+
+ENHANCEMENTS:
+* Add chart of Sugar Activity usage
+* Add chart of Turtle Block usage
+
1
Initial release.
diff --git a/activity.py b/activity.py
index 3ff0edc..d7f2e9d 100644
--- a/activity.py
+++ b/activity.py
@@ -2,8 +2,10 @@
# -*- coding: utf-8 -*-
# activity.py by:
-# Agustin Zubiaga <aguz@sugarlabs.com>
-# Rafael Ortiz <dirakx@gmail.com>
+# Agustin Zubiaga <aguz@sugarlabs.org>
+# Gonzalo Odiard <godiard@gmail.com>
+# Manuel QuiƱones <manuq@laptop.org>
+# Walter Bender <walter@sugarlabs.org>
# 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
@@ -20,221 +22,631 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import gtk
+import gobject
+import pango
import os
-import statvfs
+import simplejson
+import locale
import logging
-import pango
+import utils
+from StringIO import StringIO
from gettext import gettext as _
-from sugar import env
-from sugar import profile
-
from sugar.activity import activity
-from sugar.bundle.activitybundle import ActivityBundle
+from sugar.activity.widgets import ActivityToolbarButton
from sugar.activity.widgets import StopButton
+from sugar.activity.widgets import ToolbarButton
from sugar.graphics.toolbarbox import ToolbarBox
from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.radiotoolbutton import RadioToolButton
+from sugar.graphics.colorbutton import ColorToolButton
+from sugar.graphics.objectchooser import ObjectChooser
from sugar.graphics.icon import Icon
+from sugar.graphics.alert import Alert
+from sugar.datastore import datastore
-import help
from charts import Chart
-
-_logger = logging.getLogger('AnalyzeJournal-activity')
+from readers import FreeSpaceReader
+from readers import JournalReader
+from readers import TurtleReader
+import charthelp
+
+# GUI Colors
+_COLOR1 = utils.get_user_fill_color()
+_COLOR2 = utils.get_user_stroke_color()
+_WHITE = gtk.gdk.color_parse("white")
+
+# Paths
+_ACTIVITY_DIR = os.path.join(activity.get_activity_root(), "data/")
+_CHART_FILE = utils.get_chart_file(_ACTIVITY_DIR)
+
+# Logging
+_logger = logging.getLogger('analyze-journal-activity')
_logger.setLevel(logging.DEBUG)
logging.basicConfig()
-color = profile.get_color()
-_FILL_COLOR = color.get_fill_color()
-_STROKE_COLOR = color.get_stroke_color()
+
+class ChartArea(gtk.DrawingArea):
+
+ def __init__(self, parent):
+ """A class for Draw the chart"""
+ super(ChartArea, self).__init__()
+ self._parent = parent
+ self.add_events(gtk.gdk.EXPOSURE_MASK | gtk.gdk.VISIBILITY_NOTIFY_MASK)
+ self.connect("expose-event", self._expose_cb)
+
+ target = [("text/plain", 0, 0)]
+ self.drag_dest_set(gtk.DEST_DEFAULT_ALL, target,
+ gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
+ self.connect('drag_data_received', self._drag_data_received)
+
+ def _expose_cb(self, widget, event):
+ context = self.window.cairo_create()
+
+ xpos, ypos, width, height = self.get_allocation()
+
+ # White Background:
+ context.rectangle(0, 0, width, height)
+ context.set_source_rgb(255, 255, 255)
+ context.fill()
+
+ # Paint the chart:
+ chart_width = self._parent.current_chart.width
+ chart_height = self._parent.current_chart.height
+
+ cxpos = xpos + width / 2 - chart_width / 2
+ cypos = ypos + height / 2 - chart_height / 2
+
+ context.set_source_surface(self._parent.current_chart.surface,
+ cxpos,
+ cypos)
+ context.paint()
+
+ def _drag_data_received(self, w, context, x, y, data, info, time):
+ if data and data.format == 8:
+ io_file = StringIO(data.data)
+ reader = ClipboardReader(io_file)
+ self._parent._graph_from_reader(reader)
+ context.finish(True, False, time)
+ else:
+ context.finish(False, False, time)
class AnalyzeJournal(activity.Activity):
def __init__(self, handle):
- activity.Activity.__init__(self, handle, False)
+
+ activity.Activity.__init__(self, handle, True)
self.max_participants = 1
+ # CHART_OPTIONS
+
+ self.x_label = ""
+ self.y_label = ""
+ self.chart_color = utils.get_user_fill_color('str')
+ self.chart_line_color = utils.get_user_stroke_color('str')
+ self.current_chart = None
+ self.charts_area = None
+ self.chart_data = []
+
# TOOLBARS
toolbarbox = ToolbarBox()
- activity_button = ToolButton(self)
- bundle = ActivityBundle(activity.get_bundle_path())
- icon = Icon(file=bundle.get_icon(), xo_color=color)
- activity_button.set_icon_widget(icon)
+ activity_button = ActivityToolbarButton(self)
+ activity_btn_toolbar = activity_button.page
+
+ activity_btn_toolbar.title.connect('changed', self._set_chart_title)
+
+ save_as_image = ToolButton("save-as-image")
+ save_as_image.connect("clicked", self._save_as_image)
+ save_as_image.set_tooltip(_("Save as image"))
+ activity_btn_toolbar.insert(save_as_image, -1)
+
+ save_as_image.show()
toolbarbox.toolbar.insert(activity_button, 0)
- activity_button.show()
+
+ import_freespace = ToolButton("import-freespace")
+ import_freespace.connect("clicked", self.__import_freespace_cb)
+ import_freespace.set_tooltip(_("Read Freespace data"))
+ toolbarbox.toolbar.insert(import_freespace, -1)
+ import_freespace.show()
+
+ import_journal = ToolButton('import-journal')
+ import_journal.connect('clicked', self.__import_journal_cb)
+ import_journal.set_tooltip(_('Read Journal data'))
+ toolbarbox.toolbar.insert(import_journal, -1)
+ import_journal.show()
+
+ import_turtle = ToolButton('import-turtle')
+ import_turtle.connect('clicked', self.__import_turtle_cb)
+ import_turtle.set_tooltip(_('Read Turtle data'))
+ toolbarbox.toolbar.insert(import_turtle, -1)
+ import_turtle.show()
+
+ separator = gtk.SeparatorToolItem()
+ separator.set_draw(True)
+ separator.set_expand(False)
+ toolbarbox.toolbar.insert(separator, -1)
+
+ add_vbar_chart = RadioToolButton()
+ add_vbar_chart.connect("clicked", self._add_chart_cb, "vbar")
+ add_vbar_chart.set_tooltip(_("Vertical Bar Chart"))
+ add_vbar_chart.props.icon_name = "vbar"
+ charts_group = add_vbar_chart
+ toolbarbox.toolbar.insert(add_vbar_chart, -1)
+
+ add_hbar_chart = RadioToolButton()
+ add_hbar_chart.connect("clicked", self._add_chart_cb, "hbar")
+ add_hbar_chart.set_tooltip(_("Horizontal Bar Chart"))
+ add_hbar_chart.props.icon_name = "hbar"
+ add_hbar_chart.props.group = charts_group
+ toolbarbox.toolbar.insert(add_hbar_chart, -1)
+
+ add_pie_chart = RadioToolButton()
+ add_pie_chart.connect("clicked", self._add_chart_cb, "pie")
+ add_pie_chart.set_tooltip(_("Pie Chart"))
+ add_pie_chart.props.icon_name = "pie"
+ add_pie_chart.props.group = charts_group
+ add_pie_chart.set_active(True)
+ toolbarbox.toolbar.insert(add_pie_chart, -1)
+
+ self.chart_type_buttons = [add_vbar_chart,
+ add_hbar_chart,
+ add_pie_chart]
separator = gtk.SeparatorToolItem()
separator.set_draw(True)
separator.set_expand(False)
toolbarbox.toolbar.insert(separator, -1)
- update_btn = ToolButton('gtk-refresh')
- update_btn.connect('clicked', self._analyze)
- toolbarbox.toolbar.insert(update_btn, -1)
+ fullscreen_btn = ToolButton('view-fullscreen')
+ fullscreen_btn.set_tooltip(_('Fullscreen'))
+ fullscreen_btn.connect("clicked", self.__fullscreen_cb)
+
+ toolbarbox.toolbar.insert(fullscreen_btn, -1)
+
+ charthelp.create_help(toolbarbox.toolbar)
separator = gtk.SeparatorToolItem()
separator.set_draw(False)
separator.set_expand(True)
toolbarbox.toolbar.insert(separator, -1)
- help.create_help(toolbarbox.toolbar)
-
stopbtn = StopButton(self)
toolbarbox.toolbar.insert(stopbtn, -1)
- toolbarbox.show_all()
-
self.set_toolbar_box(toolbarbox)
- # CHART
- self.chart = None
- self.chart_data = []
-
# CANVAS
- self.area = Area(self)
- self.set_canvas(self.area)
+ paned = gtk.HPaned()
+ box = gtk.VBox()
+ self.box = box
+
+ # Set the info box width to 1/3 of the screen:
+ def size_allocate_cb(widget, allocation):
+ paned.disconnect(self._setup_handle)
+ box_width = allocation.width / 3
+ box.set_size_request(box_width, -1)
+
+ self._setup_handle = paned.connect('size_allocate',
+ size_allocate_cb)
+
+ scroll = gtk.ScrolledWindow()
+ scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.labels_and_values = ChartData(self)
+ scroll.add(self.labels_and_values)
- self.area.show_all()
+ self.labels_and_values.connect("label-changed", self._label_changed)
+ self.labels_and_values.connect("value-changed", self._value_changed)
+
+ box.pack_start(scroll, True, True, 0)
+
+ paned.add1(box)
+
+ # CHARTS AREA
+
+ eventbox = gtk.EventBox()
+ self.charts_area = ChartArea(self)
+ self.charts_area.connect('size_allocate', self._chart_size_allocate)
+
+ eventbox.modify_bg(gtk.STATE_NORMAL, _WHITE)
+
+ eventbox.add(self.charts_area)
+ paned.add2(eventbox)
+
+ self.set_canvas(paned)
- # ANALYZE
self.show_all()
- self._analyze(None)
- def _analyze(self, widget):
+ def _add_chart_cb(self, widget, type="vbar"):
+ self.current_chart = Chart(type)
+
+ self.update_chart()
+
+ def _chart_size_allocate(self, widget, allocation):
+ self._render_chart()
+
+ def unfullscreen(self):
+ self.box.show()
+ activity.Activity.unfullscreen(self)
+
+ def __fullscreen_cb(self, button):
+ self.box.hide()
+ self._render_chart(fullscreen=True)
+ activity.Activity.fullscreen(self)
+
+ def _render_chart(self, fullscreen=False):
+ if self.current_chart is None or self.charts_area is None:
+ return
+
+ try:
+ # Resize the chart for all the screen sizes
+ xpos, ypos, width, height = self.get_allocation()
+
+ if fullscreen:
+ new_width = width
+ new_height = height
+
+ if not fullscreen:
+ sxpos, sypos, width, height = self.charts_area.get_allocation()
+
+ new_width = width - 40
+ new_height = height - 40
+
+ self.current_chart.width = new_width
+ self.current_chart.height = new_height
+
+ # Set options
+ self.current_chart.set_color_scheme(color=self.chart_color)
+ self.current_chart.set_line_color(self.chart_line_color)
+
+ if self.current_chart.type == "pie":
+ self.current_chart.render(self)
+ else:
+ self.current_chart.render()
+ self.charts_area.queue_draw()
+
+ except (ZeroDivisionError, ValueError):
+ pass
+
+ return False
+
+ def _update_chart_data(self):
+ if self.current_chart is None:
+ return
+ self.current_chart.data_set(self.chart_data)
+ self._update_chart_labels()
+
+ def _set_chart_title(self, widget):
+ self._update_chart_labels(title=widget.get_text())
+
+ def _update_chart_labels(self, title=""):
+ if self.current_chart is None:
+ return
+
+ if not title and self.metadata["title"]:
+ title = self.metadata["title"]
+
+ self.current_chart.set_title(title)
+ self.current_chart.set_x_label(self.x_label)
+ self.current_chart.set_y_label(self.y_label)
+ self._render_chart()
+
+ def update_chart(self):
+ if self.current_chart:
+ self.current_chart.data_set(self.chart_data)
+ self.current_chart.set_title(self.metadata["title"])
+ self.current_chart.set_x_label(self.x_label)
+ self.current_chart.set_y_label(self.y_label)
+ self._render_chart()
+
+ def _label_changed(self, treeview, path, new_label):
+ path = int(path)
+ self.chart_data[path] = (new_label, self.chart_data[path][1])
+ self._update_chart_data()
+
+ def _value_changed(self, treeview, path, new_value):
+ path = int(path)
+ self.chart_data[path] = (self.chart_data[path][0], float(new_value))
+ self._update_chart_data()
+
+ def _set_h_label(self, widget):
+ new_text = widget.get_text()
+
+ if new_text != self.h_label._text:
+ self.x_label = new_text
+ self._update_chart_labels()
+
+ def _set_v_label(self, widget):
+ new_text = widget.get_text()
+
+ if new_text != self.v_label._text:
+ self.y_label = new_text
+ self._update_chart_labels()
+
+ def _set_chart_color(self, widget, pspec):
+ self.chart_color = utils.rgb2html(widget.get_color())
+ self._render_chart()
+
+ def _set_chart_line_color(self, widget, pspec):
+ self.chart_line_color = utils.rgb2html(widget.get_color())
+ self._render_chart()
+
+ def _object_chooser(self, mime_type, type_name):
+ chooser = ObjectChooser()
+ matches_mime_type = False
+
+ response = chooser.run()
+ if response == gtk.RESPONSE_ACCEPT:
+ jobject = chooser.get_selected_object()
+ metadata = jobject.metadata
+ file_path = jobject.file_path
+
+ if metadata['mime_type'] == mime_type:
+ matches_mime_type = True
+
+ else:
+ alert = Alert()
+
+ alert.props.title = _('Invalid object')
+ alert.props.msg = \
+ _('The selected object must be a %s file' % (type_name))
+
+ ok_icon = Icon(icon_name='dialog-ok')
+ alert.add_button(gtk.RESPONSE_OK, _('Ok'), ok_icon)
+ ok_icon.show()
+
+ alert.connect('response', lambda a, r: self.remove_alert(a))
+
+ self.add_alert(alert)
+
+ alert.show()
+
+ return matches_mime_type, file_path, metadata['title']
+
+ def _graph_from_reader(self, reader):
+ self.labels_and_values.model.clear()
self.chart_data = []
- free_space, used_space, total_space = self._get_space()
+ chart_data = reader.get_chart_data()
+ horizontal, vertical = reader.get_labels_name()
+
+ # Load the data
+ for row in chart_data:
+ self._add_value(None,
+ label=row[0], value=float(row[1]))
+
+ self.update_chart()
- # Graph
- self.chart_data.append((_('Free'), free_space))
- self.chart_data.append((_('Used'), used_space))
+ def _add_value(self, widget, label="", value="0.0"):
+ data = (label, float(value))
+ if not data in self.chart_data:
+ pos = self.labels_and_values.add_value(label, value)
+ self.chart_data.insert(pos, data)
+ self._update_chart_data()
- self.chart = Chart()
- self.chart.data_set(self.chart_data)
- self.chart.set_type('pie')
- self._resize_chart()
- self.chart.render(self)
+ def _remove_value(self, widget):
+ path = self.labels_and_values.remove_selected_value()
+ del self.chart_data[path]
+ self._update_chart_data()
- # Set info
- f_type, t_type, u_type = 'MBs', 'MBs', 'MBs'
+ def __import_freespace_cb(self, widget):
+ reader = FreeSpaceReader()
+ self._graph_from_reader(reader)
- if free_space >= 1024:
- free_space = self._get_GBs(free_space)
- f_type = 'GBs'
+ def __import_journal_cb(self, widget):
+ reader = JournalReader()
+ self._graph_from_reader(reader)
- if total_space >= 1024:
- total_space = self._get_GBs(total_space)
- t_type = 'GBs'
+ def __import_turtle_cb(self, widget):
+ matches_mime_type, file_path, title = self._object_chooser(
+ 'application/x-turtle-art', _('Turtle'))
+ if matches_mime_type:
+ reader = TurtleReader(file_path)
+ self._graph_from_reader(reader)
- if used_space >= 1024:
- used_space = self._get_GBs(used_space)
- u_type = 'GBs'
+ def _save_as_image(self, widget):
+ if self.current_chart:
+ jobject = datastore.create()
- t = '<span foreground="%s"><b>%s</b></span>' % \
- (_FILL_COLOR, self._get_info_string('t'))
+ jobject.metadata['title'] = self.metadata["title"]
+ jobject.metadata['mime_type'] = "image/png"
- ts = '<span foreground="%s"><b>%s</b></span> %s %s' % \
- (_STROKE_COLOR, self._get_info_string('ts'), total_space, t_type)
+ self.current_chart.as_png(_CHART_FILE)
+ jobject.set_file_path(_CHART_FILE)
- us = '<span foreground="%s"><b>%s</b></span> %s %s' % \
- (_STROKE_COLOR, self._get_info_string('us'), used_space, u_type)
+ datastore.write(jobject)
- fs = '<span foreground="%s"><b>%s</b></span> %s %s' % \
- (_STROKE_COLOR, self._get_info_string('fs'), free_space, f_type)
+ def load_from_file(self, f):
+ try:
+ data = simplejson.load(f)
+ finally:
+ f.close()
- info = t + '\n' + ts + '\n' + us + '\n' + fs
+ self.metadata["title"] = data['title']
+ self.x_label = data['x_label']
+ self.y_label = data['y_label']
+ self.chart_color = data['chart_color']
+ self.chart_line_color = data['chart_line_color']
+ self.current_chart.type = data['current_chart.type']
+ chart_data = data['chart_data']
- self.area.text = info
+ # Update charts buttons
+ _type = data["current_chart.type"]
+ if _type == "vbar":
+ self.chart_type_buttons[0].set_active(True)
- self.area.queue_draw()
+ elif _type == "hbar":
+ self.chart_type_buttons[1].set_active(True)
- def _get_info_string(self, string):
- if string == 't':
- return _('Info:')
+ elif _type == "line":
+ self.chart_type_buttons[2].set_active(True)
- elif string == 'ts':
- return _('Total space:')
+ elif _type == "pie":
+ self.chart_type_buttons[3].set_active(True)
- elif string == 'us':
- return _('Used space:')
+ #load the data
+ for row in chart_data:
+ self._add_value(None, label=row[0], value=float(row[1]))
- elif string == 'fs':
- return _('Free space:')
+ self.update_chart()
- def _resize_chart(self):
- sx, sy, width, height = self.area.get_allocation()
+ def write_file(self, file_path):
+ self.metadata['mime_type'] = "application/x-chart-activity"
+ if self.current_chart:
- new_width = width - 250
- new_height = height - 250
+ data = {}
+ data['title'] = self.metadata["title"]
+ data['x_label'] = self.x_label
+ data['y_label'] = self.y_label
+ data['chart_color'] = self.chart_color
+ data['chart_line_color'] = self.chart_line_color
+ data['current_chart.type'] = self.current_chart.type
+ data['chart_data'] = self.chart_data
- self.chart.width = new_width
- self.chart.height = new_height
+ f = open(file_path, 'w')
+ try:
+ simplejson.dump(data, f)
+ finally:
+ f.close()
- def _get_space(self):
- stat = os.statvfs(env.get_profile_path())
- free_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BAVAIL]
- total_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BLOCKS]
+ def read_file(self, file_path):
+ f = open(file_path, 'r')
+ self.load_from_file(f)
- free_space = self._get_MBs(free_space)
- total_space = self._get_MBs(total_space)
- used_space = total_space - free_space
- _logger.info('Free space: %s/%s MBs' % (free_space, total_space))
+class ChartData(gtk.TreeView):
- return free_space, used_space, total_space
+ __gsignals__ = {
+ 'label-changed': (gobject.SIGNAL_RUN_FIRST, None, [str, str], ),
+ 'value-changed': (gobject.SIGNAL_RUN_FIRST, None, [str, str], ), }
- def _get_MBs(self, space):
- space = space / (1024 * 1024)
+ def __init__(self, activity):
- return space
+ gtk.TreeView.__init__(self)
- def _get_GBs(self, space):
- space = space / 1024
+ self.model = gtk.ListStore(str, str)
+ self.set_model(self.model)
- return space
+ # Label column
+ column = gtk.TreeViewColumn(_("Label"))
+ label = gtk.CellRendererText()
+ label.set_property('editable', True)
+ label.connect("edited", self._label_changed, self.model)
-class Area(gtk.DrawingArea):
+ column.pack_start(label)
+ column.set_attributes(label, text=0)
+ self.append_column(column)
- def __init__(self, parent):
- super(Area, self).__init__()
- self._parent = parent
- self.add_events(gtk.gdk.EXPOSURE_MASK | gtk.gdk.VISIBILITY_NOTIFY_MASK)
- self.connect('expose-event', self._expose_cb)
+ # Value column
- self.text = ''
+ column = gtk.TreeViewColumn(_("Value"))
+ value = gtk.CellRendererText()
+ value.set_property('editable', True)
+ value.connect("edited", self._value_changed, self.model, activity)
- pango_context = self.get_pango_context()
- self.layout = pango.Layout(pango_context)
- self.layout.set_font_description(pango.FontDescription('13'))
+ column.pack_start(value)
+ column.set_attributes(value, text=1)
- def _expose_cb(self, widget, event):
- context = self.window.cairo_create()
- gc = self.window.new_gc()
+ self.append_column(column)
+ self.set_enable_search(False)
- x, y, w, h = self.get_allocation()
+ self.show_all()
- # White Background:
- context.rectangle(0, 0, w, h)
- context.set_source_rgb(255, 255, 255)
- context.fill()
+ def add_value(self, label, value):
+ selected = self.get_selection().get_selected()[1]
+ if not selected:
+ path = 0
- # Paint the chart:
- cw, ch = self._parent.chart.width, self._parent.chart.height
- cy = y + h / 2 - ch / 2
+ elif selected:
+ path = self.model.get_path(selected)[0] + 1
- context.set_source_surface(self._parent.chart.surface, x, cy)
- context.paint()
+ _iter = self.model.insert(path, [label, value])
+
+ self.set_cursor(self.model.get_path(_iter),
+ self.get_column(1),
+ True)
+
+ _logger.info("Added: %s, Value: %s" % (label, value))
+
+ return path
+
+ def remove_selected_value(self):
+ path, column = self.get_cursor()
+ path = path[0]
+
+ model, iter = self.get_selection().get_selected()
+ self.model.remove(iter)
+
+ return path
+
+ def _label_changed(self, cell, path, new_text, model):
+ _logger.info("Change '%s' to '%s'" % (model[path][0], new_text))
+ model[path][0] = new_text
+
+ self.emit("label-changed", str(path), new_text)
- # Write the info
- self.layout.set_markup(self.text)
- lh = (self.layout.get_pixel_extents()[1][2] * 3) / 1000
+ def _value_changed(self, cell, path, new_text, model, activity):
+ _logger.info("Change '%s' to '%s'" % (model[path][1], new_text))
+ is_number = True
+ number = new_text.replace(",", ".")
+ try:
+ float(number)
+ except ValueError:
+ is_number = False
+
+ if is_number:
+ decimals = utils.get_decimals(str(float(number)))
+ new_text = locale.format('%.' + decimals + 'f', float(number))
+ model[path][1] = str(new_text)
+
+ self.emit("value-changed", str(path), number)
+
+ elif not is_number:
+ alert = Alert()
+
+ alert.props.title = _('Invalid Value')
+ alert.props.msg = \
+ _('The value must be a number (integer or decimal)')
+
+ ok_icon = Icon(icon_name='dialog-ok')
+ alert.add_button(gtk.RESPONSE_OK, _('Ok'), ok_icon)
+ ok_icon.show()
+
+ alert.connect('response', lambda a, r: activity.remove_alert(a))
+
+ activity.add_alert(alert)
+
+ alert.show()
+
+
+class Entry(gtk.ToolItem):
+
+ def __init__(self, text):
+ gtk.ToolItem.__init__(self)
+
+ self.entry = gtk.Entry()
+ self.entry.set_text(text)
+ self.entry.connect("focus-in-event", self._focus_in)
+ self.entry.connect("focus-out-event", self._focus_out)
+ self.entry.modify_font(pango.FontDescription("italic"))
+
+ self._text = text
+
+ self.add(self.entry)
+
+ self.show_all()
- lx = x + cw
- ly = y + h / 2 - lh / 2
+ def _focus_in(self, widget, event):
+ if widget.get_text() == self._text:
+ widget.set_text("")
+ widget.modify_font(pango.FontDescription(""))
- self.window.draw_layout(gc, lx, ly, self.layout)
+ def _focus_out(self, widget, event):
+ if widget.get_text() == "":
+ widget.set_text(self.text)
+ widget.modify_font(pango.FontDescription("italic"))
diff --git a/activity/activity.info b/activity/activity.info
index d7dcd15..c871038 100644
--- a/activity/activity.info
+++ b/activity/activity.info
@@ -1,7 +1,8 @@
[Activity]
name = Analyze Journal
-activity_version = 1
+activity_version = 2
bundle_id = org.sugarlabs.AnalyzeJournal
-exec = sugar-activity activity.AnalyzeJournal -s
+exec = sugar-activity activity.AnalyzeJournal
icon = analyzejournal
license = GPLv3+
+summary = chart of Sugar Journal activity
diff --git a/charthelp.py b/charthelp.py
new file mode 100644
index 0000000..6150141
--- /dev/null
+++ b/charthelp.py
@@ -0,0 +1,30 @@
+# Help for ChartActivity
+
+from gettext import gettext as _
+
+from helpbutton import HelpButton
+
+
+def create_help(toolbar):
+ helpitem = HelpButton()
+ toolbar.insert(helpitem, -1)
+ helpitem.show()
+ helpitem.add_section(_('Basic usage'))
+ helpitem.add_paragraph(_('First select data type:'))
+ helpitem.add_paragraph(_('The free space in the Journal;'),
+ 'import-freespace')
+ helpitem.add_paragraph(_('The types of Sugar Activities you have used;'),
+ 'import-journal')
+ helpitem.add_paragraph(_('The types of blocks used in Turtle Art.'),
+ 'import-turtle')
+ helpitem.add_paragraph(_('The graph title is the same as the Activity title'))
+
+ helpitem.add_paragraph(_('You can change the type of graph:'))
+ helpitem.add_paragraph(_('Vertical bars'), 'vbar')
+ helpitem.add_paragraph(_('Horizontal bars'), 'hbar')
+ helpitem.add_paragraph(_('Lines'), 'line')
+ helpitem.add_paragraph(_('Pie'), 'pie')
+
+ helpitem.add_section(_('Saving as an image'))
+ helpitem.add_paragraph(_('In the activity toolbar you have button to save the graph as an image'),
+ 'save-as-image')
diff --git a/charts.py b/charts.py
index 5f19851..342e9f8 100644
--- a/charts.py
+++ b/charts.py
@@ -20,9 +20,9 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-import pycha.bar
-import pycha.line
-import pycha.pie
+import sugarpycha.bar
+import sugarpycha.line
+import sugarpycha.pie
import cairo
import gobject
@@ -49,7 +49,11 @@ class Chart(gobject.GObject):
self.options = {
'legend': {'hide': True},
+ 'titleFontSize': 16,
'axis': {
+ 'tickFontSize': 12,
+ 'labelFontSize': 14,
+ 'lineColor': '#b3b3b3',
'x': {
'ticks': [dict(v=i, label=l[0]) for i,
l in enumerate(data)],
@@ -60,9 +64,12 @@ class Chart(gobject.GObject):
'label': 'Y',
}
},
+ 'stroke': {
+ 'width': 3
+ },
'background': {
'chartColor': '#FFFFFF',
- 'lineColor': '#d1e5ec'
+ 'lineColor': '#CCCCCC'
},
'colorScheme': {
'name': 'gradient',
@@ -76,9 +83,9 @@ class Chart(gobject.GObject):
"""Set the chart color scheme"""
self.options["colorScheme"]["args"] = {'initialColor': color}
- def set_line_color(self, color='#d1e5ec'):
+ def set_line_color(self, color='#000000'):
"""Set the chart line color"""
- self.options["background"]["lineColor"] = color
+ self.options["stroke"]["color"] = color
def set_x_label(self, text="X"):
"""Set the X Label"""
@@ -92,7 +99,7 @@ class Chart(gobject.GObject):
"""Set chart type (vertical, horizontal, line, pie)"""
self.type = type
- def set_title(self, title="SimpleGraph Chart"):
+ def set_title(self, title="Chart"):
"""Set the chart title"""
self.options["title"] = title
@@ -104,20 +111,25 @@ class Chart(gobject.GObject):
self.height)
if self.type == "vbar":
- chart = pycha.bar.VerticalBarChart(self.surface, self.options)
+ chart = sugarpycha.bar.VerticalBarChart(self.surface, self.options)
elif self.type == "hbar":
- chart = pycha.bar.HorizontalBarChart(self.surface, self.options)
+ chart = sugarpycha.bar.HorizontalBarChart(self.surface,
+ self.options)
elif self.type == "line":
- chart = pycha.line.LineChart(self.surface, self.options)
+ chart = sugarpycha.line.LineChart(self.surface, self.options)
elif self.type == "pie":
self.options["legend"] = {"hide": "False"}
- chart = pycha.pie.PieChart(self.surface, self.options)
+ chart = sugarpycha.pie.PieChart(self.surface, self.options)
self.dataSet = [(data[0],
[[0, data[1]]]) for data in sg.chart_data]
+ else:
+ chart = sugarpycha.bar.HorizontalBarChart(self.surface,
+ self.options)
+
chart.addDataset(self.dataSet)
chart.render()
diff --git a/help.py b/help.py
deleted file mode 100644
index cfdf38d..0000000
--- a/help.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Help for AnalizeJournal
-
-from gettext import gettext as _
-
-from helpbutton import HelpButton
-
-
-def create_help(toolbar):
- helpitem = HelpButton()
- toolbar.insert(helpitem, -1)
- helpitem.add_section(_('Description'))
- helpitem.add_paragraph(_('This activity gives you the possibility to graphically, the journal usage'))
- helpitem.add_section(_('Usage'))
- helpitem.add_paragraph(_('In the area you can view the info'))
- helpitem.add_paragraph(_('You can update the data with this button'),
- 'gtk-refresh')
- helpitem.show()
diff --git a/helpbutton.py b/helpbutton.py
index c3e1569..639159d 100644
--- a/helpbutton.py
+++ b/helpbutton.py
@@ -39,14 +39,18 @@ class HelpButton(gtk.ToolItem):
self._palette = help_button.get_palette()
sw = gtk.ScrolledWindow()
- sw.set_size_request(int(gtk.gdk.screen_width() / 3),
- style.GRID_CELL_SIZE * 3)
+ sw.set_size_request(int(gtk.gdk.screen_width() / 2.8),
+ gtk.gdk.screen_height() - style.GRID_CELL_SIZE * 3)
sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
self._max_text_width = int(gtk.gdk.screen_width() / 3) - 20
self._vbox = gtk.VBox()
self._vbox.set_homogeneous(False)
- sw.add_with_viewport(self._vbox)
+
+ hbox = gtk.HBox()
+ hbox.pack_start(self._vbox, False, True, 0)
+
+ sw.add_with_viewport(hbox)
self._palette.set_content(sw)
sw.show_all()
diff --git a/icons/hbar.svg b/icons/hbar.svg
new file mode 100644
index 0000000..f042b84
--- /dev/null
+++ b/icons/hbar.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY fill_color "#808080">
+ <!ENTITY stroke_color "#FFFFFF">
+]>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="62"
+ height="60"
+ viewBox="0 0 62 60"
+ id="svg2"
+ xml:space="preserve"><g
+ transform="translate(60.127119,11.292373)"
+ id="activity-browse"
+ style="display:block">
+
+ <g
+ transform="translate(-128.87712,-22.838983)"
+ id="g7"
+ style="display:inline">
+
+
+
+
+
+
+ <g
+ transform="matrix(0.10822504,0,0,0.09945444,61.358446,34.085169)"
+ id="g6167"><g
+ transform="matrix(0,1.0881871,-0.9189596,0,426.39011,-308.03653)"
+ id="g3798"
+ style="fill:&fill_color;;fill-opacity:1;display:inline"><rect
+ width="81.443176"
+ height="272.19876"
+ rx="4.3524833"
+ ry="6.0284276"
+ x="373.97116"
+ y="55.900478"
+ id="rect2987"
+ style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:33.73588181;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /><rect
+ width="81.443077"
+ height="360.47952"
+ rx="4.3524833"
+ ry="6.0284276"
+ x="499.32275"
+ y="-32.380226"
+ id="rect3757"
+ style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:33.73588181;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /><rect
+ width="81.443077"
+ height="500.25723"
+ rx="4.3524833"
+ ry="6.0284276"
+ x="248.61969"
+ y="-172.15802"
+ id="rect3759"
+ style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:33.73588181;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /><rect
+ width="81.443314"
+ height="215.79736"
+ rx="4.3524833"
+ ry="6.0284276"
+ x="123.26795"
+ y="112.30204"
+ id="rect3761"
+ style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:33.73588181;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /></g></g></g>
+</g></svg> \ No newline at end of file
diff --git a/icons/import-freespace.svg b/icons/import-freespace.svg
new file mode 100644
index 0000000..19cf982
--- /dev/null
+++ b/icons/import-freespace.svg
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="Layer_1"
+ xml:space="preserve"><metadata
+ id="metadata18"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs16"><linearGradient
+ id="linearGradient3775"><stop
+ id="stop3777"
+ style="stop-color:#3e3e3e;stop-opacity:1"
+ offset="0" /><stop
+ id="stop3779"
+ style="stop-color:#3e3e3e;stop-opacity:0"
+ offset="1" /></linearGradient><radialGradient
+ cx="26.6255"
+ cy="27.5"
+ r="20.991501"
+ fx="26.6255"
+ fy="27.5"
+ id="radialGradient3783"
+ xlink:href="#linearGradient3775"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.1075912,0,-2.9587571)" />
+
+
+
+
+
+
+<radialGradient
+ cx="26.6255"
+ cy="27.5"
+ r="20.991501"
+ fx="26.6255"
+ fy="27.5"
+ id="radialGradient3783-6"
+ xlink:href="#linearGradient3775-4"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.1075912,0,-2.9587571)" /><linearGradient
+ id="linearGradient3775-4"><stop
+ id="stop3777-0"
+ style="stop-color:#3e3e3e;stop-opacity:1"
+ offset="0" /><stop
+ id="stop3779-0"
+ style="stop-color:#3e3e3e;stop-opacity:0"
+ offset="1" /></linearGradient></defs>
+<g
+ transform="translate(-4.1076373,0.46878985)"
+ id="g4139"><g
+ transform="translate(-2,0)"
+ id="g4135"><path
+ d="m 57.259649,14.549596 a 12.27034,11.60819 0 1 1 -24.54068,0 12.27034,11.60819 0 1 1 24.54068,0 z"
+ id="path6649"
+ style="color:#000000;fill:#999999;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:2;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /><path
+ d="M 57.257798,14.347943 A 12.27034,11.608189 0 0 1 42.4122,25.89887 l 2.577109,-11.349274 z"
+ id="path6619"
+ style="color:#000000;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:2;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /></g><g
+ transform="matrix(0.55205508,0,0,0.55205508,49.574089,17.356986)"
+ id="g4382"><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><g
+ id="g4310"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><g
+ id="g4316"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><line
+ id="line4326"
+ y2="23.725029"
+ y1="58.753029"
+ x2="-66.884659"
+ x1="-66.884659"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g><g
+ transform="matrix(0,-1,-1,0,50.521049,88.007596)"
+ id="g4770-3"><g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772-5"><polyline
+ id="polyline4774-1"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)" /><path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776-7"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></g></svg> \ No newline at end of file
diff --git a/icons/import-journal.svg b/icons/import-journal.svg
new file mode 100644
index 0000000..0c13d10
--- /dev/null
+++ b/icons/import-journal.svg
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="49.517162"
+ height="50.998993"
+ id="svg2">
+ <defs
+ id="defs4" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(-11.28338,3.0240822)"
+ id="g3103">
+ <g
+ transform="matrix(0.55205508,0,0,0.55205508,58.11605,12.3324)"
+ id="g4382">
+ <g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4310"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g>
+ <g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4316"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g>
+ <path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="-66.884659"
+ x2="-66.884659"
+ y1="58.753029"
+ y2="23.725029"
+ id="line4326" />
+ </g>
+ <g
+ transform="matrix(0,-1,-1,0,59.06301,82.98301)"
+ id="g4770-3">
+ <g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772-5">
+ <polyline
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ id="polyline4774-1" />
+ <path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776-7"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ </g>
+ <g
+ transform="matrix(0.55205508,0,0,0.55205508,80.6828,-11.330635)"
+ id="g4382-4">
+ <g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308-4"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4310-7"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312-5"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g>
+ <g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314-1"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4316-6"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318-2"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g>
+ <path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320-4"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322-2"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324-8"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="-66.884659"
+ x2="-66.884659"
+ y1="58.753029"
+ y2="23.725029"
+ id="line4326-2" />
+ </g>
+ </g>
+</svg>
diff --git a/icons/import-turtle.svg b/icons/import-turtle.svg
new file mode 100644
index 0000000..d0869da
--- /dev/null
+++ b/icons/import-turtle.svg
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata25"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs33">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+</defs><g
+ transform="matrix(0.55205508,0,0,0.55205508,44.618464,18.235971)"
+ id="g4382"><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4310"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4316"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g><path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><line
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="-66.884659"
+ x2="-66.884659"
+ y1="58.753029"
+ y2="23.725029"
+ id="line4326" /></g><g
+ id="g4084"><path
+ d="m 40.046106,34.391197 c -0.384814,0 -0.764757,-0.02436 -1.139134,-0.06959 l 0.965863,1.634591 0.951946,-1.60954 c -0.258167,0.02088 -0.515638,0.04454 -0.778675,0.04454 z"
+ id="path11"
+ style="fill:none;stroke:#ffffff;stroke-width:2.08759975;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><g
+ transform="matrix(0.69586655,0,0,0.69586655,20.911863,0.79545554)"
+ id="g13"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"><path
+ d="m 40.16,11.726 c -2.164,0 -3.958,1.555 -4.343,3.607 1.859,1.345 3.457,3.115 4.675,5.208 2.285,-0.172 4.094,-2.061 4.094,-4.39 0,-2.444 -1.982,-4.425 -4.426,-4.425 z"
+ id="path15"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
+ d="m 40.713,39.887 c -1.224,2.232 -2.86,4.131 -4.797,5.556 0.521,1.864 2.213,3.239 4.244,3.239 2.443,0 4.426,-1.98 4.426,-4.424 0,-2.255 -1.693,-4.096 -3.873,-4.371 z"
+ id="path17"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
+ d="m 14.273,39.871 c -2.253,0.206 -4.024,2.079 -4.024,4.387 0,2.443 1.98,4.424 4.424,4.424 2.064,0 3.784,-1.42 4.272,-3.332 -1.883,-1.416 -3.475,-3.289 -4.672,-5.479 z"
+ id="path19"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
+ d="m 19.026,15.437 c -0.343,-2.103 -2.154,-3.711 -4.353,-3.711 -2.444,0 -4.424,1.981 -4.424,4.424 0,2.382 1.886,4.31 4.245,4.406 1.186,-2.043 2.732,-3.784 4.532,-5.119 z"
+ id="path21"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g><path
+ d="m 40.046106,9.537627 c 1.327713,0 2.59419,0.2860012 3.770205,0.784937 0.515637,-0.7487519 0.819731,-1.6540743 0.819731,-2.6324627 0,-2.5656599 -2.079945,-4.6463009 -4.646301,-4.6463009 -2.56566,0 -4.645605,2.080641 -4.645605,4.6463009 0,0.9936975 0.314531,1.9129372 0.846173,2.6679527 1.199674,-0.5212044 2.496074,-0.820427 3.855797,-0.820427 z"
+ id="path23"
+ style="fill:none;stroke:#ffffff;stroke-width:2.08759975;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><g
+ transform="matrix(0.69586655,0,0,0.69586655,20.911863,0.79545554)"
+ id="g25"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"><path
+ d="m 43.102,30.421 c 0,4.7344 -1.6452,9.2798 -4.5706,12.6275 -2.9254,3.3478 -6.8973,5.2305 -11.0344,5.2305 -4.1371,0 -8.109,-1.8827 -11.0344,-5.2305 -2.9254,-3.3477 -4.5706,-7.8931 -4.5706,-12.6275 0,-9.7966 7.0444,-17.858 15.605,-17.858 8.5606,0 15.605,8.0614 15.605,17.858 z"
+ id="path2988"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g><g
+ transform="matrix(0.69586655,0,0,0.69586655,20.911863,0.79545554)"
+ id="g28"
+ style="fill:#ffffff;fill-opacity:1;stroke:none"><path
+ d="m 25.875,33.75 -1.542,-4.625 3.164,-2.587 3.615,2.626 -1.487,4.669 -3.75,-0.083 z"
+ id="path30"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" /><path
+ d="m 27.501,41.551 c -3.968,-0.16 -5.543,-2.009 -5.543,-2.009 l 3.57,-4.163 4.465,0.168 3.132,4.12 c 0,0 -2.89,1.994 -5.624,1.884 z"
+ id="path32"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" /><path
+ d="m 18.453,33.843 c -0.849,-2.968 0.172,-6.884 0.172,-6.884 l 4,2.167 1.493,4.629 -3.582,4.233 c 0,-10e-4 -1.465,-1.99 -2.083,-4.145 z"
+ id="path34"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" /><path
+ d="m 19.458,25.125 c 0,0 0.5,-1.958 3.039,-3.822 2.237,-1.643 4.465,-1.72 4.465,-1.72 l -0.037,4.981 -3.521,2.75 -3.946,-2.189 z"
+ id="path2998"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" /><path
+ d="M 32.084,27.834 28.625,24.959 29,19.75 c 0,0 1.834,-0.042 3.959,1.667 2.228,1.791 3.362,4.983 3.362,4.983 l -4.237,1.434 z"
+ id="path37"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" /><path
+ d="m 31.292,34.042 1.313,-4.464 4.187,-1.536 c 0,0 0.677,2.663 -0.042,5.667 -0.54,2.256 -2.084,4.361 -2.084,4.361 l -3.374,-4.028 z"
+ id="path3002"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" /></g></g><g
+ transform="matrix(0,-1,-1,0,43.9376,89.386573)"
+ id="g4770"><g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772"><polyline
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ id="polyline4774" /><path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></svg> \ No newline at end of file
diff --git a/icons/line.svg b/icons/line.svg
new file mode 100644
index 0000000..d6a1a5c
--- /dev/null
+++ b/icons/line.svg
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY fill_color "#808080">
+ <!ENTITY stroke_color "#FFFFFF">
+]>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="62"
+ height="60"
+ viewBox="0 0 62 60"
+ id="svg2"
+ xml:space="preserve"><g
+ transform="translate(60.127119,11.292373)"
+ id="activity-browse"
+ style="display:block">
+
+ <g
+ transform="translate(-128.87712,-22.838983)"
+ id="g7"
+ style="display:inline">
+
+
+
+
+
+
+ <g
+ transform="matrix(0.10822504,0,0,0.09945444,61.358446,34.085169)"
+ id="g6167"><path
+ d="m 594.07188,325.15231 0,-360.47952 L 434.51609,52.953494 274.96046,-175.105 115.4045,109.35506 l 0,215.79736 z"
+ id="rect3757"
+ style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:33.73588181;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /></g></g>
+</g></svg> \ No newline at end of file
diff --git a/icons/pie.svg b/icons/pie.svg
new file mode 100644
index 0000000..6c4cd5f
--- /dev/null
+++ b/icons/pie.svg
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY fill_color "#808080">
+ <!ENTITY stroke_color "#FFFFFF">
+]>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="62"
+ height="60"
+ viewBox="0 0 62 60"
+ id="svg2"
+ xml:space="preserve"><g
+ transform="translate(60.127119,11.292373)"
+ id="activity-browse"
+ style="display:block">
+
+ <g
+ transform="translate(-128.87712,-22.838983)"
+ id="g7"
+ style="display:inline">
+
+
+
+
+
+
+ <g
+ transform="matrix(0.10822504,0,0,0.09945444,61.358446,34.085169)"
+ id="g6167"><g
+ id="g6651"><path
+ d="m 7.8813553,48.68644 a 27.584745,27.584745 0 1 1 -55.1694913,0 27.584745,27.584745 0 1 1 55.1694913,0 z"
+ transform="matrix(8.3764085,0,0,9.1150996,519.78133,-368.75859)"
+ id="path6649"
+ style="color:#000000;fill:#999999;fill-opacity:1;stroke:&stroke_color;;stroke-width:1.65464818;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /><path
+ d="M -0.94352875,28.463043 A 27.584745,27.584745 0 1 1 -47.288136,48.686438 l 27.584746,2e-6 z"
+ transform="matrix(8.3764085,0,0,9.1150996,519.78133,-368.75859)"
+ id="path6617"
+ style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:1.65464818;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /><path
+ d="M 7.8771928,48.20725 A 27.584745,27.584745 0 0 1 -25.496945,75.655921 L -19.70339,48.68644 z"
+ transform="matrix(8.3764085,0,0,9.1150996,519.78133,-368.75859)"
+ id="path6619"
+ style="color:#000000;fill:#666666;fill-opacity:1;stroke:&stroke_color;;stroke-width:1.65464818;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /></g><path
+ d="m 7.8813553,48.68644 a 27.584745,27.584745 0 1 1 -55.1694913,0 27.584745,27.584745 0 1 1 55.1694913,0 z"
+ transform="matrix(8.3764085,0,0,9.1150996,519.78184,-368.75804)"
+ id="path6107"
+ style="color:#000000;fill:none;stroke:&stroke_color;;stroke-width:3.8608458;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /></g></g>
+</g></svg> \ No newline at end of file
diff --git a/icons/save-as-image.svg b/icons/save-as-image.svg
new file mode 100644
index 0000000..365f578
--- /dev/null
+++ b/icons/save-as-image.svg
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata25"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs33">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </defs><g
+ transform="matrix(0.55205508,0,0,0.55205508,77.118464,18.235971)"
+ id="g4382"><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4310"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4316"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g><path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><line
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="-66.884659"
+ x2="-66.884659"
+ y1="58.753029"
+ y2="23.725029"
+ id="line4326" /></g><g
+ transform="matrix(1.1623273,0,0,1.1623273,-14.422024,-12.63995)"
+ id="g3882"
+ style="fill:none;stroke:#ffffff;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"><g
+ id="g3884"
+ style="fill:none;stroke:#ffffff;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"><polygon
+ points="43.041,21.577 35.281,13.812 15.204,13.812 15.204,35.189 43.041,35.189 "
+ id="polygon3886"
+ style="fill:none;stroke:#ffffff;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><polyline
+ id="polyline3888"
+ points="35.281,13.812 35.281,21.577 43.041,21.577 "
+ style="fill:none;stroke:#ffffff;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g><path
+ d="m 19.426691,12.275117 c -4.727185,0 -8.666312,4.714399 -8.666312,4.714399 0,0 3.939127,4.737646 8.666312,4.735322 4.729509,-0.0046 8.668637,-4.739971 8.668637,-4.739971 0,0 -3.939128,-4.713237 -8.668637,-4.70975 z m 0,8.039818 c -1.830666,0 -3.314958,-1.484292 -3.314958,-3.31612 0,-1.827179 1.484292,-3.314958 3.314958,-3.314958 1.828341,0 3.312632,1.487779 3.312632,3.314958 0,1.831828 -1.484291,3.31612 -3.312632,3.31612 z"
+ id="path3890"
+ style="fill:#ffffff;fill-opacity:1;stroke:none;display:inline" /><circle
+ cx="29.207001"
+ cy="25.863001"
+ r="1.294"
+ transform="matrix(1.1623273,0,0,1.1623273,-14.520241,-13.061294)"
+ id="circle3892"
+ style="fill:#ffffff;fill-opacity:1;stroke:none;display:inline" /><g
+ transform="matrix(1,0,0,-1,-24.850339,47.707501)"
+ id="g4770"><g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772"><polyline
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ id="polyline4774" /><path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></svg> \ No newline at end of file
diff --git a/icons/vbar.svg b/icons/vbar.svg
new file mode 100644
index 0000000..4d06f10
--- /dev/null
+++ b/icons/vbar.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY fill_color "#808080">
+ <!ENTITY stroke_color "#FFFFFF">
+]>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="62"
+ height="60"
+ viewBox="0 0 62 60"
+ id="svg2"
+ xml:space="preserve"><g
+ transform="translate(60.127119,11.292373)"
+ id="activity-browse"
+ style="display:block">
+
+ <g
+ transform="translate(-128.87712,-22.838983)"
+ id="g7"
+ style="display:inline">
+
+
+
+
+
+
+ <g
+ transform="matrix(0.10822504,0,0,0.09945444,61.358446,34.085169)"
+ id="g6167"><g
+ transform="translate(2.7213003,-2.9469823)"
+ id="g3798"
+ style="fill:&fill_color;;fill-opacity:1"><rect
+ width="81.443176"
+ height="272.19876"
+ rx="4.3524833"
+ ry="6.0284276"
+ x="373.97116"
+ y="55.900478"
+ id="rect2987"
+ style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:33.73588181;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /><rect
+ width="81.443077"
+ height="360.47952"
+ rx="4.3524833"
+ ry="6.0284276"
+ x="499.32275"
+ y="-32.380226"
+ id="rect3757"
+ style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:33.73588181;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /><rect
+ width="81.443077"
+ height="500.25723"
+ rx="4.3524833"
+ ry="6.0284276"
+ x="248.61969"
+ y="-172.15802"
+ id="rect3759"
+ style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:33.73588181;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /><rect
+ width="81.443314"
+ height="215.79736"
+ rx="4.3524833"
+ ry="6.0284276"
+ x="123.26795"
+ y="112.30204"
+ id="rect3761"
+ style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:33.73588181;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /></g></g></g>
+</g></svg> \ No newline at end of file
diff --git a/readers.py b/readers.py
new file mode 100644
index 0000000..7106b0b
--- /dev/null
+++ b/readers.py
@@ -0,0 +1,340 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# readers.py by:
+# Agustin Zubiaga <aguz@sugarlabs.org>
+# Walter Bender <walter@sugarlabs.org>
+
+# 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import glob
+import statvfs
+
+from gettext import gettext as _
+
+from sugar import env
+from sugar import profile
+
+
+class FreeSpaceReader():
+ """Reader for Free Space
+ Measure free space on disk.
+ """
+
+ def __init__(self):
+ """Import chart data from file."""
+
+ space = self._get_space()
+ self._reader = ((_('Free space'), space[0]),
+ (_('Used space'), space[1]))
+ self.xlabel = ""
+ self.ylabel = ""
+
+ def get_chart_data(self):
+ """Return data suitable for pyCHA."""
+
+ chart_data = []
+
+ for row in self._reader:
+ print row
+ label, value = row[0], row[1]
+
+ if label == "XLabel":
+ self.xlabel = value
+
+ elif label == "YLabel":
+ self.ylabel = value
+
+ else:
+ chart_data.append((label, float(value)))
+
+ return chart_data
+
+ def _get_space(self):
+ stat = os.statvfs(env.get_profile_path())
+ free_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BAVAIL]
+ total_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BLOCKS]
+
+ free_space = self._get_MBs(free_space)
+ total_space = self._get_MBs(total_space)
+ used_space = total_space - free_space
+
+ return free_space, used_space, total_space
+
+ def _get_MBs(self, space):
+ space = space / (1024 * 1024)
+
+ return space
+
+ def _get_GBs(self, space):
+ space = space / 1024
+
+ return space
+
+ def get_labels_name(self):
+ """Return the h_label and y_label names."""
+
+ return self.xlabel, self.ylabel
+
+
+class TurtleReader():
+ """Reader for Journal activity
+
+ Import chart data from journal activity analysis
+ """
+
+ TACAT = {'clean':'forward', 'forward':'forward', 'back':'forward',
+ 'left':'forward', 'right':'forward', 'arc': 'arc',
+ 'xcor': 'coord', 'ycor': 'coord', 'heading': 'coord',
+ 'setxy2': 'setxy', 'seth': 'setxy', 'penup': 'pen', 'pendown': 'pen',
+ 'setpensize': 'pen', 'setcolor': 'pen', 'pensize': 'pen',
+ 'color': 'pen', 'setshade': 'pen', 'setgray': 'pen', 'shade': 'pen',
+ 'gray': 'pen', 'fillscreen': 'pen', 'startfill': 'fill',
+ 'stopfill': 'fill', 'plus2': 'number', 'minus2': 'number',
+ 'product2': 'number', 'division2': 'number', 'remainder2': 'number',
+ 'sqrt': 'number', 'identity2': 'number', 'and2': 'boolean',
+ 'or2': 'boolean', 'not': 'boolean', 'greater2': 'boolean',
+ 'less2': 'boolean', 'equal2': 'boolean', 'random': 'random',
+ 'repeat': 'repeat', 'forever': 'repeat', 'if': 'ifthen',
+ 'ifelse': 'ifthen', 'while': 'ifthen', 'until': 'ifthen',
+ 'hat': 'action', 'stack': 'action', 'storein': 'box', 'box': 'box',
+ 'luminance': 'sensor', 'mousex': 'sensor', 'mousey': 'sensor',
+ 'mousebutton2': 'sensor', 'keyboard': 'sensor', 'kbinput': 'sensor',
+ 'readpixel': 'sensor', 'see': 'sensor', 'time': 'sensor',
+ 'sound': 'sensor', 'volume': 'sensor', 'pitch': 'sensor',
+ 'resistance': 'sensor', 'voltage': 'sensor', 'video': 'media',
+ 'wait': 'media', 'camera': 'media', 'journal': 'media',
+ 'audio': 'media', 'show': 'media', 'setscale': 'media',
+ 'savepix': 'media', 'savesvg': 'media', 'mediawait': 'media',
+ 'mediapause': 'media', 'mediastop': 'media', 'mediaplay': 'media',
+ 'speak': 'media', 'sinewave': 'media', 'description': 'media',
+ 'push':'extras', 'pop':'extras', 'printheap':'extras',
+ 'clearheap':'extras', 'isheapempty2':'extras', 'chr':'extras',
+ 'int':'extras', 'myfunction': 'python', 'userdefined': 'python',
+ 'loadblock': 'python', 'loadpalette': 'python'}
+ TAPAL = {'forward': 'turtlep', 'arc': 'turtlep', 'coord': 'turtlep',
+ 'setxy': 'turtlep', 'pen': 'penp', 'fill': 'penp', 'number': 'numberp',
+ 'random': 'numberp', 'boolean': 'numberp', 'repeat': 'flowp',
+ 'ifthen': 'flowp', 'action': 'boxp', 'box': 'boxp',
+ 'sensor': 'sensorp', 'media': 'mediap', 'extras': 'extrasp',
+ 'python': 'extrasp'}
+ TASCORE = {'forward': 3, 'arc': 3, 'setxy': 2.5, 'coord': 4, 'turtlep': 5,
+ 'pen': 2.5, 'fill': 2.5, 'penp': 5,
+ 'number': 2.5, 'boolean': 2.5, 'random': 2.5, 'numberp': 0,
+ 'repeat': 2.5, 'ifthen': 7.5, 'flowp': 10,
+ 'box': 7.5, 'action': 7.5, 'boxp': 0,
+ 'media': 5, 'mediap': 0,
+ 'python': 5, 'extras': 5, 'extrasp': 0,
+ 'sensor': 5, 'sensorp': 0}
+ PALS = ['turtlep', 'penp', 'numberp', 'flowp', 'boxp', 'sensorp', 'mediap',
+ 'extrasp']
+ PALNAMES = [_('turtle'), _('pen'), _('number'), _('flow'), _('box'),
+ _('sensor'), _('media'), _('extras')]
+
+ def hasturtleblocks(self, path):
+ ''' Parse turtle block data and generate score based on rubric '''
+
+ fd = open(path)
+ blocks = []
+ # block name is second token in each line
+ for line in fd:
+ tokens = line.split(',')
+ if len(tokens) > 1:
+ token = tokens[1].strip('" [')
+ blocks.append(token)
+
+ score = []
+ for i in range(len(self.PALS)):
+ score.append([self.PALNAMES[i], 0])
+ cats = []
+ pals = []
+
+ for b in blocks:
+ if b in self.TACAT:
+ if not self.TACAT[b] in cats:
+ cats.append(self.TACAT[b])
+ for c in cats:
+ if c in self.TAPAL:
+ if not self.TAPAL[c] in pals:
+ pals.append(self.TAPAL[c])
+
+ for c in cats:
+ if c in self.TASCORE:
+ score[self.PALS.index(self.TAPAL[c])][1] += self.TASCORE[c]
+
+ for p in pals:
+ if p in self.TASCORE:
+ score[self.PALS.index(p)][1] += self.TASCORE[p]
+
+ return score
+
+ def __init__(self, file):
+
+ self._reader = self.hasturtleblocks(file)
+ self.xlabel = ""
+ self.ylabel = ""
+
+ def get_chart_data(self):
+ """Return data suitable for pyCHA."""
+
+ chart_data = []
+
+ for row in self._reader:
+ label, value = row[0], row[1]
+
+ if label == "XLabel":
+ self.xlabel = value
+
+ elif label == "YLabel":
+ self.ylabel = value
+
+ else:
+ chart_data.append((label, float(value)))
+
+ return chart_data
+
+ def get_labels_name(self):
+ """Return the h_label and y_label names."""
+
+ return self.xlabel, self.ylabel
+
+
+MAX = 19
+DIROFINTEREST = 'datastore'
+class ParseJournal():
+ ''' Simple parser of datastore '''
+
+ def __init__(self):
+ self._dsdict = {}
+ self._activity_name = []
+ self._activity_count = []
+ homepath = os.environ['HOME']
+
+ for path in glob.glob(os.path.join(homepath, '.sugar', '*')):
+ if isdsdir(path):
+ self._dsdict[os.path.basename(path)] = []
+ dsobjdirs = glob.glob(
+ os.path.join(path, DIROFINTEREST, '??'))
+ for dsobjdir in dsobjdirs:
+ dsobjs = glob.glob(os.path.join(dsobjdir, '*'))
+ for dsobj in dsobjs:
+ self._dsdict[os.path.basename(path)].append({})
+ activity = isactivity(dsobj)
+ if not activity:
+ self._dsdict[os.path.basename(path)][-1][
+ 'activity'] = 'media object'
+ else:
+ self._dsdict[os.path.basename(path)][-1][
+ 'activity'] = activity
+
+ for k, v in self._dsdict.iteritems():
+ for a in v:
+ if 'activity' in a:
+ if a['activity'] in self._activity_name:
+ i = self._activity_name.index(a['activity'])
+ self._activity_count[i] += 1
+ else:
+ self._activity_name.append(a['activity'])
+ self._activity_count.append(1)
+
+ def get_sorted(self):
+ activity_tuples = []
+ for i in range(len(self._activity_name)):
+ activity_tuples.append((self._activity_name[i].replace('Activity',
+ ''),
+ self._activity_count[i]))
+ sorted_tuples = sorted(activity_tuples, key=lambda x: x[1])
+ activity_list = []
+ count = 0
+ length = len(sorted_tuples)
+ for i in range(length):
+ if i < MAX:
+ activity_list.append([sorted_tuples[length - i - 1][0],
+ sorted_tuples[length - i - 1][1]])
+ else:
+ count += sorted_tuples[length - i - 1][1]
+ if count > 0:
+ activity_list.append([_('other'), count])
+ return activity_list
+
+
+class JournalReader():
+ """Reader for Journal activity
+
+ Import chart data from journal activity analysis
+ """
+
+ def __init__(self):
+
+ self._reader = ParseJournal().get_sorted()
+ self.xlabel = ""
+ self.ylabel = ""
+
+ def get_chart_data(self):
+ """Return data suitable for pyCHA."""
+
+ chart_data = []
+
+ for row in self._reader:
+ label, value = row[0], row[1]
+
+ if label == "XLabel":
+ self.xlabel = value
+
+ elif label == "YLabel":
+ self.ylabel = value
+
+ else:
+ chart_data.append((label, float(value)))
+
+ return chart_data
+
+ def get_labels_name(self):
+ """Return the h_label and y_label names."""
+
+ return self.xlabel, self.ylabel
+
+
+def hascomponent(path, component):
+ ''' Return metadata attribute, if any '''
+ if not os.path.exists(os.path.join(path, 'metadata')):
+ return False
+ if not os.path.exists(os.path.join(path, 'metadata', component)):
+ return False
+ fd = open(os.path.join(path, 'metadata', component))
+ data = fd.readline()
+ fd.close()
+ if len(data) == 0:
+ return False
+ return data
+
+
+def isactivity(path):
+ ''' Return activity name '''
+ activity = hascomponent(path, 'activity')
+ if not activity:
+ return False
+ else:
+ return activity.split('.')[-1]
+
+
+def isdsdir(path):
+ ''' Only interested if it is a datastore directory '''
+ if not os.path.isdir(path):
+ return False
+ if not os.path.exists(os.path.join(path, DIROFINTEREST)):
+ return False
+ return True
diff --git a/pycha/__init__.py b/sugarpycha/__init__.py
index 35bba09..35bba09 100644
--- a/pycha/__init__.py
+++ b/sugarpycha/__init__.py
diff --git a/pycha/bar.py b/sugarpycha/bar.py
index 8a3168a..fa3698d 100644
--- a/pycha/bar.py
+++ b/sugarpycha/bar.py
@@ -15,9 +15,9 @@
# You should have received a copy of the GNU Lesser General Public License
# along with PyCha. If not, see <http://www.gnu.org/licenses/>.
-from pycha.chart import Chart, uniqueIndices
-from pycha.color import hex2rgb
-from pycha.utils import safe_unicode
+from sugarpycha.chart import Chart, uniqueIndices
+from sugarpycha.color import hex2rgb
+from sugarpycha.utils import safe_unicode
class BarChart(Chart):
@@ -43,7 +43,7 @@ class BarChart(Chart):
if len(uniqx) == 1:
self.minxdelta = 1.0
else:
- self.minxdelta = min([abs(uniqx[j] - uniqx[j-1])
+ self.minxdelta = min([abs(uniqx[j] - uniqx[j - 1])
for j in range(1, len(uniqx))])
k = self.minxdelta * self.xscale
@@ -70,7 +70,7 @@ class BarChart(Chart):
h = self.layout.chart.h * bar.h
if (w < 1 or h < 1) and self.options.yvals.skipSmallValues:
- return # don't draw when the bar is too small
+ return # don't draw when the bar is too small
if self.options.stroke.shadow:
cx.set_source_rgba(0, 0, 0, 0.15)
@@ -132,7 +132,6 @@ class VerticalBarChart(BarChart):
xval, yval, yerr = item
else:
xval, yval = item
- yerr = 0.0
x = (((xval - self.minxval) * self.xscale)
+ self.barMargin + (i * self.barWidthForSet))
@@ -154,7 +153,7 @@ class VerticalBarChart(BarChart):
self.xticks = [(tick[0] + offset, tick[1]) for tick in self.xticks]
def _getShadowRectangle(self, x, y, w, h):
- return (x-2, y-2, w+4, h+2)
+ return (x - 2, y - 2, w + 4, h + 2)
def _renderYVal(self, cx, label, labelW, labelH, barX, barY, barW, barH):
x = barX + (barW / 2.0) - (labelW / 2.0)
@@ -238,7 +237,7 @@ class HorizontalBarChart(BarChart):
self._renderLine(cx, tick, False)
def _getShadowRectangle(self, x, y, w, h):
- return (x, y-2, w+2, h+4)
+ return (x, y - 2, w + 2, h + 4)
def _renderXAxisLabel(self, cx, labelText):
labelText = self.options.axis.x.label
diff --git a/pycha/chart.py b/sugarpycha/chart.py
index 5be11cd..2ce6e05 100644
--- a/pycha/chart.py
+++ b/sugarpycha/chart.py
@@ -21,8 +21,8 @@ import math
import cairo
-from pycha.color import ColorScheme, hex2rgb, DEFAULT_COLOR
-from pycha.utils import safe_unicode
+from sugarpycha.color import ColorScheme, hex2rgb, DEFAULT_COLOR
+from sugarpycha.utils import safe_unicode
class Chart(object):
@@ -169,7 +169,7 @@ class Chart(object):
if x_range_is_defined:
self.minxval, self.maxxval = self.options.axis.x.range
else:
- xdata = [pair[0] for pair in reduce(lambda a, b: a+b, stores)]
+ xdata = [pair[0] for pair in reduce(lambda a, b: a + b, stores)]
self.minxval = float(min(xdata))
self.maxxval = float(max(xdata))
if self.minxval * self.maxxval > 0 and self.minxval > 0:
@@ -185,7 +185,7 @@ class Chart(object):
if y_range_is_defined:
self.minyval, self.maxyval = self.options.axis.y.range
else:
- ydata = [pair[1] for pair in reduce(lambda a, b: a+b, stores)]
+ ydata = [pair[1] for pair in reduce(lambda a, b: a + b, stores)]
self.minyval = float(min(ydata))
self.maxyval = float(max(ydata))
if self.minyval * self.maxyval > 0 and self.minyval > 0:
@@ -197,7 +197,7 @@ class Chart(object):
else:
self.yscale = 1.0 / self.yrange
- if self.minyval * self.maxyval < 0: # different signs
+ if self.minyval * self.maxyval < 0: # different signs
self.origin = abs(self.minyval) * self.yscale
else:
self.origin = 0.0
@@ -365,7 +365,7 @@ class Chart(object):
cx.line_to(x2, y2)
cx.close_path()
cx.stroke()
-
+ cx.set_source_rgb(*hex2rgb('#000000'))
cx.select_font_face(self.options.axis.tickFont,
cairo.FONT_SLANT_NORMAL,
cairo.FONT_WEIGHT_NORMAL)
@@ -384,6 +384,7 @@ class Chart(object):
y = -height / 2.0
cx.move_to(x - xb, y - yb)
cx.show_text(label)
+ cx.set_source_rgb(*hex2rgb(self.options.axis.lineColor))
if self.debug:
cx.rectangle(x, y, width, height)
cx.stroke()
@@ -393,6 +394,7 @@ class Chart(object):
y -= height / 2.0
cx.move_to(x - xb, y - yb)
cx.show_text(label)
+ cx.set_source_rgb(*hex2rgb(self.options.axis.lineColor))
if self.debug:
cx.rectangle(x, y, width, height)
cx.stroke()
@@ -405,7 +407,7 @@ class Chart(object):
y = self.layout.y_ticks.y + tick[0] * self.layout.y_ticks.h
text_position = ((self.layout.y_tick_labels.x
- + self.layout.y_tick_labels.w / 2.0), y)
+ + self.layout.y_tick_labels.w / 2.0 - 5), y)
return self._renderTick(cx, tick,
x, y,
@@ -419,7 +421,7 @@ class Chart(object):
x = self.layout.x_ticks.x + tick[0] * self.layout.x_ticks.w
y = self.layout.x_ticks.y
- text_position = (x, (self.layout.x_tick_labels.y
+ text_position = (x, (self.layout.x_tick_labels.y + 5
+ self.layout.x_tick_labels.h / 2.0))
return self._renderTick(cx, tick,
@@ -493,7 +495,6 @@ class Chart(object):
return
cx.save()
- cx.set_source_rgb(*hex2rgb(self.options.axis.lineColor))
cx.set_line_width(self.options.axis.lineWidth)
if not self.options.axis.y.hide:
@@ -504,6 +505,7 @@ class Chart(object):
if self.options.axis.y.label:
self._renderYAxisLabel(cx, self.options.axis.y.label)
+ cx.set_source_rgb(*hex2rgb(self.options.axis.lineColor))
self._renderYAxis(cx)
if not self.options.axis.x.hide:
@@ -514,6 +516,7 @@ class Chart(object):
if self.options.axis.x.label:
self._renderXAxisLabel(cx, self.options.axis.x.label)
+ cx.set_source_rgb(*hex2rgb(self.options.axis.lineColor))
self._renderXAxis(cx)
cx.restore()
@@ -534,7 +537,7 @@ class Chart(object):
x = (self.layout.title.x
+ self.layout.title.w / 2.0
- title_width / 2.0)
- y = self.layout.title.y - extents[1]
+ y = self.layout.title.y - extents[1] - 10
cx.move_to(x, y)
cx.show_text(title)
@@ -651,14 +654,14 @@ class Layout(object):
self.chart = Area()
self._areas = (
- (self.title, (1, 126/255.0, 0)), # orange
- (self.y_label, (41/255.0, 91/255.0, 41/255.0)), # grey
- (self.x_label, (41/255.0, 91/255.0, 41/255.0)), # grey
- (self.y_tick_labels, (0, 115/255.0, 0)), # green
- (self.x_tick_labels, (0, 115/255.0, 0)), # green
- (self.y_ticks, (229/255.0, 241/255.0, 18/255.0)), # yellow
- (self.x_ticks, (229/255.0, 241/255.0, 18/255.0)), # yellow
- (self.chart, (75/255.0, 75/255.0, 1.0)), # blue
+ (self.title, (1, 126 / 255.0, 0)), # orange
+ (self.y_label, (41 / 255.0, 91 / 255.0, 41 / 255.0)), # grey
+ (self.x_label, (41 / 255.0, 91 / 255.0, 41 / 255.0)), # grey
+ (self.y_tick_labels, (0, 115 / 255.0, 0)), # green
+ (self.x_tick_labels, (0, 115 / 255.0, 0)), # green
+ (self.y_ticks, (229 / 255.0, 241 / 255.0, 18 / 255.0)), # yellow
+ (self.x_ticks, (229 / 255.0, 241 / 255.0, 18 / 255.0)), # yellow
+ (self.chart, (75 / 255.0, 75 / 255.0, 1.0)), # blue
)
def update(self, cx, options, width, height, xticks, yticks):
@@ -758,7 +761,7 @@ class Layout(object):
if not axis.hide:
extents = [cx.text_extents(safe_unicode(
tick[1], options.encoding,
- ))[2:4] # get width and height as a tuple
+ ))[2:4] # get width and height as a tuple
for tick in ticks]
if extents:
widths, heights = zip(*extents)
@@ -850,10 +853,10 @@ DEFAULT_OPTIONS = Option(
bottom=10,
),
stroke=Option(
- color='#ffffff',
+ color='#000000',
hide=False,
shadow=True,
- width=2
+ width=1
),
yvals=Option(
show=False,
diff --git a/pycha/color.py b/sugarpycha/color.py
index b01e0e1..fcf0784 100644
--- a/pycha/color.py
+++ b/sugarpycha/color.py
@@ -18,7 +18,7 @@
import math
-from pycha.utils import clamp
+from sugarpycha.utils import clamp
DEFAULT_COLOR = '#3c581a'
@@ -36,9 +36,9 @@ def hex2rgb(hexstring, digits=2):
return hexstring
top = float(int(digits * 'f', 16))
- r = int(hexstring[1:digits+1], 16)
- g = int(hexstring[digits+1:digits*2+1], 16)
- b = int(hexstring[digits*2+1:digits*3+1], 16)
+ r = int(hexstring[1:digits + 1], 16)
+ g = int(hexstring[digits + 1:digits * 2 + 1], 16)
+ b = int(hexstring[digits * 2 + 1:digits * 3 + 1], 16)
return r / top, g / top, b / top
diff --git a/pycha/line.py b/sugarpycha/line.py
index 71116b1..07be5e6 100644
--- a/pycha/line.py
+++ b/sugarpycha/line.py
@@ -15,8 +15,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with PyCha. If not, see <http://www.gnu.org/licenses/>.
-from pycha.chart import Chart
-from pycha.color import hex2rgb
+from sugarpycha.chart import Chart
+from sugarpycha.color import hex2rgb
class LineChart(Chart):
@@ -83,7 +83,6 @@ class LineChart(Chart):
cx.set_source_rgb(*self.colorScheme[storeName])
cx.stroke()
-
cx.save()
cx.set_line_width(self.options.stroke.width)
if self.options.shouldFill:
diff --git a/pycha/pie.py b/sugarpycha/pie.py
index 9585c37..12544dc 100644
--- a/pycha/pie.py
+++ b/sugarpycha/pie.py
@@ -19,8 +19,8 @@ import math
import cairo
-from pycha.chart import Chart, Option, Layout, Area, get_text_extents
-from pycha.color import hex2rgb
+from sugarpycha.chart import Chart, Option, Layout, Area, get_text_extents
+from sugarpycha.color import hex2rgb
class PieChart(Chart):
@@ -55,19 +55,19 @@ class PieChart(Chart):
"""Evaluates pie ticks"""
self.xticks = []
if self.options.axis.x.ticks:
- lookup = dict([(slice.xval, slice) for slice in self.slices])
+ lookup = dict([(_slice.xval, _slice) for _slice in self.slices])
for tick in self.options.axis.x.ticks:
if not isinstance(tick, Option):
tick = Option(tick)
- slice = lookup.get(tick.v, None)
+ _slice = lookup.get(tick.v, None)
label = tick.label or str(tick.v)
- if slice is not None:
- label += ' (%.1f%%)' % (slice.fraction * 100)
+ if _slice is not None:
+ label += ' (%.1f%%)' % (_slice.fraction * 100)
self.xticks.append((tick.v, label))
else:
- for slice in self.slices:
- label = '%s (%.1f%%)' % (slice.name, slice.fraction * 100)
- self.xticks.append((slice.xval, label))
+ for _slice in self.slices:
+ label = '%s (%.1f%%)' % (_slice.name, _slice.fraction * 100)
+ self.xticks.append((_slice.xval, label))
def _renderLines(self, cx):
"""Aux function for _renderBackground"""
@@ -235,12 +235,12 @@ class PieLayout(Layout):
self.radius = min(self.chart.w / 2.0, self.chart.h / 2.0)
for tick in xticks:
- slice = lookup.get(tick[0], None)
+ _slice = lookup.get(tick[0], None)
width, height = get_text_extents(cx, tick[1],
options.axis.tickFont,
options.axis.tickFontSize,
options.encoding)
- angle = slice.getNormalisedAngle()
+ angle = _slice.getNormalisedAngle()
radius = self._get_min_radius(angle, centerx, centery,
width, height)
self.radius = min(self.radius, radius)
@@ -248,8 +248,8 @@ class PieLayout(Layout):
# Now that we now the radius we move the ticks as close as we can
# to the circle
for i, tick in enumerate(xticks):
- slice = lookup.get(tick[0], None)
- angle = slice.getNormalisedAngle()
+ _slice = lookup.get(tick[0], None)
+ angle = _slice.getNormalisedAngle()
self.ticks[i] = self._get_tick_position(self.radius, angle,
self.ticks[i],
centerx, centery)
diff --git a/pycha/polygonal.py b/sugarpycha/polygonal.py
index b470c87..bb5ced6 100644
--- a/pycha/polygonal.py
+++ b/sugarpycha/polygonal.py
@@ -19,10 +19,10 @@ import math
import cairo
-from pycha.chart import Chart
-from pycha.line import Point
-from pycha.color import hex2rgb
-from pycha.utils import safe_unicode
+from sugarpycha.chart import Chart
+from sugarpycha.line import Point
+from sugarpycha.color import hex2rgb
+from sugarpycha.utils import safe_unicode
class PolygonalChart(Chart):
diff --git a/pycha/radial.py b/sugarpycha/radial.py
index 9055e26..8bbd1f9 100644
--- a/pycha/radial.py
+++ b/sugarpycha/radial.py
@@ -19,10 +19,10 @@ import math
import cairo
-from pycha.chart import Chart
-from pycha.line import Point
-from pycha.color import hex2rgb
-from pycha.utils import safe_unicode
+from sugarpycha.chart import Chart
+from sugarpycha.line import Point
+from sugarpycha.color import hex2rgb
+from sugarpycha.utils import safe_unicode
class RadialChart(Chart):
diff --git a/pycha/scatter.py b/sugarpycha/scatter.py
index 27656de..001e786 100644
--- a/pycha/scatter.py
+++ b/sugarpycha/scatter.py
@@ -17,7 +17,7 @@
import math
-from pycha.line import LineChart
+from sugarpycha.line import LineChart
class ScatterplotChart(LineChart):
diff --git a/pycha/stackedbar.py b/sugarpycha/stackedbar.py
index 92fdc7f..3daf6e3 100644
--- a/pycha/stackedbar.py
+++ b/sugarpycha/stackedbar.py
@@ -15,8 +15,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with PyCha. If not, see <http://www.gnu.org/licenses/>.
-from pycha.bar import BarChart, VerticalBarChart, HorizontalBarChart, Rect
-from pycha.chart import uniqueIndices
+from sugarpycha.bar import BarChart, VerticalBarChart, HorizontalBarChart, Rect
+from sugarpycha.chart import uniqueIndices
class StackedBarChart(BarChart):
@@ -35,7 +35,7 @@ class StackedBarChart(BarChart):
# Fix the yscale as we accumulate the y values
stores = self._getDatasetsValues()
n_stores = len(stores)
- flat_y = [pair[1] for pair in reduce(lambda a, b: a+b, stores)]
+ flat_y = [pair[1] for pair in reduce(lambda a, b: a + b, stores)]
store_size = len(flat_y) / n_stores
accum = [sum(flat_y[j]for j in xrange(i,
i + store_size * n_stores,
@@ -55,7 +55,7 @@ class StackedBarChart(BarChart):
if len(uniqx) == 1:
self.minxdelta = 1.0
else:
- self.minxdelta = min([abs(uniqx[j] - uniqx[j-1])
+ self.minxdelta = min([abs(uniqx[j] - uniqx[j - 1])
for j in range(1, len(uniqx))])
k = self.minxdelta * self.xscale
diff --git a/pycha/utils.py b/sugarpycha/utils.py
index aefc5b4..9e1b692 100644
--- a/pycha/utils.py
+++ b/sugarpycha/utils.py
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with PyCha. If not, see <http://www.gnu.org/licenses/>.
+
def clamp(minValue, maxValue, value):
"""Make sure value is between minValue and maxValue"""
if value < minValue:
diff --git a/utils.py b/utils.py
new file mode 100644
index 0000000..80716d8
--- /dev/null
+++ b/utils.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# utils.py by:
+# Agustin Zubiaga <aguzubiaga97@gmail.com>
+
+# 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import gtk
+
+from sugar import profile
+
+
+def rgb2html(color):
+ """Returns a html string from a Gdk color"""
+ red = "%x" % int(color.red / 65535.0 * 255)
+ if len(red) == 1:
+ red = "0%s" % red
+
+ green = "%x" % int(color.green / 65535.0 * 255)
+
+ if len(green) == 1:
+ green = "0%s" % green
+
+ blue = "%x" % int(color.blue / 65535.0 * 255)
+
+ if len(blue) == 1:
+ blue = "0%s" % blue
+
+ new_color = "#%s%s%s" % (red, green, blue)
+
+ return new_color
+
+
+def get_user_fill_color(type='gdk'):
+ """Returns the user fill color"""
+ color = profile.get_color()
+
+ if type == 'gdk':
+ rcolor = gtk.gdk.Color(color.get_fill_color())
+
+ elif type == 'str':
+ rcolor = color.get_fill_color()
+
+ return rcolor
+
+
+def get_user_stroke_color(type='gdk'):
+ """Returns the user stroke color"""
+ color = profile.get_color()
+
+ if type == 'gdk':
+ rcolor = gtk.gdk.Color(color.get_stroke_color())
+
+ elif type == 'str':
+ rcolor = color.get_stroke_color()
+
+ return rcolor
+
+
+def get_chart_file(activity_dir):
+ """Returns a path for write the chart in a png image"""
+ chart_file = os.path.join(activity_dir, "chart-1.png")
+ num = 0
+
+ while os.path.exists(chart_file):
+ num += 1
+ chart_file = os.path.join(activity_dir, "chart-" + str(num) + ".png")
+
+ return chart_file
+
+
+def get_decimals(number):
+ """Returns the decimals count of a number"""
+ return str(len(number.split('.')[1]))
+
+
+def get_channels():
+ path = os.path.join('/sys/class/dmi/id', 'product_version')
+ try:
+ product = open(path).readline().strip()
+
+ except:
+ product = None
+
+ if product == '1' or product == '1.0':
+ return 1
+
+ else:
+ return 2