Added TODO and DEVELOPMENT.
[faces-project.git] / faces / charting / faxes.py
blob6a0a3fa78681f2da466529ca9afdd1722a3e16cb
1 #@+leo-ver=4
2 #@+node:@file charting/faxes.py
3 #@@language python
4 #@<< Copyright >>
5 #@+node:<< Copyright >>
6 ############################################################################
7 # Copyright (C) 2005, 2006, 2007, 2008 by Reithinger GmbH
8 # mreithinger@web.de
10 # This file is part of faces.
12 # faces is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 2 of the License, or
15 # (at your option) any later version.
17 # faces is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the
24 # Free Software Foundation, Inc.,
25 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 ############################################################################
28 #@-node:<< Copyright >>
29 #@nl
30 """
31 A special axes for faces charts
32 """
33 #@<< Imports >>
34 #@+node:<< Imports >>
35 import matplotlib.axes as axes
36 import matplotlib.artist as artist
37 import matplotlib.transforms as mtrans
38 import matplotlib.ticker as ticker
39 import matplotlib._image as mimage
40 import matplotlib.font_manager as font
41 import widgets
42 import sys
43 import tools
44 import patches
45 import renderer as prend
46 import math
49 #@-node:<< Imports >>
50 #@nl
51 #@+others
52 #@+node:_cint
53 def _cint(val): return int(math.ceil(val))
54 #@-node:_cint
55 #@+node:cut_canvas
56 def cut_canvas(axes, reset=False):
57 try:
58 bbox = axes.content_bbox
59 except AttributeError:
60 bbox = axes.bbox
62 try:
63 if not reset and axes._last_viewbounds != bbox.get_bounds():
64 #when the size has changed, don't scale
65 old_bbox = mtrans.lbwh_to_bbox(*axes._last_viewbounds)
66 trans = mtrans.get_bbox_transform(axes.viewLim, old_bbox)
67 data_box = mtrans.inverse_transform_bbox(trans, bbox)
68 xmin = axes.viewLim.xmin()
69 ymax = axes.viewLim.ymax()
71 if not axes._sharey:
72 axes.set_ylim(ymax - data_box.height(), ymax, emit=True)
74 if not axes._sharex:
75 axes.set_xlim(xmin, xmin + data_box.width(), emit=True)
76 except AttributeError:
77 pass
79 axes._last_viewbounds = bbox.get_bounds()
80 #@-node:cut_canvas
81 #@+node:class _WidgetCollection
85 class _WidgetCollection(artist.Artist):
86 #@ << class _WidgetCollection declarations >>
87 #@+node:<< class _WidgetCollection declarations >>
88 # a dummy to avoid a complete rewrite of axes draw
89 zorder = -1
91 #@-node:<< class _WidgetCollection declarations >>
92 #@nl
93 #@ @+others
94 #@+node:__init__
95 def __init__(self, draw_forward):
96 artist.Artist.__init__(self)
97 self.draw_forward = draw_forward
98 #@-node:__init__
99 #@+node:draw
100 def draw(self, renderer):
101 if self.get_visible():
102 self.draw_forward(renderer)
103 #@-node:draw
104 #@-others
105 #@-node:class _WidgetCollection
106 #@+node:_get_margin
107 #time_it(self.draw_forward, renderer)
111 def _get_margin(name, kwargs):
112 margin = kwargs.get(name + "_margin")
113 if margin is not None:
114 del kwargs[name + "_margin"]
115 else:
116 margin = mtrans.Value(0)
118 return margin
119 #@-node:_get_margin
120 #@+node:class MarginAxes
121 class MarginAxes(axes.Axes):
123 An axes with a title bar inside the axes
125 #@ @+others
126 #@+node:__init__
127 def __init__(self, *args, **kwargs):
128 self.top_margin = _get_margin("top", kwargs)
129 self.bottom_margin = _get_margin("bottom", kwargs)
130 self.left_margin = _get_margin("left", kwargs)
131 self.right_margin = _get_margin("right", kwargs)
132 axes.Axes.__init__(self, *args, **kwargs)
133 #@-node:__init__
134 #@+node:build_margin_transform
135 def build_margin_transform(self, left=True, bottom=True,
136 right=True, top=True):
137 Bbox = mtrans.Bbox
138 Point = mtrans.Point
139 get_bbox_transform = mtrans.get_bbox_transform
141 if left:
142 left_margin = self.left_margin * self.fig_point_to_pixel
143 left = self.bbox.ll().x() + left_margin
144 else:
145 left = self.bbox.ll().x()
147 if right:
148 right_margin = self.right_margin * self.fig_point_to_pixel
149 right = self.bbox.ur().x() - right_margin
150 else:
151 right = self.bbox.ur().x()
153 if top:
154 top_margin = self.top_margin * self.fig_point_to_pixel
155 top = self.bbox.ur().y() - top_margin
156 else:
157 top = self.bbox.ur().y()
159 if bottom:
160 bottom_margin = self.bottom_margin * self.fig_point_to_pixel
161 bottom = self.bbox.ll().y() + bottom_margin
162 else:
163 bottom = self.bbox.ll().y()
165 bbox = Bbox(Point(left, bottom), Point(right, top))
167 transform = get_bbox_transform(self.viewLim, bbox)
168 transform.set_funcx(self.transData.get_funcx())
169 transform.set_funcy(self.transData.get_funcy())
170 return transform
171 #@-node:build_margin_transform
172 #@+node:_set_lim_and_transforms
173 def _set_lim_and_transforms(self):
174 axes.Axes._set_lim_and_transforms(self)
175 self.fig_point_to_pixel = self.get_figure().dpi / mtrans.Value(72)
177 self.org_transData = self.transData
178 self.org_transAxes = self.transAxes
180 self.transData = self.build_margin_transform()
181 self.content_bbox = self.transData.get_bbox2()
182 #self.content_bbox is self.bbox reduced by the margins
184 self.transAxes = mtrans.get_bbox_transform(mtrans.unit_bbox(),
185 self.content_bbox)
186 #@-node:_set_lim_and_transforms
187 #@+node:in_axes
188 def in_axes(self, xwin, ywin):
189 return self.content_bbox.contains(xwin, ywin)
190 #@-node:in_axes
191 #@+node:cla
192 def cla(self):
193 axes.Axes.cla(self)
194 self.axesPatch.set_transform(self.org_transAxes)
195 #@-node:cla
196 #@+node:draw
197 def draw(self, renderer, inframe=False):
198 axes.Axes.draw(self, renderer, inframe)
199 if self.axison and self._frameon:
200 fill = self.axesPatch.get_fill()
201 self.axesPatch.set_fill(False)
202 self.axesPatch.draw(renderer)
203 self.axesPatch.set_fill(fill)
204 #@-node:draw
205 #@-others
206 #@-node:class MarginAxes
207 #@+node:class WidgetAxes
208 class WidgetAxes(MarginAxes):
210 An axes which is optimized to display widgets.
211 If widgets are not inside the current view they will not
212 be drawn.
214 #@ @+others
215 #@+node:__init__
216 def __init__(self, *args, **kwargs):
217 self._first_draw = True
218 self._fobj_map = {}
219 self.widgets = []
220 self._visible_widgets = []
221 self.widget_artist = _WidgetCollection(self._draw_widgets)
222 MarginAxes.__init__(self, *args, **kwargs)
223 #@-node:__init__
224 #@+node:cla
225 def cla(self):
226 MarginAxes.cla(self)
228 self.dataLim.intervalx().set_bounds(sys.maxint, -sys.maxint)
229 self.dataLim.intervaly().set_bounds(sys.maxint, -sys.maxint)
230 self._fobj_map.clear()
231 self.widgets = []
232 self.marker = patches.Rectangle((0,0), 0, 0)
233 self.marker.widget = None
234 self.marker.set_visible(False)
235 self.add_artist(self.marker)
236 self.marker.set_clip_box(self.content_bbox)
237 self.xaxis.set_major_locator(ticker.NullLocator())
238 self.xaxis.set_minor_locator(ticker.NullLocator())
239 self.yaxis.set_major_locator(ticker.NullLocator())
240 self.yaxis.set_minor_locator(ticker.NullLocator())
241 self.reset_limits()
242 self.add_collection(self.widget_artist)
243 #@-node:cla
244 #@+node:check_limits
245 def check_limits(self, cut=True):
247 Changes the viewLimits to reasonable values
249 if cut: cut_canvas(self)
250 #@-node:check_limits
251 #@+node:reset_limits
252 def reset_limits(self, cut=True):
254 Sets the data width and data size to the default values.
255 e.g. reset the y axis to display the fonts in the original size
257 self.check_limits(cut)
258 vmin, vmax = self.get_ylim()
259 height = self.content_bbox.height() / self.fig_point_to_pixel.get()
260 self.set_ylim(vmax - height, vmax, emit=True)
261 #@-node:reset_limits
262 #@+node:_get_renderer
263 def _get_renderer(self):
264 if not self._cachedRenderer:
265 Renderer = prend.PatchedRendererAgg
266 self._cachedRenderer = Renderer(10, 10, self.get_figure().dpi)
268 return self._cachedRenderer
269 #@-node:_get_renderer
270 #@+node:add_widget
271 def add_widget(self, widget):
272 if widget.fobj:
273 idendity = widget.fobj._idendity_()
274 self._fobj_map.setdefault(idendity, []).append(widget)
276 self.widgets.append(widget)
277 widget.axes = self
278 widget.set_figure(self.figure)
279 widget.set_clip_box(self.content_bbox)
280 widget.set_transform(self.transData)
282 tools.HSEP.set(5)
283 horz, vert = widget.prepare_draw(self._get_renderer(),
284 self.point_to_pixel,
285 self.fig_point_to_pixel)
286 #update data lim
287 if horz:
288 set_bounds = self.dataLim.intervalx().set_bounds
289 set_bounds(min(widget.bbox.xmin(), self.dataLim.xmin()),
290 max(widget.bbox.xmax(), self.dataLim.xmax()))
293 if vert:
294 set_bounds = self.dataLim.intervaly().set_bounds
295 extra = tools.VSEP.get() * 4
296 set_bounds(min(widget.bbox.ymin() - extra, self.dataLim.ymin()), 0)
298 #@-node:add_widget
299 #@+node:mark_widget
300 def mark_widget(self, widget=None):
301 ow = self.marker.widget
302 self.marker.widget = widget
303 if not widget: self.marker.set_visible(False)
304 return ow != widget
305 #@-node:mark_widget
306 #@+node:find_widget
307 def find_widget(self, fobj):
308 if fobj is str:
309 return self._fobj_map.get(fobj)
311 idendity = fobj._idendity_()
312 widgets = self._fobj_map.get(idendity, ())
314 #first try to find visible widgets
315 identicals = filter(lambda w: w.fobj is fobj, widgets)
316 for w in identicals:
317 if w in self._visible_widgets: return w
319 if identicals: return identicals[0]
321 for w in widgets:
322 if w in self._visible_widgets: return w
324 return widgets and widgets[0] or None
325 #@-node:find_widget
326 #@+node:widget_at
327 def widget_at(self, x, y):
328 self._calc_hsep()
329 found = filter(lambda w: w.contains(x, y), self._visible_widgets)
330 if found: return found[-1]
331 return None
332 #@-node:widget_at
333 #@+node:set_focused_on
334 def set_focused_on(self):
335 self.marker.update(self.focused_props)
336 #@-node:set_focused_on
337 #@+node:set_focused_off
338 def set_focused_off(self):
339 self.marker.update(self.marker_props)
340 #@-node:set_focused_off
341 #@+node:set_marker
342 def set_marker(self, focused_props, normal_props):
343 self.focused_props = focused_props
344 self.marker_props = normal_props
345 self.marker.update(normal_props)
346 #@-node:set_marker
347 #@+node:widget_x_visible
348 def widget_x_visible(self, widget):
349 self._calc_hsep()
350 bbox = widget.get_bounds(self._get_renderer())
351 xmin, xmax = self.get_xlim()
352 width = xmax - xmin
353 wxmin, wxmax = bbox.intervalx().get_bounds()
354 wwidth = wxmax - wxmin
356 vwidth = min(wwidth, width)
358 if wxmax <= xmin + vwidth:
359 xmin = wxmax - vwidth
360 xmax = xmin + width
362 if wxmin >= xmax - vwidth:
363 xmax = wxmin + vwidth
364 xmin = xmax - width
366 self.set_xlim(xmin, xmax)
367 #@-node:widget_x_visible
368 #@+node:widget_y_visible
369 def widget_y_visible(self, widget):
370 ymin, ymax = self.get_ylim()
371 height = ymax - ymin
372 wymin, wymax = widget.bbox.intervaly().get_bounds()
373 if wymax <= ymin + height / 2:
374 ymin = wymax - height / 2
375 ymax = ymin + height
377 if wymin >= ymax - height / 2:
378 ymax = wymin + height / 2
379 ymin = ymax - height
381 self.set_ylim(ymin, ymax)
382 #@-node:widget_y_visible
383 #@+node:zoomx
384 def zoomx(self, numsteps):
385 MarginAxes.zoomx(self, numsteps)
386 if self.marker.get_visible():
387 self.widget_x_visible(self.marker.widget)
388 self.widget_y_visible(self.marker.widget)
389 #@-node:zoomx
390 #@+node:zoomy
391 def zoomy(self, numsteps):
392 MarginAxes.zoomy(self, numsteps)
394 if self.marker.get_visible():
395 self.widget_x_visible(self.marker.widget)
396 self.widget_y_visible(self.marker.widget)
398 #@-node:zoomy
399 #@+node:_calc_hsep
400 def _calc_hsep(self):
401 trans = self.transData
402 vsep = tools.VSEP.get() * self.point_to_pixel.get()
403 origin = trans.inverse_xy_tup((0, 0))
404 seps = trans.inverse_xy_tup((vsep, vsep))
405 tools.HSEP.set(seps[0] - origin[0])
406 #@-node:_calc_hsep
407 #@+node:_draw_widgets
408 def _draw_widgets(self, renderer):
409 trans = self.transData
410 data_box = mtrans.inverse_transform_bbox(trans, self.content_bbox)
411 self._calc_hsep()
413 if self._speed_cache:
414 l, b, w, h = self._speed_bbox.get_bounds()
415 l, b = self.transData.xy_tup((l, b))
416 renderer.draw_image(l, b, self._speed_cache, self.content_bbox)
418 for w in self.widgets:
419 if isinstance(w, (widgets.Row, widgets.Column)):
420 w.draw(renderer, data_box)
422 self._visible_widgets = self.widgets
423 else:
424 self._visible_widgets = [ w for w in self.widgets
425 if w.draw(renderer, data_box) ]
427 #print "widgets drawn", len(self._visible_widgets)
428 if self.marker.widget:
429 if self.marker.widget.overlaps(data_box):
430 bbox = self.marker.widget.bbox
431 self.marker.set_bounds(*bbox.get_bounds())
432 self.marker.set_visible(True)
433 else:
434 self.marker.set_visible(False)
435 #@-node:_draw_widgets
436 #@+node:clear_speed_cache
437 def clear_speed_cache(self):
438 self._speed_cache = None
439 #@-node:clear_speed_cache
440 #@+node:speed_up
441 _speed_cache = None
442 def speed_up(self, max_size):
443 self._speed_cache = None
445 if not self.widgets: return
447 self._calc_hsep()
448 renderer = self._get_renderer()
449 all_data = self.dataLim.deepcopy()
451 xmin = ymin = sys.maxint
452 xmax = ymax = -sys.maxint
454 for w in self.widgets:
455 bounds = w.get_bounds(renderer)
456 xmin = min(xmin, bounds.xmin())
457 xmax = max(xmax, bounds.xmax())
458 ymin = min(ymin, bounds.ymin())
459 ymax = max(ymax, bounds.ymax())
461 all_data.intervalx().set_bounds(xmin, xmax)
462 all_data.intervaly().set_bounds(ymin, ymax)
464 all_view = mtrans.transform_bbox(self.transData, all_data)
466 # increase view because of rounding mistakes
467 xmin, xmax = all_view.intervalx().get_bounds()
468 ymin, ymax = all_view.intervaly().get_bounds()
469 all_view.intervalx().set_bounds(xmin - 1, xmax + 1)
470 all_view.intervaly().set_bounds(ymin - 1, ymax + 1)
472 if all_view.width() * all_view.height() * 4 > max_size:
473 return
475 #adjust all_data to increased all_view
476 all_data = mtrans.inverse_transform_bbox(self.transData, all_view)
478 Renderer = prend.SpeedupRenderer
479 cache = Renderer(_cint(all_view.width()), _cint(all_view.height()),
480 self.get_figure().dpi)
483 render_bbox = mtrans.lbwh_to_bbox(0, 0, all_view.width(), all_view.height())
484 all_trans = mtrans.get_bbox_transform(all_data, render_bbox)
486 for w in self.widgets:
487 if isinstance(w, (widgets.Row, widgets.Column)): continue
488 w.set_transform(all_trans)
489 w.set_clip_box(render_bbox)
490 w.draw(cache, all_data)
491 w.set_transform(self.transData)
492 w.set_clip_box(self.content_bbox)
494 self._speed_cache = mimage.frombuffer(cache.buffer_rgba(0, 0),
495 cache.width, cache.height, 1)
496 if self._speed_cache:
497 self._speed_cache.flipud_out()
498 self._speed_bbox = all_data
499 #@-node:speed_up
500 #@+node:draw
501 def draw(self, renderer, inframe=False):
502 if self._first_draw:
503 self._first_draw = False
504 widgets = map(lambda w: (w[1].zorder, w[0], w[1]),
505 enumerate(self.widgets))
506 widgets.sort()
507 self.widgets = map(lambda ziw: ziw[2], widgets)
509 MarginAxes.draw(self, renderer, inframe)
510 #@-node:draw
511 #@+node:_set_lim_and_transforms
512 def _set_lim_and_transforms(self):
513 Bbox = mtrans.Bbox
514 Point = mtrans.Point
516 MarginAxes._set_lim_and_transforms(self)
517 dtop = self.viewLim.ur().y()
518 dbottom = self.viewLim.ll().y()
520 vtop = self.content_bbox.ur().y()
521 vbottom = self.content_bbox.ll().y()
522 self.point_to_pixel = (vtop - vbottom) / (dtop - dbottom)
523 cut_canvas(self, True)
524 #@-node:_set_lim_and_transforms
525 #@-others
526 #@-node:class WidgetAxes
527 #@+node:class PointAxes
528 class PointAxes(WidgetAxes):
530 An axes which scales x, y proportional to points
532 #@ @+others
533 #@+node:__init__
534 def __init__(self, *args, **kwargs):
535 WidgetAxes.__init__(self, *args, **kwargs)
536 self.zoomx = self.zoomy
537 #@-node:__init__
538 #@+node:cla
539 def cla(self):
540 WidgetAxes.cla(self)
541 self.dataLim.intervalx().set_bounds(0, 0)
542 self.dataLim.intervaly().set_bounds(0, 0)
543 #@-node:cla
544 #@+node:check_limits
545 __last_size = (0, 0)
546 def check_limits(self, cut=True):
547 WidgetAxes.check_limits(self, cut)
548 size = (self.viewLim.width(), self.viewLim.height())
549 if size != self.__last_size:
550 prop = self.content_bbox.width() / self.content_bbox.height()
551 pwidth = size[1] * prop
552 if pwidth != size[0]:
553 #we have to correct x
554 size = (pwidth, size[1])
555 xmin = self.viewLim.xmin()
556 self.set_xlim(xmin, xmin + pwidth)
558 self.__last_size = size
559 #@-node:check_limits
560 #@+node:autoscale_view
561 def autoscale_view(self, cut=True):
562 if not self._autoscaleon: return
563 self.check_limits(cut)
565 width = self.dataLim.width()
566 height = self.dataLim.height()
568 prop = self.content_bbox.width() / self.content_bbox.height()
569 pwidth = height * prop
571 xmin = self.dataLim.xmin()
572 ymax = self.dataLim.ymax()
574 if pwidth > width:
575 self.set_xlim(xmin, xmin + pwidth)
576 self.set_ylim(ymax - height, ymax)
577 else:
578 self.set_xlim(xmin, xmin + width)
579 self.set_ylim(ymax - width / prop, ymax)
581 self.__last_size = (self.viewLim.width(), self.viewLim.height())
582 #@-node:autoscale_view
583 #@-others
584 #@-node:class PointAxes
585 #@+node:class TimeAxes
586 class TimeAxes(object):
587 #@ << class TimeAxes declarations >>
588 #@+node:<< class TimeAxes declarations >>
589 time_axis = None
590 time_scale = None
593 #@-node:<< class TimeAxes declarations >>
594 #@nl
595 #@ @+others
596 #@+node:set_time_axis
597 def set_time_axis(self, time_axis):
598 try:
599 self.collections.remove(self.time_axis)
600 except ValueError:
601 pass
603 self.time_axis = time_axis
604 self.add_collection(self.time_axis)
605 axis_transform = self.build_margin_transform(top=False)
606 self.time_axis.set_transform(axis_transform)
607 self.time_axis.set_clip_box(axis_transform.get_bbox2())
608 self.update_time_axis()
609 #@-node:set_time_axis
610 #@+node:xaxis_timescale
611 def xaxis_timescale(self, time_scale):
612 self.time_scale = time_scale
613 self.xaxis.set_major_locator(ticker.NullLocator())
614 self.xaxis.set_minor_locator(ticker.NullLocator())
615 #@-node:xaxis_timescale
616 #@+node:set_time_lim
617 def set_time_lim(self, xmin=None, xmax=None, emit=False):
618 xmin = xmin and self.time_scale.to_num(xmin)
619 xmax = xmax and self.time_scale.to_num(xmax)
620 self.set_xlim(xmin=xmin, xmax=xmax, emit=emit)
621 #@-node:set_time_lim
622 #@+node:get_time_lim
623 def get_time_lim(self):
624 xmin, xmax = self.get_xlim()
625 xmin = self.time_scale.to_num(int(xmin))
626 xmax = self.time_scale.to_num(int(xmax))
627 return xmin.to_datetime(), xmax.to_datetime()
628 #@-node:get_time_lim
629 #@+node:format_coord
630 def format_coord(self, x, y):
631 'return a format string formatting the x, y coord'
633 if self.time_scale:
634 xs = self.time_scale.to_num(int(x)).strftime()
635 else:
636 xs = self.format_xdata(x)
638 ys = self.format_ydata(y)
639 return 'x=%s, y=%s'%(xs,ys)
640 #@-node:format_coord
641 #@+node:update_time_axis
642 def update_time_axis(self):
643 ah = self.time_axis \
644 and self.time_axis.get_visible() \
645 and self.time_axis.calc_height() or 0
647 self.top_margin.set(ah)
648 #@-node:update_time_axis
649 #@+node:unshare
650 def unshare(self):
651 self._sharex = None
652 self._sharey = None
653 #@-node:unshare
654 #@-others
655 #@-node:class TimeAxes
656 #@+node:class TimePlotAxes
657 class TimePlotAxes(TimeAxes, MarginAxes):
658 #@ << class TimePlotAxes declarations >>
659 #@+node:<< class TimePlotAxes declarations >>
660 first_draw = True
662 #@-node:<< class TimePlotAxes declarations >>
663 #@nl
664 #@ @+others
665 #@+node:__init__
666 def __init__(self, *args, **kwargs):
667 MarginAxes.__init__(self, *args, **kwargs)
668 #@-node:__init__
669 #@+node:draw
670 def draw(self, renderer, inframe=False):
671 if self.first_draw:
672 self.first_draw = False
673 for a in self.lines: a.set_clip_box(self.content_bbox)
674 for a in self.texts: a.set_clip_box(self.content_bbox)
675 for a in self.patches: a.set_clip_box(self.content_bbox)
676 for a in self.artists: a.set_clip_box(self.content_bbox)
678 MarginAxes.draw(self, renderer, inframe)
679 cut_canvas(self, True)
680 #@-node:draw
681 #@-others
682 #@-node:class TimePlotAxes
683 #@+node:class TimeWidgetAxes
684 class TimeWidgetAxes(TimeAxes, WidgetAxes):
686 An axes wich displays widgets horizontal in time. (e.g. GanttCharts)
688 #@ << class TimeWidgetAxes declarations >>
689 #@+node:<< class TimeWidgetAxes declarations >>
690 auto_scale_y = False
692 #@-node:<< class TimeWidgetAxes declarations >>
693 #@nl
694 #@ @+others
695 #@+node:__init__
696 def __init__(self, *args, **kwargs):
697 WidgetAxes.__init__(self, *args, **kwargs)
698 #@-node:__init__
699 #@+node:set_auto_scale_y
700 def set_auto_scale_y(self, do_scale=True):
701 self.auto_scale_y = do_scale
702 #@-node:set_auto_scale_y
703 #@+node:check_limits
704 def check_limits(self, cut=True):
705 WidgetAxes.check_limits(self, cut)
707 vmin, vmax = self.viewLim.intervaly().get_bounds()
708 if vmax > 0:
709 self.viewLim.intervaly().set_bounds((vmin - vmax), 0)
710 #@-node:check_limits
711 #@+node:autoscale_view
712 def autoscale_view(self, cut=True):
713 if not self._autoscaleon: return
714 self.check_limits(cut)
716 all_data = self.dataLim.deepcopy()
717 reduced = self.content_bbox.deepcopy()
718 renderer = self._get_renderer()
720 if self.auto_scale_y:
721 ymin, ymax = self.dataLim.intervaly().get_bounds()
722 self.set_ylim(ymin, ymax)
723 else:
724 self.reset_limits()
726 xmin, xmax = self.dataLim.intervalx().get_bounds()
727 self.set_xlim(xmin, xmax, emit=False)
728 self._calc_hsep()
730 #Notice: it is not correct to just get the bounds
731 #in the actual data coords an set the view limits
732 #This is because text on the left or right bound
733 #has always the same pixel width. This means the text witdh
734 #in data coord changes when the data coord scale changes.
736 #find out the bounds in actual data coords
737 for w in self.widgets:
738 bounds = w.get_bounds(renderer)
739 xmin = min(xmin, bounds.xmin())
740 xmax = max(xmax, bounds.xmax())
742 all_data.intervalx().set_bounds(xmin, xmax)
744 #the complete bound in pixel coords
745 all_view = mtrans.transform_bbox(self.transData, all_data)
746 add_space = 0.08 * self.get_figure().get_dpi() #2mm margin left an right
748 left_offset = self.content_bbox.xmin() - all_view.xmin() + add_space
749 right_offset = all_view.xmax() - self.content_bbox.xmax() + add_space
750 width = self.content_bbox.width()
752 if left_offset > width / 4: left_offset = width / 4
753 if right_offset > width / 4: right_offset = width / 4
755 # scale down the pixel bounds
756 xmin1 = self.content_bbox.xmin() + left_offset
757 xmax1 = self.content_bbox.xmax() - right_offset
758 reduced.intervalx().set_bounds(xmin1, xmax1)
760 # create a new transformation from scaled down pixel coords to limits
761 trans = mtrans.get_bbox_transform(reduced, self.viewLim)
762 data_box = mtrans.transform_bbox(trans, self.content_bbox)
763 self.set_xlim(data_box.xmin(), data_box.xmax(), emit=True)
764 cut_canvas(self, True)
765 #@-node:autoscale_view
766 #@+node:widget_at
767 def widget_at(self, x, y):
768 return WidgetAxes.widget_at(self, x, y)
769 #@-node:widget_at
770 #@-others
771 #@-node:class TimeWidgetAxes
772 #@-others
773 #@-node:@file charting/faxes.py
774 #@-leo