~ubuntu-branches/ubuntu/utopic/python-chaco/utopic

« back to all changes in this revision

Viewing changes to enthought/chaco/scales_axis.py

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2011-04-06 19:03:54 UTC
  • mfrom: (7.2.2 sid)
  • Revision ID: james.westby@ubuntu.com-20110406190354-rwd55l2ezjecfo41
Tags: 3.4.0-2
d/rules: fix pyshared directory path (Closes: #621116)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
""" This module is a cut-n-paste job of axis.py.  This is meant to be a
2
 
a temporary solution until we merge the Scales functionality into the
3
 
mainline of Chaco development.
4
 
"""
5
 
# Major library import
6
 
from numpy import array, around, absolute, cos, dot, float64, inf, pi, \
7
 
                  sqrt, sin, transpose
8
 
 
9
 
# Enthought Library imports
10
 
from enthought.enable.api import ColorTrait, LineStyle
11
 
from enthought.kiva.traits.kiva_font_trait import KivaFont
12
 
from enthought.traits.api import Any, Bool, Float, Int, Str, Trait, Unicode, \
13
 
     Event, List, Array, Instance, Enum, Callable
14
 
 
15
 
# Local relative imports
16
 
from ticks import AbstractTickGenerator, DefaultTickGenerator
17
 
from abstract_mapper import AbstractMapper
18
 
from abstract_overlay import AbstractOverlay
19
 
from label import Label
20
 
from log_mapper import LogMapper
21
 
 
22
 
def DEFAULT_TICK_FORMATTER(val):
23
 
    return ("%f"%val).rstrip("0").rstrip(".")
24
 
 
25
 
class PlotAxis(AbstractOverlay):
26
 
    """ This class is not currently used. See axis.PlotAxis.
27
 
    
28
 
    The PlotAxis is a visual component that can be rendered on its own as
29
 
    a standalone component or attached as an overlay to another component.
30
 
    (To attach it as an overlay, set its .component attribute.)
31
 
 
32
 
    When it is attached as an overlay, it draws into the padding around
33
 
    the component.
34
 
    """
35
 
 
36
 
    # The mapper that drives this axis
37
 
    mapper = Instance(AbstractMapper)
38
 
 
39
 
    # The text of the title
40
 
    title = Trait('', Str, Unicode) #May want to add PlotLabel option
41
 
 
42
 
    # The font in which to render the title
43
 
    title_font = KivaFont('modern 12')
44
 
 
45
 
    # The color in which to render the title
46
 
    title_color = ColorTrait("black")
47
 
 
48
 
    # Not used right now.
49
 
    # TODO: Implement this
50
 
    markers = Any
51
 
 
52
 
    # The thickness (in pixels) of each tick
53
 
    tick_weight = Float(1.0)
54
 
 
55
 
    # The color of the ticks
56
 
    tick_color = ColorTrait("black")
57
 
 
58
 
    # The font in which to render the tick labels
59
 
    tick_label_font = KivaFont('modern 10')
60
 
 
61
 
    # The color of the tick labels
62
 
    tick_label_color = ColorTrait("black")
63
 
 
64
 
    # A callable that is passed the numerical value of each tick label and
65
 
    # which should return a string.
66
 
    tick_label_formatter = Callable(DEFAULT_TICK_FORMATTER)
67
 
 
68
 
    # The number of pixels by which the ticks go "into" the plot area
69
 
    tick_in = Int(5)
70
 
 
71
 
    # The number of pixels by which the ticks extend into the label area
72
 
    tick_out = Int(5)
73
 
 
74
 
    # Are ticks visible at all?
75
 
    tick_visible = Bool(True)
76
 
 
77
 
    # What is the dataspace interval between ticks?
78
 
    tick_interval = Trait('auto', 'auto', Float)
79
 
 
80
 
    # A callable that implements the AbstractTickGenerator Interface
81
 
    tick_generator = Instance(AbstractTickGenerator)
82
 
 
83
 
    # The location of the axis relative to the plot.  This determines where
84
 
    # the axis title is located relative to the axis line.
85
 
    orientation = Enum("top", "bottom", "left", "right")
86
 
 
87
 
    # Is the axis line visible?
88
 
    axis_line_visible = Bool(True)
89
 
 
90
 
    # The color of the axis line
91
 
    axis_line_color = ColorTrait("black")
92
 
 
93
 
    # The line thickness (in pixels) of the axis line
94
 
    axis_line_weight = Float(1.0)
95
 
 
96
 
    # The dash style of the axis line
97
 
    axis_line_style = LineStyle('solid')
98
 
 
99
 
    # A special version of the axis line that is more useful for geophysical
100
 
    # plots.
101
 
    # TODO: MOVE THIS OUT OF HERE!
102
 
    small_haxis_style = Bool(False)
103
 
 
104
 
    # Should we do extra work to ensure that the end labels of the axis fall
105
 
    # within the axis's bounding area?
106
 
    ensure_labels_bounded = Bool(False)
107
 
 
108
 
    # Should we prevent the ticks from being rendered outside our bounds?
109
 
    # This is off by default because the standard axis *will* render ticks
110
 
    # that encroach on the plot area.
111
 
    ensure_ticks_bounded = Bool(False)
112
 
 
113
 
    # Fired when our range's bounds change
114
 
    updated = Event
115
 
 
116
 
    #------------------------------------------------------------------------
117
 
    # Override default values of inherited traits
118
 
    #------------------------------------------------------------------------
119
 
 
120
 
    # Axes will generally want the color of the container to show through
121
 
    bgcolor = ColorTrait("transparent")
122
 
 
123
 
    # Typically, axes are resizable in both dimensions.
124
 
    resizable = "hv"
125
 
 
126
 
    #------------------------------------------------------------------------
127
 
    # Private Traits
128
 
    #------------------------------------------------------------------------
129
 
 
130
 
    # Cached position calculations
131
 
    _tick_list = List  # These are caches of their respective positions
132
 
    _tick_positions = Any #List
133
 
    _tick_label_list = Any
134
 
    _tick_label_positions = Any
135
 
    _tick_label_bounding_boxes = List
136
 
    _major_axis_size = Float
137
 
    _minor_axis_size = Float
138
 
    _major_axis = Array
139
 
    _title_orientation = Array
140
 
    _title_angle = Float
141
 
    _origin_point = Array
142
 
    _inside_vector = Array
143
 
    _axis_vector = Array
144
 
    _axis_pixel_vector = Array
145
 
    _end_axis_point = Array
146
 
 
147
 
 
148
 
    ticklabel_cache = List
149
 
    _cache_valid = Bool(False)
150
 
 
151
 
 
152
 
    #------------------------------------------------------------------------
153
 
    # Public methods
154
 
    #------------------------------------------------------------------------
155
 
 
156
 
    def __init__(self, component=None, **kwargs):
157
 
        # TODO: change this back to a factory in the instance trait some day
158
 
        self.tick_generator = DefaultTickGenerator()
159
 
        # Override init so that our component gets set last.  We want the
160
 
        # _component_changed() event handler to get run last.
161
 
        super(PlotAxis, self).__init__(**kwargs)
162
 
        if component is not None:
163
 
            self.component = component
164
 
 
165
 
    def invalidate(self):
166
 
        """
167
 
        Invalidates the pre-computed layout and scaling data.
168
 
        """
169
 
        self._reset_cache()
170
 
        self.invalidate_draw()
171
 
        return
172
 
 
173
 
    def traits_view(self):
174
 
        """ Returns a View instance for use with Traits UI.  This method is
175
 
        called automatically be the Traits framework when .edit_traits() is
176
 
        invoked.
177
 
        """
178
 
        from axis_view import AxisView
179
 
        return AxisView
180
 
 
181
 
    #------------------------------------------------------------------------
182
 
    # PlotComponent and AbstractOverlay interface
183
 
    #------------------------------------------------------------------------
184
 
 
185
 
    def do_layout(self, *args, **kw):
186
 
        if self.use_draw_order and self.component is not None:
187
 
            self._layout_as_overlay(*args, **kw)
188
 
        else:
189
 
            super(PlotAxis, self).do_layout(*args, **kw)
190
 
        return
191
 
 
192
 
    def overlay(self, component, gc, view_bounds=None, mode='normal'):
193
 
        if not self.visible:
194
 
            return
195
 
        self._draw_component(gc, view_bounds, mode, component)
196
 
        return
197
 
 
198
 
    def _draw_overlay(self, gc, view_bounds=None, mode='normal'):
199
 
        self._draw_component(gc, view_bounds, mode)
200
 
        return
201
 
 
202
 
    def _draw_component(self, gc, view_bounds=None, mode='normal', component=None):
203
 
        if not self.visible:
204
 
            return
205
 
 
206
 
        try:
207
 
            gc.save_state()
208
 
 
209
 
            # slight optimization: if we set the font correctly on the
210
 
            # base gc before handing it in to our title and tick labels,
211
 
            # their set_font() won't have to do any work.
212
 
            gc.set_font(self.tick_label_font)
213
 
 
214
 
            if not self._cache_valid:
215
 
                if component is not None:
216
 
                    self._calculate_geometry(component)
217
 
                else:
218
 
                    self._old_calculate_geometry()
219
 
                self._compute_tick_positions(gc, component)
220
 
#                self._compute_labels(gc)
221
 
 
222
 
            if self.axis_line_visible:
223
 
                self._draw_axis_line(gc, self._origin_point, self._end_axis_point)
224
 
            if self.title:
225
 
                self._draw_title(gc)
226
 
 
227
 
            self._draw_ticks(gc)
228
 
            self._draw_labels(gc)
229
 
        finally:
230
 
            gc.restore_state()
231
 
 
232
 
        self._cache_valid = True
233
 
        return
234
 
 
235
 
 
236
 
    #------------------------------------------------------------------------
237
 
    # Private draw routines
238
 
    #------------------------------------------------------------------------
239
 
 
240
 
    def _layout_as_overlay(self, size=None, force=False):
241
 
        if self.component is not None:
242
 
            if self.orientation in ("left", "right"):
243
 
                self.y = self.component.y
244
 
                self.height = self.component.height
245
 
                if self.orientation == "left":
246
 
                    self.width = self.component.padding_left
247
 
                    self.x = self.component.outer_x
248
 
                elif self.orientation == "right":
249
 
                    self.width = self.component.padding_right
250
 
                    self.x = self.component.x2 + 1
251
 
            else:
252
 
                self.x = self.component.x
253
 
                self.width = self.component.width
254
 
                if self.orientation == "bottom":
255
 
                    self.height = self.component.padding_bottom
256
 
                    self.y = self.component.outer_y
257
 
                elif self.orientation == "top":
258
 
                    self.height = self.component.padding_top
259
 
                    self.y = self.component.y2 + 1
260
 
        return
261
 
 
262
 
    def _draw_axis_line(self, gc, startpoint, endpoint):
263
 
        gc.save_state()
264
 
        try:
265
 
            gc.set_antialias(0)
266
 
            gc.set_line_width(self.axis_line_weight)
267
 
            gc.set_stroke_color(self.axis_line_color_)
268
 
            gc.set_line_dash(self.axis_line_style_)
269
 
            gc.move_to(*around(startpoint))
270
 
            gc.line_to(*around(endpoint))
271
 
            gc.stroke_path()
272
 
        finally:
273
 
            gc.restore_state()
274
 
        return
275
 
 
276
 
    def _draw_title_old(self, gc, label=None, v_offset=20):
277
 
        #put in rotation code for right side
278
 
 
279
 
        if label is None:
280
 
            title_label = Label(text=self.title,
281
 
                                font=self.title_font,
282
 
                                color=self.title_color,
283
 
                                rotate_angle=self.title_angle)
284
 
        else:
285
 
            title_label = label
286
 
        tl_bounds = array(title_label.get_width_height(gc), float64)
287
 
 
288
 
        if self.title_angle == 0:
289
 
            text_center_to_corner = -tl_bounds/2.0
290
 
        else:
291
 
            v_offset *= 2.3 # Hack until we have some better geometry code
292
 
            corner_vec = transpose(-tl_bounds/2.0)
293
 
            rotmatrix = self._rotmatrix(-self.title_angle*pi/180.0)
294
 
            text_center_to_corner = transpose(dot(rotmatrix, corner_vec))[0]
295
 
 
296
 
        # would be good to count tick height, but more complicated.
297
 
        offset = (self._origin_point+self._end_axis_point)/2
298
 
        center_dist = self._center_dist(-self._inside_vector, tl_bounds[0], tl_bounds[1], rotation=self.title_angle)
299
 
        offset -= self._inside_vector * (center_dist + v_offset)
300
 
        offset += text_center_to_corner
301
 
 
302
 
        if self.title_angle == 90.0:
303
 
            # Horrible hack to adjust for the fact that the generic math above isn't
304
 
            # actually putting the label in the right place...
305
 
            offset[1] = offset[1] - tl_bounds[0]/2.0
306
 
 
307
 
        gc.translate_ctm(*offset)
308
 
        title_label.draw(gc)
309
 
        gc.translate_ctm(*(-offset))
310
 
        return
311
 
 
312
 
    def _draw_title(self, gc, label=None, v_offset=20):
313
 
        """ Draws the title for the axis.
314
 
        """
315
 
        #put in rotation code for right side
316
 
 
317
 
        if label is None:
318
 
            title_label = Label(text=self.title,
319
 
                                font=self.title_font,
320
 
                                color=self.title_color,
321
 
                                rotate_angle=self.title_angle)
322
 
        else:
323
 
            title_label = label
324
 
        tl_bounds = array(title_label.get_width_height(gc), float64)
325
 
 
326
 
        if self.title_angle == 0:
327
 
            text_center_to_corner = -tl_bounds/2.0
328
 
            if not self.ticklabel_cache:
329
 
                v_offset = 25
330
 
            else:
331
 
                v_offset = max([l._bounding_box[1] for l in self.ticklabel_cache]) * 1.3
332
 
            offset = (self._origin_point+self._end_axis_point)/2
333
 
            center_dist = self._center_dist(-self._inside_vector, tl_bounds[0], tl_bounds[1], rotation=self.title_angle)
334
 
            offset -= self._inside_vector * (center_dist + v_offset)
335
 
            offset += text_center_to_corner
336
 
        
337
 
        elif self.title_angle == 90:
338
 
            # Center the text vertically
339
 
            if not self.ticklabel_cache:
340
 
                v_offset = 25
341
 
            else:
342
 
                v_offset = (self._end_axis_point[1] - self._origin_point[1] - tl_bounds[0])/2.0
343
 
            h_offset = self.tick_out + tl_bounds[1] + 8
344
 
            if len(self.ticklabel_cache) > 0:
345
 
                h_offset += max([l._bounding_box[0] for l in self.ticklabel_cache]) 
346
 
            offset = array([self._origin_point[0] - h_offset, self._origin_point[1] + v_offset])
347
 
 
348
 
        else:
349
 
            if not self.ticklabel_cache:
350
 
                v_offset = 25
351
 
            else:
352
 
                v_offset = max([l._bounding_box[0] for l in self.ticklabel_cache])
353
 
            corner_vec = transpose(-tl_bounds/2.0)
354
 
            rotmatrix = self._rotmatrix(-self.title_angle*pi/180.0)
355
 
            text_center_to_corner = transpose(dot(rotmatrix, corner_vec))[0]
356
 
            offset = (self._origin_point+self._end_axis_point)/2
357
 
            center_dist = self._center_dist(-self._inside_vector, tl_bounds[0], tl_bounds[1], rotation=self.title_angle)
358
 
            offset -= self._inside_vector * (center_dist + v_offset)
359
 
            offset += text_center_to_corner
360
 
 
361
 
        gc.translate_ctm(*offset)
362
 
        title_label.draw(gc)
363
 
        gc.translate_ctm(*(-offset))
364
 
 
365
 
        return
366
 
 
367
 
    def _draw_ticks(self, gc):
368
 
        if not self.tick_visible:
369
 
            return
370
 
        gc.set_stroke_color(self.tick_color_)
371
 
        gc.set_line_width(self.tick_weight)
372
 
        gc.set_antialias(False)
373
 
        gc.begin_path()
374
 
        tick_in_vector = self._inside_vector*self.tick_in
375
 
        tick_out_vector = self._inside_vector*self.tick_out
376
 
        for tick_pos in self._tick_positions:
377
 
            gc.move_to(*(tick_pos + tick_in_vector))
378
 
            gc.line_to(*(tick_pos - tick_out_vector))
379
 
        gc.stroke_path()
380
 
        return
381
 
 
382
 
    def _draw_labels(self, gc):
383
 
        for i in range(len(self._tick_label_positions)):
384
 
            #We want a more sophisticated scheme than just 2 decimals all the time
385
 
            ticklabel = self.ticklabel_cache[i]
386
 
            tl_bounds = self._tick_label_bounding_boxes[i]
387
 
 
388
 
            #base_position puts the tick label at a point where the vector
389
 
            #extending from the tick mark inside 8 units
390
 
            #just touches the rectangular bounding box of the tick label.
391
 
            #Note: This is not necessarily optimal for non
392
 
            #horizontal/vertical axes.  More work could be done on this.
393
 
 
394
 
            base_position = (self._center_dist(-self._inside_vector, *tl_bounds)+8) \
395
 
                                * -self._inside_vector \
396
 
                                - tl_bounds/2.0 + self._tick_label_positions[i]
397
 
 
398
 
            if self.ensure_labels_bounded:
399
 
                pushdir = 0
400
 
                if i == 0:
401
 
                    pushdir = 1
402
 
                elif i == len(self._tick_label_positions)-1:
403
 
                    pushdir = -1
404
 
                push_pixel_vector = self._axis_pixel_vector * pushdir
405
 
                tlpos = around((self._center_dist(push_pixel_vector,*tl_bounds)+4) \
406
 
                                          * push_pixel_vector + base_position)
407
 
 
408
 
            else:
409
 
                tlpos = around(base_position)
410
 
 
411
 
            gc.translate_ctm(*tlpos)
412
 
            ticklabel.draw(gc)
413
 
            gc.translate_ctm(*(-tlpos))
414
 
        return
415
 
 
416
 
 
417
 
    #------------------------------------------------------------------------
418
 
    # Private methods for computing positions and layout
419
 
    #------------------------------------------------------------------------
420
 
 
421
 
    def _reset_cache(self):
422
 
        self._tick_positions = []
423
 
        self._tick_label_list = []
424
 
        self._tick_label_positions = []
425
 
        return
426
 
 
427
 
    def _compute_tick_positions(self, gc, overlay_component=None):
428
 
        if (self.mapper is None):
429
 
            self._reset_cache()
430
 
            self._cache_valid = True
431
 
            return
432
 
 
433
 
        datalow = self.mapper.range.low
434
 
        datahigh = self.mapper.range.high
435
 
        screenhigh = self.mapper.high_pos
436
 
        screenlow = self.mapper.low_pos
437
 
        direction = 'normal'
438
 
        if overlay_component is not None and hasattr(overlay_component, "x_direction") and\
439
 
                                             hasattr(overlay_component, "y_direction"):
440
 
            if self.orientation in ("top", "bottom"):
441
 
                direction = overlay_component.x_direction
442
 
            elif self.orientation in ("left", "right"):
443
 
                direction = overlay_component.y_direction
444
 
 
445
 
        else:
446
 
            if screenlow>screenhigh:
447
 
                direction = 'flipped'
448
 
        if direction == "flipped":
449
 
            screenlow, screenhigh = screenhigh, screenlow
450
 
 
451
 
 
452
 
        if (datalow == datahigh) or (screenlow == screenhigh) or \
453
 
           (datalow in [inf, -inf]) or (datahigh in [inf, -inf]):
454
 
            self._reset_cache()
455
 
            self._cache_valid = True
456
 
            return
457
 
 
458
 
        if datalow > datahigh:
459
 
            raise RuntimeError, "DataRange low is greater than high; unable to compute axis ticks."
460
 
 
461
 
        if not self.tick_generator:
462
 
            return
463
 
 
464
 
        self.tick_generator.font = self.tick_label_font
465
 
 
466
 
        if hasattr(self.tick_generator, "get_ticks_and_labels"):
467
 
            tmp = self.tick_generator.get_ticks_and_labels(datalow, datahigh,
468
 
                                   screenlow, screenhigh)
469
 
            if len(tmp) == 0:
470
 
                ticks = []
471
 
                labels = []
472
 
            else:
473
 
                ticks, labels = tmp
474
 
            mapped_tick_positions = (array(self.mapper.map_screen(ticks))-screenlow) / \
475
 
                                                (screenhigh-screenlow)
476
 
            self._tick_positions = around(array([self._axis_vector*tickpos + self._origin_point \
477
 
                                    for tickpos in mapped_tick_positions]))
478
 
            self._tick_label_list = ticks
479
 
            self._tick_label_positions = self._tick_positions
480
 
            self.ticklabel_cache = [Label(text=lab, font=self.tick_label_font, color=self.tick_label_color) \
481
 
                                    for lab in labels]
482
 
            self._tick_label_bounding_boxes = [array(ticklabel.get_bounding_box(gc), float64) for ticklabel in self.ticklabel_cache]
483
 
        else:
484
 
 
485
 
            if isinstance(self.mapper, LogMapper):
486
 
                scale = 'log'
487
 
            else:
488
 
                scale = 'linear'
489
 
 
490
 
            tick_list = array(self.tick_generator.get_ticks(datalow, datahigh,
491
 
                                                            datalow, datahigh,
492
 
                                                            self.tick_interval,
493
 
                                                            use_endpoints=False,
494
 
                                                            scale=scale), float64)
495
 
 
496
 
            mapped_tick_positions = (array(self.mapper.map_screen(tick_list))-screenlow) / \
497
 
                                                (screenhigh-screenlow)
498
 
            self._tick_positions = around(array([self._axis_vector*tickpos + self._origin_point \
499
 
                                    for tickpos in mapped_tick_positions]))
500
 
 
501
 
            if self.small_haxis_style:
502
 
                # If we're a small axis, we want the endpoints to be the labels regardless of
503
 
                # where the ticks are, as the labels represent the bounds, not where the tick
504
 
                # marks are.
505
 
                self._tick_label_list = array([datalow, datahigh])
506
 
                mapped_label_positions = (array(self.mapper.map_screen(self._tick_label_list))-screenlow) / \
507
 
                                         (screenhigh-screenlow)
508
 
                self._tick_label_positions = [self._axis_vector*tickpos + self._origin_point \
509
 
                                              for tickpos in mapped_label_positions]
510
 
            else:
511
 
                self._tick_label_list = tick_list
512
 
                self._tick_label_positions = self._tick_positions
513
 
            self._compute_labels(gc)
514
 
        return
515
 
 
516
 
    def _compute_labels(self, gc):
517
 
        """Computes the positions and generates the label objects.  Waits
518
 
        for the cache to become invalid.
519
 
        """
520
 
        self.ticklabel_cache = []
521
 
        formatter = self.tick_label_formatter
522
 
#        if formatter is not None:
523
 
#            labels = formatter.labels(self._tick_label_positions)
524
 
        for i in range(len(self._tick_label_positions)):
525
 
            val = self._tick_label_list[i]
526
 
            if formatter is not None:
527
 
                tickstring = formatter(val)
528
 
            else:
529
 
                tickstring = str(val)
530
 
            ticklabel = Label(text=tickstring,
531
 
                              font=self.tick_label_font,
532
 
                              color=self.tick_label_color)
533
 
            self.ticklabel_cache.append(ticklabel)
534
 
 
535
 
        self._tick_label_bounding_boxes = [array(ticklabel.get_bounding_box(gc), float64) for ticklabel in self.ticklabel_cache]
536
 
        return
537
 
 
538
 
 
539
 
    def _calculate_geometry(self, overlay_component=None):
540
 
        if overlay_component is not None:
541
 
            if self.orientation == "top":
542
 
                new_origin = [overlay_component.x, overlay_component.y2]
543
 
                inside_vec = [0.0, -1.0]
544
 
            elif self.orientation == "bottom":
545
 
                new_origin = [overlay_component.x, overlay_component.y]
546
 
                inside_vec = [0.0, 1.0]
547
 
            elif self.orientation == "left":
548
 
                new_origin = [overlay_component.x, overlay_component.y]
549
 
                inside_vec = [1.0, 0.0]
550
 
            else:  # self.orientation == "right":
551
 
                new_origin = [overlay_component.x2, overlay_component.y]
552
 
                inside_vec = [-1.0, 0.0]
553
 
            self._origin_point = array(new_origin)
554
 
            self._inside_vector = array(inside_vec)
555
 
        else:
556
 
            overlay_component = self
557
 
            new_origin = array(self.position)
558
 
 
559
 
        origin = getattr(overlay_component, "origin", 'bottom left')
560
 
        if self.orientation in ('top', 'bottom'):
561
 
            self._major_axis_size = overlay_component.bounds[0]
562
 
            self._minor_axis_size = overlay_component.bounds[1]
563
 
            self._major_axis = array([1., 0.])
564
 
            self._title_orientation = array([0.,1.])
565
 
            if "right" in origin: 
566
 
                flip_from_gc = True
567
 
            else: 
568
 
                flip_from_gc = False
569
 
            #this could be calculated...
570
 
            self.title_angle = 0.0
571
 
        elif self.orientation in ('left', 'right'):
572
 
            self._major_axis_size = overlay_component.bounds[1]
573
 
            self._minor_axis_size = overlay_component.bounds[0]
574
 
            self._major_axis = array([0., 1.])
575
 
            self._title_orientation = array([-1., 0])
576
 
            origin = getattr(overlay_component, "origin", 'bottom left')
577
 
            if "top" in origin: 
578
 
                flip_from_gc = True
579
 
            else: 
580
 
                flip_from_gc = False
581
 
            if self.orientation == 'left':
582
 
                self.title_angle = 90.0
583
 
            else:
584
 
                self.title_angle = 270.0                
585
 
 
586
 
        if self.ensure_ticks_bounded:
587
 
            self._origin_point -= self._inside_vector*self.tick_in
588
 
 
589
 
        screenhigh = self.mapper.high_pos
590
 
        screenlow = self.mapper.low_pos
591
 
        # TODO: should this be here, or not?
592
 
        if flip_from_gc:
593
 
            screenlow, screenhigh = screenhigh, screenlow
594
 
 
595
 
        self._end_axis_point = (screenhigh-screenlow)*self._major_axis + self._origin_point
596
 
        self._axis_vector = self._end_axis_point - self._origin_point
597
 
        # This is the vector that represents one unit of data space in terms of screen space.
598
 
        self._axis_pixel_vector = self._axis_vector/sqrt(dot(self._axis_vector,self._axis_vector))
599
 
        return
600
 
 
601
 
 
602
 
    def _old_calculate_geometry(self):
603
 
        if hasattr(self, 'mapper') and self.mapper is not None:
604
 
            screenhigh = self.mapper.high_pos
605
 
            screenlow = self.mapper.low_pos
606
 
        else:
607
 
            # fixme: this should take into account axis orientation
608
 
            screenhigh = self.x2
609
 
            screenlow = self.x
610
 
 
611
 
        if self.orientation in ('top', 'bottom'):
612
 
            self._major_axis_size = self.bounds[0]
613
 
            self._minor_axis_size = self.bounds[1]
614
 
            self._major_axis = array([1., 0.])
615
 
            self._title_orientation = array([0.,1.])
616
 
            #this could be calculated...
617
 
            self.title_angle = 0.0
618
 
            if self.orientation == 'top':
619
 
                self._origin_point = array(self.position) + self._major_axis * screenlow
620
 
                self._inside_vector = array([0.,-1.])
621
 
            else: #self.oriention == 'bottom'
622
 
                self._origin_point = array(self.position) + array([0., self.bounds[1]]) + self._major_axis*screenlow
623
 
                self._inside_vector = array([0., 1.])
624
 
        elif self.orientation in ('left', 'right'):
625
 
            self._major_axis_size = self.bounds[1]
626
 
            self._minor_axis_size = self.bounds[0]
627
 
            self._major_axis = array([0., 1.])
628
 
            self._title_orientation = array([-1., 0])
629
 
            self.title_angle = 90.0
630
 
            if self.orientation == 'left':
631
 
                self._origin_point = array(self.position) + array([self.bounds[0], 0.]) + self._major_axis*screenlow
632
 
                self._inside_vector = array([1., 0.])
633
 
            else: #self.orientation == 'right'
634
 
                self._origin_point = array(self.position) + self._major_axis*screenlow
635
 
                self._inside_vector = array([-1., 0.])
636
 
 
637
 
#        if self.mapper.high_pos<self.mapper.low_pos:
638
 
#            self._origin_point = self._origin_point + self._axis_
639
 
 
640
 
        if self.ensure_ticks_bounded:
641
 
            self._origin_point -= self._inside_vector*self.tick_in
642
 
 
643
 
        self._end_axis_point = (screenhigh-screenlow)*self._major_axis + self._origin_point
644
 
        self._axis_vector = self._end_axis_point - self._origin_point
645
 
        # This is the vector that represents one unit of data space in terms of screen space.
646
 
        self._axis_pixel_vector = self._axis_vector/sqrt(dot(self._axis_vector,self._axis_vector))
647
 
        return
648
 
 
649
 
 
650
 
    #------------------------------------------------------------------------
651
 
    # Private helper methods
652
 
    #------------------------------------------------------------------------
653
 
 
654
 
    def _rotmatrix(self, theta):
655
 
        """Returns a 2x2 rotation matrix for angle theta
656
 
        """
657
 
        return array([[cos(theta), sin(theta)], [-sin(theta), cos(theta)]], float64)
658
 
 
659
 
    def _center_dist(self, vect, width, height, rotation=0.0):
660
 
        """Given a width and height of a rectangle, find the distance in units of the vector,
661
 
        in the direction of the vector, from the center of the rectangle, to wherever the vector
662
 
        leaves the rectangle.  This is useful for determining where to place text so it doesn't
663
 
        run into other things.
664
 
        """
665
 
        rotvec = transpose(dot(self._rotmatrix(rotation*pi/180.0), transpose(array([vect], float64))))[0]
666
 
        absvec = absolute(rotvec)
667
 
        if absvec[1] != 0:
668
 
            heightdist = (float(height)/2)/float(absvec[1])
669
 
        else:
670
 
            heightdist = 9999999
671
 
        if absvec[0] != 0:
672
 
            widthdist = (float(width)/2)/float(absvec[0])
673
 
        else:
674
 
            widthdist = 99999999
675
 
 
676
 
        return min(heightdist, widthdist)
677
 
 
678
 
 
679
 
    #------------------------------------------------------------------------
680
 
    # Event handlers
681
 
    #------------------------------------------------------------------------
682
 
 
683
 
    def _bounds_changed(self, old, new):
684
 
        super(PlotAxis, self)._bounds_changed(old, new)
685
 
        self._layout_needed = True
686
 
        self._invalidate()
687
 
 
688
 
    def _bounds_items_changed(self, event):
689
 
        super(PlotAxis, self)._bounds_items_changed(event)
690
 
        self._layout_needed = True
691
 
        self._invalidate()
692
 
 
693
 
    def _mapper_changed(self, old, new):
694
 
        if old is not None:
695
 
            old.on_trait_change(self.mapper_updated, "updated", remove=True)
696
 
        if new is not None:
697
 
            new.on_trait_change(self.mapper_updated, "updated")
698
 
        self._invalidate()
699
 
 
700
 
    def mapper_updated(self):
701
 
        """
702
 
        Event handler that gets bound to our mapper's .updated event
703
 
        """
704
 
        self._invalidate()
705
 
 
706
 
    def _position_changed(self, old, new):
707
 
        super(PlotAxis, self)._position_changed(old, new)
708
 
        self._cache_valid = False
709
 
 
710
 
    def _position_items_changed(self, event):
711
 
        super(PlotAxis, self)._position_items_changed(event)
712
 
        self._cache_valid = False
713
 
 
714
 
    def _position_changed_for_component(self):
715
 
        self._cache_valid = False
716
 
 
717
 
    def _position_items_changed_for_component(self):
718
 
        self._cache_valid = False
719
 
 
720
 
    def _bounds_changed_for_component(self):
721
 
        self._cache_valid = False
722
 
        self._layout_needed = True
723
 
 
724
 
    def _bounds_items_changed_for_component(self):
725
 
        self._cache_valid = False
726
 
        self._layout_needed = True
727
 
 
728
 
    def _origin_changed_for_component(self):
729
 
        self._invalidate()
730
 
 
731
 
    def _updated_fired(self):
732
 
        """If our bounds changed, redraw."""
733
 
        self._cache_valid = False
734
 
        return
735
 
 
736
 
    def _invalidate(self):
737
 
        self._cache_valid = False
738
 
        self.invalidate_draw()
739
 
        if self.component:
740
 
            self.component.invalidate_draw()
741
 
#            self.component.request_redraw()
742
 
#        else:
743
 
#            self.request_redraw()
744
 
        return
745
 
 
746
 
    def _component_changed(self):
747
 
        if self.mapper is not None:
748
 
            # If there is a mapper set, just leave it be.
749
 
            return
750
 
 
751
 
        # Try to pick the most appropriate mapper for our orientation 
752
 
        # and what information we can glean from our component.
753
 
        attrmap = { "left": ("ymapper", "y_mapper", "value_mapper"),
754
 
                    "bottom": ("xmapper", "x_mapper", "index_mapper"), }
755
 
        attrmap["right"] = attrmap["left"]
756
 
        attrmap["top"] = attrmap["bottom"]
757
 
 
758
 
        component = self.component
759
 
        attr1, attr2, attr3 = attrmap[self.orientation]
760
 
        for attr in attrmap[self.orientation]:
761
 
            if hasattr(component, attr):
762
 
                self.mapper = getattr(component, attr)
763
 
                break
764
 
        return
765
 
 
766
 
    #------------------------------------------------------------------------
767
 
    # The following event handlers just invalidate our previously computed
768
 
    # Label instances and backbuffer if any of our visual attributes change.
769
 
    # TODO: refactor this stuff and the caching of contained objects (e.g. Label)
770
 
    #------------------------------------------------------------------------
771
 
 
772
 
    def _title_changed(self):
773
 
        self.invalidate_draw()
774
 
        if self.component:
775
 
            self.component.invalidate_draw()
776
 
#            self.component.request_redraw()
777
 
#        else:
778
 
#            self.request_redraw()
779
 
 
780
 
    def _title_color_changed(self):
781
 
        return self._invalidate()
782
 
 
783
 
    def _title_font_changed(self):
784
 
        return self._invalidate()
785
 
 
786
 
    def _tick_weight_changed(self):
787
 
        return self._invalidate()
788
 
 
789
 
    def _tick_color_changed(self):
790
 
        return self._invalidate()
791
 
 
792
 
    def _tick_font_changed(self):
793
 
        return self._invalidate()
794
 
 
795
 
    def _tick_label_font_changed(self):
796
 
        return self._invalidate()
797
 
 
798
 
    def _tick_label_color_changed(self):
799
 
        return self._invalidate()
800
 
 
801
 
    def _tick_in_changed(self):
802
 
        return self._invalidate()
803
 
 
804
 
    def _tick_out_changed(self):
805
 
        return self._invalidate()
806
 
 
807
 
    def _tick_visible_changed(self):
808
 
        return self._invalidate()
809
 
 
810
 
    def _tick_interval_changed(self):
811
 
        return self._invalidate()
812
 
 
813
 
    def _axis_line_color_changed(self):
814
 
        return self._invalidate()
815
 
 
816
 
    def _axis_line_weight_changed(self):
817
 
        return self._invalidate()
818
 
 
819
 
    def _axis_line_style_changed(self):
820
 
        return self._invalidate()
821
 
 
822
 
    def _orientation_changed(self):
823
 
        return self._invalidate()
824
 
 
825
 
    #------------------------------------------------------------------------
826
 
    # Persistence-related methods
827
 
    #------------------------------------------------------------------------
828
 
 
829
 
    def __getstate__(self):
830
 
        dont_pickle = [
831
 
            '_tick_list',
832
 
            '_tick_positions',
833
 
            '_tick_label_list',
834
 
            '_tick_label_positions',
835
 
            '_tick_label_bounding_boxes',
836
 
            '_major_axis_size',
837
 
            '_minor_axis_size',
838
 
            '_major_axis',
839
 
            '_title_orientation',
840
 
            '_title_angle',
841
 
            '_origin_point',
842
 
            '_inside_vector',
843
 
            '_axis_vector',
844
 
            '_axis_pixel_vector',
845
 
            '_end_axis_point',
846
 
            '_ticklabel_cache',
847
 
            '_cache_valid'
848
 
           ]
849
 
 
850
 
        state = super(PlotAxis,self).__getstate__()
851
 
        for key in dont_pickle:
852
 
            if state.has_key(key):
853
 
                del state[key]
854
 
 
855
 
        return state
856
 
 
857
 
    def __setstate__(self, state):
858
 
        super(PlotAxis,self).__setstate__(state)
859
 
        self._mapper_changed(None, self.mapper)
860
 
        self._reset_cache()
861
 
        self._cache_valid = False
862
 
        return
863
 
 
864
 
 
865
 
# EOF ########################################################################