Merge lp:~zeitgeist/zeitgeist/benchmark-tools into lp:~zeitgeist/zeitgeist/bluebird
- benchmark-tools
- Merge into bluebird
Status: | Merged |
---|---|
Merged at revision: | 353 |
Proposed branch: | lp:~zeitgeist/zeitgeist/benchmark-tools |
Merge into: | lp:~zeitgeist/zeitgeist/bluebird |
Diff against target: |
4038 lines (+3948/-2) 11 files modified
extensions/Makefile.am (+11/-1) extensions/benchmark.vala (+127/-0) src/Makefile.am (+1/-0) src/extension-collection.vala (+3/-1) tools/development/README (+23/-0) tools/development/benchmark.py (+14/-0) tools/development/cairoplot.py (+2371/-0) tools/development/query_sets/timerange_always.txt (+9/-0) tools/development/query_sets/timerange_interval.txt (+9/-0) tools/development/query_timings.py (+240/-0) tools/development/series.py (+1140/-0) |
To merge this branch: | bzr merge lp:~zeitgeist/zeitgeist/benchmark-tools |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Siegfried Gevatter | Approve | ||
Michal Hruby (community) | Approve | ||
Review via email: mp+86867@code.launchpad.net |
Commit message
Description of the change
Added Benchmarking tools:
- Extension to plot...
- Plotting tools
- 348. By Seif Lotfy
-
removed svg and json files
Siegfried Gevatter (rainct) wrote : | # |
Lovely :).
Pedantic comments:
- If you modified query_timings.py, copyright's missing in there.
- Return extra_data to something else (eg. result), since it's not just "extra data", but all the data :P.
- Typo: "running and instance of", "argument, multible '--plot' arguments", "zeitgeist" (capitalize), "several types of plots ??such?? using", "find_events, overall)" -> "find_events or overall", "will define multible series." (where does this sentence start? also multiple typo again).
- tools/developme
- benchmark.py seems pretty arbitrary.
- Seriously there isn't a good enough plot library in Python or Debian repository?
- 349. By Seif Lotfy
-
added unload method
fixed typos
renamed extra_data to data
Michal Hruby (mhr3) wrote : | # |
The only remaining thing I see is how do we want to deal with the extension, obviously it's only meant for development and it probably shouldn't be compiled into Zeitgeist by default (although imo it can as long as it's disabled by default)
Seif Lotfy (seif) wrote : | # |
I's say not to ship it since it adds some extra dependencies...
On Thu, Dec 29, 2011 at 1:12 AM, Michal Hruby <email address hidden>wrote:
> Review: Needs Information
>
> The only remaining thing I see is how do we want to deal with the
> extension, obviously it's only meant for development and it probably
> shouldn't be compiled into Zeitgeist by default (although imo it can as
> long as it's disabled by default)
> --
>
> https:/
> You proposed lp:~zeitgeist/zeitgeist/benchmark-tools for merging.
>
Michal Hruby (mhr3) wrote : | # |
As discussed on IRC we'll ship the extension by default but it'll be disabled, the only problem is that disabling extensions is not yet supported, so we'll fix that soon.
Siegfried Gevatter (rainct) wrote : | # |
OK, but I don't quite like the cairoplot.py copy in there.
Preview Diff
1 | === modified file 'extensions/Makefile.am' |
2 | --- extensions/Makefile.am 2011-10-31 15:28:09 +0000 |
3 | +++ extensions/Makefile.am 2011-12-28 19:50:03 +0000 |
4 | @@ -3,7 +3,7 @@ |
5 | NULL = |
6 | |
7 | #extensionsdir = $(libdir)/zeitgeist/extensions |
8 | -noinst_LTLIBRARIES = ds-registry.la blacklist.la storage-monitor.la fts.la |
9 | +noinst_LTLIBRARIES = ds-registry.la blacklist.la storage-monitor.la fts.la benchmark.la |
10 | |
11 | AM_CPPFLAGS = \ |
12 | $(ZEITGEIST_CFLAGS) \ |
13 | @@ -60,3 +60,13 @@ |
14 | fts_la_LIBADD = \ |
15 | $(ZEITGEIST_LIBS) \ |
16 | $(NULL) |
17 | + |
18 | +benchmark_la_SOURCES = \ |
19 | + benchmark.vala \ |
20 | + $(NULL) |
21 | + |
22 | +benchmark_la_LDFLAGS = -module -avoid-version |
23 | + |
24 | +benchmark_la_LIBADD = \ |
25 | + $(ZEITGEIST_LIBS) \ |
26 | + $(NULL) |
27 | |
28 | === added file 'extensions/benchmark.vala' |
29 | --- extensions/benchmark.vala 1970-01-01 00:00:00 +0000 |
30 | +++ extensions/benchmark.vala 2011-12-28 19:50:03 +0000 |
31 | @@ -0,0 +1,127 @@ |
32 | +/* fts.vala |
33 | + * |
34 | + * Copyright © 2011 Collabora Ltd. |
35 | + * By Seif Lotfy <seif@lotfy.com> |
36 | + * |
37 | + * This program is free software: you can redistribute it and/or modify |
38 | + * it under the terms of the GNU Lesser General Public License as published by |
39 | + * the Free Software Foundation, either version 2.1 of the License, or |
40 | + * (at your option) any later version. |
41 | + * |
42 | + * This program is distributed in the hope that it will be useful, |
43 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
44 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
45 | + * GNU General Public License for more details. |
46 | + * |
47 | + * You should have received a copy of the GNU Lesser General Public License |
48 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
49 | + * |
50 | + */ |
51 | + |
52 | +namespace Zeitgeist |
53 | +{ |
54 | + [DBus (name = "org.gnome.zeitgeist.Benchmark")] |
55 | + public interface RemoteBenchmarker: Object |
56 | + { |
57 | + public abstract async HashTable<string, Variant> find_events ( |
58 | + [DBus (signature = "(xx)")] Variant time_range, |
59 | + [DBus (signature = "a(asaasay)")] Variant event_templates, |
60 | + uint storage_state, uint num_events, uint result_type) |
61 | + throws Error; |
62 | + } |
63 | + |
64 | + class Benchmarker: Extension, RemoteBenchmarker |
65 | + { |
66 | + |
67 | + private uint registration_id; |
68 | + |
69 | + Benchmarker () |
70 | + { |
71 | + Object (); |
72 | + } |
73 | + |
74 | + construct |
75 | + { |
76 | + try |
77 | + { |
78 | + var connection = Bus.get_sync (BusType.SESSION, null); |
79 | + registration_id = connection.register_object<RemoteBenchmarker> ( |
80 | + "/org/gnome/zeitgeist/benchmark", this); |
81 | + } |
82 | + catch (Error err) |
83 | + { |
84 | + warning ("%s", err.message); |
85 | + } |
86 | + } |
87 | + |
88 | + public async HashTable<string, Variant> find_events (Variant time_range, |
89 | + Variant filter_templates, uint storage_state, uint num_events, |
90 | + uint result_type) |
91 | + throws Error |
92 | + { |
93 | + var data = new HashTable<string, Variant> (str_hash, str_equal); |
94 | + |
95 | + var find_event_ids_timer = new Timer (); |
96 | + var ids = engine.find_event_ids ( |
97 | + new TimeRange.from_variant (time_range), |
98 | + Events.from_variant (filter_templates), |
99 | + storage_state, num_events, result_type); |
100 | + var find_event_ids_elapsed = find_event_ids_timer.elapsed(); |
101 | + |
102 | + var get_events_timer = new Timer (); |
103 | + var events = engine.get_events (ids); |
104 | + var get_events_elapsed = get_events_timer.elapsed(); |
105 | + |
106 | + var marsh_events_timer = new Timer (); |
107 | + var marsh_events = Events.to_variant(events); |
108 | + var marsh_events_elapsed = marsh_events_timer.elapsed(); |
109 | + |
110 | + var find_events_elapsed = get_events_elapsed + find_event_ids_elapsed + marsh_events_elapsed; |
111 | + |
112 | + data.insert("find_event_ids", |
113 | + new Variant.double(find_event_ids_elapsed)); |
114 | + data.insert("get_events", |
115 | + new Variant.double(get_events_elapsed)); |
116 | + data.insert("find_events", |
117 | + new Variant.double(find_events_elapsed)); |
118 | + data.insert("marsh_events", |
119 | + new Variant.double(marsh_events_elapsed)); |
120 | + data.insert("events", marsh_events); |
121 | + |
122 | + return data; |
123 | + } |
124 | + |
125 | + public override void unload () |
126 | + { |
127 | + try |
128 | + { |
129 | + var connection = Bus.get_sync (BusType.SESSION, null); |
130 | + if (registration_id != 0) |
131 | + { |
132 | + connection.unregister_object (registration_id); |
133 | + registration_id = 0; |
134 | + } |
135 | + } |
136 | + catch (Error err) |
137 | + { |
138 | + warning ("%s", err.message); |
139 | + } |
140 | + |
141 | + debug ("%s, this.ref_count = %u", Log.METHOD, this.ref_count); |
142 | + } |
143 | + |
144 | + } |
145 | + |
146 | + [ModuleInit] |
147 | +#if BUILTIN_EXTENSIONS |
148 | + public static Type benchmark_init (TypeModule module) |
149 | + { |
150 | +#else |
151 | + public static Type extension_register (TypeModule module) |
152 | + { |
153 | +#endif |
154 | + return typeof (Benchmarker); |
155 | + } |
156 | +} |
157 | + |
158 | +// vim:expandtab:ts=4:sw=4 |
159 | |
160 | === modified file 'src/Makefile.am' |
161 | --- src/Makefile.am 2011-11-01 18:30:37 +0000 |
162 | +++ src/Makefile.am 2011-12-28 19:50:03 +0000 |
163 | @@ -25,6 +25,7 @@ |
164 | ext-histogram.vala \ |
165 | ext-storage-monitor.vala \ |
166 | ext-fts.vala \ |
167 | + ext-benchmark.vala \ |
168 | $(NULL) |
169 | |
170 | zeitgeist_daemon_VALASOURCES = \ |
171 | |
172 | === added symlink 'src/ext-benchmark.vala' |
173 | === target is u'/home/seif/Projects/zeitgeist/extensions/benchmark.vala' |
174 | === modified file 'src/extension-collection.vala' |
175 | --- src/extension-collection.vala 2011-10-20 11:20:36 +0000 |
176 | +++ src/extension-collection.vala 2011-12-28 19:50:03 +0000 |
177 | @@ -48,7 +48,8 @@ |
178 | blacklist_init, |
179 | histogram_init, |
180 | storage_monitor_init, |
181 | - fts_init |
182 | + fts_init, |
183 | + benchmark_init |
184 | }; |
185 | |
186 | foreach (var func in builtins) |
187 | @@ -163,6 +164,7 @@ |
188 | private extern static Type histogram_init (TypeModule mod); |
189 | private extern static Type storage_monitor_init (TypeModule mod); |
190 | private extern static Type fts_init (TypeModule mod); |
191 | + private extern static Type benchmark_init (TypeModule mod); |
192 | #endif |
193 | |
194 | } |
195 | |
196 | === added directory 'tools' |
197 | === added directory 'tools/development' |
198 | === added file 'tools/development/README' |
199 | --- tools/development/README 1970-01-01 00:00:00 +0000 |
200 | +++ tools/development/README 2011-12-28 19:50:03 +0000 |
201 | @@ -0,0 +1,23 @@ |
202 | +# USAGE |
203 | +# |
204 | +# To run the benchmarks on a certain branch of Zeitgeist make sure you are |
205 | +# running an instance of the branch. Make sure the branch includes the benchmark |
206 | +# extension. |
207 | +# |
208 | +# To benchmark and output timing to 'output.json' run |
209 | +# ./query_timings.py -o output.json |
210 | +# If you already have data in the output file and want to merge both |
211 | +# data sets (build avg.) run |
212 | +# ./query_timings.py -m -o output.json |
213 | +# To plot the data use the '--plot' argument, multiple '--plot' arguments |
214 | +# will define multiple series. |
215 | +# When plotting one needs to choose between several types of plots using |
216 | +# --type with arguments marsh_time, get_events_time, find_ids_time, find_events and overall |
217 | +# |
218 | +# In short, a run always looks like: |
219 | +# tools/development/query_timings.py --name "lp:zeitgeist" -o trunk.json \ |
220 | +# --queries tools/development/query_sets/timerange_always.txt |
221 | +# tools/development/query_timings.py --name "lp:some-branch" -o somebranch.json \ |
222 | +# --queries tools/development/query_sets/timerange_always.txt |
223 | +# tools/development/query_timings.py --plot --type marsh_time somebranch.json --plot trunk.json -o benchmark.svg |
224 | + |
225 | |
226 | === added file 'tools/development/benchmark.py' |
227 | --- tools/development/benchmark.py 1970-01-01 00:00:00 +0000 |
228 | +++ tools/development/benchmark.py 2011-12-28 19:50:03 +0000 |
229 | @@ -0,0 +1,14 @@ |
230 | +import dbus |
231 | + |
232 | +BUS_NAME = "org.gnome.zeitgeist.Engine" |
233 | +INTERFACE_NAME = "org.gnome.zeitgeist.Benchmark" |
234 | +OBJECT_PATH = "/org/gnome/zeitgeist/benchmark" |
235 | + |
236 | +bus = dbus.SessionBus() |
237 | +benchmark_obj = bus.get_object(BUS_NAME, OBJECT_PATH) |
238 | +benchmark_interface = dbus.Interface(benchmark_obj, |
239 | + dbus_interface = INTERFACE_NAME) |
240 | + |
241 | +def find_events(time_frame, templates, storage_type, num_events, result_type): |
242 | + return benchmark_interface.FindEvents(time_frame, templates, storage_type, |
243 | + num_events, result_type) |
244 | |
245 | === added file 'tools/development/cairoplot.py' |
246 | --- tools/development/cairoplot.py 1970-01-01 00:00:00 +0000 |
247 | +++ tools/development/cairoplot.py 2011-12-28 19:50:03 +0000 |
248 | @@ -0,0 +1,2371 @@ |
249 | +#!/usr/bin/env python |
250 | +# -*- coding: utf-8 -*- |
251 | + |
252 | +# CairoPlot.py |
253 | +# |
254 | +# Copyright (c) 2008 Rodrigo Moreira Araújo |
255 | +# |
256 | +# Author: Rodrigo Moreiro Araujo <alf.rodrigo@gmail.com> |
257 | +# Markus Korn <thekorn@gmx.de> |
258 | +# |
259 | +# This program is free software; you can redistribute it and/or |
260 | +# modify it under the terms of the GNU Lesser General Public License |
261 | +# as published by the Free Software Foundation; either version 2 of |
262 | +# the License, or (at your option) any later version. |
263 | +# |
264 | +# This program is distributed in the hope that it will be useful, |
265 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
266 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
267 | +# GNU General Public License for more details. |
268 | +# |
269 | +# You should have received a copy of the GNU Lesser General Public |
270 | +# License along with this program; if not, write to the Free Software |
271 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
272 | +# USA |
273 | + |
274 | +#Contributor: João S. O. Bueno |
275 | + |
276 | +#TODO: review BarPlot Code |
277 | +#TODO: x_label colision problem on Horizontal Bar Plot |
278 | +#TODO: y_label's eat too much space on HBP |
279 | + |
280 | +# thekorn: added optional borders around vertical bars in VerticalBarPlot |
281 | +# thekorn: break long x-axis labels into two lines |
282 | + |
283 | +__version__ = 1.1 |
284 | + |
285 | +import cairo |
286 | +import math |
287 | +import random |
288 | +from series import Series, Group, Data |
289 | + |
290 | +HORZ = 0 |
291 | +VERT = 1 |
292 | +NORM = 2 |
293 | + |
294 | +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), |
295 | + "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), |
296 | + "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), |
297 | + "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), |
298 | + "gray" : (0.5,0.5,0.5,1.0), "light_gray" : (0.9,0.9,0.9,1.0), |
299 | + "transparent" : (0.0,0.0,0.0,0.0)} |
300 | + |
301 | +THEMES = {"black_red" : [(0.0,0.0,0.0,1.0), (1.0,0.0,0.0,1.0)], |
302 | + "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)], |
303 | + "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)], |
304 | + "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)], |
305 | + "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)]} |
306 | + |
307 | +def colors_from_theme( theme, series_length, mode = 'solid' ): |
308 | + colors = [] |
309 | + if theme not in THEMES.keys() : |
310 | + raise Exception, "Theme not defined" |
311 | + color_steps = THEMES[theme] |
312 | + n_colors = len(color_steps) |
313 | + if series_length <= n_colors: |
314 | + colors = [color + tuple([mode]) for color in color_steps[0:n_colors]] |
315 | + else: |
316 | + iterations = [(series_length - n_colors)/(n_colors - 1) for i in color_steps[:-1]] |
317 | + over_iterations = (series_length - n_colors) % (n_colors - 1) |
318 | + for i in range(n_colors - 1): |
319 | + if over_iterations <= 0: |
320 | + break |
321 | + iterations[i] += 1 |
322 | + over_iterations -= 1 |
323 | + for index,color in enumerate(color_steps[:-1]): |
324 | + colors.append(color + tuple([mode])) |
325 | + if iterations[index] == 0: |
326 | + continue |
327 | + next_color = color_steps[index+1] |
328 | + color_step = ((next_color[0] - color[0])/(iterations[index] + 1), |
329 | + (next_color[1] - color[1])/(iterations[index] + 1), |
330 | + (next_color[2] - color[2])/(iterations[index] + 1), |
331 | + (next_color[3] - color[3])/(iterations[index] + 1)) |
332 | + for i in range( iterations[index] ): |
333 | + colors.append((color[0] + color_step[0]*(i+1), |
334 | + color[1] + color_step[1]*(i+1), |
335 | + color[2] + color_step[2]*(i+1), |
336 | + color[3] + color_step[3]*(i+1), |
337 | + mode)) |
338 | + colors.append(color_steps[-1] + tuple([mode])) |
339 | + return colors |
340 | + |
341 | + |
342 | +def other_direction(direction): |
343 | + "explicit is better than implicit" |
344 | + if direction == HORZ: |
345 | + return VERT |
346 | + else: |
347 | + return HORZ |
348 | + |
349 | +#Class definition |
350 | + |
351 | +class Plot(object): |
352 | + def __init__(self, |
353 | + surface=None, |
354 | + data=None, |
355 | + width=640, |
356 | + height=480, |
357 | + background=None, |
358 | + border = 0, |
359 | + x_labels = None, |
360 | + y_labels = None, |
361 | + series_colors = None): |
362 | + random.seed(2) |
363 | + self.create_surface(surface, width, height) |
364 | + self.dimensions = {} |
365 | + self.dimensions[HORZ] = width |
366 | + self.dimensions[VERT] = height |
367 | + self.context = cairo.Context(self.surface) |
368 | + self.labels={} |
369 | + self.labels[HORZ] = x_labels |
370 | + self.labels[VERT] = y_labels |
371 | + self.load_series(data, x_labels, y_labels, series_colors) |
372 | + self.font_size = 10 |
373 | + self.set_background (background) |
374 | + self.border = border |
375 | + self.borders = {} |
376 | + self.line_color = (0.5, 0.5, 0.5) |
377 | + self.line_width = 0.5 |
378 | + self.label_color = (0.0, 0.0, 0.0) |
379 | + self.grid_color = (0.8, 0.8, 0.8) |
380 | + |
381 | + def create_surface(self, surface, width=None, height=None): |
382 | + self.filename = None |
383 | + if isinstance(surface, cairo.Surface): |
384 | + self.surface = surface |
385 | + return |
386 | + if not type(surface) in (str, unicode): |
387 | + raise TypeError("Surface should be either a Cairo surface or a filename, not %s" % surface) |
388 | + sufix = surface.rsplit(".")[-1].lower() |
389 | + self.filename = surface |
390 | + if sufix == "png": |
391 | + self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) |
392 | + elif sufix == "ps": |
393 | + self.surface = cairo.PSSurface(surface, width, height) |
394 | + elif sufix == "pdf": |
395 | + self.surface = cairo.PSSurface(surface, width, height) |
396 | + else: |
397 | + if sufix != "svg": |
398 | + self.filename += ".svg" |
399 | + self.surface = cairo.SVGSurface(self.filename, width, height) |
400 | + |
401 | + def commit(self): |
402 | + try: |
403 | + self.context.show_page() |
404 | + if self.filename and self.filename.endswith(".png"): |
405 | + self.surface.write_to_png(self.filename) |
406 | + else: |
407 | + self.surface.finish() |
408 | + except cairo.Error: |
409 | + pass |
410 | + |
411 | + def load_series (self, data, x_labels=None, y_labels=None, series_colors=None): |
412 | + self.series_labels = [] |
413 | + self.series = None |
414 | + |
415 | + #The pretty way |
416 | + #if not isinstance(data, Series): |
417 | + # # Not an instance of Series |
418 | + # self.series = Series(data) |
419 | + #else: |
420 | + # self.series = data |
421 | + # |
422 | + #self.series_labels = self.series.get_names() |
423 | + |
424 | + #TODO: Remove on next version |
425 | + # The ugly way, keeping retrocompatibility... |
426 | + if callable(data) or type(data) is list and callable(data[0]): # Lambda or List of lambdas |
427 | + self.series = data |
428 | + self.series_labels = None |
429 | + elif isinstance(data, Series): # Instance of Series |
430 | + self.series = data |
431 | + self.series_labels = data.get_names() |
432 | + else: # Anything else |
433 | + self.series = Series(data) |
434 | + self.series_labels = self.series.get_names() |
435 | + |
436 | + #TODO: allow user passed series_widths |
437 | + self.series_widths = [1.0 for group in self.series] |
438 | + |
439 | + #TODO: Remove on next version |
440 | + self.process_colors( series_colors ) |
441 | + |
442 | + def process_colors( self, series_colors, length = None, mode = 'solid' ): |
443 | + #series_colors might be None, a theme, a string of colors names or a list of color tuples |
444 | + if length is None : |
445 | + length = len( self.series.to_list() ) |
446 | + |
447 | + #no colors passed |
448 | + if not series_colors: |
449 | + #Randomize colors |
450 | + self.series_colors = [ [random.random() for i in range(3)] + [1.0, mode] for series in range( length ) ] |
451 | + else: |
452 | + #Just theme pattern |
453 | + if not hasattr( series_colors, "__iter__" ): |
454 | + theme = series_colors |
455 | + self.series_colors = colors_from_theme( theme.lower(), length ) |
456 | + |
457 | + #Theme pattern and mode |
458 | + elif not hasattr(series_colors, '__delitem__') and not hasattr( series_colors[0], "__iter__" ): |
459 | + theme = series_colors[0] |
460 | + mode = series_colors[1] |
461 | + self.series_colors = colors_from_theme( theme.lower(), length, mode ) |
462 | + |
463 | + #List |
464 | + else: |
465 | + self.series_colors = series_colors |
466 | + for index, color in enumerate( self.series_colors ): |
467 | + #element is a color name |
468 | + if not hasattr(color, "__iter__"): |
469 | + self.series_colors[index] = COLORS[color.lower()] + tuple([mode]) |
470 | + #element is rgb tuple instead of rgba |
471 | + elif len( color ) == 3 : |
472 | + self.series_colors[index] += (1.0,mode) |
473 | + #element has 4 elements, might be rgba tuple or rgb tuple with mode |
474 | + elif len( color ) == 4 : |
475 | + #last element is mode |
476 | + if not hasattr(color[3], "__iter__"): |
477 | + self.series_colors[index] += tuple([color[3]]) |
478 | + self.series_colors[index][3] = 1.0 |
479 | + #last element is alpha |
480 | + else: |
481 | + self.series_colors[index] += tuple([mode]) |
482 | + |
483 | + def get_width(self): |
484 | + return self.surface.get_width() |
485 | + |
486 | + def get_height(self): |
487 | + return self.surface.get_height() |
488 | + |
489 | + def set_background(self, background): |
490 | + if background is None: |
491 | + self.background = (0.0,0.0,0.0,0.0) |
492 | + elif type(background) in (cairo.LinearGradient, tuple): |
493 | + self.background = background |
494 | + elif not hasattr(background,"__iter__"): |
495 | + colors = background.split(" ") |
496 | + if len(colors) == 1 and colors[0] in COLORS: |
497 | + self.background = COLORS[background] |
498 | + elif len(colors) > 1: |
499 | + self.background = cairo.LinearGradient(self.dimensions[HORZ] / 2, 0, self.dimensions[HORZ] / 2, self.dimensions[VERT]) |
500 | + for index,color in enumerate(colors): |
501 | + self.background.add_color_stop_rgba(float(index)/(len(colors)-1),*COLORS[color]) |
502 | + else: |
503 | + raise TypeError ("Background should be either cairo.LinearGradient or a 3/4-tuple, not %s" % type(background)) |
504 | + |
505 | + def render_background(self): |
506 | + if isinstance(self.background, cairo.LinearGradient): |
507 | + self.context.set_source(self.background) |
508 | + else: |
509 | + self.context.set_source_rgba(*self.background) |
510 | + self.context.rectangle(0,0, self.dimensions[HORZ], self.dimensions[VERT]) |
511 | + self.context.fill() |
512 | + |
513 | + def render_bounding_box(self): |
514 | + self.context.set_source_rgba(*self.line_color) |
515 | + self.context.set_line_width(self.line_width) |
516 | + self.context.rectangle(self.border, self.border, |
517 | + self.dimensions[HORZ] - 2 * self.border, |
518 | + self.dimensions[VERT] - 2 * self.border) |
519 | + self.context.stroke() |
520 | + |
521 | + def render(self): |
522 | + pass |
523 | + |
524 | +class ScatterPlot( Plot ): |
525 | + def __init__(self, |
526 | + surface=None, |
527 | + data=None, |
528 | + errorx=None, |
529 | + errory=None, |
530 | + width=640, |
531 | + height=480, |
532 | + background=None, |
533 | + border=0, |
534 | + axis = False, |
535 | + dash = False, |
536 | + discrete = False, |
537 | + dots = 0, |
538 | + grid = False, |
539 | + series_legend = False, |
540 | + x_labels = None, |
541 | + y_labels = None, |
542 | + x_bounds = None, |
543 | + y_bounds = None, |
544 | + z_bounds = None, |
545 | + x_title = None, |
546 | + y_title = None, |
547 | + series_colors = None, |
548 | + circle_colors = None ): |
549 | + |
550 | + self.bounds = {} |
551 | + self.bounds[HORZ] = x_bounds |
552 | + self.bounds[VERT] = y_bounds |
553 | + self.bounds[NORM] = z_bounds |
554 | + self.titles = {} |
555 | + self.titles[HORZ] = x_title |
556 | + self.titles[VERT] = y_title |
557 | + self.max_value = {} |
558 | + self.axis = axis |
559 | + self.discrete = discrete |
560 | + self.dots = dots |
561 | + self.grid = grid |
562 | + self.series_legend = series_legend |
563 | + self.variable_radius = False |
564 | + self.x_label_angle = math.pi / 2.5 |
565 | + self.circle_colors = circle_colors |
566 | + |
567 | + Plot.__init__(self, surface, data, width, height, background, border, x_labels, y_labels, series_colors) |
568 | + |
569 | + self.dash = None |
570 | + if dash: |
571 | + if hasattr(dash, "keys"): |
572 | + self.dash = [dash[key] for key in self.series_labels] |
573 | + elif max([hasattr(item,'__delitem__') for item in data]) : |
574 | + self.dash = dash |
575 | + else: |
576 | + self.dash = [dash] |
577 | + |
578 | + self.load_errors(errorx, errory) |
579 | + |
580 | + def convert_list_to_tuple(self, data): |
581 | + #Data must be converted from lists of coordinates to a single |
582 | + # list of tuples |
583 | + out_data = zip(*data) |
584 | + if len(data) == 3: |
585 | + self.variable_radius = True |
586 | + return out_data |
587 | + |
588 | + def load_series(self, data, x_labels = None, y_labels = None, series_colors=None): |
589 | + #TODO: In cairoplot 2.0 keep only the Series instances |
590 | + |
591 | + # Convert Data and Group to Series |
592 | + if isinstance(data, Data) or isinstance(data, Group): |
593 | + data = Series(data) |
594 | + |
595 | + # Series |
596 | + if isinstance(data, Series): |
597 | + for group in data: |
598 | + for item in group: |
599 | + if len(item) is 3: |
600 | + self.variable_radius = True |
601 | + |
602 | + #Dictionary with lists |
603 | + if hasattr(data, "keys") : |
604 | + if hasattr( data.values()[0][0], "__delitem__" ) : |
605 | + for key in data.keys() : |
606 | + data[key] = self.convert_list_to_tuple(data[key]) |
607 | + elif len(data.values()[0][0]) == 3: |
608 | + self.variable_radius = True |
609 | + #List |
610 | + elif hasattr(data[0], "__delitem__") : |
611 | + #List of lists |
612 | + if hasattr(data[0][0], "__delitem__") : |
613 | + for index,value in enumerate(data) : |
614 | + data[index] = self.convert_list_to_tuple(value) |
615 | + #List |
616 | + elif type(data[0][0]) != type((0,0)): |
617 | + data = self.convert_list_to_tuple(data) |
618 | + #Three dimensional data |
619 | + elif len(data[0][0]) == 3: |
620 | + self.variable_radius = True |
621 | + |
622 | + #List with three dimensional tuples |
623 | + elif len(data[0]) == 3: |
624 | + self.variable_radius = True |
625 | + Plot.load_series(self, data, x_labels, y_labels, series_colors) |
626 | + self.calc_boundaries() |
627 | + self.calc_labels() |
628 | + |
629 | + def load_errors(self, errorx, errory): |
630 | + self.errors = None |
631 | + if errorx == None and errory == None: |
632 | + return |
633 | + self.errors = {} |
634 | + self.errors[HORZ] = None |
635 | + self.errors[VERT] = None |
636 | + #asimetric errors |
637 | + if errorx and hasattr(errorx[0], "__delitem__"): |
638 | + self.errors[HORZ] = errorx |
639 | + #simetric errors |
640 | + elif errorx: |
641 | + self.errors[HORZ] = [errorx] |
642 | + #asimetric errors |
643 | + if errory and hasattr(errory[0], "__delitem__"): |
644 | + self.errors[VERT] = errory |
645 | + #simetric errors |
646 | + elif errory: |
647 | + self.errors[VERT] = [errory] |
648 | + |
649 | + def calc_labels(self): |
650 | + if not self.labels[HORZ]: |
651 | + amplitude = self.bounds[HORZ][1] - self.bounds[HORZ][0] |
652 | + if amplitude % 10: #if horizontal labels need floating points |
653 | + self.labels[HORZ] = ["%.2lf" % (float(self.bounds[HORZ][0] + (amplitude * i / 10.0))) for i in range(11) ] |
654 | + else: |
655 | + self.labels[HORZ] = ["%d" % (int(self.bounds[HORZ][0] + (amplitude * i / 10.0))) for i in range(11) ] |
656 | + if not self.labels[VERT]: |
657 | + amplitude = self.bounds[VERT][1] - self.bounds[VERT][0] |
658 | + if amplitude % 10: #if vertical labels need floating points |
659 | + self.labels[VERT] = ["%.2lf" % (float(self.bounds[VERT][0] + (amplitude * i / 10.0))) for i in range(11) ] |
660 | + else: |
661 | + self.labels[VERT] = ["%d" % (int(self.bounds[VERT][0] + (amplitude * i / 10.0))) for i in range(11) ] |
662 | + |
663 | + def calc_extents(self, direction): |
664 | + self.context.set_font_size(self.font_size * 0.8) |
665 | + self.max_value[direction] = max(self.context.text_extents(item)[2] for item in self.labels[direction]) |
666 | + self.borders[other_direction(direction)] = self.max_value[direction] + self.border + 20 |
667 | + |
668 | + def calc_boundaries(self): |
669 | + #HORZ = 0, VERT = 1, NORM = 2 |
670 | + min_data_value = [0,0,0] |
671 | + max_data_value = [0,0,0] |
672 | + |
673 | + for group in self.series: |
674 | + if type(group[0].content) in (int, float, long): |
675 | + group = [Data((index, item.content)) for index,item in enumerate(group)] |
676 | + |
677 | + for point in group: |
678 | + for index, item in enumerate(point.content): |
679 | + if item > max_data_value[index]: |
680 | + max_data_value[index] = item |
681 | + elif item < min_data_value[index]: |
682 | + min_data_value[index] = item |
683 | + |
684 | + if not self.bounds[HORZ]: |
685 | + self.bounds[HORZ] = (min_data_value[HORZ], max_data_value[HORZ]) |
686 | + if not self.bounds[VERT]: |
687 | + self.bounds[VERT] = (min_data_value[VERT], max_data_value[VERT]) |
688 | + if not self.bounds[NORM]: |
689 | + self.bounds[NORM] = (min_data_value[NORM], max_data_value[NORM]) |
690 | + |
691 | + def calc_all_extents(self): |
692 | + self.calc_extents(HORZ) |
693 | + self.calc_extents(VERT) |
694 | + |
695 | + self.plot_height = self.dimensions[VERT] - 2 * self.borders[VERT] |
696 | + self.plot_width = self.dimensions[HORZ] - 2* self.borders[HORZ] |
697 | + |
698 | + self.plot_top = self.dimensions[VERT] - self.borders[VERT] |
699 | + |
700 | + def calc_steps(self): |
701 | + #Calculates all the x, y, z and color steps |
702 | + series_amplitude = [self.bounds[index][1] - self.bounds[index][0] for index in range(3)] |
703 | + |
704 | + if series_amplitude[HORZ]: |
705 | + self.horizontal_step = float (self.plot_width) / series_amplitude[HORZ] |
706 | + else: |
707 | + self.horizontal_step = 0.00 |
708 | + |
709 | + if series_amplitude[VERT]: |
710 | + self.vertical_step = float (self.plot_height) / series_amplitude[VERT] |
711 | + else: |
712 | + self.vertical_step = 0.00 |
713 | + |
714 | + if series_amplitude[NORM]: |
715 | + if self.variable_radius: |
716 | + self.z_step = float (self.bounds[NORM][1]) / series_amplitude[NORM] |
717 | + if self.circle_colors: |
718 | + self.circle_color_step = tuple([float(self.circle_colors[1][i]-self.circle_colors[0][i])/series_amplitude[NORM] for i in range(4)]) |
719 | + else: |
720 | + self.z_step = 0.00 |
721 | + self.circle_color_step = ( 0.0, 0.0, 0.0, 0.0 ) |
722 | + |
723 | + def get_circle_color(self, value): |
724 | + return tuple( [self.circle_colors[0][i] + value*self.circle_color_step[i] for i in range(4)] ) |
725 | + |
726 | + def render(self): |
727 | + self.calc_all_extents() |
728 | + self.calc_steps() |
729 | + self.render_background() |
730 | + self.render_bounding_box() |
731 | + if self.axis: |
732 | + self.render_axis() |
733 | + if self.grid: |
734 | + self.render_grid() |
735 | + self.render_labels() |
736 | + self.render_plot() |
737 | + if self.errors: |
738 | + self.render_errors() |
739 | + if self.series_legend and self.series_labels: |
740 | + self.render_legend() |
741 | + |
742 | + def render_axis(self): |
743 | + #Draws both the axis lines and their titles |
744 | + cr = self.context |
745 | + cr.set_source_rgba(*self.line_color) |
746 | + cr.move_to(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT]) |
747 | + cr.line_to(self.borders[HORZ], self.borders[VERT]) |
748 | + cr.stroke() |
749 | + |
750 | + cr.move_to(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT]) |
751 | + cr.line_to(self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT]) |
752 | + cr.stroke() |
753 | + |
754 | + cr.set_source_rgba(*self.label_color) |
755 | + self.context.set_font_size( 1.2 * self.font_size ) |
756 | + if self.titles[HORZ]: |
757 | + title_width,title_height = cr.text_extents(self.titles[HORZ])[2:4] |
758 | + cr.move_to( self.dimensions[HORZ]/2 - title_width/2, self.borders[VERT] - title_height/2 ) |
759 | + cr.show_text( self.titles[HORZ] ) |
760 | + |
761 | + if self.titles[VERT]: |
762 | + title_width,title_height = cr.text_extents(self.titles[VERT])[2:4] |
763 | + cr.move_to( self.dimensions[HORZ] - self.borders[HORZ] + title_height/2, self.dimensions[VERT]/2 - title_width/2) |
764 | + cr.save() |
765 | + cr.rotate( math.pi/2 ) |
766 | + cr.show_text( self.titles[VERT] ) |
767 | + cr.restore() |
768 | + |
769 | + def render_grid(self): |
770 | + cr = self.context |
771 | + horizontal_step = float( self.plot_height ) / ( len( self.labels[VERT] ) - 1 ) |
772 | + vertical_step = float( self.plot_width ) / ( len( self.labels[HORZ] ) - 1 ) |
773 | + |
774 | + x = self.borders[HORZ] + vertical_step |
775 | + y = self.plot_top - horizontal_step |
776 | + |
777 | + for label in self.labels[HORZ][:-1]: |
778 | + cr.set_source_rgba(*self.grid_color) |
779 | + cr.move_to(x, self.dimensions[VERT] - self.borders[VERT]) |
780 | + cr.line_to(x, self.borders[VERT]) |
781 | + cr.stroke() |
782 | + x += vertical_step |
783 | + for label in self.labels[VERT][:-1]: |
784 | + cr.set_source_rgba(*self.grid_color) |
785 | + cr.move_to(self.borders[HORZ], y) |
786 | + cr.line_to(self.dimensions[HORZ] - self.borders[HORZ], y) |
787 | + cr.stroke() |
788 | + y -= horizontal_step |
789 | + |
790 | + def render_labels(self): |
791 | + self.context.set_font_size(self.font_size * 0.8) |
792 | + self.render_horz_labels() |
793 | + self.render_vert_labels() |
794 | + |
795 | + def render_horz_labels(self): |
796 | + cr = self.context |
797 | + step = float( self.plot_width ) / ( len( self.labels[HORZ] ) - 1 ) |
798 | + x = self.borders[HORZ] |
799 | + y = self.dimensions[VERT] - self.borders[VERT] + 5 |
800 | + |
801 | + # store rotation matrix from the initial state |
802 | + rotation_matrix = cr.get_matrix() |
803 | + rotation_matrix.rotate(self.x_label_angle) |
804 | + |
805 | + cr.set_source_rgba(*self.label_color) |
806 | + |
807 | + for item in self.labels[HORZ]: |
808 | + width = cr.text_extents(item)[2] |
809 | + cr.move_to(x, y) |
810 | + cr.save() |
811 | + cr.set_matrix(rotation_matrix) |
812 | + cr.show_text(item) |
813 | + cr.restore() |
814 | + x += step |
815 | + |
816 | + def render_vert_labels(self): |
817 | + cr = self.context |
818 | + step = ( self.plot_height ) / ( len( self.labels[VERT] ) - 1 ) |
819 | + y = self.plot_top |
820 | + cr.set_source_rgba(*self.label_color) |
821 | + for item in self.labels[VERT]: |
822 | + width = cr.text_extents(item)[2] |
823 | + cr.move_to(self.borders[HORZ] - width - 5,y) |
824 | + cr.show_text(item) |
825 | + y -= step |
826 | + |
827 | + def render_legend(self): |
828 | + cr = self.context |
829 | + cr.set_font_size(self.font_size) |
830 | + cr.set_line_width(self.line_width) |
831 | + |
832 | + widest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[2]) |
833 | + tallest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[3]) |
834 | + max_width = self.context.text_extents(widest_word)[2] |
835 | + max_height = self.context.text_extents(tallest_word)[3] * 1.1 |
836 | + |
837 | + color_box_height = max_height / 2 |
838 | + color_box_width = color_box_height * 2 |
839 | + |
840 | + #Draw a bounding box |
841 | + bounding_box_width = max_width + color_box_width + 15 |
842 | + bounding_box_height = (len(self.series_labels)+0.5) * max_height |
843 | + cr.set_source_rgba(1,1,1) |
844 | + cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - bounding_box_width, self.borders[VERT], |
845 | + bounding_box_width, bounding_box_height) |
846 | + cr.fill() |
847 | + |
848 | + cr.set_source_rgba(*self.line_color) |
849 | + cr.set_line_width(self.line_width) |
850 | + cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - bounding_box_width, self.borders[VERT], |
851 | + bounding_box_width, bounding_box_height) |
852 | + cr.stroke() |
853 | + |
854 | + for idx,key in enumerate(self.series_labels): |
855 | + #Draw color box |
856 | + cr.set_source_rgba(*self.series_colors[idx][:4]) |
857 | + cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - max_width - color_box_width - 10, |
858 | + self.borders[VERT] + color_box_height + (idx*max_height) , |
859 | + color_box_width, color_box_height) |
860 | + cr.fill() |
861 | + |
862 | + cr.set_source_rgba(0, 0, 0) |
863 | + cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - max_width - color_box_width - 10, |
864 | + self.borders[VERT] + color_box_height + (idx*max_height), |
865 | + color_box_width, color_box_height) |
866 | + cr.stroke() |
867 | + |
868 | + #Draw series labels |
869 | + cr.set_source_rgba(0, 0, 0) |
870 | + cr.move_to(self.dimensions[HORZ] - self.borders[HORZ] - max_width - 5, self.borders[VERT] + ((idx+1)*max_height)) |
871 | + cr.show_text(key) |
872 | + |
873 | + def render_errors(self): |
874 | + cr = self.context |
875 | + cr.rectangle(self.borders[HORZ], self.borders[VERT], self.plot_width, self.plot_height) |
876 | + cr.clip() |
877 | + radius = self.dots |
878 | + x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step |
879 | + y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step |
880 | + for index, group in enumerate(self.series): |
881 | + cr.set_source_rgba(*self.series_colors[index][:4]) |
882 | + for number, data in enumerate(group): |
883 | + x = x0 + self.horizontal_step * data.content[0] |
884 | + y = self.dimensions[VERT] - y0 - self.vertical_step * data.content[1] |
885 | + if self.errors[HORZ]: |
886 | + cr.move_to(x, y) |
887 | + x1 = x - self.horizontal_step * self.errors[HORZ][0][number] |
888 | + cr.line_to(x1, y) |
889 | + cr.line_to(x1, y - radius) |
890 | + cr.line_to(x1, y + radius) |
891 | + cr.stroke() |
892 | + if self.errors[HORZ] and len(self.errors[HORZ]) == 2: |
893 | + cr.move_to(x, y) |
894 | + x1 = x + self.horizontal_step * self.errors[HORZ][1][number] |
895 | + cr.line_to(x1, y) |
896 | + cr.line_to(x1, y - radius) |
897 | + cr.line_to(x1, y + radius) |
898 | + cr.stroke() |
899 | + if self.errors[VERT]: |
900 | + cr.move_to(x, y) |
901 | + y1 = y + self.vertical_step * self.errors[VERT][0][number] |
902 | + cr.line_to(x, y1) |
903 | + cr.line_to(x - radius, y1) |
904 | + cr.line_to(x + radius, y1) |
905 | + cr.stroke() |
906 | + if self.errors[VERT] and len(self.errors[VERT]) == 2: |
907 | + cr.move_to(x, y) |
908 | + y1 = y - self.vertical_step * self.errors[VERT][1][number] |
909 | + cr.line_to(x, y1) |
910 | + cr.line_to(x - radius, y1) |
911 | + cr.line_to(x + radius, y1) |
912 | + cr.stroke() |
913 | + |
914 | + |
915 | + def render_plot(self): |
916 | + cr = self.context |
917 | + if self.discrete: |
918 | + cr.rectangle(self.borders[HORZ], self.borders[VERT], self.plot_width, self.plot_height) |
919 | + cr.clip() |
920 | + x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step |
921 | + y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step |
922 | + radius = self.dots |
923 | + for number, group in enumerate (self.series): |
924 | + cr.set_source_rgba(*self.series_colors[number][:4]) |
925 | + for data in group : |
926 | + if self.variable_radius: |
927 | + radius = data.content[2]*self.z_step |
928 | + if self.circle_colors: |
929 | + cr.set_source_rgba( *self.get_circle_color( data.content[2]) ) |
930 | + x = x0 + self.horizontal_step*data.content[0] |
931 | + y = y0 + self.vertical_step*data.content[1] |
932 | + cr.arc(x, self.dimensions[VERT] - y, radius, 0, 2*math.pi) |
933 | + cr.fill() |
934 | + else: |
935 | + cr.rectangle(self.borders[HORZ], self.borders[VERT], self.plot_width, self.plot_height) |
936 | + cr.clip() |
937 | + x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step |
938 | + y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step |
939 | + radius = self.dots |
940 | + for number, group in enumerate (self.series): |
941 | + last_data = None |
942 | + cr.set_source_rgba(*self.series_colors[number][:4]) |
943 | + for data in group : |
944 | + x = x0 + self.horizontal_step*data.content[0] |
945 | + y = y0 + self.vertical_step*data.content[1] |
946 | + if self.dots: |
947 | + if self.variable_radius: |
948 | + radius = data.content[2]*self.z_step |
949 | + cr.arc(x, self.dimensions[VERT] - y, radius, 0, 2*math.pi) |
950 | + cr.fill() |
951 | + if last_data : |
952 | + old_x = x0 + self.horizontal_step*last_data.content[0] |
953 | + old_y = y0 + self.vertical_step*last_data.content[1] |
954 | + cr.move_to( old_x, self.dimensions[VERT] - old_y ) |
955 | + cr.line_to( x, self.dimensions[VERT] - y) |
956 | + cr.set_line_width(self.series_widths[number]) |
957 | + |
958 | + # Display line as dash line |
959 | + if self.dash and self.dash[number]: |
960 | + s = self.series_widths[number] |
961 | + cr.set_dash([s*3, s*3], 0) |
962 | + |
963 | + cr.stroke() |
964 | + cr.set_dash([]) |
965 | + last_data = data |
966 | + |
967 | +class DotLinePlot(ScatterPlot): |
968 | + def __init__(self, |
969 | + surface=None, |
970 | + data=None, |
971 | + width=640, |
972 | + height=480, |
973 | + background=None, |
974 | + border=0, |
975 | + axis = False, |
976 | + dash = False, |
977 | + dots = 0, |
978 | + grid = False, |
979 | + series_legend = False, |
980 | + x_labels = None, |
981 | + y_labels = None, |
982 | + x_bounds = None, |
983 | + y_bounds = None, |
984 | + x_title = None, |
985 | + y_title = None, |
986 | + series_colors = None): |
987 | + |
988 | + ScatterPlot.__init__(self, surface, data, None, None, width, height, background, border, |
989 | + axis, dash, False, dots, grid, series_legend, x_labels, y_labels, |
990 | + x_bounds, y_bounds, None, x_title, y_title, series_colors, None ) |
991 | + |
992 | + |
993 | + def load_series(self, data, x_labels = None, y_labels = None, series_colors=None): |
994 | + Plot.load_series(self, data, x_labels, y_labels, series_colors) |
995 | + for group in self.series : |
996 | + for index,data in enumerate(group): |
997 | + group[index].content = (index, data.content) |
998 | + |
999 | + self.calc_boundaries() |
1000 | + self.calc_labels() |
1001 | + |
1002 | +class FunctionPlot(ScatterPlot): |
1003 | + def __init__(self, |
1004 | + surface=None, |
1005 | + data=None, |
1006 | + width=640, |
1007 | + height=480, |
1008 | + background=None, |
1009 | + border=0, |
1010 | + axis = False, |
1011 | + discrete = False, |
1012 | + dots = 0, |
1013 | + grid = False, |
1014 | + series_legend = False, |
1015 | + x_labels = None, |
1016 | + y_labels = None, |
1017 | + x_bounds = None, |
1018 | + y_bounds = None, |
1019 | + x_title = None, |
1020 | + y_title = None, |
1021 | + series_colors = None, |
1022 | + step = 1): |
1023 | + |
1024 | + self.function = data |
1025 | + self.step = step |
1026 | + self.discrete = discrete |
1027 | + |
1028 | + data, x_bounds = self.load_series_from_function( self.function, x_bounds ) |
1029 | + |
1030 | + ScatterPlot.__init__(self, surface, data, None, None, width, height, background, border, |
1031 | + axis, False, discrete, dots, grid, series_legend, x_labels, y_labels, |
1032 | + x_bounds, y_bounds, None, x_title, y_title, series_colors, None ) |
1033 | + |
1034 | + def load_series(self, data, x_labels = None, y_labels = None, series_colors=None): |
1035 | + Plot.load_series(self, data, x_labels, y_labels, series_colors) |
1036 | + |
1037 | + if len(self.series[0][0]) is 1: |
1038 | + for group_id, group in enumerate(self.series) : |
1039 | + for index,data in enumerate(group): |
1040 | + group[index].content = (self.bounds[HORZ][0] + self.step*index, data.content) |
1041 | + |
1042 | + self.calc_boundaries() |
1043 | + self.calc_labels() |
1044 | + |
1045 | + def load_series_from_function( self, function, x_bounds ): |
1046 | + #TODO: Add the possibility for the user to define multiple functions with different discretization parameters |
1047 | + |
1048 | + #This function converts a function, a list of functions or a dictionary |
1049 | + #of functions into its corresponding array of data |
1050 | + series = Series() |
1051 | + |
1052 | + if isinstance(function, Group) or isinstance(function, Data): |
1053 | + function = Series(function) |
1054 | + |
1055 | + # If is instance of Series |
1056 | + if isinstance(function, Series): |
1057 | + # Overwrite any bounds passed by the function |
1058 | + x_bounds = (function.range[0],function.range[-1]) |
1059 | + |
1060 | + #if no bounds are provided |
1061 | + if x_bounds == None: |
1062 | + x_bounds = (0,10) |
1063 | + |
1064 | + |
1065 | + #TODO: Finish the dict translation |
1066 | + if hasattr(function, "keys"): #dictionary: |
1067 | + for key in function.keys(): |
1068 | + group = Group(name=key) |
1069 | + #data[ key ] = [] |
1070 | + i = x_bounds[0] |
1071 | + while i <= x_bounds[1] : |
1072 | + group.add_data(function[ key ](i)) |
1073 | + #data[ key ].append( function[ key ](i) ) |
1074 | + i += self.step |
1075 | + series.add_group(group) |
1076 | + |
1077 | + elif hasattr(function, "__delitem__"): #list of functions |
1078 | + for index,f in enumerate( function ) : |
1079 | + group = Group() |
1080 | + #data.append( [] ) |
1081 | + i = x_bounds[0] |
1082 | + while i <= x_bounds[1] : |
1083 | + group.add_data(f(i)) |
1084 | + #data[ index ].append( f(i) ) |
1085 | + i += self.step |
1086 | + series.add_group(group) |
1087 | + |
1088 | + elif isinstance(function, Series): # instance of Series |
1089 | + series = function |
1090 | + |
1091 | + else: #function |
1092 | + group = Group() |
1093 | + i = x_bounds[0] |
1094 | + while i <= x_bounds[1] : |
1095 | + group.add_data(function(i)) |
1096 | + i += self.step |
1097 | + series.add_group(group) |
1098 | + |
1099 | + |
1100 | + return series, x_bounds |
1101 | + |
1102 | + def calc_labels(self): |
1103 | + if not self.labels[HORZ]: |
1104 | + self.labels[HORZ] = [] |
1105 | + i = self.bounds[HORZ][0] |
1106 | + while i<=self.bounds[HORZ][1]: |
1107 | + self.labels[HORZ].append(str(i)) |
1108 | + i += float(self.bounds[HORZ][1] - self.bounds[HORZ][0])/10 |
1109 | + ScatterPlot.calc_labels(self) |
1110 | + |
1111 | + def render_plot(self): |
1112 | + if not self.discrete: |
1113 | + ScatterPlot.render_plot(self) |
1114 | + else: |
1115 | + last = None |
1116 | + cr = self.context |
1117 | + for number, group in enumerate (self.series): |
1118 | + cr.set_source_rgba(*self.series_colors[number][:4]) |
1119 | + x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step |
1120 | + y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step |
1121 | + for data in group: |
1122 | + x = x0 + self.horizontal_step * data.content[0] |
1123 | + y = y0 + self.vertical_step * data.content[1] |
1124 | + cr.move_to(x, self.dimensions[VERT] - y) |
1125 | + cr.line_to(x, self.plot_top) |
1126 | + cr.set_line_width(self.series_widths[number]) |
1127 | + cr.stroke() |
1128 | + if self.dots: |
1129 | + cr.new_path() |
1130 | + cr.arc(x, self.dimensions[VERT] - y, 3, 0, 2.1 * math.pi) |
1131 | + cr.close_path() |
1132 | + cr.fill() |
1133 | + |
1134 | +class BarPlot(Plot): |
1135 | + def __init__(self, |
1136 | + surface = None, |
1137 | + data = None, |
1138 | + width = 640, |
1139 | + height = 480, |
1140 | + background = "white light_gray", |
1141 | + border = 0, |
1142 | + display_values = False, |
1143 | + grid = False, |
1144 | + rounded_corners = False, |
1145 | + stack = False, |
1146 | + three_dimension = False, |
1147 | + x_labels = None, |
1148 | + y_labels = None, |
1149 | + x_bounds = None, |
1150 | + y_bounds = None, |
1151 | + series_colors = None, |
1152 | + main_dir = None): |
1153 | + |
1154 | + self.bounds = {} |
1155 | + self.bounds[HORZ] = x_bounds |
1156 | + self.bounds[VERT] = y_bounds |
1157 | + self.display_values = display_values |
1158 | + self.grid = grid |
1159 | + self.rounded_corners = rounded_corners |
1160 | + self.stack = stack |
1161 | + self.three_dimension = three_dimension |
1162 | + self.x_label_angle = math.pi / 2.5 |
1163 | + self.main_dir = main_dir |
1164 | + self.max_value = {} |
1165 | + self.plot_dimensions = {} |
1166 | + self.steps = {} |
1167 | + self.value_label_color = (0.5,0.5,0.5,1.0) |
1168 | + |
1169 | + Plot.__init__(self, surface, data, width, height, background, border, x_labels, y_labels, series_colors) |
1170 | + |
1171 | + def load_series(self, data, x_labels = None, y_labels = None, series_colors = None): |
1172 | + Plot.load_series(self, data, x_labels, y_labels, series_colors) |
1173 | + self.calc_boundaries() |
1174 | + |
1175 | + def process_colors(self, series_colors): |
1176 | + #Data for a BarPlot might be a List or a List of Lists. |
1177 | + #On the first case, colors must be generated for all bars, |
1178 | + #On the second, colors must be generated for each of the inner lists. |
1179 | + |
1180 | + #TODO: Didn't get it... |
1181 | + #if hasattr(self.data[0], '__getitem__'): |
1182 | + # length = max(len(series) for series in self.data) |
1183 | + #else: |
1184 | + # length = len( self.data ) |
1185 | + |
1186 | + length = max(len(group) for group in self.series) |
1187 | + |
1188 | + Plot.process_colors( self, series_colors, length, 'linear') |
1189 | + |
1190 | + def calc_boundaries(self): |
1191 | + if not self.bounds[self.main_dir]: |
1192 | + if self.stack: |
1193 | + max_data_value = max(sum(group.to_list()) for group in self.series) |
1194 | + else: |
1195 | + max_data_value = max(max(group.to_list()) for group in self.series) |
1196 | + self.bounds[self.main_dir] = (0, max_data_value) |
1197 | + if not self.bounds[other_direction(self.main_dir)]: |
1198 | + self.bounds[other_direction(self.main_dir)] = (0, len(self.series)) |
1199 | + |
1200 | + def calc_extents(self, direction): |
1201 | + self.max_value[direction] = 0 |
1202 | + if self.labels[direction]: |
1203 | + widest_word = max(self.labels[direction], key = lambda item: self.context.text_extents(item)[2]) |
1204 | + self.max_value[direction] = self.context.text_extents(widest_word)[3 - direction] |
1205 | + self.borders[other_direction(direction)] = (2-direction)*self.max_value[direction] + self.border + direction*(5) |
1206 | + else: |
1207 | + self.borders[other_direction(direction)] = self.border |
1208 | + |
1209 | + def calc_horz_extents(self): |
1210 | + self.calc_extents(HORZ) |
1211 | + |
1212 | + def calc_vert_extents(self): |
1213 | + self.calc_extents(VERT) |
1214 | + |
1215 | + def calc_all_extents(self): |
1216 | + self.calc_horz_extents() |
1217 | + self.calc_vert_extents() |
1218 | + other_dir = other_direction(self.main_dir) |
1219 | + self.value_label = 0 |
1220 | + if self.display_values: |
1221 | + if self.stack: |
1222 | + self.value_label = self.context.text_extents(str(max(sum(group.to_list()) for group in self.series)))[2 + self.main_dir] |
1223 | + else: |
1224 | + self.value_label = self.context.text_extents(str(max(max(group.to_list()) for group in self.series)))[2 + self.main_dir] |
1225 | + if self.labels[self.main_dir]: |
1226 | + self.plot_dimensions[self.main_dir] = self.dimensions[self.main_dir] - 2*self.borders[self.main_dir] - self.value_label |
1227 | + else: |
1228 | + self.plot_dimensions[self.main_dir] = self.dimensions[self.main_dir] - self.borders[self.main_dir] - 1.2*self.border - self.value_label |
1229 | + self.plot_dimensions[other_dir] = self.dimensions[other_dir] - self.borders[other_dir] - self.border |
1230 | + self.plot_top = self.dimensions[VERT] - self.borders[VERT] |
1231 | + |
1232 | + def calc_steps(self): |
1233 | + other_dir = other_direction(self.main_dir) |
1234 | + self.series_amplitude = self.bounds[self.main_dir][1] - self.bounds[self.main_dir][0] |
1235 | + if self.series_amplitude: |
1236 | + self.steps[self.main_dir] = float(self.plot_dimensions[self.main_dir])/self.series_amplitude |
1237 | + else: |
1238 | + self.steps[self.main_dir] = 0.00 |
1239 | + series_length = len(self.series) |
1240 | + self.steps[other_dir] = float(self.plot_dimensions[other_dir])/(series_length + 0.1*(series_length + 1)) |
1241 | + self.space = 0.1*self.steps[other_dir] |
1242 | + |
1243 | + def render(self): |
1244 | + self.calc_all_extents() |
1245 | + self.calc_steps() |
1246 | + self.render_background() |
1247 | + self.render_bounding_box() |
1248 | + if self.grid: |
1249 | + self.render_grid() |
1250 | + if self.three_dimension: |
1251 | + self.render_ground() |
1252 | + if self.display_values: |
1253 | + self.render_values() |
1254 | + self.render_labels() |
1255 | + self.render_plot() |
1256 | + if self.series_labels: |
1257 | + self.render_legend() |
1258 | + |
1259 | + def draw_3d_rectangle_front(self, x0, y0, x1, y1, shift): |
1260 | + self.context.rectangle(x0-shift, y0+shift, x1-x0, y1-y0) |
1261 | + |
1262 | + def draw_3d_rectangle_side(self, x0, y0, x1, y1, shift): |
1263 | + self.context.move_to(x1-shift,y0+shift) |
1264 | + self.context.line_to(x1, y0) |
1265 | + self.context.line_to(x1, y1) |
1266 | + self.context.line_to(x1-shift, y1+shift) |
1267 | + self.context.line_to(x1-shift, y0+shift) |
1268 | + self.context.close_path() |
1269 | + |
1270 | + def draw_3d_rectangle_top(self, x0, y0, x1, y1, shift): |
1271 | + self.context.move_to(x0-shift,y0+shift) |
1272 | + self.context.line_to(x0, y0) |
1273 | + self.context.line_to(x1, y0) |
1274 | + self.context.line_to(x1-shift, y0+shift) |
1275 | + self.context.line_to(x0-shift, y0+shift) |
1276 | + self.context.close_path() |
1277 | + |
1278 | + def draw_round_rectangle(self, x0, y0, x1, y1): |
1279 | + self.context.arc(x0+5, y0+5, 5, -math.pi, -math.pi/2) |
1280 | + self.context.line_to(x1-5, y0) |
1281 | + self.context.arc(x1-5, y0+5, 5, -math.pi/2, 0) |
1282 | + self.context.line_to(x1, y1-5) |
1283 | + self.context.arc(x1-5, y1-5, 5, 0, math.pi/2) |
1284 | + self.context.line_to(x0+5, y1) |
1285 | + self.context.arc(x0+5, y1-5, 5, math.pi/2, math.pi) |
1286 | + self.context.line_to(x0, y0+5) |
1287 | + self.context.close_path() |
1288 | + |
1289 | + def render_ground(self): |
1290 | + self.draw_3d_rectangle_front(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
1291 | + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
1292 | + self.context.fill() |
1293 | + |
1294 | + self.draw_3d_rectangle_side (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
1295 | + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
1296 | + self.context.fill() |
1297 | + |
1298 | + self.draw_3d_rectangle_top (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
1299 | + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
1300 | + self.context.fill() |
1301 | + |
1302 | + def render_labels(self): |
1303 | + self.context.set_font_size(self.font_size * 0.8) |
1304 | + if self.labels[HORZ]: |
1305 | + self.render_horz_labels() |
1306 | + if self.labels[VERT]: |
1307 | + self.render_vert_labels() |
1308 | + |
1309 | + def render_legend(self): |
1310 | + has_border = bool(getattr(self, "bar_borders", False)) |
1311 | + cr = self.context |
1312 | + cr.set_font_size(self.font_size) |
1313 | + cr.set_line_width(self.line_width) |
1314 | + |
1315 | + widest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[2]) |
1316 | + tallest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[3]) |
1317 | + max_width = self.context.text_extents(widest_word)[2] |
1318 | + max_height = self.context.text_extents(tallest_word)[3] * 1.1 + 5 |
1319 | + |
1320 | + color_box_height = max_height / 2 |
1321 | + color_box_width = color_box_height * 2 |
1322 | + |
1323 | + #Draw a bounding box |
1324 | + bounding_box_width = max_width + color_box_width + 15 |
1325 | + bounding_box_height = (len(self.series_labels) + int(has_border) +0.5) * max_height |
1326 | + cr.set_source_rgba(1,1,1) |
1327 | + cr.rectangle(self.dimensions[HORZ] - self.border - bounding_box_width, self.border, |
1328 | + bounding_box_width, bounding_box_height) |
1329 | + cr.fill() |
1330 | + |
1331 | + cr.set_source_rgba(*self.line_color) |
1332 | + cr.set_line_width(self.line_width) |
1333 | + cr.rectangle(self.dimensions[HORZ] - self.border - bounding_box_width, self.border, |
1334 | + bounding_box_width, bounding_box_height) |
1335 | + cr.stroke() |
1336 | + |
1337 | + for idx,key in enumerate(self.series_labels): |
1338 | + #Draw color box |
1339 | + cr.set_source_rgba(*self.series_colors[idx][:4]) |
1340 | + cr.rectangle(self.dimensions[HORZ] - self.border - max_width - color_box_width - 10, |
1341 | + self.border + color_box_height + (idx*max_height) , |
1342 | + color_box_width, color_box_height) |
1343 | + cr.fill() |
1344 | + |
1345 | + cr.set_source_rgba(0, 0, 0) |
1346 | + cr.rectangle(self.dimensions[HORZ] - self.border - max_width - color_box_width - 10, |
1347 | + self.border + color_box_height + (idx*max_height), |
1348 | + color_box_width, color_box_height) |
1349 | + cr.stroke() |
1350 | + |
1351 | + #Draw series labels |
1352 | + cr.set_source_rgba(0, 0, 0) |
1353 | + cr.move_to(self.dimensions[HORZ] - self.border - max_width - 5, self.border + ((idx+1)*max_height)) |
1354 | + cr.show_text(key) |
1355 | + |
1356 | + if has_border: |
1357 | + idx += 1 |
1358 | + |
1359 | + cr.set_source_rgba(1, 0, 0) |
1360 | + cr.rectangle(self.dimensions[HORZ] - self.border - max_width - color_box_width - 10, |
1361 | + self.border + color_box_height + (idx*max_height), |
1362 | + color_box_width, color_box_height) |
1363 | + cr.stroke() |
1364 | + |
1365 | + #Draw series labels |
1366 | + cr.set_source_rgba(0, 0, 0) |
1367 | + cr.move_to(self.dimensions[HORZ] - self.border - max_width - 5, self.border + ((idx+1)*max_height)) |
1368 | + cr.show_text("is not using index") |
1369 | + |
1370 | + |
1371 | + |
1372 | +class HorizontalBarPlot(BarPlot): |
1373 | + def __init__(self, |
1374 | + surface = None, |
1375 | + data = None, |
1376 | + width = 640, |
1377 | + height = 480, |
1378 | + background = "white light_gray", |
1379 | + border = 0, |
1380 | + display_values = False, |
1381 | + grid = False, |
1382 | + rounded_corners = False, |
1383 | + stack = False, |
1384 | + three_dimension = False, |
1385 | + series_labels = None, |
1386 | + x_labels = None, |
1387 | + y_labels = None, |
1388 | + x_bounds = None, |
1389 | + y_bounds = None, |
1390 | + series_colors = None): |
1391 | + |
1392 | + BarPlot.__init__(self, surface, data, width, height, background, border, |
1393 | + display_values, grid, rounded_corners, stack, three_dimension, |
1394 | + x_labels, y_labels, x_bounds, y_bounds, series_colors, HORZ) |
1395 | + self.series_labels = series_labels |
1396 | + |
1397 | + def calc_vert_extents(self): |
1398 | + self.calc_extents(VERT) |
1399 | + if self.labels[HORZ] and not self.labels[VERT]: |
1400 | + self.borders[HORZ] += 10 |
1401 | + |
1402 | + def draw_rectangle_bottom(self, x0, y0, x1, y1): |
1403 | + self.context.arc(x0+5, y1-5, 5, math.pi/2, math.pi) |
1404 | + self.context.line_to(x0, y0+5) |
1405 | + self.context.arc(x0+5, y0+5, 5, -math.pi, -math.pi/2) |
1406 | + self.context.line_to(x1, y0) |
1407 | + self.context.line_to(x1, y1) |
1408 | + self.context.line_to(x0+5, y1) |
1409 | + self.context.close_path() |
1410 | + |
1411 | + def draw_rectangle_top(self, x0, y0, x1, y1): |
1412 | + self.context.arc(x1-5, y0+5, 5, -math.pi/2, 0) |
1413 | + self.context.line_to(x1, y1-5) |
1414 | + self.context.arc(x1-5, y1-5, 5, 0, math.pi/2) |
1415 | + self.context.line_to(x0, y1) |
1416 | + self.context.line_to(x0, y0) |
1417 | + self.context.line_to(x1, y0) |
1418 | + self.context.close_path() |
1419 | + |
1420 | + def draw_rectangle(self, index, length, x0, y0, x1, y1): |
1421 | + if length == 1: |
1422 | + BarPlot.draw_rectangle(self, x0, y0, x1, y1) |
1423 | + elif index == 0: |
1424 | + self.draw_rectangle_bottom(x0, y0, x1, y1) |
1425 | + elif index == length-1: |
1426 | + self.draw_rectangle_top(x0, y0, x1, y1) |
1427 | + else: |
1428 | + self.context.rectangle(x0, y0, x1-x0, y1-y0) |
1429 | + |
1430 | + #TODO: Review BarPlot.render_grid code |
1431 | + def render_grid(self): |
1432 | + self.context.set_source_rgba(0.8, 0.8, 0.8) |
1433 | + if self.labels[HORZ]: |
1434 | + self.context.set_font_size(self.font_size * 0.8) |
1435 | + step = (self.dimensions[HORZ] - 2*self.borders[HORZ] - self.value_label)/(len(self.labels[HORZ])-1) |
1436 | + x = self.borders[HORZ] |
1437 | + next_x = 0 |
1438 | + for item in self.labels[HORZ]: |
1439 | + width = self.context.text_extents(item)[2] |
1440 | + if x - width/2 > next_x and x - width/2 > self.border: |
1441 | + self.context.move_to(x, self.border) |
1442 | + self.context.line_to(x, self.dimensions[VERT] - self.borders[VERT]) |
1443 | + self.context.stroke() |
1444 | + next_x = x + width/2 |
1445 | + x += step |
1446 | + else: |
1447 | + lines = 11 |
1448 | + horizontal_step = float(self.plot_dimensions[HORZ])/(lines-1) |
1449 | + x = self.borders[HORZ] |
1450 | + for y in xrange(0, lines): |
1451 | + self.context.move_to(x, self.border) |
1452 | + self.context.line_to(x, self.dimensions[VERT] - self.borders[VERT]) |
1453 | + self.context.stroke() |
1454 | + x += horizontal_step |
1455 | + |
1456 | + def render_horz_labels(self): |
1457 | + step = (self.dimensions[HORZ] - 2*self.borders[HORZ])/(len(self.labels[HORZ])-1) |
1458 | + x = self.borders[HORZ] |
1459 | + next_x = 0 |
1460 | + |
1461 | + for item in self.labels[HORZ]: |
1462 | + self.context.set_source_rgba(*self.label_color) |
1463 | + width = self.context.text_extents(item)[2] |
1464 | + if x - width/2 > next_x and x - width/2 > self.border: |
1465 | + self.context.move_to(x - width/2, self.dimensions[VERT] - self.borders[VERT] + self.max_value[HORZ] + 3) |
1466 | + self.context.show_text(item) |
1467 | + next_x = x + width/2 |
1468 | + x += step |
1469 | + |
1470 | + def render_vert_labels(self): |
1471 | + series_length = len(self.labels[VERT]) |
1472 | + step = (self.plot_dimensions[VERT] - (series_length + 1)*self.space)/(len(self.labels[VERT])) |
1473 | + y = self.border + step/2 + self.space |
1474 | + |
1475 | + for item in self.labels[VERT]: |
1476 | + self.context.set_source_rgba(*self.label_color) |
1477 | + width, height = self.context.text_extents(item)[2:4] |
1478 | + self.context.move_to(self.borders[HORZ] - width - 5, y + height/2) |
1479 | + self.context.show_text(item) |
1480 | + y += step + self.space |
1481 | + self.labels[VERT].reverse() |
1482 | + |
1483 | + def render_values(self): |
1484 | + self.context.set_source_rgba(*self.value_label_color) |
1485 | + self.context.set_font_size(self.font_size * 0.8) |
1486 | + if self.stack: |
1487 | + for i,group in enumerate(self.series): |
1488 | + value = sum(group.to_list()) |
1489 | + height = self.context.text_extents(str(value))[3] |
1490 | + x = self.borders[HORZ] + value*self.steps[HORZ] + 2 |
1491 | + y = self.borders[VERT] + (i+0.5)*self.steps[VERT] + (i+1)*self.space + height/2 |
1492 | + self.context.move_to(x, y) |
1493 | + self.context.show_text(str(value)) |
1494 | + else: |
1495 | + for i,group in enumerate(self.series): |
1496 | + inner_step = self.steps[VERT]/len(group) |
1497 | + y0 = self.border + i*self.steps[VERT] + (i+1)*self.space |
1498 | + for number,data in enumerate(group): |
1499 | + height = self.context.text_extents(str(data.content))[3] |
1500 | + self.context.move_to(self.borders[HORZ] + data.content*self.steps[HORZ] + 2, y0 + 0.5*inner_step + height/2, ) |
1501 | + self.context.show_text(str(data.content)) |
1502 | + y0 += inner_step |
1503 | + |
1504 | + def render_plot(self): |
1505 | + if self.stack: |
1506 | + for i,group in enumerate(self.series): |
1507 | + x0 = self.borders[HORZ] |
1508 | + y0 = self.borders[VERT] + i*self.steps[VERT] + (i+1)*self.space |
1509 | + for number,data in enumerate(group): |
1510 | + if self.series_colors[number][4] in ('radial','linear') : |
1511 | + linear = cairo.LinearGradient( data.content*self.steps[HORZ]/2, y0, data.content*self.steps[HORZ]/2, y0 + self.steps[VERT] ) |
1512 | + color = self.series_colors[number] |
1513 | + 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) |
1514 | + linear.add_color_stop_rgba(1.0, *color[:4]) |
1515 | + self.context.set_source(linear) |
1516 | + elif self.series_colors[number][4] == 'solid': |
1517 | + self.context.set_source_rgba(*self.series_colors[number][:4]) |
1518 | + if self.rounded_corners: |
1519 | + self.draw_rectangle(number, len(group), x0, y0, x0+data.content*self.steps[HORZ], y0+self.steps[VERT]) |
1520 | + self.context.fill() |
1521 | + else: |
1522 | + self.context.rectangle(x0, y0, data.content*self.steps[HORZ], self.steps[VERT]) |
1523 | + self.context.fill() |
1524 | + x0 += data.content*self.steps[HORZ] |
1525 | + else: |
1526 | + for i,group in enumerate(self.series): |
1527 | + inner_step = self.steps[VERT]/len(group) |
1528 | + x0 = self.borders[HORZ] |
1529 | + y0 = self.border + i*self.steps[VERT] + (i+1)*self.space |
1530 | + for number,data in enumerate(group): |
1531 | + linear = cairo.LinearGradient(data.content*self.steps[HORZ]/2, y0, data.content*self.steps[HORZ]/2, y0 + inner_step) |
1532 | + color = self.series_colors[number] |
1533 | + 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) |
1534 | + linear.add_color_stop_rgba(1.0, *color[:4]) |
1535 | + self.context.set_source(linear) |
1536 | + if self.rounded_corners and data.content != 0: |
1537 | + BarPlot.draw_round_rectangle(self,x0, y0, x0 + data.content*self.steps[HORZ], y0 + inner_step) |
1538 | + self.context.fill() |
1539 | + else: |
1540 | + self.context.rectangle(x0, y0, data.content*self.steps[HORZ], inner_step) |
1541 | + self.context.fill() |
1542 | + y0 += inner_step |
1543 | + |
1544 | +class VerticalBarPlot(BarPlot): |
1545 | + def __init__(self, |
1546 | + surface = None, |
1547 | + data = None, |
1548 | + width = 640, |
1549 | + height = 480, |
1550 | + background = "white light_gray", |
1551 | + border = 0, |
1552 | + display_values = False, |
1553 | + grid = False, |
1554 | + rounded_corners = False, |
1555 | + stack = False, |
1556 | + three_dimension = False, |
1557 | + series_labels = None, |
1558 | + x_labels = None, |
1559 | + y_labels = None, |
1560 | + x_bounds = None, |
1561 | + y_bounds = None, |
1562 | + series_colors = None): |
1563 | + |
1564 | + BarPlot.__init__(self, surface, data, width, height, background, border, |
1565 | + display_values, grid, rounded_corners, stack, three_dimension, |
1566 | + x_labels, y_labels, x_bounds, y_bounds, series_colors, VERT) |
1567 | + self.series_labels = series_labels |
1568 | + |
1569 | + def calc_vert_extents(self): |
1570 | + self.calc_extents(VERT) |
1571 | + if self.labels[VERT] and not self.labels[HORZ]: |
1572 | + self.borders[VERT] += 10 |
1573 | + |
1574 | + def draw_rectangle_bottom(self, x0, y0, x1, y1): |
1575 | + self.context.move_to(x1,y1) |
1576 | + self.context.arc(x1-5, y1-5, 5, 0, math.pi/2) |
1577 | + self.context.line_to(x0+5, y1) |
1578 | + self.context.arc(x0+5, y1-5, 5, math.pi/2, math.pi) |
1579 | + self.context.line_to(x0, y0) |
1580 | + self.context.line_to(x1, y0) |
1581 | + self.context.line_to(x1, y1) |
1582 | + self.context.close_path() |
1583 | + |
1584 | + def draw_rectangle_top(self, x0, y0, x1, y1): |
1585 | + self.context.arc(x0+5, y0+5, 5, -math.pi, -math.pi/2) |
1586 | + self.context.line_to(x1-5, y0) |
1587 | + self.context.arc(x1-5, y0+5, 5, -math.pi/2, 0) |
1588 | + self.context.line_to(x1, y1) |
1589 | + self.context.line_to(x0, y1) |
1590 | + self.context.line_to(x0, y0) |
1591 | + self.context.close_path() |
1592 | + |
1593 | + def draw_rectangle(self, index, length, x0, y0, x1, y1): |
1594 | + if length == 1: |
1595 | + BarPlot.draw_rectangle(self, x0, y0, x1, y1) |
1596 | + elif index == 0: |
1597 | + self.draw_rectangle_bottom(x0, y0, x1, y1) |
1598 | + elif index == length-1: |
1599 | + self.draw_rectangle_top(x0, y0, x1, y1) |
1600 | + else: |
1601 | + self.context.rectangle(x0, y0, x1-x0, y1-y0) |
1602 | + |
1603 | + def render_grid(self): |
1604 | + self.context.set_source_rgba(0.8, 0.8, 0.8) |
1605 | + if self.labels[VERT]: |
1606 | + lines = len(self.labels[VERT]) |
1607 | + vertical_step = float(self.plot_dimensions[self.main_dir])/(lines-1) |
1608 | + y = self.borders[VERT] + self.value_label |
1609 | + else: |
1610 | + lines = 11 |
1611 | + vertical_step = float(self.plot_dimensions[self.main_dir])/(lines-1) |
1612 | + y = 1.2*self.border + self.value_label |
1613 | + for x in xrange(0, lines): |
1614 | + self.context.move_to(self.borders[HORZ], y) |
1615 | + self.context.line_to(self.dimensions[HORZ] - self.border, y) |
1616 | + self.context.stroke() |
1617 | + y += vertical_step |
1618 | + |
1619 | + def render_ground(self): |
1620 | + self.draw_3d_rectangle_front(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
1621 | + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
1622 | + self.context.fill() |
1623 | + |
1624 | + self.draw_3d_rectangle_side (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
1625 | + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
1626 | + self.context.fill() |
1627 | + |
1628 | + self.draw_3d_rectangle_top (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
1629 | + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
1630 | + self.context.fill() |
1631 | + |
1632 | + def render_horz_labels(self): |
1633 | + series_length = len(self.labels[HORZ]) |
1634 | + step = float (self.plot_dimensions[HORZ] - (series_length + 1)*self.space)/len(self.labels[HORZ]) |
1635 | + x = self.borders[HORZ] + step/2 + self.space |
1636 | + next_x = 0 |
1637 | + |
1638 | + for item in self.labels[HORZ]: |
1639 | + self.context.set_source_rgba(*self.label_color) |
1640 | + width = self.context.text_extents(item)[2] |
1641 | + if not(x - width/2 > next_x and x - width/2 > self.borders[HORZ]): |
1642 | + items = [item[:len(item)/2], item[len(item)/2:],] |
1643 | + else: |
1644 | + items = [item,] |
1645 | + for i, item in enumerate(items): |
1646 | + width = self.context.text_extents(item)[2] |
1647 | + height = self.context.text_extents(item)[3] |
1648 | + v_space = (len(items) - i - 1) * height |
1649 | + |
1650 | + if x - width/2 > next_x and x - width/2 > self.borders[HORZ]: |
1651 | + self.context.move_to(x - width/2, self.dimensions[VERT] - self.borders[VERT] + self.max_value[HORZ] + 3 - v_space + 3) |
1652 | + self.context.show_text(item) |
1653 | + next_x = x + width/2 |
1654 | + x += step + self.space |
1655 | + |
1656 | + def render_vert_labels(self): |
1657 | + self.context.set_source_rgba(*self.label_color) |
1658 | + y = self.borders[VERT] + self.value_label |
1659 | + step = (self.dimensions[VERT] - 2*self.borders[VERT] - self.value_label)/(len(self.labels[VERT]) - 1) |
1660 | + self.labels[VERT].reverse() |
1661 | + for item in self.labels[VERT]: |
1662 | + width, height = self.context.text_extents(item)[2:4] |
1663 | + self.context.move_to(self.borders[HORZ] - width - 5, y + height/2) |
1664 | + self.context.show_text(item) |
1665 | + y += step |
1666 | + self.labels[VERT].reverse() |
1667 | + |
1668 | + def render_values(self): |
1669 | + self.context.set_source_rgba(*self.value_label_color) |
1670 | + self.context.set_font_size(self.font_size * 0.8) |
1671 | + if self.stack: |
1672 | + for i,group in enumerate(self.series): |
1673 | + value = sum(group.to_list()) |
1674 | + width = self.context.text_extents(str(value))[2] |
1675 | + x = self.borders[HORZ] + (i+0.5)*self.steps[HORZ] + (i+1)*self.space - width/2 |
1676 | + y = value*self.steps[VERT] + 2 |
1677 | + self.context.move_to(x, self.plot_top-y) |
1678 | + self.context.show_text(str(value)) |
1679 | + else: |
1680 | + for i,group in enumerate(self.series): |
1681 | + inner_step = self.steps[HORZ]/len(group) |
1682 | + x0 = self.borders[HORZ] + i*self.steps[HORZ] + (i+1)*self.space |
1683 | + for number,data in enumerate(group): |
1684 | + width = self.context.text_extents(str(data.content))[2] |
1685 | + self.context.move_to(x0 + 0.5*inner_step - width/2, self.plot_top - data.content*self.steps[VERT] - 2) |
1686 | + self.context.show_text(str(data.content)) |
1687 | + x0 += inner_step |
1688 | + |
1689 | + def render_plot(self): |
1690 | + if self.stack: |
1691 | + for i,group in enumerate(self.series): |
1692 | + x0 = self.borders[HORZ] + i*self.steps[HORZ] + (i+1)*self.space |
1693 | + y0 = 0 |
1694 | + for number,data in enumerate(group): |
1695 | + if self.series_colors[number][4] in ('linear','radial'): |
1696 | + linear = cairo.LinearGradient( x0, data.content*self.steps[VERT]/2, x0 + self.steps[HORZ], data.content*self.steps[VERT]/2 ) |
1697 | + color = self.series_colors[number] |
1698 | + 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) |
1699 | + linear.add_color_stop_rgba(1.0, *color[:4]) |
1700 | + self.context.set_source(linear) |
1701 | + elif self.series_colors[number][4] == 'solid': |
1702 | + self.context.set_source_rgba(*self.series_colors[number][:4]) |
1703 | + if self.rounded_corners: |
1704 | + self.draw_rectangle(number, len(group), x0, self.plot_top - y0 - data.content*self.steps[VERT], x0 + self.steps[HORZ], self.plot_top - y0) |
1705 | + self.context.fill() |
1706 | + else: |
1707 | + self.context.rectangle(x0, self.plot_top - y0 - data.content*self.steps[VERT], self.steps[HORZ], data.content*self.steps[VERT]) |
1708 | + self.context.fill() |
1709 | + y0 += data.content*self.steps[VERT] |
1710 | + else: |
1711 | + for i,group in enumerate(self.series): |
1712 | + inner_step = self.steps[HORZ]/len(group) |
1713 | + y0 = self.borders[VERT] |
1714 | + x0 = self.borders[HORZ] + i*self.steps[HORZ] + (i+1)*self.space |
1715 | + for number,data in enumerate(group): |
1716 | + if self.series_colors[number][4] == 'linear': |
1717 | + linear = cairo.LinearGradient( x0, data.content*self.steps[VERT]/2, x0 + inner_step, data.content*self.steps[VERT]/2 ) |
1718 | + color = self.series_colors[number] |
1719 | + 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) |
1720 | + linear.add_color_stop_rgba(1.0, *color[:4]) |
1721 | + self.context.set_source(linear) |
1722 | + elif self.series_colors[number][4] == 'solid': |
1723 | + self.context.set_source_rgba(*self.series_colors[number][:4]) |
1724 | + if self.rounded_corners and data.content != 0: |
1725 | + BarPlot.draw_round_rectangle(self, x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top) |
1726 | + self.context.fill() |
1727 | + elif self.three_dimension: |
1728 | + self.draw_3d_rectangle_front(x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top, 5) |
1729 | + self.context.fill() |
1730 | + self.draw_3d_rectangle_side(x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top, 5) |
1731 | + self.context.fill() |
1732 | + self.draw_3d_rectangle_top(x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top, 5) |
1733 | + self.context.fill() |
1734 | + else: |
1735 | + self.context.rectangle(x0, self.plot_top - data.content*self.steps[VERT], inner_step, data.content*self.steps[VERT]) |
1736 | + if (i, number) in self.bar_borders: |
1737 | + self.context.fill_preserve() |
1738 | + self.context.set_source_rgb(1, 0, 0) |
1739 | + self.context.stroke() |
1740 | + else: |
1741 | + self.context.fill() |
1742 | + |
1743 | + x0 += inner_step |
1744 | + |
1745 | +class StreamChart(VerticalBarPlot): |
1746 | + def __init__(self, |
1747 | + surface = None, |
1748 | + data = None, |
1749 | + width = 640, |
1750 | + height = 480, |
1751 | + background = "white light_gray", |
1752 | + border = 0, |
1753 | + grid = False, |
1754 | + series_legend = None, |
1755 | + x_labels = None, |
1756 | + x_bounds = None, |
1757 | + y_bounds = None, |
1758 | + series_colors = None): |
1759 | + |
1760 | + VerticalBarPlot.__init__(self, surface, data, width, height, background, border, |
1761 | + False, grid, False, True, False, |
1762 | + None, x_labels, None, x_bounds, y_bounds, series_colors) |
1763 | + |
1764 | + def calc_steps(self): |
1765 | + other_dir = other_direction(self.main_dir) |
1766 | + self.series_amplitude = self.bounds[self.main_dir][1] - self.bounds[self.main_dir][0] |
1767 | + if self.series_amplitude: |
1768 | + self.steps[self.main_dir] = float(self.plot_dimensions[self.main_dir])/self.series_amplitude |
1769 | + else: |
1770 | + self.steps[self.main_dir] = 0.00 |
1771 | + series_length = len(self.data) |
1772 | + self.steps[other_dir] = float(self.plot_dimensions[other_dir])/series_length |
1773 | + |
1774 | + def render_legend(self): |
1775 | + pass |
1776 | + |
1777 | + def ground(self, index): |
1778 | + sum_values = sum(self.data[index]) |
1779 | + return -0.5*sum_values |
1780 | + |
1781 | + def calc_angles(self): |
1782 | + middle = self.plot_top - self.plot_dimensions[VERT]/2.0 |
1783 | + self.angles = [tuple([0.0 for x in range(len(self.data)+1)])] |
1784 | + for x_index in range(1, len(self.data)-1): |
1785 | + t = [] |
1786 | + x0 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ] |
1787 | + x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ] |
1788 | + y0 = middle - self.ground(x_index-1)*self.steps[VERT] |
1789 | + y2 = middle - self.ground(x_index+1)*self.steps[VERT] |
1790 | + t.append(math.atan(float(y0-y2)/(x0-x2))) |
1791 | + for data_index in range(len(self.data[x_index])): |
1792 | + x0 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ] |
1793 | + x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ] |
1794 | + y0 = middle - self.ground(x_index-1)*self.steps[VERT] - self.data[x_index-1][data_index]*self.steps[VERT] |
1795 | + y2 = middle - self.ground(x_index+1)*self.steps[VERT] - self.data[x_index+1][data_index]*self.steps[VERT] |
1796 | + |
1797 | + for i in range(0,data_index): |
1798 | + y0 -= self.data[x_index-1][i]*self.steps[VERT] |
1799 | + y2 -= self.data[x_index+1][i]*self.steps[VERT] |
1800 | + |
1801 | + if data_index == len(self.data[0])-1 and False: |
1802 | + self.context.set_source_rgba(0.0,0.0,0.0,0.3) |
1803 | + self.context.move_to(x0,y0) |
1804 | + self.context.line_to(x2,y2) |
1805 | + self.context.stroke() |
1806 | + self.context.arc(x0,y0,2,0,2*math.pi) |
1807 | + self.context.fill() |
1808 | + t.append(math.atan(float(y0-y2)/(x0-x2))) |
1809 | + self.angles.append(tuple(t)) |
1810 | + self.angles.append(tuple([0.0 for x in range(len(self.data)+1)])) |
1811 | + |
1812 | + def render_plot(self): |
1813 | + self.calc_angles() |
1814 | + middle = self.plot_top - self.plot_dimensions[VERT]/2.0 |
1815 | + p = 0.4*self.steps[HORZ] |
1816 | + for data_index in range(len(self.data[0])-1,-1,-1): |
1817 | + self.context.set_source_rgba(*self.series_colors[data_index][:4]) |
1818 | + |
1819 | + #draw the upper line |
1820 | + for x_index in range(len(self.data)-1) : |
1821 | + x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ] |
1822 | + y1 = middle - self.ground(x_index)*self.steps[VERT] - self.data[x_index][data_index]*self.steps[VERT] |
1823 | + x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ] |
1824 | + y2 = middle - self.ground(x_index + 1)*self.steps[VERT] - self.data[x_index + 1][data_index]*self.steps[VERT] |
1825 | + |
1826 | + for i in range(0,data_index): |
1827 | + y1 -= self.data[x_index][i]*self.steps[VERT] |
1828 | + y2 -= self.data[x_index+1][i]*self.steps[VERT] |
1829 | + |
1830 | + if x_index == 0: |
1831 | + self.context.move_to(x1,y1) |
1832 | + |
1833 | + ang1 = self.angles[x_index][data_index+1] |
1834 | + ang2 = self.angles[x_index+1][data_index+1] + math.pi |
1835 | + self.context.curve_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1), |
1836 | + x2+p*math.cos(ang2),y2+p*math.sin(ang2), |
1837 | + x2,y2) |
1838 | + |
1839 | + for x_index in range(len(self.data)-1,0,-1) : |
1840 | + x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ] |
1841 | + y1 = middle - self.ground(x_index)*self.steps[VERT] |
1842 | + x2 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ] |
1843 | + y2 = middle - self.ground(x_index - 1)*self.steps[VERT] |
1844 | + |
1845 | + for i in range(0,data_index): |
1846 | + y1 -= self.data[x_index][i]*self.steps[VERT] |
1847 | + y2 -= self.data[x_index-1][i]*self.steps[VERT] |
1848 | + |
1849 | + if x_index == len(self.data)-1: |
1850 | + self.context.line_to(x1,y1+2) |
1851 | + |
1852 | + #revert angles by pi degrees to take the turn back |
1853 | + ang1 = self.angles[x_index][data_index] + math.pi |
1854 | + ang2 = self.angles[x_index-1][data_index] |
1855 | + self.context.curve_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1), |
1856 | + x2+p*math.cos(ang2),y2+p*math.sin(ang2), |
1857 | + x2,y2+2) |
1858 | + |
1859 | + self.context.close_path() |
1860 | + self.context.fill() |
1861 | + |
1862 | + if False: |
1863 | + self.context.move_to(self.borders[HORZ] + 0.5*self.steps[HORZ], middle) |
1864 | + for x_index in range(len(self.data)-1) : |
1865 | + x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ] |
1866 | + y1 = middle - self.ground(x_index)*self.steps[VERT] - self.data[x_index][data_index]*self.steps[VERT] |
1867 | + x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ] |
1868 | + y2 = middle - self.ground(x_index + 1)*self.steps[VERT] - self.data[x_index + 1][data_index]*self.steps[VERT] |
1869 | + |
1870 | + for i in range(0,data_index): |
1871 | + y1 -= self.data[x_index][i]*self.steps[VERT] |
1872 | + y2 -= self.data[x_index+1][i]*self.steps[VERT] |
1873 | + |
1874 | + ang1 = self.angles[x_index][data_index+1] |
1875 | + ang2 = self.angles[x_index+1][data_index+1] + math.pi |
1876 | + self.context.set_source_rgba(1.0,0.0,0.0) |
1877 | + self.context.arc(x1+p*math.cos(ang1),y1+p*math.sin(ang1),2,0,2*math.pi) |
1878 | + self.context.fill() |
1879 | + self.context.set_source_rgba(0.0,0.0,0.0) |
1880 | + self.context.arc(x2+p*math.cos(ang2),y2+p*math.sin(ang2),2,0,2*math.pi) |
1881 | + self.context.fill() |
1882 | + '''self.context.set_source_rgba(0.0,0.0,0.0,0.3) |
1883 | + self.context.arc(x2,y2,2,0,2*math.pi) |
1884 | + self.context.fill()''' |
1885 | + self.context.move_to(x1,y1) |
1886 | + self.context.line_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1)) |
1887 | + self.context.stroke() |
1888 | + self.context.move_to(x2,y2) |
1889 | + self.context.line_to(x2+p*math.cos(ang2),y2+p*math.sin(ang2)) |
1890 | + self.context.stroke() |
1891 | + if False: |
1892 | + for x_index in range(len(self.data)-1,0,-1) : |
1893 | + x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ] |
1894 | + y1 = middle - self.ground(x_index)*self.steps[VERT] |
1895 | + x2 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ] |
1896 | + y2 = middle - self.ground(x_index - 1)*self.steps[VERT] |
1897 | + |
1898 | + for i in range(0,data_index): |
1899 | + y1 -= self.data[x_index][i]*self.steps[VERT] |
1900 | + y2 -= self.data[x_index-1][i]*self.steps[VERT] |
1901 | + |
1902 | + #revert angles by pi degrees to take the turn back |
1903 | + ang1 = self.angles[x_index][data_index] + math.pi |
1904 | + ang2 = self.angles[x_index-1][data_index] |
1905 | + self.context.set_source_rgba(0.0,1.0,0.0) |
1906 | + self.context.arc(x1+p*math.cos(ang1),y1+p*math.sin(ang1),2,0,2*math.pi) |
1907 | + self.context.fill() |
1908 | + self.context.set_source_rgba(0.0,0.0,1.0) |
1909 | + self.context.arc(x2+p*math.cos(ang2),y2+p*math.sin(ang2),2,0,2*math.pi) |
1910 | + self.context.fill() |
1911 | + '''self.context.set_source_rgba(0.0,0.0,0.0,0.3) |
1912 | + self.context.arc(x2,y2,2,0,2*math.pi) |
1913 | + self.context.fill()''' |
1914 | + self.context.move_to(x1,y1) |
1915 | + self.context.line_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1)) |
1916 | + self.context.stroke() |
1917 | + self.context.move_to(x2,y2) |
1918 | + self.context.line_to(x2+p*math.cos(ang2),y2+p*math.sin(ang2)) |
1919 | + self.context.stroke() |
1920 | + #break |
1921 | + |
1922 | + #self.context.arc(self.dimensions[HORZ]/2, self.dimensions[VERT]/2,50,0,3*math.pi/2) |
1923 | + #self.context.fill() |
1924 | + |
1925 | + |
1926 | +class PiePlot(Plot): |
1927 | + #TODO: Check the old cairoplot, graphs aren't matching |
1928 | + def __init__ (self, |
1929 | + surface = None, |
1930 | + data = None, |
1931 | + width = 640, |
1932 | + height = 480, |
1933 | + background = "white light_gray", |
1934 | + gradient = False, |
1935 | + shadow = False, |
1936 | + colors = None): |
1937 | + |
1938 | + Plot.__init__( self, surface, data, width, height, background, series_colors = colors ) |
1939 | + self.center = (self.dimensions[HORZ]/2, self.dimensions[VERT]/2) |
1940 | + self.total = sum( self.series.to_list() ) |
1941 | + self.radius = min(self.dimensions[HORZ]/3,self.dimensions[VERT]/3) |
1942 | + self.gradient = gradient |
1943 | + self.shadow = shadow |
1944 | + |
1945 | + def sort_function(x,y): |
1946 | + return x.content - y.content |
1947 | + |
1948 | + def load_series(self, data, x_labels=None, y_labels=None, series_colors=None): |
1949 | + Plot.load_series(self, data, x_labels, y_labels, series_colors) |
1950 | + # Already done inside series |
1951 | + #self.data = sorted(self.data) |
1952 | + |
1953 | + def draw_piece(self, angle, next_angle): |
1954 | + self.context.move_to(self.center[0],self.center[1]) |
1955 | + self.context.line_to(self.center[0] + self.radius*math.cos(angle), self.center[1] + self.radius*math.sin(angle)) |
1956 | + self.context.arc(self.center[0], self.center[1], self.radius, angle, next_angle) |
1957 | + self.context.line_to(self.center[0], self.center[1]) |
1958 | + self.context.close_path() |
1959 | + |
1960 | + def render(self): |
1961 | + self.render_background() |
1962 | + self.render_bounding_box() |
1963 | + if self.shadow: |
1964 | + self.render_shadow() |
1965 | + self.render_plot() |
1966 | + self.render_series_labels() |
1967 | + |
1968 | + def render_shadow(self): |
1969 | + horizontal_shift = 3 |
1970 | + vertical_shift = 3 |
1971 | + self.context.set_source_rgba(0, 0, 0, 0.5) |
1972 | + self.context.arc(self.center[0] + horizontal_shift, self.center[1] + vertical_shift, self.radius, 0, 2*math.pi) |
1973 | + self.context.fill() |
1974 | + |
1975 | + def render_series_labels(self): |
1976 | + angle = 0 |
1977 | + next_angle = 0 |
1978 | + x0,y0 = self.center |
1979 | + cr = self.context |
1980 | + for number,key in enumerate(self.series_labels): |
1981 | + # self.data[number] should be just a number |
1982 | + data = sum(self.series[number].to_list()) |
1983 | + |
1984 | + next_angle = angle + 2.0*math.pi*data/self.total |
1985 | + cr.set_source_rgba(*self.series_colors[number][:4]) |
1986 | + w = cr.text_extents(key)[2] |
1987 | + if (angle + next_angle)/2 < math.pi/2 or (angle + next_angle)/2 > 3*math.pi/2: |
1988 | + cr.move_to(x0 + (self.radius+10)*math.cos((angle+next_angle)/2), y0 + (self.radius+10)*math.sin((angle+next_angle)/2) ) |
1989 | + else: |
1990 | + cr.move_to(x0 + (self.radius+10)*math.cos((angle+next_angle)/2) - w, y0 + (self.radius+10)*math.sin((angle+next_angle)/2) ) |
1991 | + cr.show_text(key) |
1992 | + angle = next_angle |
1993 | + |
1994 | + def render_plot(self): |
1995 | + angle = 0 |
1996 | + next_angle = 0 |
1997 | + x0,y0 = self.center |
1998 | + cr = self.context |
1999 | + for number,group in enumerate(self.series): |
2000 | + # Group should be just a number |
2001 | + data = sum(group.to_list()) |
2002 | + next_angle = angle + 2.0*math.pi*data/self.total |
2003 | + if self.gradient or self.series_colors[number][4] in ('linear','radial'): |
2004 | + gradient_color = cairo.RadialGradient(self.center[0], self.center[1], 0, self.center[0], self.center[1], self.radius) |
2005 | + gradient_color.add_color_stop_rgba(0.3, *self.series_colors[number][:4]) |
2006 | + gradient_color.add_color_stop_rgba(1, self.series_colors[number][0]*0.7, |
2007 | + self.series_colors[number][1]*0.7, |
2008 | + self.series_colors[number][2]*0.7, |
2009 | + self.series_colors[number][3]) |
2010 | + cr.set_source(gradient_color) |
2011 | + else: |
2012 | + cr.set_source_rgba(*self.series_colors[number][:4]) |
2013 | + |
2014 | + self.draw_piece(angle, next_angle) |
2015 | + cr.fill() |
2016 | + |
2017 | + cr.set_source_rgba(1.0, 1.0, 1.0) |
2018 | + self.draw_piece(angle, next_angle) |
2019 | + cr.stroke() |
2020 | + |
2021 | + angle = next_angle |
2022 | + |
2023 | +class DonutPlot(PiePlot): |
2024 | + def __init__ (self, |
2025 | + surface = None, |
2026 | + data = None, |
2027 | + width = 640, |
2028 | + height = 480, |
2029 | + background = "white light_gray", |
2030 | + gradient = False, |
2031 | + shadow = False, |
2032 | + colors = None, |
2033 | + inner_radius=-1): |
2034 | + |
2035 | + Plot.__init__( self, surface, data, width, height, background, series_colors = colors ) |
2036 | + |
2037 | + self.center = ( self.dimensions[HORZ]/2, self.dimensions[VERT]/2 ) |
2038 | + self.total = sum( self.series.to_list() ) |
2039 | + self.radius = min( self.dimensions[HORZ]/3,self.dimensions[VERT]/3 ) |
2040 | + self.inner_radius = inner_radius*self.radius |
2041 | + |
2042 | + if inner_radius == -1: |
2043 | + self.inner_radius = self.radius/3 |
2044 | + |
2045 | + self.gradient = gradient |
2046 | + self.shadow = shadow |
2047 | + |
2048 | + def draw_piece(self, angle, next_angle): |
2049 | + self.context.move_to(self.center[0] + (self.inner_radius)*math.cos(angle), self.center[1] + (self.inner_radius)*math.sin(angle)) |
2050 | + self.context.line_to(self.center[0] + self.radius*math.cos(angle), self.center[1] + self.radius*math.sin(angle)) |
2051 | + self.context.arc(self.center[0], self.center[1], self.radius, angle, next_angle) |
2052 | + self.context.line_to(self.center[0] + (self.inner_radius)*math.cos(next_angle), self.center[1] + (self.inner_radius)*math.sin(next_angle)) |
2053 | + self.context.arc_negative(self.center[0], self.center[1], self.inner_radius, next_angle, angle) |
2054 | + self.context.close_path() |
2055 | + |
2056 | + def render_shadow(self): |
2057 | + horizontal_shift = 3 |
2058 | + vertical_shift = 3 |
2059 | + self.context.set_source_rgba(0, 0, 0, 0.5) |
2060 | + self.context.arc(self.center[0] + horizontal_shift, self.center[1] + vertical_shift, self.inner_radius, 0, 2*math.pi) |
2061 | + self.context.arc_negative(self.center[0] + horizontal_shift, self.center[1] + vertical_shift, self.radius, 0, -2*math.pi) |
2062 | + self.context.fill() |
2063 | + |
2064 | +class GanttChart (Plot) : |
2065 | + def __init__(self, |
2066 | + surface = None, |
2067 | + data = None, |
2068 | + width = 640, |
2069 | + height = 480, |
2070 | + x_labels = None, |
2071 | + y_labels = None, |
2072 | + colors = None): |
2073 | + self.bounds = {} |
2074 | + self.max_value = {} |
2075 | + Plot.__init__(self, surface, data, width, height, x_labels = x_labels, y_labels = y_labels, series_colors = colors) |
2076 | + |
2077 | + def load_series(self, data, x_labels=None, y_labels=None, series_colors=None): |
2078 | + Plot.load_series(self, data, x_labels, y_labels, series_colors) |
2079 | + self.calc_boundaries() |
2080 | + |
2081 | + def calc_boundaries(self): |
2082 | + self.bounds[HORZ] = (0,len(self.series)) |
2083 | + end_pos = max(self.series.to_list()) |
2084 | + |
2085 | + #for group in self.series: |
2086 | + # if hasattr(item, "__delitem__"): |
2087 | + # for sub_item in item: |
2088 | + # end_pos = max(sub_item) |
2089 | + # else: |
2090 | + # end_pos = max(item) |
2091 | + self.bounds[VERT] = (0,end_pos) |
2092 | + |
2093 | + def calc_extents(self, direction): |
2094 | + self.max_value[direction] = 0 |
2095 | + if self.labels[direction]: |
2096 | + self.max_value[direction] = max(self.context.text_extents(item)[2] for item in self.labels[direction]) |
2097 | + else: |
2098 | + self.max_value[direction] = self.context.text_extents( str(self.bounds[direction][1] + 1) )[2] |
2099 | + |
2100 | + def calc_horz_extents(self): |
2101 | + self.calc_extents(HORZ) |
2102 | + self.borders[HORZ] = 100 + self.max_value[HORZ] |
2103 | + |
2104 | + def calc_vert_extents(self): |
2105 | + self.calc_extents(VERT) |
2106 | + self.borders[VERT] = self.dimensions[VERT]/(self.bounds[HORZ][1] + 1) |
2107 | + |
2108 | + def calc_steps(self): |
2109 | + self.horizontal_step = (self.dimensions[HORZ] - self.borders[HORZ])/(len(self.labels[VERT])) |
2110 | + self.vertical_step = self.borders[VERT] |
2111 | + |
2112 | + def render(self): |
2113 | + self.calc_horz_extents() |
2114 | + self.calc_vert_extents() |
2115 | + self.calc_steps() |
2116 | + self.render_background() |
2117 | + |
2118 | + self.render_labels() |
2119 | + self.render_grid() |
2120 | + self.render_plot() |
2121 | + |
2122 | + def render_background(self): |
2123 | + cr = self.context |
2124 | + cr.set_source_rgba(255,255,255) |
2125 | + cr.rectangle(0,0,self.dimensions[HORZ], self.dimensions[VERT]) |
2126 | + cr.fill() |
2127 | + for number,group in enumerate(self.series): |
2128 | + linear = cairo.LinearGradient(self.dimensions[HORZ]/2, self.borders[VERT] + number*self.vertical_step, |
2129 | + self.dimensions[HORZ]/2, self.borders[VERT] + (number+1)*self.vertical_step) |
2130 | + linear.add_color_stop_rgba(0,1.0,1.0,1.0,1.0) |
2131 | + linear.add_color_stop_rgba(1.0,0.9,0.9,0.9,1.0) |
2132 | + cr.set_source(linear) |
2133 | + cr.rectangle(0,self.borders[VERT] + number*self.vertical_step,self.dimensions[HORZ],self.vertical_step) |
2134 | + cr.fill() |
2135 | + |
2136 | + def render_grid(self): |
2137 | + cr = self.context |
2138 | + cr.set_source_rgba(0.7, 0.7, 0.7) |
2139 | + cr.set_dash((1,0,0,0,0,0,1)) |
2140 | + cr.set_line_width(0.5) |
2141 | + for number,label in enumerate(self.labels[VERT]): |
2142 | + h = cr.text_extents(label)[3] |
2143 | + cr.move_to(self.borders[HORZ] + number*self.horizontal_step, self.vertical_step/2 + h) |
2144 | + cr.line_to(self.borders[HORZ] + number*self.horizontal_step, self.dimensions[VERT]) |
2145 | + cr.stroke() |
2146 | + |
2147 | + def render_labels(self): |
2148 | + self.context.set_font_size(0.02 * self.dimensions[HORZ]) |
2149 | + |
2150 | + self.render_horz_labels() |
2151 | + self.render_vert_labels() |
2152 | + |
2153 | + def render_horz_labels(self): |
2154 | + cr = self.context |
2155 | + labels = self.labels[HORZ] |
2156 | + if not labels: |
2157 | + labels = [str(i) for i in range(1, self.bounds[HORZ][1] + 1) ] |
2158 | + for number,label in enumerate(labels): |
2159 | + if label != None: |
2160 | + cr.set_source_rgba(0.5, 0.5, 0.5) |
2161 | + w,h = cr.text_extents(label)[2], cr.text_extents(label)[3] |
2162 | + cr.move_to(40,self.borders[VERT] + number*self.vertical_step + self.vertical_step/2 + h/2) |
2163 | + cr.show_text(label) |
2164 | + |
2165 | + def render_vert_labels(self): |
2166 | + cr = self.context |
2167 | + labels = self.labels[VERT] |
2168 | + if not labels: |
2169 | + labels = [str(i) for i in range(1, self.bounds[VERT][1] + 1) ] |
2170 | + for number,label in enumerate(labels): |
2171 | + w,h = cr.text_extents(label)[2], cr.text_extents(label)[3] |
2172 | + cr.move_to(self.borders[HORZ] + number*self.horizontal_step - w/2, self.vertical_step/2) |
2173 | + cr.show_text(label) |
2174 | + |
2175 | + def render_rectangle(self, x0, y0, x1, y1, color): |
2176 | + self.draw_shadow(x0, y0, x1, y1) |
2177 | + self.draw_rectangle(x0, y0, x1, y1, color) |
2178 | + |
2179 | + def draw_rectangular_shadow(self, gradient, x0, y0, w, h): |
2180 | + self.context.set_source(gradient) |
2181 | + self.context.rectangle(x0,y0,w,h) |
2182 | + self.context.fill() |
2183 | + |
2184 | + def draw_circular_shadow(self, x, y, radius, ang_start, ang_end, mult, shadow): |
2185 | + gradient = cairo.RadialGradient(x, y, 0, x, y, 2*radius) |
2186 | + gradient.add_color_stop_rgba(0, 0, 0, 0, shadow) |
2187 | + gradient.add_color_stop_rgba(1, 0, 0, 0, 0) |
2188 | + self.context.set_source(gradient) |
2189 | + self.context.move_to(x,y) |
2190 | + self.context.line_to(x + mult[0]*radius,y + mult[1]*radius) |
2191 | + self.context.arc(x, y, 8, ang_start, ang_end) |
2192 | + self.context.line_to(x,y) |
2193 | + self.context.close_path() |
2194 | + self.context.fill() |
2195 | + |
2196 | + def draw_rectangle(self, x0, y0, x1, y1, color): |
2197 | + cr = self.context |
2198 | + middle = (x0+x1)/2 |
2199 | + linear = cairo.LinearGradient(middle,y0,middle,y1) |
2200 | + 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) |
2201 | + linear.add_color_stop_rgba(1,*color[:4]) |
2202 | + cr.set_source(linear) |
2203 | + |
2204 | + cr.arc(x0+5, y0+5, 5, 0, 2*math.pi) |
2205 | + cr.arc(x1-5, y0+5, 5, 0, 2*math.pi) |
2206 | + cr.arc(x0+5, y1-5, 5, 0, 2*math.pi) |
2207 | + cr.arc(x1-5, y1-5, 5, 0, 2*math.pi) |
2208 | + cr.rectangle(x0+5,y0,x1-x0-10,y1-y0) |
2209 | + cr.rectangle(x0,y0+5,x1-x0,y1-y0-10) |
2210 | + cr.fill() |
2211 | + |
2212 | + def draw_shadow(self, x0, y0, x1, y1): |
2213 | + shadow = 0.4 |
2214 | + h_mid = (x0+x1)/2 |
2215 | + v_mid = (y0+y1)/2 |
2216 | + h_linear_1 = cairo.LinearGradient(h_mid,y0-4,h_mid,y0+4) |
2217 | + h_linear_2 = cairo.LinearGradient(h_mid,y1-4,h_mid,y1+4) |
2218 | + v_linear_1 = cairo.LinearGradient(x0-4,v_mid,x0+4,v_mid) |
2219 | + v_linear_2 = cairo.LinearGradient(x1-4,v_mid,x1+4,v_mid) |
2220 | + |
2221 | + h_linear_1.add_color_stop_rgba( 0, 0, 0, 0, 0) |
2222 | + h_linear_1.add_color_stop_rgba( 1, 0, 0, 0, shadow) |
2223 | + h_linear_2.add_color_stop_rgba( 0, 0, 0, 0, shadow) |
2224 | + h_linear_2.add_color_stop_rgba( 1, 0, 0, 0, 0) |
2225 | + v_linear_1.add_color_stop_rgba( 0, 0, 0, 0, 0) |
2226 | + v_linear_1.add_color_stop_rgba( 1, 0, 0, 0, shadow) |
2227 | + v_linear_2.add_color_stop_rgba( 0, 0, 0, 0, shadow) |
2228 | + v_linear_2.add_color_stop_rgba( 1, 0, 0, 0, 0) |
2229 | + |
2230 | + self.draw_rectangular_shadow(h_linear_1,x0+4,y0-4,x1-x0-8,8) |
2231 | + self.draw_rectangular_shadow(h_linear_2,x0+4,y1-4,x1-x0-8,8) |
2232 | + self.draw_rectangular_shadow(v_linear_1,x0-4,y0+4,8,y1-y0-8) |
2233 | + self.draw_rectangular_shadow(v_linear_2,x1-4,y0+4,8,y1-y0-8) |
2234 | + |
2235 | + self.draw_circular_shadow(x0+4, y0+4, 4, math.pi, 3*math.pi/2, (-1,0), shadow) |
2236 | + self.draw_circular_shadow(x1-4, y0+4, 4, 3*math.pi/2, 2*math.pi, (0,-1), shadow) |
2237 | + self.draw_circular_shadow(x0+4, y1-4, 4, math.pi/2, math.pi, (0,1), shadow) |
2238 | + self.draw_circular_shadow(x1-4, y1-4, 4, 0, math.pi/2, (1,0), shadow) |
2239 | + |
2240 | + def render_plot(self): |
2241 | + for index,group in enumerate(self.series): |
2242 | + for data in group: |
2243 | + self.render_rectangle(self.borders[HORZ] + data.content[0]*self.horizontal_step, |
2244 | + self.borders[VERT] + index*self.vertical_step + self.vertical_step/4.0, |
2245 | + self.borders[HORZ] + data.content[1]*self.horizontal_step, |
2246 | + self.borders[VERT] + index*self.vertical_step + 3.0*self.vertical_step/4.0, |
2247 | + self.series_colors[index]) |
2248 | + |
2249 | +# Function definition |
2250 | + |
2251 | +def scatter_plot(name, |
2252 | + data = None, |
2253 | + errorx = None, |
2254 | + errory = None, |
2255 | + width = 640, |
2256 | + height = 480, |
2257 | + background = "white light_gray", |
2258 | + border = 0, |
2259 | + axis = False, |
2260 | + dash = False, |
2261 | + discrete = False, |
2262 | + dots = False, |
2263 | + grid = False, |
2264 | + series_legend = False, |
2265 | + x_labels = None, |
2266 | + y_labels = None, |
2267 | + x_bounds = None, |
2268 | + y_bounds = None, |
2269 | + z_bounds = None, |
2270 | + x_title = None, |
2271 | + y_title = None, |
2272 | + series_colors = None, |
2273 | + circle_colors = None): |
2274 | + |
2275 | + ''' |
2276 | + - Function to plot scatter data. |
2277 | + |
2278 | + - Parameters |
2279 | + |
2280 | + data - The values to be ploted might be passed in a two basic: |
2281 | + list of points: [(0,0), (0,1), (0,2)] or [(0,0,1), (0,1,4), (0,2,1)] |
2282 | + lists of coordinates: [ [0,0,0] , [0,1,2] ] or [ [0,0,0] , [0,1,2] , [1,4,1] ] |
2283 | + Notice that these kinds of that can be grouped in order to form more complex data |
2284 | + using lists of lists or dictionaries; |
2285 | + series_colors - Define color values for each of the series |
2286 | + circle_colors - Define a lower and an upper bound for the circle colors for variable radius |
2287 | + (3 dimensions) series |
2288 | + ''' |
2289 | + |
2290 | + plot = ScatterPlot( name, data, errorx, errory, width, height, background, border, |
2291 | + axis, dash, discrete, dots, grid, series_legend, x_labels, y_labels, |
2292 | + x_bounds, y_bounds, z_bounds, x_title, y_title, series_colors, circle_colors ) |
2293 | + plot.render() |
2294 | + plot.commit() |
2295 | + |
2296 | +def dot_line_plot(name, |
2297 | + data, |
2298 | + width, |
2299 | + height, |
2300 | + background = "white light_gray", |
2301 | + border = 0, |
2302 | + axis = False, |
2303 | + dash = False, |
2304 | + dots = False, |
2305 | + grid = False, |
2306 | + series_legend = False, |
2307 | + x_labels = None, |
2308 | + y_labels = None, |
2309 | + x_bounds = None, |
2310 | + y_bounds = None, |
2311 | + x_title = None, |
2312 | + y_title = None, |
2313 | + series_colors = None): |
2314 | + ''' |
2315 | + - Function to plot graphics using dots and lines. |
2316 | + |
2317 | + 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) |
2318 | + |
2319 | + - Parameters |
2320 | + |
2321 | + name - Name of the desired output file, no need to input the .svg as it will be added at runtim; |
2322 | + data - The list, list of lists or dictionary holding the data to be plotted; |
2323 | + width, height - Dimensions of the output image; |
2324 | + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
2325 | + If left None, a gray to white gradient will be generated; |
2326 | + border - Distance in pixels of a square border into which the graphics will be drawn; |
2327 | + axis - Whether or not the axis are to be drawn; |
2328 | + dash - Boolean or a list or a dictionary of booleans indicating whether or not the associated series should be drawn in dashed mode; |
2329 | + dots - Whether or not dots should be drawn on each point; |
2330 | + grid - Whether or not the gris is to be drawn; |
2331 | + series_legend - Whether or not the legend is to be drawn; |
2332 | + x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis; |
2333 | + x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted; |
2334 | + x_title - Whether or not to plot a title over the x axis. |
2335 | + y_title - Whether or not to plot a title over the y axis. |
2336 | + |
2337 | + - Examples of use |
2338 | + |
2339 | + data = [0, 1, 3, 8, 9, 0, 10, 10, 2, 1] |
2340 | + CairoPlot.dot_line_plot('teste', data, 400, 300) |
2341 | + |
2342 | + data = { "john" : [10, 10, 10, 10, 30], "mary" : [0, 0, 3, 5, 15], "philip" : [13, 32, 11, 25, 2] } |
2343 | + x_labels = ["jan/2008", "feb/2008", "mar/2008", "apr/2008", "may/2008" ] |
2344 | + CairoPlot.dot_line_plot( 'test', data, 400, 300, axis = True, grid = True, |
2345 | + series_legend = True, x_labels = x_labels ) |
2346 | + ''' |
2347 | + plot = DotLinePlot( name, data, width, height, background, border, |
2348 | + axis, dash, dots, grid, series_legend, x_labels, y_labels, |
2349 | + x_bounds, y_bounds, x_title, y_title, series_colors ) |
2350 | + plot.render() |
2351 | + plot.commit() |
2352 | + |
2353 | +def function_plot(name, |
2354 | + data, |
2355 | + width, |
2356 | + height, |
2357 | + background = "white light_gray", |
2358 | + border = 0, |
2359 | + axis = True, |
2360 | + dots = False, |
2361 | + discrete = False, |
2362 | + grid = False, |
2363 | + series_legend = False, |
2364 | + x_labels = None, |
2365 | + y_labels = None, |
2366 | + x_bounds = None, |
2367 | + y_bounds = None, |
2368 | + x_title = None, |
2369 | + y_title = None, |
2370 | + series_colors = None, |
2371 | + step = 1): |
2372 | + |
2373 | + ''' |
2374 | + - Function to plot functions. |
2375 | + |
2376 | + 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) |
2377 | + |
2378 | + - Parameters |
2379 | + |
2380 | + name - Name of the desired output file, no need to input the .svg as it will be added at runtim; |
2381 | + data - The list, list of lists or dictionary holding the data to be plotted; |
2382 | + width, height - Dimensions of the output image; |
2383 | + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
2384 | + If left None, a gray to white gradient will be generated; |
2385 | + border - Distance in pixels of a square border into which the graphics will be drawn; |
2386 | + axis - Whether or not the axis are to be drawn; |
2387 | + grid - Whether or not the gris is to be drawn; |
2388 | + dots - Whether or not dots should be shown at each point; |
2389 | + x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis; |
2390 | + x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted; |
2391 | + step - the horizontal distance from one point to the other. The smaller, the smoother the curve will be; |
2392 | + discrete - whether or not the function should be plotted in discrete format. |
2393 | + |
2394 | + - Example of use |
2395 | + |
2396 | + data = lambda x : x**2 |
2397 | + CairoPlot.function_plot('function4', data, 400, 300, grid = True, x_bounds=(-10,10), step = 0.1) |
2398 | + ''' |
2399 | + |
2400 | + plot = FunctionPlot( name, data, width, height, background, border, |
2401 | + axis, discrete, dots, grid, series_legend, x_labels, y_labels, |
2402 | + x_bounds, y_bounds, x_title, y_title, series_colors, step ) |
2403 | + plot.render() |
2404 | + plot.commit() |
2405 | + |
2406 | +def pie_plot( name, data, width, height, background = "white light_gray", gradient = False, shadow = False, colors = None ): |
2407 | + |
2408 | + ''' |
2409 | + - Function to plot pie graphics. |
2410 | + |
2411 | + pie_plot(name, data, width, height, background = "white light_gray", gradient = False, colors = None) |
2412 | + |
2413 | + - Parameters |
2414 | + |
2415 | + name - Name of the desired output file, no need to input the .svg as it will be added at runtim; |
2416 | + data - The list, list of lists or dictionary holding the data to be plotted; |
2417 | + width, height - Dimensions of the output image; |
2418 | + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
2419 | + If left None, a gray to white gradient will be generated; |
2420 | + gradient - Whether or not the pie color will be painted with a gradient; |
2421 | + shadow - Whether or not there will be a shadow behind the pie; |
2422 | + colors - List of slices colors. |
2423 | + |
2424 | + - Example of use |
2425 | + |
2426 | + teste_data = {"john" : 123, "mary" : 489, "philip" : 890 , "suzy" : 235} |
2427 | + CairoPlot.pie_plot("pie_teste", teste_data, 500, 500) |
2428 | + ''' |
2429 | + |
2430 | + plot = PiePlot( name, data, width, height, background, gradient, shadow, colors ) |
2431 | + plot.render() |
2432 | + plot.commit() |
2433 | + |
2434 | +def donut_plot(name, data, width, height, background = "white light_gray", gradient = False, shadow = False, colors = None, inner_radius = -1): |
2435 | + |
2436 | + ''' |
2437 | + - Function to plot donut graphics. |
2438 | + |
2439 | + donut_plot(name, data, width, height, background = "white light_gray", gradient = False, inner_radius = -1) |
2440 | + |
2441 | + - Parameters |
2442 | + |
2443 | + name - Name of the desired output file, no need to input the .svg as it will be added at runtim; |
2444 | + data - The list, list of lists or dictionary holding the data to be plotted; |
2445 | + width, height - Dimensions of the output image; |
2446 | + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
2447 | + If left None, a gray to white gradient will be generated; |
2448 | + shadow - Whether or not there will be a shadow behind the donut; |
2449 | + gradient - Whether or not the donut color will be painted with a gradient; |
2450 | + colors - List of slices colors; |
2451 | + inner_radius - The radius of the donut's inner circle. |
2452 | + |
2453 | + - Example of use |
2454 | + |
2455 | + teste_data = {"john" : 123, "mary" : 489, "philip" : 890 , "suzy" : 235} |
2456 | + CairoPlot.donut_plot("donut_teste", teste_data, 500, 500) |
2457 | + ''' |
2458 | + |
2459 | + plot = DonutPlot(name, data, width, height, background, gradient, shadow, colors, inner_radius) |
2460 | + plot.render() |
2461 | + plot.commit() |
2462 | + |
2463 | +def gantt_chart(name, pieces, width, height, x_labels, y_labels, colors): |
2464 | + |
2465 | + ''' |
2466 | + - Function to generate Gantt Charts. |
2467 | + |
2468 | + gantt_chart(name, pieces, width, height, x_labels, y_labels, colors): |
2469 | + |
2470 | + - Parameters |
2471 | + |
2472 | + name - Name of the desired output file, no need to input the .svg as it will be added at runtim; |
2473 | + 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; |
2474 | + width, height - Dimensions of the output image; |
2475 | + x_labels - A list of names for each of the vertical lines; |
2476 | + y_labels - A list of names for each of the horizontal spaces; |
2477 | + colors - List containing the colors expected for each of the horizontal spaces |
2478 | + |
2479 | + - Example of use |
2480 | + |
2481 | + pieces = [ (0.5,5.5) , [(0,4),(6,8)] , (5.5,7) , (7,8)] |
2482 | + x_labels = [ 'teste01', 'teste02', 'teste03', 'teste04'] |
2483 | + y_labels = [ '0001', '0002', '0003', '0004', '0005', '0006', '0007', '0008', '0009', '0010' ] |
2484 | + 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) ] |
2485 | + CairoPlot.gantt_chart('gantt_teste', pieces, 600, 300, x_labels, y_labels, colors) |
2486 | + ''' |
2487 | + |
2488 | + plot = GanttChart(name, pieces, width, height, x_labels, y_labels, colors) |
2489 | + plot.render() |
2490 | + plot.commit() |
2491 | + |
2492 | +def vertical_bar_plot(name, |
2493 | + data, |
2494 | + width, |
2495 | + height, |
2496 | + background = "white light_gray", |
2497 | + border = 0, |
2498 | + display_values = False, |
2499 | + grid = False, |
2500 | + rounded_corners = False, |
2501 | + stack = False, |
2502 | + three_dimension = False, |
2503 | + series_labels = None, |
2504 | + x_labels = None, |
2505 | + y_labels = None, |
2506 | + x_bounds = None, |
2507 | + y_bounds = None, |
2508 | + colors = None, |
2509 | + bar_borders=None): |
2510 | + #TODO: Fix docstring for vertical_bar_plot |
2511 | + ''' |
2512 | + - Function to generate vertical Bar Plot Charts. |
2513 | + |
2514 | + bar_plot(name, data, width, height, background, border, grid, rounded_corners, three_dimension, |
2515 | + x_labels, y_labels, x_bounds, y_bounds, colors): |
2516 | + |
2517 | + - Parameters |
2518 | + |
2519 | + name - Name of the desired output file, no need to input the .svg as it will be added at runtime; |
2520 | + data - The list, list of lists or dictionary holding the data to be plotted; |
2521 | + width, height - Dimensions of the output image; |
2522 | + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
2523 | + If left None, a gray to white gradient will be generated; |
2524 | + border - Distance in pixels of a square border into which the graphics will be drawn; |
2525 | + grid - Whether or not the gris is to be drawn; |
2526 | + rounded_corners - Whether or not the bars should have rounded corners; |
2527 | + three_dimension - Whether or not the bars should be drawn in pseudo 3D; |
2528 | + x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis; |
2529 | + x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted; |
2530 | + colors - List containing the colors expected for each of the bars. |
2531 | + |
2532 | + - Example of use |
2533 | + |
2534 | + data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |
2535 | + CairoPlot.vertical_bar_plot ('bar2', data, 400, 300, border = 20, grid = True, rounded_corners = False) |
2536 | + ''' |
2537 | + |
2538 | + plot = VerticalBarPlot(name, data, width, height, background, border, |
2539 | + display_values, grid, rounded_corners, stack, three_dimension, |
2540 | + series_labels, x_labels, y_labels, x_bounds, y_bounds, colors) |
2541 | + plot.bar_borders = bar_borders or list() |
2542 | + plot.render() |
2543 | + plot.commit() |
2544 | + |
2545 | +def horizontal_bar_plot(name, |
2546 | + data, |
2547 | + width, |
2548 | + height, |
2549 | + background = "white light_gray", |
2550 | + border = 0, |
2551 | + display_values = False, |
2552 | + grid = False, |
2553 | + rounded_corners = False, |
2554 | + stack = False, |
2555 | + three_dimension = False, |
2556 | + series_labels = None, |
2557 | + x_labels = None, |
2558 | + y_labels = None, |
2559 | + x_bounds = None, |
2560 | + y_bounds = None, |
2561 | + colors = None): |
2562 | + |
2563 | + #TODO: Fix docstring for horizontal_bar_plot |
2564 | + ''' |
2565 | + - Function to generate Horizontal Bar Plot Charts. |
2566 | + |
2567 | + bar_plot(name, data, width, height, background, border, grid, rounded_corners, three_dimension, |
2568 | + x_labels, y_labels, x_bounds, y_bounds, colors): |
2569 | + |
2570 | + - Parameters |
2571 | + |
2572 | + name - Name of the desired output file, no need to input the .svg as it will be added at runtime; |
2573 | + data - The list, list of lists or dictionary holding the data to be plotted; |
2574 | + width, height - Dimensions of the output image; |
2575 | + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
2576 | + If left None, a gray to white gradient will be generated; |
2577 | + border - Distance in pixels of a square border into which the graphics will be drawn; |
2578 | + grid - Whether or not the gris is to be drawn; |
2579 | + rounded_corners - Whether or not the bars should have rounded corners; |
2580 | + three_dimension - Whether or not the bars should be drawn in pseudo 3D; |
2581 | + x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis; |
2582 | + x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted; |
2583 | + colors - List containing the colors expected for each of the bars. |
2584 | + |
2585 | + - Example of use |
2586 | + |
2587 | + data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |
2588 | + CairoPlot.bar_plot ('bar2', data, 400, 300, border = 20, grid = True, rounded_corners = False) |
2589 | + ''' |
2590 | + |
2591 | + plot = HorizontalBarPlot(name, data, width, height, background, border, |
2592 | + display_values, grid, rounded_corners, stack, three_dimension, |
2593 | + series_labels, x_labels, y_labels, x_bounds, y_bounds, colors) |
2594 | + plot.render() |
2595 | + plot.commit() |
2596 | + |
2597 | +def stream_chart(name, |
2598 | + data, |
2599 | + width, |
2600 | + height, |
2601 | + background = "white light_gray", |
2602 | + border = 0, |
2603 | + grid = False, |
2604 | + series_legend = None, |
2605 | + x_labels = None, |
2606 | + x_bounds = None, |
2607 | + y_bounds = None, |
2608 | + colors = None): |
2609 | + |
2610 | + #TODO: Fix docstring for horizontal_bar_plot |
2611 | + plot = StreamChart(name, data, width, height, background, border, |
2612 | + grid, series_legend, x_labels, x_bounds, y_bounds, colors) |
2613 | + plot.render() |
2614 | + plot.commit() |
2615 | + |
2616 | + |
2617 | +if __name__ == "__main__": |
2618 | + import tests |
2619 | + import seriestests |
2620 | |
2621 | === added directory 'tools/development/query_sets' |
2622 | === added file 'tools/development/query_sets/timerange_always.txt' |
2623 | --- tools/development/query_sets/timerange_always.txt 1970-01-01 00:00:00 +0000 |
2624 | +++ tools/development/query_sets/timerange_always.txt 2011-12-28 19:50:03 +0000 |
2625 | @@ -0,0 +1,9 @@ |
2626 | +TimeRange.always(), [], StorageState.Any, 100, ResultType.MostRecentEvents |
2627 | +TimeRange.always(), [], StorageState.Any, 100, ResultType.MostRecentSubjects |
2628 | +TimeRange.always(), [], StorageState.Any, 100, ResultType.MostPopularSubjects |
2629 | +TimeRange.always(), [], StorageState.Any, 100, ResultType.MostPopularActor |
2630 | +TimeRange.always(), [], StorageState.Any, 100, ResultType.MostRecentActor |
2631 | +TimeRange.always(), [], StorageState.Any, 100, ResultType.MostRecentSubjectInterpretation |
2632 | +TimeRange.always(), [], StorageState.Any, 100, ResultType.MostPopularSubjectInterpretation |
2633 | +TimeRange.always(), [], StorageState.Any, 100, ResultType.MostRecentMimeType |
2634 | +TimeRange.always(), [], StorageState.Any, 100, ResultType.MostPopularMimeType |
2635 | |
2636 | === added file 'tools/development/query_sets/timerange_interval.txt' |
2637 | --- tools/development/query_sets/timerange_interval.txt 1970-01-01 00:00:00 +0000 |
2638 | +++ tools/development/query_sets/timerange_interval.txt 2011-12-28 19:50:03 +0000 |
2639 | @@ -0,0 +1,9 @@ |
2640 | +(1, 60000), [], StorageState.Any, 6, ResultType.MostRecentEvents |
2641 | +(1, 60000), [], StorageState.Any, 6, ResultType.MostRecentSubjects |
2642 | +(1, 60000), [], StorageState.Any, 6, ResultType.MostPopularSubjects |
2643 | +(1, 60000), [], StorageState.Any, 6, ResultType.MostPopularActor |
2644 | +(1, 60000), [], StorageState.Any, 6, ResultType.MostRecentActor |
2645 | +(1, 60000), [], StorageState.Any, 6, ResultType.MostRecentSubjectInterpretation |
2646 | +(1, 60000), [], StorageState.Any, 6, ResultType.MostPopularSubjectInterpretation |
2647 | +(1, 60000), [], StorageState.Any, 6, ResultType.MostRecentMimeType |
2648 | +(1, 60000), [], StorageState.Any, 6, ResultType.MostPopularMimeType |
2649 | |
2650 | === added file 'tools/development/query_timings.py' |
2651 | --- tools/development/query_timings.py 1970-01-01 00:00:00 +0000 |
2652 | +++ tools/development/query_timings.py 2011-12-28 19:50:03 +0000 |
2653 | @@ -0,0 +1,240 @@ |
2654 | +#! /usr/bin/env python |
2655 | +# -.- coding: utf-8 -.- |
2656 | + |
2657 | +# Zeitgeist |
2658 | +# |
2659 | +# Copyright © 2010 Markus Korn <thekorn@gmx.net> |
2660 | +# Copyright © 2011 Collabora Ltd. |
2661 | +# By Seif Lotfy <seif@lotfy.com> |
2662 | +# |
2663 | +# This program is free software: you can redistribute it and/or modify |
2664 | +# it under the terms of the GNU Lesser General Public License as published by |
2665 | +# the Free Software Foundation, either version 2.1 of the License, or |
2666 | +# (at your option) any later version. |
2667 | +# |
2668 | +# This program is distributed in the hope that it will be useful, |
2669 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2670 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2671 | +# GNU Lesser General Public License for more details. |
2672 | +# |
2673 | +# You should have received a copy of the GNU Lesser General Public License |
2674 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2675 | + |
2676 | +# USAGE |
2677 | +# |
2678 | +# To run the benchmarks on a certain branch of zeitgeist make sure this |
2679 | +# branch is in PYTHONPATH. |
2680 | +# To benchmark and output overall to 'output.json' run |
2681 | +# ./query_overalls.py -o output.json |
2682 | +# If you already have data in the output file and want to merge both |
2683 | +# data sets (build avg.) run |
2684 | +# ./query_overalls.py -m -o output.json |
2685 | +# To plot the data use the '--plot' argument, multible '--plot' arguments |
2686 | +# will define multible series. |
2687 | +# ./query_overalls.py --plot output.json --plot output1.json -o plot.svg |
2688 | +# In short, a run always looks like: |
2689 | +# PYTHONPATH=../trunk tools/development/query_overalls.py --name "lp:zeitgeist" -o trunk.json \ |
2690 | +# --queries tools/development/query_sets/timerange_always.txt |
2691 | +# PYTHONPATH=. tools/development/query_overalls.py --name "lp:some-branch" -o somebranch.json |
2692 | +# PYTHONPATH=. tools/development/query_overalls.py --plot somebranch.json --plot trunk.json -o benchmark.svg |
2693 | + |
2694 | +import os |
2695 | +import random |
2696 | +import time |
2697 | +import json |
2698 | +import sys |
2699 | +import logging |
2700 | + |
2701 | +from optparse import OptionParser |
2702 | +from logging import handlers |
2703 | +from collections import defaultdict |
2704 | + |
2705 | +from zeitgeist.datamodel import TimeRange, StorageState, ResultType |
2706 | +from zeitgeist.datamodel import Event |
2707 | +import benchmark as engine |
2708 | + |
2709 | +from cairoplot import vertical_bar_plot |
2710 | + |
2711 | + |
2712 | +class QueryPlanHandler(handlers.MemoryHandler): |
2713 | + |
2714 | + @staticmethod |
2715 | + def get_plan(msg): |
2716 | + if "SELECT id FROM event_view" not in msg: |
2717 | + return None |
2718 | + msg = msg.splitlines() |
2719 | + if not "PLAN:" in msg: |
2720 | + return None |
2721 | + for plan in msg[msg.index("PLAN:")+1:]: |
2722 | + if "INDEX" not in plan and "PRIMARY KEY" not in plan: |
2723 | + return False |
2724 | + return True |
2725 | + |
2726 | + def __init__(self): |
2727 | + handlers.MemoryHandler.__init__(self, 200000, logging.DEBUG) |
2728 | + self.uses_index = None |
2729 | + |
2730 | + def emit(self, record): |
2731 | + x = self.get_plan(record.msg) |
2732 | + if x is not None: |
2733 | + if not x or self.uses_index is None: |
2734 | + self.uses_index = x |
2735 | + return handlers.MemoryHandler.emit(self, record) |
2736 | + |
2737 | +def get_reference_engine(): |
2738 | + return engine |
2739 | + |
2740 | +def get_query_set(source): |
2741 | + for line in open(source): |
2742 | + yield line.strip() |
2743 | + |
2744 | +def get_cmdline(): |
2745 | + parser = OptionParser() |
2746 | + parser.add_option("-o", dest="output", help="write output to FILE", metavar="FILE") |
2747 | + parser.add_option("--queries", dest="queryset", help="run all queries in FILE", metavar="FILE") |
2748 | + parser.add_option("--name", dest="name", help="name of the data series", metavar="NAME") |
2749 | + parser.add_option("-i", dest="isolated", action="store_true", |
2750 | + default=False, help="run each query isolated") |
2751 | + parser.add_option("-m", dest="merge", action="store_true", |
2752 | + default=False, help="if the datafile already contains data the new data gets merged") |
2753 | + parser.add_option("--plot", dest="plot_files", metavar="DATA_FILE", |
2754 | + action="append", type="str") |
2755 | + parser.add_option("--type", dest="type", help="type of plot") |
2756 | + (options, args) = parser.parse_args() |
2757 | + assert not args |
2758 | + return options |
2759 | + |
2760 | +def get_name(data, alternative_name): |
2761 | + try: |
2762 | + return data["__metadata__"]["name"] |
2763 | + except: |
2764 | + return alternative_name |
2765 | + |
2766 | +def get_data(dataset, query, key): |
2767 | + try: |
2768 | + return float(dataset[query][key]) |
2769 | + except: |
2770 | + return 0.0 |
2771 | + |
2772 | +def compare_queries(a, b): |
2773 | + result = cmp(a.strip().split()[-1], b.strip().split()[-1]) |
2774 | + if result != 0: |
2775 | + return result |
2776 | + return cmp(a[0], b[0]) |
2777 | + |
2778 | +def plot(output_filename, plot_type, *data_files): |
2779 | + raw_data = map(lambda x: json.load(open(x)), data_files) |
2780 | + series_labels = map(lambda x: get_name(x[1], data_files[x[0]]), enumerate(raw_data)) |
2781 | + queries = sorted( |
2782 | + filter(lambda x: x != "__metadata__", set(sum([d.keys() for d in raw_data], []))), |
2783 | + cmp=compare_queries |
2784 | + ) |
2785 | + data = [] |
2786 | + max_value = 0 |
2787 | + no_index = list() |
2788 | + style = plot_type |
2789 | + for n, query in enumerate(queries): |
2790 | + x = [get_data(d, query, style) for d in raw_data] |
2791 | + print x |
2792 | + y = max(x) |
2793 | + idx_border = [not d.get(query, {}).get("uses_index", True) for d in raw_data] |
2794 | + for i, b in enumerate(idx_border): |
2795 | + if b: |
2796 | + no_index.append((n, i)) |
2797 | + if y > max_value: |
2798 | + max_value = y |
2799 | + data.append(x) |
2800 | + y_parts = max_value / float(4) |
2801 | + y_labels = ["%.5fs" %(i*y_parts) for i in range(5)] |
2802 | + vertical_bar_plot( |
2803 | + output_filename, data, len(queries)*400, 600, |
2804 | + x_labels=queries, y_labels=y_labels, |
2805 | + grid=True, series_labels=series_labels, bar_borders=no_index) |
2806 | + |
2807 | + |
2808 | +if __name__ == "__main__": |
2809 | + options = get_cmdline() |
2810 | + if options.plot_files: |
2811 | + if options.type in ("marsh_time", "get_events_time", "find_ids_time", "find_events", "overall"): |
2812 | + assert options.output |
2813 | + plot(options.output, options.type, *options.plot_files) |
2814 | + else: |
2815 | + print "please specify plot type (marsh_time, get_events_time, find_ids_time, find_events, overall)" |
2816 | + else: |
2817 | + engine = get_reference_engine() |
2818 | + result = {} |
2819 | + if options.name: |
2820 | + result["__metadata__"] = { |
2821 | + "name": options.name, |
2822 | + } |
2823 | + if options.output and os.path.exists(options.output): |
2824 | + existing_data = json.load(open(options.output)) |
2825 | + else: |
2826 | + existing_data = {} |
2827 | + logging.basicConfig(level=logging.DEBUG) |
2828 | + for query in get_query_set(options.queryset): |
2829 | + args = eval(query) |
2830 | + start_time = time.time() |
2831 | + logging.getLogger("").removeHandler(logging.getLogger("").handlers[0]) |
2832 | + handler = QueryPlanHandler() |
2833 | + logging.getLogger("").addHandler(handler) |
2834 | + results = {} |
2835 | + for i in xrange (50): |
2836 | + t1 = time.time() |
2837 | + temp = engine.find_events(*args) |
2838 | + temp["overall"] = time.time() - t1 |
2839 | + if len(results.keys()) == 0: |
2840 | + for key in results.keys(): |
2841 | + temp[key] = temp[key] |
2842 | + results = temp |
2843 | + else: |
2844 | + for key in temp.keys(): |
2845 | + if key != "events": |
2846 | + results[key] += temp[key] |
2847 | + print results[key] |
2848 | + |
2849 | + for key in temp.keys(): |
2850 | + if key != "events": |
2851 | + results[key] = results[key]/50 |
2852 | + |
2853 | + print (results.keys()) |
2854 | + |
2855 | + events = results["events"] |
2856 | + run_time = results["find_events"] |
2857 | + find_ids_time = results["overall"] |
2858 | + find_events_time = results["find_event_ids"] |
2859 | + get_events_time = results["get_events"] |
2860 | + marsh_time = results["marsh_events"] |
2861 | + |
2862 | + print "===>", find_ids_time |
2863 | + |
2864 | + if query in existing_data and options.merge: |
2865 | + print "==================================" |
2866 | + counter = existing_data[query].get("counter", 1) |
2867 | + old_time = existing_data[query]["overall"] |
2868 | + run_time = (old_time * counter + run_time)/(counter + 1) |
2869 | + |
2870 | + result[query] = { |
2871 | + "overall": run_time, |
2872 | + "counter": counter + 1, |
2873 | + "find_ids_time": find_ids_time, |
2874 | + "get_events_time": get_events_time, |
2875 | + "find_events": find_events_time, |
2876 | + "marsh_time": marsh_time, |
2877 | + } |
2878 | + else: |
2879 | + result[query] = { |
2880 | + "overall": run_time, |
2881 | + "find_ids_time": find_ids_time, |
2882 | + "get_events_time": get_events_time, |
2883 | + "find_events": find_events_time, |
2884 | + "marsh_time": marsh_time, |
2885 | + } |
2886 | + if options.output: |
2887 | + f = open(options.output, "w") |
2888 | + else: |
2889 | + f = sys.stdout |
2890 | + try: |
2891 | + json.dump(result, f, indent=4) |
2892 | + finally: |
2893 | + f.close() |
2894 | |
2895 | === added file 'tools/development/series.py' |
2896 | --- tools/development/series.py 1970-01-01 00:00:00 +0000 |
2897 | +++ tools/development/series.py 2011-12-28 19:50:03 +0000 |
2898 | @@ -0,0 +1,1140 @@ |
2899 | +#!/usr/bin/env python |
2900 | +# -*- coding: utf-8 -*- |
2901 | + |
2902 | +# Serie.py |
2903 | +# |
2904 | +# Copyright (c) 2008 Magnun Leno da Silva |
2905 | +# |
2906 | +# Author: Magnun Leno da Silva <magnun.leno@gmail.com> |
2907 | +# |
2908 | +# This program is free software; you can redistribute it and/or |
2909 | +# modify it under the terms of the GNU Lesser General Public License |
2910 | +# as published by the Free Software Foundation; either version 2 of |
2911 | +# the License, or (at your option) any later version. |
2912 | +# |
2913 | +# This program is distributed in the hope that it will be useful, |
2914 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2915 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2916 | +# GNU General Public License for more details. |
2917 | +# |
2918 | +# You should have received a copy of the GNU Lesser General Public |
2919 | +# License along with this program; if not, write to the Free Software |
2920 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
2921 | +# USA |
2922 | + |
2923 | +# Contributor: Rodrigo Moreiro Araujo <alf.rodrigo@gmail.com> |
2924 | + |
2925 | +#import cairoplot |
2926 | +import doctest |
2927 | + |
2928 | +NUMTYPES = (int, float, long) |
2929 | +LISTTYPES = (list, tuple) |
2930 | +STRTYPES = (str, unicode) |
2931 | +FILLING_TYPES = ['linear', 'solid', 'gradient'] |
2932 | +DEFAULT_COLOR_FILLING = 'solid' |
2933 | +#TODO: Define default color list |
2934 | +DEFAULT_COLOR_LIST = None |
2935 | + |
2936 | +class Data(object): |
2937 | + ''' |
2938 | + Class that models the main data structure. |
2939 | + It can hold: |
2940 | + - a number type (int, float or long) |
2941 | + - a tuple, witch represents a point and can have 2 or 3 items (x,y,z) |
2942 | + - if a list is passed it will be converted to a tuple. |
2943 | + |
2944 | + obs: In case a tuple is passed it will convert to tuple |
2945 | + ''' |
2946 | + def __init__(self, data=None, name=None, parent=None): |
2947 | + ''' |
2948 | + Starts main atributes from the Data class |
2949 | + @name - Name for each point; |
2950 | + @content - The real data, can be an int, float, long or tuple, which |
2951 | + represents a point (x,y) or (x,y,z); |
2952 | + @parent - A pointer that give the data access to it's parent. |
2953 | + |
2954 | + Usage: |
2955 | + >>> d = Data(name='empty'); print d |
2956 | + empty: () |
2957 | + >>> d = Data((1,1),'point a'); print d |
2958 | + point a: (1, 1) |
2959 | + >>> d = Data((1,2,3),'point b'); print d |
2960 | + point b: (1, 2, 3) |
2961 | + >>> d = Data([2,3],'point c'); print d |
2962 | + point c: (2, 3) |
2963 | + >>> d = Data(12, 'simple value'); print d |
2964 | + simple value: 12 |
2965 | + ''' |
2966 | + # Initial values |
2967 | + self.__content = None |
2968 | + self.__name = None |
2969 | + |
2970 | + # Setting passed values |
2971 | + self.parent = parent |
2972 | + self.name = name |
2973 | + self.content = data |
2974 | + |
2975 | + # Name property |
2976 | + @apply |
2977 | + def name(): |
2978 | + doc = ''' |
2979 | + Name is a read/write property that controls the input of name. |
2980 | + - If passed an invalid value it cleans the name with None |
2981 | + |
2982 | + Usage: |
2983 | + >>> d = Data(13); d.name = 'name_test'; print d |
2984 | + name_test: 13 |
2985 | + >>> d.name = 11; print d |
2986 | + 13 |
2987 | + >>> d.name = 'other_name'; print d |
2988 | + other_name: 13 |
2989 | + >>> d.name = None; print d |
2990 | + 13 |
2991 | + >>> d.name = 'last_name'; print d |
2992 | + last_name: 13 |
2993 | + >>> d.name = ''; print d |
2994 | + 13 |
2995 | + ''' |
2996 | + def fget(self): |
2997 | + ''' |
2998 | + returns the name as a string |
2999 | + ''' |
3000 | + return self.__name |
3001 | + |
3002 | + def fset(self, name): |
3003 | + ''' |
3004 | + Sets the name of the Data |
3005 | + ''' |
3006 | + if type(name) in STRTYPES and len(name) > 0: |
3007 | + self.__name = name |
3008 | + else: |
3009 | + self.__name = None |
3010 | + |
3011 | + |
3012 | + |
3013 | + return property(**locals()) |
3014 | + |
3015 | + # Content property |
3016 | + @apply |
3017 | + def content(): |
3018 | + doc = ''' |
3019 | + Content is a read/write property that validate the data passed |
3020 | + and return it. |
3021 | + |
3022 | + Usage: |
3023 | + >>> d = Data(); d.content = 13; d.content |
3024 | + 13 |
3025 | + >>> d = Data(); d.content = (1,2); d.content |
3026 | + (1, 2) |
3027 | + >>> d = Data(); d.content = (1,2,3); d.content |
3028 | + (1, 2, 3) |
3029 | + >>> d = Data(); d.content = [1,2,3]; d.content |
3030 | + (1, 2, 3) |
3031 | + >>> d = Data(); d.content = [1.5,.2,3.3]; d.content |
3032 | + (1.5, 0.20000000000000001, 3.2999999999999998) |
3033 | + ''' |
3034 | + def fget(self): |
3035 | + ''' |
3036 | + Return the content of Data |
3037 | + ''' |
3038 | + return self.__content |
3039 | + |
3040 | + def fset(self, data): |
3041 | + ''' |
3042 | + Ensures that data is a valid tuple/list or a number (int, float |
3043 | + or long) |
3044 | + ''' |
3045 | + # Type: None |
3046 | + if data is None: |
3047 | + self.__content = None |
3048 | + return |
3049 | + |
3050 | + # Type: Int or Float |
3051 | + elif type(data) in NUMTYPES: |
3052 | + self.__content = data |
3053 | + |
3054 | + # Type: List or Tuple |
3055 | + elif type(data) in LISTTYPES: |
3056 | + # Ensures the correct size |
3057 | + if len(data) not in (2, 3): |
3058 | + raise TypeError, "Data (as list/tuple) must have 2 or 3 items" |
3059 | + return |
3060 | + |
3061 | + # Ensures that all items in list/tuple is a number |
3062 | + isnum = lambda x : type(x) not in NUMTYPES |
3063 | + |
3064 | + if max(map(isnum, data)): |
3065 | + # An item in data isn't an int or a float |
3066 | + raise TypeError, "All content of data must be a number (int or float)" |
3067 | + |
3068 | + # Convert the tuple to list |
3069 | + if type(data) is list: |
3070 | + data = tuple(data) |
3071 | + |
3072 | + # Append a copy and sets the type |
3073 | + self.__content = data[:] |
3074 | + |
3075 | + # Unknown type! |
3076 | + else: |
3077 | + self.__content = None |
3078 | + raise TypeError, "Data must be an int, float or a tuple with two or three items" |
3079 | + return |
3080 | + |
3081 | + return property(**locals()) |
3082 | + |
3083 | + |
3084 | + def clear(self): |
3085 | + ''' |
3086 | + Clear the all Data (content, name and parent) |
3087 | + ''' |
3088 | + self.content = None |
3089 | + self.name = None |
3090 | + self.parent = None |
3091 | + |
3092 | + def copy(self): |
3093 | + ''' |
3094 | + Returns a copy of the Data structure |
3095 | + ''' |
3096 | + # The copy |
3097 | + new_data = Data() |
3098 | + if self.content is not None: |
3099 | + # If content is a point |
3100 | + if type(self.content) is tuple: |
3101 | + new_data.__content = self.content[:] |
3102 | + |
3103 | + # If content is a number |
3104 | + else: |
3105 | + new_data.__content = self.content |
3106 | + |
3107 | + # If it has a name |
3108 | + if self.name is not None: |
3109 | + new_data.__name = self.name |
3110 | + |
3111 | + return new_data |
3112 | + |
3113 | + def __str__(self): |
3114 | + ''' |
3115 | + Return a string representation of the Data structure |
3116 | + ''' |
3117 | + if self.name is None: |
3118 | + if self.content is None: |
3119 | + return '' |
3120 | + return str(self.content) |
3121 | + else: |
3122 | + if self.content is None: |
3123 | + return self.name+": ()" |
3124 | + return self.name+": "+str(self.content) |
3125 | + |
3126 | + def __len__(self): |
3127 | + ''' |
3128 | + Return the length of the Data. |
3129 | + - If it's a number return 1; |
3130 | + - If it's a list return it's length; |
3131 | + - If its None return 0. |
3132 | + ''' |
3133 | + if self.content is None: |
3134 | + return 0 |
3135 | + elif type(self.content) in NUMTYPES: |
3136 | + return 1 |
3137 | + return len(self.content) |
3138 | + |
3139 | + |
3140 | + |
3141 | + |
3142 | +class Group(object): |
3143 | + ''' |
3144 | + Class that models a group of data. Every value (int, float, long, tuple |
3145 | + or list) passed is converted to a list of Data. |
3146 | + It can receive: |
3147 | + - A single number (int, float, long); |
3148 | + - A list of numbers; |
3149 | + - A tuple of numbers; |
3150 | + - An instance of Data; |
3151 | + - A list of Data; |
3152 | + |
3153 | + Obs: If a tuple with 2 or 3 items is passed it is converted to a point. |
3154 | + If a tuple with only 1 item is passed it's converted to a number; |
3155 | + If a tuple with more than 2 items is passed it's converted to a |
3156 | + list of numbers |
3157 | + ''' |
3158 | + def __init__(self, group=None, name=None, parent=None): |
3159 | + ''' |
3160 | + Starts main atributes in Group instance. |
3161 | + @data_list - a list of data which forms the group; |
3162 | + @range - a range that represent the x axis of possible functions; |
3163 | + @name - name of the data group; |
3164 | + @parent - the Serie parent of this group. |
3165 | + |
3166 | + Usage: |
3167 | + >>> g = Group(13, 'simple number'); print g |
3168 | + simple number ['13'] |
3169 | + >>> g = Group((1,2), 'simple point'); print g |
3170 | + simple point ['(1, 2)'] |
3171 | + >>> g = Group([1,2,3,4], 'list of numbers'); print g |
3172 | + list of numbers ['1', '2', '3', '4'] |
3173 | + >>> g = Group((1,2,3,4),'int in tuple'); print g |
3174 | + int in tuple ['1', '2', '3', '4'] |
3175 | + >>> g = Group([(1,2),(2,3),(3,4)], 'list of points'); print g |
3176 | + list of points ['(1, 2)', '(2, 3)', '(3, 4)'] |
3177 | + >>> g = Group([[1,2,3],[1,2,3]], '2D coordinate lists'); print g |
3178 | + 2D coordinated lists ['(1, 1)', '(2, 2)', '(3, 3)'] |
3179 | + >>> g = Group([[1,2],[1,2],[1,2]], '3D coordinate lists'); print g |
3180 | + 3D coordinated lists ['(1, 1, 1)', '(2, 2, 2)'] |
3181 | + ''' |
3182 | + # Initial values |
3183 | + self.__data_list = [] |
3184 | + self.__range = [] |
3185 | + self.__name = None |
3186 | + |
3187 | + |
3188 | + self.parent = parent |
3189 | + self.name = name |
3190 | + self.data_list = group |
3191 | + |
3192 | + # Name property |
3193 | + @apply |
3194 | + def name(): |
3195 | + doc = ''' |
3196 | + Name is a read/write property that controls the input of name. |
3197 | + - If passed an invalid value it cleans the name with None |
3198 | + |
3199 | + Usage: |
3200 | + >>> g = Group(13); g.name = 'name_test'; print g |
3201 | + name_test ['13'] |
3202 | + >>> g.name = 11; print g |
3203 | + ['13'] |
3204 | + >>> g.name = 'other_name'; print g |
3205 | + other_name ['13'] |
3206 | + >>> g.name = None; print g |
3207 | + ['13'] |
3208 | + >>> g.name = 'last_name'; print g |
3209 | + last_name ['13'] |
3210 | + >>> g.name = ''; print g |
3211 | + ['13'] |
3212 | + ''' |
3213 | + def fget(self): |
3214 | + ''' |
3215 | + Returns the name as a string |
3216 | + ''' |
3217 | + return self.__name |
3218 | + |
3219 | + def fset(self, name): |
3220 | + ''' |
3221 | + Sets the name of the Group |
3222 | + ''' |
3223 | + if type(name) in STRTYPES and len(name) > 0: |
3224 | + self.__name = name |
3225 | + else: |
3226 | + self.__name = None |
3227 | + |
3228 | + return property(**locals()) |
3229 | + |
3230 | + # data_list property |
3231 | + @apply |
3232 | + def data_list(): |
3233 | + doc = ''' |
3234 | + The data_list is a read/write property that can be a list of |
3235 | + numbers, a list of points or a list of 2 or 3 coordinate lists. This |
3236 | + property uses mainly the self.add_data method. |
3237 | + |
3238 | + Usage: |
3239 | + >>> g = Group(); g.data_list = 13; print g |
3240 | + ['13'] |
3241 | + >>> g.data_list = (1,2); print g |
3242 | + ['(1, 2)'] |
3243 | + >>> g.data_list = Data((1,2),'point a'); print g |
3244 | + ['point a: (1, 2)'] |
3245 | + >>> g.data_list = [1,2,3]; print g |
3246 | + ['1', '2', '3'] |
3247 | + >>> g.data_list = (1,2,3,4); print g |
3248 | + ['1', '2', '3', '4'] |
3249 | + >>> g.data_list = [(1,2),(2,3),(3,4)]; print g |
3250 | + ['(1, 2)', '(2, 3)', '(3, 4)'] |
3251 | + >>> g.data_list = [[1,2],[1,2]]; print g |
3252 | + ['(1, 1)', '(2, 2)'] |
3253 | + >>> g.data_list = [[1,2],[1,2],[1,2]]; print g |
3254 | + ['(1, 1, 1)', '(2, 2, 2)'] |
3255 | + >>> g.range = (10); g.data_list = lambda x:x**2; print g |
3256 | + ['(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)'] |
3257 | + ''' |
3258 | + def fget(self): |
3259 | + ''' |
3260 | + Returns the value of data_list |
3261 | + ''' |
3262 | + return self.__data_list |
3263 | + |
3264 | + def fset(self, group): |
3265 | + ''' |
3266 | + Ensures that group is valid. |
3267 | + ''' |
3268 | + # None |
3269 | + if group is None: |
3270 | + self.__data_list = [] |
3271 | + |
3272 | + # Int/float/long or Instance of Data |
3273 | + elif type(group) in NUMTYPES or isinstance(group, Data): |
3274 | + # Clean data_list |
3275 | + self.__data_list = [] |
3276 | + self.add_data(group) |
3277 | + |
3278 | + # One point |
3279 | + elif type(group) is tuple and len(group) in (2,3): |
3280 | + self.__data_list = [] |
3281 | + self.add_data(group) |
3282 | + |
3283 | + # list of items |
3284 | + elif type(group) in LISTTYPES and type(group[0]) is not list: |
3285 | + # Clean data_list |
3286 | + self.__data_list = [] |
3287 | + for item in group: |
3288 | + # try to append and catch an exception |
3289 | + self.add_data(item) |
3290 | + |
3291 | + # function lambda |
3292 | + elif callable(group): |
3293 | + # Explicit is better than implicit |
3294 | + function = group |
3295 | + # Has range |
3296 | + if len(self.range) is not 0: |
3297 | + # Clean data_list |
3298 | + self.__data_list = [] |
3299 | + # Generate values for the lambda function |
3300 | + for x in self.range: |
3301 | + #self.add_data((x,round(group(x),2))) |
3302 | + self.add_data((x,function(x))) |
3303 | + |
3304 | + # Only have range in parent |
3305 | + elif self.parent is not None and len(self.parent.range) is not 0: |
3306 | + # Copy parent range |
3307 | + self.__range = self.parent.range[:] |
3308 | + # Clean data_list |
3309 | + self.__data_list = [] |
3310 | + # Generate values for the lambda function |
3311 | + for x in self.range: |
3312 | + #self.add_data((x,round(group(x),2))) |
3313 | + self.add_data((x,function(x))) |
3314 | + |
3315 | + # Don't have range anywhere |
3316 | + else: |
3317 | + # x_data don't exist |
3318 | + raise Exception, "Data argument is valid but to use function type please set x_range first" |
3319 | + |
3320 | + # Coordinate Lists |
3321 | + elif type(group) in LISTTYPES and type(group[0]) is list: |
3322 | + # Clean data_list |
3323 | + self.__data_list = [] |
3324 | + data = [] |
3325 | + if len(group) == 3: |
3326 | + data = zip(group[0], group[1], group[2]) |
3327 | + elif len(group) == 2: |
3328 | + data = zip(group[0], group[1]) |
3329 | + else: |
3330 | + raise TypeError, "Only one list of coordinates was received." |
3331 | + |
3332 | + for item in data: |
3333 | + self.add_data(item) |
3334 | + |
3335 | + else: |
3336 | + raise TypeError, "Group type not supported" |
3337 | + |
3338 | + return property(**locals()) |
3339 | + |
3340 | + @apply |
3341 | + def range(): |
3342 | + doc = ''' |
3343 | + The range is a read/write property that generates a range of values |
3344 | + for the x axis of the functions. When passed a tuple it almost works |
3345 | + like the built-in range funtion: |
3346 | + - 1 item, represent the end of the range started from 0; |
3347 | + - 2 items, represents the start and the end, respectively; |
3348 | + - 3 items, the last one represents the step; |
3349 | + |
3350 | + When passed a list the range function understands as a valid range. |
3351 | + |
3352 | + Usage: |
3353 | + >>> g = Group(); g.range = 10; print g.range |
3354 | + [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] |
3355 | + >>> g = Group(); g.range = (5); print g.range |
3356 | + [0.0, 1.0, 2.0, 3.0, 4.0] |
3357 | + >>> g = Group(); g.range = (1,7); print g.range |
3358 | + [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] |
3359 | + >>> g = Group(); g.range = (0,10,2); print g.range |
3360 | + [0.0, 2.0, 4.0, 6.0, 8.0] |
3361 | + >>> |
3362 | + >>> g = Group(); g.range = [0]; print g.range |
3363 | + [0.0] |
3364 | + >>> g = Group(); g.range = [0,10,20]; print g.range |
3365 | + [0.0, 10.0, 20.0] |
3366 | + ''' |
3367 | + def fget(self): |
3368 | + ''' |
3369 | + Returns the range |
3370 | + ''' |
3371 | + return self.__range |
3372 | + |
3373 | + def fset(self, x_range): |
3374 | + ''' |
3375 | + Controls the input of a valid type and generate the range |
3376 | + ''' |
3377 | + # if passed a simple number convert to tuple |
3378 | + if type(x_range) in NUMTYPES: |
3379 | + x_range = (x_range,) |
3380 | + |
3381 | + # A list, just convert to float |
3382 | + if type(x_range) is list and len(x_range) > 0: |
3383 | + # Convert all to float |
3384 | + x_range = map(float, x_range) |
3385 | + # Prevents repeated values and convert back to list |
3386 | + self.__range = list(set(x_range[:])) |
3387 | + # Sort the list to ascending order |
3388 | + self.__range.sort() |
3389 | + |
3390 | + # A tuple, must check the lengths and generate the values |
3391 | + elif type(x_range) is tuple and len(x_range) in (1,2,3): |
3392 | + # Convert all to float |
3393 | + x_range = map(float, x_range) |
3394 | + |
3395 | + # Inital values |
3396 | + start = 0.0 |
3397 | + step = 1.0 |
3398 | + end = 0.0 |
3399 | + |
3400 | + # Only the end and it can't be less or iqual to 0 |
3401 | + if len(x_range) is 1 and x_range > 0: |
3402 | + end = x_range[0] |
3403 | + |
3404 | + # The start and the end but the start must be less then the end |
3405 | + elif len(x_range) is 2 and x_range[0] < x_range[1]: |
3406 | + start = x_range[0] |
3407 | + end = x_range[1] |
3408 | + |
3409 | + # All 3, but the start must be less then the end |
3410 | + elif x_range[0] <= x_range[1]: |
3411 | + start = x_range[0] |
3412 | + end = x_range[1] |
3413 | + step = x_range[2] |
3414 | + |
3415 | + # Starts the range |
3416 | + self.__range = [] |
3417 | + # Generate the range |
3418 | + # Can't use the range function because it doesn't support float values |
3419 | + while start < end: |
3420 | + self.__range.append(start) |
3421 | + start += step |
3422 | + |
3423 | + # Incorrect type |
3424 | + else: |
3425 | + raise Exception, "x_range must be a list with one or more items or a tuple with 2 or 3 items" |
3426 | + |
3427 | + return property(**locals()) |
3428 | + |
3429 | + def add_data(self, data, name=None): |
3430 | + ''' |
3431 | + Append a new data to the data_list. |
3432 | + - If data is an instance of Data, append it |
3433 | + - If it's an int, float, tuple or list create an instance of Data and append it |
3434 | + |
3435 | + Usage: |
3436 | + >>> g = Group() |
3437 | + >>> g.add_data(12); print g |
3438 | + ['12'] |
3439 | + >>> g.add_data(7,'other'); print g |
3440 | + ['12', 'other: 7'] |
3441 | + >>> |
3442 | + >>> g = Group() |
3443 | + >>> g.add_data((1,1),'a'); print g |
3444 | + ['a: (1, 1)'] |
3445 | + >>> g.add_data((2,2),'b'); print g |
3446 | + ['a: (1, 1)', 'b: (2, 2)'] |
3447 | + >>> |
3448 | + >>> g.add_data(Data((1,2),'c')); print g |
3449 | + ['a: (1, 1)', 'b: (2, 2)', 'c: (1, 2)'] |
3450 | + ''' |
3451 | + if not isinstance(data, Data): |
3452 | + # Try to convert |
3453 | + data = Data(data,name,self) |
3454 | + |
3455 | + if data.content is not None: |
3456 | + self.__data_list.append(data.copy()) |
3457 | + self.__data_list[-1].parent = self |
3458 | + |
3459 | + |
3460 | + def to_list(self): |
3461 | + ''' |
3462 | + Returns the group as a list of numbers (int, float or long) or a |
3463 | + list of tuples (points 2D or 3D). |
3464 | + |
3465 | + Usage: |
3466 | + >>> g = Group([1,2,3,4],'g1'); g.to_list() |
3467 | + [1, 2, 3, 4] |
3468 | + >>> g = Group([(1,2),(2,3),(3,4)],'g2'); g.to_list() |
3469 | + [(1, 2), (2, 3), (3, 4)] |
3470 | + >>> g = Group([(1,2,3),(3,4,5)],'g2'); g.to_list() |
3471 | + [(1, 2, 3), (3, 4, 5)] |
3472 | + ''' |
3473 | + return [data.content for data in self] |
3474 | + |
3475 | + def copy(self): |
3476 | + ''' |
3477 | + Returns a copy of this group |
3478 | + ''' |
3479 | + new_group = Group() |
3480 | + new_group.__name = self.__name |
3481 | + if self.__range is not None: |
3482 | + new_group.__range = self.__range[:] |
3483 | + for data in self: |
3484 | + new_group.add_data(data.copy()) |
3485 | + return new_group |
3486 | + |
3487 | + def get_names(self): |
3488 | + ''' |
3489 | + Return a list with the names of all data in this group |
3490 | + ''' |
3491 | + names = [] |
3492 | + for data in self: |
3493 | + if data.name is None: |
3494 | + names.append('Data '+str(data.index()+1)) |
3495 | + else: |
3496 | + names.append(data.name) |
3497 | + return names |
3498 | + |
3499 | + |
3500 | + def __str__ (self): |
3501 | + ''' |
3502 | + Returns a string representing the Group |
3503 | + ''' |
3504 | + ret = "" |
3505 | + if self.name is not None: |
3506 | + ret += self.name + " " |
3507 | + if len(self) > 0: |
3508 | + list_str = [str(item) for item in self] |
3509 | + ret += str(list_str) |
3510 | + else: |
3511 | + ret += "[]" |
3512 | + return ret |
3513 | + |
3514 | + def __getitem__(self, key): |
3515 | + ''' |
3516 | + Makes a Group iterable, based in the data_list property |
3517 | + ''' |
3518 | + return self.data_list[key] |
3519 | + |
3520 | + def __len__(self): |
3521 | + ''' |
3522 | + Returns the length of the Group, based in the data_list property |
3523 | + ''' |
3524 | + return len(self.data_list) |
3525 | + |
3526 | + |
3527 | +class Colors(object): |
3528 | + ''' |
3529 | + Class that models the colors its labels (names) and its properties, RGB |
3530 | + and filling type. |
3531 | + |
3532 | + It can receive: |
3533 | + - A list where each item is a list with 3 or 4 items. The |
3534 | + first 3 items represent the RGB values and the last argument |
3535 | + defines the filling type. The list will be converted to a dict |
3536 | + and each color will receve a name based in its position in the |
3537 | + list. |
3538 | + - A dictionary where each key will be the color name and its item |
3539 | + can be a list with 3 or 4 items. The first 3 items represent |
3540 | + the RGB colors and the last argument defines the filling type. |
3541 | + ''' |
3542 | + def __init__(self, color_list=None): |
3543 | + ''' |
3544 | + Start the color_list property |
3545 | + @ color_list - the list or dict contaning the colors properties. |
3546 | + ''' |
3547 | + self.__color_list = None |
3548 | + |
3549 | + self.color_list = color_list |
3550 | + |
3551 | + @apply |
3552 | + def color_list(): |
3553 | + doc = ''' |
3554 | + >>> c = Colors([[1,1,1],[2,2,2,'linear'],[3,3,3,'gradient']]) |
3555 | + >>> print c.color_list |
3556 | + {'Color 2': [2, 2, 2, 'linear'], 'Color 3': [3, 3, 3, 'gradient'], 'Color 1': [1, 1, 1, 'solid']} |
3557 | + >>> c.color_list = [[1,1,1],(2,2,2,'solid'),(3,3,3,'linear')] |
3558 | + >>> print c.color_list |
3559 | + {'Color 2': [2, 2, 2, 'solid'], 'Color 3': [3, 3, 3, 'linear'], 'Color 1': [1, 1, 1, 'solid']} |
3560 | + >>> c.color_list = {'a':[1,1,1],'b':(2,2,2,'solid'),'c':(3,3,3,'linear'), 'd':(4,4,4)} |
3561 | + >>> print c.color_list |
3562 | + {'a': [1, 1, 1, 'solid'], 'c': [3, 3, 3, 'linear'], 'b': [2, 2, 2, 'solid'], 'd': [4, 4, 4, 'solid']} |
3563 | + ''' |
3564 | + def fget(self): |
3565 | + ''' |
3566 | + Return the color list |
3567 | + ''' |
3568 | + return self.__color_list |
3569 | + |
3570 | + def fset(self, color_list): |
3571 | + ''' |
3572 | + Format the color list to a dictionary |
3573 | + ''' |
3574 | + if color_list is None: |
3575 | + self.__color_list = None |
3576 | + return |
3577 | + |
3578 | + if type(color_list) in LISTTYPES and type(color_list[0]) in LISTTYPES: |
3579 | + old_color_list = color_list[:] |
3580 | + color_list = {} |
3581 | + for index, color in enumerate(old_color_list): |
3582 | + if len(color) is 3 and max(map(type, color)) in NUMTYPES: |
3583 | + color_list['Color '+str(index+1)] = list(color)+[DEFAULT_COLOR_FILLING] |
3584 | + elif len(color) is 4 and max(map(type, color[:-1])) in NUMTYPES and color[-1] in FILLING_TYPES: |
3585 | + color_list['Color '+str(index+1)] = list(color) |
3586 | + else: |
3587 | + raise TypeError, "Unsuported color format" |
3588 | + elif type(color_list) is not dict: |
3589 | + raise TypeError, "Unsuported color format" |
3590 | + |
3591 | + for name, color in color_list.items(): |
3592 | + if len(color) is 3: |
3593 | + if max(map(type, color)) in NUMTYPES: |
3594 | + color_list[name] = list(color)+[DEFAULT_COLOR_FILLING] |
3595 | + else: |
3596 | + raise TypeError, "Unsuported color format" |
3597 | + elif len(color) is 4: |
3598 | + if max(map(type, color[:-1])) in NUMTYPES and color[-1] in FILLING_TYPES: |
3599 | + color_list[name] = list(color) |
3600 | + else: |
3601 | + raise TypeError, "Unsuported color format" |
3602 | + self.__color_list = color_list.copy() |
3603 | + |
3604 | + return property(**locals()) |
3605 | + |
3606 | + |
3607 | +class Series(object): |
3608 | + ''' |
3609 | + Class that models a Series (group of groups). Every value (int, float, |
3610 | + long, tuple or list) passed is converted to a list of Group or Data. |
3611 | + It can receive: |
3612 | + - a single number or point, will be converted to a Group of one Data; |
3613 | + - a list of numbers, will be converted to a group of numbers; |
3614 | + - a list of tuples, will converted to a single Group of points; |
3615 | + - a list of lists of numbers, each 'sublist' will be converted to a |
3616 | + group of numbers; |
3617 | + - a list of lists of tuples, each 'sublist' will be converted to a |
3618 | + group of points; |
3619 | + - a list of lists of lists, the content of the 'sublist' will be |
3620 | + processed as coordinated lists and the result will be converted to |
3621 | + a group of points; |
3622 | + - a Dictionary where each item can be the same of the list: number, |
3623 | + point, list of numbers, list of points or list of lists (coordinated |
3624 | + lists); |
3625 | + - an instance of Data; |
3626 | + - an instance of group. |
3627 | + ''' |
3628 | + def __init__(self, series=None, name=None, property=[], colors=None): |
3629 | + ''' |
3630 | + Starts main atributes in Group instance. |
3631 | + @series - a list, dict of data of which the series is composed; |
3632 | + @name - name of the series; |
3633 | + @property - a list/dict of properties to be used in the plots of |
3634 | + this Series |
3635 | + |
3636 | + Usage: |
3637 | + >>> print Series([1,2,3,4]) |
3638 | + ["Group 1 ['1', '2', '3', '4']"] |
3639 | + >>> print Series([[1,2,3],[4,5,6]]) |
3640 | + ["Group 1 ['1', '2', '3']", "Group 2 ['4', '5', '6']"] |
3641 | + >>> print Series((1,2)) |
3642 | + ["Group 1 ['(1, 2)']"] |
3643 | + >>> print Series([(1,2),(2,3)]) |
3644 | + ["Group 1 ['(1, 2)', '(2, 3)']"] |
3645 | + >>> print Series([[(1,2),(2,3)],[(4,5),(5,6)]]) |
3646 | + ["Group 1 ['(1, 2)', '(2, 3)']", "Group 2 ['(4, 5)', '(5, 6)']"] |
3647 | + >>> print Series([[[1,2,3],[1,2,3],[1,2,3]]]) |
3648 | + ["Group 1 ['(1, 1, 1)', '(2, 2, 2)', '(3, 3, 3)']"] |
3649 | + >>> print Series({'g1':[1,2,3], 'g2':[4,5,6]}) |
3650 | + ["g1 ['1', '2', '3']", "g2 ['4', '5', '6']"] |
3651 | + >>> print Series({'g1':[(1,2),(2,3)], 'g2':[(4,5),(5,6)]}) |
3652 | + ["g1 ['(1, 2)', '(2, 3)']", "g2 ['(4, 5)', '(5, 6)']"] |
3653 | + >>> print Series({'g1':[[1,2],[1,2]], 'g2':[[4,5],[4,5]]}) |
3654 | + ["g1 ['(1, 1)', '(2, 2)']", "g2 ['(4, 4)', '(5, 5)']"] |
3655 | + >>> print Series(Data(1,'d1')) |
3656 | + ["Group 1 ['d1: 1']"] |
3657 | + >>> print Series(Group([(1,2),(2,3)],'g1')) |
3658 | + ["g1 ['(1, 2)', '(2, 3)']"] |
3659 | + ''' |
3660 | + # Intial values |
3661 | + self.__group_list = [] |
3662 | + self.__name = None |
3663 | + self.__range = None |
3664 | + |
3665 | + # TODO: Implement colors with filling |
3666 | + self.__colors = None |
3667 | + |
3668 | + self.name = name |
3669 | + self.group_list = series |
3670 | + self.colors = colors |
3671 | + |
3672 | + # Name property |
3673 | + @apply |
3674 | + def name(): |
3675 | + doc = ''' |
3676 | + Name is a read/write property that controls the input of name. |
3677 | + - If passed an invalid value it cleans the name with None |
3678 | + |
3679 | + Usage: |
3680 | + >>> s = Series(13); s.name = 'name_test'; print s |
3681 | + name_test ["Group 1 ['13']"] |
3682 | + >>> s.name = 11; print s |
3683 | + ["Group 1 ['13']"] |
3684 | + >>> s.name = 'other_name'; print s |
3685 | + other_name ["Group 1 ['13']"] |
3686 | + >>> s.name = None; print s |
3687 | + ["Group 1 ['13']"] |
3688 | + >>> s.name = 'last_name'; print s |
3689 | + last_name ["Group 1 ['13']"] |
3690 | + >>> s.name = ''; print s |
3691 | + ["Group 1 ['13']"] |
3692 | + ''' |
3693 | + def fget(self): |
3694 | + ''' |
3695 | + Returns the name as a string |
3696 | + ''' |
3697 | + return self.__name |
3698 | + |
3699 | + def fset(self, name): |
3700 | + ''' |
3701 | + Sets the name of the Group |
3702 | + ''' |
3703 | + if type(name) in STRTYPES and len(name) > 0: |
3704 | + self.__name = name |
3705 | + else: |
3706 | + self.__name = None |
3707 | + |
3708 | + return property(**locals()) |
3709 | + |
3710 | + |
3711 | + |
3712 | + # Colors property |
3713 | + @apply |
3714 | + def colors(): |
3715 | + doc = ''' |
3716 | + >>> s = Series() |
3717 | + >>> s.colors = [[1,1,1],[2,2,2,'linear'],[3,3,3,'gradient']] |
3718 | + >>> print s.colors |
3719 | + {'Color 2': [2, 2, 2, 'linear'], 'Color 3': [3, 3, 3, 'gradient'], 'Color 1': [1, 1, 1, 'solid']} |
3720 | + >>> s.colors = [[1,1,1],(2,2,2,'solid'),(3,3,3,'linear')] |
3721 | + >>> print s.colors |
3722 | + {'Color 2': [2, 2, 2, 'solid'], 'Color 3': [3, 3, 3, 'linear'], 'Color 1': [1, 1, 1, 'solid']} |
3723 | + >>> s.colors = {'a':[1,1,1],'b':(2,2,2,'solid'),'c':(3,3,3,'linear'), 'd':(4,4,4)} |
3724 | + >>> print s.colors |
3725 | + {'a': [1, 1, 1, 'solid'], 'c': [3, 3, 3, 'linear'], 'b': [2, 2, 2, 'solid'], 'd': [4, 4, 4, 'solid']} |
3726 | + ''' |
3727 | + def fget(self): |
3728 | + ''' |
3729 | + Return the color list |
3730 | + ''' |
3731 | + return self.__colors.color_list |
3732 | + |
3733 | + def fset(self, colors): |
3734 | + ''' |
3735 | + Format the color list to a dictionary |
3736 | + ''' |
3737 | + self.__colors = Colors(colors) |
3738 | + |
3739 | + return property(**locals()) |
3740 | + |
3741 | + @apply |
3742 | + def range(): |
3743 | + doc = ''' |
3744 | + The range is a read/write property that generates a range of values |
3745 | + for the x axis of the functions. When passed a tuple it almost works |
3746 | + like the built-in range funtion: |
3747 | + - 1 item, represent the end of the range started from 0; |
3748 | + - 2 items, represents the start and the end, respectively; |
3749 | + - 3 items, the last one represents the step; |
3750 | + |
3751 | + When passed a list the range function understands as a valid range. |
3752 | + |
3753 | + Usage: |
3754 | + >>> s = Series(); s.range = 10; print s.range |
3755 | + [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] |
3756 | + >>> s = Series(); s.range = (5); print s.range |
3757 | + [0.0, 1.0, 2.0, 3.0, 4.0, 5.0] |
3758 | + >>> s = Series(); s.range = (1,7); print s.range |
3759 | + [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] |
3760 | + >>> s = Series(); s.range = (0,10,2); print s.range |
3761 | + [0.0, 2.0, 4.0, 6.0, 8.0, 10.0] |
3762 | + >>> |
3763 | + >>> s = Series(); s.range = [0]; print s.range |
3764 | + [0.0] |
3765 | + >>> s = Series(); s.range = [0,10,20]; print s.range |
3766 | + [0.0, 10.0, 20.0] |
3767 | + ''' |
3768 | + def fget(self): |
3769 | + ''' |
3770 | + Returns the range |
3771 | + ''' |
3772 | + return self.__range |
3773 | + |
3774 | + def fset(self, x_range): |
3775 | + ''' |
3776 | + Controls the input of a valid type and generate the range |
3777 | + ''' |
3778 | + # if passed a simple number convert to tuple |
3779 | + if type(x_range) in NUMTYPES: |
3780 | + x_range = (x_range,) |
3781 | + |
3782 | + # A list, just convert to float |
3783 | + if type(x_range) is list and len(x_range) > 0: |
3784 | + # Convert all to float |
3785 | + x_range = map(float, x_range) |
3786 | + # Prevents repeated values and convert back to list |
3787 | + self.__range = list(set(x_range[:])) |
3788 | + # Sort the list to ascending order |
3789 | + self.__range.sort() |
3790 | + |
3791 | + # A tuple, must check the lengths and generate the values |
3792 | + elif type(x_range) is tuple and len(x_range) in (1,2,3): |
3793 | + # Convert all to float |
3794 | + x_range = map(float, x_range) |
3795 | + |
3796 | + # Inital values |
3797 | + start = 0.0 |
3798 | + step = 1.0 |
3799 | + end = 0.0 |
3800 | + |
3801 | + # Only the end and it can't be less or iqual to 0 |
3802 | + if len(x_range) is 1 and x_range > 0: |
3803 | + end = x_range[0] |
3804 | + |
3805 | + # The start and the end but the start must be lesser then the end |
3806 | + elif len(x_range) is 2 and x_range[0] < x_range[1]: |
3807 | + start = x_range[0] |
3808 | + end = x_range[1] |
3809 | + |
3810 | + # All 3, but the start must be lesser then the end |
3811 | + elif x_range[0] < x_range[1]: |
3812 | + start = x_range[0] |
3813 | + end = x_range[1] |
3814 | + step = x_range[2] |
3815 | + |
3816 | + # Starts the range |
3817 | + self.__range = [] |
3818 | + # Generate the range |
3819 | + # Cnat use the range function becouse it don't suport float values |
3820 | + while start <= end: |
3821 | + self.__range.append(start) |
3822 | + start += step |
3823 | + |
3824 | + # Incorrect type |
3825 | + else: |
3826 | + raise Exception, "x_range must be a list with one or more item or a tuple with 2 or 3 items" |
3827 | + |
3828 | + return property(**locals()) |
3829 | + |
3830 | + @apply |
3831 | + def group_list(): |
3832 | + doc = ''' |
3833 | + The group_list is a read/write property used to pre-process the list |
3834 | + of Groups. |
3835 | + It can be: |
3836 | + - a single number, point or lambda, will be converted to a single |
3837 | + Group of one Data; |
3838 | + - a list of numbers, will be converted to a group of numbers; |
3839 | + - a list of tuples, will converted to a single Group of points; |
3840 | + - a list of lists of numbers, each 'sublist' will be converted to |
3841 | + a group of numbers; |
3842 | + - a list of lists of tuples, each 'sublist' will be converted to a |
3843 | + group of points; |
3844 | + - a list of lists of lists, the content of the 'sublist' will be |
3845 | + processed as coordinated lists and the result will be converted |
3846 | + to a group of points; |
3847 | + - a list of lambdas, each lambda represents a Group; |
3848 | + - a Dictionary where each item can be the same of the list: number, |
3849 | + point, list of numbers, list of points, list of lists |
3850 | + (coordinated lists) or lambdas |
3851 | + - an instance of Data; |
3852 | + - an instance of group. |
3853 | + |
3854 | + Usage: |
3855 | + >>> s = Series() |
3856 | + >>> s.group_list = [1,2,3,4]; print s |
3857 | + ["Group 1 ['1', '2', '3', '4']"] |
3858 | + >>> s.group_list = [[1,2,3],[4,5,6]]; print s |
3859 | + ["Group 1 ['1', '2', '3']", "Group 2 ['4', '5', '6']"] |
3860 | + >>> s.group_list = (1,2); print s |
3861 | + ["Group 1 ['(1, 2)']"] |
3862 | + >>> s.group_list = [(1,2),(2,3)]; print s |
3863 | + ["Group 1 ['(1, 2)', '(2, 3)']"] |
3864 | + >>> s.group_list = [[(1,2),(2,3)],[(4,5),(5,6)]]; print s |
3865 | + ["Group 1 ['(1, 2)', '(2, 3)']", "Group 2 ['(4, 5)', '(5, 6)']"] |
3866 | + >>> s.group_list = [[[1,2,3],[1,2,3],[1,2,3]]]; print s |
3867 | + ["Group 1 ['(1, 1, 1)', '(2, 2, 2)', '(3, 3, 3)']"] |
3868 | + >>> s.group_list = [(0.5,5.5) , [(0,4),(6,8)] , (5.5,7) , (7,9)]; print s |
3869 | + ["Group 1 ['(0.5, 5.5)']", "Group 2 ['(0, 4)', '(6, 8)']", "Group 3 ['(5.5, 7)']", "Group 4 ['(7, 9)']"] |
3870 | + >>> s.group_list = {'g1':[1,2,3], 'g2':[4,5,6]}; print s |
3871 | + ["g1 ['1', '2', '3']", "g2 ['4', '5', '6']"] |
3872 | + >>> s.group_list = {'g1':[(1,2),(2,3)], 'g2':[(4,5),(5,6)]}; print s |
3873 | + ["g1 ['(1, 2)', '(2, 3)']", "g2 ['(4, 5)', '(5, 6)']"] |
3874 | + >>> s.group_list = {'g1':[[1,2],[1,2]], 'g2':[[4,5],[4,5]]}; print s |
3875 | + ["g1 ['(1, 1)', '(2, 2)']", "g2 ['(4, 4)', '(5, 5)']"] |
3876 | + >>> s.range = 10 |
3877 | + >>> s.group_list = lambda x:x*2 |
3878 | + >>> s.group_list = [lambda x:x*2, lambda x:x**2, lambda x:x**3]; print s |
3879 | + ["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)']"] |
3880 | + >>> s.group_list = {'linear':lambda x:x*2, 'square':lambda x:x**2, 'cubic':lambda x:x**3}; print s |
3881 | + ["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)']"] |
3882 | + >>> s.group_list = Data(1,'d1'); print s |
3883 | + ["Group 1 ['d1: 1']"] |
3884 | + >>> s.group_list = Group([(1,2),(2,3)],'g1'); print s |
3885 | + ["g1 ['(1, 2)', '(2, 3)']"] |
3886 | + ''' |
3887 | + def fget(self): |
3888 | + ''' |
3889 | + Return the group list. |
3890 | + ''' |
3891 | + return self.__group_list |
3892 | + |
3893 | + def fset(self, series): |
3894 | + ''' |
3895 | + Controls the input of a valid group list. |
3896 | + ''' |
3897 | + #TODO: Add support to the following strem of data: [ (0.5,5.5) , [(0,4),(6,8)] , (5.5,7) , (7,9)] |
3898 | + |
3899 | + # Type: None |
3900 | + if series is None: |
3901 | + self.__group_list = [] |
3902 | + |
3903 | + # List or Tuple |
3904 | + elif type(series) in LISTTYPES: |
3905 | + self.__group_list = [] |
3906 | + |
3907 | + is_function = lambda x: callable(x) |
3908 | + # Groups |
3909 | + if list in map(type, series) or max(map(is_function, series)): |
3910 | + for group in series: |
3911 | + self.add_group(group) |
3912 | + |
3913 | + # single group |
3914 | + else: |
3915 | + self.add_group(series) |
3916 | + |
3917 | + #old code |
3918 | + ## List of numbers |
3919 | + #if type(series[0]) in NUMTYPES or type(series[0]) is tuple: |
3920 | + # print series |
3921 | + # self.add_group(series) |
3922 | + # |
3923 | + ## List of anything else |
3924 | + #else: |
3925 | + # for group in series: |
3926 | + # self.add_group(group) |
3927 | + |
3928 | + # Dict representing series of groups |
3929 | + elif type(series) is dict: |
3930 | + self.__group_list = [] |
3931 | + names = series.keys() |
3932 | + names.sort() |
3933 | + for name in names: |
3934 | + self.add_group(Group(series[name],name,self)) |
3935 | + |
3936 | + # A single lambda |
3937 | + elif callable(series): |
3938 | + self.__group_list = [] |
3939 | + self.add_group(series) |
3940 | + |
3941 | + # Int/float, instance of Group or Data |
3942 | + elif type(series) in NUMTYPES or isinstance(series, Group) or isinstance(series, Data): |
3943 | + self.__group_list = [] |
3944 | + self.add_group(series) |
3945 | + |
3946 | + # Default |
3947 | + else: |
3948 | + raise TypeError, "Serie type not supported" |
3949 | + |
3950 | + return property(**locals()) |
3951 | + |
3952 | + def add_group(self, group, name=None): |
3953 | + ''' |
3954 | + Append a new group in group_list |
3955 | + ''' |
3956 | + if not isinstance(group, Group): |
3957 | + #Try to convert |
3958 | + group = Group(group, name, self) |
3959 | + |
3960 | + if len(group.data_list) is not 0: |
3961 | + # Auto naming groups |
3962 | + if group.name is None: |
3963 | + group.name = "Group "+str(len(self.__group_list)+1) |
3964 | + |
3965 | + self.__group_list.append(group) |
3966 | + self.__group_list[-1].parent = self |
3967 | + |
3968 | + def copy(self): |
3969 | + ''' |
3970 | + Returns a copy of the Series |
3971 | + ''' |
3972 | + new_series = Series() |
3973 | + new_series.__name = self.__name |
3974 | + if self.__range is not None: |
3975 | + new_series.__range = self.__range[:] |
3976 | + #Add color property in the copy method |
3977 | + #self.__colors = None |
3978 | + |
3979 | + for group in self: |
3980 | + new_series.add_group(group.copy()) |
3981 | + |
3982 | + return new_series |
3983 | + |
3984 | + def get_names(self): |
3985 | + ''' |
3986 | + Returns a list of the names of all groups in the Serie |
3987 | + ''' |
3988 | + names = [] |
3989 | + for group in self: |
3990 | + if group.name is None: |
3991 | + names.append('Group '+str(group.index()+1)) |
3992 | + else: |
3993 | + names.append(group.name) |
3994 | + |
3995 | + return names |
3996 | + |
3997 | + def to_list(self): |
3998 | + ''' |
3999 | + Returns a list with the content of all groups and data |
4000 | + ''' |
4001 | + big_list = [] |
4002 | + for group in self: |
4003 | + for data in group: |
4004 | + if type(data.content) in NUMTYPES: |
4005 | + big_list.append(data.content) |
4006 | + else: |
4007 | + big_list = big_list + list(data.content) |
4008 | + return big_list |
4009 | + |
4010 | + def __getitem__(self, key): |
4011 | + ''' |
4012 | + Makes the Series iterable, based in the group_list property |
4013 | + ''' |
4014 | + return self.__group_list[key] |
4015 | + |
4016 | + def __str__(self): |
4017 | + ''' |
4018 | + Returns a string that represents the Series |
4019 | + ''' |
4020 | + ret = "" |
4021 | + if self.name is not None: |
4022 | + ret += self.name + " " |
4023 | + if len(self) > 0: |
4024 | + list_str = [str(item) for item in self] |
4025 | + ret += str(list_str) |
4026 | + else: |
4027 | + ret += "[]" |
4028 | + return ret |
4029 | + |
4030 | + def __len__(self): |
4031 | + ''' |
4032 | + Returns the length of the Series, based in the group_lsit property |
4033 | + ''' |
4034 | + return len(self.group_list) |
4035 | + |
4036 | + |
4037 | +if __name__ == '__main__': |
4038 | + doctest.testmod() |
Please remove the json and svg files from version control.
The extension is also missing unload() implementation.