#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2012 S. Daniel Francis # # 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 Street, Fifth Floor, Boston, # MA 02110-1301, USA. import json import gtk from math import * from sugar import profile from graph import Graph from graph import SCALE_TYPE_DEC from graph import SCALE_TYPE_RAD from graph import SCALE_TYPE_CUST from functions import FunctionsList x_res = 1 x_max = '5.0' x_min = '-5.0' x_scale = '1.0' y_max = '3.0' y_min = '-3.0' y_scale = '1.0' 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 class GraphPlotterCanvas(gtk.HPaned): #def write_file(self, file_path): #x, y, w, h = self.graph.get_allocation() #pix_buffer = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, w, h) #pix_buffer.get_from_drawable(self.graph.pix_map, #self.graph.pix_map.get_colormap(), #0, 0, 0, 0, w, h) #pix_buffer.save(file_path, 'png') def write_file(self, path): jfile = open(path, 'w') _list = [] for i in self.functions_list.get_list(): _list.append([rgb2html(i[0]), i[2]]) graph_props = {'list': _list, 'xmin': self.graph.xmin, 'xmax': self.graph.xmax, 'ymin': self.graph.ymin, 'ymax': self.graph.ymax} try: json.dump(graph_props, jfile) finally: jfile.close() def read_file(self, path): jfile = open(path, 'r') try: graph_props = json.load(jfile) self.functions_list.model.clear() for i in graph_props['list']: self.functions_list.append_function(i[0], i[1]) finally: jfile.close() def __init__(self, toolbar_box, activity): gtk.HPaned.__init__(self) self.activity = activity self.x_max = x_max self.x_min = x_min self.x_scale = x_scale self.y_max = y_max self.y_min = y_min self.y_scale = y_scale self._resize_queue = 4 self.profile_color = profile.get_color() self.graph = Graph() self.activity.connect('key-press-event', self.graph._key_press_event) self.activity.connect('key-release-event', self.graph._key_release_event) self.graph.connect('update-x-y', self._update_coordinates) self.graph.reload_functions([('sin(x)', gtk.gdk.color_parse(self.profile_color.get_fill_color()))]) self.graph.connect('repopulate-parameter-entries', self.parameter_entries_repopulate) self.toolbar_box = toolbar_box self.toolbar_box.connect('save-png', self.graph.save_png) self.toolbar_box.connect('append-function', self._append_function) self.toolbar_box.connect('remove-function', self._remove_function) self.toolbar_box.connect('color-updated', self._update_line_color) self.toolbar_box.connect('evaluate', self._evaluate_cb) # FIXME: Have to connect correctly all the motions in toolbar self.toolbar_box.connect('motion_notify_event', self._event_motion) self.toolbar_box.view_toolbar.connect('zoom-in', self.zoom_in) self.toolbar_box.view_toolbar.connect('zoom-out', self.zoom_out) self.toolbar_box.view_toolbar.connect('zoom-reset', self.zoom_reset) self.toolbar_box.view_toolbar.connect('scale-range', self.update_parameters) self.toolbar_box.view_toolbar.connect('fullscreen', self._fullscreen) self.toolbar_box.view_toolbar.connect('connect-points', self.connect_points) self.toolbar_box.view_toolbar.connect('decimal-scale', self.scale_dec) self.toolbar_box.view_toolbar.connect('radian-scale', self.scale_rad) self.toolbar_box.view_toolbar.connect('custom-scale', self.scale_cust) self.toolbar_box.view_toolbar.connect('scale', self._update_scale) self.toolbar_box.view_toolbar.connect('show-grid', lambda w, d: self.graph.set_show_grid(d)) self.toolbar_box.show() self.functions_list = FunctionsList() self.functions_list.connect('motion_notify_event', self._event_motion) self.functions_list.connect('list-updated', self.update_graph) self.functions_list.connect('function-selected', self._update_color_selection) self.functions_list.append_function(self.profile_color.get_fill_color()) self.functions_list.show() functions_window = gtk.ScrolledWindow() functions_window.add(self.functions_list) functions_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) functions_window.set_hadjustment(self.functions_list.get_hadjustment()) functions_window.set_vadjustment(self.functions_list.get_vadjustment()) functions_window.show() self.pack1(functions_window) self.toolbar_box.view_toolbar.x_min_entry.set_text(self.x_min) self.toolbar_box.view_toolbar.x_max_entry.set_text(self.x_max) self.toolbar_box.view_toolbar.x_scale_entry.set_text(self.x_scale) self.toolbar_box.view_toolbar.y_min_entry.set_text(self.y_min) self.toolbar_box.view_toolbar.y_max_entry.set_text(self.y_max) self.toolbar_box.view_toolbar.y_scale_entry.set_text(self.y_scale) self.pack2(self.graph) self.graph.show() self.connect('expose-event', self._paned_expose_event_cb) def _event_motion(self, widget, event): self.graph.pointer = None self.graph.queue_draw() self.toolbar_box.xlabel.set_text('') self.toolbar_box.ylabel.set_text('') self.toolbar_box.separator.hide() def _update_coordinates(self, widget, x, y): self.toolbar_box.xlabel.set_text("x = " + str(int(x * 100) / 100.0)) self.toolbar_box.ylabel.set_text("y = " + str(int(y * 100) / 100.0)) self.toolbar_box.separator.show() def _fullscreen(self, widget): self._resize_queue = 0 self.activity.fullscreen() def _update_scale(self, widget=None): xscale, yscale = self.toolbar_box.view_toolbar.get_scale() self.graph.set_x_scale(xscale) self.graph.set_y_scale(yscale) self.update_graph(None, self.functions_list.get_list()) def _evaluate_cb(self, widget, x): safe_dict = self.graph.safe_dict try: x = eval(x.replace('^', '**'), {'__builtins__': {}}, safe_dict) safe_dict['x'] = x except: safe_dict['x'] = None self.functions_list.evaluate(safe_dict) def update_parameters(self, widget): xmin, xmax, ymin, ymax = self.toolbar_box.view_toolbar.get_scale_range() self.graph.set_xmin(xmin) self.graph.set_xmax(xmax) self.graph.set_ymin(ymin) self.graph.set_ymax(ymax) self.update_graph(None, self.functions_list.get_list()) def _paned_expose_event_cb(self, widget, event): if self._resize_queue != None: min_position = self.get_property('min-position') max_position = self.get_property('max-position') if self._resize_queue == 0: self.set_position(min_position) else: self.set_position((max_position - min_position) /\ self._resize_queue + min_position) self._resize_queue = None def parameter_entries_repopulate(self, widget=None): # set text in entries for parameters self.toolbar_box.view_toolbar.x_min_entry.set_text( str(self.graph.xmin)) self.toolbar_box.view_toolbar.x_max_entry.set_text( str(self.graph.xmax)) self.toolbar_box.view_toolbar.x_scale_entry.set_text( str(self.graph.x_scale)) self.toolbar_box.view_toolbar.y_min_entry.set_text( str(self.graph.ymin)) self.toolbar_box.view_toolbar.y_max_entry.set_text( str(self.graph.ymax)) self.toolbar_box.view_toolbar.y_scale_entry.set_text( str(self.graph.y_scale)) def zoom_in(self, widget, event=None): 'Narrow the plotted section by half' center_x = (self.graph.xmin + self.graph.xmax) / 2 center_y = (self.graph.ymin + self.graph.ymax) / 2 range_x = (self.graph.xmax - self.graph.xmin) range_y = (self.graph.ymax - self.graph.ymin) self.graph.xmin = center_x - (range_x / 4) self.graph.xmax = center_x + (range_x / 4) self.graph.ymin = center_y - (range_y / 4) self.graph.ymax = center_y + (range_y / 4) self.parameter_entries_repopulate() self.update_graph(None, self.functions_list.get_list()) def zoom_out(self, widget, event=None): """Double the plotted section""" center_x = (self.graph.xmin + self.graph.xmax) / 2 center_y = (self.graph.ymin + self.graph.ymax) / 2 range_x = (self.graph.xmax - self.graph.xmin) range_y = (self.graph.ymax - self.graph.ymin) self.graph.xmin = center_x - (range_x) self.graph.xmax = center_x + (range_x) self.graph.ymin = center_y - (range_y) self.graph.ymax = center_y + (range_y) self.parameter_entries_repopulate() self.update_graph(None, self.functions_list.get_list()) def zoom_reset(self, widget, event=None): """Set the range back to the user's input""" self.graph.xmin = eval(self.x_min, {'__builtins__': {}}, self.graph.safe_dict) self.graph.ymin = eval(self.y_min, {'__builtins__': {}}, self.graph.safe_dict) self.graph.xmax = eval(self.x_max, {'__builtins__': {}}, self.graph.safe_dict) self.graph.ymax = eval(self.y_max, {'__builtins__': {}}, self.graph.safe_dict) self.toolbar_box.view_toolbar.x_min_entry.set_text(self.x_min) self.toolbar_box.view_toolbar.x_max_entry.set_text(self.x_max) self.toolbar_box.view_toolbar.x_scale_entry.set_text(self.x_scale) self.toolbar_box.view_toolbar.y_min_entry.set_text(self.y_min) self.toolbar_box.view_toolbar.y_max_entry.set_text(self.y_max) self.toolbar_box.view_toolbar.y_scale_entry.set_text(self.y_scale) self.update_graph(None, self.functions_list.get_list()) def scale_dec(self, widget, event=None): self.graph.scale_style = SCALE_TYPE_DEC self.update_graph(None, self.functions_list.get_list()) def scale_rad(self, widget, event=None): self.graph.scale_style = SCALE_TYPE_RAD self.update_graph(None, self.functions_list.get_list()) def scale_cust(self, widget, event=None): self.graph.scale_style = SCALE_TYPE_CUST self.update_graph(None, self.functions_list.get_list()) def connect_points(self, widget, connect_points): self.graph.connect_points = connect_points self.update_graph(None, self.functions_list.get_list()) def _update_line_color(self, widget, color): self.functions_list.set_current_line_color(color) self.update_graph(None, self.functions_list.get_list()) def _update_color_selection(self, widget, color): self.toolbar_box.line_color_btn.set_color(gtk.gdk.color_parse(color)) self.update_graph(None, self.functions_list.get_list()) def _remove_function(self, widget): self.functions_list.remove_function() self.update_graph(None, self.functions_list.get_list()) def _append_function(self, widget): if len(self.functions_list.get_list()) % 2 == 0: color = self.profile_color.get_fill_color() else: color = self.profile_color.get_stroke_color() self.functions_list.append_function(color) self.update_graph(None, self.functions_list.get_list()) def update_graph(self, widget, info): self._evaluate_cb(None, self.toolbar_box.evaluate_entry.get_text()) self.graph.reload_functions([(func, color, selected) for color, name, func, selected in info])