1
""" Defines the PlotAxis class, and associated validator and UI.
4
from numpy import array, around, absolute, cos, dot, float64, inf, pi, \
7
# Enthought Library imports
8
from enthought.enable.api import ColorTrait, LineStyle
9
from enthought.kiva.traits.kiva_font_trait import KivaFont
10
from enthought.traits.api import Any, Float, Int, Str, Trait, Unicode, \
11
Bool, Event, List, Array, Instance, Enum, Callable
13
# Local relative imports
14
from ticks import AbstractTickGenerator, DefaultTickGenerator
15
from abstract_mapper import AbstractMapper
16
from abstract_overlay import AbstractOverlay
17
from label import Label
18
from log_mapper import LogMapper
21
def DEFAULT_TICK_FORMATTER(val):
22
return ("%f"%val).rstrip("0").rstrip(".")
24
class PlotAxis(AbstractOverlay):
26
The PlotAxis is a visual component that can be rendered on its own as
27
a standalone component or attached as an overlay to another component.
28
(To attach it as an overlay, set its **component** attribute.)
30
When it is attached as an overlay, it draws into the padding around
34
# The mapper that drives this axis.
35
mapper = Instance(AbstractMapper)
37
# The text of the axis title.
38
title = Trait('', Str, Unicode) #May want to add PlotLabel option
40
# The font of the title.
41
title_font = KivaFont('modern 12')
43
# The spacing between the axis line and the title
44
title_spacing = Trait('auto', 'auto', Float)
46
# The color of the title.
47
title_color = ColorTrait("black")
50
markers = Any # TODO: Implement this
52
# The thickness (in pixels) of each tick.
53
tick_weight = Float(1.0)
55
# The color of the ticks.
56
tick_color = ColorTrait("black")
58
# The font of the tick labels.
59
tick_label_font = KivaFont('modern 10')
61
# The color of the tick labels.
62
tick_label_color = ColorTrait("black")
64
# A callable that is passed the numerical value of each tick label and
65
# that returns a string.
66
tick_label_formatter = Callable(DEFAULT_TICK_FORMATTER)
68
# The number of pixels by which the ticks extend into the plot area.
71
# The number of pixels by which the ticks extend into the label area.
74
# Are ticks visible at all?
75
tick_visible = Bool(True)
77
# The dataspace interval between ticks.
78
tick_interval = Trait('auto', 'auto', Float)
80
# A callable that implements the AbstractTickGenerator interface.
81
tick_generator = Instance(AbstractTickGenerator)
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")
87
# Is the axis line visible?
88
axis_line_visible = Bool(True)
90
# The color of the axis line.
91
axis_line_color = ColorTrait("black")
93
# The line thickness (in pixels) of the axis line.
94
axis_line_weight = Float(1.0)
96
# The dash style of the axis line.
97
axis_line_style = LineStyle('solid')
99
# A special version of the axis line that is more useful for geophysical
101
small_haxis_style = Bool(False) # TODO: MOVE THIS OUT OF HERE!
103
# Does the axis ensure that its end labels fall within its bounding area?
104
ensure_labels_bounded = Bool(False)
106
# Does the axis prevent the ticks from being rendered outside its bounds?
107
# This flag is off by default because the standard axis *does* render ticks
108
# that encroach on the plot area.
109
ensure_ticks_bounded = Bool(False)
111
# Fired when the axis's range bounds change.
114
#------------------------------------------------------------------------
115
# Override default values of inherited traits
116
#------------------------------------------------------------------------
118
# Background color (overrides AbstractOverlay). Axes usually let the color of
119
# the container show through.
120
bgcolor = ColorTrait("transparent")
122
# Dimensions that the axis is resizable in (overrides PlotComponent).
123
# Typically, axes are resizable in both dimensions.
126
#------------------------------------------------------------------------
128
#------------------------------------------------------------------------
130
# Cached position calculations
132
_tick_list = List # These are caches of their respective positions
133
_tick_positions = Any #List
134
_tick_label_list = Any
135
_tick_label_positions = Any
136
_tick_label_bounding_boxes = List
137
_major_axis_size = Float
138
_minor_axis_size = Float
140
_title_orientation = Array
142
_origin_point = Array
143
_inside_vector = Array
145
_axis_pixel_vector = Array
146
_end_axis_point = Array
149
ticklabel_cache = List
150
_cache_valid = Bool(False)
153
#------------------------------------------------------------------------
155
#------------------------------------------------------------------------
157
def __init__(self, component=None, **kwargs):
158
# TODO: change this back to a factory in the instance trait some day
159
self.tick_generator = DefaultTickGenerator()
160
# Override init so that our component gets set last. We want the
161
# _component_changed() event handler to get run last.
162
super(PlotAxis, self).__init__(**kwargs)
163
if component is not None:
164
self.component = component
166
def invalidate(self):
167
""" Invalidates the pre-computed layout and scaling data.
170
self.invalidate_draw()
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
178
from axis_view import AxisView
182
#------------------------------------------------------------------------
183
# PlotComponent and AbstractOverlay interface
184
#------------------------------------------------------------------------
186
def _do_layout(self, *args, **kw):
187
""" Tells this component to do layout at a given size.
189
Overrides PlotComponent.
191
if self.use_draw_order and self.component is not None:
192
self._layout_as_overlay(*args, **kw)
194
super(PlotAxis, self)._do_layout(*args, **kw)
197
def overlay(self, component, gc, view_bounds=None, mode='normal'):
198
""" Draws this component overlaid on another component.
200
Overrides AbstractOverlay.
204
self._draw_component(gc, view_bounds, mode, component)
207
def _draw_overlay(self, gc, view_bounds=None, mode='normal'):
208
""" Draws the overlay layer of a component.
210
Overrides PlotComponent.
212
self._draw_component(gc, view_bounds, mode)
215
def _draw_component(self, gc, view_bounds=None, mode='normal', component=None):
216
""" Draws the component.
218
This method is preserved for backwards compatibility. Overrides
224
if not self._cache_valid:
225
if component is not None:
226
self._calculate_geometry(component)
228
self._old_calculate_geometry()
229
self._compute_tick_positions(gc, component)
230
self._compute_labels(gc)
235
# slight optimization: if we set the font correctly on the
236
# base gc before handing it in to our title and tick labels,
237
# their set_font() won't have to do any work.
238
gc.set_font(self.tick_label_font)
240
if self.axis_line_visible:
241
self._draw_axis_line(gc, self._origin_point, self._end_axis_point)
246
self._draw_labels(gc)
250
self._cache_valid = True
254
#------------------------------------------------------------------------
255
# Private draw routines
256
#------------------------------------------------------------------------
258
def _layout_as_overlay(self, size=None, force=False):
259
""" Lays out the axis as an overlay on another component.
261
if self.component is not None:
262
if self.orientation in ("left", "right"):
263
self.y = self.component.y
264
self.height = self.component.height
265
if self.orientation == "left":
266
self.width = self.component.padding_left
267
self.x = self.component.outer_x
268
elif self.orientation == "right":
269
self.width = self.component.padding_right
270
self.x = self.component.x2 + 1
272
self.x = self.component.x
273
self.width = self.component.width
274
if self.orientation == "bottom":
275
self.height = self.component.padding_bottom
276
self.y = self.component.outer_y
277
elif self.orientation == "top":
278
self.height = self.component.padding_top
279
self.y = self.component.y2 + 1
282
def _draw_axis_line(self, gc, startpoint, endpoint):
283
""" Draws the line for the axis.
288
gc.set_line_width(self.axis_line_weight)
289
gc.set_stroke_color(self.axis_line_color_)
290
gc.set_line_dash(self.axis_line_style_)
291
gc.move_to(*around(startpoint))
292
gc.line_to(*around(endpoint))
298
def _draw_title_old(self, gc, label=None, v_offset=20):
299
""" Draws the title for the axis.
301
#put in rotation code for right side
304
title_label = Label(text=self.title,
305
font=self.title_font,
306
color=self.title_color,
307
rotate_angle=self.title_angle)
310
tl_bounds = array(title_label.get_width_height(gc), float64)
312
if self.title_angle == 0:
313
text_center_to_corner = -tl_bounds/2.0
314
v_offset = max([l._bounding_box[1] for l in self.ticklabel_cache]) * 1.3
316
v_offset = max([l._bounding_box[0] for l in self.ticklabel_cache]) * 1.3
317
corner_vec = transpose(-tl_bounds/2.0)
318
rotmatrix = self._rotmatrix(-self.title_angle*pi/180.0)
319
text_center_to_corner = transpose(dot(rotmatrix, corner_vec))[0]
321
offset = (self._origin_point+self._end_axis_point)/2
322
center_dist = self._center_dist(-self._inside_vector, tl_bounds[0], tl_bounds[1], rotation=self.title_angle)
323
offset -= self._inside_vector * (center_dist + v_offset)
324
offset += text_center_to_corner
326
if self.title_angle == 90.0:
327
# Horrible hack to adjust for the fact that the generic math above isn't
328
# actually putting the label in the right place...
329
offset[1] = offset[1] - tl_bounds[0]/2.0
331
gc.translate_ctm(*offset)
333
gc.translate_ctm(*(-offset))
338
def _draw_title(self, gc, label=None, v_offset=None):
339
""" Draws the title for the axis.
341
#put in rotation code for right side
345
title_label = Label(text=self.title,
346
font=self.title_font,
347
color=self.title_color,
348
rotate_angle=self.title_angle)
351
tl_bounds = array(title_label.get_width_height(gc), float64)
353
if self.title_spacing != 'auto':
354
v_offset = self.title_spacing
355
calculate_v_offset = (self.title_spacing) and (v_offset is None )
357
if self.title_angle == 0:
358
text_center_to_corner = -tl_bounds/2.0
359
if calculate_v_offset:
360
if not self.ticklabel_cache:
363
v_offset = max([l._bounding_box[1] for l in self.ticklabel_cache]) * 1.3
365
offset = (self._origin_point+self._end_axis_point)/2
366
center_dist = self._center_dist(-self._inside_vector, tl_bounds[0], tl_bounds[1], rotation=self.title_angle)
367
offset -= self._inside_vector * (center_dist + v_offset)
368
offset += text_center_to_corner
370
elif self.title_angle == 90:
371
# Center the text vertically
372
if calculate_v_offset:
373
if not self.ticklabel_cache:
376
v_offset = (self._end_axis_point[1] - self._origin_point[1] - tl_bounds[0])/2.0
377
h_offset = self.tick_out + tl_bounds[1] + 8
378
if len(self.ticklabel_cache) > 0:
379
h_offset += max([l._bounding_box[0] for l in self.ticklabel_cache])
380
offset = array([self._origin_point[0] - h_offset, self._origin_point[1] + v_offset])
382
elif self.title_angle == 270:
383
# Center the text vertically
384
if calculate_v_offset:
385
if not self.ticklabel_cache:
388
v_offset = (self._end_axis_point[1] - self._origin_point[1] + tl_bounds[0])/2.0
389
h_offset = self.tick_out + tl_bounds[1] + 8
390
if len(self.ticklabel_cache) > 0:
391
h_offset += max([l._bounding_box[0] for l in self.ticklabel_cache])
392
offset = array([self._origin_point[0] + h_offset, self._origin_point[1] + v_offset])
395
if calculate_v_offset:
396
if not self.ticklabel_cache:
399
v_offset = max([l._bounding_box[0] for l in self.ticklabel_cache]) * 1.3
400
corner_vec = transpose(-tl_bounds/2.0)
401
rotmatrix = self._rotmatrix(-self.title_angle*pi/180.0)
402
text_center_to_corner = transpose(dot(rotmatrix, corner_vec))[0]
403
offset = (self._origin_point+self._end_axis_point)/2
404
center_dist = self._center_dist(-self._inside_vector, tl_bounds[0], tl_bounds[1], rotation=self.title_angle)
405
offset -= self._inside_vector * (center_dist + v_offset)
406
offset += text_center_to_corner
408
gc.translate_ctm(*offset)
410
gc.translate_ctm(*(-offset))
415
def _draw_ticks(self, gc):
416
""" Draws the tick marks for the axis.
418
if not self.tick_visible:
420
gc.set_stroke_color(self.tick_color_)
421
gc.set_line_width(self.tick_weight)
422
gc.set_antialias(False)
424
tick_in_vector = self._inside_vector*self.tick_in
425
tick_out_vector = self._inside_vector*self.tick_out
426
for tick_pos in self._tick_positions:
427
gc.move_to(*(tick_pos + tick_in_vector))
428
gc.line_to(*(tick_pos - tick_out_vector))
432
def _draw_labels(self, gc):
433
""" Draws the tick labels for the axis.
435
for i in range(len(self._tick_label_positions)):
436
#We want a more sophisticated scheme than just 2 decimals all the time
437
ticklabel = self.ticklabel_cache[i]
438
tl_bounds = self._tick_label_bounding_boxes[i]
440
#base_position puts the tick label at a point where the vector
441
#extending from the tick mark inside 8 units
442
#just touches the rectangular bounding box of the tick label.
443
#Note: This is not necessarily optimal for non
444
#horizontal/vertical axes. More work could be done on this.
446
base_position = (self._center_dist(-self._inside_vector, *tl_bounds)+8) \
447
* -self._inside_vector \
448
- tl_bounds/2.0 + self._tick_label_positions[i]
450
if self.ensure_labels_bounded:
454
elif i == len(self._tick_label_positions)-1:
456
push_pixel_vector = self._axis_pixel_vector * pushdir
457
tlpos = around((self._center_dist(push_pixel_vector,*tl_bounds)+4) \
458
* push_pixel_vector + base_position)
461
tlpos = around(base_position)
463
gc.translate_ctm(*tlpos)
465
gc.translate_ctm(*(-tlpos))
469
#------------------------------------------------------------------------
470
# Private methods for computing positions and layout
471
#------------------------------------------------------------------------
473
def _reset_cache(self):
474
""" Clears the cached tick positions, labels, and label positions.
476
self._tick_positions = []
477
self._tick_label_list = []
478
self._tick_label_positions = []
481
def _compute_tick_positions(self, gc, overlay_component=None):
482
""" Calculates the positions for the tick marks.
484
if (self.mapper is None):
486
self._cache_valid = True
489
datalow = self.mapper.range.low
490
datahigh = self.mapper.range.high
491
screenhigh = self.mapper.high_pos
492
screenlow = self.mapper.low_pos
493
if overlay_component is not None:
494
origin = getattr(overlay_component, 'origin', 'bottom left')
495
if self.orientation in ("top", "bottom"):
496
if "right" in origin:
500
elif self.orientation in ("left", "right"):
507
screenlow, screenhigh = screenhigh, screenlow
510
if (datalow == datahigh) or (screenlow == screenhigh) or \
511
(datalow in [inf, -inf]) or (datahigh in [inf, -inf]):
513
self._cache_valid = True
516
if datalow > datahigh:
517
raise RuntimeError, "DataRange low is greater than high; unable to compute axis ticks."
519
if not self.tick_generator:
522
if isinstance(self.mapper, LogMapper):
527
tick_list = array(self.tick_generator.get_ticks(datalow, datahigh,
531
scale=scale), float64)
533
mapped_tick_positions = (array(self.mapper.map_screen(tick_list))-screenlow) / \
534
(screenhigh-screenlow)
535
self._tick_positions = around(array([self._axis_vector*tickpos + self._origin_point \
536
for tickpos in mapped_tick_positions]))
538
if self.small_haxis_style:
539
# If we're a small axis, we want the endpoints to be the labels regardless of
540
# where the ticks are, as the labels represent the bounds, not where the tick
542
self._tick_label_list = array([datalow, datahigh])
543
mapped_label_positions = (array(self.mapper.map_screen(self._tick_label_list))-screenlow) / \
544
(screenhigh-screenlow)
545
self._tick_label_positions = [self._axis_vector*tickpos + self._origin_point \
546
for tickpos in mapped_label_positions]
548
self._tick_label_list = tick_list
549
self._tick_label_positions = self._tick_positions
553
def _compute_labels(self, gc):
554
"""Generates the labels for tick marks.
556
Waits for the cache to become invalid.
558
self.ticklabel_cache = []
559
formatter = self.tick_label_formatter
560
for i in range(len(self._tick_label_positions)):
561
val = self._tick_label_list[i]
562
if formatter is not None:
563
tickstring = formatter(val)
565
tickstring = str(val)
566
ticklabel = Label(text=tickstring,
567
font=self.tick_label_font,
568
color=self.tick_label_color)
569
self.ticklabel_cache.append(ticklabel)
571
# TODO: Right now we are hardcoding this handling of a scaled CTM,
572
# eventually it would be nice if we didn't have to do this.
576
scale = array((ctm[0], ctm[3]))
579
scale = array((ctm[0][0], ctm[1][1]))
581
scale = array((1.0, 1.0))
582
self._tick_label_bounding_boxes = [array(ticklabel.get_bounding_box(gc), float) / scale
583
for ticklabel in self.ticklabel_cache]
587
def _calculate_geometry(self, overlay_component=None):
588
if overlay_component is not None:
589
if self.orientation == "top":
590
new_origin = [overlay_component.x, overlay_component.y2]
591
inside_vec = [0.0, -1.0]
592
elif self.orientation == "bottom":
593
new_origin = [overlay_component.x, overlay_component.y]
594
inside_vec = [0.0, 1.0]
595
elif self.orientation == "left":
596
new_origin = [overlay_component.x, overlay_component.y]
597
inside_vec = [1.0, 0.0]
598
else: # self.orientation == "right":
599
new_origin = [overlay_component.x2, overlay_component.y]
600
inside_vec = [-1.0, 0.0]
601
self._origin_point = array(new_origin)
602
self._inside_vector = array(inside_vec)
604
#FIXME: Why aren't we setting self._inside_vector here?
605
overlay_component = self
606
new_origin = array(self.position)
608
origin = getattr(overlay_component, "origin", 'bottom left')
609
if self.orientation in ('top', 'bottom'):
610
self._major_axis_size = overlay_component.bounds[0]
611
self._minor_axis_size = overlay_component.bounds[1]
612
self._major_axis = array([1., 0.])
613
self._title_orientation = array([0.,1.])
614
if "right" in origin:
618
#this could be calculated...
619
self.title_angle = 0.0
620
elif self.orientation in ('left', 'right'):
621
self._major_axis_size = overlay_component.bounds[1]
622
self._minor_axis_size = overlay_component.bounds[0]
623
self._major_axis = array([0., 1.])
624
self._title_orientation = array([-1., 0])
625
origin = getattr(overlay_component, "origin", 'bottom left')
630
if self.orientation == 'left':
631
self.title_angle = 90.0
633
self.title_angle = 270.0
635
if self.ensure_ticks_bounded:
636
self._origin_point -= self._inside_vector*self.tick_in
638
screenhigh = self.mapper.high_pos
639
screenlow = self.mapper.low_pos
640
# TODO: should this be here, or not?
642
screenlow, screenhigh = screenhigh, screenlow
644
self._end_axis_point = (screenhigh-screenlow)*self._major_axis + self._origin_point
645
self._axis_vector = self._end_axis_point - self._origin_point
646
# This is the vector that represents one unit of data space in terms of screen space.
647
self._axis_pixel_vector = self._axis_vector/sqrt(dot(self._axis_vector,self._axis_vector))
651
def _old_calculate_geometry(self):
652
if hasattr(self, 'mapper') and self.mapper is not None:
653
screenhigh = self.mapper.high_pos
654
screenlow = self.mapper.low_pos
656
# fixme: this should take into account axis orientation
660
if self.orientation in ('top', 'bottom'):
661
self._major_axis_size = self.bounds[0]
662
self._minor_axis_size = self.bounds[1]
663
self._major_axis = array([1., 0.])
664
self._title_orientation = array([0.,1.])
665
#this could be calculated...
666
self.title_angle = 0.0
667
if self.orientation == 'top':
668
self._origin_point = array(self.position) + self._major_axis * screenlow
669
self._inside_vector = array([0.,-1.])
670
else: #self.oriention == 'bottom'
671
self._origin_point = array(self.position) + array([0., self.bounds[1]]) + self._major_axis*screenlow
672
self._inside_vector = array([0., 1.])
673
elif self.orientation in ('left', 'right'):
674
self._major_axis_size = self.bounds[1]
675
self._minor_axis_size = self.bounds[0]
676
self._major_axis = array([0., 1.])
677
self._title_orientation = array([-1., 0])
678
self.title_angle = 90.0
679
if self.orientation == 'left':
680
self._origin_point = array(self.position) + array([self.bounds[0], 0.]) + self._major_axis*screenlow
681
self._inside_vector = array([1., 0.])
682
else: #self.orientation == 'right'
683
self._origin_point = array(self.position) + self._major_axis*screenlow
684
self._inside_vector = array([-1., 0.])
686
# if self.mapper.high_pos<self.mapper.low_pos:
687
# self._origin_point = self._origin_point + self._axis_
689
if self.ensure_ticks_bounded:
690
self._origin_point -= self._inside_vector*self.tick_in
692
self._end_axis_point = (screenhigh-screenlow)*self._major_axis + self._origin_point
693
self._axis_vector = self._end_axis_point - self._origin_point
694
# This is the vector that represents one unit of data space in terms of screen space.
695
self._axis_pixel_vector = self._axis_vector/sqrt(dot(self._axis_vector,self._axis_vector))
699
#------------------------------------------------------------------------
700
# Private helper methods
701
#------------------------------------------------------------------------
703
def _rotmatrix(self, theta):
704
"""Returns a 2x2 rotation matrix for angle *theta*.
706
return array([[cos(theta), sin(theta)], [-sin(theta), cos(theta)]], float64)
708
def _center_dist(self, vect, width, height, rotation=0.0):
709
"""Given a width and height of a rectangle, this method finds the
710
distance in units of the vector, in the direction of the vector, from
711
the center of the rectangle, to wherever the vector leaves the
712
rectangle. This method is useful for determining where to place text so
713
it doesn't run into other components. """
714
rotvec = transpose(dot(self._rotmatrix(rotation*pi/180.0), transpose(array([vect], float64))))[0]
715
absvec = absolute(rotvec)
717
heightdist = (float(height)/2)/float(absvec[1])
721
widthdist = (float(width)/2)/float(absvec[0])
725
return min(heightdist, widthdist)
728
#------------------------------------------------------------------------
730
#------------------------------------------------------------------------
732
def _bounds_changed(self, old, new):
733
super(PlotAxis, self)._bounds_changed(old, new)
734
self._layout_needed = True
737
def _bounds_items_changed(self, event):
738
super(PlotAxis, self)._bounds_items_changed(event)
739
self._layout_needed = True
742
def _mapper_changed(self, old, new):
744
old.on_trait_change(self.mapper_updated, "updated", remove=True)
746
new.on_trait_change(self.mapper_updated, "updated")
749
def mapper_updated(self):
751
Event handler that is bound to this axis's mapper's **updated** event
755
def _position_changed(self, old, new):
756
super(PlotAxis, self)._position_changed(old, new)
757
self._cache_valid = False
759
def _position_items_changed(self, event):
760
super(PlotAxis, self)._position_items_changed(event)
761
self._cache_valid = False
763
def _position_changed_for_component(self):
764
self._cache_valid = False
766
def _position_items_changed_for_component(self):
767
self._cache_valid = False
769
def _bounds_changed_for_component(self):
770
self._cache_valid = False
771
self._layout_needed = True
773
def _bounds_items_changed_for_component(self):
774
self._cache_valid = False
775
self._layout_needed = True
777
def _origin_changed_for_component(self):
780
def _updated_fired(self):
781
"""If the axis bounds changed, redraw."""
782
self._cache_valid = False
785
def _invalidate(self):
786
self._cache_valid = False
787
self.invalidate_draw()
789
self.component.invalidate_draw()
790
# self.component.request_redraw()
792
# self.request_redraw()
795
def _component_changed(self):
796
if self.mapper is not None:
797
# If there is a mapper set, just leave it be.
800
# Try to pick the most appropriate mapper for our orientation
801
# and what information we can glean from our component.
802
attrmap = { "left": ("ymapper", "y_mapper", "value_mapper"),
803
"bottom": ("xmapper", "x_mapper", "index_mapper"), }
804
attrmap["right"] = attrmap["left"]
805
attrmap["top"] = attrmap["bottom"]
807
component = self.component
808
attr1, attr2, attr3 = attrmap[self.orientation]
809
for attr in attrmap[self.orientation]:
810
if hasattr(component, attr):
811
self.mapper = getattr(component, attr)
816
#------------------------------------------------------------------------
817
# The following event handlers just invalidate our previously computed
818
# Label instances and backbuffer if any of our visual attributes change.
819
# TODO: refactor this stuff and the caching of contained objects (e.g. Label)
820
#------------------------------------------------------------------------
822
def _title_changed(self):
823
self.invalidate_draw()
825
self.component.invalidate_draw()
826
# self.component.request_redraw()
828
# self.request_redraw()
830
def _title_color_changed(self):
831
return self._invalidate()
833
def _title_font_changed(self):
834
return self._invalidate()
836
def _tick_weight_changed(self):
837
return self._invalidate()
839
def _tick_color_changed(self):
840
return self._invalidate()
842
def _tick_font_changed(self):
843
return self._invalidate()
845
def _tick_label_font_changed(self):
846
return self._invalidate()
848
def _tick_label_color_changed(self):
849
return self._invalidate()
851
def _tick_in_changed(self):
852
return self._invalidate()
854
def _tick_out_changed(self):
855
return self._invalidate()
857
def _tick_visible_changed(self):
858
return self._invalidate()
860
def _tick_interval_changed(self):
861
return self._invalidate()
863
def _axis_line_color_changed(self):
864
return self._invalidate()
866
def _axis_line_weight_changed(self):
867
return self._invalidate()
869
def _axis_line_style_changed(self):
870
return self._invalidate()
872
def _orientation_changed(self):
873
return self._invalidate()
875
#------------------------------------------------------------------------
876
# Persistence-related methods
877
#------------------------------------------------------------------------
879
def __getstate__(self):
884
'_tick_label_positions',
885
'_tick_label_bounding_boxes',
889
'_title_orientation',
894
'_axis_pixel_vector',
900
state = super(PlotAxis,self).__getstate__()
901
for key in dont_pickle:
902
if state.has_key(key):
907
def __setstate__(self, state):
908
super(PlotAxis,self).__setstate__(state)
909
self._mapper_changed(None, self.mapper)
911
self._cache_valid = False
915
# EOF ########################################################################