From mboxrd@z Thu Jan 1 00:00:00 1970 From: mathieu.desnoyers@efficios.com (Mathieu Desnoyers) Date: Thu, 31 Jan 2013 14:16:15 -0500 Subject: [lttng-dev] [PATCH babeltrace 2/2] Added Python interpreter version directives and reorganized example scripts In-Reply-To: <1359640251-6133-2-git-send-email-jeremie.galarneau@efficios.com> References: <1359640251-6133-1-git-send-email-jeremie.galarneau@efficios.com> <1359640251-6133-2-git-send-email-jeremie.galarneau@efficios.com> Message-ID: <20130131191614.GB15166@Krystal> * J?r?mie Galarneau (jeremie.galarneau at efficios.com) wrote: > > Signed-off-by: J?r?mie Galarneau merged into bindings/python branch too. Thanks, Mathieu > --- > bindings/python/examples/babeltrace_and_lttng.py | 12 +- > bindings/python/examples/eventcount.py | 84 - > bindings/python/examples/eventcountlist.py | 83 - > bindings/python/examples/events_per_cpu.py | 99 - > bindings/python/examples/example-api-test.py | 11 +- > bindings/python/examples/histogram.py | 139 -- > .../examples/output_format_modules/cairoplot.py | 2336 -------------------- > .../examples/output_format_modules/pprint_table.py | 37 - > .../examples/output_format_modules/series.py | 1140 ---------- > bindings/python/examples/python2/eventcount.py | 85 + > bindings/python/examples/python2/eventcountlist.py | 84 + > bindings/python/examples/python2/events_per_cpu.py | 100 + > bindings/python/examples/python2/histogram.py | 140 ++ > .../python2/output_format_modules/cairoplot.py | 2336 ++++++++++++++++++++ > .../python2/output_format_modules/pprint_table.py | 37 + > .../python2/output_format_modules/series.py | 1140 ++++++++++ > bindings/python/examples/python2/softirqtimes.py | 154 ++ > .../python/examples/python2/syscalls_by_pid.py | 85 + > bindings/python/examples/sched_switch.py | 11 +- > bindings/python/examples/softirqtimes.py | 153 -- > bindings/python/examples/syscalls_by_pid.py | 84 - > tests/tests-python.py | 1 + > 22 files changed, 4183 insertions(+), 4168 deletions(-) > mode change 100644 => 100755 bindings/python/examples/babeltrace_and_lttng.py > delete mode 100644 bindings/python/examples/eventcount.py > delete mode 100644 bindings/python/examples/eventcountlist.py > delete mode 100644 bindings/python/examples/events_per_cpu.py > mode change 100644 => 100755 bindings/python/examples/example-api-test.py > delete mode 100644 bindings/python/examples/histogram.py > delete mode 100644 bindings/python/examples/output_format_modules/cairoplot.py > delete mode 100644 bindings/python/examples/output_format_modules/pprint_table.py > delete mode 100644 bindings/python/examples/output_format_modules/series.py > create mode 100755 bindings/python/examples/python2/eventcount.py > create mode 100755 bindings/python/examples/python2/eventcountlist.py > create mode 100755 bindings/python/examples/python2/events_per_cpu.py > create mode 100755 bindings/python/examples/python2/histogram.py > create mode 100644 bindings/python/examples/python2/output_format_modules/cairoplot.py > create mode 100644 bindings/python/examples/python2/output_format_modules/pprint_table.py > create mode 100644 bindings/python/examples/python2/output_format_modules/series.py > create mode 100755 bindings/python/examples/python2/softirqtimes.py > create mode 100755 bindings/python/examples/python2/syscalls_by_pid.py > mode change 100644 => 100755 bindings/python/examples/sched_switch.py > delete mode 100644 bindings/python/examples/softirqtimes.py > delete mode 100644 bindings/python/examples/syscalls_by_pid.py > mode change 100644 => 100755 tests/tests-python.py > > diff --git a/bindings/python/examples/babeltrace_and_lttng.py b/bindings/python/examples/babeltrace_and_lttng.py > old mode 100644 > new mode 100755 > index cb44796..cfd611f > --- a/bindings/python/examples/babeltrace_and_lttng.py > +++ b/bindings/python/examples/babeltrace_and_lttng.py > @@ -1,3 +1,4 @@ > +#!/usr/bin/env python3 > # babeltrace_and_lttng.py > # > # Babeltrace and LTTng example script > @@ -59,18 +60,23 @@ ret = lttng.create(ses_name,trace_path) > if ret < 0: > raise LTTngError(lttng.strerror(ret)) > > +domain = lttng.Domain() > +domain.type = lttng.DOMAIN_KERNEL > + > han = None > -han = lttng.Handle(ses_name, lttng.Domain()) > +han = lttng.Handle(ses_name, domain) > if han is None: > raise LTTngError("Handle not created") > > > # Enabling all events > -ret = lttng.enable_event(han, lttng.Event(), None) > +event = lttng.Event() > +event.type = lttng.EVENT_ALL > +event.loglevel_type = lttng.EVENT_LOGLEVEL_ALL > +ret = lttng.enable_event(han, event, None) > if ret < 0: > raise LTTngError(lttng.strerror(ret)) > > - > # Start, wait, stop > ret = lttng.start(ses_name) > if ret < 0: > diff --git a/bindings/python/examples/eventcount.py b/bindings/python/examples/eventcount.py > deleted file mode 100644 > index 5e96a43..0000000 > --- a/bindings/python/examples/eventcount.py > +++ /dev/null > @@ -1,84 +0,0 @@ > -# eventcount.py > -# > -# Babeltrace event count example script > -# > -# Copyright 2012 EfficiOS Inc. > -# > -# Author: Danny Serres > -# > -# Permission is hereby granted, free of charge, to any person obtaining a copy > -# of this software and associated documentation files (the "Software"), to deal > -# in the Software without restriction, including without limitation the rights > -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > -# copies of the Software, and to permit persons to whom the Software is > -# furnished to do so, subject to the following conditions: > -# > -# The above copyright notice and this permission notice shall be included in > -# all copies or substantial portions of the Software. > - > -# The script prints a count of specified events and > -# their related tid's in a given trace. > -# The trace needs TID context (lttng add-context -k -t tid) > - > -import sys > -from babeltrace import * > -from output_format_modules.pprint_table import pprint_table as pprint > - > -if len(sys.argv) < 3: > - raise TypeError("Usage: python eventcount.py event1 [event2 ...] path/to/trace") > - > -ctx = Context() > -ret = ctx.add_trace(sys.argv[len(sys.argv)-1], "ctf") > -if ret is None: > - raise IOError("Error adding trace") > - > -counts = {} > - > -# Setting iterator > -bp = IterPos(SEEK_BEGIN) > -ctf_it = ctf.Iterator(ctx, bp) > - > -# Reading events > -event = ctf_it.read_event() > -while(event is not None): > - for event_type in sys.argv[1:len(sys.argv)-1]: > - if event_type == event.get_name(): > - > - # Getting scope definition > - sco = event.get_top_level_scope(ctf.scope.STREAM_EVENT_CONTEXT) > - if sco is None: > - print("ERROR: Cannot get definition scope for {}".format( > - event.get_name())) > - continue > - > - # Getting TID > - tid_field = event.get_field(sco, "_tid") > - tid = tid_field.get_int64() > - > - if ctf.field_error(): > - print("ERROR: Missing TID info for {}".format( > - event.get_name())) > - continue > - > - tmp = (tid, event.get_name()) > - > - if tmp in counts: > - counts[tmp] += 1 > - else: > - counts[tmp] = 1 > - > - # Next event > - ret = ctf_it.next() > - if ret < 0: > - break > - event = ctf_it.read_event() > - > -del ctf_it > - > -# Appending data to table for output > -table = [] > -for item in counts: > - table.append([item[0], item[1], counts[item]]) > -table = sorted(table) > -table.insert(0,["TID", "EVENT", "COUNT"]) > -pprint(table, 2) > diff --git a/bindings/python/examples/eventcountlist.py b/bindings/python/examples/eventcountlist.py > deleted file mode 100644 > index 945a960..0000000 > --- a/bindings/python/examples/eventcountlist.py > +++ /dev/null > @@ -1,83 +0,0 @@ > -# eventcountlist.py > -# > -# Babeltrace event count list example script > -# > -# Copyright 2012 EfficiOS Inc. > -# > -# Author: Danny Serres > -# > -# Permission is hereby granted, free of charge, to any person obtaining a copy > -# of this software and associated documentation files (the "Software"), to deal > -# in the Software without restriction, including without limitation the rights > -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > -# copies of the Software, and to permit persons to whom the Software is > -# furnished to do so, subject to the following conditions: > -# > -# The above copyright notice and this permission notice shall be included in > -# all copies or substantial portions of the Software. > - > -# The script prints a count and rate of events. > -# It also outputs a bar graph of count per event, using the cairoplot module. > - > -import sys > -from babeltrace import * > -from output_format_modules import cairoplot > -from output_format_modules.pprint_table import pprint_table as pprint > - > -# Check for path arg: > -if len(sys.argv) < 2: > - raise TypeError("Usage: python eventcountlist.py path/to/trace") > - > -ctx = Context() > -ret = ctx.add_trace(sys.argv[1], "ctf") > -if ret is None: > - raise IOError("Error adding trace") > - > -# Events and their assossiated count > -# will be stored as a dict: > -events_count = {} > - > -# Setting iterator: > -bp = IterPos(SEEK_BEGIN) > -ctf_it = ctf.Iterator(ctx,bp) > - > -prev_event = None > -event = ctf_it.read_event() > - > -start_time = event.get_timestamp() > - > -# Reading events: > -while(event is not None): > - if event.get_name() in events_count: > - events_count[event.get_name()] += 1 > - else: > - events_count[event.get_name()] = 1 > - > - ret = ctf_it.next() > - if ret < 0: > - break > - else: > - prev_event = event > - event = ctf_it.read_event() > - > -if event: > - total_time = event.get_timestamp() - start_time > -else: > - total_time = prev_event.get_timestamp() - start_time > - > -del ctf_it > - > -# Printing encountered events with respective count and rate: > -print("Total time: {} ns".format(total_time)) > -table = [["EVENT", "COUNT", "RATE (Hz)"]] > -for item in sorted(events_count.iterkeys()): > - tmp = [item, events_count[item], > - events_count[item]/(total_time/1000000000.0)] > - table.append(tmp) > -pprint(table) > - > -# Exporting data as bar graph > -cairoplot.vertical_bar_plot ( 'eventcountlist.svg', events_count, 50+85*len(events_count), > - 800, border = 20, display_values = True, grid = True, > - rounded_corners = True, > - x_labels = sorted(events_count.keys()) ) > diff --git a/bindings/python/examples/events_per_cpu.py b/bindings/python/examples/events_per_cpu.py > deleted file mode 100644 > index be497ec..0000000 > --- a/bindings/python/examples/events_per_cpu.py > +++ /dev/null > @@ -1,99 +0,0 @@ > -# events_per_cpu.py > -# > -# Babeltrace events per cpu example script > -# > -# Copyright 2012 EfficiOS Inc. > -# > -# Author: Danny Serres > -# > -# Permission is hereby granted, free of charge, to any person obtaining a copy > -# of this software and associated documentation files (the "Software"), to deal > -# in the Software without restriction, including without limitation the rights > -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > -# copies of the Software, and to permit persons to whom the Software is > -# furnished to do so, subject to the following conditions: > -# > -# The above copyright notice and this permission notice shall be included in > -# all copies or substantial portions of the Software. > - > -# The script opens a trace and prints out CPU statistics > -# for the given trace (event count per CPU, total active > -# time and % of time processing events). > -# It also outputs a .txt file showing each time interval > -# (since the beginning of the trace) in which each CPU > -# was active and the corresponding event. > - > -import sys, multiprocessing > -from output_format_modules.pprint_table import pprint_table as pprint > -from babeltrace import * > - > -if len(sys.argv) < 2: > - raise TypeError("Usage: python events_per_cpu.py path/to/trace") > - > -# Adding trace > -ctx = Context() > -ret = ctx.add_trace(sys.argv[1], "ctf") > -if ret is None: > - raise IOError("Error adding trace") > - > -cpu_usage = [] > -nbEvents = 0 > -i = 0 > -while i < multiprocessing.cpu_count(): > - cpu_usage.append([]) > - i += 1 > - > -# Setting iterator > -bp = IterPos(SEEK_BEGIN) > -ctf_it = ctf.Iterator(ctx, bp) > - > -# Reading events > -event = ctf_it.read_event() > -start_time = event.get_timestamp() > - > -while(event is not None): > - > - event_name = event.get_name() > - ts = event.get_timestamp() > - > - # Getting cpu_id > - scope = event.get_top_level_scope(ctf.scope.STREAM_PACKET_CONTEXT) > - field = event.get_field(scope, "cpu_id") > - cpu_id = field.get_uint64() > - if ctf.field_error(): > - print("ERROR: Missing cpu_id info for {}".format(event.get_name())) > - else: > - cpu_usage[cpu_id].append( (int(ts), event_name) ) > - nbEvents += 1 > - > - # Next Event > - ret = ctf_it.next() > - if ret < 0: > - break > - event = ctf_it.read_event() > - > - > -# Outputting > -table = [] > -output = open("events_per_cpu.txt", "wt") > -output.write("(timestamp, event)\n") > - > -for cpu in range(len(cpu_usage)): > - # Setting table > - event_str = str(100.0 * len(cpu_usage[cpu]) / nbEvents) + '000' > - # % is printed with 2 decimals > - table.append([cpu, len(cpu_usage[cpu]), event_str[0:event_str.find('.') + 3] + ' %']) > - > - # Writing to file > - output.write("\n\n\n----------------------\n") > - output.write("CPU {}\n\n".format(cpu)) > - for event in cpu_usage[cpu]: > - output.write(str(event) + '\n') > - > -# Printing table > -table.insert(0, ["CPU ID", "EVENT COUNT", "TRACE EVENT %"]) > -pprint(table) > -print("Total event count: {}".format(nbEvents)) > -print("Total trace time: {} ns".format(ts - start_time)) > - > -output.close() > diff --git a/bindings/python/examples/example-api-test.py b/bindings/python/examples/example-api-test.py > old mode 100644 > new mode 100755 > index 104f2d5..fc59e24 > --- a/bindings/python/examples/example-api-test.py > +++ b/bindings/python/examples/example-api-test.py > @@ -1,18 +1,19 @@ > +#!/usr/bin/env python3 > # example_api_test.py > -# > +# > # Babeltrace example script based on the Babeltrace API test script > -# > +# > # Copyright 2012 EfficiOS Inc. > -# > +# > # Author: Danny Serres > -# > +# > # Permission is hereby granted, free of charge, to any person obtaining a copy > # of this software and associated documentation files (the "Software"), to deal > # in the Software without restriction, including without limitation the rights > # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > # copies of the Software, and to permit persons to whom the Software is > # furnished to do so, subject to the following conditions: > -# > +# > # The above copyright notice and this permission notice shall be included in > # all copies or substantial portions of the Software. > > diff --git a/bindings/python/examples/histogram.py b/bindings/python/examples/histogram.py > deleted file mode 100644 > index 44616a6..0000000 > --- a/bindings/python/examples/histogram.py > +++ /dev/null > @@ -1,139 +0,0 @@ > -# histogram.py > -# > -# Babeltrace histogram example script > -# > -# Copyright 2012 EfficiOS Inc. > -# > -# Author: Danny Serres > -# > -# Permission is hereby granted, free of charge, to any person obtaining a copy > -# of this software and associated documentation files (the "Software"), to deal > -# in the Software without restriction, including without limitation the rights > -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > -# copies of the Software, and to permit persons to whom the Software is > -# furnished to do so, subject to the following conditions: > -# > -# The above copyright notice and this permission notice shall be included in > -# all copies or substantial portions of the Software. > - > -# The script checks the number of events in the trace > -# and outputs a table and a .svg histogram for the specified > -# range (microseconds) or the total trace if no range specified. > -# The graph is generated using the cairoplot module. > - > -import sys > -from babeltrace import * > -from output_format_modules import cairoplot > -from output_format_modules.pprint_table import pprint_table as pprint > - > -# ------------------------------------------------ > -# Output settings > - > -# number of intervals: > -nbDiv = 25 # Should not be over 150 > - # for usable graph output > - > -# table output stream (file-like object): > -out = sys.stdout > -# ------------------------------------------------- > - > -if len(sys.argv) < 2 or len(sys.argv) > 4: > - raise TypeError("Usage: python histogram.py [ start_time [end_time] ] path/to/trace") > - > -ctx = Context() > -ret = ctx.add_trace(sys.argv[len(sys.argv)-1], "ctf") > -if ret is None: > - raise IOError("Error adding trace") > - > -# Check when to start/stop graphing > -sinceBegin = True > -beginTime = 0.0 > -if len(sys.argv) > 2: > - sinceBegin = False > - beginTime = float(sys.argv[1]) > -untilEnd = True > -if len(sys.argv) == 4: > - untilEnd = False > - > -# Setting iterator > -bp = IterPos(SEEK_BEGIN) > -ctf_it = ctf.Iterator(ctx, bp) > - > -# Reading events > -event = ctf_it.read_event() > -start_time = event.get_timestamp() > -time = 0 > -count = {} > - > -while(event is not None): > - # Microsec. > - time = (event.get_timestamp() - start_time)/1000.0 > - > - # Check if in range > - if not sinceBegin: > - if time < beginTime: > - # Next Event > - ret = ctf_it.next() > - if ret < 0: > - break > - event = ctf_it.read_event() > - continue > - if not untilEnd: > - if time > float(sys.argv[2]): > - break > - > - # Counting events per timestamp: > - if time in count: > - count[time] += 1 > - else: > - count[time] = 1 > - > - # Next Event > - ret = ctf_it.next() > - if ret < 0: > - break > - event = ctf_it.read_event() > - > -del ctf_it > - > -# Setting data for output > -interval = (time - beginTime)/nbDiv > -div_begin_time = beginTime > -div_end_time = beginTime + interval > -data = {} > - > -# Prefix for string sorting, considering > -# there should not be over 150 intervals. > -# This would work up to 9999 intervals. > -# If needed, add zeros. > -prefix = 0.0001 > - > -while div_end_time <= time: > - key = str(prefix) + '[' + str(div_begin_time) + ';' + str(div_end_time) + '[' > - for tmp in count: > - if tmp >= div_begin_time and tmp < div_end_time: > - if key in data: > - data[key] += count[tmp] > - else: > - data[key] = count[tmp] > - if not key in data: > - data[key] = 0 > - div_begin_time = div_end_time > - div_end_time += interval > - # Prefix increment > - prefix += 0.001 > - > -table = [] > -x_labels = [] > -for key in sorted(data): > - table.append([key[key.find('['):], data[key]]) > - x_labels.append(key[key.find('['):]) > - > -# Table output > -table.insert(0, ["INTERVAL (us)", "COUNT"]) > -pprint(table, 1, out) > - > -# Graph output > -cairoplot.vertical_bar_plot ( 'histogram.svg', data, 50 + 150*nbDiv, 50*nbDiv, > - border = 20, display_values = True, grid = True, > - x_labels = x_labels, rounded_corners = True ) > diff --git a/bindings/python/examples/output_format_modules/cairoplot.py b/bindings/python/examples/output_format_modules/cairoplot.py > deleted file mode 100644 > index a27113f..0000000 > --- a/bindings/python/examples/output_format_modules/cairoplot.py > +++ /dev/null > @@ -1,2336 +0,0 @@ > -?#!/usr/bin/env python > -# -*- coding: utf-8 -*- > - > -# CairoPlot.py > -# > -# Copyright (c) 2008 Rodrigo Moreira Ara?jo > -# > -# Author: Rodrigo Moreiro Araujo > -# > -# This program is free software; you can redistribute it and/or > -# modify it under the terms of the GNU Lesser General Public License > -# as published by the Free Software Foundation; either version 2 of > -# the License, or (at your option) any later version. > -# > -# This program is distributed in the hope that it will be useful, > -# but WITHOUT ANY WARRANTY; without even the implied warranty of > -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > -# GNU General Public License for more details. > -# > -# You should have received a copy of the GNU Lesser General Public > -# License along with this program; if not, write to the Free Software > -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 > -# USA > - > -#Contributor: Jo?o S. O. Bueno > - > -#TODO: review BarPlot Code > -#TODO: x_label colision problem on Horizontal Bar Plot > -#TODO: y_label's eat too much space on HBP > - > - > -__version__ = 1.2 > - > -import cairo > -import math > -import random > -from series import Series, Group, Data > - > -HORZ = 0 > -VERT = 1 > -NORM = 2 > - > -COLORS = {"red" : (1.0,0.0,0.0,1.0), "lime" : (0.0,1.0,0.0,1.0), "blue" : (0.0,0.0,1.0,1.0), > - "maroon" : (0.5,0.0,0.0,1.0), "green" : (0.0,0.5,0.0,1.0), "navy" : (0.0,0.0,0.5,1.0), > - "yellow" : (1.0,1.0,0.0,1.0), "magenta" : (1.0,0.0,1.0,1.0), "cyan" : (0.0,1.0,1.0,1.0), > - "orange" : (1.0,0.5,0.0,1.0), "white" : (1.0,1.0,1.0,1.0), "black" : (0.0,0.0,0.0,1.0), > - "gray" : (0.5,0.5,0.5,1.0), "light_gray" : (0.9,0.9,0.9,1.0), > - "transparent" : (0.0,0.0,0.0,0.0)} > - > -THEMES = {"black_red" : [(0.0,0.0,0.0,1.0), (1.0,0.0,0.0,1.0)], > - "red_green_blue" : [(1.0,0.0,0.0,1.0), (0.0,1.0,0.0,1.0), (0.0,0.0,1.0,1.0)], > - "red_orange_yellow" : [(1.0,0.2,0.0,1.0), (1.0,0.7,0.0,1.0), (1.0,1.0,0.0,1.0)], > - "yellow_orange_red" : [(1.0,1.0,0.0,1.0), (1.0,0.7,0.0,1.0), (1.0,0.2,0.0,1.0)], > - "rainbow" : [(1.0,0.0,0.0,1.0), (1.0,0.5,0.0,1.0), (1.0,1.0,0.0,1.0), (0.0,1.0,0.0,1.0), (0.0,0.0,1.0,1.0), (0.3, 0.0, 0.5,1.0), (0.5, 0.0, 1.0, 1.0)]} > - > -def colors_from_theme( theme, series_length, mode = 'solid' ): > - colors = [] > - if theme not in THEMES.keys() : > - raise Exception, "Theme not defined" > - color_steps = THEMES[theme] > - n_colors = len(color_steps) > - if series_length <= n_colors: > - colors = [color + tuple([mode]) for color in color_steps[0:n_colors]] > - else: > - iterations = [(series_length - n_colors)/(n_colors - 1) for i in color_steps[:-1]] > - over_iterations = (series_length - n_colors) % (n_colors - 1) > - for i in range(n_colors - 1): > - if over_iterations <= 0: > - break > - iterations[i] += 1 > - over_iterations -= 1 > - for index,color in enumerate(color_steps[:-1]): > - colors.append(color + tuple([mode])) > - if iterations[index] == 0: > - continue > - next_color = color_steps[index+1] > - color_step = ((next_color[0] - color[0])/(iterations[index] + 1), > - (next_color[1] - color[1])/(iterations[index] + 1), > - (next_color[2] - color[2])/(iterations[index] + 1), > - (next_color[3] - color[3])/(iterations[index] + 1)) > - for i in range( iterations[index] ): > - colors.append((color[0] + color_step[0]*(i+1), > - color[1] + color_step[1]*(i+1), > - color[2] + color_step[2]*(i+1), > - color[3] + color_step[3]*(i+1), > - mode)) > - colors.append(color_steps[-1] + tuple([mode])) > - return colors > - > - > -def other_direction(direction): > - "explicit is better than implicit" > - if direction == HORZ: > - return VERT > - else: > - return HORZ > - > -#Class definition > - > -class Plot(object): > - def __init__(self, > - surface=None, > - data=None, > - width=640, > - height=480, > - background=None, > - border = 0, > - x_labels = None, > - y_labels = None, > - series_colors = None): > - random.seed(2) > - self.create_surface(surface, width, height) > - self.dimensions = {} > - self.dimensions[HORZ] = width > - self.dimensions[VERT] = height > - self.context = cairo.Context(self.surface) > - self.labels={} > - self.labels[HORZ] = x_labels > - self.labels[VERT] = y_labels > - self.load_series(data, x_labels, y_labels, series_colors) > - self.font_size = 10 > - self.set_background (background) > - self.border = border > - self.borders = {} > - self.line_color = (0.5, 0.5, 0.5) > - self.line_width = 0.5 > - self.label_color = (0.0, 0.0, 0.0) > - self.grid_color = (0.8, 0.8, 0.8) > - > - def create_surface(self, surface, width=None, height=None): > - self.filename = None > - if isinstance(surface, cairo.Surface): > - self.surface = surface > - return > - if not type(surface) in (str, unicode): > - raise TypeError("Surface should be either a Cairo surface or a filename, not %s" % surface) > - sufix = surface.rsplit(".")[-1].lower() > - self.filename = surface > - if sufix == "png": > - self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) > - elif sufix == "ps": > - self.surface = cairo.PSSurface(surface, width, height) > - elif sufix == "pdf": > - self.surface = cairo.PSSurface(surface, width, height) > - else: > - if sufix != "svg": > - self.filename += ".svg" > - self.surface = cairo.SVGSurface(self.filename, width, height) > - > - def commit(self): > - try: > - self.context.show_page() > - if self.filename and self.filename.endswith(".png"): > - self.surface.write_to_png(self.filename) > - else: > - self.surface.finish() > - except cairo.Error: > - pass > - > - def load_series (self, data, x_labels=None, y_labels=None, series_colors=None): > - self.series_labels = [] > - self.series = None > - > - #The pretty way > - #if not isinstance(data, Series): > - # # Not an instance of Series > - # self.series = Series(data) > - #else: > - # self.series = data > - # > - #self.series_labels = self.series.get_names() > - > - #TODO: Remove on next version > - # The ugly way, keeping retrocompatibility... > - if callable(data) or type(data) is list and callable(data[0]): # Lambda or List of lambdas > - self.series = data > - self.series_labels = None > - elif isinstance(data, Series): # Instance of Series > - self.series = data > - self.series_labels = data.get_names() > - else: # Anything else > - self.series = Series(data) > - self.series_labels = self.series.get_names() > - > - #TODO: allow user passed series_widths > - self.series_widths = [1.0 for group in self.series] > - > - #TODO: Remove on next version > - self.process_colors( series_colors ) > - > - def process_colors( self, series_colors, length = None, mode = 'solid' ): > - #series_colors might be None, a theme, a string of colors names or a list of color tuples > - if length is None : > - length = len( self.series.to_list() ) > - > - #no colors passed > - if not series_colors: > - #Randomize colors > - self.series_colors = [ [random.random() for i in range(3)] + [1.0, mode] for series in range( length ) ] > - else: > - #Just theme pattern > - if not hasattr( series_colors, "__iter__" ): > - theme = series_colors > - self.series_colors = colors_from_theme( theme.lower(), length ) > - > - #Theme pattern and mode > - elif not hasattr(series_colors, '__delitem__') and not hasattr( series_colors[0], "__iter__" ): > - theme = series_colors[0] > - mode = series_colors[1] > - self.series_colors = colors_from_theme( theme.lower(), length, mode ) > - > - #List > - else: > - self.series_colors = series_colors > - for index, color in enumerate( self.series_colors ): > - #element is a color name > - if not hasattr(color, "__iter__"): > - self.series_colors[index] = COLORS[color.lower()] + tuple([mode]) > - #element is rgb tuple instead of rgba > - elif len( color ) == 3 : > - self.series_colors[index] += (1.0,mode) > - #element has 4 elements, might be rgba tuple or rgb tuple with mode > - elif len( color ) == 4 : > - #last element is mode > - if not hasattr(color[3], "__iter__"): > - self.series_colors[index] += tuple([color[3]]) > - self.series_colors[index][3] = 1.0 > - #last element is alpha > - else: > - self.series_colors[index] += tuple([mode]) > - > - def get_width(self): > - return self.surface.get_width() > - > - def get_height(self): > - return self.surface.get_height() > - > - def set_background(self, background): > - if background is None: > - self.background = (0.0,0.0,0.0,0.0) > - elif type(background) in (cairo.LinearGradient, tuple): > - self.background = background > - elif not hasattr(background,"__iter__"): > - colors = background.split(" ") > - if len(colors) == 1 and colors[0] in COLORS: > - self.background = COLORS[background] > - elif len(colors) > 1: > - self.background = cairo.LinearGradient(self.dimensions[HORZ] / 2, 0, self.dimensions[HORZ] / 2, self.dimensions[VERT]) > - for index,color in enumerate(colors): > - self.background.add_color_stop_rgba(float(index)/(len(colors)-1),*COLORS[color]) > - else: > - raise TypeError ("Background should be either cairo.LinearGradient or a 3/4-tuple, not %s" % type(background)) > - > - def render_background(self): > - if isinstance(self.background, cairo.LinearGradient): > - self.context.set_source(self.background) > - else: > - self.context.set_source_rgba(*self.background) > - self.context.rectangle(0,0, self.dimensions[HORZ], self.dimensions[VERT]) > - self.context.fill() > - > - def render_bounding_box(self): > - self.context.set_source_rgba(*self.line_color) > - self.context.set_line_width(self.line_width) > - self.context.rectangle(self.border, self.border, > - self.dimensions[HORZ] - 2 * self.border, > - self.dimensions[VERT] - 2 * self.border) > - self.context.stroke() > - > - def render(self): > - pass > - > -class ScatterPlot( Plot ): > - def __init__(self, > - surface=None, > - data=None, > - errorx=None, > - errory=None, > - width=640, > - height=480, > - background=None, > - border=0, > - axis = False, > - dash = False, > - discrete = False, > - dots = 0, > - grid = False, > - series_legend = False, > - x_labels = None, > - y_labels = None, > - x_bounds = None, > - y_bounds = None, > - z_bounds = None, > - x_title = None, > - y_title = None, > - series_colors = None, > - circle_colors = None ): > - > - self.bounds = {} > - self.bounds[HORZ] = x_bounds > - self.bounds[VERT] = y_bounds > - self.bounds[NORM] = z_bounds > - self.titles = {} > - self.titles[HORZ] = x_title > - self.titles[VERT] = y_title > - self.max_value = {} > - self.axis = axis > - self.discrete = discrete > - self.dots = dots > - self.grid = grid > - self.series_legend = series_legend > - self.variable_radius = False > - self.x_label_angle = math.pi / 2.5 > - self.circle_colors = circle_colors > - > - Plot.__init__(self, surface, data, width, height, background, border, x_labels, y_labels, series_colors) > - > - self.dash = None > - if dash: > - if hasattr(dash, "keys"): > - self.dash = [dash[key] for key in self.series_labels] > - elif max([hasattr(item,'__delitem__') for item in data]) : > - self.dash = dash > - else: > - self.dash = [dash] > - > - self.load_errors(errorx, errory) > - > - def convert_list_to_tuple(self, data): > - #Data must be converted from lists of coordinates to a single > - # list of tuples > - out_data = zip(*data) > - if len(data) == 3: > - self.variable_radius = True > - return out_data > - > - def load_series(self, data, x_labels = None, y_labels = None, series_colors=None): > - #TODO: In cairoplot 2.0 keep only the Series instances > - > - # Convert Data and Group to Series > - if isinstance(data, Data) or isinstance(data, Group): > - data = Series(data) > - > - # Series > - if isinstance(data, Series): > - for group in data: > - for item in group: > - if len(item) is 3: > - self.variable_radius = True > - > - #Dictionary with lists > - if hasattr(data, "keys") : > - if hasattr( data.values()[0][0], "__delitem__" ) : > - for key in data.keys() : > - data[key] = self.convert_list_to_tuple(data[key]) > - elif len(data.values()[0][0]) == 3: > - self.variable_radius = True > - #List > - elif hasattr(data[0], "__delitem__") : > - #List of lists > - if hasattr(data[0][0], "__delitem__") : > - for index,value in enumerate(data) : > - data[index] = self.convert_list_to_tuple(value) > - #List > - elif type(data[0][0]) != type((0,0)): > - data = self.convert_list_to_tuple(data) > - #Three dimensional data > - elif len(data[0][0]) == 3: > - self.variable_radius = True > - > - #List with three dimensional tuples > - elif len(data[0]) == 3: > - self.variable_radius = True > - Plot.load_series(self, data, x_labels, y_labels, series_colors) > - self.calc_boundaries() > - self.calc_labels() > - > - def load_errors(self, errorx, errory): > - self.errors = None > - if errorx == None and errory == None: > - return > - self.errors = {} > - self.errors[HORZ] = None > - self.errors[VERT] = None > - #asimetric errors > - if errorx and hasattr(errorx[0], "__delitem__"): > - self.errors[HORZ] = errorx > - #simetric errors > - elif errorx: > - self.errors[HORZ] = [errorx] > - #asimetric errors > - if errory and hasattr(errory[0], "__delitem__"): > - self.errors[VERT] = errory > - #simetric errors > - elif errory: > - self.errors[VERT] = [errory] > - > - def calc_labels(self): > - if not self.labels[HORZ]: > - amplitude = self.bounds[HORZ][1] - self.bounds[HORZ][0] > - if amplitude % 10: #if horizontal labels need floating points > - self.labels[HORZ] = ["%.2lf" % (float(self.bounds[HORZ][0] + (amplitude * i / 10.0))) for i in range(11) ] > - else: > - self.labels[HORZ] = ["%d" % (int(self.bounds[HORZ][0] + (amplitude * i / 10.0))) for i in range(11) ] > - if not self.labels[VERT]: > - amplitude = self.bounds[VERT][1] - self.bounds[VERT][0] > - if amplitude % 10: #if vertical labels need floating points > - self.labels[VERT] = ["%.2lf" % (float(self.bounds[VERT][0] + (amplitude * i / 10.0))) for i in range(11) ] > - else: > - self.labels[VERT] = ["%d" % (int(self.bounds[VERT][0] + (amplitude * i / 10.0))) for i in range(11) ] > - > - def calc_extents(self, direction): > - self.context.set_font_size(self.font_size * 0.8) > - self.max_value[direction] = max(self.context.text_extents(item)[2] for item in self.labels[direction]) > - self.borders[other_direction(direction)] = self.max_value[direction] + self.border + 20 > - > - def calc_boundaries(self): > - #HORZ = 0, VERT = 1, NORM = 2 > - min_data_value = [0,0,0] > - max_data_value = [0,0,0] > - > - for group in self.series: > - if type(group[0].content) in (int, float, long): > - group = [Data((index, item.content)) for index,item in enumerate(group)] > - > - for point in group: > - for index, item in enumerate(point.content): > - if item > max_data_value[index]: > - max_data_value[index] = item > - elif item < min_data_value[index]: > - min_data_value[index] = item > - > - if not self.bounds[HORZ]: > - self.bounds[HORZ] = (min_data_value[HORZ], max_data_value[HORZ]) > - if not self.bounds[VERT]: > - self.bounds[VERT] = (min_data_value[VERT], max_data_value[VERT]) > - if not self.bounds[NORM]: > - self.bounds[NORM] = (min_data_value[NORM], max_data_value[NORM]) > - > - def calc_all_extents(self): > - self.calc_extents(HORZ) > - self.calc_extents(VERT) > - > - self.plot_height = self.dimensions[VERT] - 2 * self.borders[VERT] > - self.plot_width = self.dimensions[HORZ] - 2* self.borders[HORZ] > - > - self.plot_top = self.dimensions[VERT] - self.borders[VERT] > - > - def calc_steps(self): > - #Calculates all the x, y, z and color steps > - series_amplitude = [self.bounds[index][1] - self.bounds[index][0] for index in range(3)] > - > - if series_amplitude[HORZ]: > - self.horizontal_step = float (self.plot_width) / series_amplitude[HORZ] > - else: > - self.horizontal_step = 0.00 > - > - if series_amplitude[VERT]: > - self.vertical_step = float (self.plot_height) / series_amplitude[VERT] > - else: > - self.vertical_step = 0.00 > - > - if series_amplitude[NORM]: > - if self.variable_radius: > - self.z_step = float (self.bounds[NORM][1]) / series_amplitude[NORM] > - if self.circle_colors: > - self.circle_color_step = tuple([float(self.circle_colors[1][i]-self.circle_colors[0][i])/series_amplitude[NORM] for i in range(4)]) > - else: > - self.z_step = 0.00 > - self.circle_color_step = ( 0.0, 0.0, 0.0, 0.0 ) > - > - def get_circle_color(self, value): > - return tuple( [self.circle_colors[0][i] + value*self.circle_color_step[i] for i in range(4)] ) > - > - def render(self): > - self.calc_all_extents() > - self.calc_steps() > - self.render_background() > - self.render_bounding_box() > - if self.axis: > - self.render_axis() > - if self.grid: > - self.render_grid() > - self.render_labels() > - self.render_plot() > - if self.errors: > - self.render_errors() > - if self.series_legend and self.series_labels: > - self.render_legend() > - > - def render_axis(self): > - #Draws both the axis lines and their titles > - cr = self.context > - cr.set_source_rgba(*self.line_color) > - cr.move_to(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT]) > - cr.line_to(self.borders[HORZ], self.borders[VERT]) > - cr.stroke() > - > - cr.move_to(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT]) > - cr.line_to(self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT]) > - cr.stroke() > - > - cr.set_source_rgba(*self.label_color) > - self.context.set_font_size( 1.2 * self.font_size ) > - if self.titles[HORZ]: > - title_width,title_height = cr.text_extents(self.titles[HORZ])[2:4] > - cr.move_to( self.dimensions[HORZ]/2 - title_width/2, self.borders[VERT] - title_height/2 ) > - cr.show_text( self.titles[HORZ] ) > - > - if self.titles[VERT]: > - title_width,title_height = cr.text_extents(self.titles[VERT])[2:4] > - cr.move_to( self.dimensions[HORZ] - self.borders[HORZ] + title_height/2, self.dimensions[VERT]/2 - title_width/2) > - cr.save() > - cr.rotate( math.pi/2 ) > - cr.show_text( self.titles[VERT] ) > - cr.restore() > - > - def render_grid(self): > - cr = self.context > - horizontal_step = float( self.plot_height ) / ( len( self.labels[VERT] ) - 1 ) > - vertical_step = float( self.plot_width ) / ( len( self.labels[HORZ] ) - 1 ) > - > - x = self.borders[HORZ] + vertical_step > - y = self.plot_top - horizontal_step > - > - for label in self.labels[HORZ][:-1]: > - cr.set_source_rgba(*self.grid_color) > - cr.move_to(x, self.dimensions[VERT] - self.borders[VERT]) > - cr.line_to(x, self.borders[VERT]) > - cr.stroke() > - x += vertical_step > - for label in self.labels[VERT][:-1]: > - cr.set_source_rgba(*self.grid_color) > - cr.move_to(self.borders[HORZ], y) > - cr.line_to(self.dimensions[HORZ] - self.borders[HORZ], y) > - cr.stroke() > - y -= horizontal_step > - > - def render_labels(self): > - self.context.set_font_size(self.font_size * 0.8) > - self.render_horz_labels() > - self.render_vert_labels() > - > - def render_horz_labels(self): > - cr = self.context > - step = float( self.plot_width ) / ( len( self.labels[HORZ] ) - 1 ) > - x = self.borders[HORZ] > - y = self.dimensions[VERT] - self.borders[VERT] + 5 > - > - # store rotation matrix from the initial state > - rotation_matrix = cr.get_matrix() > - rotation_matrix.rotate(self.x_label_angle) > - > - cr.set_source_rgba(*self.label_color) > - > - for item in self.labels[HORZ]: > - width = cr.text_extents(item)[2] > - cr.move_to(x, y) > - cr.save() > - cr.set_matrix(rotation_matrix) > - cr.show_text(item) > - cr.restore() > - x += step > - > - def render_vert_labels(self): > - cr = self.context > - step = ( self.plot_height ) / ( len( self.labels[VERT] ) - 1 ) > - y = self.plot_top > - cr.set_source_rgba(*self.label_color) > - for item in self.labels[VERT]: > - width = cr.text_extents(item)[2] > - cr.move_to(self.borders[HORZ] - width - 5,y) > - cr.show_text(item) > - y -= step > - > - def render_legend(self): > - cr = self.context > - cr.set_font_size(self.font_size) > - cr.set_line_width(self.line_width) > - > - widest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[2]) > - tallest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[3]) > - max_width = self.context.text_extents(widest_word)[2] > - max_height = self.context.text_extents(tallest_word)[3] * 1.1 > - > - color_box_height = max_height / 2 > - color_box_width = color_box_height * 2 > - > - #Draw a bounding box > - bounding_box_width = max_width + color_box_width + 15 > - bounding_box_height = (len(self.series_labels)+0.5) * max_height > - cr.set_source_rgba(1,1,1) > - cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - bounding_box_width, self.borders[VERT], > - bounding_box_width, bounding_box_height) > - cr.fill() > - > - cr.set_source_rgba(*self.line_color) > - cr.set_line_width(self.line_width) > - cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - bounding_box_width, self.borders[VERT], > - bounding_box_width, bounding_box_height) > - cr.stroke() > - > - for idx,key in enumerate(self.series_labels): > - #Draw color box > - cr.set_source_rgba(*self.series_colors[idx][:4]) > - cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - max_width - color_box_width - 10, > - self.borders[VERT] + color_box_height + (idx*max_height) , > - color_box_width, color_box_height) > - cr.fill() > - > - cr.set_source_rgba(0, 0, 0) > - cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - max_width - color_box_width - 10, > - self.borders[VERT] + color_box_height + (idx*max_height), > - color_box_width, color_box_height) > - cr.stroke() > - > - #Draw series labels > - cr.set_source_rgba(0, 0, 0) > - cr.move_to(self.dimensions[HORZ] - self.borders[HORZ] - max_width - 5, self.borders[VERT] + ((idx+1)*max_height)) > - cr.show_text(key) > - > - def render_errors(self): > - cr = self.context > - cr.rectangle(self.borders[HORZ], self.borders[VERT], self.plot_width, self.plot_height) > - cr.clip() > - radius = self.dots > - x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step > - y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step > - for index, group in enumerate(self.series): > - cr.set_source_rgba(*self.series_colors[index][:4]) > - for number, data in enumerate(group): > - x = x0 + self.horizontal_step * data.content[0] > - y = self.dimensions[VERT] - y0 - self.vertical_step * data.content[1] > - if self.errors[HORZ]: > - cr.move_to(x, y) > - x1 = x - self.horizontal_step * self.errors[HORZ][0][number] > - cr.line_to(x1, y) > - cr.line_to(x1, y - radius) > - cr.line_to(x1, y + radius) > - cr.stroke() > - if self.errors[HORZ] and len(self.errors[HORZ]) == 2: > - cr.move_to(x, y) > - x1 = x + self.horizontal_step * self.errors[HORZ][1][number] > - cr.line_to(x1, y) > - cr.line_to(x1, y - radius) > - cr.line_to(x1, y + radius) > - cr.stroke() > - if self.errors[VERT]: > - cr.move_to(x, y) > - y1 = y + self.vertical_step * self.errors[VERT][0][number] > - cr.line_to(x, y1) > - cr.line_to(x - radius, y1) > - cr.line_to(x + radius, y1) > - cr.stroke() > - if self.errors[VERT] and len(self.errors[VERT]) == 2: > - cr.move_to(x, y) > - y1 = y - self.vertical_step * self.errors[VERT][1][number] > - cr.line_to(x, y1) > - cr.line_to(x - radius, y1) > - cr.line_to(x + radius, y1) > - cr.stroke() > - > - > - def render_plot(self): > - cr = self.context > - if self.discrete: > - cr.rectangle(self.borders[HORZ], self.borders[VERT], self.plot_width, self.plot_height) > - cr.clip() > - x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step > - y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step > - radius = self.dots > - for number, group in enumerate (self.series): > - cr.set_source_rgba(*self.series_colors[number][:4]) > - for data in group : > - if self.variable_radius: > - radius = data.content[2]*self.z_step > - if self.circle_colors: > - cr.set_source_rgba( *self.get_circle_color( data.content[2]) ) > - x = x0 + self.horizontal_step*data.content[0] > - y = y0 + self.vertical_step*data.content[1] > - cr.arc(x, self.dimensions[VERT] - y, radius, 0, 2*math.pi) > - cr.fill() > - else: > - cr.rectangle(self.borders[HORZ], self.borders[VERT], self.plot_width, self.plot_height) > - cr.clip() > - x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step > - y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step > - radius = self.dots > - for number, group in enumerate (self.series): > - last_data = None > - cr.set_source_rgba(*self.series_colors[number][:4]) > - for data in group : > - x = x0 + self.horizontal_step*data.content[0] > - y = y0 + self.vertical_step*data.content[1] > - if self.dots: > - if self.variable_radius: > - radius = data.content[2]*self.z_step > - cr.arc(x, self.dimensions[VERT] - y, radius, 0, 2*math.pi) > - cr.fill() > - if last_data : > - old_x = x0 + self.horizontal_step*last_data.content[0] > - old_y = y0 + self.vertical_step*last_data.content[1] > - cr.move_to( old_x, self.dimensions[VERT] - old_y ) > - cr.line_to( x, self.dimensions[VERT] - y) > - cr.set_line_width(self.series_widths[number]) > - > - #?Display line as dash line > - if self.dash and self.dash[number]: > - s = self.series_widths[number] > - cr.set_dash([s*3, s*3], 0) > - > - cr.stroke() > - cr.set_dash([]) > - last_data = data > - > -class DotLinePlot(ScatterPlot): > - def __init__(self, > - surface=None, > - data=None, > - width=640, > - height=480, > - background=None, > - border=0, > - axis = False, > - dash = False, > - dots = 0, > - grid = False, > - series_legend = False, > - x_labels = None, > - y_labels = None, > - x_bounds = None, > - y_bounds = None, > - x_title = None, > - y_title = None, > - series_colors = None): > - > - ScatterPlot.__init__(self, surface, data, None, None, width, height, background, border, > - axis, dash, False, dots, grid, series_legend, x_labels, y_labels, > - x_bounds, y_bounds, None, x_title, y_title, series_colors, None ) > - > - > - def load_series(self, data, x_labels = None, y_labels = None, series_colors=None): > - Plot.load_series(self, data, x_labels, y_labels, series_colors) > - for group in self.series : > - for index,data in enumerate(group): > - group[index].content = (index, data.content) > - > - self.calc_boundaries() > - self.calc_labels() > - > -class FunctionPlot(ScatterPlot): > - def __init__(self, > - surface=None, > - data=None, > - width=640, > - height=480, > - background=None, > - border=0, > - axis = False, > - discrete = False, > - dots = 0, > - grid = False, > - series_legend = False, > - x_labels = None, > - y_labels = None, > - x_bounds = None, > - y_bounds = None, > - x_title = None, > - y_title = None, > - series_colors = None, > - step = 1): > - > - self.function = data > - self.step = step > - self.discrete = discrete > - > - data, x_bounds = self.load_series_from_function( self.function, x_bounds ) > - > - ScatterPlot.__init__(self, surface, data, None, None, width, height, background, border, > - axis, False, discrete, dots, grid, series_legend, x_labels, y_labels, > - x_bounds, y_bounds, None, x_title, y_title, series_colors, None ) > - > - def load_series(self, data, x_labels = None, y_labels = None, series_colors=None): > - Plot.load_series(self, data, x_labels, y_labels, series_colors) > - > - if len(self.series[0][0]) is 1: > - for group_id, group in enumerate(self.series) : > - for index,data in enumerate(group): > - group[index].content = (self.bounds[HORZ][0] + self.step*index, data.content) > - > - self.calc_boundaries() > - self.calc_labels() > - > - def load_series_from_function( self, function, x_bounds ): > - #TODO: Add the possibility for the user to define multiple functions with different discretization parameters > - > - #This function converts a function, a list of functions or a dictionary > - #of functions into its corresponding array of data > - series = Series() > - > - if isinstance(function, Group) or isinstance(function, Data): > - function = Series(function) > - > - # If is instance of Series > - if isinstance(function, Series): > - # Overwrite any bounds passed by the function > - x_bounds = (function.range[0],function.range[-1]) > - > - #if no bounds are provided > - if x_bounds == None: > - x_bounds = (0,10) > - > - > - #TODO: Finish the dict translation > - if hasattr(function, "keys"): #dictionary: > - for key in function.keys(): > - group = Group(name=key) > - #data[ key ] = [] > - i = x_bounds[0] > - while i <= x_bounds[1] : > - group.add_data(function[ key ](i)) > - #data[ key ].append( function[ key ](i) ) > - i += self.step > - series.add_group(group) > - > - elif hasattr(function, "__delitem__"): #list of functions > - for index,f in enumerate( function ) : > - group = Group() > - #data.append( [] ) > - i = x_bounds[0] > - while i <= x_bounds[1] : > - group.add_data(f(i)) > - #data[ index ].append( f(i) ) > - i += self.step > - series.add_group(group) > - > - elif isinstance(function, Series): # instance of Series > - series = function > - > - else: #function > - group = Group() > - i = x_bounds[0] > - while i <= x_bounds[1] : > - group.add_data(function(i)) > - i += self.step > - series.add_group(group) > - > - > - return series, x_bounds > - > - def calc_labels(self): > - if not self.labels[HORZ]: > - self.labels[HORZ] = [] > - i = self.bounds[HORZ][0] > - while i<=self.bounds[HORZ][1]: > - self.labels[HORZ].append(str(i)) > - i += float(self.bounds[HORZ][1] - self.bounds[HORZ][0])/10 > - ScatterPlot.calc_labels(self) > - > - def render_plot(self): > - if not self.discrete: > - ScatterPlot.render_plot(self) > - else: > - last = None > - cr = self.context > - for number, group in enumerate (self.series): > - cr.set_source_rgba(*self.series_colors[number][:4]) > - x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step > - y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step > - for data in group: > - x = x0 + self.horizontal_step * data.content[0] > - y = y0 + self.vertical_step * data.content[1] > - cr.move_to(x, self.dimensions[VERT] - y) > - cr.line_to(x, self.plot_top) > - cr.set_line_width(self.series_widths[number]) > - cr.stroke() > - if self.dots: > - cr.new_path() > - cr.arc(x, self.dimensions[VERT] - y, 3, 0, 2.1 * math.pi) > - cr.close_path() > - cr.fill() > - > -class BarPlot(Plot): > - def __init__(self, > - surface = None, > - data = None, > - width = 640, > - height = 480, > - background = "white light_gray", > - border = 0, > - display_values = False, > - grid = False, > - rounded_corners = False, > - stack = False, > - three_dimension = False, > - x_labels = None, > - y_labels = None, > - x_bounds = None, > - y_bounds = None, > - series_colors = None, > - main_dir = None): > - > - self.bounds = {} > - self.bounds[HORZ] = x_bounds > - self.bounds[VERT] = y_bounds > - self.display_values = display_values > - self.grid = grid > - self.rounded_corners = rounded_corners > - self.stack = stack > - self.three_dimension = three_dimension > - self.x_label_angle = math.pi / 2.5 > - self.main_dir = main_dir > - self.max_value = {} > - self.plot_dimensions = {} > - self.steps = {} > - self.value_label_color = (0.5,0.5,0.5,1.0) > - > - Plot.__init__(self, surface, data, width, height, background, border, x_labels, y_labels, series_colors) > - > - def load_series(self, data, x_labels = None, y_labels = None, series_colors = None): > - Plot.load_series(self, data, x_labels, y_labels, series_colors) > - self.calc_boundaries() > - > - def process_colors(self, series_colors): > - #Data for a BarPlot might be a List or a List of Lists. > - #On the first case, colors must be generated for all bars, > - #On the second, colors must be generated for each of the inner lists. > - > - #TODO: Didn't get it... > - #if hasattr(self.data[0], '__getitem__'): > - # length = max(len(series) for series in self.data) > - #else: > - # length = len( self.data ) > - > - length = max(len(group) for group in self.series) > - > - Plot.process_colors( self, series_colors, length, 'linear') > - > - def calc_boundaries(self): > - if not self.bounds[self.main_dir]: > - if self.stack: > - max_data_value = max(sum(group.to_list()) for group in self.series) > - else: > - max_data_value = max(max(group.to_list()) for group in self.series) > - self.bounds[self.main_dir] = (0, max_data_value) > - if not self.bounds[other_direction(self.main_dir)]: > - self.bounds[other_direction(self.main_dir)] = (0, len(self.series)) > - > - def calc_extents(self, direction): > - self.max_value[direction] = 0 > - if self.labels[direction]: > - widest_word = max(self.labels[direction], key = lambda item: self.context.text_extents(item)[2]) > - self.max_value[direction] = self.context.text_extents(widest_word)[3 - direction] > - self.borders[other_direction(direction)] = (2-direction)*self.max_value[direction] + self.border + direction*(5) > - else: > - self.borders[other_direction(direction)] = self.border > - > - def calc_horz_extents(self): > - self.calc_extents(HORZ) > - > - def calc_vert_extents(self): > - self.calc_extents(VERT) > - > - def calc_all_extents(self): > - self.calc_horz_extents() > - self.calc_vert_extents() > - other_dir = other_direction(self.main_dir) > - self.value_label = 0 > - if self.display_values: > - if self.stack: > - self.value_label = self.context.text_extents(str(max(sum(group.to_list()) for group in self.series)))[2 + self.main_dir] > - else: > - self.value_label = self.context.text_extents(str(max(max(group.to_list()) for group in self.series)))[2 + self.main_dir] > - if self.labels[self.main_dir]: > - self.plot_dimensions[self.main_dir] = self.dimensions[self.main_dir] - 2*self.borders[self.main_dir] - self.value_label > - else: > - self.plot_dimensions[self.main_dir] = self.dimensions[self.main_dir] - self.borders[self.main_dir] - 1.2*self.border - self.value_label > - self.plot_dimensions[other_dir] = self.dimensions[other_dir] - self.borders[other_dir] - self.border > - self.plot_top = self.dimensions[VERT] - self.borders[VERT] > - > - def calc_steps(self): > - other_dir = other_direction(self.main_dir) > - self.series_amplitude = self.bounds[self.main_dir][1] - self.bounds[self.main_dir][0] > - if self.series_amplitude: > - self.steps[self.main_dir] = float(self.plot_dimensions[self.main_dir])/self.series_amplitude > - else: > - self.steps[self.main_dir] = 0.00 > - series_length = len(self.series) > - self.steps[other_dir] = float(self.plot_dimensions[other_dir])/(series_length + 0.1*(series_length + 1)) > - self.space = 0.1*self.steps[other_dir] > - > - def render(self): > - self.calc_all_extents() > - self.calc_steps() > - self.render_background() > - self.render_bounding_box() > - if self.grid: > - self.render_grid() > - if self.three_dimension: > - self.render_ground() > - if self.display_values: > - self.render_values() > - self.render_labels() > - self.render_plot() > - if self.series_labels: > - self.render_legend() > - > - def draw_3d_rectangle_front(self, x0, y0, x1, y1, shift): > - self.context.rectangle(x0-shift, y0+shift, x1-x0, y1-y0) > - > - def draw_3d_rectangle_side(self, x0, y0, x1, y1, shift): > - self.context.move_to(x1-shift,y0+shift) > - self.context.line_to(x1, y0) > - self.context.line_to(x1, y1) > - self.context.line_to(x1-shift, y1+shift) > - self.context.line_to(x1-shift, y0+shift) > - self.context.close_path() > - > - def draw_3d_rectangle_top(self, x0, y0, x1, y1, shift): > - self.context.move_to(x0-shift,y0+shift) > - self.context.line_to(x0, y0) > - self.context.line_to(x1, y0) > - self.context.line_to(x1-shift, y0+shift) > - self.context.line_to(x0-shift, y0+shift) > - self.context.close_path() > - > - def draw_round_rectangle(self, x0, y0, x1, y1): > - self.context.arc(x0+5, y0+5, 5, -math.pi, -math.pi/2) > - self.context.line_to(x1-5, y0) > - self.context.arc(x1-5, y0+5, 5, -math.pi/2, 0) > - self.context.line_to(x1, y1-5) > - self.context.arc(x1-5, y1-5, 5, 0, math.pi/2) > - self.context.line_to(x0+5, y1) > - self.context.arc(x0+5, y1-5, 5, math.pi/2, math.pi) > - self.context.line_to(x0, y0+5) > - self.context.close_path() > - > - def render_ground(self): > - self.draw_3d_rectangle_front(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], > - self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) > - self.context.fill() > - > - self.draw_3d_rectangle_side (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], > - self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) > - self.context.fill() > - > - self.draw_3d_rectangle_top (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], > - self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) > - self.context.fill() > - > - def render_labels(self): > - self.context.set_font_size(self.font_size * 0.8) > - if self.labels[HORZ]: > - self.render_horz_labels() > - if self.labels[VERT]: > - self.render_vert_labels() > - > - def render_legend(self): > - cr = self.context > - cr.set_font_size(self.font_size) > - cr.set_line_width(self.line_width) > - > - widest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[2]) > - tallest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[3]) > - max_width = self.context.text_extents(widest_word)[2] > - max_height = self.context.text_extents(tallest_word)[3] * 1.1 + 5 > - > - color_box_height = max_height / 2 > - color_box_width = color_box_height * 2 > - > - #Draw a bounding box > - bounding_box_width = max_width + color_box_width + 15 > - bounding_box_height = (len(self.series_labels)+0.5) * max_height > - cr.set_source_rgba(1,1,1) > - cr.rectangle(self.dimensions[HORZ] - self.border - bounding_box_width, self.border, > - bounding_box_width, bounding_box_height) > - cr.fill() > - > - cr.set_source_rgba(*self.line_color) > - cr.set_line_width(self.line_width) > - cr.rectangle(self.dimensions[HORZ] - self.border - bounding_box_width, self.border, > - bounding_box_width, bounding_box_height) > - cr.stroke() > - > - for idx,key in enumerate(self.series_labels): > - #Draw color box > - cr.set_source_rgba(*self.series_colors[idx][:4]) > - cr.rectangle(self.dimensions[HORZ] - self.border - max_width - color_box_width - 10, > - self.border + color_box_height + (idx*max_height) , > - color_box_width, color_box_height) > - cr.fill() > - > - cr.set_source_rgba(0, 0, 0) > - cr.rectangle(self.dimensions[HORZ] - self.border - max_width - color_box_width - 10, > - self.border + color_box_height + (idx*max_height), > - color_box_width, color_box_height) > - cr.stroke() > - > - #Draw series labels > - cr.set_source_rgba(0, 0, 0) > - cr.move_to(self.dimensions[HORZ] - self.border - max_width - 5, self.border + ((idx+1)*max_height)) > - cr.show_text(key) > - > - > -class HorizontalBarPlot(BarPlot): > - def __init__(self, > - surface = None, > - data = None, > - width = 640, > - height = 480, > - background = "white light_gray", > - border = 0, > - display_values = False, > - grid = False, > - rounded_corners = False, > - stack = False, > - three_dimension = False, > - series_labels = None, > - x_labels = None, > - y_labels = None, > - x_bounds = None, > - y_bounds = None, > - series_colors = None): > - > - BarPlot.__init__(self, surface, data, width, height, background, border, > - display_values, grid, rounded_corners, stack, three_dimension, > - x_labels, y_labels, x_bounds, y_bounds, series_colors, HORZ) > - self.series_labels = series_labels > - > - def calc_vert_extents(self): > - self.calc_extents(VERT) > - if self.labels[HORZ] and not self.labels[VERT]: > - self.borders[HORZ] += 10 > - > - def draw_rectangle_bottom(self, x0, y0, x1, y1): > - self.context.arc(x0+5, y1-5, 5, math.pi/2, math.pi) > - self.context.line_to(x0, y0+5) > - self.context.arc(x0+5, y0+5, 5, -math.pi, -math.pi/2) > - self.context.line_to(x1, y0) > - self.context.line_to(x1, y1) > - self.context.line_to(x0+5, y1) > - self.context.close_path() > - > - def draw_rectangle_top(self, x0, y0, x1, y1): > - self.context.arc(x1-5, y0+5, 5, -math.pi/2, 0) > - self.context.line_to(x1, y1-5) > - self.context.arc(x1-5, y1-5, 5, 0, math.pi/2) > - self.context.line_to(x0, y1) > - self.context.line_to(x0, y0) > - self.context.line_to(x1, y0) > - self.context.close_path() > - > - def draw_rectangle(self, index, length, x0, y0, x1, y1): > - if length == 1: > - BarPlot.draw_rectangle(self, x0, y0, x1, y1) > - elif index == 0: > - self.draw_rectangle_bottom(x0, y0, x1, y1) > - elif index == length-1: > - self.draw_rectangle_top(x0, y0, x1, y1) > - else: > - self.context.rectangle(x0, y0, x1-x0, y1-y0) > - > - #TODO: Review BarPlot.render_grid code > - def render_grid(self): > - self.context.set_source_rgba(0.8, 0.8, 0.8) > - if self.labels[HORZ]: > - self.context.set_font_size(self.font_size * 0.8) > - step = (self.dimensions[HORZ] - 2*self.borders[HORZ] - self.value_label)/(len(self.labels[HORZ])-1) > - x = self.borders[HORZ] > - next_x = 0 > - for item in self.labels[HORZ]: > - width = self.context.text_extents(item)[2] > - if x - width/2 > next_x and x - width/2 > self.border: > - self.context.move_to(x, self.border) > - self.context.line_to(x, self.dimensions[VERT] - self.borders[VERT]) > - self.context.stroke() > - next_x = x + width/2 > - x += step > - else: > - lines = 11 > - horizontal_step = float(self.plot_dimensions[HORZ])/(lines-1) > - x = self.borders[HORZ] > - for y in xrange(0, lines): > - self.context.move_to(x, self.border) > - self.context.line_to(x, self.dimensions[VERT] - self.borders[VERT]) > - self.context.stroke() > - x += horizontal_step > - > - def render_horz_labels(self): > - step = (self.dimensions[HORZ] - 2*self.borders[HORZ])/(len(self.labels[HORZ])-1) > - x = self.borders[HORZ] > - next_x = 0 > - > - for item in self.labels[HORZ]: > - self.context.set_source_rgba(*self.label_color) > - width = self.context.text_extents(item)[2] > - if x - width/2 > next_x and x - width/2 > self.border: > - self.context.move_to(x - width/2, self.dimensions[VERT] - self.borders[VERT] + self.max_value[HORZ] + 3) > - self.context.show_text(item) > - next_x = x + width/2 > - x += step > - > - def render_vert_labels(self): > - series_length = len(self.labels[VERT]) > - step = (self.plot_dimensions[VERT] - (series_length + 1)*self.space)/(len(self.labels[VERT])) > - y = self.border + step/2 + self.space > - > - for item in self.labels[VERT]: > - self.context.set_source_rgba(*self.label_color) > - width, height = self.context.text_extents(item)[2:4] > - self.context.move_to(self.borders[HORZ] - width - 5, y + height/2) > - self.context.show_text(item) > - y += step + self.space > - self.labels[VERT].reverse() > - > - def render_values(self): > - self.context.set_source_rgba(*self.value_label_color) > - self.context.set_font_size(self.font_size * 0.8) > - if self.stack: > - for i,group in enumerate(self.series): > - value = sum(group.to_list()) > - height = self.context.text_extents(str(value))[3] > - x = self.borders[HORZ] + value*self.steps[HORZ] + 2 > - y = self.borders[VERT] + (i+0.5)*self.steps[VERT] + (i+1)*self.space + height/2 > - self.context.move_to(x, y) > - self.context.show_text(str(value)) > - else: > - for i,group in enumerate(self.series): > - inner_step = self.steps[VERT]/len(group) > - y0 = self.border + i*self.steps[VERT] + (i+1)*self.space > - for number,data in enumerate(group): > - height = self.context.text_extents(str(data.content))[3] > - self.context.move_to(self.borders[HORZ] + data.content*self.steps[HORZ] + 2, y0 + 0.5*inner_step + height/2, ) > - self.context.show_text(str(data.content)) > - y0 += inner_step > - > - def render_plot(self): > - if self.stack: > - for i,group in enumerate(self.series): > - x0 = self.borders[HORZ] > - y0 = self.borders[VERT] + i*self.steps[VERT] + (i+1)*self.space > - for number,data in enumerate(group): > - if self.series_colors[number][4] in ('radial','linear') : > - linear = cairo.LinearGradient( data.content*self.steps[HORZ]/2, y0, data.content*self.steps[HORZ]/2, y0 + self.steps[VERT] ) > - color = self.series_colors[number] > - linear.add_color_stop_rgba(0.0, 3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0,1.0) > - linear.add_color_stop_rgba(1.0, *color[:4]) > - self.context.set_source(linear) > - elif self.series_colors[number][4] == 'solid': > - self.context.set_source_rgba(*self.series_colors[number][:4]) > - if self.rounded_corners: > - self.draw_rectangle(number, len(group), x0, y0, x0+data.content*self.steps[HORZ], y0+self.steps[VERT]) > - self.context.fill() > - else: > - self.context.rectangle(x0, y0, data.content*self.steps[HORZ], self.steps[VERT]) > - self.context.fill() > - x0 += data.content*self.steps[HORZ] > - else: > - for i,group in enumerate(self.series): > - inner_step = self.steps[VERT]/len(group) > - x0 = self.borders[HORZ] > - y0 = self.border + i*self.steps[VERT] + (i+1)*self.space > - for number,data in enumerate(group): > - linear = cairo.LinearGradient(data.content*self.steps[HORZ]/2, y0, data.content*self.steps[HORZ]/2, y0 + inner_step) > - color = self.series_colors[number] > - linear.add_color_stop_rgba(0.0, 3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0,1.0) > - linear.add_color_stop_rgba(1.0, *color[:4]) > - self.context.set_source(linear) > - if self.rounded_corners and data.content != 0: > - BarPlot.draw_round_rectangle(self,x0, y0, x0 + data.content*self.steps[HORZ], y0 + inner_step) > - self.context.fill() > - else: > - self.context.rectangle(x0, y0, data.content*self.steps[HORZ], inner_step) > - self.context.fill() > - y0 += inner_step > - > -class VerticalBarPlot(BarPlot): > - def __init__(self, > - surface = None, > - data = None, > - width = 640, > - height = 480, > - background = "white light_gray", > - border = 0, > - display_values = False, > - grid = False, > - rounded_corners = False, > - stack = False, > - three_dimension = False, > - series_labels = None, > - x_labels = None, > - y_labels = None, > - x_bounds = None, > - y_bounds = None, > - series_colors = None): > - > - BarPlot.__init__(self, surface, data, width, height, background, border, > - display_values, grid, rounded_corners, stack, three_dimension, > - x_labels, y_labels, x_bounds, y_bounds, series_colors, VERT) > - self.series_labels = series_labels > - > - def calc_vert_extents(self): > - self.calc_extents(VERT) > - if self.labels[VERT] and not self.labels[HORZ]: > - self.borders[VERT] += 10 > - > - def draw_rectangle_bottom(self, x0, y0, x1, y1): > - self.context.move_to(x1,y1) > - self.context.arc(x1-5, y1-5, 5, 0, math.pi/2) > - self.context.line_to(x0+5, y1) > - self.context.arc(x0+5, y1-5, 5, math.pi/2, math.pi) > - self.context.line_to(x0, y0) > - self.context.line_to(x1, y0) > - self.context.line_to(x1, y1) > - self.context.close_path() > - > - def draw_rectangle_top(self, x0, y0, x1, y1): > - self.context.arc(x0+5, y0+5, 5, -math.pi, -math.pi/2) > - self.context.line_to(x1-5, y0) > - self.context.arc(x1-5, y0+5, 5, -math.pi/2, 0) > - self.context.line_to(x1, y1) > - self.context.line_to(x0, y1) > - self.context.line_to(x0, y0) > - self.context.close_path() > - > - def draw_rectangle(self, index, length, x0, y0, x1, y1): > - if length == 1: > - BarPlot.draw_rectangle(self, x0, y0, x1, y1) > - elif index == 0: > - self.draw_rectangle_bottom(x0, y0, x1, y1) > - elif index == length-1: > - self.draw_rectangle_top(x0, y0, x1, y1) > - else: > - self.context.rectangle(x0, y0, x1-x0, y1-y0) > - > - def render_grid(self): > - self.context.set_source_rgba(0.8, 0.8, 0.8) > - if self.labels[VERT]: > - lines = len(self.labels[VERT]) > - vertical_step = float(self.plot_dimensions[self.main_dir])/(lines-1) > - y = self.borders[VERT] + self.value_label > - else: > - lines = 11 > - vertical_step = float(self.plot_dimensions[self.main_dir])/(lines-1) > - y = 1.2*self.border + self.value_label > - for x in xrange(0, lines): > - self.context.move_to(self.borders[HORZ], y) > - self.context.line_to(self.dimensions[HORZ] - self.border, y) > - self.context.stroke() > - y += vertical_step > - > - def render_ground(self): > - self.draw_3d_rectangle_front(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], > - self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) > - self.context.fill() > - > - self.draw_3d_rectangle_side (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], > - self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) > - self.context.fill() > - > - self.draw_3d_rectangle_top (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], > - self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) > - self.context.fill() > - > - def render_horz_labels(self): > - series_length = len(self.labels[HORZ]) > - step = float (self.plot_dimensions[HORZ] - (series_length + 1)*self.space)/len(self.labels[HORZ]) > - x = self.borders[HORZ] + step/2 + self.space > - next_x = 0 > - > - for item in self.labels[HORZ]: > - self.context.set_source_rgba(*self.label_color) > - width = self.context.text_extents(item)[2] > - if x - width/2 > next_x and x - width/2 > self.borders[HORZ]: > - self.context.move_to(x - width/2, self.dimensions[VERT] - self.borders[VERT] + self.max_value[HORZ] + 3) > - self.context.show_text(item) > - next_x = x + width/2 > - x += step + self.space > - > - def render_vert_labels(self): > - self.context.set_source_rgba(*self.label_color) > - y = self.borders[VERT] + self.value_label > - step = (self.dimensions[VERT] - 2*self.borders[VERT] - self.value_label)/(len(self.labels[VERT]) - 1) > - self.labels[VERT].reverse() > - for item in self.labels[VERT]: > - width, height = self.context.text_extents(item)[2:4] > - self.context.move_to(self.borders[HORZ] - width - 5, y + height/2) > - self.context.show_text(item) > - y += step > - self.labels[VERT].reverse() > - > - def render_values(self): > - self.context.set_source_rgba(*self.value_label_color) > - self.context.set_font_size(self.font_size * 0.8) > - if self.stack: > - for i,group in enumerate(self.series): > - value = sum(group.to_list()) > - width = self.context.text_extents(str(value))[2] > - x = self.borders[HORZ] + (i+0.5)*self.steps[HORZ] + (i+1)*self.space - width/2 > - y = value*self.steps[VERT] + 2 > - self.context.move_to(x, self.plot_top-y) > - self.context.show_text(str(value)) > - else: > - for i,group in enumerate(self.series): > - inner_step = self.steps[HORZ]/len(group) > - x0 = self.borders[HORZ] + i*self.steps[HORZ] + (i+1)*self.space > - for number,data in enumerate(group): > - width = self.context.text_extents(str(data.content))[2] > - self.context.move_to(x0 + 0.5*inner_step - width/2, self.plot_top - data.content*self.steps[VERT] - 2) > - self.context.show_text(str(data.content)) > - x0 += inner_step > - > - def render_plot(self): > - if self.stack: > - for i,group in enumerate(self.series): > - x0 = self.borders[HORZ] + i*self.steps[HORZ] + (i+1)*self.space > - y0 = 0 > - for number,data in enumerate(group): > - if self.series_colors[number][4] in ('linear','radial'): > - linear = cairo.LinearGradient( x0, data.content*self.steps[VERT]/2, x0 + self.steps[HORZ], data.content*self.steps[VERT]/2 ) > - color = self.series_colors[number] > - linear.add_color_stop_rgba(0.0, 3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0,1.0) > - linear.add_color_stop_rgba(1.0, *color[:4]) > - self.context.set_source(linear) > - elif self.series_colors[number][4] == 'solid': > - self.context.set_source_rgba(*self.series_colors[number][:4]) > - if self.rounded_corners: > - self.draw_rectangle(number, len(group), x0, self.plot_top - y0 - data.content*self.steps[VERT], x0 + self.steps[HORZ], self.plot_top - y0) > - self.context.fill() > - else: > - self.context.rectangle(x0, self.plot_top - y0 - data.content*self.steps[VERT], self.steps[HORZ], data.content*self.steps[VERT]) > - self.context.fill() > - y0 += data.content*self.steps[VERT] > - else: > - for i,group in enumerate(self.series): > - inner_step = self.steps[HORZ]/len(group) > - y0 = self.borders[VERT] > - x0 = self.borders[HORZ] + i*self.steps[HORZ] + (i+1)*self.space > - for number,data in enumerate(group): > - if self.series_colors[number][4] == 'linear': > - linear = cairo.LinearGradient( x0, data.content*self.steps[VERT]/2, x0 + inner_step, data.content*self.steps[VERT]/2 ) > - color = self.series_colors[number] > - linear.add_color_stop_rgba(0.0, 3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0,1.0) > - linear.add_color_stop_rgba(1.0, *color[:4]) > - self.context.set_source(linear) > - elif self.series_colors[number][4] == 'solid': > - self.context.set_source_rgba(*self.series_colors[number][:4]) > - if self.rounded_corners and data.content != 0: > - BarPlot.draw_round_rectangle(self, x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top) > - self.context.fill() > - elif self.three_dimension: > - self.draw_3d_rectangle_front(x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top, 5) > - self.context.fill() > - self.draw_3d_rectangle_side(x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top, 5) > - self.context.fill() > - self.draw_3d_rectangle_top(x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top, 5) > - self.context.fill() > - else: > - self.context.rectangle(x0, self.plot_top - data.content*self.steps[VERT], inner_step, data.content*self.steps[VERT]) > - self.context.fill() > - > - x0 += inner_step > - > -class StreamChart(VerticalBarPlot): > - def __init__(self, > - surface = None, > - data = None, > - width = 640, > - height = 480, > - background = "white light_gray", > - border = 0, > - grid = False, > - series_legend = None, > - x_labels = None, > - x_bounds = None, > - y_bounds = None, > - series_colors = None): > - > - VerticalBarPlot.__init__(self, surface, data, width, height, background, border, > - False, grid, False, True, False, > - None, x_labels, None, x_bounds, y_bounds, series_colors) > - > - def calc_steps(self): > - other_dir = other_direction(self.main_dir) > - self.series_amplitude = self.bounds[self.main_dir][1] - self.bounds[self.main_dir][0] > - if self.series_amplitude: > - self.steps[self.main_dir] = float(self.plot_dimensions[self.main_dir])/self.series_amplitude > - else: > - self.steps[self.main_dir] = 0.00 > - series_length = len(self.data) > - self.steps[other_dir] = float(self.plot_dimensions[other_dir])/series_length > - > - def render_legend(self): > - pass > - > - def ground(self, index): > - sum_values = sum(self.data[index]) > - return -0.5*sum_values > - > - def calc_angles(self): > - middle = self.plot_top - self.plot_dimensions[VERT]/2.0 > - self.angles = [tuple([0.0 for x in range(len(self.data)+1)])] > - for x_index in range(1, len(self.data)-1): > - t = [] > - x0 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ] > - x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ] > - y0 = middle - self.ground(x_index-1)*self.steps[VERT] > - y2 = middle - self.ground(x_index+1)*self.steps[VERT] > - t.append(math.atan(float(y0-y2)/(x0-x2))) > - for data_index in range(len(self.data[x_index])): > - x0 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ] > - x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ] > - y0 = middle - self.ground(x_index-1)*self.steps[VERT] - self.data[x_index-1][data_index]*self.steps[VERT] > - y2 = middle - self.ground(x_index+1)*self.steps[VERT] - self.data[x_index+1][data_index]*self.steps[VERT] > - > - for i in range(0,data_index): > - y0 -= self.data[x_index-1][i]*self.steps[VERT] > - y2 -= self.data[x_index+1][i]*self.steps[VERT] > - > - if data_index == len(self.data[0])-1 and False: > - self.context.set_source_rgba(0.0,0.0,0.0,0.3) > - self.context.move_to(x0,y0) > - self.context.line_to(x2,y2) > - self.context.stroke() > - self.context.arc(x0,y0,2,0,2*math.pi) > - self.context.fill() > - t.append(math.atan(float(y0-y2)/(x0-x2))) > - self.angles.append(tuple(t)) > - self.angles.append(tuple([0.0 for x in range(len(self.data)+1)])) > - > - def render_plot(self): > - self.calc_angles() > - middle = self.plot_top - self.plot_dimensions[VERT]/2.0 > - p = 0.4*self.steps[HORZ] > - for data_index in range(len(self.data[0])-1,-1,-1): > - self.context.set_source_rgba(*self.series_colors[data_index][:4]) > - > - #draw the upper line > - for x_index in range(len(self.data)-1) : > - x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ] > - y1 = middle - self.ground(x_index)*self.steps[VERT] - self.data[x_index][data_index]*self.steps[VERT] > - x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ] > - y2 = middle - self.ground(x_index + 1)*self.steps[VERT] - self.data[x_index + 1][data_index]*self.steps[VERT] > - > - for i in range(0,data_index): > - y1 -= self.data[x_index][i]*self.steps[VERT] > - y2 -= self.data[x_index+1][i]*self.steps[VERT] > - > - if x_index == 0: > - self.context.move_to(x1,y1) > - > - ang1 = self.angles[x_index][data_index+1] > - ang2 = self.angles[x_index+1][data_index+1] + math.pi > - self.context.curve_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1), > - x2+p*math.cos(ang2),y2+p*math.sin(ang2), > - x2,y2) > - > - for x_index in range(len(self.data)-1,0,-1) : > - x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ] > - y1 = middle - self.ground(x_index)*self.steps[VERT] > - x2 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ] > - y2 = middle - self.ground(x_index - 1)*self.steps[VERT] > - > - for i in range(0,data_index): > - y1 -= self.data[x_index][i]*self.steps[VERT] > - y2 -= self.data[x_index-1][i]*self.steps[VERT] > - > - if x_index == len(self.data)-1: > - self.context.line_to(x1,y1+2) > - > - #revert angles by pi degrees to take the turn back > - ang1 = self.angles[x_index][data_index] + math.pi > - ang2 = self.angles[x_index-1][data_index] > - self.context.curve_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1), > - x2+p*math.cos(ang2),y2+p*math.sin(ang2), > - x2,y2+2) > - > - self.context.close_path() > - self.context.fill() > - > - if False: > - self.context.move_to(self.borders[HORZ] + 0.5*self.steps[HORZ], middle) > - for x_index in range(len(self.data)-1) : > - x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ] > - y1 = middle - self.ground(x_index)*self.steps[VERT] - self.data[x_index][data_index]*self.steps[VERT] > - x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ] > - y2 = middle - self.ground(x_index + 1)*self.steps[VERT] - self.data[x_index + 1][data_index]*self.steps[VERT] > - > - for i in range(0,data_index): > - y1 -= self.data[x_index][i]*self.steps[VERT] > - y2 -= self.data[x_index+1][i]*self.steps[VERT] > - > - ang1 = self.angles[x_index][data_index+1] > - ang2 = self.angles[x_index+1][data_index+1] + math.pi > - self.context.set_source_rgba(1.0,0.0,0.0) > - self.context.arc(x1+p*math.cos(ang1),y1+p*math.sin(ang1),2,0,2*math.pi) > - self.context.fill() > - self.context.set_source_rgba(0.0,0.0,0.0) > - self.context.arc(x2+p*math.cos(ang2),y2+p*math.sin(ang2),2,0,2*math.pi) > - self.context.fill() > - '''self.context.set_source_rgba(0.0,0.0,0.0,0.3) > - self.context.arc(x2,y2,2,0,2*math.pi) > - self.context.fill()''' > - self.context.move_to(x1,y1) > - self.context.line_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1)) > - self.context.stroke() > - self.context.move_to(x2,y2) > - self.context.line_to(x2+p*math.cos(ang2),y2+p*math.sin(ang2)) > - self.context.stroke() > - if False: > - for x_index in range(len(self.data)-1,0,-1) : > - x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ] > - y1 = middle - self.ground(x_index)*self.steps[VERT] > - x2 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ] > - y2 = middle - self.ground(x_index - 1)*self.steps[VERT] > - > - for i in range(0,data_index): > - y1 -= self.data[x_index][i]*self.steps[VERT] > - y2 -= self.data[x_index-1][i]*self.steps[VERT] > - > - #revert angles by pi degrees to take the turn back > - ang1 = self.angles[x_index][data_index] + math.pi > - ang2 = self.angles[x_index-1][data_index] > - self.context.set_source_rgba(0.0,1.0,0.0) > - self.context.arc(x1+p*math.cos(ang1),y1+p*math.sin(ang1),2,0,2*math.pi) > - self.context.fill() > - self.context.set_source_rgba(0.0,0.0,1.0) > - self.context.arc(x2+p*math.cos(ang2),y2+p*math.sin(ang2),2,0,2*math.pi) > - self.context.fill() > - '''self.context.set_source_rgba(0.0,0.0,0.0,0.3) > - self.context.arc(x2,y2,2,0,2*math.pi) > - self.context.fill()''' > - self.context.move_to(x1,y1) > - self.context.line_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1)) > - self.context.stroke() > - self.context.move_to(x2,y2) > - self.context.line_to(x2+p*math.cos(ang2),y2+p*math.sin(ang2)) > - self.context.stroke() > - #break > - > - #self.context.arc(self.dimensions[HORZ]/2, self.dimensions[VERT]/2,50,0,3*math.pi/2) > - #self.context.fill() > - > - > -class PiePlot(Plot): > - #TODO: Check the old cairoplot, graphs aren't matching > - def __init__ (self, > - surface = None, > - data = None, > - width = 640, > - height = 480, > - background = "white light_gray", > - gradient = False, > - shadow = False, > - colors = None): > - > - Plot.__init__( self, surface, data, width, height, background, series_colors = colors ) > - self.center = (self.dimensions[HORZ]/2, self.dimensions[VERT]/2) > - self.total = sum( self.series.to_list() ) > - self.radius = min(self.dimensions[HORZ]/3,self.dimensions[VERT]/3) > - self.gradient = gradient > - self.shadow = shadow > - > - def sort_function(x,y): > - return x.content - y.content > - > - def load_series(self, data, x_labels=None, y_labels=None, series_colors=None): > - Plot.load_series(self, data, x_labels, y_labels, series_colors) > - # Already done inside series > - #self.data = sorted(self.data) > - > - def draw_piece(self, angle, next_angle): > - self.context.move_to(self.center[0],self.center[1]) > - self.context.line_to(self.center[0] + self.radius*math.cos(angle), self.center[1] + self.radius*math.sin(angle)) > - self.context.arc(self.center[0], self.center[1], self.radius, angle, next_angle) > - self.context.line_to(self.center[0], self.center[1]) > - self.context.close_path() > - > - def render(self): > - self.render_background() > - self.render_bounding_box() > - if self.shadow: > - self.render_shadow() > - self.render_plot() > - self.render_series_labels() > - > - def render_shadow(self): > - horizontal_shift = 3 > - vertical_shift = 3 > - self.context.set_source_rgba(0, 0, 0, 0.5) > - self.context.arc(self.center[0] + horizontal_shift, self.center[1] + vertical_shift, self.radius, 0, 2*math.pi) > - self.context.fill() > - > - def render_series_labels(self): > - angle = 0 > - next_angle = 0 > - x0,y0 = self.center > - cr = self.context > - for number,key in enumerate(self.series_labels): > - # self.data[number] should be just a number > - data = sum(self.series[number].to_list()) > - > - next_angle = angle + 2.0*math.pi*data/self.total > - cr.set_source_rgba(*self.series_colors[number][:4]) > - w = cr.text_extents(key)[2] > - if (angle + next_angle)/2 < math.pi/2 or (angle + next_angle)/2 > 3*math.pi/2: > - cr.move_to(x0 + (self.radius+10)*math.cos((angle+next_angle)/2), y0 + (self.radius+10)*math.sin((angle+next_angle)/2) ) > - else: > - cr.move_to(x0 + (self.radius+10)*math.cos((angle+next_angle)/2) - w, y0 + (self.radius+10)*math.sin((angle+next_angle)/2) ) > - cr.show_text(key) > - angle = next_angle > - > - def render_plot(self): > - angle = 0 > - next_angle = 0 > - x0,y0 = self.center > - cr = self.context > - for number,group in enumerate(self.series): > - # Group should be just a number > - data = sum(group.to_list()) > - next_angle = angle + 2.0*math.pi*data/self.total > - if self.gradient or self.series_colors[number][4] in ('linear','radial'): > - gradient_color = cairo.RadialGradient(self.center[0], self.center[1], 0, self.center[0], self.center[1], self.radius) > - gradient_color.add_color_stop_rgba(0.3, *self.series_colors[number][:4]) > - gradient_color.add_color_stop_rgba(1, self.series_colors[number][0]*0.7, > - self.series_colors[number][1]*0.7, > - self.series_colors[number][2]*0.7, > - self.series_colors[number][3]) > - cr.set_source(gradient_color) > - else: > - cr.set_source_rgba(*self.series_colors[number][:4]) > - > - self.draw_piece(angle, next_angle) > - cr.fill() > - > - cr.set_source_rgba(1.0, 1.0, 1.0) > - self.draw_piece(angle, next_angle) > - cr.stroke() > - > - angle = next_angle > - > -class DonutPlot(PiePlot): > - def __init__ (self, > - surface = None, > - data = None, > - width = 640, > - height = 480, > - background = "white light_gray", > - gradient = False, > - shadow = False, > - colors = None, > - inner_radius=-1): > - > - Plot.__init__( self, surface, data, width, height, background, series_colors = colors ) > - > - self.center = ( self.dimensions[HORZ]/2, self.dimensions[VERT]/2 ) > - self.total = sum( self.series.to_list() ) > - self.radius = min( self.dimensions[HORZ]/3,self.dimensions[VERT]/3 ) > - self.inner_radius = inner_radius*self.radius > - > - if inner_radius == -1: > - self.inner_radius = self.radius/3 > - > - self.gradient = gradient > - self.shadow = shadow > - > - def draw_piece(self, angle, next_angle): > - self.context.move_to(self.center[0] + (self.inner_radius)*math.cos(angle), self.center[1] + (self.inner_radius)*math.sin(angle)) > - self.context.line_to(self.center[0] + self.radius*math.cos(angle), self.center[1] + self.radius*math.sin(angle)) > - self.context.arc(self.center[0], self.center[1], self.radius, angle, next_angle) > - self.context.line_to(self.center[0] + (self.inner_radius)*math.cos(next_angle), self.center[1] + (self.inner_radius)*math.sin(next_angle)) > - self.context.arc_negative(self.center[0], self.center[1], self.inner_radius, next_angle, angle) > - self.context.close_path() > - > - def render_shadow(self): > - horizontal_shift = 3 > - vertical_shift = 3 > - self.context.set_source_rgba(0, 0, 0, 0.5) > - self.context.arc(self.center[0] + horizontal_shift, self.center[1] + vertical_shift, self.inner_radius, 0, 2*math.pi) > - self.context.arc_negative(self.center[0] + horizontal_shift, self.center[1] + vertical_shift, self.radius, 0, -2*math.pi) > - self.context.fill() > - > -class GanttChart (Plot) : > - def __init__(self, > - surface = None, > - data = None, > - width = 640, > - height = 480, > - x_labels = None, > - y_labels = None, > - colors = None): > - self.bounds = {} > - self.max_value = {} > - Plot.__init__(self, surface, data, width, height, x_labels = x_labels, y_labels = y_labels, series_colors = colors) > - > - def load_series(self, data, x_labels=None, y_labels=None, series_colors=None): > - Plot.load_series(self, data, x_labels, y_labels, series_colors) > - self.calc_boundaries() > - > - def calc_boundaries(self): > - self.bounds[HORZ] = (0,len(self.series)) > - end_pos = max(self.series.to_list()) > - > - #for group in self.series: > - # if hasattr(item, "__delitem__"): > - # for sub_item in item: > - # end_pos = max(sub_item) > - # else: > - # end_pos = max(item) > - self.bounds[VERT] = (0,end_pos) > - > - def calc_extents(self, direction): > - self.max_value[direction] = 0 > - if self.labels[direction]: > - self.max_value[direction] = max(self.context.text_extents(item)[2] for item in self.labels[direction]) > - else: > - self.max_value[direction] = self.context.text_extents( str(self.bounds[direction][1] + 1) )[2] > - > - def calc_horz_extents(self): > - self.calc_extents(HORZ) > - self.borders[HORZ] = 100 + self.max_value[HORZ] > - > - def calc_vert_extents(self): > - self.calc_extents(VERT) > - self.borders[VERT] = self.dimensions[VERT]/(self.bounds[HORZ][1] + 1) > - > - def calc_steps(self): > - self.horizontal_step = (self.dimensions[HORZ] - self.borders[HORZ])/(len(self.labels[VERT])) > - self.vertical_step = self.borders[VERT] > - > - def render(self): > - self.calc_horz_extents() > - self.calc_vert_extents() > - self.calc_steps() > - self.render_background() > - > - self.render_labels() > - self.render_grid() > - self.render_plot() > - > - def render_background(self): > - cr = self.context > - cr.set_source_rgba(255,255,255) > - cr.rectangle(0,0,self.dimensions[HORZ], self.dimensions[VERT]) > - cr.fill() > - for number,group in enumerate(self.series): > - linear = cairo.LinearGradient(self.dimensions[HORZ]/2, self.borders[VERT] + number*self.vertical_step, > - self.dimensions[HORZ]/2, self.borders[VERT] + (number+1)*self.vertical_step) > - linear.add_color_stop_rgba(0,1.0,1.0,1.0,1.0) > - linear.add_color_stop_rgba(1.0,0.9,0.9,0.9,1.0) > - cr.set_source(linear) > - cr.rectangle(0,self.borders[VERT] + number*self.vertical_step,self.dimensions[HORZ],self.vertical_step) > - cr.fill() > - > - def render_grid(self): > - cr = self.context > - cr.set_source_rgba(0.7, 0.7, 0.7) > - cr.set_dash((1,0,0,0,0,0,1)) > - cr.set_line_width(0.5) > - for number,label in enumerate(self.labels[VERT]): > - h = cr.text_extents(label)[3] > - cr.move_to(self.borders[HORZ] + number*self.horizontal_step, self.vertical_step/2 + h) > - cr.line_to(self.borders[HORZ] + number*self.horizontal_step, self.dimensions[VERT]) > - cr.stroke() > - > - def render_labels(self): > - self.context.set_font_size(0.02 * self.dimensions[HORZ]) > - > - self.render_horz_labels() > - self.render_vert_labels() > - > - def render_horz_labels(self): > - cr = self.context > - labels = self.labels[HORZ] > - if not labels: > - labels = [str(i) for i in range(1, self.bounds[HORZ][1] + 1) ] > - for number,label in enumerate(labels): > - if label != None: > - cr.set_source_rgba(0.5, 0.5, 0.5) > - w,h = cr.text_extents(label)[2], cr.text_extents(label)[3] > - cr.move_to(40,self.borders[VERT] + number*self.vertical_step + self.vertical_step/2 + h/2) > - cr.show_text(label) > - > - def render_vert_labels(self): > - cr = self.context > - labels = self.labels[VERT] > - if not labels: > - labels = [str(i) for i in range(1, self.bounds[VERT][1] + 1) ] > - for number,label in enumerate(labels): > - w,h = cr.text_extents(label)[2], cr.text_extents(label)[3] > - cr.move_to(self.borders[HORZ] + number*self.horizontal_step - w/2, self.vertical_step/2) > - cr.show_text(label) > - > - def render_rectangle(self, x0, y0, x1, y1, color): > - self.draw_shadow(x0, y0, x1, y1) > - self.draw_rectangle(x0, y0, x1, y1, color) > - > - def draw_rectangular_shadow(self, gradient, x0, y0, w, h): > - self.context.set_source(gradient) > - self.context.rectangle(x0,y0,w,h) > - self.context.fill() > - > - def draw_circular_shadow(self, x, y, radius, ang_start, ang_end, mult, shadow): > - gradient = cairo.RadialGradient(x, y, 0, x, y, 2*radius) > - gradient.add_color_stop_rgba(0, 0, 0, 0, shadow) > - gradient.add_color_stop_rgba(1, 0, 0, 0, 0) > - self.context.set_source(gradient) > - self.context.move_to(x,y) > - self.context.line_to(x + mult[0]*radius,y + mult[1]*radius) > - self.context.arc(x, y, 8, ang_start, ang_end) > - self.context.line_to(x,y) > - self.context.close_path() > - self.context.fill() > - > - def draw_rectangle(self, x0, y0, x1, y1, color): > - cr = self.context > - middle = (x0+x1)/2 > - linear = cairo.LinearGradient(middle,y0,middle,y1) > - linear.add_color_stop_rgba(0,3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0,1.0) > - linear.add_color_stop_rgba(1,*color[:4]) > - cr.set_source(linear) > - > - cr.arc(x0+5, y0+5, 5, 0, 2*math.pi) > - cr.arc(x1-5, y0+5, 5, 0, 2*math.pi) > - cr.arc(x0+5, y1-5, 5, 0, 2*math.pi) > - cr.arc(x1-5, y1-5, 5, 0, 2*math.pi) > - cr.rectangle(x0+5,y0,x1-x0-10,y1-y0) > - cr.rectangle(x0,y0+5,x1-x0,y1-y0-10) > - cr.fill() > - > - def draw_shadow(self, x0, y0, x1, y1): > - shadow = 0.4 > - h_mid = (x0+x1)/2 > - v_mid = (y0+y1)/2 > - h_linear_1 = cairo.LinearGradient(h_mid,y0-4,h_mid,y0+4) > - h_linear_2 = cairo.LinearGradient(h_mid,y1-4,h_mid,y1+4) > - v_linear_1 = cairo.LinearGradient(x0-4,v_mid,x0+4,v_mid) > - v_linear_2 = cairo.LinearGradient(x1-4,v_mid,x1+4,v_mid) > - > - h_linear_1.add_color_stop_rgba( 0, 0, 0, 0, 0) > - h_linear_1.add_color_stop_rgba( 1, 0, 0, 0, shadow) > - h_linear_2.add_color_stop_rgba( 0, 0, 0, 0, shadow) > - h_linear_2.add_color_stop_rgba( 1, 0, 0, 0, 0) > - v_linear_1.add_color_stop_rgba( 0, 0, 0, 0, 0) > - v_linear_1.add_color_stop_rgba( 1, 0, 0, 0, shadow) > - v_linear_2.add_color_stop_rgba( 0, 0, 0, 0, shadow) > - v_linear_2.add_color_stop_rgba( 1, 0, 0, 0, 0) > - > - self.draw_rectangular_shadow(h_linear_1,x0+4,y0-4,x1-x0-8,8) > - self.draw_rectangular_shadow(h_linear_2,x0+4,y1-4,x1-x0-8,8) > - self.draw_rectangular_shadow(v_linear_1,x0-4,y0+4,8,y1-y0-8) > - self.draw_rectangular_shadow(v_linear_2,x1-4,y0+4,8,y1-y0-8) > - > - self.draw_circular_shadow(x0+4, y0+4, 4, math.pi, 3*math.pi/2, (-1,0), shadow) > - self.draw_circular_shadow(x1-4, y0+4, 4, 3*math.pi/2, 2*math.pi, (0,-1), shadow) > - self.draw_circular_shadow(x0+4, y1-4, 4, math.pi/2, math.pi, (0,1), shadow) > - self.draw_circular_shadow(x1-4, y1-4, 4, 0, math.pi/2, (1,0), shadow) > - > - def render_plot(self): > - for index,group in enumerate(self.series): > - for data in group: > - self.render_rectangle(self.borders[HORZ] + data.content[0]*self.horizontal_step, > - self.borders[VERT] + index*self.vertical_step + self.vertical_step/4.0, > - self.borders[HORZ] + data.content[1]*self.horizontal_step, > - self.borders[VERT] + index*self.vertical_step + 3.0*self.vertical_step/4.0, > - self.series_colors[index]) > - > -# Function definition > - > -def scatter_plot(name, > - data = None, > - errorx = None, > - errory = None, > - width = 640, > - height = 480, > - background = "white light_gray", > - border = 0, > - axis = False, > - dash = False, > - discrete = False, > - dots = False, > - grid = False, > - series_legend = False, > - x_labels = None, > - y_labels = None, > - x_bounds = None, > - y_bounds = None, > - z_bounds = None, > - x_title = None, > - y_title = None, > - series_colors = None, > - circle_colors = None): > - > - ''' > - - Function to plot scatter data. > - > - - Parameters > - > - data - The values to be ploted might be passed in a two basic: > - list of points: [(0,0), (0,1), (0,2)] or [(0,0,1), (0,1,4), (0,2,1)] > - lists of coordinates: [ [0,0,0] , [0,1,2] ] or [ [0,0,0] , [0,1,2] , [1,4,1] ] > - Notice that these kinds of that can be grouped in order to form more complex data > - using lists of lists or dictionaries; > - series_colors - Define color values for each of the series > - circle_colors - Define a lower and an upper bound for the circle colors for variable radius > - (3 dimensions) series > - ''' > - > - plot = ScatterPlot( name, data, errorx, errory, width, height, background, border, > - axis, dash, discrete, dots, grid, series_legend, x_labels, y_labels, > - x_bounds, y_bounds, z_bounds, x_title, y_title, series_colors, circle_colors ) > - plot.render() > - plot.commit() > - > -def dot_line_plot(name, > - data, > - width, > - height, > - background = "white light_gray", > - border = 0, > - axis = False, > - dash = False, > - dots = False, > - grid = False, > - series_legend = False, > - x_labels = None, > - y_labels = None, > - x_bounds = None, > - y_bounds = None, > - x_title = None, > - y_title = None, > - series_colors = None): > - ''' > - - Function to plot graphics using dots and lines. > - > - dot_line_plot (name, data, width, height, background = "white light_gray", border = 0, axis = False, grid = False, x_labels = None, y_labels = None, x_bounds = None, y_bounds = None) > - > - - Parameters > - > - name - Name of the desired output file, no need to input the .svg as it will be added at runtim; > - data - The list, list of lists or dictionary holding the data to be plotted; > - width, height - Dimensions of the output image; > - background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. > - If left None, a gray to white gradient will be generated; > - border - Distance in pixels of a square border into which the graphics will be drawn; > - axis - Whether or not the axis are to be drawn; > - dash - Boolean or a list or a dictionary of booleans indicating whether or not the associated series should be drawn in dashed mode; > - dots - Whether or not dots should be drawn on each point; > - grid - Whether or not the gris is to be drawn; > - series_legend - Whether or not the legend is to be drawn; > - x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis; > - x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted; > - x_title - Whether or not to plot a title over the x axis. > - y_title - Whether or not to plot a title over the y axis. > - > - - Examples of use > - > - data = [0, 1, 3, 8, 9, 0, 10, 10, 2, 1] > - CairoPlot.dot_line_plot('teste', data, 400, 300) > - > - data = { "john" : [10, 10, 10, 10, 30], "mary" : [0, 0, 3, 5, 15], "philip" : [13, 32, 11, 25, 2] } > - x_labels = ["jan/2008", "feb/2008", "mar/2008", "apr/2008", "may/2008" ] > - CairoPlot.dot_line_plot( 'test', data, 400, 300, axis = True, grid = True, > - series_legend = True, x_labels = x_labels ) > - ''' > - plot = DotLinePlot( name, data, width, height, background, border, > - axis, dash, dots, grid, series_legend, x_labels, y_labels, > - x_bounds, y_bounds, x_title, y_title, series_colors ) > - plot.render() > - plot.commit() > - > -def function_plot(name, > - data, > - width, > - height, > - background = "white light_gray", > - border = 0, > - axis = True, > - dots = False, > - discrete = False, > - grid = False, > - series_legend = False, > - x_labels = None, > - y_labels = None, > - x_bounds = None, > - y_bounds = None, > - x_title = None, > - y_title = None, > - series_colors = None, > - step = 1): > - > - ''' > - - Function to plot functions. > - > - function_plot(name, data, width, height, background = "white light_gray", border = 0, axis = True, grid = False, dots = False, x_labels = None, y_labels = None, x_bounds = None, y_bounds = None, step = 1, discrete = False) > - > - - Parameters > - > - name - Name of the desired output file, no need to input the .svg as it will be added at runtim; > - data - The list, list of lists or dictionary holding the data to be plotted; > - width, height - Dimensions of the output image; > - background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. > - If left None, a gray to white gradient will be generated; > - border - Distance in pixels of a square border into which the graphics will be drawn; > - axis - Whether or not the axis are to be drawn; > - grid - Whether or not the gris is to be drawn; > - dots - Whether or not dots should be shown at each point; > - x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis; > - x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted; > - step - the horizontal distance from one point to the other. The smaller, the smoother the curve will be; > - discrete - whether or not the function should be plotted in discrete format. > - > - - Example of use > - > - data = lambda x : x**2 > - CairoPlot.function_plot('function4', data, 400, 300, grid = True, x_bounds=(-10,10), step = 0.1) > - ''' > - > - plot = FunctionPlot( name, data, width, height, background, border, > - axis, discrete, dots, grid, series_legend, x_labels, y_labels, > - x_bounds, y_bounds, x_title, y_title, series_colors, step ) > - plot.render() > - plot.commit() > - > -def pie_plot( name, data, width, height, background = "white light_gray", gradient = False, shadow = False, colors = None ): > - > - ''' > - - Function to plot pie graphics. > - > - pie_plot(name, data, width, height, background = "white light_gray", gradient = False, colors = None) > - > - - Parameters > - > - name - Name of the desired output file, no need to input the .svg as it will be added at runtim; > - data - The list, list of lists or dictionary holding the data to be plotted; > - width, height - Dimensions of the output image; > - background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. > - If left None, a gray to white gradient will be generated; > - gradient - Whether or not the pie color will be painted with a gradient; > - shadow - Whether or not there will be a shadow behind the pie; > - colors - List of slices colors. > - > - - Example of use > - > - teste_data = {"john" : 123, "mary" : 489, "philip" : 890 , "suzy" : 235} > - CairoPlot.pie_plot("pie_teste", teste_data, 500, 500) > - ''' > - > - plot = PiePlot( name, data, width, height, background, gradient, shadow, colors ) > - plot.render() > - plot.commit() > - > -def donut_plot(name, data, width, height, background = "white light_gray", gradient = False, shadow = False, colors = None, inner_radius = -1): > - > - ''' > - - Function to plot donut graphics. > - > - donut_plot(name, data, width, height, background = "white light_gray", gradient = False, inner_radius = -1) > - > - - Parameters > - > - name - Name of the desired output file, no need to input the .svg as it will be added at runtim; > - data - The list, list of lists or dictionary holding the data to be plotted; > - width, height - Dimensions of the output image; > - background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. > - If left None, a gray to white gradient will be generated; > - shadow - Whether or not there will be a shadow behind the donut; > - gradient - Whether or not the donut color will be painted with a gradient; > - colors - List of slices colors; > - inner_radius - The radius of the donut's inner circle. > - > - - Example of use > - > - teste_data = {"john" : 123, "mary" : 489, "philip" : 890 , "suzy" : 235} > - CairoPlot.donut_plot("donut_teste", teste_data, 500, 500) > - ''' > - > - plot = DonutPlot(name, data, width, height, background, gradient, shadow, colors, inner_radius) > - plot.render() > - plot.commit() > - > -def gantt_chart(name, pieces, width, height, x_labels, y_labels, colors): > - > - ''' > - - Function to generate Gantt Charts. > - > - gantt_chart(name, pieces, width, height, x_labels, y_labels, colors): > - > - - Parameters > - > - name - Name of the desired output file, no need to input the .svg as it will be added at runtim; > - pieces - A list defining the spaces to be drawn. The user must pass, for each line, the index of its start and the index of its end. If a line must have two or more spaces, they must be passed inside a list; > - width, height - Dimensions of the output image; > - x_labels - A list of names for each of the vertical lines; > - y_labels - A list of names for each of the horizontal spaces; > - colors - List containing the colors expected for each of the horizontal spaces > - > - - Example of use > - > - pieces = [ (0.5,5.5) , [(0,4),(6,8)] , (5.5,7) , (7,8)] > - x_labels = [ 'teste01', 'teste02', 'teste03', 'teste04'] > - y_labels = [ '0001', '0002', '0003', '0004', '0005', '0006', '0007', '0008', '0009', '0010' ] > - colors = [ (1.0, 0.0, 0.0), (1.0, 0.7, 0.0), (1.0, 1.0, 0.0), (0.0, 1.0, 0.0) ] > - CairoPlot.gantt_chart('gantt_teste', pieces, 600, 300, x_labels, y_labels, colors) > - ''' > - > - plot = GanttChart(name, pieces, width, height, x_labels, y_labels, colors) > - plot.render() > - plot.commit() > - > -def vertical_bar_plot(name, > - data, > - width, > - height, > - background = "white light_gray", > - border = 0, > - display_values = False, > - grid = False, > - rounded_corners = False, > - stack = False, > - three_dimension = False, > - series_labels = None, > - x_labels = None, > - y_labels = None, > - x_bounds = None, > - y_bounds = None, > - colors = None): > - #TODO: Fix docstring for vertical_bar_plot > - ''' > - - Function to generate vertical Bar Plot Charts. > - > - bar_plot(name, data, width, height, background, border, grid, rounded_corners, three_dimension, > - x_labels, y_labels, x_bounds, y_bounds, colors): > - > - - Parameters > - > - name - Name of the desired output file, no need to input the .svg as it will be added at runtime; > - data - The list, list of lists or dictionary holding the data to be plotted; > - width, height - Dimensions of the output image; > - background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. > - If left None, a gray to white gradient will be generated; > - border - Distance in pixels of a square border into which the graphics will be drawn; > - grid - Whether or not the gris is to be drawn; > - rounded_corners - Whether or not the bars should have rounded corners; > - three_dimension - Whether or not the bars should be drawn in pseudo 3D; > - x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis; > - x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted; > - colors - List containing the colors expected for each of the bars. > - > - - Example of use > - > - data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] > - CairoPlot.vertical_bar_plot ('bar2', data, 400, 300, border = 20, grid = True, rounded_corners = False) > - ''' > - > - plot = VerticalBarPlot(name, data, width, height, background, border, > - display_values, grid, rounded_corners, stack, three_dimension, > - series_labels, x_labels, y_labels, x_bounds, y_bounds, colors) > - plot.render() > - plot.commit() > - > -def horizontal_bar_plot(name, > - data, > - width, > - height, > - background = "white light_gray", > - border = 0, > - display_values = False, > - grid = False, > - rounded_corners = False, > - stack = False, > - three_dimension = False, > - series_labels = None, > - x_labels = None, > - y_labels = None, > - x_bounds = None, > - y_bounds = None, > - colors = None): > - > - #TODO: Fix docstring for horizontal_bar_plot > - ''' > - - Function to generate Horizontal Bar Plot Charts. > - > - bar_plot(name, data, width, height, background, border, grid, rounded_corners, three_dimension, > - x_labels, y_labels, x_bounds, y_bounds, colors): > - > - - Parameters > - > - name - Name of the desired output file, no need to input the .svg as it will be added at runtime; > - data - The list, list of lists or dictionary holding the data to be plotted; > - width, height - Dimensions of the output image; > - background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. > - If left None, a gray to white gradient will be generated; > - border - Distance in pixels of a square border into which the graphics will be drawn; > - grid - Whether or not the gris is to be drawn; > - rounded_corners - Whether or not the bars should have rounded corners; > - three_dimension - Whether or not the bars should be drawn in pseudo 3D; > - x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis; > - x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted; > - colors - List containing the colors expected for each of the bars. > - > - - Example of use > - > - data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] > - CairoPlot.bar_plot ('bar2', data, 400, 300, border = 20, grid = True, rounded_corners = False) > - ''' > - > - plot = HorizontalBarPlot(name, data, width, height, background, border, > - display_values, grid, rounded_corners, stack, three_dimension, > - series_labels, x_labels, y_labels, x_bounds, y_bounds, colors) > - plot.render() > - plot.commit() > - > -def stream_chart(name, > - data, > - width, > - height, > - background = "white light_gray", > - border = 0, > - grid = False, > - series_legend = None, > - x_labels = None, > - x_bounds = None, > - y_bounds = None, > - colors = None): > - > - #TODO: Fix docstring for horizontal_bar_plot > - plot = StreamChart(name, data, width, height, background, border, > - grid, series_legend, x_labels, x_bounds, y_bounds, colors) > - plot.render() > - plot.commit() > - > - > -if __name__ == "__main__": > - import tests > - import seriestests > diff --git a/bindings/python/examples/output_format_modules/pprint_table.py b/bindings/python/examples/output_format_modules/pprint_table.py > deleted file mode 100644 > index a7e8255..0000000 > --- a/bindings/python/examples/output_format_modules/pprint_table.py > +++ /dev/null > @@ -1,37 +0,0 @@ > -# pprint_table.py > -# > -# This module is used to pretty-print a table > -# Adapted from > -# http://ginstrom.com/scribbles/2007/09/04/pretty-printing-a-table-in-python/ > - > -import sys > - > -def get_max_width(table, index): > - """Get the maximum width of the given column index""" > - > - return max([len(str(row[index])) for row in table]) > - > - > -def pprint_table(table, nbLeft=1, out=sys.stdout): > - """ > - Prints out a table of data, padded for alignment > - @param table: The table to print. A list of lists. > - Each row must have the same number of columns. > - @param nbLeft: The number of columns aligned left > - @param out: Output stream (file-like object) > - """ > - > - col_paddings = [] > - > - for i in range(len(table[0])): > - col_paddings.append(get_max_width(table, i)) > - > - for row in table: > - # left cols > - for i in range(nbLeft): > - print >> out, str(row[i]).ljust(col_paddings[i] + 1), > - # rest of the cols > - for i in range(nbLeft, len(row)): > - col = str(row[i]).rjust(col_paddings[i] + 2) > - print >> out, col, > - print >> out > diff --git a/bindings/python/examples/output_format_modules/series.py b/bindings/python/examples/output_format_modules/series.py > deleted file mode 100644 > index 8e8b236..0000000 > --- a/bindings/python/examples/output_format_modules/series.py > +++ /dev/null > @@ -1,1140 +0,0 @@ > -#!/usr/bin/env python > -# -*- coding: utf-8 -*- > - > -# Serie.py > -# > -# Copyright (c) 2008 Magnun Leno da Silva > -# > -# Author: Magnun Leno da Silva > -# > -# This program is free software; you can redistribute it and/or > -# modify it under the terms of the GNU Lesser General Public License > -# as published by the Free Software Foundation; either version 2 of > -# the License, or (at your option) any later version. > -# > -# This program is distributed in the hope that it will be useful, > -# but WITHOUT ANY WARRANTY; without even the implied warranty of > -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > -# GNU General Public License for more details. > -# > -# You should have received a copy of the GNU Lesser General Public > -# License along with this program; if not, write to the Free Software > -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 > -# USA > - > -# Contributor: Rodrigo Moreiro Araujo > - > -#import cairoplot > -import doctest > - > -NUMTYPES = (int, float, long) > -LISTTYPES = (list, tuple) > -STRTYPES = (str, unicode) > -FILLING_TYPES = ['linear', 'solid', 'gradient'] > -DEFAULT_COLOR_FILLING = 'solid' > -#TODO: Define default color list > -DEFAULT_COLOR_LIST = None > - > -class Data(object): > - ''' > - Class that models the main data structure. > - It can hold: > - - a number type (int, float or long) > - - a tuple, witch represents a point and can have 2 or 3 items (x,y,z) > - - if a list is passed it will be converted to a tuple. > - > - obs: In case a tuple is passed it will convert to tuple > - ''' > - def __init__(self, data=None, name=None, parent=None): > - ''' > - Starts main atributes from the Data class > - @name - Name for each point; > - @content - The real data, can be an int, float, long or tuple, which > - represents a point (x,y) or (x,y,z); > - @parent - A pointer that give the data access to it's parent. > - > - Usage: > - >>> d = Data(name='empty'); print d > - empty: () > - >>> d = Data((1,1),'point a'); print d > - point a: (1, 1) > - >>> d = Data((1,2,3),'point b'); print d > - point b: (1, 2, 3) > - >>> d = Data([2,3],'point c'); print d > - point c: (2, 3) > - >>> d = Data(12, 'simple value'); print d > - simple value: 12 > - ''' > - # Initial values > - self.__content = None > - self.__name = None > - > - # Setting passed values > - self.parent = parent > - self.name = name > - self.content = data > - > - # Name property > - @apply > - def name(): > - doc = ''' > - Name is a read/write property that controls the input of name. > - - If passed an invalid value it cleans the name with None > - > - Usage: > - >>> d = Data(13); d.name = 'name_test'; print d > - name_test: 13 > - >>> d.name = 11; print d > - 13 > - >>> d.name = 'other_name'; print d > - other_name: 13 > - >>> d.name = None; print d > - 13 > - >>> d.name = 'last_name'; print d > - last_name: 13 > - >>> d.name = ''; print d > - 13 > - ''' > - def fget(self): > - ''' > - returns the name as a string > - ''' > - return self.__name > - > - def fset(self, name): > - ''' > - Sets the name of the Data > - ''' > - if type(name) in STRTYPES and len(name) > 0: > - self.__name = name > - else: > - self.__name = None > - > - > - > - return property(**locals()) > - > - # Content property > - @apply > - def content(): > - doc = ''' > - Content is a read/write property that validate the data passed > - and return it. > - > - Usage: > - >>> d = Data(); d.content = 13; d.content > - 13 > - >>> d = Data(); d.content = (1,2); d.content > - (1, 2) > - >>> d = Data(); d.content = (1,2,3); d.content > - (1, 2, 3) > - >>> d = Data(); d.content = [1,2,3]; d.content > - (1, 2, 3) > - >>> d = Data(); d.content = [1.5,.2,3.3]; d.content > - (1.5, 0.20000000000000001, 3.2999999999999998) > - ''' > - def fget(self): > - ''' > - Return the content of Data > - ''' > - return self.__content > - > - def fset(self, data): > - ''' > - Ensures that data is a valid tuple/list or a number (int, float > - or long) > - ''' > - # Type: None > - if data is None: > - self.__content = None > - return > - > - # Type: Int or Float > - elif type(data) in NUMTYPES: > - self.__content = data > - > - # Type: List or Tuple > - elif type(data) in LISTTYPES: > - # Ensures the correct size > - if len(data) not in (2, 3): > - raise TypeError, "Data (as list/tuple) must have 2 or 3 items" > - return > - > - # Ensures that all items in list/tuple is a number > - isnum = lambda x : type(x) not in NUMTYPES > - > - if max(map(isnum, data)): > - # An item in data isn't an int or a float > - raise TypeError, "All content of data must be a number (int or float)" > - > - # Convert the tuple to list > - if type(data) is list: > - data = tuple(data) > - > - # Append a copy and sets the type > - self.__content = data[:] > - > - # Unknown type! > - else: > - self.__content = None > - raise TypeError, "Data must be an int, float or a tuple with two or three items" > - return > - > - return property(**locals()) > - > - > - def clear(self): > - ''' > - Clear the all Data (content, name and parent) > - ''' > - self.content = None > - self.name = None > - self.parent = None > - > - def copy(self): > - ''' > - Returns a copy of the Data structure > - ''' > - # The copy > - new_data = Data() > - if self.content is not None: > - # If content is a point > - if type(self.content) is tuple: > - new_data.__content = self.content[:] > - > - # If content is a number > - else: > - new_data.__content = self.content > - > - # If it has a name > - if self.name is not None: > - new_data.__name = self.name > - > - return new_data > - > - def __str__(self): > - ''' > - Return a string representation of the Data structure > - ''' > - if self.name is None: > - if self.content is None: > - return '' > - return str(self.content) > - else: > - if self.content is None: > - return self.name+": ()" > - return self.name+": "+str(self.content) > - > - def __len__(self): > - ''' > - Return the length of the Data. > - - If it's a number return 1; > - - If it's a list return it's length; > - - If its None return 0. > - ''' > - if self.content is None: > - return 0 > - elif type(self.content) in NUMTYPES: > - return 1 > - return len(self.content) > - > - > - > - > -class Group(object): > - ''' > - Class that models a group of data. Every value (int, float, long, tuple > - or list) passed is converted to a list of Data. > - It can receive: > - - A single number (int, float, long); > - - A list of numbers; > - - A tuple of numbers; > - - An instance of Data; > - - A list of Data; > - > - Obs: If a tuple with 2 or 3 items is passed it is converted to a point. > - If a tuple with only 1 item is passed it's converted to a number; > - If a tuple with more than 2 items is passed it's converted to a > - list of numbers > - ''' > - def __init__(self, group=None, name=None, parent=None): > - ''' > - Starts main atributes in Group instance. > - @data_list - a list of data which forms the group; > - @range - a range that represent the x axis of possible functions; > - @name - name of the data group; > - @parent - the Serie parent of this group. > - > - Usage: > - >>> g = Group(13, 'simple number'); print g > - simple number ['13'] > - >>> g = Group((1,2), 'simple point'); print g > - simple point ['(1, 2)'] > - >>> g = Group([1,2,3,4], 'list of numbers'); print g > - list of numbers ['1', '2', '3', '4'] > - >>> g = Group((1,2,3,4),'int in tuple'); print g > - int in tuple ['1', '2', '3', '4'] > - >>> g = Group([(1,2),(2,3),(3,4)], 'list of points'); print g > - list of points ['(1, 2)', '(2, 3)', '(3, 4)'] > - >>> g = Group([[1,2,3],[1,2,3]], '2D coordinate lists'); print g > - 2D coordinated lists ['(1, 1)', '(2, 2)', '(3, 3)'] > - >>> g = Group([[1,2],[1,2],[1,2]], '3D coordinate lists'); print g > - 3D coordinated lists ['(1, 1, 1)', '(2, 2, 2)'] > - ''' > - # Initial values > - self.__data_list = [] > - self.__range = [] > - self.__name = None > - > - > - self.parent = parent > - self.name = name > - self.data_list = group > - > - # Name property > - @apply > - def name(): > - doc = ''' > - Name is a read/write property that controls the input of name. > - - If passed an invalid value it cleans the name with None > - > - Usage: > - >>> g = Group(13); g.name = 'name_test'; print g > - name_test ['13'] > - >>> g.name = 11; print g > - ['13'] > - >>> g.name = 'other_name'; print g > - other_name ['13'] > - >>> g.name = None; print g > - ['13'] > - >>> g.name = 'last_name'; print g > - last_name ['13'] > - >>> g.name = ''; print g > - ['13'] > - ''' > - def fget(self): > - ''' > - Returns the name as a string > - ''' > - return self.__name > - > - def fset(self, name): > - ''' > - Sets the name of the Group > - ''' > - if type(name) in STRTYPES and len(name) > 0: > - self.__name = name > - else: > - self.__name = None > - > - return property(**locals()) > - > - # data_list property > - @apply > - def data_list(): > - doc = ''' > - The data_list is a read/write property that can be a list of > - numbers, a list of points or a list of 2 or 3 coordinate lists. This > - property uses mainly the self.add_data method. > - > - Usage: > - >>> g = Group(); g.data_list = 13; print g > - ['13'] > - >>> g.data_list = (1,2); print g > - ['(1, 2)'] > - >>> g.data_list = Data((1,2),'point a'); print g > - ['point a: (1, 2)'] > - >>> g.data_list = [1,2,3]; print g > - ['1', '2', '3'] > - >>> g.data_list = (1,2,3,4); print g > - ['1', '2', '3', '4'] > - >>> g.data_list = [(1,2),(2,3),(3,4)]; print g > - ['(1, 2)', '(2, 3)', '(3, 4)'] > - >>> g.data_list = [[1,2],[1,2]]; print g > - ['(1, 1)', '(2, 2)'] > - >>> g.data_list = [[1,2],[1,2],[1,2]]; print g > - ['(1, 1, 1)', '(2, 2, 2)'] > - >>> g.range = (10); g.data_list = lambda x:x**2; print g > - ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 4.0)', '(3.0, 9.0)', '(4.0, 16.0)', '(5.0, 25.0)', '(6.0, 36.0)', '(7.0, 49.0)', '(8.0, 64.0)', '(9.0, 81.0)'] > - ''' > - def fget(self): > - ''' > - Returns the value of data_list > - ''' > - return self.__data_list > - > - def fset(self, group): > - ''' > - Ensures that group is valid. > - ''' > - # None > - if group is None: > - self.__data_list = [] > - > - # Int/float/long or Instance of Data > - elif type(group) in NUMTYPES or isinstance(group, Data): > - # Clean data_list > - self.__data_list = [] > - self.add_data(group) > - > - # One point > - elif type(group) is tuple and len(group) in (2,3): > - self.__data_list = [] > - self.add_data(group) > - > - # list of items > - elif type(group) in LISTTYPES and type(group[0]) is not list: > - # Clean data_list > - self.__data_list = [] > - for item in group: > - # try to append and catch an exception > - self.add_data(item) > - > - # function lambda > - elif callable(group): > - # Explicit is better than implicit > - function = group > - # Has range > - if len(self.range) is not 0: > - # Clean data_list > - self.__data_list = [] > - # Generate values for the lambda function > - for x in self.range: > - #self.add_data((x,round(group(x),2))) > - self.add_data((x,function(x))) > - > - # Only have range in parent > - elif self.parent is not None and len(self.parent.range) is not 0: > - # Copy parent range > - self.__range = self.parent.range[:] > - # Clean data_list > - self.__data_list = [] > - # Generate values for the lambda function > - for x in self.range: > - #self.add_data((x,round(group(x),2))) > - self.add_data((x,function(x))) > - > - # Don't have range anywhere > - else: > - # x_data don't exist > - raise Exception, "Data argument is valid but to use function type please set x_range first" > - > - # Coordinate Lists > - elif type(group) in LISTTYPES and type(group[0]) is list: > - # Clean data_list > - self.__data_list = [] > - data = [] > - if len(group) == 3: > - data = zip(group[0], group[1], group[2]) > - elif len(group) == 2: > - data = zip(group[0], group[1]) > - else: > - raise TypeError, "Only one list of coordinates was received." > - > - for item in data: > - self.add_data(item) > - > - else: > - raise TypeError, "Group type not supported" > - > - return property(**locals()) > - > - @apply > - def range(): > - doc = ''' > - The range is a read/write property that generates a range of values > - for the x axis of the functions. When passed a tuple it almost works > - like the built-in range funtion: > - - 1 item, represent the end of the range started from 0; > - - 2 items, represents the start and the end, respectively; > - - 3 items, the last one represents the step; > - > - When passed a list the range function understands as a valid range. > - > - Usage: > - >>> g = Group(); g.range = 10; print g.range > - [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] > - >>> g = Group(); g.range = (5); print g.range > - [0.0, 1.0, 2.0, 3.0, 4.0] > - >>> g = Group(); g.range = (1,7); print g.range > - [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] > - >>> g = Group(); g.range = (0,10,2); print g.range > - [0.0, 2.0, 4.0, 6.0, 8.0] > - >>> > - >>> g = Group(); g.range = [0]; print g.range > - [0.0] > - >>> g = Group(); g.range = [0,10,20]; print g.range > - [0.0, 10.0, 20.0] > - ''' > - def fget(self): > - ''' > - Returns the range > - ''' > - return self.__range > - > - def fset(self, x_range): > - ''' > - Controls the input of a valid type and generate the range > - ''' > - # if passed a simple number convert to tuple > - if type(x_range) in NUMTYPES: > - x_range = (x_range,) > - > - # A list, just convert to float > - if type(x_range) is list and len(x_range) > 0: > - # Convert all to float > - x_range = map(float, x_range) > - # Prevents repeated values and convert back to list > - self.__range = list(set(x_range[:])) > - # Sort the list to ascending order > - self.__range.sort() > - > - # A tuple, must check the lengths and generate the values > - elif type(x_range) is tuple and len(x_range) in (1,2,3): > - # Convert all to float > - x_range = map(float, x_range) > - > - # Inital values > - start = 0.0 > - step = 1.0 > - end = 0.0 > - > - # Only the end and it can't be less or iqual to 0 > - if len(x_range) is 1 and x_range > 0: > - end = x_range[0] > - > - # The start and the end but the start must be less then the end > - elif len(x_range) is 2 and x_range[0] < x_range[1]: > - start = x_range[0] > - end = x_range[1] > - > - # All 3, but the start must be less then the end > - elif x_range[0] <= x_range[1]: > - start = x_range[0] > - end = x_range[1] > - step = x_range[2] > - > - # Starts the range > - self.__range = [] > - # Generate the range > - # Can't use the range function because it doesn't support float values > - while start < end: > - self.__range.append(start) > - start += step > - > - # Incorrect type > - else: > - raise Exception, "x_range must be a list with one or more items or a tuple with 2 or 3 items" > - > - return property(**locals()) > - > - def add_data(self, data, name=None): > - ''' > - Append a new data to the data_list. > - - If data is an instance of Data, append it > - - If it's an int, float, tuple or list create an instance of Data and append it > - > - Usage: > - >>> g = Group() > - >>> g.add_data(12); print g > - ['12'] > - >>> g.add_data(7,'other'); print g > - ['12', 'other: 7'] > - >>> > - >>> g = Group() > - >>> g.add_data((1,1),'a'); print g > - ['a: (1, 1)'] > - >>> g.add_data((2,2),'b'); print g > - ['a: (1, 1)', 'b: (2, 2)'] > - >>> > - >>> g.add_data(Data((1,2),'c')); print g > - ['a: (1, 1)', 'b: (2, 2)', 'c: (1, 2)'] > - ''' > - if not isinstance(data, Data): > - # Try to convert > - data = Data(data,name,self) > - > - if data.content is not None: > - self.__data_list.append(data.copy()) > - self.__data_list[-1].parent = self > - > - > - def to_list(self): > - ''' > - Returns the group as a list of numbers (int, float or long) or a > - list of tuples (points 2D or 3D). > - > - Usage: > - >>> g = Group([1,2,3,4],'g1'); g.to_list() > - [1, 2, 3, 4] > - >>> g = Group([(1,2),(2,3),(3,4)],'g2'); g.to_list() > - [(1, 2), (2, 3), (3, 4)] > - >>> g = Group([(1,2,3),(3,4,5)],'g2'); g.to_list() > - [(1, 2, 3), (3, 4, 5)] > - ''' > - return [data.content for data in self] > - > - def copy(self): > - ''' > - Returns a copy of this group > - ''' > - new_group = Group() > - new_group.__name = self.__name > - if self.__range is not None: > - new_group.__range = self.__range[:] > - for data in self: > - new_group.add_data(data.copy()) > - return new_group > - > - def get_names(self): > - ''' > - Return a list with the names of all data in this group > - ''' > - names = [] > - for data in self: > - if data.name is None: > - names.append('Data '+str(data.index()+1)) > - else: > - names.append(data.name) > - return names > - > - > - def __str__ (self): > - ''' > - Returns a string representing the Group > - ''' > - ret = "" > - if self.name is not None: > - ret += self.name + " " > - if len(self) > 0: > - list_str = [str(item) for item in self] > - ret += str(list_str) > - else: > - ret += "[]" > - return ret > - > - def __getitem__(self, key): > - ''' > - Makes a Group iterable, based in the data_list property > - ''' > - return self.data_list[key] > - > - def __len__(self): > - ''' > - Returns the length of the Group, based in the data_list property > - ''' > - return len(self.data_list) > - > - > -class Colors(object): > - ''' > - Class that models the colors its labels (names) and its properties, RGB > - and filling type. > - > - It can receive: > - - A list where each item is a list with 3 or 4 items. The > - first 3 items represent the RGB values and the last argument > - defines the filling type. The list will be converted to a dict > - and each color will receve a name based in its position in the > - list. > - - A dictionary where each key will be the color name and its item > - can be a list with 3 or 4 items. The first 3 items represent > - the RGB colors and the last argument defines the filling type. > - ''' > - def __init__(self, color_list=None): > - ''' > - Start the color_list property > - @ color_list - the list or dict contaning the colors properties. > - ''' > - self.__color_list = None > - > - self.color_list = color_list > - > - @apply > - def color_list(): > - doc = ''' > - >>> c = Colors([[1,1,1],[2,2,2,'linear'],[3,3,3,'gradient']]) > - >>> print c.color_list > - {'Color 2': [2, 2, 2, 'linear'], 'Color 3': [3, 3, 3, 'gradient'], 'Color 1': [1, 1, 1, 'solid']} > - >>> c.color_list = [[1,1,1],(2,2,2,'solid'),(3,3,3,'linear')] > - >>> print c.color_list > - {'Color 2': [2, 2, 2, 'solid'], 'Color 3': [3, 3, 3, 'linear'], 'Color 1': [1, 1, 1, 'solid']} > - >>> c.color_list = {'a':[1,1,1],'b':(2,2,2,'solid'),'c':(3,3,3,'linear'), 'd':(4,4,4)} > - >>> print c.color_list > - {'a': [1, 1, 1, 'solid'], 'c': [3, 3, 3, 'linear'], 'b': [2, 2, 2, 'solid'], 'd': [4, 4, 4, 'solid']} > - ''' > - def fget(self): > - ''' > - Return the color list > - ''' > - return self.__color_list > - > - def fset(self, color_list): > - ''' > - Format the color list to a dictionary > - ''' > - if color_list is None: > - self.__color_list = None > - return > - > - if type(color_list) in LISTTYPES and type(color_list[0]) in LISTTYPES: > - old_color_list = color_list[:] > - color_list = {} > - for index, color in enumerate(old_color_list): > - if len(color) is 3 and max(map(type, color)) in NUMTYPES: > - color_list['Color '+str(index+1)] = list(color)+[DEFAULT_COLOR_FILLING] > - elif len(color) is 4 and max(map(type, color[:-1])) in NUMTYPES and color[-1] in FILLING_TYPES: > - color_list['Color '+str(index+1)] = list(color) > - else: > - raise TypeError, "Unsuported color format" > - elif type(color_list) is not dict: > - raise TypeError, "Unsuported color format" > - > - for name, color in color_list.items(): > - if len(color) is 3: > - if max(map(type, color)) in NUMTYPES: > - color_list[name] = list(color)+[DEFAULT_COLOR_FILLING] > - else: > - raise TypeError, "Unsuported color format" > - elif len(color) is 4: > - if max(map(type, color[:-1])) in NUMTYPES and color[-1] in FILLING_TYPES: > - color_list[name] = list(color) > - else: > - raise TypeError, "Unsuported color format" > - self.__color_list = color_list.copy() > - > - return property(**locals()) > - > - > -class Series(object): > - ''' > - Class that models a Series (group of groups). Every value (int, float, > - long, tuple or list) passed is converted to a list of Group or Data. > - It can receive: > - - a single number or point, will be converted to a Group of one Data; > - - a list of numbers, will be converted to a group of numbers; > - - a list of tuples, will converted to a single Group of points; > - - a list of lists of numbers, each 'sublist' will be converted to a > - group of numbers; > - - a list of lists of tuples, each 'sublist' will be converted to a > - group of points; > - - a list of lists of lists, the content of the 'sublist' will be > - processed as coordinated lists and the result will be converted to > - a group of points; > - - a Dictionary where each item can be the same of the list: number, > - point, list of numbers, list of points or list of lists (coordinated > - lists); > - - an instance of Data; > - - an instance of group. > - ''' > - def __init__(self, series=None, name=None, property=[], colors=None): > - ''' > - Starts main atributes in Group instance. > - @series - a list, dict of data of which the series is composed; > - @name - name of the series; > - @property - a list/dict of properties to be used in the plots of > - this Series > - > - Usage: > - >>> print Series([1,2,3,4]) > - ["Group 1 ['1', '2', '3', '4']"] > - >>> print Series([[1,2,3],[4,5,6]]) > - ["Group 1 ['1', '2', '3']", "Group 2 ['4', '5', '6']"] > - >>> print Series((1,2)) > - ["Group 1 ['(1, 2)']"] > - >>> print Series([(1,2),(2,3)]) > - ["Group 1 ['(1, 2)', '(2, 3)']"] > - >>> print Series([[(1,2),(2,3)],[(4,5),(5,6)]]) > - ["Group 1 ['(1, 2)', '(2, 3)']", "Group 2 ['(4, 5)', '(5, 6)']"] > - >>> print Series([[[1,2,3],[1,2,3],[1,2,3]]]) > - ["Group 1 ['(1, 1, 1)', '(2, 2, 2)', '(3, 3, 3)']"] > - >>> print Series({'g1':[1,2,3], 'g2':[4,5,6]}) > - ["g1 ['1', '2', '3']", "g2 ['4', '5', '6']"] > - >>> print Series({'g1':[(1,2),(2,3)], 'g2':[(4,5),(5,6)]}) > - ["g1 ['(1, 2)', '(2, 3)']", "g2 ['(4, 5)', '(5, 6)']"] > - >>> print Series({'g1':[[1,2],[1,2]], 'g2':[[4,5],[4,5]]}) > - ["g1 ['(1, 1)', '(2, 2)']", "g2 ['(4, 4)', '(5, 5)']"] > - >>> print Series(Data(1,'d1')) > - ["Group 1 ['d1: 1']"] > - >>> print Series(Group([(1,2),(2,3)],'g1')) > - ["g1 ['(1, 2)', '(2, 3)']"] > - ''' > - # Intial values > - self.__group_list = [] > - self.__name = None > - self.__range = None > - > - # TODO: Implement colors with filling > - self.__colors = None > - > - self.name = name > - self.group_list = series > - self.colors = colors > - > - # Name property > - @apply > - def name(): > - doc = ''' > - Name is a read/write property that controls the input of name. > - - If passed an invalid value it cleans the name with None > - > - Usage: > - >>> s = Series(13); s.name = 'name_test'; print s > - name_test ["Group 1 ['13']"] > - >>> s.name = 11; print s > - ["Group 1 ['13']"] > - >>> s.name = 'other_name'; print s > - other_name ["Group 1 ['13']"] > - >>> s.name = None; print s > - ["Group 1 ['13']"] > - >>> s.name = 'last_name'; print s > - last_name ["Group 1 ['13']"] > - >>> s.name = ''; print s > - ["Group 1 ['13']"] > - ''' > - def fget(self): > - ''' > - Returns the name as a string > - ''' > - return self.__name > - > - def fset(self, name): > - ''' > - Sets the name of the Group > - ''' > - if type(name) in STRTYPES and len(name) > 0: > - self.__name = name > - else: > - self.__name = None > - > - return property(**locals()) > - > - > - > - # Colors property > - @apply > - def colors(): > - doc = ''' > - >>> s = Series() > - >>> s.colors = [[1,1,1],[2,2,2,'linear'],[3,3,3,'gradient']] > - >>> print s.colors > - {'Color 2': [2, 2, 2, 'linear'], 'Color 3': [3, 3, 3, 'gradient'], 'Color 1': [1, 1, 1, 'solid']} > - >>> s.colors = [[1,1,1],(2,2,2,'solid'),(3,3,3,'linear')] > - >>> print s.colors > - {'Color 2': [2, 2, 2, 'solid'], 'Color 3': [3, 3, 3, 'linear'], 'Color 1': [1, 1, 1, 'solid']} > - >>> s.colors = {'a':[1,1,1],'b':(2,2,2,'solid'),'c':(3,3,3,'linear'), 'd':(4,4,4)} > - >>> print s.colors > - {'a': [1, 1, 1, 'solid'], 'c': [3, 3, 3, 'linear'], 'b': [2, 2, 2, 'solid'], 'd': [4, 4, 4, 'solid']} > - ''' > - def fget(self): > - ''' > - Return the color list > - ''' > - return self.__colors.color_list > - > - def fset(self, colors): > - ''' > - Format the color list to a dictionary > - ''' > - self.__colors = Colors(colors) > - > - return property(**locals()) > - > - @apply > - def range(): > - doc = ''' > - The range is a read/write property that generates a range of values > - for the x axis of the functions. When passed a tuple it almost works > - like the built-in range funtion: > - - 1 item, represent the end of the range started from 0; > - - 2 items, represents the start and the end, respectively; > - - 3 items, the last one represents the step; > - > - When passed a list the range function understands as a valid range. > - > - Usage: > - >>> s = Series(); s.range = 10; print s.range > - [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] > - >>> s = Series(); s.range = (5); print s.range > - [0.0, 1.0, 2.0, 3.0, 4.0, 5.0] > - >>> s = Series(); s.range = (1,7); print s.range > - [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] > - >>> s = Series(); s.range = (0,10,2); print s.range > - [0.0, 2.0, 4.0, 6.0, 8.0, 10.0] > - >>> > - >>> s = Series(); s.range = [0]; print s.range > - [0.0] > - >>> s = Series(); s.range = [0,10,20]; print s.range > - [0.0, 10.0, 20.0] > - ''' > - def fget(self): > - ''' > - Returns the range > - ''' > - return self.__range > - > - def fset(self, x_range): > - ''' > - Controls the input of a valid type and generate the range > - ''' > - # if passed a simple number convert to tuple > - if type(x_range) in NUMTYPES: > - x_range = (x_range,) > - > - # A list, just convert to float > - if type(x_range) is list and len(x_range) > 0: > - # Convert all to float > - x_range = map(float, x_range) > - # Prevents repeated values and convert back to list > - self.__range = list(set(x_range[:])) > - # Sort the list to ascending order > - self.__range.sort() > - > - # A tuple, must check the lengths and generate the values > - elif type(x_range) is tuple and len(x_range) in (1,2,3): > - # Convert all to float > - x_range = map(float, x_range) > - > - # Inital values > - start = 0.0 > - step = 1.0 > - end = 0.0 > - > - # Only the end and it can't be less or iqual to 0 > - if len(x_range) is 1 and x_range > 0: > - end = x_range[0] > - > - # The start and the end but the start must be lesser then the end > - elif len(x_range) is 2 and x_range[0] < x_range[1]: > - start = x_range[0] > - end = x_range[1] > - > - # All 3, but the start must be lesser then the end > - elif x_range[0] < x_range[1]: > - start = x_range[0] > - end = x_range[1] > - step = x_range[2] > - > - # Starts the range > - self.__range = [] > - # Generate the range > - # Cnat use the range function becouse it don't suport float values > - while start <= end: > - self.__range.append(start) > - start += step > - > - # Incorrect type > - else: > - raise Exception, "x_range must be a list with one or more item or a tuple with 2 or 3 items" > - > - return property(**locals()) > - > - @apply > - def group_list(): > - doc = ''' > - The group_list is a read/write property used to pre-process the list > - of Groups. > - It can be: > - - a single number, point or lambda, will be converted to a single > - Group of one Data; > - - a list of numbers, will be converted to a group of numbers; > - - a list of tuples, will converted to a single Group of points; > - - a list of lists of numbers, each 'sublist' will be converted to > - a group of numbers; > - - a list of lists of tuples, each 'sublist' will be converted to a > - group of points; > - - a list of lists of lists, the content of the 'sublist' will be > - processed as coordinated lists and the result will be converted > - to a group of points; > - - a list of lambdas, each lambda represents a Group; > - - a Dictionary where each item can be the same of the list: number, > - point, list of numbers, list of points, list of lists > - (coordinated lists) or lambdas > - - an instance of Data; > - - an instance of group. > - > - Usage: > - >>> s = Series() > - >>> s.group_list = [1,2,3,4]; print s > - ["Group 1 ['1', '2', '3', '4']"] > - >>> s.group_list = [[1,2,3],[4,5,6]]; print s > - ["Group 1 ['1', '2', '3']", "Group 2 ['4', '5', '6']"] > - >>> s.group_list = (1,2); print s > - ["Group 1 ['(1, 2)']"] > - >>> s.group_list = [(1,2),(2,3)]; print s > - ["Group 1 ['(1, 2)', '(2, 3)']"] > - >>> s.group_list = [[(1,2),(2,3)],[(4,5),(5,6)]]; print s > - ["Group 1 ['(1, 2)', '(2, 3)']", "Group 2 ['(4, 5)', '(5, 6)']"] > - >>> s.group_list = [[[1,2,3],[1,2,3],[1,2,3]]]; print s > - ["Group 1 ['(1, 1, 1)', '(2, 2, 2)', '(3, 3, 3)']"] > - >>> s.group_list = [(0.5,5.5) , [(0,4),(6,8)] , (5.5,7) , (7,9)]; print s > - ["Group 1 ['(0.5, 5.5)']", "Group 2 ['(0, 4)', '(6, 8)']", "Group 3 ['(5.5, 7)']", "Group 4 ['(7, 9)']"] > - >>> s.group_list = {'g1':[1,2,3], 'g2':[4,5,6]}; print s > - ["g1 ['1', '2', '3']", "g2 ['4', '5', '6']"] > - >>> s.group_list = {'g1':[(1,2),(2,3)], 'g2':[(4,5),(5,6)]}; print s > - ["g1 ['(1, 2)', '(2, 3)']", "g2 ['(4, 5)', '(5, 6)']"] > - >>> s.group_list = {'g1':[[1,2],[1,2]], 'g2':[[4,5],[4,5]]}; print s > - ["g1 ['(1, 1)', '(2, 2)']", "g2 ['(4, 4)', '(5, 5)']"] > - >>> s.range = 10 > - >>> s.group_list = lambda x:x*2 > - >>> s.group_list = [lambda x:x*2, lambda x:x**2, lambda x:x**3]; print s > - ["Group 1 ['(0.0, 0.0)', '(1.0, 2.0)', '(2.0, 4.0)', '(3.0, 6.0)', '(4.0, 8.0)', '(5.0, 10.0)', '(6.0, 12.0)', '(7.0, 14.0)', '(8.0, 16.0)', '(9.0, 18.0)', '(10.0, 20.0)']", "Group 2 ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 4.0)', '(3.0, 9.0)', '(4.0, 16.0)', '(5.0, 25.0)', '(6.0, 36.0)', '(7.0, 49.0)', '(8.0, 64.0)', '(9.0, 81.0)', '(10.0, 100.0)']", "Group 3 ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 8.0)', '(3.0, 27.0)', '(4.0, 64.0)', '(5.0, 125.0)', '(6.0, 216.0)', '(7.0, 343.0)', '(8.0, 512.0)', '(9.0, 729.0)', '(10.0, 1000.0)']"] > - >>> s.group_list = {'linear':lambda x:x*2, 'square':lambda x:x**2, 'cubic':lambda x:x**3}; print s > - ["cubic ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 8.0)', '(3.0, 27.0)', '(4.0, 64.0)', '(5.0, 125.0)', '(6.0, 216.0)', '(7.0, 343.0)', '(8.0, 512.0)', '(9.0, 729.0)', '(10.0, 1000.0)']", "linear ['(0.0, 0.0)', '(1.0, 2.0)', '(2.0, 4.0)', '(3.0, 6.0)', '(4.0, 8.0)', '(5.0, 10.0)', '(6.0, 12.0)', '(7.0, 14.0)', '(8.0, 16.0)', '(9.0, 18.0)', '(10.0, 20.0)']", "square ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 4.0)', '(3.0, 9.0)', '(4.0, 16.0)', '(5.0, 25.0)', '(6.0, 36.0)', '(7.0, 49.0)', '(8.0, 64.0)', '(9.0, 81.0)', '(10.0, 100.0)']"] > - >>> s.group_list = Data(1,'d1'); print s > - ["Group 1 ['d1: 1']"] > - >>> s.group_list = Group([(1,2),(2,3)],'g1'); print s > - ["g1 ['(1, 2)', '(2, 3)']"] > - ''' > - def fget(self): > - ''' > - Return the group list. > - ''' > - return self.__group_list > - > - def fset(self, series): > - ''' > - Controls the input of a valid group list. > - ''' > - #TODO: Add support to the following strem of data: [ (0.5,5.5) , [(0,4),(6,8)] , (5.5,7) , (7,9)] > - > - # Type: None > - if series is None: > - self.__group_list = [] > - > - # List or Tuple > - elif type(series) in LISTTYPES: > - self.__group_list = [] > - > - is_function = lambda x: callable(x) > - # Groups > - if list in map(type, series) or max(map(is_function, series)): > - for group in series: > - self.add_group(group) > - > - # single group > - else: > - self.add_group(series) > - > - #old code > - ## List of numbers > - #if type(series[0]) in NUMTYPES or type(series[0]) is tuple: > - # print series > - # self.add_group(series) > - # > - ## List of anything else > - #else: > - # for group in series: > - # self.add_group(group) > - > - # Dict representing series of groups > - elif type(series) is dict: > - self.__group_list = [] > - names = series.keys() > - names.sort() > - for name in names: > - self.add_group(Group(series[name],name,self)) > - > - # A single lambda > - elif callable(series): > - self.__group_list = [] > - self.add_group(series) > - > - # Int/float, instance of Group or Data > - elif type(series) in NUMTYPES or isinstance(series, Group) or isinstance(series, Data): > - self.__group_list = [] > - self.add_group(series) > - > - # Default > - else: > - raise TypeError, "Serie type not supported" > - > - return property(**locals()) > - > - def add_group(self, group, name=None): > - ''' > - Append a new group in group_list > - ''' > - if not isinstance(group, Group): > - #Try to convert > - group = Group(group, name, self) > - > - if len(group.data_list) is not 0: > - # Auto naming groups > - if group.name is None: > - group.name = "Group "+str(len(self.__group_list)+1) > - > - self.__group_list.append(group) > - self.__group_list[-1].parent = self > - > - def copy(self): > - ''' > - Returns a copy of the Series > - ''' > - new_series = Series() > - new_series.__name = self.__name > - if self.__range is not None: > - new_series.__range = self.__range[:] > - #Add color property in the copy method > - #self.__colors = None > - > - for group in self: > - new_series.add_group(group.copy()) > - > - return new_series > - > - def get_names(self): > - ''' > - Returns a list of the names of all groups in the Serie > - ''' > - names = [] > - for group in self: > - if group.name is None: > - names.append('Group '+str(group.index()+1)) > - else: > - names.append(group.name) > - > - return names > - > - def to_list(self): > - ''' > - Returns a list with the content of all groups and data > - ''' > - big_list = [] > - for group in self: > - for data in group: > - if type(data.content) in NUMTYPES: > - big_list.append(data.content) > - else: > - big_list = big_list + list(data.content) > - return big_list > - > - def __getitem__(self, key): > - ''' > - Makes the Series iterable, based in the group_list property > - ''' > - return self.__group_list[key] > - > - def __str__(self): > - ''' > - Returns a string that represents the Series > - ''' > - ret = "" > - if self.name is not None: > - ret += self.name + " " > - if len(self) > 0: > - list_str = [str(item) for item in self] > - ret += str(list_str) > - else: > - ret += "[]" > - return ret > - > - def __len__(self): > - ''' > - Returns the length of the Series, based in the group_lsit property > - ''' > - return len(self.group_list) > - > - > -if __name__ == '__main__': > - doctest.testmod() > diff --git a/bindings/python/examples/python2/eventcount.py b/bindings/python/examples/python2/eventcount.py > new file mode 100755 > index 0000000..079633c > --- /dev/null > +++ b/bindings/python/examples/python2/eventcount.py > @@ -0,0 +1,85 @@ > +#!/usr/bin/env python2 > +# eventcount.py > +# > +# Babeltrace event count example script > +# > +# Copyright 2012 EfficiOS Inc. > +# > +# Author: Danny Serres > +# > +# Permission is hereby granted, free of charge, to any person obtaining a copy > +# of this software and associated documentation files (the "Software"), to deal > +# in the Software without restriction, including without limitation the rights > +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > +# copies of the Software, and to permit persons to whom the Software is > +# furnished to do so, subject to the following conditions: > +# > +# The above copyright notice and this permission notice shall be included in > +# all copies or substantial portions of the Software. > + > +# The script prints a count of specified events and > +# their related tid's in a given trace. > +# The trace needs TID context (lttng add-context -k -t tid) > + > +import sys > +from babeltrace import * > +from output_format_modules.pprint_table import pprint_table as pprint > + > +if len(sys.argv) < 3: > + raise TypeError("Usage: python eventcount.py event1 [event2 ...] path/to/trace") > + > +ctx = Context() > +ret = ctx.add_trace(sys.argv[len(sys.argv)-1], "ctf") > +if ret is None: > + raise IOError("Error adding trace") > + > +counts = {} > + > +# Setting iterator > +bp = IterPos(SEEK_BEGIN) > +ctf_it = ctf.Iterator(ctx, bp) > + > +# Reading events > +event = ctf_it.read_event() > +while(event is not None): > + for event_type in sys.argv[1:len(sys.argv)-1]: > + if event_type == event.get_name(): > + > + # Getting scope definition > + sco = event.get_top_level_scope(ctf.scope.STREAM_EVENT_CONTEXT) > + if sco is None: > + print("ERROR: Cannot get definition scope for {}".format( > + event.get_name())) > + continue > + > + # Getting TID > + tid_field = event.get_field(sco, "_tid") > + tid = tid_field.get_int64() > + > + if ctf.field_error(): > + print("ERROR: Missing TID info for {}".format( > + event.get_name())) > + continue > + > + tmp = (tid, event.get_name()) > + > + if tmp in counts: > + counts[tmp] += 1 > + else: > + counts[tmp] = 1 > + > + # Next event > + ret = ctf_it.next() > + if ret < 0: > + break > + event = ctf_it.read_event() > + > +del ctf_it > + > +# Appending data to table for output > +table = [] > +for item in counts: > + table.append([item[0], item[1], counts[item]]) > +table = sorted(table) > +table.insert(0,["TID", "EVENT", "COUNT"]) > +pprint(table, 2) > diff --git a/bindings/python/examples/python2/eventcountlist.py b/bindings/python/examples/python2/eventcountlist.py > new file mode 100755 > index 0000000..1b42b4e > --- /dev/null > +++ b/bindings/python/examples/python2/eventcountlist.py > @@ -0,0 +1,84 @@ > +#!/usr/bin/env python2 > +# eventcountlist.py > +# > +# Babeltrace event count list example script > +# > +# Copyright 2012 EfficiOS Inc. > +# > +# Author: Danny Serres > +# > +# Permission is hereby granted, free of charge, to any person obtaining a copy > +# of this software and associated documentation files (the "Software"), to deal > +# in the Software without restriction, including without limitation the rights > +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > +# copies of the Software, and to permit persons to whom the Software is > +# furnished to do so, subject to the following conditions: > +# > +# The above copyright notice and this permission notice shall be included in > +# all copies or substantial portions of the Software. > + > +# The script prints a count and rate of events. > +# It also outputs a bar graph of count per event, using the cairoplot module. > + > +import sys > +from babeltrace import * > +from output_format_modules import cairoplot > +from output_format_modules.pprint_table import pprint_table as pprint > + > +# Check for path arg: > +if len(sys.argv) < 2: > + raise TypeError("Usage: python eventcountlist.py path/to/trace") > + > +ctx = Context() > +ret = ctx.add_trace(sys.argv[1], "ctf") > +if ret is None: > + raise IOError("Error adding trace") > + > +# Events and their assossiated count > +# will be stored as a dict: > +events_count = {} > + > +# Setting iterator: > +bp = IterPos(SEEK_BEGIN) > +ctf_it = ctf.Iterator(ctx,bp) > + > +prev_event = None > +event = ctf_it.read_event() > + > +start_time = event.get_timestamp() > + > +# Reading events: > +while(event is not None): > + if event.get_name() in events_count: > + events_count[event.get_name()] += 1 > + else: > + events_count[event.get_name()] = 1 > + > + ret = ctf_it.next() > + if ret < 0: > + break > + else: > + prev_event = event > + event = ctf_it.read_event() > + > +if event: > + total_time = event.get_timestamp() - start_time > +else: > + total_time = prev_event.get_timestamp() - start_time > + > +del ctf_it > + > +# Printing encountered events with respective count and rate: > +print("Total time: {} ns".format(total_time)) > +table = [["EVENT", "COUNT", "RATE (Hz)"]] > +for item in sorted(events_count.iterkeys()): > + tmp = [item, events_count[item], > + events_count[item]/(total_time/1000000000.0)] > + table.append(tmp) > +pprint(table) > + > +# Exporting data as bar graph > +cairoplot.vertical_bar_plot ( 'eventcountlist.svg', events_count, 50+85*len(events_count), > + 800, border = 20, display_values = True, grid = True, > + rounded_corners = True, > + x_labels = sorted(events_count.keys()) ) > diff --git a/bindings/python/examples/python2/events_per_cpu.py b/bindings/python/examples/python2/events_per_cpu.py > new file mode 100755 > index 0000000..6425b2d > --- /dev/null > +++ b/bindings/python/examples/python2/events_per_cpu.py > @@ -0,0 +1,100 @@ > +#!/usr/bin/env python2 > +# events_per_cpu.py > +# > +# Babeltrace events per cpu example script > +# > +# Copyright 2012 EfficiOS Inc. > +# > +# Author: Danny Serres > +# > +# Permission is hereby granted, free of charge, to any person obtaining a copy > +# of this software and associated documentation files (the "Software"), to deal > +# in the Software without restriction, including without limitation the rights > +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > +# copies of the Software, and to permit persons to whom the Software is > +# furnished to do so, subject to the following conditions: > +# > +# The above copyright notice and this permission notice shall be included in > +# all copies or substantial portions of the Software. > + > +# The script opens a trace and prints out CPU statistics > +# for the given trace (event count per CPU, total active > +# time and % of time processing events). > +# It also outputs a .txt file showing each time interval > +# (since the beginning of the trace) in which each CPU > +# was active and the corresponding event. > + > +import sys, multiprocessing > +from output_format_modules.pprint_table import pprint_table as pprint > +from babeltrace import * > + > +if len(sys.argv) < 2: > + raise TypeError("Usage: python events_per_cpu.py path/to/trace") > + > +# Adding trace > +ctx = Context() > +ret = ctx.add_trace(sys.argv[1], "ctf") > +if ret is None: > + raise IOError("Error adding trace") > + > +cpu_usage = [] > +nbEvents = 0 > +i = 0 > +while i < multiprocessing.cpu_count(): > + cpu_usage.append([]) > + i += 1 > + > +# Setting iterator > +bp = IterPos(SEEK_BEGIN) > +ctf_it = ctf.Iterator(ctx, bp) > + > +# Reading events > +event = ctf_it.read_event() > +start_time = event.get_timestamp() > + > +while(event is not None): > + > + event_name = event.get_name() > + ts = event.get_timestamp() > + > + # Getting cpu_id > + scope = event.get_top_level_scope(ctf.scope.STREAM_PACKET_CONTEXT) > + field = event.get_field(scope, "cpu_id") > + cpu_id = field.get_uint64() > + if ctf.field_error(): > + print("ERROR: Missing cpu_id info for {}".format(event.get_name())) > + else: > + cpu_usage[cpu_id].append( (int(ts), event_name) ) > + nbEvents += 1 > + > + # Next Event > + ret = ctf_it.next() > + if ret < 0: > + break > + event = ctf_it.read_event() > + > + > +# Outputting > +table = [] > +output = open("events_per_cpu.txt", "wt") > +output.write("(timestamp, event)\n") > + > +for cpu in range(len(cpu_usage)): > + # Setting table > + event_str = str(100.0 * len(cpu_usage[cpu]) / nbEvents) + '000' > + # % is printed with 2 decimals > + table.append([cpu, len(cpu_usage[cpu]), event_str[0:event_str.find('.') + 3] + ' %']) > + > + # Writing to file > + output.write("\n\n\n----------------------\n") > + output.write("CPU {}\n\n".format(cpu)) > + for event in cpu_usage[cpu]: > + output.write(str(event) + '\n') > + > +# Printing table > +table.insert(0, ["CPU ID", "EVENT COUNT", "TRACE EVENT %"]) > +pprint(table) > +print("Total event count: {}".format(nbEvents)) > +print("Total trace time: {} ns".format(ts - start_time)) > + > +output.close() > diff --git a/bindings/python/examples/python2/histogram.py b/bindings/python/examples/python2/histogram.py > new file mode 100755 > index 0000000..09618cb > --- /dev/null > +++ b/bindings/python/examples/python2/histogram.py > @@ -0,0 +1,140 @@ > +#!/usr/bin/env python2 > +# histogram.py > +# > +# Babeltrace histogram example script > +# > +# Copyright 2012 EfficiOS Inc. > +# > +# Author: Danny Serres > +# > +# Permission is hereby granted, free of charge, to any person obtaining a copy > +# of this software and associated documentation files (the "Software"), to deal > +# in the Software without restriction, including without limitation the rights > +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > +# copies of the Software, and to permit persons to whom the Software is > +# furnished to do so, subject to the following conditions: > +# > +# The above copyright notice and this permission notice shall be included in > +# all copies or substantial portions of the Software. > + > +# The script checks the number of events in the trace > +# and outputs a table and a .svg histogram for the specified > +# range (microseconds) or the total trace if no range specified. > +# The graph is generated using the cairoplot module. > + > +import sys > +from babeltrace import * > +from output_format_modules import cairoplot > +from output_format_modules.pprint_table import pprint_table as pprint > + > +# ------------------------------------------------ > +# Output settings > + > +# number of intervals: > +nbDiv = 25 # Should not be over 150 > + # for usable graph output > + > +# table output stream (file-like object): > +out = sys.stdout > +# ------------------------------------------------- > + > +if len(sys.argv) < 2 or len(sys.argv) > 4: > + raise TypeError("Usage: python histogram.py [ start_time [end_time] ] path/to/trace") > + > +ctx = Context() > +ret = ctx.add_trace(sys.argv[len(sys.argv)-1], "ctf") > +if ret is None: > + raise IOError("Error adding trace") > + > +# Check when to start/stop graphing > +sinceBegin = True > +beginTime = 0.0 > +if len(sys.argv) > 2: > + sinceBegin = False > + beginTime = float(sys.argv[1]) > +untilEnd = True > +if len(sys.argv) == 4: > + untilEnd = False > + > +# Setting iterator > +bp = IterPos(SEEK_BEGIN) > +ctf_it = ctf.Iterator(ctx, bp) > + > +# Reading events > +event = ctf_it.read_event() > +start_time = event.get_timestamp() > +time = 0 > +count = {} > + > +while(event is not None): > + # Microsec. > + time = (event.get_timestamp() - start_time)/1000.0 > + > + # Check if in range > + if not sinceBegin: > + if time < beginTime: > + # Next Event > + ret = ctf_it.next() > + if ret < 0: > + break > + event = ctf_it.read_event() > + continue > + if not untilEnd: > + if time > float(sys.argv[2]): > + break > + > + # Counting events per timestamp: > + if time in count: > + count[time] += 1 > + else: > + count[time] = 1 > + > + # Next Event > + ret = ctf_it.next() > + if ret < 0: > + break > + event = ctf_it.read_event() > + > +del ctf_it > + > +# Setting data for output > +interval = (time - beginTime)/nbDiv > +div_begin_time = beginTime > +div_end_time = beginTime + interval > +data = {} > + > +# Prefix for string sorting, considering > +# there should not be over 150 intervals. > +# This would work up to 9999 intervals. > +# If needed, add zeros. > +prefix = 0.0001 > + > +while div_end_time <= time: > + key = str(prefix) + '[' + str(div_begin_time) + ';' + str(div_end_time) + '[' > + for tmp in count: > + if tmp >= div_begin_time and tmp < div_end_time: > + if key in data: > + data[key] += count[tmp] > + else: > + data[key] = count[tmp] > + if not key in data: > + data[key] = 0 > + div_begin_time = div_end_time > + div_end_time += interval > + # Prefix increment > + prefix += 0.001 > + > +table = [] > +x_labels = [] > +for key in sorted(data): > + table.append([key[key.find('['):], data[key]]) > + x_labels.append(key[key.find('['):]) > + > +# Table output > +table.insert(0, ["INTERVAL (us)", "COUNT"]) > +pprint(table, 1, out) > + > +# Graph output > +cairoplot.vertical_bar_plot ( 'histogram.svg', data, 50 + 150*nbDiv, 50*nbDiv, > + border = 20, display_values = True, grid = True, > + x_labels = x_labels, rounded_corners = True ) > diff --git a/bindings/python/examples/python2/output_format_modules/cairoplot.py b/bindings/python/examples/python2/output_format_modules/cairoplot.py > new file mode 100644 > index 0000000..a27113f > --- /dev/null > +++ b/bindings/python/examples/python2/output_format_modules/cairoplot.py > @@ -0,0 +1,2336 @@ > +?#!/usr/bin/env python > +# -*- coding: utf-8 -*- > + > +# CairoPlot.py > +# > +# Copyright (c) 2008 Rodrigo Moreira Ara?jo > +# > +# Author: Rodrigo Moreiro Araujo > +# > +# This program is free software; you can redistribute it and/or > +# modify it under the terms of the GNU Lesser General Public License > +# as published by the Free Software Foundation; either version 2 of > +# the License, or (at your option) any later version. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU Lesser General Public > +# License along with this program; if not, write to the Free Software > +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 > +# USA > + > +#Contributor: Jo?o S. O. Bueno > + > +#TODO: review BarPlot Code > +#TODO: x_label colision problem on Horizontal Bar Plot > +#TODO: y_label's eat too much space on HBP > + > + > +__version__ = 1.2 > + > +import cairo > +import math > +import random > +from series import Series, Group, Data > + > +HORZ = 0 > +VERT = 1 > +NORM = 2 > + > +COLORS = {"red" : (1.0,0.0,0.0,1.0), "lime" : (0.0,1.0,0.0,1.0), "blue" : (0.0,0.0,1.0,1.0), > + "maroon" : (0.5,0.0,0.0,1.0), "green" : (0.0,0.5,0.0,1.0), "navy" : (0.0,0.0,0.5,1.0), > + "yellow" : (1.0,1.0,0.0,1.0), "magenta" : (1.0,0.0,1.0,1.0), "cyan" : (0.0,1.0,1.0,1.0), > + "orange" : (1.0,0.5,0.0,1.0), "white" : (1.0,1.0,1.0,1.0), "black" : (0.0,0.0,0.0,1.0), > + "gray" : (0.5,0.5,0.5,1.0), "light_gray" : (0.9,0.9,0.9,1.0), > + "transparent" : (0.0,0.0,0.0,0.0)} > + > +THEMES = {"black_red" : [(0.0,0.0,0.0,1.0), (1.0,0.0,0.0,1.0)], > + "red_green_blue" : [(1.0,0.0,0.0,1.0), (0.0,1.0,0.0,1.0), (0.0,0.0,1.0,1.0)], > + "red_orange_yellow" : [(1.0,0.2,0.0,1.0), (1.0,0.7,0.0,1.0), (1.0,1.0,0.0,1.0)], > + "yellow_orange_red" : [(1.0,1.0,0.0,1.0), (1.0,0.7,0.0,1.0), (1.0,0.2,0.0,1.0)], > + "rainbow" : [(1.0,0.0,0.0,1.0), (1.0,0.5,0.0,1.0), (1.0,1.0,0.0,1.0), (0.0,1.0,0.0,1.0), (0.0,0.0,1.0,1.0), (0.3, 0.0, 0.5,1.0), (0.5, 0.0, 1.0, 1.0)]} > + > +def colors_from_theme( theme, series_length, mode = 'solid' ): > + colors = [] > + if theme not in THEMES.keys() : > + raise Exception, "Theme not defined" > + color_steps = THEMES[theme] > + n_colors = len(color_steps) > + if series_length <= n_colors: > + colors = [color + tuple([mode]) for color in color_steps[0:n_colors]] > + else: > + iterations = [(series_length - n_colors)/(n_colors - 1) for i in color_steps[:-1]] > + over_iterations = (series_length - n_colors) % (n_colors - 1) > + for i in range(n_colors - 1): > + if over_iterations <= 0: > + break > + iterations[i] += 1 > + over_iterations -= 1 > + for index,color in enumerate(color_steps[:-1]): > + colors.append(color + tuple([mode])) > + if iterations[index] == 0: > + continue > + next_color = color_steps[index+1] > + color_step = ((next_color[0] - color[0])/(iterations[index] + 1), > + (next_color[1] - color[1])/(iterations[index] + 1), > + (next_color[2] - color[2])/(iterations[index] + 1), > + (next_color[3] - color[3])/(iterations[index] + 1)) > + for i in range( iterations[index] ): > + colors.append((color[0] + color_step[0]*(i+1), > + color[1] + color_step[1]*(i+1), > + color[2] + color_step[2]*(i+1), > + color[3] + color_step[3]*(i+1), > + mode)) > + colors.append(color_steps[-1] + tuple([mode])) > + return colors > + > + > +def other_direction(direction): > + "explicit is better than implicit" > + if direction == HORZ: > + return VERT > + else: > + return HORZ > + > +#Class definition > + > +class Plot(object): > + def __init__(self, > + surface=None, > + data=None, > + width=640, > + height=480, > + background=None, > + border = 0, > + x_labels = None, > + y_labels = None, > + series_colors = None): > + random.seed(2) > + self.create_surface(surface, width, height) > + self.dimensions = {} > + self.dimensions[HORZ] = width > + self.dimensions[VERT] = height > + self.context = cairo.Context(self.surface) > + self.labels={} > + self.labels[HORZ] = x_labels > + self.labels[VERT] = y_labels > + self.load_series(data, x_labels, y_labels, series_colors) > + self.font_size = 10 > + self.set_background (background) > + self.border = border > + self.borders = {} > + self.line_color = (0.5, 0.5, 0.5) > + self.line_width = 0.5 > + self.label_color = (0.0, 0.0, 0.0) > + self.grid_color = (0.8, 0.8, 0.8) > + > + def create_surface(self, surface, width=None, height=None): > + self.filename = None > + if isinstance(surface, cairo.Surface): > + self.surface = surface > + return > + if not type(surface) in (str, unicode): > + raise TypeError("Surface should be either a Cairo surface or a filename, not %s" % surface) > + sufix = surface.rsplit(".")[-1].lower() > + self.filename = surface > + if sufix == "png": > + self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) > + elif sufix == "ps": > + self.surface = cairo.PSSurface(surface, width, height) > + elif sufix == "pdf": > + self.surface = cairo.PSSurface(surface, width, height) > + else: > + if sufix != "svg": > + self.filename += ".svg" > + self.surface = cairo.SVGSurface(self.filename, width, height) > + > + def commit(self): > + try: > + self.context.show_page() > + if self.filename and self.filename.endswith(".png"): > + self.surface.write_to_png(self.filename) > + else: > + self.surface.finish() > + except cairo.Error: > + pass > + > + def load_series (self, data, x_labels=None, y_labels=None, series_colors=None): > + self.series_labels = [] > + self.series = None > + > + #The pretty way > + #if not isinstance(data, Series): > + # # Not an instance of Series > + # self.series = Series(data) > + #else: > + # self.series = data > + # > + #self.series_labels = self.series.get_names() > + > + #TODO: Remove on next version > + # The ugly way, keeping retrocompatibility... > + if callable(data) or type(data) is list and callable(data[0]): # Lambda or List of lambdas > + self.series = data > + self.series_labels = None > + elif isinstance(data, Series): # Instance of Series > + self.series = data > + self.series_labels = data.get_names() > + else: # Anything else > + self.series = Series(data) > + self.series_labels = self.series.get_names() > + > + #TODO: allow user passed series_widths > + self.series_widths = [1.0 for group in self.series] > + > + #TODO: Remove on next version > + self.process_colors( series_colors ) > + > + def process_colors( self, series_colors, length = None, mode = 'solid' ): > + #series_colors might be None, a theme, a string of colors names or a list of color tuples > + if length is None : > + length = len( self.series.to_list() ) > + > + #no colors passed > + if not series_colors: > + #Randomize colors > + self.series_colors = [ [random.random() for i in range(3)] + [1.0, mode] for series in range( length ) ] > + else: > + #Just theme pattern > + if not hasattr( series_colors, "__iter__" ): > + theme = series_colors > + self.series_colors = colors_from_theme( theme.lower(), length ) > + > + #Theme pattern and mode > + elif not hasattr(series_colors, '__delitem__') and not hasattr( series_colors[0], "__iter__" ): > + theme = series_colors[0] > + mode = series_colors[1] > + self.series_colors = colors_from_theme( theme.lower(), length, mode ) > + > + #List > + else: > + self.series_colors = series_colors > + for index, color in enumerate( self.series_colors ): > + #element is a color name > + if not hasattr(color, "__iter__"): > + self.series_colors[index] = COLORS[color.lower()] + tuple([mode]) > + #element is rgb tuple instead of rgba > + elif len( color ) == 3 : > + self.series_colors[index] += (1.0,mode) > + #element has 4 elements, might be rgba tuple or rgb tuple with mode > + elif len( color ) == 4 : > + #last element is mode > + if not hasattr(color[3], "__iter__"): > + self.series_colors[index] += tuple([color[3]]) > + self.series_colors[index][3] = 1.0 > + #last element is alpha > + else: > + self.series_colors[index] += tuple([mode]) > + > + def get_width(self): > + return self.surface.get_width() > + > + def get_height(self): > + return self.surface.get_height() > + > + def set_background(self, background): > + if background is None: > + self.background = (0.0,0.0,0.0,0.0) > + elif type(background) in (cairo.LinearGradient, tuple): > + self.background = background > + elif not hasattr(background,"__iter__"): > + colors = background.split(" ") > + if len(colors) == 1 and colors[0] in COLORS: > + self.background = COLORS[background] > + elif len(colors) > 1: > + self.background = cairo.LinearGradient(self.dimensions[HORZ] / 2, 0, self.dimensions[HORZ] / 2, self.dimensions[VERT]) > + for index,color in enumerate(colors): > + self.background.add_color_stop_rgba(float(index)/(len(colors)-1),*COLORS[color]) > + else: > + raise TypeError ("Background should be either cairo.LinearGradient or a 3/4-tuple, not %s" % type(background)) > + > + def render_background(self): > + if isinstance(self.background, cairo.LinearGradient): > + self.context.set_source(self.background) > + else: > + self.context.set_source_rgba(*self.background) > + self.context.rectangle(0,0, self.dimensions[HORZ], self.dimensions[VERT]) > + self.context.fill() > + > + def render_bounding_box(self): > + self.context.set_source_rgba(*self.line_color) > + self.context.set_line_width(self.line_width) > + self.context.rectangle(self.border, self.border, > + self.dimensions[HORZ] - 2 * self.border, > + self.dimensions[VERT] - 2 * self.border) > + self.context.stroke() > + > + def render(self): > + pass > + > +class ScatterPlot( Plot ): > + def __init__(self, > + surface=None, > + data=None, > + errorx=None, > + errory=None, > + width=640, > + height=480, > + background=None, > + border=0, > + axis = False, > + dash = False, > + discrete = False, > + dots = 0, > + grid = False, > + series_legend = False, > + x_labels = None, > + y_labels = None, > + x_bounds = None, > + y_bounds = None, > + z_bounds = None, > + x_title = None, > + y_title = None, > + series_colors = None, > + circle_colors = None ): > + > + self.bounds = {} > + self.bounds[HORZ] = x_bounds > + self.bounds[VERT] = y_bounds > + self.bounds[NORM] = z_bounds > + self.titles = {} > + self.titles[HORZ] = x_title > + self.titles[VERT] = y_title > + self.max_value = {} > + self.axis = axis > + self.discrete = discrete > + self.dots = dots > + self.grid = grid > + self.series_legend = series_legend > + self.variable_radius = False > + self.x_label_angle = math.pi / 2.5 > + self.circle_colors = circle_colors > + > + Plot.__init__(self, surface, data, width, height, background, border, x_labels, y_labels, series_colors) > + > + self.dash = None > + if dash: > + if hasattr(dash, "keys"): > + self.dash = [dash[key] for key in self.series_labels] > + elif max([hasattr(item,'__delitem__') for item in data]) : > + self.dash = dash > + else: > + self.dash = [dash] > + > + self.load_errors(errorx, errory) > + > + def convert_list_to_tuple(self, data): > + #Data must be converted from lists of coordinates to a single > + # list of tuples > + out_data = zip(*data) > + if len(data) == 3: > + self.variable_radius = True > + return out_data > + > + def load_series(self, data, x_labels = None, y_labels = None, series_colors=None): > + #TODO: In cairoplot 2.0 keep only the Series instances > + > + # Convert Data and Group to Series > + if isinstance(data, Data) or isinstance(data, Group): > + data = Series(data) > + > + # Series > + if isinstance(data, Series): > + for group in data: > + for item in group: > + if len(item) is 3: > + self.variable_radius = True > + > + #Dictionary with lists > + if hasattr(data, "keys") : > + if hasattr( data.values()[0][0], "__delitem__" ) : > + for key in data.keys() : > + data[key] = self.convert_list_to_tuple(data[key]) > + elif len(data.values()[0][0]) == 3: > + self.variable_radius = True > + #List > + elif hasattr(data[0], "__delitem__") : > + #List of lists > + if hasattr(data[0][0], "__delitem__") : > + for index,value in enumerate(data) : > + data[index] = self.convert_list_to_tuple(value) > + #List > + elif type(data[0][0]) != type((0,0)): > + data = self.convert_list_to_tuple(data) > + #Three dimensional data > + elif len(data[0][0]) == 3: > + self.variable_radius = True > + > + #List with three dimensional tuples > + elif len(data[0]) == 3: > + self.variable_radius = True > + Plot.load_series(self, data, x_labels, y_labels, series_colors) > + self.calc_boundaries() > + self.calc_labels() > + > + def load_errors(self, errorx, errory): > + self.errors = None > + if errorx == None and errory == None: > + return > + self.errors = {} > + self.errors[HORZ] = None > + self.errors[VERT] = None > + #asimetric errors > + if errorx and hasattr(errorx[0], "__delitem__"): > + self.errors[HORZ] = errorx > + #simetric errors > + elif errorx: > + self.errors[HORZ] = [errorx] > + #asimetric errors > + if errory and hasattr(errory[0], "__delitem__"): > + self.errors[VERT] = errory > + #simetric errors > + elif errory: > + self.errors[VERT] = [errory] > + > + def calc_labels(self): > + if not self.labels[HORZ]: > + amplitude = self.bounds[HORZ][1] - self.bounds[HORZ][0] > + if amplitude % 10: #if horizontal labels need floating points > + self.labels[HORZ] = ["%.2lf" % (float(self.bounds[HORZ][0] + (amplitude * i / 10.0))) for i in range(11) ] > + else: > + self.labels[HORZ] = ["%d" % (int(self.bounds[HORZ][0] + (amplitude * i / 10.0))) for i in range(11) ] > + if not self.labels[VERT]: > + amplitude = self.bounds[VERT][1] - self.bounds[VERT][0] > + if amplitude % 10: #if vertical labels need floating points > + self.labels[VERT] = ["%.2lf" % (float(self.bounds[VERT][0] + (amplitude * i / 10.0))) for i in range(11) ] > + else: > + self.labels[VERT] = ["%d" % (int(self.bounds[VERT][0] + (amplitude * i / 10.0))) for i in range(11) ] > + > + def calc_extents(self, direction): > + self.context.set_font_size(self.font_size * 0.8) > + self.max_value[direction] = max(self.context.text_extents(item)[2] for item in self.labels[direction]) > + self.borders[other_direction(direction)] = self.max_value[direction] + self.border + 20 > + > + def calc_boundaries(self): > + #HORZ = 0, VERT = 1, NORM = 2 > + min_data_value = [0,0,0] > + max_data_value = [0,0,0] > + > + for group in self.series: > + if type(group[0].content) in (int, float, long): > + group = [Data((index, item.content)) for index,item in enumerate(group)] > + > + for point in group: > + for index, item in enumerate(point.content): > + if item > max_data_value[index]: > + max_data_value[index] = item > + elif item < min_data_value[index]: > + min_data_value[index] = item > + > + if not self.bounds[HORZ]: > + self.bounds[HORZ] = (min_data_value[HORZ], max_data_value[HORZ]) > + if not self.bounds[VERT]: > + self.bounds[VERT] = (min_data_value[VERT], max_data_value[VERT]) > + if not self.bounds[NORM]: > + self.bounds[NORM] = (min_data_value[NORM], max_data_value[NORM]) > + > + def calc_all_extents(self): > + self.calc_extents(HORZ) > + self.calc_extents(VERT) > + > + self.plot_height = self.dimensions[VERT] - 2 * self.borders[VERT] > + self.plot_width = self.dimensions[HORZ] - 2* self.borders[HORZ] > + > + self.plot_top = self.dimensions[VERT] - self.borders[VERT] > + > + def calc_steps(self): > + #Calculates all the x, y, z and color steps > + series_amplitude = [self.bounds[index][1] - self.bounds[index][0] for index in range(3)] > + > + if series_amplitude[HORZ]: > + self.horizontal_step = float (self.plot_width) / series_amplitude[HORZ] > + else: > + self.horizontal_step = 0.00 > + > + if series_amplitude[VERT]: > + self.vertical_step = float (self.plot_height) / series_amplitude[VERT] > + else: > + self.vertical_step = 0.00 > + > + if series_amplitude[NORM]: > + if self.variable_radius: > + self.z_step = float (self.bounds[NORM][1]) / series_amplitude[NORM] > + if self.circle_colors: > + self.circle_color_step = tuple([float(self.circle_colors[1][i]-self.circle_colors[0][i])/series_amplitude[NORM] for i in range(4)]) > + else: > + self.z_step = 0.00 > + self.circle_color_step = ( 0.0, 0.0, 0.0, 0.0 ) > + > + def get_circle_color(self, value): > + return tuple( [self.circle_colors[0][i] + value*self.circle_color_step[i] for i in range(4)] ) > + > + def render(self): > + self.calc_all_extents() > + self.calc_steps() > + self.render_background() > + self.render_bounding_box() > + if self.axis: > + self.render_axis() > + if self.grid: > + self.render_grid() > + self.render_labels() > + self.render_plot() > + if self.errors: > + self.render_errors() > + if self.series_legend and self.series_labels: > + self.render_legend() > + > + def render_axis(self): > + #Draws both the axis lines and their titles > + cr = self.context > + cr.set_source_rgba(*self.line_color) > + cr.move_to(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT]) > + cr.line_to(self.borders[HORZ], self.borders[VERT]) > + cr.stroke() > + > + cr.move_to(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT]) > + cr.line_to(self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT]) > + cr.stroke() > + > + cr.set_source_rgba(*self.label_color) > + self.context.set_font_size( 1.2 * self.font_size ) > + if self.titles[HORZ]: > + title_width,title_height = cr.text_extents(self.titles[HORZ])[2:4] > + cr.move_to( self.dimensions[HORZ]/2 - title_width/2, self.borders[VERT] - title_height/2 ) > + cr.show_text( self.titles[HORZ] ) > + > + if self.titles[VERT]: > + title_width,title_height = cr.text_extents(self.titles[VERT])[2:4] > + cr.move_to( self.dimensions[HORZ] - self.borders[HORZ] + title_height/2, self.dimensions[VERT]/2 - title_width/2) > + cr.save() > + cr.rotate( math.pi/2 ) > + cr.show_text( self.titles[VERT] ) > + cr.restore() > + > + def render_grid(self): > + cr = self.context > + horizontal_step = float( self.plot_height ) / ( len( self.labels[VERT] ) - 1 ) > + vertical_step = float( self.plot_width ) / ( len( self.labels[HORZ] ) - 1 ) > + > + x = self.borders[HORZ] + vertical_step > + y = self.plot_top - horizontal_step > + > + for label in self.labels[HORZ][:-1]: > + cr.set_source_rgba(*self.grid_color) > + cr.move_to(x, self.dimensions[VERT] - self.borders[VERT]) > + cr.line_to(x, self.borders[VERT]) > + cr.stroke() > + x += vertical_step > + for label in self.labels[VERT][:-1]: > + cr.set_source_rgba(*self.grid_color) > + cr.move_to(self.borders[HORZ], y) > + cr.line_to(self.dimensions[HORZ] - self.borders[HORZ], y) > + cr.stroke() > + y -= horizontal_step > + > + def render_labels(self): > + self.context.set_font_size(self.font_size * 0.8) > + self.render_horz_labels() > + self.render_vert_labels() > + > + def render_horz_labels(self): > + cr = self.context > + step = float( self.plot_width ) / ( len( self.labels[HORZ] ) - 1 ) > + x = self.borders[HORZ] > + y = self.dimensions[VERT] - self.borders[VERT] + 5 > + > + # store rotation matrix from the initial state > + rotation_matrix = cr.get_matrix() > + rotation_matrix.rotate(self.x_label_angle) > + > + cr.set_source_rgba(*self.label_color) > + > + for item in self.labels[HORZ]: > + width = cr.text_extents(item)[2] > + cr.move_to(x, y) > + cr.save() > + cr.set_matrix(rotation_matrix) > + cr.show_text(item) > + cr.restore() > + x += step > + > + def render_vert_labels(self): > + cr = self.context > + step = ( self.plot_height ) / ( len( self.labels[VERT] ) - 1 ) > + y = self.plot_top > + cr.set_source_rgba(*self.label_color) > + for item in self.labels[VERT]: > + width = cr.text_extents(item)[2] > + cr.move_to(self.borders[HORZ] - width - 5,y) > + cr.show_text(item) > + y -= step > + > + def render_legend(self): > + cr = self.context > + cr.set_font_size(self.font_size) > + cr.set_line_width(self.line_width) > + > + widest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[2]) > + tallest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[3]) > + max_width = self.context.text_extents(widest_word)[2] > + max_height = self.context.text_extents(tallest_word)[3] * 1.1 > + > + color_box_height = max_height / 2 > + color_box_width = color_box_height * 2 > + > + #Draw a bounding box > + bounding_box_width = max_width + color_box_width + 15 > + bounding_box_height = (len(self.series_labels)+0.5) * max_height > + cr.set_source_rgba(1,1,1) > + cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - bounding_box_width, self.borders[VERT], > + bounding_box_width, bounding_box_height) > + cr.fill() > + > + cr.set_source_rgba(*self.line_color) > + cr.set_line_width(self.line_width) > + cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - bounding_box_width, self.borders[VERT], > + bounding_box_width, bounding_box_height) > + cr.stroke() > + > + for idx,key in enumerate(self.series_labels): > + #Draw color box > + cr.set_source_rgba(*self.series_colors[idx][:4]) > + cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - max_width - color_box_width - 10, > + self.borders[VERT] + color_box_height + (idx*max_height) , > + color_box_width, color_box_height) > + cr.fill() > + > + cr.set_source_rgba(0, 0, 0) > + cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - max_width - color_box_width - 10, > + self.borders[VERT] + color_box_height + (idx*max_height), > + color_box_width, color_box_height) > + cr.stroke() > + > + #Draw series labels > + cr.set_source_rgba(0, 0, 0) > + cr.move_to(self.dimensions[HORZ] - self.borders[HORZ] - max_width - 5, self.borders[VERT] + ((idx+1)*max_height)) > + cr.show_text(key) > + > + def render_errors(self): > + cr = self.context > + cr.rectangle(self.borders[HORZ], self.borders[VERT], self.plot_width, self.plot_height) > + cr.clip() > + radius = self.dots > + x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step > + y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step > + for index, group in enumerate(self.series): > + cr.set_source_rgba(*self.series_colors[index][:4]) > + for number, data in enumerate(group): > + x = x0 + self.horizontal_step * data.content[0] > + y = self.dimensions[VERT] - y0 - self.vertical_step * data.content[1] > + if self.errors[HORZ]: > + cr.move_to(x, y) > + x1 = x - self.horizontal_step * self.errors[HORZ][0][number] > + cr.line_to(x1, y) > + cr.line_to(x1, y - radius) > + cr.line_to(x1, y + radius) > + cr.stroke() > + if self.errors[HORZ] and len(self.errors[HORZ]) == 2: > + cr.move_to(x, y) > + x1 = x + self.horizontal_step * self.errors[HORZ][1][number] > + cr.line_to(x1, y) > + cr.line_to(x1, y - radius) > + cr.line_to(x1, y + radius) > + cr.stroke() > + if self.errors[VERT]: > + cr.move_to(x, y) > + y1 = y + self.vertical_step * self.errors[VERT][0][number] > + cr.line_to(x, y1) > + cr.line_to(x - radius, y1) > + cr.line_to(x + radius, y1) > + cr.stroke() > + if self.errors[VERT] and len(self.errors[VERT]) == 2: > + cr.move_to(x, y) > + y1 = y - self.vertical_step * self.errors[VERT][1][number] > + cr.line_to(x, y1) > + cr.line_to(x - radius, y1) > + cr.line_to(x + radius, y1) > + cr.stroke() > + > + > + def render_plot(self): > + cr = self.context > + if self.discrete: > + cr.rectangle(self.borders[HORZ], self.borders[VERT], self.plot_width, self.plot_height) > + cr.clip() > + x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step > + y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step > + radius = self.dots > + for number, group in enumerate (self.series): > + cr.set_source_rgba(*self.series_colors[number][:4]) > + for data in group : > + if self.variable_radius: > + radius = data.content[2]*self.z_step > + if self.circle_colors: > + cr.set_source_rgba( *self.get_circle_color( data.content[2]) ) > + x = x0 + self.horizontal_step*data.content[0] > + y = y0 + self.vertical_step*data.content[1] > + cr.arc(x, self.dimensions[VERT] - y, radius, 0, 2*math.pi) > + cr.fill() > + else: > + cr.rectangle(self.borders[HORZ], self.borders[VERT], self.plot_width, self.plot_height) > + cr.clip() > + x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step > + y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step > + radius = self.dots > + for number, group in enumerate (self.series): > + last_data = None > + cr.set_source_rgba(*self.series_colors[number][:4]) > + for data in group : > + x = x0 + self.horizontal_step*data.content[0] > + y = y0 + self.vertical_step*data.content[1] > + if self.dots: > + if self.variable_radius: > + radius = data.content[2]*self.z_step > + cr.arc(x, self.dimensions[VERT] - y, radius, 0, 2*math.pi) > + cr.fill() > + if last_data : > + old_x = x0 + self.horizontal_step*last_data.content[0] > + old_y = y0 + self.vertical_step*last_data.content[1] > + cr.move_to( old_x, self.dimensions[VERT] - old_y ) > + cr.line_to( x, self.dimensions[VERT] - y) > + cr.set_line_width(self.series_widths[number]) > + > + #?Display line as dash line > + if self.dash and self.dash[number]: > + s = self.series_widths[number] > + cr.set_dash([s*3, s*3], 0) > + > + cr.stroke() > + cr.set_dash([]) > + last_data = data > + > +class DotLinePlot(ScatterPlot): > + def __init__(self, > + surface=None, > + data=None, > + width=640, > + height=480, > + background=None, > + border=0, > + axis = False, > + dash = False, > + dots = 0, > + grid = False, > + series_legend = False, > + x_labels = None, > + y_labels = None, > + x_bounds = None, > + y_bounds = None, > + x_title = None, > + y_title = None, > + series_colors = None): > + > + ScatterPlot.__init__(self, surface, data, None, None, width, height, background, border, > + axis, dash, False, dots, grid, series_legend, x_labels, y_labels, > + x_bounds, y_bounds, None, x_title, y_title, series_colors, None ) > + > + > + def load_series(self, data, x_labels = None, y_labels = None, series_colors=None): > + Plot.load_series(self, data, x_labels, y_labels, series_colors) > + for group in self.series : > + for index,data in enumerate(group): > + group[index].content = (index, data.content) > + > + self.calc_boundaries() > + self.calc_labels() > + > +class FunctionPlot(ScatterPlot): > + def __init__(self, > + surface=None, > + data=None, > + width=640, > + height=480, > + background=None, > + border=0, > + axis = False, > + discrete = False, > + dots = 0, > + grid = False, > + series_legend = False, > + x_labels = None, > + y_labels = None, > + x_bounds = None, > + y_bounds = None, > + x_title = None, > + y_title = None, > + series_colors = None, > + step = 1): > + > + self.function = data > + self.step = step > + self.discrete = discrete > + > + data, x_bounds = self.load_series_from_function( self.function, x_bounds ) > + > + ScatterPlot.__init__(self, surface, data, None, None, width, height, background, border, > + axis, False, discrete, dots, grid, series_legend, x_labels, y_labels, > + x_bounds, y_bounds, None, x_title, y_title, series_colors, None ) > + > + def load_series(self, data, x_labels = None, y_labels = None, series_colors=None): > + Plot.load_series(self, data, x_labels, y_labels, series_colors) > + > + if len(self.series[0][0]) is 1: > + for group_id, group in enumerate(self.series) : > + for index,data in enumerate(group): > + group[index].content = (self.bounds[HORZ][0] + self.step*index, data.content) > + > + self.calc_boundaries() > + self.calc_labels() > + > + def load_series_from_function( self, function, x_bounds ): > + #TODO: Add the possibility for the user to define multiple functions with different discretization parameters > + > + #This function converts a function, a list of functions or a dictionary > + #of functions into its corresponding array of data > + series = Series() > + > + if isinstance(function, Group) or isinstance(function, Data): > + function = Series(function) > + > + # If is instance of Series > + if isinstance(function, Series): > + # Overwrite any bounds passed by the function > + x_bounds = (function.range[0],function.range[-1]) > + > + #if no bounds are provided > + if x_bounds == None: > + x_bounds = (0,10) > + > + > + #TODO: Finish the dict translation > + if hasattr(function, "keys"): #dictionary: > + for key in function.keys(): > + group = Group(name=key) > + #data[ key ] = [] > + i = x_bounds[0] > + while i <= x_bounds[1] : > + group.add_data(function[ key ](i)) > + #data[ key ].append( function[ key ](i) ) > + i += self.step > + series.add_group(group) > + > + elif hasattr(function, "__delitem__"): #list of functions > + for index,f in enumerate( function ) : > + group = Group() > + #data.append( [] ) > + i = x_bounds[0] > + while i <= x_bounds[1] : > + group.add_data(f(i)) > + #data[ index ].append( f(i) ) > + i += self.step > + series.add_group(group) > + > + elif isinstance(function, Series): # instance of Series > + series = function > + > + else: #function > + group = Group() > + i = x_bounds[0] > + while i <= x_bounds[1] : > + group.add_data(function(i)) > + i += self.step > + series.add_group(group) > + > + > + return series, x_bounds > + > + def calc_labels(self): > + if not self.labels[HORZ]: > + self.labels[HORZ] = [] > + i = self.bounds[HORZ][0] > + while i<=self.bounds[HORZ][1]: > + self.labels[HORZ].append(str(i)) > + i += float(self.bounds[HORZ][1] - self.bounds[HORZ][0])/10 > + ScatterPlot.calc_labels(self) > + > + def render_plot(self): > + if not self.discrete: > + ScatterPlot.render_plot(self) > + else: > + last = None > + cr = self.context > + for number, group in enumerate (self.series): > + cr.set_source_rgba(*self.series_colors[number][:4]) > + x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step > + y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step > + for data in group: > + x = x0 + self.horizontal_step * data.content[0] > + y = y0 + self.vertical_step * data.content[1] > + cr.move_to(x, self.dimensions[VERT] - y) > + cr.line_to(x, self.plot_top) > + cr.set_line_width(self.series_widths[number]) > + cr.stroke() > + if self.dots: > + cr.new_path() > + cr.arc(x, self.dimensions[VERT] - y, 3, 0, 2.1 * math.pi) > + cr.close_path() > + cr.fill() > + > +class BarPlot(Plot): > + def __init__(self, > + surface = None, > + data = None, > + width = 640, > + height = 480, > + background = "white light_gray", > + border = 0, > + display_values = False, > + grid = False, > + rounded_corners = False, > + stack = False, > + three_dimension = False, > + x_labels = None, > + y_labels = None, > + x_bounds = None, > + y_bounds = None, > + series_colors = None, > + main_dir = None): > + > + self.bounds = {} > + self.bounds[HORZ] = x_bounds > + self.bounds[VERT] = y_bounds > + self.display_values = display_values > + self.grid = grid > + self.rounded_corners = rounded_corners > + self.stack = stack > + self.three_dimension = three_dimension > + self.x_label_angle = math.pi / 2.5 > + self.main_dir = main_dir > + self.max_value = {} > + self.plot_dimensions = {} > + self.steps = {} > + self.value_label_color = (0.5,0.5,0.5,1.0) > + > + Plot.__init__(self, surface, data, width, height, background, border, x_labels, y_labels, series_colors) > + > + def load_series(self, data, x_labels = None, y_labels = None, series_colors = None): > + Plot.load_series(self, data, x_labels, y_labels, series_colors) > + self.calc_boundaries() > + > + def process_colors(self, series_colors): > + #Data for a BarPlot might be a List or a List of Lists. > + #On the first case, colors must be generated for all bars, > + #On the second, colors must be generated for each of the inner lists. > + > + #TODO: Didn't get it... > + #if hasattr(self.data[0], '__getitem__'): > + # length = max(len(series) for series in self.data) > + #else: > + # length = len( self.data ) > + > + length = max(len(group) for group in self.series) > + > + Plot.process_colors( self, series_colors, length, 'linear') > + > + def calc_boundaries(self): > + if not self.bounds[self.main_dir]: > + if self.stack: > + max_data_value = max(sum(group.to_list()) for group in self.series) > + else: > + max_data_value = max(max(group.to_list()) for group in self.series) > + self.bounds[self.main_dir] = (0, max_data_value) > + if not self.bounds[other_direction(self.main_dir)]: > + self.bounds[other_direction(self.main_dir)] = (0, len(self.series)) > + > + def calc_extents(self, direction): > + self.max_value[direction] = 0 > + if self.labels[direction]: > + widest_word = max(self.labels[direction], key = lambda item: self.context.text_extents(item)[2]) > + self.max_value[direction] = self.context.text_extents(widest_word)[3 - direction] > + self.borders[other_direction(direction)] = (2-direction)*self.max_value[direction] + self.border + direction*(5) > + else: > + self.borders[other_direction(direction)] = self.border > + > + def calc_horz_extents(self): > + self.calc_extents(HORZ) > + > + def calc_vert_extents(self): > + self.calc_extents(VERT) > + > + def calc_all_extents(self): > + self.calc_horz_extents() > + self.calc_vert_extents() > + other_dir = other_direction(self.main_dir) > + self.value_label = 0 > + if self.display_values: > + if self.stack: > + self.value_label = self.context.text_extents(str(max(sum(group.to_list()) for group in self.series)))[2 + self.main_dir] > + else: > + self.value_label = self.context.text_extents(str(max(max(group.to_list()) for group in self.series)))[2 + self.main_dir] > + if self.labels[self.main_dir]: > + self.plot_dimensions[self.main_dir] = self.dimensions[self.main_dir] - 2*self.borders[self.main_dir] - self.value_label > + else: > + self.plot_dimensions[self.main_dir] = self.dimensions[self.main_dir] - self.borders[self.main_dir] - 1.2*self.border - self.value_label > + self.plot_dimensions[other_dir] = self.dimensions[other_dir] - self.borders[other_dir] - self.border > + self.plot_top = self.dimensions[VERT] - self.borders[VERT] > + > + def calc_steps(self): > + other_dir = other_direction(self.main_dir) > + self.series_amplitude = self.bounds[self.main_dir][1] - self.bounds[self.main_dir][0] > + if self.series_amplitude: > + self.steps[self.main_dir] = float(self.plot_dimensions[self.main_dir])/self.series_amplitude > + else: > + self.steps[self.main_dir] = 0.00 > + series_length = len(self.series) > + self.steps[other_dir] = float(self.plot_dimensions[other_dir])/(series_length + 0.1*(series_length + 1)) > + self.space = 0.1*self.steps[other_dir] > + > + def render(self): > + self.calc_all_extents() > + self.calc_steps() > + self.render_background() > + self.render_bounding_box() > + if self.grid: > + self.render_grid() > + if self.three_dimension: > + self.render_ground() > + if self.display_values: > + self.render_values() > + self.render_labels() > + self.render_plot() > + if self.series_labels: > + self.render_legend() > + > + def draw_3d_rectangle_front(self, x0, y0, x1, y1, shift): > + self.context.rectangle(x0-shift, y0+shift, x1-x0, y1-y0) > + > + def draw_3d_rectangle_side(self, x0, y0, x1, y1, shift): > + self.context.move_to(x1-shift,y0+shift) > + self.context.line_to(x1, y0) > + self.context.line_to(x1, y1) > + self.context.line_to(x1-shift, y1+shift) > + self.context.line_to(x1-shift, y0+shift) > + self.context.close_path() > + > + def draw_3d_rectangle_top(self, x0, y0, x1, y1, shift): > + self.context.move_to(x0-shift,y0+shift) > + self.context.line_to(x0, y0) > + self.context.line_to(x1, y0) > + self.context.line_to(x1-shift, y0+shift) > + self.context.line_to(x0-shift, y0+shift) > + self.context.close_path() > + > + def draw_round_rectangle(self, x0, y0, x1, y1): > + self.context.arc(x0+5, y0+5, 5, -math.pi, -math.pi/2) > + self.context.line_to(x1-5, y0) > + self.context.arc(x1-5, y0+5, 5, -math.pi/2, 0) > + self.context.line_to(x1, y1-5) > + self.context.arc(x1-5, y1-5, 5, 0, math.pi/2) > + self.context.line_to(x0+5, y1) > + self.context.arc(x0+5, y1-5, 5, math.pi/2, math.pi) > + self.context.line_to(x0, y0+5) > + self.context.close_path() > + > + def render_ground(self): > + self.draw_3d_rectangle_front(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], > + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) > + self.context.fill() > + > + self.draw_3d_rectangle_side (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], > + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) > + self.context.fill() > + > + self.draw_3d_rectangle_top (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], > + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) > + self.context.fill() > + > + def render_labels(self): > + self.context.set_font_size(self.font_size * 0.8) > + if self.labels[HORZ]: > + self.render_horz_labels() > + if self.labels[VERT]: > + self.render_vert_labels() > + > + def render_legend(self): > + cr = self.context > + cr.set_font_size(self.font_size) > + cr.set_line_width(self.line_width) > + > + widest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[2]) > + tallest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[3]) > + max_width = self.context.text_extents(widest_word)[2] > + max_height = self.context.text_extents(tallest_word)[3] * 1.1 + 5 > + > + color_box_height = max_height / 2 > + color_box_width = color_box_height * 2 > + > + #Draw a bounding box > + bounding_box_width = max_width + color_box_width + 15 > + bounding_box_height = (len(self.series_labels)+0.5) * max_height > + cr.set_source_rgba(1,1,1) > + cr.rectangle(self.dimensions[HORZ] - self.border - bounding_box_width, self.border, > + bounding_box_width, bounding_box_height) > + cr.fill() > + > + cr.set_source_rgba(*self.line_color) > + cr.set_line_width(self.line_width) > + cr.rectangle(self.dimensions[HORZ] - self.border - bounding_box_width, self.border, > + bounding_box_width, bounding_box_height) > + cr.stroke() > + > + for idx,key in enumerate(self.series_labels): > + #Draw color box > + cr.set_source_rgba(*self.series_colors[idx][:4]) > + cr.rectangle(self.dimensions[HORZ] - self.border - max_width - color_box_width - 10, > + self.border + color_box_height + (idx*max_height) , > + color_box_width, color_box_height) > + cr.fill() > + > + cr.set_source_rgba(0, 0, 0) > + cr.rectangle(self.dimensions[HORZ] - self.border - max_width - color_box_width - 10, > + self.border + color_box_height + (idx*max_height), > + color_box_width, color_box_height) > + cr.stroke() > + > + #Draw series labels > + cr.set_source_rgba(0, 0, 0) > + cr.move_to(self.dimensions[HORZ] - self.border - max_width - 5, self.border + ((idx+1)*max_height)) > + cr.show_text(key) > + > + > +class HorizontalBarPlot(BarPlot): > + def __init__(self, > + surface = None, > + data = None, > + width = 640, > + height = 480, > + background = "white light_gray", > + border = 0, > + display_values = False, > + grid = False, > + rounded_corners = False, > + stack = False, > + three_dimension = False, > + series_labels = None, > + x_labels = None, > + y_labels = None, > + x_bounds = None, > + y_bounds = None, > + series_colors = None): > + > + BarPlot.__init__(self, surface, data, width, height, background, border, > + display_values, grid, rounded_corners, stack, three_dimension, > + x_labels, y_labels, x_bounds, y_bounds, series_colors, HORZ) > + self.series_labels = series_labels > + > + def calc_vert_extents(self): > + self.calc_extents(VERT) > + if self.labels[HORZ] and not self.labels[VERT]: > + self.borders[HORZ] += 10 > + > + def draw_rectangle_bottom(self, x0, y0, x1, y1): > + self.context.arc(x0+5, y1-5, 5, math.pi/2, math.pi) > + self.context.line_to(x0, y0+5) > + self.context.arc(x0+5, y0+5, 5, -math.pi, -math.pi/2) > + self.context.line_to(x1, y0) > + self.context.line_to(x1, y1) > + self.context.line_to(x0+5, y1) > + self.context.close_path() > + > + def draw_rectangle_top(self, x0, y0, x1, y1): > + self.context.arc(x1-5, y0+5, 5, -math.pi/2, 0) > + self.context.line_to(x1, y1-5) > + self.context.arc(x1-5, y1-5, 5, 0, math.pi/2) > + self.context.line_to(x0, y1) > + self.context.line_to(x0, y0) > + self.context.line_to(x1, y0) > + self.context.close_path() > + > + def draw_rectangle(self, index, length, x0, y0, x1, y1): > + if length == 1: > + BarPlot.draw_rectangle(self, x0, y0, x1, y1) > + elif index == 0: > + self.draw_rectangle_bottom(x0, y0, x1, y1) > + elif index == length-1: > + self.draw_rectangle_top(x0, y0, x1, y1) > + else: > + self.context.rectangle(x0, y0, x1-x0, y1-y0) > + > + #TODO: Review BarPlot.render_grid code > + def render_grid(self): > + self.context.set_source_rgba(0.8, 0.8, 0.8) > + if self.labels[HORZ]: > + self.context.set_font_size(self.font_size * 0.8) > + step = (self.dimensions[HORZ] - 2*self.borders[HORZ] - self.value_label)/(len(self.labels[HORZ])-1) > + x = self.borders[HORZ] > + next_x = 0 > + for item in self.labels[HORZ]: > + width = self.context.text_extents(item)[2] > + if x - width/2 > next_x and x - width/2 > self.border: > + self.context.move_to(x, self.border) > + self.context.line_to(x, self.dimensions[VERT] - self.borders[VERT]) > + self.context.stroke() > + next_x = x + width/2 > + x += step > + else: > + lines = 11 > + horizontal_step = float(self.plot_dimensions[HORZ])/(lines-1) > + x = self.borders[HORZ] > + for y in xrange(0, lines): > + self.context.move_to(x, self.border) > + self.context.line_to(x, self.dimensions[VERT] - self.borders[VERT]) > + self.context.stroke() > + x += horizontal_step > + > + def render_horz_labels(self): > + step = (self.dimensions[HORZ] - 2*self.borders[HORZ])/(len(self.labels[HORZ])-1) > + x = self.borders[HORZ] > + next_x = 0 > + > + for item in self.labels[HORZ]: > + self.context.set_source_rgba(*self.label_color) > + width = self.context.text_extents(item)[2] > + if x - width/2 > next_x and x - width/2 > self.border: > + self.context.move_to(x - width/2, self.dimensions[VERT] - self.borders[VERT] + self.max_value[HORZ] + 3) > + self.context.show_text(item) > + next_x = x + width/2 > + x += step > + > + def render_vert_labels(self): > + series_length = len(self.labels[VERT]) > + step = (self.plot_dimensions[VERT] - (series_length + 1)*self.space)/(len(self.labels[VERT])) > + y = self.border + step/2 + self.space > + > + for item in self.labels[VERT]: > + self.context.set_source_rgba(*self.label_color) > + width, height = self.context.text_extents(item)[2:4] > + self.context.move_to(self.borders[HORZ] - width - 5, y + height/2) > + self.context.show_text(item) > + y += step + self.space > + self.labels[VERT].reverse() > + > + def render_values(self): > + self.context.set_source_rgba(*self.value_label_color) > + self.context.set_font_size(self.font_size * 0.8) > + if self.stack: > + for i,group in enumerate(self.series): > + value = sum(group.to_list()) > + height = self.context.text_extents(str(value))[3] > + x = self.borders[HORZ] + value*self.steps[HORZ] + 2 > + y = self.borders[VERT] + (i+0.5)*self.steps[VERT] + (i+1)*self.space + height/2 > + self.context.move_to(x, y) > + self.context.show_text(str(value)) > + else: > + for i,group in enumerate(self.series): > + inner_step = self.steps[VERT]/len(group) > + y0 = self.border + i*self.steps[VERT] + (i+1)*self.space > + for number,data in enumerate(group): > + height = self.context.text_extents(str(data.content))[3] > + self.context.move_to(self.borders[HORZ] + data.content*self.steps[HORZ] + 2, y0 + 0.5*inner_step + height/2, ) > + self.context.show_text(str(data.content)) > + y0 += inner_step > + > + def render_plot(self): > + if self.stack: > + for i,group in enumerate(self.series): > + x0 = self.borders[HORZ] > + y0 = self.borders[VERT] + i*self.steps[VERT] + (i+1)*self.space > + for number,data in enumerate(group): > + if self.series_colors[number][4] in ('radial','linear') : > + linear = cairo.LinearGradient( data.content*self.steps[HORZ]/2, y0, data.content*self.steps[HORZ]/2, y0 + self.steps[VERT] ) > + color = self.series_colors[number] > + linear.add_color_stop_rgba(0.0, 3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0,1.0) > + linear.add_color_stop_rgba(1.0, *color[:4]) > + self.context.set_source(linear) > + elif self.series_colors[number][4] == 'solid': > + self.context.set_source_rgba(*self.series_colors[number][:4]) > + if self.rounded_corners: > + self.draw_rectangle(number, len(group), x0, y0, x0+data.content*self.steps[HORZ], y0+self.steps[VERT]) > + self.context.fill() > + else: > + self.context.rectangle(x0, y0, data.content*self.steps[HORZ], self.steps[VERT]) > + self.context.fill() > + x0 += data.content*self.steps[HORZ] > + else: > + for i,group in enumerate(self.series): > + inner_step = self.steps[VERT]/len(group) > + x0 = self.borders[HORZ] > + y0 = self.border + i*self.steps[VERT] + (i+1)*self.space > + for number,data in enumerate(group): > + linear = cairo.LinearGradient(data.content*self.steps[HORZ]/2, y0, data.content*self.steps[HORZ]/2, y0 + inner_step) > + color = self.series_colors[number] > + linear.add_color_stop_rgba(0.0, 3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0,1.0) > + linear.add_color_stop_rgba(1.0, *color[:4]) > + self.context.set_source(linear) > + if self.rounded_corners and data.content != 0: > + BarPlot.draw_round_rectangle(self,x0, y0, x0 + data.content*self.steps[HORZ], y0 + inner_step) > + self.context.fill() > + else: > + self.context.rectangle(x0, y0, data.content*self.steps[HORZ], inner_step) > + self.context.fill() > + y0 += inner_step > + > +class VerticalBarPlot(BarPlot): > + def __init__(self, > + surface = None, > + data = None, > + width = 640, > + height = 480, > + background = "white light_gray", > + border = 0, > + display_values = False, > + grid = False, > + rounded_corners = False, > + stack = False, > + three_dimension = False, > + series_labels = None, > + x_labels = None, > + y_labels = None, > + x_bounds = None, > + y_bounds = None, > + series_colors = None): > + > + BarPlot.__init__(self, surface, data, width, height, background, border, > + display_values, grid, rounded_corners, stack, three_dimension, > + x_labels, y_labels, x_bounds, y_bounds, series_colors, VERT) > + self.series_labels = series_labels > + > + def calc_vert_extents(self): > + self.calc_extents(VERT) > + if self.labels[VERT] and not self.labels[HORZ]: > + self.borders[VERT] += 10 > + > + def draw_rectangle_bottom(self, x0, y0, x1, y1): > + self.context.move_to(x1,y1) > + self.context.arc(x1-5, y1-5, 5, 0, math.pi/2) > + self.context.line_to(x0+5, y1) > + self.context.arc(x0+5, y1-5, 5, math.pi/2, math.pi) > + self.context.line_to(x0, y0) > + self.context.line_to(x1, y0) > + self.context.line_to(x1, y1) > + self.context.close_path() > + > + def draw_rectangle_top(self, x0, y0, x1, y1): > + self.context.arc(x0+5, y0+5, 5, -math.pi, -math.pi/2) > + self.context.line_to(x1-5, y0) > + self.context.arc(x1-5, y0+5, 5, -math.pi/2, 0) > + self.context.line_to(x1, y1) > + self.context.line_to(x0, y1) > + self.context.line_to(x0, y0) > + self.context.close_path() > + > + def draw_rectangle(self, index, length, x0, y0, x1, y1): > + if length == 1: > + BarPlot.draw_rectangle(self, x0, y0, x1, y1) > + elif index == 0: > + self.draw_rectangle_bottom(x0, y0, x1, y1) > + elif index == length-1: > + self.draw_rectangle_top(x0, y0, x1, y1) > + else: > + self.context.rectangle(x0, y0, x1-x0, y1-y0) > + > + def render_grid(self): > + self.context.set_source_rgba(0.8, 0.8, 0.8) > + if self.labels[VERT]: > + lines = len(self.labels[VERT]) > + vertical_step = float(self.plot_dimensions[self.main_dir])/(lines-1) > + y = self.borders[VERT] + self.value_label > + else: > + lines = 11 > + vertical_step = float(self.plot_dimensions[self.main_dir])/(lines-1) > + y = 1.2*self.border + self.value_label > + for x in xrange(0, lines): > + self.context.move_to(self.borders[HORZ], y) > + self.context.line_to(self.dimensions[HORZ] - self.border, y) > + self.context.stroke() > + y += vertical_step > + > + def render_ground(self): > + self.draw_3d_rectangle_front(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], > + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) > + self.context.fill() > + > + self.draw_3d_rectangle_side (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], > + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) > + self.context.fill() > + > + self.draw_3d_rectangle_top (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], > + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) > + self.context.fill() > + > + def render_horz_labels(self): > + series_length = len(self.labels[HORZ]) > + step = float (self.plot_dimensions[HORZ] - (series_length + 1)*self.space)/len(self.labels[HORZ]) > + x = self.borders[HORZ] + step/2 + self.space > + next_x = 0 > + > + for item in self.labels[HORZ]: > + self.context.set_source_rgba(*self.label_color) > + width = self.context.text_extents(item)[2] > + if x - width/2 > next_x and x - width/2 > self.borders[HORZ]: > + self.context.move_to(x - width/2, self.dimensions[VERT] - self.borders[VERT] + self.max_value[HORZ] + 3) > + self.context.show_text(item) > + next_x = x + width/2 > + x += step + self.space > + > + def render_vert_labels(self): > + self.context.set_source_rgba(*self.label_color) > + y = self.borders[VERT] + self.value_label > + step = (self.dimensions[VERT] - 2*self.borders[VERT] - self.value_label)/(len(self.labels[VERT]) - 1) > + self.labels[VERT].reverse() > + for item in self.labels[VERT]: > + width, height = self.context.text_extents(item)[2:4] > + self.context.move_to(self.borders[HORZ] - width - 5, y + height/2) > + self.context.show_text(item) > + y += step > + self.labels[VERT].reverse() > + > + def render_values(self): > + self.context.set_source_rgba(*self.value_label_color) > + self.context.set_font_size(self.font_size * 0.8) > + if self.stack: > + for i,group in enumerate(self.series): > + value = sum(group.to_list()) > + width = self.context.text_extents(str(value))[2] > + x = self.borders[HORZ] + (i+0.5)*self.steps[HORZ] + (i+1)*self.space - width/2 > + y = value*self.steps[VERT] + 2 > + self.context.move_to(x, self.plot_top-y) > + self.context.show_text(str(value)) > + else: > + for i,group in enumerate(self.series): > + inner_step = self.steps[HORZ]/len(group) > + x0 = self.borders[HORZ] + i*self.steps[HORZ] + (i+1)*self.space > + for number,data in enumerate(group): > + width = self.context.text_extents(str(data.content))[2] > + self.context.move_to(x0 + 0.5*inner_step - width/2, self.plot_top - data.content*self.steps[VERT] - 2) > + self.context.show_text(str(data.content)) > + x0 += inner_step > + > + def render_plot(self): > + if self.stack: > + for i,group in enumerate(self.series): > + x0 = self.borders[HORZ] + i*self.steps[HORZ] + (i+1)*self.space > + y0 = 0 > + for number,data in enumerate(group): > + if self.series_colors[number][4] in ('linear','radial'): > + linear = cairo.LinearGradient( x0, data.content*self.steps[VERT]/2, x0 + self.steps[HORZ], data.content*self.steps[VERT]/2 ) > + color = self.series_colors[number] > + linear.add_color_stop_rgba(0.0, 3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0,1.0) > + linear.add_color_stop_rgba(1.0, *color[:4]) > + self.context.set_source(linear) > + elif self.series_colors[number][4] == 'solid': > + self.context.set_source_rgba(*self.series_colors[number][:4]) > + if self.rounded_corners: > + self.draw_rectangle(number, len(group), x0, self.plot_top - y0 - data.content*self.steps[VERT], x0 + self.steps[HORZ], self.plot_top - y0) > + self.context.fill() > + else: > + self.context.rectangle(x0, self.plot_top - y0 - data.content*self.steps[VERT], self.steps[HORZ], data.content*self.steps[VERT]) > + self.context.fill() > + y0 += data.content*self.steps[VERT] > + else: > + for i,group in enumerate(self.series): > + inner_step = self.steps[HORZ]/len(group) > + y0 = self.borders[VERT] > + x0 = self.borders[HORZ] + i*self.steps[HORZ] + (i+1)*self.space > + for number,data in enumerate(group): > + if self.series_colors[number][4] == 'linear': > + linear = cairo.LinearGradient( x0, data.content*self.steps[VERT]/2, x0 + inner_step, data.content*self.steps[VERT]/2 ) > + color = self.series_colors[number] > + linear.add_color_stop_rgba(0.0, 3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0,1.0) > + linear.add_color_stop_rgba(1.0, *color[:4]) > + self.context.set_source(linear) > + elif self.series_colors[number][4] == 'solid': > + self.context.set_source_rgba(*self.series_colors[number][:4]) > + if self.rounded_corners and data.content != 0: > + BarPlot.draw_round_rectangle(self, x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top) > + self.context.fill() > + elif self.three_dimension: > + self.draw_3d_rectangle_front(x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top, 5) > + self.context.fill() > + self.draw_3d_rectangle_side(x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top, 5) > + self.context.fill() > + self.draw_3d_rectangle_top(x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top, 5) > + self.context.fill() > + else: > + self.context.rectangle(x0, self.plot_top - data.content*self.steps[VERT], inner_step, data.content*self.steps[VERT]) > + self.context.fill() > + > + x0 += inner_step > + > +class StreamChart(VerticalBarPlot): > + def __init__(self, > + surface = None, > + data = None, > + width = 640, > + height = 480, > + background = "white light_gray", > + border = 0, > + grid = False, > + series_legend = None, > + x_labels = None, > + x_bounds = None, > + y_bounds = None, > + series_colors = None): > + > + VerticalBarPlot.__init__(self, surface, data, width, height, background, border, > + False, grid, False, True, False, > + None, x_labels, None, x_bounds, y_bounds, series_colors) > + > + def calc_steps(self): > + other_dir = other_direction(self.main_dir) > + self.series_amplitude = self.bounds[self.main_dir][1] - self.bounds[self.main_dir][0] > + if self.series_amplitude: > + self.steps[self.main_dir] = float(self.plot_dimensions[self.main_dir])/self.series_amplitude > + else: > + self.steps[self.main_dir] = 0.00 > + series_length = len(self.data) > + self.steps[other_dir] = float(self.plot_dimensions[other_dir])/series_length > + > + def render_legend(self): > + pass > + > + def ground(self, index): > + sum_values = sum(self.data[index]) > + return -0.5*sum_values > + > + def calc_angles(self): > + middle = self.plot_top - self.plot_dimensions[VERT]/2.0 > + self.angles = [tuple([0.0 for x in range(len(self.data)+1)])] > + for x_index in range(1, len(self.data)-1): > + t = [] > + x0 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ] > + x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ] > + y0 = middle - self.ground(x_index-1)*self.steps[VERT] > + y2 = middle - self.ground(x_index+1)*self.steps[VERT] > + t.append(math.atan(float(y0-y2)/(x0-x2))) > + for data_index in range(len(self.data[x_index])): > + x0 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ] > + x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ] > + y0 = middle - self.ground(x_index-1)*self.steps[VERT] - self.data[x_index-1][data_index]*self.steps[VERT] > + y2 = middle - self.ground(x_index+1)*self.steps[VERT] - self.data[x_index+1][data_index]*self.steps[VERT] > + > + for i in range(0,data_index): > + y0 -= self.data[x_index-1][i]*self.steps[VERT] > + y2 -= self.data[x_index+1][i]*self.steps[VERT] > + > + if data_index == len(self.data[0])-1 and False: > + self.context.set_source_rgba(0.0,0.0,0.0,0.3) > + self.context.move_to(x0,y0) > + self.context.line_to(x2,y2) > + self.context.stroke() > + self.context.arc(x0,y0,2,0,2*math.pi) > + self.context.fill() > + t.append(math.atan(float(y0-y2)/(x0-x2))) > + self.angles.append(tuple(t)) > + self.angles.append(tuple([0.0 for x in range(len(self.data)+1)])) > + > + def render_plot(self): > + self.calc_angles() > + middle = self.plot_top - self.plot_dimensions[VERT]/2.0 > + p = 0.4*self.steps[HORZ] > + for data_index in range(len(self.data[0])-1,-1,-1): > + self.context.set_source_rgba(*self.series_colors[data_index][:4]) > + > + #draw the upper line > + for x_index in range(len(self.data)-1) : > + x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ] > + y1 = middle - self.ground(x_index)*self.steps[VERT] - self.data[x_index][data_index]*self.steps[VERT] > + x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ] > + y2 = middle - self.ground(x_index + 1)*self.steps[VERT] - self.data[x_index + 1][data_index]*self.steps[VERT] > + > + for i in range(0,data_index): > + y1 -= self.data[x_index][i]*self.steps[VERT] > + y2 -= self.data[x_index+1][i]*self.steps[VERT] > + > + if x_index == 0: > + self.context.move_to(x1,y1) > + > + ang1 = self.angles[x_index][data_index+1] > + ang2 = self.angles[x_index+1][data_index+1] + math.pi > + self.context.curve_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1), > + x2+p*math.cos(ang2),y2+p*math.sin(ang2), > + x2,y2) > + > + for x_index in range(len(self.data)-1,0,-1) : > + x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ] > + y1 = middle - self.ground(x_index)*self.steps[VERT] > + x2 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ] > + y2 = middle - self.ground(x_index - 1)*self.steps[VERT] > + > + for i in range(0,data_index): > + y1 -= self.data[x_index][i]*self.steps[VERT] > + y2 -= self.data[x_index-1][i]*self.steps[VERT] > + > + if x_index == len(self.data)-1: > + self.context.line_to(x1,y1+2) > + > + #revert angles by pi degrees to take the turn back > + ang1 = self.angles[x_index][data_index] + math.pi > + ang2 = self.angles[x_index-1][data_index] > + self.context.curve_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1), > + x2+p*math.cos(ang2),y2+p*math.sin(ang2), > + x2,y2+2) > + > + self.context.close_path() > + self.context.fill() > + > + if False: > + self.context.move_to(self.borders[HORZ] + 0.5*self.steps[HORZ], middle) > + for x_index in range(len(self.data)-1) : > + x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ] > + y1 = middle - self.ground(x_index)*self.steps[VERT] - self.data[x_index][data_index]*self.steps[VERT] > + x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ] > + y2 = middle - self.ground(x_index + 1)*self.steps[VERT] - self.data[x_index + 1][data_index]*self.steps[VERT] > + > + for i in range(0,data_index): > + y1 -= self.data[x_index][i]*self.steps[VERT] > + y2 -= self.data[x_index+1][i]*self.steps[VERT] > + > + ang1 = self.angles[x_index][data_index+1] > + ang2 = self.angles[x_index+1][data_index+1] + math.pi > + self.context.set_source_rgba(1.0,0.0,0.0) > + self.context.arc(x1+p*math.cos(ang1),y1+p*math.sin(ang1),2,0,2*math.pi) > + self.context.fill() > + self.context.set_source_rgba(0.0,0.0,0.0) > + self.context.arc(x2+p*math.cos(ang2),y2+p*math.sin(ang2),2,0,2*math.pi) > + self.context.fill() > + '''self.context.set_source_rgba(0.0,0.0,0.0,0.3) > + self.context.arc(x2,y2,2,0,2*math.pi) > + self.context.fill()''' > + self.context.move_to(x1,y1) > + self.context.line_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1)) > + self.context.stroke() > + self.context.move_to(x2,y2) > + self.context.line_to(x2+p*math.cos(ang2),y2+p*math.sin(ang2)) > + self.context.stroke() > + if False: > + for x_index in range(len(self.data)-1,0,-1) : > + x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ] > + y1 = middle - self.ground(x_index)*self.steps[VERT] > + x2 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ] > + y2 = middle - self.ground(x_index - 1)*self.steps[VERT] > + > + for i in range(0,data_index): > + y1 -= self.data[x_index][i]*self.steps[VERT] > + y2 -= self.data[x_index-1][i]*self.steps[VERT] > + > + #revert angles by pi degrees to take the turn back > + ang1 = self.angles[x_index][data_index] + math.pi > + ang2 = self.angles[x_index-1][data_index] > + self.context.set_source_rgba(0.0,1.0,0.0) > + self.context.arc(x1+p*math.cos(ang1),y1+p*math.sin(ang1),2,0,2*math.pi) > + self.context.fill() > + self.context.set_source_rgba(0.0,0.0,1.0) > + self.context.arc(x2+p*math.cos(ang2),y2+p*math.sin(ang2),2,0,2*math.pi) > + self.context.fill() > + '''self.context.set_source_rgba(0.0,0.0,0.0,0.3) > + self.context.arc(x2,y2,2,0,2*math.pi) > + self.context.fill()''' > + self.context.move_to(x1,y1) > + self.context.line_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1)) > + self.context.stroke() > + self.context.move_to(x2,y2) > + self.context.line_to(x2+p*math.cos(ang2),y2+p*math.sin(ang2)) > + self.context.stroke() > + #break > + > + #self.context.arc(self.dimensions[HORZ]/2, self.dimensions[VERT]/2,50,0,3*math.pi/2) > + #self.context.fill() > + > + > +class PiePlot(Plot): > + #TODO: Check the old cairoplot, graphs aren't matching > + def __init__ (self, > + surface = None, > + data = None, > + width = 640, > + height = 480, > + background = "white light_gray", > + gradient = False, > + shadow = False, > + colors = None): > + > + Plot.__init__( self, surface, data, width, height, background, series_colors = colors ) > + self.center = (self.dimensions[HORZ]/2, self.dimensions[VERT]/2) > + self.total = sum( self.series.to_list() ) > + self.radius = min(self.dimensions[HORZ]/3,self.dimensions[VERT]/3) > + self.gradient = gradient > + self.shadow = shadow > + > + def sort_function(x,y): > + return x.content - y.content > + > + def load_series(self, data, x_labels=None, y_labels=None, series_colors=None): > + Plot.load_series(self, data, x_labels, y_labels, series_colors) > + # Already done inside series > + #self.data = sorted(self.data) > + > + def draw_piece(self, angle, next_angle): > + self.context.move_to(self.center[0],self.center[1]) > + self.context.line_to(self.center[0] + self.radius*math.cos(angle), self.center[1] + self.radius*math.sin(angle)) > + self.context.arc(self.center[0], self.center[1], self.radius, angle, next_angle) > + self.context.line_to(self.center[0], self.center[1]) > + self.context.close_path() > + > + def render(self): > + self.render_background() > + self.render_bounding_box() > + if self.shadow: > + self.render_shadow() > + self.render_plot() > + self.render_series_labels() > + > + def render_shadow(self): > + horizontal_shift = 3 > + vertical_shift = 3 > + self.context.set_source_rgba(0, 0, 0, 0.5) > + self.context.arc(self.center[0] + horizontal_shift, self.center[1] + vertical_shift, self.radius, 0, 2*math.pi) > + self.context.fill() > + > + def render_series_labels(self): > + angle = 0 > + next_angle = 0 > + x0,y0 = self.center > + cr = self.context > + for number,key in enumerate(self.series_labels): > + # self.data[number] should be just a number > + data = sum(self.series[number].to_list()) > + > + next_angle = angle + 2.0*math.pi*data/self.total > + cr.set_source_rgba(*self.series_colors[number][:4]) > + w = cr.text_extents(key)[2] > + if (angle + next_angle)/2 < math.pi/2 or (angle + next_angle)/2 > 3*math.pi/2: > + cr.move_to(x0 + (self.radius+10)*math.cos((angle+next_angle)/2), y0 + (self.radius+10)*math.sin((angle+next_angle)/2) ) > + else: > + cr.move_to(x0 + (self.radius+10)*math.cos((angle+next_angle)/2) - w, y0 + (self.radius+10)*math.sin((angle+next_angle)/2) ) > + cr.show_text(key) > + angle = next_angle > + > + def render_plot(self): > + angle = 0 > + next_angle = 0 > + x0,y0 = self.center > + cr = self.context > + for number,group in enumerate(self.series): > + # Group should be just a number > + data = sum(group.to_list()) > + next_angle = angle + 2.0*math.pi*data/self.total > + if self.gradient or self.series_colors[number][4] in ('linear','radial'): > + gradient_color = cairo.RadialGradient(self.center[0], self.center[1], 0, self.center[0], self.center[1], self.radius) > + gradient_color.add_color_stop_rgba(0.3, *self.series_colors[number][:4]) > + gradient_color.add_color_stop_rgba(1, self.series_colors[number][0]*0.7, > + self.series_colors[number][1]*0.7, > + self.series_colors[number][2]*0.7, > + self.series_colors[number][3]) > + cr.set_source(gradient_color) > + else: > + cr.set_source_rgba(*self.series_colors[number][:4]) > + > + self.draw_piece(angle, next_angle) > + cr.fill() > + > + cr.set_source_rgba(1.0, 1.0, 1.0) > + self.draw_piece(angle, next_angle) > + cr.stroke() > + > + angle = next_angle > + > +class DonutPlot(PiePlot): > + def __init__ (self, > + surface = None, > + data = None, > + width = 640, > + height = 480, > + background = "white light_gray", > + gradient = False, > + shadow = False, > + colors = None, > + inner_radius=-1): > + > + Plot.__init__( self, surface, data, width, height, background, series_colors = colors ) > + > + self.center = ( self.dimensions[HORZ]/2, self.dimensions[VERT]/2 ) > + self.total = sum( self.series.to_list() ) > + self.radius = min( self.dimensions[HORZ]/3,self.dimensions[VERT]/3 ) > + self.inner_radius = inner_radius*self.radius > + > + if inner_radius == -1: > + self.inner_radius = self.radius/3 > + > + self.gradient = gradient > + self.shadow = shadow > + > + def draw_piece(self, angle, next_angle): > + self.context.move_to(self.center[0] + (self.inner_radius)*math.cos(angle), self.center[1] + (self.inner_radius)*math.sin(angle)) > + self.context.line_to(self.center[0] + self.radius*math.cos(angle), self.center[1] + self.radius*math.sin(angle)) > + self.context.arc(self.center[0], self.center[1], self.radius, angle, next_angle) > + self.context.line_to(self.center[0] + (self.inner_radius)*math.cos(next_angle), self.center[1] + (self.inner_radius)*math.sin(next_angle)) > + self.context.arc_negative(self.center[0], self.center[1], self.inner_radius, next_angle, angle) > + self.context.close_path() > + > + def render_shadow(self): > + horizontal_shift = 3 > + vertical_shift = 3 > + self.context.set_source_rgba(0, 0, 0, 0.5) > + self.context.arc(self.center[0] + horizontal_shift, self.center[1] + vertical_shift, self.inner_radius, 0, 2*math.pi) > + self.context.arc_negative(self.center[0] + horizontal_shift, self.center[1] + vertical_shift, self.radius, 0, -2*math.pi) > + self.context.fill() > + > +class GanttChart (Plot) : > + def __init__(self, > + surface = None, > + data = None, > + width = 640, > + height = 480, > + x_labels = None, > + y_labels = None, > + colors = None): > + self.bounds = {} > + self.max_value = {} > + Plot.__init__(self, surface, data, width, height, x_labels = x_labels, y_labels = y_labels, series_colors = colors) > + > + def load_series(self, data, x_labels=None, y_labels=None, series_colors=None): > + Plot.load_series(self, data, x_labels, y_labels, series_colors) > + self.calc_boundaries() > + > + def calc_boundaries(self): > + self.bounds[HORZ] = (0,len(self.series)) > + end_pos = max(self.series.to_list()) > + > + #for group in self.series: > + # if hasattr(item, "__delitem__"): > + # for sub_item in item: > + # end_pos = max(sub_item) > + # else: > + # end_pos = max(item) > + self.bounds[VERT] = (0,end_pos) > + > + def calc_extents(self, direction): > + self.max_value[direction] = 0 > + if self.labels[direction]: > + self.max_value[direction] = max(self.context.text_extents(item)[2] for item in self.labels[direction]) > + else: > + self.max_value[direction] = self.context.text_extents( str(self.bounds[direction][1] + 1) )[2] > + > + def calc_horz_extents(self): > + self.calc_extents(HORZ) > + self.borders[HORZ] = 100 + self.max_value[HORZ] > + > + def calc_vert_extents(self): > + self.calc_extents(VERT) > + self.borders[VERT] = self.dimensions[VERT]/(self.bounds[HORZ][1] + 1) > + > + def calc_steps(self): > + self.horizontal_step = (self.dimensions[HORZ] - self.borders[HORZ])/(len(self.labels[VERT])) > + self.vertical_step = self.borders[VERT] > + > + def render(self): > + self.calc_horz_extents() > + self.calc_vert_extents() > + self.calc_steps() > + self.render_background() > + > + self.render_labels() > + self.render_grid() > + self.render_plot() > + > + def render_background(self): > + cr = self.context > + cr.set_source_rgba(255,255,255) > + cr.rectangle(0,0,self.dimensions[HORZ], self.dimensions[VERT]) > + cr.fill() > + for number,group in enumerate(self.series): > + linear = cairo.LinearGradient(self.dimensions[HORZ]/2, self.borders[VERT] + number*self.vertical_step, > + self.dimensions[HORZ]/2, self.borders[VERT] + (number+1)*self.vertical_step) > + linear.add_color_stop_rgba(0,1.0,1.0,1.0,1.0) > + linear.add_color_stop_rgba(1.0,0.9,0.9,0.9,1.0) > + cr.set_source(linear) > + cr.rectangle(0,self.borders[VERT] + number*self.vertical_step,self.dimensions[HORZ],self.vertical_step) > + cr.fill() > + > + def render_grid(self): > + cr = self.context > + cr.set_source_rgba(0.7, 0.7, 0.7) > + cr.set_dash((1,0,0,0,0,0,1)) > + cr.set_line_width(0.5) > + for number,label in enumerate(self.labels[VERT]): > + h = cr.text_extents(label)[3] > + cr.move_to(self.borders[HORZ] + number*self.horizontal_step, self.vertical_step/2 + h) > + cr.line_to(self.borders[HORZ] + number*self.horizontal_step, self.dimensions[VERT]) > + cr.stroke() > + > + def render_labels(self): > + self.context.set_font_size(0.02 * self.dimensions[HORZ]) > + > + self.render_horz_labels() > + self.render_vert_labels() > + > + def render_horz_labels(self): > + cr = self.context > + labels = self.labels[HORZ] > + if not labels: > + labels = [str(i) for i in range(1, self.bounds[HORZ][1] + 1) ] > + for number,label in enumerate(labels): > + if label != None: > + cr.set_source_rgba(0.5, 0.5, 0.5) > + w,h = cr.text_extents(label)[2], cr.text_extents(label)[3] > + cr.move_to(40,self.borders[VERT] + number*self.vertical_step + self.vertical_step/2 + h/2) > + cr.show_text(label) > + > + def render_vert_labels(self): > + cr = self.context > + labels = self.labels[VERT] > + if not labels: > + labels = [str(i) for i in range(1, self.bounds[VERT][1] + 1) ] > + for number,label in enumerate(labels): > + w,h = cr.text_extents(label)[2], cr.text_extents(label)[3] > + cr.move_to(self.borders[HORZ] + number*self.horizontal_step - w/2, self.vertical_step/2) > + cr.show_text(label) > + > + def render_rectangle(self, x0, y0, x1, y1, color): > + self.draw_shadow(x0, y0, x1, y1) > + self.draw_rectangle(x0, y0, x1, y1, color) > + > + def draw_rectangular_shadow(self, gradient, x0, y0, w, h): > + self.context.set_source(gradient) > + self.context.rectangle(x0,y0,w,h) > + self.context.fill() > + > + def draw_circular_shadow(self, x, y, radius, ang_start, ang_end, mult, shadow): > + gradient = cairo.RadialGradient(x, y, 0, x, y, 2*radius) > + gradient.add_color_stop_rgba(0, 0, 0, 0, shadow) > + gradient.add_color_stop_rgba(1, 0, 0, 0, 0) > + self.context.set_source(gradient) > + self.context.move_to(x,y) > + self.context.line_to(x + mult[0]*radius,y + mult[1]*radius) > + self.context.arc(x, y, 8, ang_start, ang_end) > + self.context.line_to(x,y) > + self.context.close_path() > + self.context.fill() > + > + def draw_rectangle(self, x0, y0, x1, y1, color): > + cr = self.context > + middle = (x0+x1)/2 > + linear = cairo.LinearGradient(middle,y0,middle,y1) > + linear.add_color_stop_rgba(0,3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0,1.0) > + linear.add_color_stop_rgba(1,*color[:4]) > + cr.set_source(linear) > + > + cr.arc(x0+5, y0+5, 5, 0, 2*math.pi) > + cr.arc(x1-5, y0+5, 5, 0, 2*math.pi) > + cr.arc(x0+5, y1-5, 5, 0, 2*math.pi) > + cr.arc(x1-5, y1-5, 5, 0, 2*math.pi) > + cr.rectangle(x0+5,y0,x1-x0-10,y1-y0) > + cr.rectangle(x0,y0+5,x1-x0,y1-y0-10) > + cr.fill() > + > + def draw_shadow(self, x0, y0, x1, y1): > + shadow = 0.4 > + h_mid = (x0+x1)/2 > + v_mid = (y0+y1)/2 > + h_linear_1 = cairo.LinearGradient(h_mid,y0-4,h_mid,y0+4) > + h_linear_2 = cairo.LinearGradient(h_mid,y1-4,h_mid,y1+4) > + v_linear_1 = cairo.LinearGradient(x0-4,v_mid,x0+4,v_mid) > + v_linear_2 = cairo.LinearGradient(x1-4,v_mid,x1+4,v_mid) > + > + h_linear_1.add_color_stop_rgba( 0, 0, 0, 0, 0) > + h_linear_1.add_color_stop_rgba( 1, 0, 0, 0, shadow) > + h_linear_2.add_color_stop_rgba( 0, 0, 0, 0, shadow) > + h_linear_2.add_color_stop_rgba( 1, 0, 0, 0, 0) > + v_linear_1.add_color_stop_rgba( 0, 0, 0, 0, 0) > + v_linear_1.add_color_stop_rgba( 1, 0, 0, 0, shadow) > + v_linear_2.add_color_stop_rgba( 0, 0, 0, 0, shadow) > + v_linear_2.add_color_stop_rgba( 1, 0, 0, 0, 0) > + > + self.draw_rectangular_shadow(h_linear_1,x0+4,y0-4,x1-x0-8,8) > + self.draw_rectangular_shadow(h_linear_2,x0+4,y1-4,x1-x0-8,8) > + self.draw_rectangular_shadow(v_linear_1,x0-4,y0+4,8,y1-y0-8) > + self.draw_rectangular_shadow(v_linear_2,x1-4,y0+4,8,y1-y0-8) > + > + self.draw_circular_shadow(x0+4, y0+4, 4, math.pi, 3*math.pi/2, (-1,0), shadow) > + self.draw_circular_shadow(x1-4, y0+4, 4, 3*math.pi/2, 2*math.pi, (0,-1), shadow) > + self.draw_circular_shadow(x0+4, y1-4, 4, math.pi/2, math.pi, (0,1), shadow) > + self.draw_circular_shadow(x1-4, y1-4, 4, 0, math.pi/2, (1,0), shadow) > + > + def render_plot(self): > + for index,group in enumerate(self.series): > + for data in group: > + self.render_rectangle(self.borders[HORZ] + data.content[0]*self.horizontal_step, > + self.borders[VERT] + index*self.vertical_step + self.vertical_step/4.0, > + self.borders[HORZ] + data.content[1]*self.horizontal_step, > + self.borders[VERT] + index*self.vertical_step + 3.0*self.vertical_step/4.0, > + self.series_colors[index]) > + > +# Function definition > + > +def scatter_plot(name, > + data = None, > + errorx = None, > + errory = None, > + width = 640, > + height = 480, > + background = "white light_gray", > + border = 0, > + axis = False, > + dash = False, > + discrete = False, > + dots = False, > + grid = False, > + series_legend = False, > + x_labels = None, > + y_labels = None, > + x_bounds = None, > + y_bounds = None, > + z_bounds = None, > + x_title = None, > + y_title = None, > + series_colors = None, > + circle_colors = None): > + > + ''' > + - Function to plot scatter data. > + > + - Parameters > + > + data - The values to be ploted might be passed in a two basic: > + list of points: [(0,0), (0,1), (0,2)] or [(0,0,1), (0,1,4), (0,2,1)] > + lists of coordinates: [ [0,0,0] , [0,1,2] ] or [ [0,0,0] , [0,1,2] , [1,4,1] ] > + Notice that these kinds of that can be grouped in order to form more complex data > + using lists of lists or dictionaries; > + series_colors - Define color values for each of the series > + circle_colors - Define a lower and an upper bound for the circle colors for variable radius > + (3 dimensions) series > + ''' > + > + plot = ScatterPlot( name, data, errorx, errory, width, height, background, border, > + axis, dash, discrete, dots, grid, series_legend, x_labels, y_labels, > + x_bounds, y_bounds, z_bounds, x_title, y_title, series_colors, circle_colors ) > + plot.render() > + plot.commit() > + > +def dot_line_plot(name, > + data, > + width, > + height, > + background = "white light_gray", > + border = 0, > + axis = False, > + dash = False, > + dots = False, > + grid = False, > + series_legend = False, > + x_labels = None, > + y_labels = None, > + x_bounds = None, > + y_bounds = None, > + x_title = None, > + y_title = None, > + series_colors = None): > + ''' > + - Function to plot graphics using dots and lines. > + > + dot_line_plot (name, data, width, height, background = "white light_gray", border = 0, axis = False, grid = False, x_labels = None, y_labels = None, x_bounds = None, y_bounds = None) > + > + - Parameters > + > + name - Name of the desired output file, no need to input the .svg as it will be added at runtim; > + data - The list, list of lists or dictionary holding the data to be plotted; > + width, height - Dimensions of the output image; > + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. > + If left None, a gray to white gradient will be generated; > + border - Distance in pixels of a square border into which the graphics will be drawn; > + axis - Whether or not the axis are to be drawn; > + dash - Boolean or a list or a dictionary of booleans indicating whether or not the associated series should be drawn in dashed mode; > + dots - Whether or not dots should be drawn on each point; > + grid - Whether or not the gris is to be drawn; > + series_legend - Whether or not the legend is to be drawn; > + x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis; > + x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted; > + x_title - Whether or not to plot a title over the x axis. > + y_title - Whether or not to plot a title over the y axis. > + > + - Examples of use > + > + data = [0, 1, 3, 8, 9, 0, 10, 10, 2, 1] > + CairoPlot.dot_line_plot('teste', data, 400, 300) > + > + data = { "john" : [10, 10, 10, 10, 30], "mary" : [0, 0, 3, 5, 15], "philip" : [13, 32, 11, 25, 2] } > + x_labels = ["jan/2008", "feb/2008", "mar/2008", "apr/2008", "may/2008" ] > + CairoPlot.dot_line_plot( 'test', data, 400, 300, axis = True, grid = True, > + series_legend = True, x_labels = x_labels ) > + ''' > + plot = DotLinePlot( name, data, width, height, background, border, > + axis, dash, dots, grid, series_legend, x_labels, y_labels, > + x_bounds, y_bounds, x_title, y_title, series_colors ) > + plot.render() > + plot.commit() > + > +def function_plot(name, > + data, > + width, > + height, > + background = "white light_gray", > + border = 0, > + axis = True, > + dots = False, > + discrete = False, > + grid = False, > + series_legend = False, > + x_labels = None, > + y_labels = None, > + x_bounds = None, > + y_bounds = None, > + x_title = None, > + y_title = None, > + series_colors = None, > + step = 1): > + > + ''' > + - Function to plot functions. > + > + function_plot(name, data, width, height, background = "white light_gray", border = 0, axis = True, grid = False, dots = False, x_labels = None, y_labels = None, x_bounds = None, y_bounds = None, step = 1, discrete = False) > + > + - Parameters > + > + name - Name of the desired output file, no need to input the .svg as it will be added at runtim; > + data - The list, list of lists or dictionary holding the data to be plotted; > + width, height - Dimensions of the output image; > + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. > + If left None, a gray to white gradient will be generated; > + border - Distance in pixels of a square border into which the graphics will be drawn; > + axis - Whether or not the axis are to be drawn; > + grid - Whether or not the gris is to be drawn; > + dots - Whether or not dots should be shown at each point; > + x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis; > + x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted; > + step - the horizontal distance from one point to the other. The smaller, the smoother the curve will be; > + discrete - whether or not the function should be plotted in discrete format. > + > + - Example of use > + > + data = lambda x : x**2 > + CairoPlot.function_plot('function4', data, 400, 300, grid = True, x_bounds=(-10,10), step = 0.1) > + ''' > + > + plot = FunctionPlot( name, data, width, height, background, border, > + axis, discrete, dots, grid, series_legend, x_labels, y_labels, > + x_bounds, y_bounds, x_title, y_title, series_colors, step ) > + plot.render() > + plot.commit() > + > +def pie_plot( name, data, width, height, background = "white light_gray", gradient = False, shadow = False, colors = None ): > + > + ''' > + - Function to plot pie graphics. > + > + pie_plot(name, data, width, height, background = "white light_gray", gradient = False, colors = None) > + > + - Parameters > + > + name - Name of the desired output file, no need to input the .svg as it will be added at runtim; > + data - The list, list of lists or dictionary holding the data to be plotted; > + width, height - Dimensions of the output image; > + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. > + If left None, a gray to white gradient will be generated; > + gradient - Whether or not the pie color will be painted with a gradient; > + shadow - Whether or not there will be a shadow behind the pie; > + colors - List of slices colors. > + > + - Example of use > + > + teste_data = {"john" : 123, "mary" : 489, "philip" : 890 , "suzy" : 235} > + CairoPlot.pie_plot("pie_teste", teste_data, 500, 500) > + ''' > + > + plot = PiePlot( name, data, width, height, background, gradient, shadow, colors ) > + plot.render() > + plot.commit() > + > +def donut_plot(name, data, width, height, background = "white light_gray", gradient = False, shadow = False, colors = None, inner_radius = -1): > + > + ''' > + - Function to plot donut graphics. > + > + donut_plot(name, data, width, height, background = "white light_gray", gradient = False, inner_radius = -1) > + > + - Parameters > + > + name - Name of the desired output file, no need to input the .svg as it will be added at runtim; > + data - The list, list of lists or dictionary holding the data to be plotted; > + width, height - Dimensions of the output image; > + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. > + If left None, a gray to white gradient will be generated; > + shadow - Whether or not there will be a shadow behind the donut; > + gradient - Whether or not the donut color will be painted with a gradient; > + colors - List of slices colors; > + inner_radius - The radius of the donut's inner circle. > + > + - Example of use > + > + teste_data = {"john" : 123, "mary" : 489, "philip" : 890 , "suzy" : 235} > + CairoPlot.donut_plot("donut_teste", teste_data, 500, 500) > + ''' > + > + plot = DonutPlot(name, data, width, height, background, gradient, shadow, colors, inner_radius) > + plot.render() > + plot.commit() > + > +def gantt_chart(name, pieces, width, height, x_labels, y_labels, colors): > + > + ''' > + - Function to generate Gantt Charts. > + > + gantt_chart(name, pieces, width, height, x_labels, y_labels, colors): > + > + - Parameters > + > + name - Name of the desired output file, no need to input the .svg as it will be added at runtim; > + pieces - A list defining the spaces to be drawn. The user must pass, for each line, the index of its start and the index of its end. If a line must have two or more spaces, they must be passed inside a list; > + width, height - Dimensions of the output image; > + x_labels - A list of names for each of the vertical lines; > + y_labels - A list of names for each of the horizontal spaces; > + colors - List containing the colors expected for each of the horizontal spaces > + > + - Example of use > + > + pieces = [ (0.5,5.5) , [(0,4),(6,8)] , (5.5,7) , (7,8)] > + x_labels = [ 'teste01', 'teste02', 'teste03', 'teste04'] > + y_labels = [ '0001', '0002', '0003', '0004', '0005', '0006', '0007', '0008', '0009', '0010' ] > + colors = [ (1.0, 0.0, 0.0), (1.0, 0.7, 0.0), (1.0, 1.0, 0.0), (0.0, 1.0, 0.0) ] > + CairoPlot.gantt_chart('gantt_teste', pieces, 600, 300, x_labels, y_labels, colors) > + ''' > + > + plot = GanttChart(name, pieces, width, height, x_labels, y_labels, colors) > + plot.render() > + plot.commit() > + > +def vertical_bar_plot(name, > + data, > + width, > + height, > + background = "white light_gray", > + border = 0, > + display_values = False, > + grid = False, > + rounded_corners = False, > + stack = False, > + three_dimension = False, > + series_labels = None, > + x_labels = None, > + y_labels = None, > + x_bounds = None, > + y_bounds = None, > + colors = None): > + #TODO: Fix docstring for vertical_bar_plot > + ''' > + - Function to generate vertical Bar Plot Charts. > + > + bar_plot(name, data, width, height, background, border, grid, rounded_corners, three_dimension, > + x_labels, y_labels, x_bounds, y_bounds, colors): > + > + - Parameters > + > + name - Name of the desired output file, no need to input the .svg as it will be added at runtime; > + data - The list, list of lists or dictionary holding the data to be plotted; > + width, height - Dimensions of the output image; > + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. > + If left None, a gray to white gradient will be generated; > + border - Distance in pixels of a square border into which the graphics will be drawn; > + grid - Whether or not the gris is to be drawn; > + rounded_corners - Whether or not the bars should have rounded corners; > + three_dimension - Whether or not the bars should be drawn in pseudo 3D; > + x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis; > + x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted; > + colors - List containing the colors expected for each of the bars. > + > + - Example of use > + > + data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] > + CairoPlot.vertical_bar_plot ('bar2', data, 400, 300, border = 20, grid = True, rounded_corners = False) > + ''' > + > + plot = VerticalBarPlot(name, data, width, height, background, border, > + display_values, grid, rounded_corners, stack, three_dimension, > + series_labels, x_labels, y_labels, x_bounds, y_bounds, colors) > + plot.render() > + plot.commit() > + > +def horizontal_bar_plot(name, > + data, > + width, > + height, > + background = "white light_gray", > + border = 0, > + display_values = False, > + grid = False, > + rounded_corners = False, > + stack = False, > + three_dimension = False, > + series_labels = None, > + x_labels = None, > + y_labels = None, > + x_bounds = None, > + y_bounds = None, > + colors = None): > + > + #TODO: Fix docstring for horizontal_bar_plot > + ''' > + - Function to generate Horizontal Bar Plot Charts. > + > + bar_plot(name, data, width, height, background, border, grid, rounded_corners, three_dimension, > + x_labels, y_labels, x_bounds, y_bounds, colors): > + > + - Parameters > + > + name - Name of the desired output file, no need to input the .svg as it will be added at runtime; > + data - The list, list of lists or dictionary holding the data to be plotted; > + width, height - Dimensions of the output image; > + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. > + If left None, a gray to white gradient will be generated; > + border - Distance in pixels of a square border into which the graphics will be drawn; > + grid - Whether or not the gris is to be drawn; > + rounded_corners - Whether or not the bars should have rounded corners; > + three_dimension - Whether or not the bars should be drawn in pseudo 3D; > + x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis; > + x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted; > + colors - List containing the colors expected for each of the bars. > + > + - Example of use > + > + data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] > + CairoPlot.bar_plot ('bar2', data, 400, 300, border = 20, grid = True, rounded_corners = False) > + ''' > + > + plot = HorizontalBarPlot(name, data, width, height, background, border, > + display_values, grid, rounded_corners, stack, three_dimension, > + series_labels, x_labels, y_labels, x_bounds, y_bounds, colors) > + plot.render() > + plot.commit() > + > +def stream_chart(name, > + data, > + width, > + height, > + background = "white light_gray", > + border = 0, > + grid = False, > + series_legend = None, > + x_labels = None, > + x_bounds = None, > + y_bounds = None, > + colors = None): > + > + #TODO: Fix docstring for horizontal_bar_plot > + plot = StreamChart(name, data, width, height, background, border, > + grid, series_legend, x_labels, x_bounds, y_bounds, colors) > + plot.render() > + plot.commit() > + > + > +if __name__ == "__main__": > + import tests > + import seriestests > diff --git a/bindings/python/examples/python2/output_format_modules/pprint_table.py b/bindings/python/examples/python2/output_format_modules/pprint_table.py > new file mode 100644 > index 0000000..3a63d62 > --- /dev/null > +++ b/bindings/python/examples/python2/output_format_modules/pprint_table.py > @@ -0,0 +1,37 @@ > +# pprint_table.py > +# > +# This module is used to pretty-print a table > +# Adapted from > +# http://ginstrom.com/scribbles/2007/09/04/pretty-printing-a-table-in-python/ > + > +import sys > + > +def get_max_width(table, index): > + """Get the maximum width of the given column index""" > + > + return max([len(str(row[index])) for row in table]) > + > + > +def pprint_table(table, nbLeft=1, out=sys.stdout): > + """ > + Prints out a table of data, padded for alignment > + @param table: The table to print. A list of lists. > + Each row must have the same number of columns. > + @param nbLeft: The number of columns aligned left > + @param out: Output stream (file-like object) > + """ > + > + col_paddings = [] > + > + for i in range(len(table[0])): > + col_paddings.append(get_max_width(table, i)) > + > + for row in table: > + # left cols > + for i in range(nbLeft): > + print >> out, str(row[i]).ljust(col_paddings[i] + 1), > + # rest of the cols > + for i in range(nbLeft, len(row)): > + col = str(row[i]).rjust(col_paddings[i] + 2) > + print >> out, col, > + print >> out > diff --git a/bindings/python/examples/python2/output_format_modules/series.py b/bindings/python/examples/python2/output_format_modules/series.py > new file mode 100644 > index 0000000..8e8b236 > --- /dev/null > +++ b/bindings/python/examples/python2/output_format_modules/series.py > @@ -0,0 +1,1140 @@ > +#!/usr/bin/env python > +# -*- coding: utf-8 -*- > + > +# Serie.py > +# > +# Copyright (c) 2008 Magnun Leno da Silva > +# > +# Author: Magnun Leno da Silva > +# > +# This program is free software; you can redistribute it and/or > +# modify it under the terms of the GNU Lesser General Public License > +# as published by the Free Software Foundation; either version 2 of > +# the License, or (at your option) any later version. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU Lesser General Public > +# License along with this program; if not, write to the Free Software > +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 > +# USA > + > +# Contributor: Rodrigo Moreiro Araujo > + > +#import cairoplot > +import doctest > + > +NUMTYPES = (int, float, long) > +LISTTYPES = (list, tuple) > +STRTYPES = (str, unicode) > +FILLING_TYPES = ['linear', 'solid', 'gradient'] > +DEFAULT_COLOR_FILLING = 'solid' > +#TODO: Define default color list > +DEFAULT_COLOR_LIST = None > + > +class Data(object): > + ''' > + Class that models the main data structure. > + It can hold: > + - a number type (int, float or long) > + - a tuple, witch represents a point and can have 2 or 3 items (x,y,z) > + - if a list is passed it will be converted to a tuple. > + > + obs: In case a tuple is passed it will convert to tuple > + ''' > + def __init__(self, data=None, name=None, parent=None): > + ''' > + Starts main atributes from the Data class > + @name - Name for each point; > + @content - The real data, can be an int, float, long or tuple, which > + represents a point (x,y) or (x,y,z); > + @parent - A pointer that give the data access to it's parent. > + > + Usage: > + >>> d = Data(name='empty'); print d > + empty: () > + >>> d = Data((1,1),'point a'); print d > + point a: (1, 1) > + >>> d = Data((1,2,3),'point b'); print d > + point b: (1, 2, 3) > + >>> d = Data([2,3],'point c'); print d > + point c: (2, 3) > + >>> d = Data(12, 'simple value'); print d > + simple value: 12 > + ''' > + # Initial values > + self.__content = None > + self.__name = None > + > + # Setting passed values > + self.parent = parent > + self.name = name > + self.content = data > + > + # Name property > + @apply > + def name(): > + doc = ''' > + Name is a read/write property that controls the input of name. > + - If passed an invalid value it cleans the name with None > + > + Usage: > + >>> d = Data(13); d.name = 'name_test'; print d > + name_test: 13 > + >>> d.name = 11; print d > + 13 > + >>> d.name = 'other_name'; print d > + other_name: 13 > + >>> d.name = None; print d > + 13 > + >>> d.name = 'last_name'; print d > + last_name: 13 > + >>> d.name = ''; print d > + 13 > + ''' > + def fget(self): > + ''' > + returns the name as a string > + ''' > + return self.__name > + > + def fset(self, name): > + ''' > + Sets the name of the Data > + ''' > + if type(name) in STRTYPES and len(name) > 0: > + self.__name = name > + else: > + self.__name = None > + > + > + > + return property(**locals()) > + > + # Content property > + @apply > + def content(): > + doc = ''' > + Content is a read/write property that validate the data passed > + and return it. > + > + Usage: > + >>> d = Data(); d.content = 13; d.content > + 13 > + >>> d = Data(); d.content = (1,2); d.content > + (1, 2) > + >>> d = Data(); d.content = (1,2,3); d.content > + (1, 2, 3) > + >>> d = Data(); d.content = [1,2,3]; d.content > + (1, 2, 3) > + >>> d = Data(); d.content = [1.5,.2,3.3]; d.content > + (1.5, 0.20000000000000001, 3.2999999999999998) > + ''' > + def fget(self): > + ''' > + Return the content of Data > + ''' > + return self.__content > + > + def fset(self, data): > + ''' > + Ensures that data is a valid tuple/list or a number (int, float > + or long) > + ''' > + # Type: None > + if data is None: > + self.__content = None > + return > + > + # Type: Int or Float > + elif type(data) in NUMTYPES: > + self.__content = data > + > + # Type: List or Tuple > + elif type(data) in LISTTYPES: > + # Ensures the correct size > + if len(data) not in (2, 3): > + raise TypeError, "Data (as list/tuple) must have 2 or 3 items" > + return > + > + # Ensures that all items in list/tuple is a number > + isnum = lambda x : type(x) not in NUMTYPES > + > + if max(map(isnum, data)): > + # An item in data isn't an int or a float > + raise TypeError, "All content of data must be a number (int or float)" > + > + # Convert the tuple to list > + if type(data) is list: > + data = tuple(data) > + > + # Append a copy and sets the type > + self.__content = data[:] > + > + # Unknown type! > + else: > + self.__content = None > + raise TypeError, "Data must be an int, float or a tuple with two or three items" > + return > + > + return property(**locals()) > + > + > + def clear(self): > + ''' > + Clear the all Data (content, name and parent) > + ''' > + self.content = None > + self.name = None > + self.parent = None > + > + def copy(self): > + ''' > + Returns a copy of the Data structure > + ''' > + # The copy > + new_data = Data() > + if self.content is not None: > + # If content is a point > + if type(self.content) is tuple: > + new_data.__content = self.content[:] > + > + # If content is a number > + else: > + new_data.__content = self.content > + > + # If it has a name > + if self.name is not None: > + new_data.__name = self.name > + > + return new_data > + > + def __str__(self): > + ''' > + Return a string representation of the Data structure > + ''' > + if self.name is None: > + if self.content is None: > + return '' > + return str(self.content) > + else: > + if self.content is None: > + return self.name+": ()" > + return self.name+": "+str(self.content) > + > + def __len__(self): > + ''' > + Return the length of the Data. > + - If it's a number return 1; > + - If it's a list return it's length; > + - If its None return 0. > + ''' > + if self.content is None: > + return 0 > + elif type(self.content) in NUMTYPES: > + return 1 > + return len(self.content) > + > + > + > + > +class Group(object): > + ''' > + Class that models a group of data. Every value (int, float, long, tuple > + or list) passed is converted to a list of Data. > + It can receive: > + - A single number (int, float, long); > + - A list of numbers; > + - A tuple of numbers; > + - An instance of Data; > + - A list of Data; > + > + Obs: If a tuple with 2 or 3 items is passed it is converted to a point. > + If a tuple with only 1 item is passed it's converted to a number; > + If a tuple with more than 2 items is passed it's converted to a > + list of numbers > + ''' > + def __init__(self, group=None, name=None, parent=None): > + ''' > + Starts main atributes in Group instance. > + @data_list - a list of data which forms the group; > + @range - a range that represent the x axis of possible functions; > + @name - name of the data group; > + @parent - the Serie parent of this group. > + > + Usage: > + >>> g = Group(13, 'simple number'); print g > + simple number ['13'] > + >>> g = Group((1,2), 'simple point'); print g > + simple point ['(1, 2)'] > + >>> g = Group([1,2,3,4], 'list of numbers'); print g > + list of numbers ['1', '2', '3', '4'] > + >>> g = Group((1,2,3,4),'int in tuple'); print g > + int in tuple ['1', '2', '3', '4'] > + >>> g = Group([(1,2),(2,3),(3,4)], 'list of points'); print g > + list of points ['(1, 2)', '(2, 3)', '(3, 4)'] > + >>> g = Group([[1,2,3],[1,2,3]], '2D coordinate lists'); print g > + 2D coordinated lists ['(1, 1)', '(2, 2)', '(3, 3)'] > + >>> g = Group([[1,2],[1,2],[1,2]], '3D coordinate lists'); print g > + 3D coordinated lists ['(1, 1, 1)', '(2, 2, 2)'] > + ''' > + # Initial values > + self.__data_list = [] > + self.__range = [] > + self.__name = None > + > + > + self.parent = parent > + self.name = name > + self.data_list = group > + > + # Name property > + @apply > + def name(): > + doc = ''' > + Name is a read/write property that controls the input of name. > + - If passed an invalid value it cleans the name with None > + > + Usage: > + >>> g = Group(13); g.name = 'name_test'; print g > + name_test ['13'] > + >>> g.name = 11; print g > + ['13'] > + >>> g.name = 'other_name'; print g > + other_name ['13'] > + >>> g.name = None; print g > + ['13'] > + >>> g.name = 'last_name'; print g > + last_name ['13'] > + >>> g.name = ''; print g > + ['13'] > + ''' > + def fget(self): > + ''' > + Returns the name as a string > + ''' > + return self.__name > + > + def fset(self, name): > + ''' > + Sets the name of the Group > + ''' > + if type(name) in STRTYPES and len(name) > 0: > + self.__name = name > + else: > + self.__name = None > + > + return property(**locals()) > + > + # data_list property > + @apply > + def data_list(): > + doc = ''' > + The data_list is a read/write property that can be a list of > + numbers, a list of points or a list of 2 or 3 coordinate lists. This > + property uses mainly the self.add_data method. > + > + Usage: > + >>> g = Group(); g.data_list = 13; print g > + ['13'] > + >>> g.data_list = (1,2); print g > + ['(1, 2)'] > + >>> g.data_list = Data((1,2),'point a'); print g > + ['point a: (1, 2)'] > + >>> g.data_list = [1,2,3]; print g > + ['1', '2', '3'] > + >>> g.data_list = (1,2,3,4); print g > + ['1', '2', '3', '4'] > + >>> g.data_list = [(1,2),(2,3),(3,4)]; print g > + ['(1, 2)', '(2, 3)', '(3, 4)'] > + >>> g.data_list = [[1,2],[1,2]]; print g > + ['(1, 1)', '(2, 2)'] > + >>> g.data_list = [[1,2],[1,2],[1,2]]; print g > + ['(1, 1, 1)', '(2, 2, 2)'] > + >>> g.range = (10); g.data_list = lambda x:x**2; print g > + ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 4.0)', '(3.0, 9.0)', '(4.0, 16.0)', '(5.0, 25.0)', '(6.0, 36.0)', '(7.0, 49.0)', '(8.0, 64.0)', '(9.0, 81.0)'] > + ''' > + def fget(self): > + ''' > + Returns the value of data_list > + ''' > + return self.__data_list > + > + def fset(self, group): > + ''' > + Ensures that group is valid. > + ''' > + # None > + if group is None: > + self.__data_list = [] > + > + # Int/float/long or Instance of Data > + elif type(group) in NUMTYPES or isinstance(group, Data): > + # Clean data_list > + self.__data_list = [] > + self.add_data(group) > + > + # One point > + elif type(group) is tuple and len(group) in (2,3): > + self.__data_list = [] > + self.add_data(group) > + > + # list of items > + elif type(group) in LISTTYPES and type(group[0]) is not list: > + # Clean data_list > + self.__data_list = [] > + for item in group: > + # try to append and catch an exception > + self.add_data(item) > + > + # function lambda > + elif callable(group): > + # Explicit is better than implicit > + function = group > + # Has range > + if len(self.range) is not 0: > + # Clean data_list > + self.__data_list = [] > + # Generate values for the lambda function > + for x in self.range: > + #self.add_data((x,round(group(x),2))) > + self.add_data((x,function(x))) > + > + # Only have range in parent > + elif self.parent is not None and len(self.parent.range) is not 0: > + # Copy parent range > + self.__range = self.parent.range[:] > + # Clean data_list > + self.__data_list = [] > + # Generate values for the lambda function > + for x in self.range: > + #self.add_data((x,round(group(x),2))) > + self.add_data((x,function(x))) > + > + # Don't have range anywhere > + else: > + # x_data don't exist > + raise Exception, "Data argument is valid but to use function type please set x_range first" > + > + # Coordinate Lists > + elif type(group) in LISTTYPES and type(group[0]) is list: > + # Clean data_list > + self.__data_list = [] > + data = [] > + if len(group) == 3: > + data = zip(group[0], group[1], group[2]) > + elif len(group) == 2: > + data = zip(group[0], group[1]) > + else: > + raise TypeError, "Only one list of coordinates was received." > + > + for item in data: > + self.add_data(item) > + > + else: > + raise TypeError, "Group type not supported" > + > + return property(**locals()) > + > + @apply > + def range(): > + doc = ''' > + The range is a read/write property that generates a range of values > + for the x axis of the functions. When passed a tuple it almost works > + like the built-in range funtion: > + - 1 item, represent the end of the range started from 0; > + - 2 items, represents the start and the end, respectively; > + - 3 items, the last one represents the step; > + > + When passed a list the range function understands as a valid range. > + > + Usage: > + >>> g = Group(); g.range = 10; print g.range > + [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] > + >>> g = Group(); g.range = (5); print g.range > + [0.0, 1.0, 2.0, 3.0, 4.0] > + >>> g = Group(); g.range = (1,7); print g.range > + [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] > + >>> g = Group(); g.range = (0,10,2); print g.range > + [0.0, 2.0, 4.0, 6.0, 8.0] > + >>> > + >>> g = Group(); g.range = [0]; print g.range > + [0.0] > + >>> g = Group(); g.range = [0,10,20]; print g.range > + [0.0, 10.0, 20.0] > + ''' > + def fget(self): > + ''' > + Returns the range > + ''' > + return self.__range > + > + def fset(self, x_range): > + ''' > + Controls the input of a valid type and generate the range > + ''' > + # if passed a simple number convert to tuple > + if type(x_range) in NUMTYPES: > + x_range = (x_range,) > + > + # A list, just convert to float > + if type(x_range) is list and len(x_range) > 0: > + # Convert all to float > + x_range = map(float, x_range) > + # Prevents repeated values and convert back to list > + self.__range = list(set(x_range[:])) > + # Sort the list to ascending order > + self.__range.sort() > + > + # A tuple, must check the lengths and generate the values > + elif type(x_range) is tuple and len(x_range) in (1,2,3): > + # Convert all to float > + x_range = map(float, x_range) > + > + # Inital values > + start = 0.0 > + step = 1.0 > + end = 0.0 > + > + # Only the end and it can't be less or iqual to 0 > + if len(x_range) is 1 and x_range > 0: > + end = x_range[0] > + > + # The start and the end but the start must be less then the end > + elif len(x_range) is 2 and x_range[0] < x_range[1]: > + start = x_range[0] > + end = x_range[1] > + > + # All 3, but the start must be less then the end > + elif x_range[0] <= x_range[1]: > + start = x_range[0] > + end = x_range[1] > + step = x_range[2] > + > + # Starts the range > + self.__range = [] > + # Generate the range > + # Can't use the range function because it doesn't support float values > + while start < end: > + self.__range.append(start) > + start += step > + > + # Incorrect type > + else: > + raise Exception, "x_range must be a list with one or more items or a tuple with 2 or 3 items" > + > + return property(**locals()) > + > + def add_data(self, data, name=None): > + ''' > + Append a new data to the data_list. > + - If data is an instance of Data, append it > + - If it's an int, float, tuple or list create an instance of Data and append it > + > + Usage: > + >>> g = Group() > + >>> g.add_data(12); print g > + ['12'] > + >>> g.add_data(7,'other'); print g > + ['12', 'other: 7'] > + >>> > + >>> g = Group() > + >>> g.add_data((1,1),'a'); print g > + ['a: (1, 1)'] > + >>> g.add_data((2,2),'b'); print g > + ['a: (1, 1)', 'b: (2, 2)'] > + >>> > + >>> g.add_data(Data((1,2),'c')); print g > + ['a: (1, 1)', 'b: (2, 2)', 'c: (1, 2)'] > + ''' > + if not isinstance(data, Data): > + # Try to convert > + data = Data(data,name,self) > + > + if data.content is not None: > + self.__data_list.append(data.copy()) > + self.__data_list[-1].parent = self > + > + > + def to_list(self): > + ''' > + Returns the group as a list of numbers (int, float or long) or a > + list of tuples (points 2D or 3D). > + > + Usage: > + >>> g = Group([1,2,3,4],'g1'); g.to_list() > + [1, 2, 3, 4] > + >>> g = Group([(1,2),(2,3),(3,4)],'g2'); g.to_list() > + [(1, 2), (2, 3), (3, 4)] > + >>> g = Group([(1,2,3),(3,4,5)],'g2'); g.to_list() > + [(1, 2, 3), (3, 4, 5)] > + ''' > + return [data.content for data in self] > + > + def copy(self): > + ''' > + Returns a copy of this group > + ''' > + new_group = Group() > + new_group.__name = self.__name > + if self.__range is not None: > + new_group.__range = self.__range[:] > + for data in self: > + new_group.add_data(data.copy()) > + return new_group > + > + def get_names(self): > + ''' > + Return a list with the names of all data in this group > + ''' > + names = [] > + for data in self: > + if data.name is None: > + names.append('Data '+str(data.index()+1)) > + else: > + names.append(data.name) > + return names > + > + > + def __str__ (self): > + ''' > + Returns a string representing the Group > + ''' > + ret = "" > + if self.name is not None: > + ret += self.name + " " > + if len(self) > 0: > + list_str = [str(item) for item in self] > + ret += str(list_str) > + else: > + ret += "[]" > + return ret > + > + def __getitem__(self, key): > + ''' > + Makes a Group iterable, based in the data_list property > + ''' > + return self.data_list[key] > + > + def __len__(self): > + ''' > + Returns the length of the Group, based in the data_list property > + ''' > + return len(self.data_list) > + > + > +class Colors(object): > + ''' > + Class that models the colors its labels (names) and its properties, RGB > + and filling type. > + > + It can receive: > + - A list where each item is a list with 3 or 4 items. The > + first 3 items represent the RGB values and the last argument > + defines the filling type. The list will be converted to a dict > + and each color will receve a name based in its position in the > + list. > + - A dictionary where each key will be the color name and its item > + can be a list with 3 or 4 items. The first 3 items represent > + the RGB colors and the last argument defines the filling type. > + ''' > + def __init__(self, color_list=None): > + ''' > + Start the color_list property > + @ color_list - the list or dict contaning the colors properties. > + ''' > + self.__color_list = None > + > + self.color_list = color_list > + > + @apply > + def color_list(): > + doc = ''' > + >>> c = Colors([[1,1,1],[2,2,2,'linear'],[3,3,3,'gradient']]) > + >>> print c.color_list > + {'Color 2': [2, 2, 2, 'linear'], 'Color 3': [3, 3, 3, 'gradient'], 'Color 1': [1, 1, 1, 'solid']} > + >>> c.color_list = [[1,1,1],(2,2,2,'solid'),(3,3,3,'linear')] > + >>> print c.color_list > + {'Color 2': [2, 2, 2, 'solid'], 'Color 3': [3, 3, 3, 'linear'], 'Color 1': [1, 1, 1, 'solid']} > + >>> c.color_list = {'a':[1,1,1],'b':(2,2,2,'solid'),'c':(3,3,3,'linear'), 'd':(4,4,4)} > + >>> print c.color_list > + {'a': [1, 1, 1, 'solid'], 'c': [3, 3, 3, 'linear'], 'b': [2, 2, 2, 'solid'], 'd': [4, 4, 4, 'solid']} > + ''' > + def fget(self): > + ''' > + Return the color list > + ''' > + return self.__color_list > + > + def fset(self, color_list): > + ''' > + Format the color list to a dictionary > + ''' > + if color_list is None: > + self.__color_list = None > + return > + > + if type(color_list) in LISTTYPES and type(color_list[0]) in LISTTYPES: > + old_color_list = color_list[:] > + color_list = {} > + for index, color in enumerate(old_color_list): > + if len(color) is 3 and max(map(type, color)) in NUMTYPES: > + color_list['Color '+str(index+1)] = list(color)+[DEFAULT_COLOR_FILLING] > + elif len(color) is 4 and max(map(type, color[:-1])) in NUMTYPES and color[-1] in FILLING_TYPES: > + color_list['Color '+str(index+1)] = list(color) > + else: > + raise TypeError, "Unsuported color format" > + elif type(color_list) is not dict: > + raise TypeError, "Unsuported color format" > + > + for name, color in color_list.items(): > + if len(color) is 3: > + if max(map(type, color)) in NUMTYPES: > + color_list[name] = list(color)+[DEFAULT_COLOR_FILLING] > + else: > + raise TypeError, "Unsuported color format" > + elif len(color) is 4: > + if max(map(type, color[:-1])) in NUMTYPES and color[-1] in FILLING_TYPES: > + color_list[name] = list(color) > + else: > + raise TypeError, "Unsuported color format" > + self.__color_list = color_list.copy() > + > + return property(**locals()) > + > + > +class Series(object): > + ''' > + Class that models a Series (group of groups). Every value (int, float, > + long, tuple or list) passed is converted to a list of Group or Data. > + It can receive: > + - a single number or point, will be converted to a Group of one Data; > + - a list of numbers, will be converted to a group of numbers; > + - a list of tuples, will converted to a single Group of points; > + - a list of lists of numbers, each 'sublist' will be converted to a > + group of numbers; > + - a list of lists of tuples, each 'sublist' will be converted to a > + group of points; > + - a list of lists of lists, the content of the 'sublist' will be > + processed as coordinated lists and the result will be converted to > + a group of points; > + - a Dictionary where each item can be the same of the list: number, > + point, list of numbers, list of points or list of lists (coordinated > + lists); > + - an instance of Data; > + - an instance of group. > + ''' > + def __init__(self, series=None, name=None, property=[], colors=None): > + ''' > + Starts main atributes in Group instance. > + @series - a list, dict of data of which the series is composed; > + @name - name of the series; > + @property - a list/dict of properties to be used in the plots of > + this Series > + > + Usage: > + >>> print Series([1,2,3,4]) > + ["Group 1 ['1', '2', '3', '4']"] > + >>> print Series([[1,2,3],[4,5,6]]) > + ["Group 1 ['1', '2', '3']", "Group 2 ['4', '5', '6']"] > + >>> print Series((1,2)) > + ["Group 1 ['(1, 2)']"] > + >>> print Series([(1,2),(2,3)]) > + ["Group 1 ['(1, 2)', '(2, 3)']"] > + >>> print Series([[(1,2),(2,3)],[(4,5),(5,6)]]) > + ["Group 1 ['(1, 2)', '(2, 3)']", "Group 2 ['(4, 5)', '(5, 6)']"] > + >>> print Series([[[1,2,3],[1,2,3],[1,2,3]]]) > + ["Group 1 ['(1, 1, 1)', '(2, 2, 2)', '(3, 3, 3)']"] > + >>> print Series({'g1':[1,2,3], 'g2':[4,5,6]}) > + ["g1 ['1', '2', '3']", "g2 ['4', '5', '6']"] > + >>> print Series({'g1':[(1,2),(2,3)], 'g2':[(4,5),(5,6)]}) > + ["g1 ['(1, 2)', '(2, 3)']", "g2 ['(4, 5)', '(5, 6)']"] > + >>> print Series({'g1':[[1,2],[1,2]], 'g2':[[4,5],[4,5]]}) > + ["g1 ['(1, 1)', '(2, 2)']", "g2 ['(4, 4)', '(5, 5)']"] > + >>> print Series(Data(1,'d1')) > + ["Group 1 ['d1: 1']"] > + >>> print Series(Group([(1,2),(2,3)],'g1')) > + ["g1 ['(1, 2)', '(2, 3)']"] > + ''' > + # Intial values > + self.__group_list = [] > + self.__name = None > + self.__range = None > + > + # TODO: Implement colors with filling > + self.__colors = None > + > + self.name = name > + self.group_list = series > + self.colors = colors > + > + # Name property > + @apply > + def name(): > + doc = ''' > + Name is a read/write property that controls the input of name. > + - If passed an invalid value it cleans the name with None > + > + Usage: > + >>> s = Series(13); s.name = 'name_test'; print s > + name_test ["Group 1 ['13']"] > + >>> s.name = 11; print s > + ["Group 1 ['13']"] > + >>> s.name = 'other_name'; print s > + other_name ["Group 1 ['13']"] > + >>> s.name = None; print s > + ["Group 1 ['13']"] > + >>> s.name = 'last_name'; print s > + last_name ["Group 1 ['13']"] > + >>> s.name = ''; print s > + ["Group 1 ['13']"] > + ''' > + def fget(self): > + ''' > + Returns the name as a string > + ''' > + return self.__name > + > + def fset(self, name): > + ''' > + Sets the name of the Group > + ''' > + if type(name) in STRTYPES and len(name) > 0: > + self.__name = name > + else: > + self.__name = None > + > + return property(**locals()) > + > + > + > + # Colors property > + @apply > + def colors(): > + doc = ''' > + >>> s = Series() > + >>> s.colors = [[1,1,1],[2,2,2,'linear'],[3,3,3,'gradient']] > + >>> print s.colors > + {'Color 2': [2, 2, 2, 'linear'], 'Color 3': [3, 3, 3, 'gradient'], 'Color 1': [1, 1, 1, 'solid']} > + >>> s.colors = [[1,1,1],(2,2,2,'solid'),(3,3,3,'linear')] > + >>> print s.colors > + {'Color 2': [2, 2, 2, 'solid'], 'Color 3': [3, 3, 3, 'linear'], 'Color 1': [1, 1, 1, 'solid']} > + >>> s.colors = {'a':[1,1,1],'b':(2,2,2,'solid'),'c':(3,3,3,'linear'), 'd':(4,4,4)} > + >>> print s.colors > + {'a': [1, 1, 1, 'solid'], 'c': [3, 3, 3, 'linear'], 'b': [2, 2, 2, 'solid'], 'd': [4, 4, 4, 'solid']} > + ''' > + def fget(self): > + ''' > + Return the color list > + ''' > + return self.__colors.color_list > + > + def fset(self, colors): > + ''' > + Format the color list to a dictionary > + ''' > + self.__colors = Colors(colors) > + > + return property(**locals()) > + > + @apply > + def range(): > + doc = ''' > + The range is a read/write property that generates a range of values > + for the x axis of the functions. When passed a tuple it almost works > + like the built-in range funtion: > + - 1 item, represent the end of the range started from 0; > + - 2 items, represents the start and the end, respectively; > + - 3 items, the last one represents the step; > + > + When passed a list the range function understands as a valid range. > + > + Usage: > + >>> s = Series(); s.range = 10; print s.range > + [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] > + >>> s = Series(); s.range = (5); print s.range > + [0.0, 1.0, 2.0, 3.0, 4.0, 5.0] > + >>> s = Series(); s.range = (1,7); print s.range > + [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] > + >>> s = Series(); s.range = (0,10,2); print s.range > + [0.0, 2.0, 4.0, 6.0, 8.0, 10.0] > + >>> > + >>> s = Series(); s.range = [0]; print s.range > + [0.0] > + >>> s = Series(); s.range = [0,10,20]; print s.range > + [0.0, 10.0, 20.0] > + ''' > + def fget(self): > + ''' > + Returns the range > + ''' > + return self.__range > + > + def fset(self, x_range): > + ''' > + Controls the input of a valid type and generate the range > + ''' > + # if passed a simple number convert to tuple > + if type(x_range) in NUMTYPES: > + x_range = (x_range,) > + > + # A list, just convert to float > + if type(x_range) is list and len(x_range) > 0: > + # Convert all to float > + x_range = map(float, x_range) > + # Prevents repeated values and convert back to list > + self.__range = list(set(x_range[:])) > + # Sort the list to ascending order > + self.__range.sort() > + > + # A tuple, must check the lengths and generate the values > + elif type(x_range) is tuple and len(x_range) in (1,2,3): > + # Convert all to float > + x_range = map(float, x_range) > + > + # Inital values > + start = 0.0 > + step = 1.0 > + end = 0.0 > + > + # Only the end and it can't be less or iqual to 0 > + if len(x_range) is 1 and x_range > 0: > + end = x_range[0] > + > + # The start and the end but the start must be lesser then the end > + elif len(x_range) is 2 and x_range[0] < x_range[1]: > + start = x_range[0] > + end = x_range[1] > + > + # All 3, but the start must be lesser then the end > + elif x_range[0] < x_range[1]: > + start = x_range[0] > + end = x_range[1] > + step = x_range[2] > + > + # Starts the range > + self.__range = [] > + # Generate the range > + # Cnat use the range function becouse it don't suport float values > + while start <= end: > + self.__range.append(start) > + start += step > + > + # Incorrect type > + else: > + raise Exception, "x_range must be a list with one or more item or a tuple with 2 or 3 items" > + > + return property(**locals()) > + > + @apply > + def group_list(): > + doc = ''' > + The group_list is a read/write property used to pre-process the list > + of Groups. > + It can be: > + - a single number, point or lambda, will be converted to a single > + Group of one Data; > + - a list of numbers, will be converted to a group of numbers; > + - a list of tuples, will converted to a single Group of points; > + - a list of lists of numbers, each 'sublist' will be converted to > + a group of numbers; > + - a list of lists of tuples, each 'sublist' will be converted to a > + group of points; > + - a list of lists of lists, the content of the 'sublist' will be > + processed as coordinated lists and the result will be converted > + to a group of points; > + - a list of lambdas, each lambda represents a Group; > + - a Dictionary where each item can be the same of the list: number, > + point, list of numbers, list of points, list of lists > + (coordinated lists) or lambdas > + - an instance of Data; > + - an instance of group. > + > + Usage: > + >>> s = Series() > + >>> s.group_list = [1,2,3,4]; print s > + ["Group 1 ['1', '2', '3', '4']"] > + >>> s.group_list = [[1,2,3],[4,5,6]]; print s > + ["Group 1 ['1', '2', '3']", "Group 2 ['4', '5', '6']"] > + >>> s.group_list = (1,2); print s > + ["Group 1 ['(1, 2)']"] > + >>> s.group_list = [(1,2),(2,3)]; print s > + ["Group 1 ['(1, 2)', '(2, 3)']"] > + >>> s.group_list = [[(1,2),(2,3)],[(4,5),(5,6)]]; print s > + ["Group 1 ['(1, 2)', '(2, 3)']", "Group 2 ['(4, 5)', '(5, 6)']"] > + >>> s.group_list = [[[1,2,3],[1,2,3],[1,2,3]]]; print s > + ["Group 1 ['(1, 1, 1)', '(2, 2, 2)', '(3, 3, 3)']"] > + >>> s.group_list = [(0.5,5.5) , [(0,4),(6,8)] , (5.5,7) , (7,9)]; print s > + ["Group 1 ['(0.5, 5.5)']", "Group 2 ['(0, 4)', '(6, 8)']", "Group 3 ['(5.5, 7)']", "Group 4 ['(7, 9)']"] > + >>> s.group_list = {'g1':[1,2,3], 'g2':[4,5,6]}; print s > + ["g1 ['1', '2', '3']", "g2 ['4', '5', '6']"] > + >>> s.group_list = {'g1':[(1,2),(2,3)], 'g2':[(4,5),(5,6)]}; print s > + ["g1 ['(1, 2)', '(2, 3)']", "g2 ['(4, 5)', '(5, 6)']"] > + >>> s.group_list = {'g1':[[1,2],[1,2]], 'g2':[[4,5],[4,5]]}; print s > + ["g1 ['(1, 1)', '(2, 2)']", "g2 ['(4, 4)', '(5, 5)']"] > + >>> s.range = 10 > + >>> s.group_list = lambda x:x*2 > + >>> s.group_list = [lambda x:x*2, lambda x:x**2, lambda x:x**3]; print s > + ["Group 1 ['(0.0, 0.0)', '(1.0, 2.0)', '(2.0, 4.0)', '(3.0, 6.0)', '(4.0, 8.0)', '(5.0, 10.0)', '(6.0, 12.0)', '(7.0, 14.0)', '(8.0, 16.0)', '(9.0, 18.0)', '(10.0, 20.0)']", "Group 2 ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 4.0)', '(3.0, 9.0)', '(4.0, 16.0)', '(5.0, 25.0)', '(6.0, 36.0)', '(7.0, 49.0)', '(8.0, 64.0)', '(9.0, 81.0)', '(10.0, 100.0)']", "Group 3 ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 8.0)', '(3.0, 27.0)', '(4.0, 64.0)', '(5.0, 125.0)', '(6.0, 216.0)', '(7.0, 343.0)', '(8.0, 512.0)', '(9.0, 729.0)', '(10.0, 1000.0)']"] > + >>> s.group_list = {'linear':lambda x:x*2, 'square':lambda x:x**2, 'cubic':lambda x:x**3}; print s > + ["cubic ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 8.0)', '(3.0, 27.0)', '(4.0, 64.0)', '(5.0, 125.0)', '(6.0, 216.0)', '(7.0, 343.0)', '(8.0, 512.0)', '(9.0, 729.0)', '(10.0, 1000.0)']", "linear ['(0.0, 0.0)', '(1.0, 2.0)', '(2.0, 4.0)', '(3.0, 6.0)', '(4.0, 8.0)', '(5.0, 10.0)', '(6.0, 12.0)', '(7.0, 14.0)', '(8.0, 16.0)', '(9.0, 18.0)', '(10.0, 20.0)']", "square ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 4.0)', '(3.0, 9.0)', '(4.0, 16.0)', '(5.0, 25.0)', '(6.0, 36.0)', '(7.0, 49.0)', '(8.0, 64.0)', '(9.0, 81.0)', '(10.0, 100.0)']"] > + >>> s.group_list = Data(1,'d1'); print s > + ["Group 1 ['d1: 1']"] > + >>> s.group_list = Group([(1,2),(2,3)],'g1'); print s > + ["g1 ['(1, 2)', '(2, 3)']"] > + ''' > + def fget(self): > + ''' > + Return the group list. > + ''' > + return self.__group_list > + > + def fset(self, series): > + ''' > + Controls the input of a valid group list. > + ''' > + #TODO: Add support to the following strem of data: [ (0.5,5.5) , [(0,4),(6,8)] , (5.5,7) , (7,9)] > + > + # Type: None > + if series is None: > + self.__group_list = [] > + > + # List or Tuple > + elif type(series) in LISTTYPES: > + self.__group_list = [] > + > + is_function = lambda x: callable(x) > + # Groups > + if list in map(type, series) or max(map(is_function, series)): > + for group in series: > + self.add_group(group) > + > + # single group > + else: > + self.add_group(series) > + > + #old code > + ## List of numbers > + #if type(series[0]) in NUMTYPES or type(series[0]) is tuple: > + # print series > + # self.add_group(series) > + # > + ## List of anything else > + #else: > + # for group in series: > + # self.add_group(group) > + > + # Dict representing series of groups > + elif type(series) is dict: > + self.__group_list = [] > + names = series.keys() > + names.sort() > + for name in names: > + self.add_group(Group(series[name],name,self)) > + > + # A single lambda > + elif callable(series): > + self.__group_list = [] > + self.add_group(series) > + > + # Int/float, instance of Group or Data > + elif type(series) in NUMTYPES or isinstance(series, Group) or isinstance(series, Data): > + self.__group_list = [] > + self.add_group(series) > + > + # Default > + else: > + raise TypeError, "Serie type not supported" > + > + return property(**locals()) > + > + def add_group(self, group, name=None): > + ''' > + Append a new group in group_list > + ''' > + if not isinstance(group, Group): > + #Try to convert > + group = Group(group, name, self) > + > + if len(group.data_list) is not 0: > + # Auto naming groups > + if group.name is None: > + group.name = "Group "+str(len(self.__group_list)+1) > + > + self.__group_list.append(group) > + self.__group_list[-1].parent = self > + > + def copy(self): > + ''' > + Returns a copy of the Series > + ''' > + new_series = Series() > + new_series.__name = self.__name > + if self.__range is not None: > + new_series.__range = self.__range[:] > + #Add color property in the copy method > + #self.__colors = None > + > + for group in self: > + new_series.add_group(group.copy()) > + > + return new_series > + > + def get_names(self): > + ''' > + Returns a list of the names of all groups in the Serie > + ''' > + names = [] > + for group in self: > + if group.name is None: > + names.append('Group '+str(group.index()+1)) > + else: > + names.append(group.name) > + > + return names > + > + def to_list(self): > + ''' > + Returns a list with the content of all groups and data > + ''' > + big_list = [] > + for group in self: > + for data in group: > + if type(data.content) in NUMTYPES: > + big_list.append(data.content) > + else: > + big_list = big_list + list(data.content) > + return big_list > + > + def __getitem__(self, key): > + ''' > + Makes the Series iterable, based in the group_list property > + ''' > + return self.__group_list[key] > + > + def __str__(self): > + ''' > + Returns a string that represents the Series > + ''' > + ret = "" > + if self.name is not None: > + ret += self.name + " " > + if len(self) > 0: > + list_str = [str(item) for item in self] > + ret += str(list_str) > + else: > + ret += "[]" > + return ret > + > + def __len__(self): > + ''' > + Returns the length of the Series, based in the group_lsit property > + ''' > + return len(self.group_list) > + > + > +if __name__ == '__main__': > + doctest.testmod() > diff --git a/bindings/python/examples/python2/softirqtimes.py b/bindings/python/examples/python2/softirqtimes.py > new file mode 100755 > index 0000000..59905c1 > --- /dev/null > +++ b/bindings/python/examples/python2/softirqtimes.py > @@ -0,0 +1,154 @@ > +#!/usr/bin/env python2 > +# softirqtimes.py > +# > +# Babeltrace time of softirqs example script > +# > +# Copyright 2012 EfficiOS Inc. > +# > +# Author: Danny Serres > +# > +# Permission is hereby granted, free of charge, to any person obtaining a copy > +# of this software and associated documentation files (the "Software"), to deal > +# in the Software without restriction, including without limitation the rights > +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > +# copies of the Software, and to permit persons to whom the Software is > +# furnished to do so, subject to the following conditions: > +# > +# The above copyright notice and this permission notice shall be included in > +# all copies or substantial portions of the Software. > + > +# The script checks the number of events in the trace > +# and outputs a table and a .svg histogram for the specified > +# range (microseconds) or the total trace if no range specified. > +# The graph is generated using the cairoplot module. > + > +# The script checks the trace for the amount of time > +# spent from each softirq_raise to softirq_exit. > +# It prints out the min, max (with timestamp), > +# average times, the standard deviation and the total count. > +# Using the cairoplot module, a .svg graph is also outputted > +# showing the taken time in function of the time since the > +# beginning of the trace. > + > +import sys, math > +from output_format_modules import cairoplot > +from babeltrace import * > + > +if len(sys.argv) < 2: > + raise TypeError("Usage: python softirqtimes.py path/to/trace") > + > +ctx = Context() > +ret = ctx.add_trace(sys.argv[1], "ctf") > +if ret is None: > + raise IOError("Error adding trace") > + > +time_taken = [] > +graph_data = [] > +max_time = (0.0, 0.0) # (val, ts) > + > +# tmp template: {(cpu_id, vec):TS raise} > +tmp = {} > +largest_val = 0 > + > +# Setting iterator > +bp = IterPos(SEEK_BEGIN) > +ctf_it = ctf.Iterator(ctx, bp) > + > +# Reading events > +event = ctf_it.read_event() > +start_time = event.get_timestamp() > +while(event is not None): > + > + event_name = event.get_name() > + error = True > + appendNext = False > + > + if event_name == 'softirq_raise' or event_name == 'softirq_exit': > + # Recover cpu_id and vec values to make a key to tmp > + error = False > + scope = event.get_top_level_scope(ctf.scope.STREAM_PACKET_CONTEXT) > + field = event.get_field(scope, "cpu_id") > + cpu_id = field.get_uint64() > + if ctf.field_error(): > + print("ERROR: Missing cpu_id info for {}".format( > + event.get_name())) > + error = True > + > + scope = event.get_top_level_scope(ctf.scope.EVENT_FIELDS) > + field = event.get_field(scope, "_vec") > + vec = field.get_uint64() > + if ctf.field_error(): > + print("ERROR: Missing vec info for {}".format( > + event.get_name())) > + error = True > + key = (cpu_id, vec) > + > + if event_name == 'softirq_raise' and not error: > + # Add timestamp to tmp > + if key in tmp: > + # If key already exists > + i = 0 > + while True: > + # Add index > + key = (cpu_id, vec, i) > + if key in tmp: > + i += 1 > + continue > + if i > largest_val: > + largest_val = i > + break > + > + tmp[key] = event.get_timestamp() > + > + if event_name == 'softirq_exit' and not error: > + # Saving data for output > + # Key check > + if not (key in tmp): > + i = 0 > + while i <= largest_val: > + key = (key[0], key[1], i) > + if key in tmp: > + break > + i += 1 > + > + raise_timestamp = tmp[key] > + time_data = event.get_timestamp() - tmp.pop(key) > + if time_data > max_time[0]: > + # max_time = (val, ts) > + max_time = (time_data, raise_timestamp) > + time_taken.append(time_data) > + graph_data.append((raise_timestamp - start_time, time_data)) > + > + # Next Event > + ret = ctf_it.next() > + if ret < 0: > + break > + event = ctf_it.read_event() > + > + > +del ctf_it > + > +# Standard dev. calc. > +try: > + mean = sum(time_taken)/float(len(time_taken)) > +except ZeroDivisionError: > + raise TypeError("empty data") > +deviations_squared = [] > +for x in time_taken: > + deviations_squared.append(math.pow((x - mean), 2)) > +try: > + stddev = math.sqrt(sum(deviations_squared) / (len(deviations_squared) - 1)) > +except ZeroDivisionError: > + stddev = '-' > + > +# Terminal output > +print("AVG TIME: {} ns".format(mean)) > +print("MIN TIME: {} ns".format(min(time_taken))) > +print("MAX TIME: {} ns, TS: {}".format(max_time[0], max_time[1])) > +print("STD DEV: {}".format(stddev)) > +print("TOTAL COUNT: {}".format(len(time_taken))) > + > +# Graph output > +cairoplot.scatter_plot ( 'softirqtimes.svg', data = graph_data, > + width = 5000, height = 4000, border = 20, axis = True, > + grid = True, series_colors = ["red"] ) > diff --git a/bindings/python/examples/python2/syscalls_by_pid.py b/bindings/python/examples/python2/syscalls_by_pid.py > new file mode 100755 > index 0000000..cf1d581 > --- /dev/null > +++ b/bindings/python/examples/python2/syscalls_by_pid.py > @@ -0,0 +1,85 @@ > +#!/usr/bin/env python2 > +# syscall_by_pid.py > +# > +# Babeltrace syscall by pid example script > +# > +# Copyright 2012 EfficiOS Inc. > +# > +# Author: Danny Serres > +# > +# Permission is hereby granted, free of charge, to any person obtaining a copy > +# of this software and associated documentation files (the "Software"), to deal > +# in the Software without restriction, including without limitation the rights > +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > +# copies of the Software, and to permit persons to whom the Software is > +# furnished to do so, subject to the following conditions: > +# > +# The above copyright notice and this permission notice shall be included in > +# all copies or substantial portions of the Software. > + > +# The script checks the number of events in the trace > +# and outputs a table and a .svg histogram for the specified > +# range (microseconds) or the total trace if no range specified. > +# The graph is generated using the cairoplot module. > + > +# The script checks all syscall in the trace and prints a list > +# showing the number of systemcalls executed by each PID > +# ordered from greatest to least number of syscalls. > +# The trace needs PID context (lttng add-context -k -t pid) > + > +import sys > +from babeltrace import * > +from output_format_modules.pprint_table import pprint_table as pprint > + > +if len(sys.argv) < 2 : > + raise TypeError("Usage: python syscalls_by_pid.py path/to/trace") > + > +ctx = Context() > +ret = ctx.add_trace(sys.argv[1], "ctf") > +if ret is None: > + raise IOError("Error adding trace") > + > +data = {} > + > +# Setting iterator > +bp = IterPos(SEEK_BEGIN) > +ctf_it = ctf.Iterator(ctx, bp) > + > +# Reading events > +event = ctf_it.read_event() > +while event is not None: > + if event.get_name().find("sys") >= 0: > + # Getting scope definition > + sco = event.get_top_level_scope(ctf.scope.STREAM_EVENT_CONTEXT) > + if sco is None: > + print("ERROR: Cannot get definition scope for {}".format( > + event.get_name())) > + else: > + # Getting PID > + pid_field = event.get_field(sco, "_pid") > + pid = pid_field.get_int64() > + > + if ctf.field_error(): > + print("ERROR: Missing PID info for sched_switch".format( > + event.get_name())) > + elif pid in data: > + data[pid] += 1 > + else: > + data[pid] = 1 > + # Next event > + ret = ctf_it.next() > + if ret < 0: > + break > + event = ctf_it.read_event() > + > +del ctf_it > + > +# Setting table for output > +table = [] > +for item in data: > + table.append([data[item], item]) # [count, pid] > +table.sort(reverse = True) # [big count first, pid] > +for i in range(len(table)): > + table[i].reverse() # [pid, big count first] > +table.insert(0, ["PID", "SYSCALL COUNT"]) > +pprint(table) > diff --git a/bindings/python/examples/sched_switch.py b/bindings/python/examples/sched_switch.py > old mode 100644 > new mode 100755 > index 7ae834b..d5ed25b > --- a/bindings/python/examples/sched_switch.py > +++ b/bindings/python/examples/sched_switch.py > @@ -1,18 +1,19 @@ > +#!/usr/bin/env python3 > # sched_switch.py > -# > +# > # Babeltrace example script with sched_switch events > -# > +# > # Copyright 2012 EfficiOS Inc. > -# > +# > # Author: Danny Serres > -# > +# > # Permission is hereby granted, free of charge, to any person obtaining a copy > # of this software and associated documentation files (the "Software"), to deal > # in the Software without restriction, including without limitation the rights > # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > # copies of the Software, and to permit persons to whom the Software is > # furnished to do so, subject to the following conditions: > -# > +# > # The above copyright notice and this permission notice shall be included in > # all copies or substantial portions of the Software. > > diff --git a/bindings/python/examples/softirqtimes.py b/bindings/python/examples/softirqtimes.py > deleted file mode 100644 > index 903bf3e..0000000 > --- a/bindings/python/examples/softirqtimes.py > +++ /dev/null > @@ -1,153 +0,0 @@ > -# softirqtimes.py > -# > -# Babeltrace time of softirqs example script > -# > -# Copyright 2012 EfficiOS Inc. > -# > -# Author: Danny Serres > -# > -# Permission is hereby granted, free of charge, to any person obtaining a copy > -# of this software and associated documentation files (the "Software"), to deal > -# in the Software without restriction, including without limitation the rights > -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > -# copies of the Software, and to permit persons to whom the Software is > -# furnished to do so, subject to the following conditions: > -# > -# The above copyright notice and this permission notice shall be included in > -# all copies or substantial portions of the Software. > - > -# The script checks the number of events in the trace > -# and outputs a table and a .svg histogram for the specified > -# range (microseconds) or the total trace if no range specified. > -# The graph is generated using the cairoplot module. > - > -# The script checks the trace for the amount of time > -# spent from each softirq_raise to softirq_exit. > -# It prints out the min, max (with timestamp), > -# average times, the standard deviation and the total count. > -# Using the cairoplot module, a .svg graph is also outputted > -# showing the taken time in function of the time since the > -# beginning of the trace. > - > -import sys, math > -from output_format_modules import cairoplot > -from babeltrace import * > - > -if len(sys.argv) < 2: > - raise TypeError("Usage: python softirqtimes.py path/to/trace") > - > -ctx = Context() > -ret = ctx.add_trace(sys.argv[1], "ctf") > -if ret is None: > - raise IOError("Error adding trace") > - > -time_taken = [] > -graph_data = [] > -max_time = (0.0, 0.0) # (val, ts) > - > -# tmp template: {(cpu_id, vec):TS raise} > -tmp = {} > -largest_val = 0 > - > -# Setting iterator > -bp = IterPos(SEEK_BEGIN) > -ctf_it = ctf.Iterator(ctx, bp) > - > -# Reading events > -event = ctf_it.read_event() > -start_time = event.get_timestamp() > -while(event is not None): > - > - event_name = event.get_name() > - error = True > - appendNext = False > - > - if event_name == 'softirq_raise' or event_name == 'softirq_exit': > - # Recover cpu_id and vec values to make a key to tmp > - error = False > - scope = event.get_top_level_scope(ctf.scope.STREAM_PACKET_CONTEXT) > - field = event.get_field(scope, "cpu_id") > - cpu_id = field.get_uint64() > - if ctf.field_error(): > - print("ERROR: Missing cpu_id info for {}".format( > - event.get_name())) > - error = True > - > - scope = event.get_top_level_scope(ctf.scope.EVENT_FIELDS) > - field = event.get_field(scope, "_vec") > - vec = field.get_uint64() > - if ctf.field_error(): > - print("ERROR: Missing vec info for {}".format( > - event.get_name())) > - error = True > - key = (cpu_id, vec) > - > - if event_name == 'softirq_raise' and not error: > - # Add timestamp to tmp > - if key in tmp: > - # If key already exists > - i = 0 > - while True: > - # Add index > - key = (cpu_id, vec, i) > - if key in tmp: > - i += 1 > - continue > - if i > largest_val: > - largest_val = i > - break > - > - tmp[key] = event.get_timestamp() > - > - if event_name == 'softirq_exit' and not error: > - # Saving data for output > - # Key check > - if not (key in tmp): > - i = 0 > - while i <= largest_val: > - key = (key[0], key[1], i) > - if key in tmp: > - break > - i += 1 > - > - raise_timestamp = tmp[key] > - time_data = event.get_timestamp() - tmp.pop(key) > - if time_data > max_time[0]: > - # max_time = (val, ts) > - max_time = (time_data, raise_timestamp) > - time_taken.append(time_data) > - graph_data.append((raise_timestamp - start_time, time_data)) > - > - # Next Event > - ret = ctf_it.next() > - if ret < 0: > - break > - event = ctf_it.read_event() > - > - > -del ctf_it > - > -# Standard dev. calc. > -try: > - mean = sum(time_taken)/float(len(time_taken)) > -except ZeroDivisionError: > - raise TypeError("empty data") > -deviations_squared = [] > -for x in time_taken: > - deviations_squared.append(math.pow((x - mean), 2)) > -try: > - stddev = math.sqrt(sum(deviations_squared) / (len(deviations_squared) - 1)) > -except ZeroDivisionError: > - stddev = '-' > - > -# Terminal output > -print("AVG TIME: {} ns".format(mean)) > -print("MIN TIME: {} ns".format(min(time_taken))) > -print("MAX TIME: {} ns, TS: {}".format(max_time[0], max_time[1])) > -print("STD DEV: {}".format(stddev)) > -print("TOTAL COUNT: {}".format(len(time_taken))) > - > -# Graph output > -cairoplot.scatter_plot ( 'softirqtimes.svg', data = graph_data, > - width = 5000, height = 4000, border = 20, axis = True, > - grid = True, series_colors = ["red"] ) > diff --git a/bindings/python/examples/syscalls_by_pid.py b/bindings/python/examples/syscalls_by_pid.py > deleted file mode 100644 > index 3ae342e..0000000 > --- a/bindings/python/examples/syscalls_by_pid.py > +++ /dev/null > @@ -1,84 +0,0 @@ > -# syscall_by_pid.py > -# > -# Babeltrace syscall by pid example script > -# > -# Copyright 2012 EfficiOS Inc. > -# > -# Author: Danny Serres > -# > -# Permission is hereby granted, free of charge, to any person obtaining a copy > -# of this software and associated documentation files (the "Software"), to deal > -# in the Software without restriction, including without limitation the rights > -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell > -# copies of the Software, and to permit persons to whom the Software is > -# furnished to do so, subject to the following conditions: > -# > -# The above copyright notice and this permission notice shall be included in > -# all copies or substantial portions of the Software. > - > -# The script checks the number of events in the trace > -# and outputs a table and a .svg histogram for the specified > -# range (microseconds) or the total trace if no range specified. > -# The graph is generated using the cairoplot module. > - > -# The script checks all syscall in the trace and prints a list > -# showing the number of systemcalls executed by each PID > -# ordered from greatest to least number of syscalls. > -# The trace needs PID context (lttng add-context -k -t pid) > - > -import sys > -from babeltrace import * > -from output_format_modules.pprint_table import pprint_table as pprint > - > -if len(sys.argv) < 2 : > - raise TypeError("Usage: python syscalls_by_pid.py path/to/trace") > - > -ctx = Context() > -ret = ctx.add_trace(sys.argv[1], "ctf") > -if ret is None: > - raise IOError("Error adding trace") > - > -data = {} > - > -# Setting iterator > -bp = IterPos(SEEK_BEGIN) > -ctf_it = ctf.Iterator(ctx, bp) > - > -# Reading events > -event = ctf_it.read_event() > -while event is not None: > - if event.get_name().find("sys") >= 0: > - # Getting scope definition > - sco = event.get_top_level_scope(ctf.scope.STREAM_EVENT_CONTEXT) > - if sco is None: > - print("ERROR: Cannot get definition scope for {}".format( > - event.get_name())) > - else: > - # Getting PID > - pid_field = event.get_field(sco, "_pid") > - pid = pid_field.get_int64() > - > - if ctf.field_error(): > - print("ERROR: Missing PID info for sched_switch".format( > - event.get_name())) > - elif pid in data: > - data[pid] += 1 > - else: > - data[pid] = 1 > - # Next event > - ret = ctf_it.next() > - if ret < 0: > - break > - event = ctf_it.read_event() > - > -del ctf_it > - > -# Setting table for output > -table = [] > -for item in data: > - table.append([data[item], item]) # [count, pid] > -table.sort(reverse = True) # [big count first, pid] > -for i in range(len(table)): > - table[i].reverse() # [pid, big count first] > -table.insert(0, ["PID", "SYSCALL COUNT"]) > -pprint(table) > diff --git a/tests/tests-python.py b/tests/tests-python.py > old mode 100644 > new mode 100755 > index 0bd71c2..8695f61 > --- a/tests/tests-python.py > +++ b/tests/tests-python.py > @@ -1,3 +1,4 @@ > +#!/usr/bin/env python3 > import unittest > import sys > from babeltrace import * > -- > 1.8.1.1 > > > _______________________________________________ > lttng-dev mailing list > lttng-dev at lists.lttng.org > http://lists.lttng.org/cgi-bin/mailman/listinfo/lttng-dev -- Mathieu Desnoyers EfficiOS Inc. http://www.efficios.com