Added TODO and DEVELOPMENT.
[faces-project.git] / faces / charting / taxis.py
blob5f3453911d67d32eeeddf70b8e00138754c5a046
1 #@+leo-ver=4
2 #@+node:@file charting/taxis.py
3 #@@language python
4 """
5 A timeaxis for gantt and resource charts.
6 """
7 #@<< Copyright >>
8 #@+node:<< Copyright >>
9 ############################################################################
10 # Copyright (C) 2005, 2006, 2007, 2008 by Reithinger GmbH
11 # mreithinger@web.de
13 # This file is part of faces.
15 # faces is free software; you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation; either version 2 of the License, or
18 # (at your option) any later version.
20 # faces is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the
27 # Free Software Foundation, Inc.,
28 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 ############################################################################
31 #@-node:<< Copyright >>
32 #@nl
33 #@<< Imports >>
34 #@+node:<< Imports >>
35 import matplotlib.font_manager as font
36 import matplotlib.transforms as mtrans
37 import matplotlib.colors as colors
38 import matplotlib.artist as artist
39 import matplotlib._image as mimage
40 import datetime
41 import widgets
42 import faces.plocale
43 import locale
44 from faces.pcalendar import strftime
45 from tools import *
48 #@-node:<< Imports >>
49 #@nl
51 _is_source_ = True
52 _ = faces.plocale.get_gettext()
54 _colorConverter = colors.colorConverter
56 _week_name = _("Week")
58 def alt_week_locator(alt=True):
59 """
60 use an alternate week locator for gantt charts.
61 """
62 global _week_name
63 if alt:
64 _week_name = ""
65 else:
66 _week_name = _("Week")
68 #@+others
69 #@+node:Locators
70 #@+others
71 #@+node:class Locator
72 class Locator(object):
73 #@ << declarations >>
74 #@+node:<< declarations >>
75 can_locate_free_time = False
77 #@-node:<< declarations >>
78 #@nl
79 #@ @+others
80 #@+node:__init__
81 def __init__(self):
82 self.tick_pos = (0, 0) # position is 0, the highest positon is 0
83 self.sizes = {}
84 self.format_cache = { }
85 #@-node:__init__
86 #@+node:get_marks
87 def get_marks(self, intervals, scale, transform):
88 xmin, xmax = transform.get_bbox1().intervalx().get_bounds()
89 if intervals[0][0] < xmin:
90 intervals[0] = (xmin, intervals[0][1])
92 if intervals[-1][1] > xmax:
93 intervals[-1] = (intervals[-1][0], xmax)
95 middles = map(lambda i: (i[0] + i[1]) / 2, intervals)
96 build_mark = self.build_mark
97 marks = [ build_mark(i, scale, transform) for i in intervals ]
98 xs = transform.seq_x_y(middles, middles)[0]
99 return zip(marks, xs)
100 #@-node:get_marks
101 #@+node:build_mark
102 def build_mark(self, interval, scale, transform):
103 format = self._get_format(interval, transform)
104 date = scale.to_num(int(interval[0])).to_datetime()
105 quater = 1 + (date.month - 1) / 3
106 decade = 10 * (date.year / 10)
107 f = format.replace("%Q", str(quater))
108 f = f.replace("%D", str(decade))
109 return strftime(date, f)
110 #@nonl
111 #@-node:build_mark
112 #@+node:fits
113 def fits(self, transform, scale):
114 delta = self._delta(scale)
115 key = self.tick_pos[0] == self.tick_pos[1] and "top" or "default"
116 sizes = self.sizes.get(key, self.sizes["default"])[0]
117 delta = transform(delta)
118 return sizes[-1][0] < delta
119 #@-node:fits
120 #@+node:prepare
121 def prepare(self, renderer, fonts, tickers):
122 "precalculates all possible marker sizes"
123 self.renderer = renderer
124 self.fonts = zip(tickers, fonts)
125 self._calc_sizes()
126 for v in self.sizes.itervalues():
127 for s in v.itervalues():
128 s.sort()
129 s.reverse()
131 del self.renderer
132 del self.fonts
133 #@-node:prepare
134 #@+node:_calc_sizes
135 def _calc_sizes(self):
136 raise RuntimeError("abstract")
137 #@-node:_calc_sizes
138 #@+node:_delta
139 def _delta(self, scale):
140 raise RuntimeError("abstract")
141 #@-node:_delta
142 #@+node:_get_format
143 def _get_format(self, interval, transform):
144 delta = int(interval[0] - interval[1])
145 format = self.format_cache.get(delta)
146 if format: return format
148 x, y = transform.seq_x_y(interval, (0, 0))
149 tdelta = x[1] - x[0]
150 key = self.tick_pos[0] == self.tick_pos[1] and "top" or "default"
151 sizes = self.sizes.get(key, self.sizes["default"])[self.tick_pos[0]]
152 for s, f in sizes:
153 if s < tdelta:
154 format = f
155 break
156 else:
157 format = ""
159 self.format_cache[delta] = format
160 return format
161 #@-node:_get_format
162 #@+node:_calc_markers
163 def _calc_markers(self, markers, format, key="default"):
164 extent = self.renderer.get_text_width_height
166 key_fonts = self.sizes.get(key)
167 if not key_fonts:
168 key_fonts = {}
169 for i, f in self.fonts: key_fonts[i] = []
171 if not isinstance(markers, (list, tuple)):
172 markers = (markers,)
174 for i, f in self.fonts:
175 size = max([extent(m, f, False)[0] for m in markers])
176 key_fonts[i].append((size, str(format)))
178 self.sizes[key] = key_fonts
179 #@-node:_calc_markers
180 #@+node:is_free
181 def is_free(self, num_date):
182 #num_date is int
183 return False
184 #@-node:is_free
185 #@-others
186 #@-node:class Locator
187 #@+node:class DecadeLocator
188 class DecadeLocator(Locator):
189 #@ @+others
190 #@+node:_delta
191 def _delta(self, scale):
192 return scale.week_delta * 52 * 10
193 #@-node:_delta
194 #@+node:_calc_sizes
195 def _calc_sizes(self):
196 self._calc_markers("88888", "%D")
197 #@-node:_calc_sizes
198 #@+node:__call__
199 def __call__(self, left, right, time_scale):
200 num = time_scale.to_num
201 dt = datetime.datetime
202 left = num(int(left))
203 right = num(int(right))
204 start = left.to_datetime().year / 10
205 end = right.to_datetime().year / 10 + 2
206 locs = map(lambda y: num(dt(y * 10, 1, 1)), range(start, end))
207 return locs
208 #@-node:__call__
209 #@-others
210 #@-node:class DecadeLocator
211 #@+node:class YearLocator
212 class YearLocator(Locator):
213 #@ @+others
214 #@+node:_delta
215 def _delta(self, scale):
216 return scale.week_delta * 52
217 #@-node:_delta
218 #@+node:_calc_sizes
219 def _calc_sizes(self):
220 self._calc_markers("88888", "%IY")
221 #@-node:_calc_sizes
222 #@+node:__call__
223 def __call__(self, left, right, time_scale):
224 num = time_scale.to_num
225 dt = datetime.datetime
226 left = num(int(left))
227 right = num(int(right))
228 start = left.to_datetime().year
229 end = right.to_datetime().year + 2
230 locs = map(lambda y: num(dt(y, 1, 1)), range(start, end))
231 return locs
232 #@-node:__call__
233 #@-others
234 #@-node:class YearLocator
235 #@+node:class QuaterLocator
236 class QuaterLocator(Locator):
237 #@ @+others
238 #@+node:_delta
239 def _delta(self, scale):
240 return scale.week_delta * 12
241 #@-node:_delta
242 #@+node:_calc_sizes
243 def _calc_sizes(self):
244 self._calc_markers("Q 8/88888", "Q %Q/%IY", "top")
245 self._calc_markers("Q 88", "Q %Q")
246 #@-node:_calc_sizes
247 #@+node:__call__
248 def __call__(self, left, right, time_scale):
249 num = time_scale.to_num
250 dt = datetime.datetime
251 left = num(int(left))
252 right = num(int(right))
253 start = left.to_datetime()
254 end = right.to_datetime()
255 start = start.year * 4 + (start.month - 1) / 3
256 end = end.year * 4 + (end.month - 1) / 3 + 2
257 locs = map(lambda qy: num(dt(qy/4, (qy%4)*3+1, 1)), range(start, end))
258 return locs
259 #@-node:__call__
260 #@-others
261 #@-node:class QuaterLocator
262 #@+node:class MonthLocator
263 class MonthLocator(Locator):
264 #@ @+others
265 #@+node:_delta
266 def _delta(self, scale):
267 return scale.week_delta * 4
268 #@-node:_delta
269 #@+node:_calc_sizes
270 def _calc_sizes(self):
271 dt = datetime.datetime
272 def mlist(format):
273 return map(lambda m: strftime(dt(2005, m, 1), format), range(1, 13))
275 self._calc_markers(mlist("%B 88888"), "%B %IY", "top")
276 self._calc_markers(mlist("%b 88888"), "%b %IY", "top")
277 self._calc_markers(mlist("%m.88888"), "%m.%IY", "top")
278 self._calc_markers(mlist("%B"), "%B")
279 self._calc_markers(mlist("%b"), "%b")
280 self._calc_markers("8888", "%m")
281 #@-node:_calc_sizes
282 #@+node:__call__
283 def __call__(self, left, right, time_scale):
284 num = time_scale.to_num
285 dt = datetime.datetime
287 left = num(int(left))
288 right = num(int(right))
289 start = left.to_datetime()
290 end = right.to_datetime()
291 start = start.year * 12 + start.month - 1
292 end = end.year * 12 + end.month + 1
293 locs = map(lambda my: num(dt(my/12, 1+my%12, 1)), range(start, end))
294 return locs
295 #@-node:__call__
296 #@-others
297 #@-node:class MonthLocator
298 #@+node:class WeekLocator
299 class WeekLocator(Locator):
300 #@ @+others
301 #@+node:_delta
302 def _delta(self, scale):
303 return scale.week_delta
304 #@-node:_delta
305 #@+node:_calc_sizes
306 def _calc_sizes(self):
307 global _week_name
309 dt = datetime.datetime
310 def mlist(format):
311 return map(lambda m: strftime(dt(2005, m, 1), str(format)), range(1, 13))
313 if _week_name:
314 self._calc_markers(mlist("%IW. " + _week_name + " %IB 88888"),
315 "%IW. " + _week_name + " %IB %IY", "top")
316 self._calc_markers(mlist("%IW. " + _week_name + " %ib 88888"),
317 "%IW. " + _week_name + " %ib %IY", "top")
318 self._calc_markers(mlist("%IW. " + _week_name + " %im.88888"),
319 "%IW. " + _week_name + " %m.%IY", "top")
320 self._calc_markers(mlist("%IW %ib 88888"), "%IW %ib %IY", "top")
321 self._calc_markers(mlist("%IW %im 88888"), "%IW %im.%IY", "top")
322 self._calc_markers("888. " + _week_name, "%IW. " + _week_name)
323 self._calc_markers("8888", "%IW")
324 else:
325 # in the US week numbers are not used
326 self._calc_markers(mlist("%B 88"), "%B %d")
327 self._calc_markers(mlist("%b. 88"), "%b. %d")
329 #@-node:_calc_sizes
330 #@+node:__call__
331 def __call__(self, left, right, time_scale):
332 num = time_scale.to_num
333 left = num(int(left))
334 right = num(int(right)) + time_scale.week_delta
335 start = left.to_datetime().replace(hour=0, minute=0)
336 start -= datetime.timedelta(days=start.weekday())
337 start = num(start)
338 locs = range(start, right, time_scale.week_delta)
339 return locs
340 #@-node:__call__
341 #@-others
342 #@-node:class WeekLocator
343 #@+node:class DayLocator
344 class DayLocator(Locator):
345 #@ << declarations >>
346 #@+node:<< declarations >>
347 can_locate_free_time = True
350 #@-node:<< declarations >>
351 #@nl
352 #@ @+others
353 #@+node:_delta
354 def _delta(self, scale):
355 return scale.day_delta
356 #@-node:_delta
357 #@+node:_calc_sizes
358 def _calc_sizes(self):
359 dt = datetime.datetime
360 def dlist(format):
361 return map(lambda d: strftime(dt(2005, 1, d), format), range(1, 8))
363 self._calc_markers(dlist("%A %x88"), "%A %x", "top")
364 self._calc_markers(dlist("%a %x88"), "%a %x", "top")
365 self._calc_markers(dlist("%x88"), "%x", "top")
366 self._calc_markers(dlist("%A 888."), "%A %d.")
367 self._calc_markers(dlist("%a 888."), "%a %d.")
368 self._calc_markers("8888", "%d")
369 #@-node:_calc_sizes
370 #@+node:__call__
371 def __call__(self, left, right, time_scale):
372 self.time_scale = time_scale
373 num = time_scale.to_num
374 date = time_scale.to_datetime
375 td = datetime.timedelta
376 left = date(num(int(left))).replace(hour=0, minute=0)
377 right = date(num(int(right)))
378 days = (right - left).days + 2
379 locs = map(lambda d: num(left + td(days=d)), range(0, days))
380 return locs
381 #@-node:__call__
382 #@+node:is_free
383 def is_free(self, num_date):
384 return self.time_scale.is_free_day(num_date)
385 #@-node:is_free
386 #@-others
387 #@-node:class DayLocator
388 #@+node:class SlotLocator
389 class SlotLocator(Locator):
390 #@ << declarations >>
391 #@+node:<< declarations >>
392 can_locate_free_time = True
395 #@-node:<< declarations >>
396 #@nl
397 #@ @+others
398 #@+node:_delta
399 def _delta(self, scale):
400 return scale.slot_delta
401 #@-node:_delta
402 #@+node:__call__
403 def __call__(self, left, right, time_scale):
404 self.time_scale = time_scale
405 num = time_scale.to_num
406 date = time_scale.to_datetime
407 td = datetime.timedelta
408 left = date(num(int(left))).replace(hour=0, minute=0)
409 right = date(num(int(right)))
410 days = (right - left).days + 2
411 days = map(lambda d: left + td(days=d), range(0, days))
412 get_working_times = time_scale.chart_calendar.get_working_times
414 locs = []
415 for d in days:
416 slots = get_working_times(d.weekday())
417 locs.extend(map(lambda s: num(d + td(minutes=s[0])), slots))
419 return locs
420 #@-node:__call__
421 #@+node:_calc_sizes
422 def _calc_sizes(self):
423 self._calc_markers("888:88-88:88", "%(sh)02i:%(sm)02i-%(eh)02i:%(em)02i")
424 self._calc_markers("888-88", "%(sh)02i-%(eh)02i")
425 self._calc_markers("888:88", "%(sh)02i:%(sm)02i")
426 self._calc_markers("888", "%(sh)02i")
427 #@-node:_calc_sizes
428 #@+node:get_marks
429 def get_marks(self, intervals, scale, transform):
430 def build_mark(interval):
431 format = self._get_format(interval, transform)
432 start = scale.to_num(interval[0]).to_datetime()
433 end = scale.to_num(interval[1]).to_datetime()
434 vals = { "sh" : start.hour,
435 "sm" : start.minute,
436 "eh" : end.hour,
437 "em" : end.minute }
438 return format % vals
440 middles = map(lambda i: (i[0] + i[1]) / 2, intervals)
441 marks = map(build_mark, intervals)
442 xs = transform.seq_x_y(middles, (0,)*len(middles))[0]
443 return zip(marks, xs)
444 #@-node:get_marks
445 #@+node:is_free
446 def is_free(self, num_date):
447 return self.time_scale.is_free_slot(num_date)
448 #@-node:is_free
449 #@-others
450 #@-node:class SlotLocator
451 #@-others
453 _locators = ( SlotLocator,
454 DayLocator,
455 WeekLocator,
456 MonthLocator,
457 QuaterLocator,
458 YearLocator,
459 DecadeLocator )
460 #@-node:Locators
461 #@+node:_zigzag_lines
462 def _zigzag_lines(locs, top, bottom):
463 xs = locs * 2
464 xs.sort()
465 ys = [ top, bottom, bottom, top ] * ((len(locs) + 1) / 2)
466 if len(locs) % 2: del ys[-2:]
467 return xs, ys
468 #@-node:_zigzag_lines
469 #@+node:class TimeAxis
470 class TimeAxis(artist.Artist, widgets._PropertyAware):
471 #@ << declarations >>
472 #@+node:<< declarations >>
473 properties = {
474 "family": "sans-serif",
475 #"family": [ "Arial", "Verdana", "Bitstream Vera Sans" ] ,
476 "weight": "normal",
477 "size" : "medium",
478 "style" : "normal",
479 "variant" : "normal",
480 "2.weight" : "bold",
481 "2.size" : "x-large",
482 "1.weight" : "bold",
483 "1.size" : "large",
484 "color": 'black',
485 "0.facecolor" : 'white',
486 "facecolor" : 'darkgray',
487 "edgecolor" : 'black',
488 "grid.edgecolor" : 'darkgray',
489 "free.facecolor": "lightgrey",
490 "linewidth" : 1,
491 "joinstyle" : 'miter',
492 "linestyle" : 'solid',
493 "now.edgecolor" : "black",
494 "now.linewidth" : 2,
495 "now.linestyle" : "dashed",
496 "antialiased" : True,
497 "alpha" : 1.0,
498 "tickers" : (1, ) }
501 zorder = -100
502 show_grid = True
503 show_scale = True
504 show_free_time = True
505 show_now = True
506 time_scale = None # must be set by Chart
508 #@-node:<< declarations >>
509 #@nl
510 #@ @+others
511 #@+node:__init__
512 def __init__(self, properties=None):
513 widgets._PropertyAware.__init__(self, properties)
514 artist.Artist.__init__(self)
515 self._locators = tuple(map(lambda l: l(), _locators))
516 self._last_cache = None
517 self._last_cache_state = None
518 self._last_width = 0
519 self.encoding = locale.getlocale()[1] or "ascii"
520 #@-node:__init__
521 #@+node:calc_height
522 def calc_height(self):
523 if not self.show_scale:
524 self.height = 0
525 return 0
527 prop = self.get_property
528 def_height = font.fontManager.get_default_size()
530 sep = def_height / 3
531 tickers = (0,) + prop("tickers")
532 self.height = 0
533 for t in tickers:
534 tsize = self.get_font(str(t)).get_size_in_points()
535 self.height += tsize + 2 * sep
537 return self.height
538 #@-node:calc_height
539 #@+node:set_transform
540 def set_transform(self, t):
541 #a non scaled point y axis
542 Value = mtrans.Value
543 Point = mtrans.Point
544 Bbox = mtrans.Bbox
545 Transformation = mtrans.SeparableTransformation
547 fig_point_to_pixel = self.get_figure().dpi / mtrans.Value(72)
549 view_box = t.get_bbox2()
550 top = view_box.ur().y()
551 bottom = view_box.ll().y()
552 point_height = (bottom - top) / fig_point_to_pixel
554 bbox = t.get_bbox1()
555 ll = bbox.ll()
556 ur = bbox.ur()
557 new_ll = Point(ll.x(), point_height)
558 new_ur = Point(ur.x(), Value(0))
559 data_box = Bbox(new_ll, new_ur)
561 t = Transformation(data_box, view_box, t.get_funcx(), t.get_funcy())
562 artist.Artist.set_transform(self, t)
563 #@-node:set_transform
564 #@+node:draw
565 __prepared = False
566 def draw(self, renderer):
567 if not self.get_visible(): return
569 trans = self.get_transform()
570 trans.freeze()
571 try:
572 if not self.__prepared:
573 self.__prepared = True
574 tickers = (0,) + self.get_property("tickers")
575 fonts = map(lambda t: self.get_font(str(t)), tickers)
576 for l in self._locators:
577 l.prepare(renderer, fonts, tickers)
579 data_box = trans.get_bbox1()
580 view_box = trans.get_bbox2()
581 width = data_box.width()
583 if self._last_width != width:
584 self._last_width = width
585 self.find_ticker(renderer)
587 cache_state = (self.show_grid + self.show_scale,
588 view_box.width(), view_box.height(),
589 renderer, data_box.xmin())
591 if self._last_cache_state == cache_state and self._last_cache:
592 try:
593 #not now because of memory leak
594 renderer.draw_image(0, 0, self._last_cache, view_box)
595 #renderer.restore_region(self._last_cache)
596 return
597 except:
598 pass
600 gc = renderer.new_gc()
602 if self.get_clip_on():
603 gc.set_clip_rectangle(self.clipbox.get_bounds())
605 if self.show_grid: self.draw_grid(renderer, gc, trans)
606 if self.show_now:
607 time_scale = self.time_scale
608 left, right = data_box.intervalx().get_bounds()
609 if left <= time_scale.now <= right:
610 top, bottom = data_box.intervaly().get_bounds()
611 self.set_gc(gc, "now")
612 renderer.draw_lines(gc,
613 (time_scale.now, time_scale.now),
614 (top, bottom), trans)
616 if self.show_scale: self.draw_scale(renderer, gc, trans)
618 self._last_cache_state = cache_state
619 #self._last_cache = renderer.copy_from_bbox(view_box)
620 try:
621 self._last_cache = mimage.frombuffer(\
622 renderer.buffer_rgba(0, 0),
623 renderer.width,
624 renderer.height, 1)
625 except AttributeError:
626 self._last_cache = None
628 if self._last_cache:
629 self._last_cache.flipud_out()
630 finally:
631 trans.thaw()
632 #@-node:draw
633 #@+node:find_ticker
634 def find_ticker(self, renderer):
635 time_scale = self.time_scale
637 tickers = self.get_property("tickers")
638 if not isinstance(tickers, tuple):
639 tickers = tuple(tickers)
641 tickers = (0,) + tickers
642 highest_locator = tickers[-1]
644 transform = self.get_transform()
645 origin = transform.xy_tup((0, 0))[0]
647 def delta_trans(x_delta):
648 p = transform.xy_tup((x_delta, 0))
649 return p[0] - origin
651 def refresh_locators(lowest):
652 self.ticker = lowest
653 for t in tickers:
654 loc = self._locators[lowest + t]
655 loc.tick_pos = (t, highest_locator)
656 loc.format_cache.clear()
658 for ti in range(len(self._locators) - highest_locator):
659 loc = self._locators[ti]
660 loc.tick_pos = (0, highest_locator)
662 if loc.fits(delta_trans, time_scale):
663 refresh_locators(ti)
664 break
665 else:
666 refresh_locators(len(self._locators) - highest_locator - 1)
667 #@-node:find_ticker
668 #@+node:draw_scale
669 def draw_scale(self, renderer, gc, trans):
670 prop = self.get_property
671 time_scale = self.time_scale
673 def_height = font.fontManager.get_default_size()
674 sep = def_height / 3
675 left, right = trans.get_bbox1().intervalx().get_bounds()
676 dpi = self.get_figure().get_dpi()
678 if left >= right: return
680 self.set_gc(gc)
681 free_face = _colorConverter.to_rgb(prop("free.facecolor"))
683 def dline(x1, y1, x2, y2):
684 draw_line(renderer, gc, x1, y1, x2, y2, trans)
686 def draw_ticks(bottom, locator, name, show_free_time=False):
687 fp = self.get_font(name)
688 top = bottom - fp.get_size_in_points() - 2 * sep
690 locs = locator(left, right, time_scale)
691 lintervals = zip(locs[:-1], locs[1:])
693 face = _colorConverter.to_rgb(prop(name + ".facecolor"))
694 verts = ((left, -bottom), (left, -top),
695 (right, -top), (right, -bottom))
696 verts = trans.seq_xy_tups(verts)
697 renderer.draw_polygon(gc, face, verts)
699 if show_free_time and locator.can_locate_free_time:
700 gc.set_linewidth(0)
701 for l, r in lintervals:
702 #if locator.is_free((l + r) / 2):
703 if locator.is_free(l):
704 verts = ((l, -bottom), (l, -top),
705 (r, -top), (r, -bottom))
706 verts = trans.seq_xy_tups(verts)
707 renderer.draw_polygon(gc, free_face, verts)
709 fp = self.get_font(name)
710 gc.set_foreground(prop(name + ".color"))
711 x, y = trans.xy_tup((0, -bottom + sep))
712 markers = locator.get_marks(lintervals, time_scale, trans)
713 for m, x in markers:
714 self.draw_text(renderer, gc, x, y, m, fp, "bc", dpi)
716 gc.set_foreground(prop(name + ".edgecolor"))
717 gc.set_linewidth(prop(name + ".linewidth"))
719 xs, ys = _zigzag_lines(locs, -top, -bottom)
720 renderer.draw_lines(gc, xs, ys, trans)
722 gc.set_linewidth(prop("linewidth"))
723 dline(left, -top, right, -top)
724 dline(left, -bottom, right, -bottom)
726 return top
728 tickers = prop("tickers")
729 bottom = self.height
730 ticks = self._locators[self.ticker]
731 bottom = draw_ticks(bottom, ticks, "0", self.show_free_time)
732 for t in tickers:
733 ticks = self._locators[self.ticker + t]
734 bottom = draw_ticks(bottom, ticks, str(t))
735 #@-node:draw_scale
736 #@+node:draw_grid
737 def draw_grid(self, renderer, gc, trans):
738 time_scale = self.time_scale
739 prop = self.get_property
741 data_box = trans.get_bbox1()
742 left, right = data_box.intervalx().get_bounds()
743 top, bottom = data_box.intervaly().get_bounds()
745 if left >= right: return
747 locator = self._locators[self.ticker]
748 locs = locator(left, right, time_scale)
749 lintervals = zip(locs[:-1], locs[1:])
751 self.set_gc(gc, "grid")
752 if self.show_free_time and locator.can_locate_free_time:
753 gc.set_linewidth(0)
754 free_face = _colorConverter.to_rgb(prop("free.facecolor"))
755 for l, r in lintervals:
756 if locator.is_free((l + r) / 2):
757 verts = trans.seq_xy_tups(((l, bottom), (l, top),
758 (r, top), (r, bottom)))
760 renderer.draw_polygon(gc, free_face, verts)
763 gc.set_linewidth(prop("grid.linewidth"))
765 xs, ys = _zigzag_lines(locs, top, bottom)
766 renderer.draw_lines(gc, xs, ys, trans)
767 draw_line(renderer, gc, left, bottom, right, bottom, trans)
768 #@-node:draw_grid
769 #@+node:draw_text
770 def draw_text(self, renderer, gc, x, y, text, fp, align, dpi):
772 special draw_text for taxis using the locale encoding which is used by
773 the strftime functions
776 if not text: return
777 text = text.decode(self.encoding)
779 w, h = renderer.get_text_width_height(text, fp, False)
780 if align[0] == 'c':
781 y -= h / 2
782 elif align[0] == 't':
783 y -= h
785 if align[1] == 'c':
786 x -= w / 2
787 elif align[1] == 'r':
788 x -= w
790 if renderer.flipy():
791 canvasw, canvash = renderer.get_canvas_width_height()
792 y = canvash-y
794 renderer.draw_text(gc, x, y, text, fp, 0, False)
795 #@-node:draw_text
796 #@-others
797 #@-node:class TimeAxis
798 #@-others
799 #@-node:@file charting/taxis.py
800 #@-leo