1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """
22 Contains the LineChart widget.
23
24 Author: Sven Festersen (sven@sven-festersen.de)
25 """
26 __docformat__ = "epytext"
27 import gobject
28 import cairo
29 import gtk
30 import math
31
32 import os
33
34 from pygtk_chart.basics import *
35 from pygtk_chart import chart
36
37 RANGE_AUTO = 0
38 GRAPH_PADDING = 1 / 15.0
39 GRAPH_POINTS = 1
40 GRAPH_LINES = 2
41 GRAPH_BOTH = 3
42 COLOR_AUTO = 4
43 POSITION_AUTO = 5
44 POSITION_LEFT = 6
45 POSITION_RIGHT = 7
46 POSITION_BOTTOM = 6
47 POSITION_TOP = 7
48
49
50 COLORS = color_list_from_file(os.path.dirname(__file__) + "/data/tango.color")
51
52
54 """
55 This helper class calculates ranges. It is used by the LineChart
56 widget internally, there is no need to create an instance yourself.
57 """
59 self._data_xrange = None
60 self._data_yrange = None
61 self._xrange = RANGE_AUTO
62 self._yrange = RANGE_AUTO
63 self._cached_xtics = []
64 self._cached_ytics = []
65
67 if self._data_xrange == None:
68 self._data_yrange = graph.get_y_range()
69 self._data_xrange = graph.get_x_range()
70 else:
71 yrange = graph.get_y_range()
72 xrange = graph.get_x_range()
73
74 if xrange and yrange:
75 xmin = min(xrange[0], self._data_xrange[0])
76 xmax = max(xrange[1], self._data_xrange[1])
77 ymin = min(yrange[0], self._data_yrange[0])
78 ymax = max(yrange[1], self._data_yrange[1])
79
80 self._data_xrange = (xmin, xmax)
81 self._data_yrange = (ymin, ymax)
82
84 xrange = self._xrange
85 if xrange == RANGE_AUTO:
86 xrange = self._data_xrange
87 if xrange[0] == xrange[1]:
88 xrange = (xrange[0], xrange[0] + 0.1)
89
90 yrange = self._yrange
91 if yrange == RANGE_AUTO:
92 yrange = self._data_yrange
93 if yrange[0] == yrange[1]:
94 yrange = (yrange[0], yrange[0] + 0.1)
95
96 return (xrange, yrange)
97
100
102 self._yrange = yrange
103
105 xrange, yrange = self.get_ranges()
106
107 xfactor = float(rect.width * (1 - 2 * GRAPH_PADDING)) / (xrange[1] - xrange[0])
108 yfactor = float(rect.height * (1 - 2 * GRAPH_PADDING)) / (yrange[1] - yrange[0])
109 zx = (rect.width * GRAPH_PADDING) - xrange[0] * xfactor
110 zy = rect.height - ((rect.height * GRAPH_PADDING) - yrange[0] * yfactor)
111
112 return (zx,zy)
113
115 (zx, zy) = self.get_absolute_zero(rect)
116 xrange, yrange = self.get_ranges()
117
118 xfactor = float(rect.width * (1 - 2 * GRAPH_PADDING)) / (xrange[1] - xrange[0])
119 yfactor = float(rect.height * (1 - 2 * GRAPH_PADDING)) / (yrange[1] - yrange[0])
120
121 ax = zx + x * xfactor
122 ay = zy - y * yfactor
123 return (ax, ay)
124
128
130 return self._cached_xtics
131
133 return self._cached_ytics
134
136 tics = []
137 (zx, zy) = self.get_absolute_zero(rect)
138 (xrange, yrange) = self.get_ranges()
139 delta = xrange[1] - xrange[0]
140 exp = int(math.log10(delta)) - 1
141
142 first_n = int(xrange[0] / (10 ** exp))
143 last_n = int(xrange[1] / (10 ** exp))
144 n = last_n - first_n
145 N = rect.width / 50.0
146 divide_by = int(n / N)
147 if divide_by == 0: divide_by = 1
148
149 left = rect.width * GRAPH_PADDING
150 right = rect.width * (1 - GRAPH_PADDING)
151
152 for i in range(first_n, last_n + 1):
153 num = i * 10 ** exp
154 (x, y) = self.get_absolute_point(rect, num, 0)
155 if i % divide_by == 0 and is_in_range(x, (left, right)):
156 tics.append(((x, y), num))
157
158 return tics
159
161 tics = []
162 (zx, zy) = self.get_absolute_zero(rect)
163 (xrange, yrange) = self.get_ranges()
164 delta = yrange[1] - yrange[0]
165 exp = int(math.log10(delta)) - 1
166
167 first_n = int(yrange[0] / (10 ** exp))
168 last_n = int(yrange[1] / (10 ** exp))
169 n = last_n - first_n
170 N = rect.height / 50.0
171 divide_by = int(n / N)
172 if divide_by == 0: divide_by = 1
173
174 top = rect.height * GRAPH_PADDING
175 bottom = rect.height * (1 - GRAPH_PADDING)
176
177 for i in range(first_n, last_n + 1):
178 num = i * 10 ** exp
179 (x, y) = self.get_absolute_point(rect, 0, num)
180 if i % divide_by == 0 and is_in_range(y, (top, bottom)):
181 tics.append(((x, y), num))
182
183 return tics
184
185
187 """
188 A widget that shows a line chart. The following objects can be accessed:
189 - LineChart.background (inherited from chart.Chart)
190 - LineChart.title (inherited from chart.Chart)
191 - LineChart.graphs
192 - LineChart.grid
193 - LineChart.xaxis
194 - LineChart.yaxis
195 """
207
209 """
210 Draw all the graphs.
211
212 @type context: cairo.Context
213 @param context: The context to draw on.
214 @type rect: gtk.gdk.Rectangle
215 @param rect: A rectangle representing the charts area.
216 """
217 for (name, graph) in self.graphs.iteritems():
218 graph.draw(context, rect)
219
221 """
222 Draw x and y axis.
223
224 @type context: cairo.Context
225 @param context: The context to draw on.
226 @type rect: gtk.gdk.Rectangle
227 @param rect: A rectangle representing the charts area.
228 """
229 self.xaxis.draw(context, rect, self.yaxis)
230 self.yaxis.draw(context, rect, self.xaxis)
231
232 - def draw(self, context):
233 """
234 Draw the widget. This method is called automatically. Don't call it
235 yourself. If you want to force a redrawing of the widget, call
236 the queue_draw() method.
237
238 @type context: cairo.Context
239 @param context: The context to draw on.
240 """
241 rect = self.get_allocation()
242 self._range_calc.prepare_tics(rect)
243
244 context.set_line_width(1)
245 font = gtk.Label().style.font_desc.get_family()
246 context.select_font_face(font,cairo.FONT_SLANT_NORMAL, \
247 cairo.FONT_WEIGHT_NORMAL)
248
249 self.draw_basics(context, rect)
250 data_available = False
251 for (name, graph) in self.graphs.iteritems():
252 if graph.has_something_to_draw():
253 data_available = True
254 break
255
256 if self.graphs and data_available:
257 self.grid.draw(context, rect)
258 self._do_draw_graphs(context, rect)
259 self._do_draw_axes(context, rect)
260
275
277 """
278 Remove a graph from the plot.
279
280 @type name: string
281 @param name: The name of the graph to remove.
282 """
283 del self.graphs[name]
284 self.queue_draw()
285
287 """
288 Set the visible xrange. xrange has to be a pair: (xmin, xmax) or
289 RANGE_AUTO. If you set it to RANGE_AUTO, the visible range will
290 be calculated.
291
292 @type xrange: pair of numbers
293 @param xrange: The new xrange.
294 """
295 self._range_calc.set_xrange(xrange)
296
298 """
299 Set the visible yrange. yrange has to be a pair: (ymin, ymax) or
300 RANGE_AUTO. If you set it to RANGE_AUTO, the visible range will
301 be calculated.
302
303 @type yrange: pair of numbers
304 @param yrange: The new yrange.
305 """
306 self._range_calc.set_yrange(yrange)
307
308
309 -class Axis(chart.ChartObject):
310
311 __gproperties__ = {"label": (gobject.TYPE_STRING, "axis label",
312 "The label of the axis.", "",
313 gobject.PARAM_READWRITE),
314 "show-label": (gobject.TYPE_BOOLEAN, "show label",
315 "Set whether to show the axis label.",
316 True, gobject.PARAM_READWRITE),
317 "position": (gobject.TYPE_INT, "axis position",
318 "Position of the axis.", 5, 7, 5,
319 gobject.PARAM_READWRITE),
320 "show-tics": (gobject.TYPE_BOOLEAN, "show tics",
321 "Set whether to draw tics.", True,
322 gobject.PARAM_READWRITE),
323 "show-tic-labels": (gobject.TYPE_BOOLEAN,
324 "show tic labels",
325 "Set whether to draw tic labels",
326 True,
327 gobject.PARAM_READWRITE),
328 "tic-format-function": (gobject.TYPE_PYOBJECT,
329 "tic format function",
330 "This function is used to label the tics.",
331 gobject.PARAM_READWRITE)}
332
334 chart.ChartObject.__init__(self)
335 self.set_property("antialias", False)
336
337 self._label = label
338 self._show_label = True
339 self._position = POSITION_AUTO
340 self._show_tics = True
341 self._show_tic_labels = True
342 self._tic_format_function = str
343
344 self._range_calc = range_calc
345
347 if property.name == "visible":
348 return self._show
349 elif property.name == "antialias":
350 return self._antialias
351 elif property.name == "label":
352 return self._label
353 elif property.name == "show-label":
354 return self._show_label
355 elif property.name == "position":
356 return self._position
357 elif property.name == "show-tics":
358 return self._show_tics
359 elif property.name == "show-tic-labels":
360 return self._show_tic_labels
361 elif property.name == "tic-format-function":
362 return self._tic_format_function
363 else:
364 raise AttributeError, "Property %s does not exist." % property.name
365
367 if property.name == "visible":
368 self._show = value
369 elif property.name == "antialias":
370 self._antialias = value
371 elif property.name == "label":
372 self._label = value
373 elif property.name == "show-label":
374 self._show_label = value
375 elif property.name == "position":
376 self._position = value
377 elif property.name == "show-tics":
378 self._show_tics = value
379 elif property.name == "show-tic-labels":
380 self._show_tic_labels = value
381 elif property.name == "tic-format-function":
382 self._tic_format_function = value
383 else:
384 raise AttributeError, "Property %s does not exist." % property.name
385
387 """
388 Set the label of the axis.
389
390 @param label: new label
391 @type label: string.
392 """
393 self.set_property("label", label)
394 self.emit("appearance_changed")
395
397 """
398 Returns the current label of the axis.
399
400 @return: string.
401 """
402 return self.get_property("label")
403
405 """
406 Set whether to show the axis' label.
407
408 @type show: boolean.
409 """
410 self.set_property("show-label", show)
411 self.emit("appearance_changed")
412
414 """
415 Returns True if the axis' label is shown.
416
417 @return: boolean.
418 """
419 return self.get_property("show-label")
420
422 """
423 Set the position of the axis. pos hast to be one these
424 constants: POSITION_AUTO, POSITION_BOTTOM, POSITION_LEFT,
425 POSITION_RIGHT, POSITION_TOP.
426 """
427 self.set_property("position", pos)
428 self.emit("appearance_changed")
429
431 """
432 Returns the position of the axis. (see set_position for
433 details).
434 """
435 return self.get_property("position")
436
438 """
439 Set whether to draw tics at the axis.
440
441 @type show: boolean.
442 """
443 self.set_property("show-tics", show)
444 self.emit("appearance_changed")
445
447 """
448 Returns True if tics are drawn.
449
450 @return: boolean.
451 """
452 return self.get_property("show-tics")
453
455 """
456 Set whether to draw tic labels. Labels are only drawn if
457 tics are drawn.
458
459 @type show: boolean.
460 """
461 self.set_property("show-tic-labels", show)
462 self.emit("appearance_changed")
463
465 """
466 Returns True if tic labels are shown.
467
468 @return: boolean.
469 """
470 return self.get_property("show-tic-labels")
471
482
488
489
491 """
492 This class represents the xaxis. It is used by the LineChart
493 widget internally, there is no need to create an instance yourself.
494 """
497
498 - def draw(self, context, rect, yaxis):
499 """
500 This method is called by the parent Plot instance. It
501 calls _do_draw.
502 """
503 if self._show:
504 if not self._antialias:
505 context.set_antialias(cairo.ANTIALIAS_NONE)
506 self._do_draw(context, rect, yaxis)
507 context.set_antialias(cairo.ANTIALIAS_DEFAULT)
508
510 if self._show_tics:
511 tics = self._range_calc.get_xtics(rect)
512
513
514 font_size = rect.height / 50
515 if font_size < 9: font_size = 9
516 context.set_font_size(font_size)
517
518 for ((x,y), label) in tics:
519 if self._position == POSITION_TOP:
520 y = rect.height * GRAPH_PADDING
521 elif self._position == POSITION_BOTTOM:
522 y = rect.height * (1 - GRAPH_PADDING)
523 tic_height = rect.height / 80.0
524 context.move_to(x, y + tic_height / 2)
525 context.rel_line_to(0, - tic_height)
526 context.stroke()
527
528 if self._show_tic_labels:
529 if label == 0 and self._position == POSITION_AUTO and yaxis.get_position() == POSITION_AUTO:
530 label = " "
531 else:
532 label = self._tic_format_function(label)
533 size = context.text_extents(label)
534 x = x - size[2] / 2
535 y = y + size[3] + font_size / 2
536 if self._position == POSITION_TOP:
537 y = y - size[3] - font_size / 2 - tic_height
538 if label[0] == "-":
539 x = x - context.text_extents("-")[2]
540 context.move_to(x, y)
541 context.show_text(label)
542 context.stroke()
543
545 (x, y) = pos
546 font_size = rect.height / 50
547 if font_size < 9: font_size = 9
548 context.set_font_size(font_size)
549 size = context.text_extents(self._label)
550 x = x + size[2] / 2
551 y = y + size[3]
552
553 context.move_to(x, y)
554 context.show_text(self._label)
555 context.stroke()
556
557 - def _do_draw(self, context, rect, yaxis):
558 """
559 Draw the axis.
560 """
561 (zx, zy) = self._range_calc.get_absolute_zero(rect)
562 if self._position == POSITION_BOTTOM:
563 zy = rect.height * (1 - GRAPH_PADDING)
564 elif self._position == POSITION_TOP:
565 zy = rect.height * GRAPH_PADDING
566 if rect.height * GRAPH_PADDING <= zy and rect.height * (1 - GRAPH_PADDING) >= zy:
567 context.set_source_rgb(0, 0, 0)
568
569 context.move_to(rect.width * GRAPH_PADDING, zy)
570 context.line_to(rect.width * (1 - GRAPH_PADDING), zy)
571 context.stroke()
572
573 context.move_to(rect.width * (1 - GRAPH_PADDING) + 3, zy)
574 context.rel_line_to(-3, -3)
575 context.rel_line_to(0, 6)
576 context.close_path()
577 context.fill()
578
579 if self._show_label:
580 self._do_draw_label(context, rect, (rect.width * (1 - GRAPH_PADDING) + 3, zy))
581 self._do_draw_tics(context, rect, yaxis)
582
583
585 """
586 This class represents the yaxis. It is used by the LineChart
587 widget internally, there is no need to create an instance yourself.
588 """
591
592 - def draw(self, context, rect, xaxis):
593 """
594 This method is called by the parent Plot instance. It
595 calls _do_draw.
596 """
597 if self._show:
598 if not self._antialias:
599 context.set_antialias(cairo.ANTIALIAS_NONE)
600 self._do_draw(context, rect, xaxis)
601 context.set_antialias(cairo.ANTIALIAS_DEFAULT)
602
604 if self._show_tics:
605 tics = self._range_calc.get_ytics(rect)
606
607
608 font_size = rect.height / 50
609
610 context.set_font_size(font_size)
611
612 for ((x,y), label) in tics:
613 if self._position == POSITION_LEFT:
614 x = rect.width * GRAPH_PADDING
615 elif self._position == POSITION_RIGHT:
616 x = rect.width * (1 - GRAPH_PADDING)
617 tic_width = rect.height / 80.0
618 context.move_to(x + tic_width / 2, y)
619 context.rel_line_to(- tic_width, 0)
620 context.stroke()
621
622 if self._show_tic_labels:
623 if label == 0 and self._position == POSITION_AUTO and xaxis.get_position() == POSITION_AUTO:
624 label = " "
625 else:
626 label = self._tic_format_function(label)
627 size = context.text_extents(label)
628 x = x - size[2] - font_size / 2
629 if self._position == POSITION_RIGHT:
630 x = x + size[2] + font_size / 2 + tic_width
631 y = y + size[3] / 2
632 if label[0] == "-":
633 x = x - context.text_extents("-")[2]
634 context.move_to(x, y)
635 context.show_text(label)
636 context.stroke()
637
639 (x, y) = pos
640 font_size = rect.height / 50
641 if font_size < 9: font_size = 9
642 context.set_font_size(font_size)
643 size = context.text_extents(self._label)
644 x = x - size[2]
645 y = y - size[3] / 2
646
647 context.move_to(x, y)
648 context.show_text(self._label)
649 context.stroke()
650
651 - def _do_draw(self, context, rect, xaxis):
652 (zx, zy) = self._range_calc.get_absolute_zero(rect)
653 if self._position == POSITION_LEFT:
654 zx = rect.width * GRAPH_PADDING
655 elif self._position == POSITION_RIGHT:
656 zx = rect.width * (1 - GRAPH_PADDING)
657 if rect.width * GRAPH_PADDING <= zx and rect.width * (1 - GRAPH_PADDING) >= zx:
658 context.set_source_rgb(0, 0, 0)
659
660 context.move_to(zx, rect.height * (1 - GRAPH_PADDING))
661 context.line_to(zx, rect.height * GRAPH_PADDING)
662 context.stroke()
663
664 context.move_to(zx, rect.height * GRAPH_PADDING - 3)
665 context.rel_line_to(-3, 3)
666 context.rel_line_to(6, 0)
667 context.close_path()
668 context.fill()
669
670 if self._show_label:
671 self._do_draw_label(context, rect, (zx, rect.height * GRAPH_PADDING - 3))
672 self._do_draw_tics(context, rect, xaxis)
673
674
675 -class Grid(chart.ChartObject):
676 """
677 A class representing the grid of the chart. It is used by the LineChart
678 widget internally, there is no need to create an instance yourself.
679 """
680
681 __gproperties__ = {"show-horizontal": (gobject.TYPE_BOOLEAN,
682 "show horizontal lines",
683 "Set whether to draw horizontal lines.",
684 True, gobject.PARAM_READWRITE),
685 "show-vertical": (gobject.TYPE_BOOLEAN,
686 "show vertical lines",
687 "Set whether to draw vertical lines.",
688 True, gobject.PARAM_READWRITE),
689 "color": (gobject.TYPE_PYOBJECT,
690 "grid color",
691 "The color of the grid in (r,g,b) format. r,g,b in [0,1]",
692 gobject.PARAM_READWRITE)}
693
695 chart.ChartObject.__init__(self)
696 self.set_property("antialias", False)
697 self._range_calc = range_calc
698 self._color = (0.9, 0.9, 0.9)
699 self._show_h = True
700 self._show_v = True
701
703 if property.name == "visible":
704 return self._show
705 elif property.name == "antialias":
706 return self._antialias
707 elif property.name == "show-horizontal":
708 return self._show_h
709 elif property.name == "show-vertical":
710 return self._show_v
711 elif property.name == "color":
712 return self._color
713 else:
714 raise AttributeError, "Property %s does not exist." % property.name
715
717 if property.name == "visible":
718 self._show = value
719 elif property.name == "antialias":
720 self._antialias = value
721 elif property.name == "show-horizontal":
722 self._show_h = value
723 elif property.name == "show-vertical":
724 self._show_v = value
725 elif property.name == "color":
726 self._color = value
727 else:
728 raise AttributeError, "Property %s does not exist." % property.name
729
731 c = self._color
732 context.set_source_rgb(c[0], c[1], c[2])
733
734 if self._show_h:
735 ytics = self._range_calc.get_ytics(rect)
736 xa = rect.width * GRAPH_PADDING
737 xb = rect.width * (1 - GRAPH_PADDING)
738 for ((x, y), label) in ytics:
739 context.move_to(xa, y)
740 context.line_to(xb, y)
741 context.stroke()
742
743
744 if self._show_v:
745 xtics = self._range_calc.get_xtics(rect)
746 ya = rect.height * GRAPH_PADDING
747 yb = rect.height * (1 - GRAPH_PADDING)
748 for ((x, y), label) in xtics:
749 context.move_to(x, ya)
750 context.line_to(x, yb)
751 context.stroke()
752
754 """
755 Set whether to draw horizontal grid lines.
756
757 @type draw: boolean.
758 """
759 self.set_property("show-horizontal", draw)
760 self.emit("appearance_changed")
761
763 """
764 Returns True if horizontal grid lines are drawn.
765
766 @return: boolean.
767 """
768 return self.get_property("show-horizontal")
769
771 """
772 Set whether to draw vertical grid lines.
773
774 @type draw: boolean.
775 """
776 self.set_property("show-vertical", draw)
777 self.emit("appearance_changed")
778
780 """
781 Returns True if vertical grid lines are drawn.
782
783 @return: boolean.
784 """
785 return self.get_property("show-vertical")
786
788 """
789 Set the color of the grid.
790
791 @type color: a color
792 @param color: The new color of the grid.
793 """
794 self.set_property("color", color)
795 self.emit("appearance_changed")
796
798 """
799 Returns the color of the grid.
800
801 @return: a color.
802 """
803 return self.get_property("color")
804
805
806 -class Graph(chart.ChartObject):
807 """
808 This class represents a graph or the data you want to plot on your
809 LineChart widget.
810 """
811
812 __gproperties__ = {"name": (gobject.TYPE_STRING, "graph id",
813 "The graph's unique name.",
814 "", gobject.PARAM_READABLE),
815 "title": (gobject.TYPE_STRING, "graph title",
816 "The title of the graph.", "",
817 gobject.PARAM_READWRITE),
818 "color": (gobject.TYPE_PYOBJECT,
819 "graph color",
820 "The color of the graph in (r,g,b) format. r,g,b in [0,1].",
821 gobject.PARAM_READWRITE),
822 "type": (gobject.TYPE_INT, "graph type",
823 "The type of the graph.", 1, 3, 3,
824 gobject.PARAM_READWRITE),
825 "point-size": (gobject.TYPE_INT, "point size",
826 "Radius of the data points.", 1,
827 100, 2, gobject.PARAM_READWRITE),
828 "fill-to": (gobject.TYPE_PYOBJECT, "fill to",
829 "Set how to fill space under the graph.",
830 gobject.PARAM_READWRITE),
831 "fill-color": (gobject.TYPE_PYOBJECT, "fill color",
832 "Set which color to use when filling space under the graph.",
833 gobject.PARAM_READWRITE),
834 "fill-opacity": (gobject.TYPE_FLOAT, "fill opacity",
835 "Set which opacity to use when filling space under the graph.",
836 0.0, 1.0, 0.3, gobject.PARAM_READWRITE),
837 "show-values": (gobject.TYPE_BOOLEAN, "show values",
838 "Sets whether to show the y values.",
839 False, gobject.PARAM_READWRITE),
840 "show-title": (gobject.TYPE_BOOLEAN, "show title",
841 "Sets whether to show the graph's title.",
842 True, gobject.PARAM_READWRITE)}
843
845 """
846 Create a new instance.
847
848 @type name: string
849 @param name: A unique name for the graph. This could be everything.
850 It's just a name used internally for identification. You need to know
851 this if you want to access or delete a graph from a chart.
852 @type title: string
853 @param title: The graphs title. This can be drawn on the chart.
854 @type data: list of pairs of numbers
855 @param data: This is the data you want to be visualized. data has to
856 be a list of (x, y) pairs.
857 """
858 chart.ChartObject.__init__(self)
859 self._name = name
860 self._title = title
861 self._data = data
862 self._color = COLOR_AUTO
863 self._type = GRAPH_BOTH
864 self._point_size = 2
865 self._show_value = False
866 self._show_title = True
867 self._fill_to = None
868 self._fill_color = COLOR_AUTO
869 self._fill_opacity = 0.3
870
871 self._range_calc = None
872
874 if property.name == "visible":
875 return self._show
876 elif property.name == "antialias":
877 return self._antialias
878 elif property.name == "name":
879 return self._name
880 elif property.name == "title":
881 return self._title
882 elif property.name == "color":
883 return self._color
884 elif property.name == "type":
885 return self._type
886 elif property.name == "point-size":
887 return self._point_size
888 elif property.name == "fill-to":
889 return self._fill_to
890 elif property.name == "fill-color":
891 return self._fill_color
892 elif property.name == "fill-opacity":
893 return self._fill_opacity
894 elif property.name == "show-values":
895 return self._show_value
896 elif property.name == "show-title":
897 return self._show_title
898 else:
899 raise AttributeError, "Property %s does not exist." % property.name
900
902 if property.name == "visible":
903 self._show = value
904 elif property.name == "antialias":
905 self._antialias = value
906 elif property.name == "title":
907 self._title = value
908 elif property.name == "color":
909 self._color = value
910 elif property.name == "type":
911 self._type = value
912 elif property.name == "point-size":
913 self._point_size = value
914 elif property.name == "fill-to":
915 self._fill_to = value
916 elif property.name == "fill-color":
917 self._fill_color = value
918 elif property.name == "fill-opacity":
919 self._fill_opacity = value
920 elif property.name == "show-values":
921 self._show_value = value
922 elif property.name == "show-title":
923 self._show_title = value
924 else:
925 raise AttributeError, "Property %s does not exist." % property.name
926
928 return self._data != []
929
931 """
932 Draws the title.
933
934 @type context: cairo.Context
935 @param context: The context to draw on.
936 @type rect: gtk.gdk.Rectangle
937 @param rect: A rectangle representing the charts area.
938 @type last_point: pairs of numbers
939 @param last_point: The absolute position of the last drawn data point.
940 """
941 c = self._color
942 context.set_source_rgb(c[0], c[1], c[2])
943
944 font_size = rect.height / 50
945 if font_size < 9: font_size = 9
946 context.set_font_size(font_size)
947 size = context.text_extents(self._title)
948 if last_point:
949 context.move_to(last_point[0] + 5, last_point[1] + size[3] / 3)
950 context.show_text(self._title)
951 context.stroke()
952
954 if type(self._fill_to) in (int, float):
955 data = []
956 for i, (x, y) in enumerate(self._data):
957 if is_in_range(x, xrange) and not data:
958 data.append((x, self._fill_to))
959 elif not is_in_range(x, xrange) and len(data) == 1:
960 data.append((prev, self._fill_to))
961 break
962 elif i == len(self._data) - 1:
963 data.append((x, self._fill_to))
964 prev = x
965 graph = Graph("none", "", data)
966 elif type(self._fill_to) == Graph:
967 graph = self._fill_to
968 d = graph.get_data()
969 range_b = d[0][0], d[-1][0]
970 xrange = intersect_ranges(xrange, range_b)
971
972 if not graph.get_visible(): return
973
974 c = self._fill_color
975 if c == COLOR_AUTO: c = self._color
976 context.set_source_rgba(c[0], c[1], c[2], self._fill_opacity)
977
978 data_a = self._data
979 data_b = graph.get_data()
980
981 first = True
982 start_point = (0, 0)
983 for x, y in data_a:
984 if is_in_range(x, xrange):
985 xa, ya = self._range_calc.get_absolute_point(rect, x, y)
986 if first:
987 context.move_to(xa, ya)
988 start_point = xa, ya
989 first = False
990 else:
991 context.line_to(xa, ya)
992
993 first = True
994 for i in range(0, len(data_b)):
995 j = len(data_b) - i - 1
996 x, y = data_b[j]
997 xa, ya = self._range_calc.get_absolute_point(rect, x, y)
998 if is_in_range(x, xrange):
999 context.line_to(xa, ya)
1000 context.line_to(*start_point)
1001 context.fill()
1002
1004 """
1005 Draw the graph.
1006
1007 @type context: cairo.Context
1008 @param context: The context to draw on.
1009 @type rect: gtk.gdk.Rectangle
1010 @param rect: A rectangle representing the charts area.
1011 """
1012
1013 (xrange, yrange) = self._range_calc.get_ranges()
1014 c = self._color
1015 context.set_source_rgb(c[0], c[1], c[2])
1016 previous = None
1017 last = None
1018 for (x, y) in self._data:
1019 if is_in_range(x, xrange) and is_in_range(y, yrange):
1020 (ax, ay) = self._range_calc.get_absolute_point(rect, x, y)
1021 if self._type == GRAPH_POINTS or self._type == GRAPH_BOTH:
1022 context.arc(ax, ay, self._point_size, 0, 2 * math.pi)
1023 context.fill()
1024 context.move_to(ax+2*self._point_size,ay)
1025 if self._show_value:
1026 font_size = rect.height / 50
1027 if font_size < 9: font_size = 9
1028 context.set_font_size(font_size)
1029 context.show_text(str(y))
1030 if self._type == GRAPH_LINES or self._type == GRAPH_BOTH:
1031 if previous != None:
1032 context.move_to(previous[0], previous[1])
1033 context.line_to(ax, ay)
1034 context.stroke()
1035 previous = (ax, ay)
1036 last = (ax, ay)
1037 else:
1038 previous = None
1039
1040 if self._fill_to != None:
1041 self._do_draw_fill(context, rect, xrange)
1042
1043 if self._show_title:
1044 self._do_draw_title(context, rect, last)
1045
1047 """
1048 Get the the endpoints of the x interval.
1049
1050 @return: pair of numbers
1051 """
1052 try:
1053 self._data.sort(lambda x, y: cmp(x[0], y[0]))
1054 return (self._data[0][0], self._data[-1][0])
1055 except:
1056 return None
1057
1059 """
1060 Get the the endpoints of the y interval.
1061
1062 @return: pair of numbers
1063 """
1064 try:
1065 self._data.sort(lambda x, y: cmp(x[1], y[1]))
1066 return (self._data[0][1], self._data[-1][1])
1067 except:
1068 return None
1069
1071 """
1072 Get the name of the graph.
1073
1074 @return: string
1075 """
1076 return self.get_property("name")
1077
1079 """
1080 Returns the title of the graph.
1081
1082 @return: string
1083 """
1084 return self.get_property("title")
1085
1087 """
1088 Set the title of the graph.
1089
1090 @type title: string
1091 @param title: The graph's new title.
1092 """
1093 self.set_property("title", title)
1094 self.emit("appearance_changed")
1095
1097 self._range_calc = range_calc
1098
1100 """
1101 Returns the current color of the graph or COLOR_AUTO.
1102
1103 @return: a color (see set_color() for details).
1104 """
1105 return self.get_property("color")
1106
1108 """
1109 Set the color of the graph. color has to be a (r, g, b) triple
1110 where r, g, b are between 0 and 1.
1111 If set to COLOR_AUTO, the color will be choosen dynamicly.
1112
1113 @type color: a color
1114 @param color: The new color of the graph.
1115 """
1116 self.set_property("color", color)
1117 self.emit("appearance_changed")
1118
1120 """
1121 Returns the type of the graph.
1122
1123 @return: a type constant (see set_type() for details)
1124 """
1125 return self.get_property("type")
1126
1128 """
1129 Set the type of the graph to one of these:
1130 - GRAPH_POINTS: only show points
1131 - GRAPH_LINES: only draw lines
1132 - GRAPH_BOTH: draw points and lines, i.e. connect points with lines
1133
1134 @param type: One of the constants above.
1135 """
1136 self.set_property("type", type)
1137 self.emit("appearance_changed")
1138
1140 """
1141 Returns the radius of the data points.
1142
1143 @return: a poisitive integer
1144 """
1145 return self.get_property("point_size")
1146
1148 """
1149 Set the radius of the drawn points.
1150
1151 @type size: a positive integer in [1, 100]
1152 @param size: The new radius of the points.
1153 """
1154 self.set_property("point_size", size)
1155 self.emit("appearance_changed")
1156
1158 """
1159 The return value of this method depends on the filling under
1160 the graph. See set_fill_to() for details.
1161 """
1162 return self.get_property("fill-to")
1163
1165 """
1166 Use this method to specify how the space under the graph should
1167 be filled. fill_to has to be one of these:
1168
1169 - None: dont't fill the space under the graph.
1170 - int or float: fill the space to the value specified (setting
1171 fill_to=0 means filling the space between graph and xaxis).
1172 - a Graph object: fill the space between this graph and the
1173 graph given as the argument.
1174
1175 The color of the filling is the graph's color with 30% opacity.
1176
1177 @type fill_to: one of the possibilities listed above.
1178 """
1179 self.set_property("fill-to", fill_to)
1180 self.emit("appearance_changed")
1181
1183 """
1184 Returns the color that is used to fill space under the graph
1185 or COLOR_AUTO.
1186 """
1187 return self.get_property("fill-color")
1188
1190 """
1191 Set which color should be used when filling the space under a
1192 graph.
1193 If color is COLOR_AUTO, the graph's color will be used.
1194
1195 @type color: a color or COLOR_AUTO.
1196 """
1197 self.set_property("fill-color", color)
1198 self.emit("appearance_changed")
1199
1201 """
1202 Returns the opacity that is used to fill space under the graph.
1203 """
1204 return self.get_property("fill-opacity")
1205
1207 """
1208 Set which opacity should be used when filling the space under a
1209 graph. The default is 0.3.
1210
1211 @type opacity: float in [0, 1].
1212 """
1213 self.set_property("fill-opacity", opacity)
1214 self.emit("appearance_changed")
1215
1217 """
1218 Returns True if y values are shown.
1219
1220 @return: boolean
1221 """
1222 return self.get_property("show-values")
1223
1225 """
1226 Set whether the y values should be shown (only if graph type
1227 is GRAPH_POINTS or GRAPH_BOTH).
1228
1229 @type show: boolean
1230 """
1231 self.set_property("show-values", show)
1232 self.emit("appearance_changed")
1233
1235 """
1236 Returns True if the title of the graph is shown.
1237
1238 @return: boolean.
1239 """
1240 return self.get_property("show-title")
1241
1243 """
1244 Set whether to show the graph's title or not.
1245
1246 @type show: boolean.
1247 """
1248 self.set_property("show-title", show)
1249 self.emit("appearance_changed")
1250
1252 """
1253 Add data to the graph.
1254
1255 @type data_list: a list of pairs of numbers
1256 """
1257 self._data += data_list
1258 self._range_calc.add_graph(self)
1259
1261 """
1262 Returns the data of the graph.
1263
1264 @return: a list of x, y pairs.
1265 """
1266 return self._data
1267
1268
1270 """
1271 Returns a line_chart.Graph with data created from the function
1272 y = func(x) with x in [xmin, xmax]. The id of the new graph is
1273 graph_name.
1274 The parameter samples gives the number of points that should be
1275 evaluated in [xmin, xmax] (default: 100).
1276 If do_optimize_sampling is True (default) additional points will be
1277 evaluated to smoothen the curve.
1278
1279 @type func: a function
1280 @param func: the function to evaluate
1281 @type xmin: float
1282 @param xmin: the minimum x value to evaluate
1283 @type xmax: float
1284 @param xmax: the maximum x value to evaluate
1285 @type graph_name: string
1286 @param graph_name: a unique name for the new graph
1287 @type samples: int
1288 @param samples: number of samples
1289 @type do_optimize_sampling: boolean
1290 @param do_optimize_sampling: set whether to add additional points
1291
1292 @return: line_chart.Graph
1293 """
1294 delta = (xmax - xmin) / float(samples)
1295 data = []
1296 x = xmin
1297 while x <= xmax:
1298 data.append((x, func(x)))
1299 x += delta
1300
1301 if do_optimize_sampling:
1302 data = optimize_sampling(func, data)
1303
1304 return Graph(graph_name, "", data)
1305
1307 new_data = []
1308 prev_point = None
1309 prev_slope = None
1310 for x, y in data:
1311 if prev_point != None:
1312 if (x - prev_point[0]) == 0: return data
1313 slope = (y - prev_point[1]) / (x - prev_point[0])
1314 if prev_slope != None:
1315 if abs(slope - prev_slope) >= 0.1:
1316 nx = prev_point[0] + (x - prev_point[0]) / 2.0
1317 ny = func(nx)
1318 new_data.append((nx, ny))
1319
1320 prev_slope = slope
1321
1322 prev_point = x, y
1323
1324 if new_data:
1325 data += new_data
1326 data.sort(lambda x, y: cmp(x[0], y[0]))
1327 return optimize_sampling(func, data)
1328 else:
1329 return data
1330
1332 """
1333 Returns a line_chart.Graph with point taken from data file
1334 filename.
1335 The id of the new graph is graph_name.
1336
1337 Data file format:
1338 The columns in the file have to be separated by tabs or one
1339 or more spaces. Everything after '#' is ignored (comment).
1340
1341 Use the parameters x_col and y_col to control which columns to use
1342 for plotting. By default, the first column (x_col=0) is used for
1343 x values, the second (y_col=1) is used for y values.
1344
1345 @type filename: string
1346 @param filename: path to the data file
1347 @type graph_name: string
1348 @param graph_name: a unique name for the graph
1349 @type x_col: int
1350 @param x_col: the number of the column to use for x values
1351 @type y_col: int
1352 @param y_col: the number of the column to use for y values
1353
1354 @return: line_chart.Graph
1355 """
1356 points = []
1357 f = open(filename, "r")
1358 data = f.read()
1359 f.close()
1360 lines = data.split("\n")
1361
1362 for line in lines:
1363 line = line.strip()
1364
1365
1366 a = line.split("#", 1)
1367 if a and a[0]:
1368 line = a[0]
1369
1370 if line.find("\t") != -1:
1371
1372 d = line.split("\t")
1373 else:
1374
1375
1376 while line.find(" ") != -1:
1377 line = line.replace(" ", " ")
1378 d = line.split(" ")
1379 d = filter(lambda x: x, d)
1380 d = map(lambda x: float(x), d)
1381
1382 points.append((d[x_col], d[y_col]))
1383 return Graph(graph_name, "", points)
1384