Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a5761e8

Browse files
committed
spines and ticks: implement smart bounds
svn path=/trunk/matplotlib/; revision=8048
1 parent f5899e1 commit a5761e8

File tree

3 files changed

+156
-2
lines changed

3 files changed

+156
-2
lines changed

examples/pylab_examples/spine_placement_demo.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
ax.spines['right'].set_color('none')
3838
ax.spines['bottom'].set_position('center')
3939
ax.spines['top'].set_color('none')
40+
ax.spines['left'].set_smart_bounds(True)
41+
ax.spines['bottom'].set_smart_bounds(True)
4042
ax.xaxis.set_ticks_position('bottom')
4143
ax.yaxis.set_ticks_position('left')
4244

@@ -47,6 +49,8 @@
4749
ax.spines['right'].set_color('none')
4850
ax.spines['bottom'].set_position('zero')
4951
ax.spines['top'].set_color('none')
52+
ax.spines['left'].set_smart_bounds(True)
53+
ax.spines['bottom'].set_smart_bounds(True)
5054
ax.xaxis.set_ticks_position('bottom')
5155
ax.yaxis.set_ticks_position('left')
5256

@@ -57,6 +61,8 @@
5761
ax.spines['right'].set_color('none')
5862
ax.spines['bottom'].set_position(('axes',0.1))
5963
ax.spines['top'].set_color('none')
64+
ax.spines['left'].set_smart_bounds(True)
65+
ax.spines['bottom'].set_smart_bounds(True)
6066
ax.xaxis.set_ticks_position('bottom')
6167
ax.yaxis.set_ticks_position('left')
6268

@@ -67,15 +73,17 @@
6773
ax.spines['right'].set_color('none')
6874
ax.spines['bottom'].set_position(('data',2))
6975
ax.spines['top'].set_color('none')
76+
ax.spines['left'].set_smart_bounds(True)
77+
ax.spines['bottom'].set_smart_bounds(True)
7078
ax.xaxis.set_ticks_position('bottom')
7179
ax.yaxis.set_ticks_position('left')
72-
7380
# ----------------------------------------------------
7481

7582
def adjust_spines(ax,spines):
7683
for loc, spine in ax.spines.iteritems():
7784
if loc in spines:
7885
spine.set_position(('outward',10)) # outward by 10 points
86+
spine.set_smart_bounds(True)
7987
else:
8088
spine.set_color('none') # don't draw spine
8189

lib/matplotlib/axis.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import matplotlib.ticker as mticker
1616
import matplotlib.transforms as mtransforms
1717
import matplotlib.units as munits
18+
import numpy as np
1819

1920
GRIDLINE_INTERPOLATION_STEPS = 180
2021

@@ -539,6 +540,8 @@ def __init__(self, axes, pickradius=15):
539540
#self.minor = dummy()
540541

541542
self._autolabelpos = True
543+
self._smart_bounds = False
544+
542545
self.label = self._get_label()
543546
self.labelpad = 5
544547
self.offsetText = self._get_offset_text()
@@ -737,6 +740,14 @@ def get_ticklabel_extents(self, renderer):
737740
bbox2 = mtransforms.Bbox.from_extents(0, 0, 0, 0)
738741
return bbox, bbox2
739742

743+
def set_smart_bounds(self,value):
744+
"""set the axis to have smart bounds"""
745+
self._smart_bounds = value
746+
747+
def get_smart_bounds(self):
748+
"""get whether the axis has smart bounds"""
749+
return self._smart_bounds
750+
740751
@allow_rasterization
741752
def draw(self, renderer, *args, **kwargs):
742753
'Draw the axis lines, grid lines, tick lines and labels'
@@ -746,7 +757,47 @@ def draw(self, renderer, *args, **kwargs):
746757
if not self.get_visible(): return
747758
renderer.open_group(__name__)
748759
interval = self.get_view_interval()
749-
for tick, loc, label in self.iter_ticks():
760+
tick_tups = [ t for t in self.iter_ticks()]
761+
if self._smart_bounds:
762+
# handle inverted limits
763+
view_low, view_high = min(*interval), max(*interval)
764+
data_low, data_high = self.get_data_interval()
765+
if data_low > data_high:
766+
data_low, data_high = data_high, data_low
767+
locs = [ti[1] for ti in tick_tups]
768+
locs.sort()
769+
locs = np.array(locs)
770+
if len(locs):
771+
if data_low <= view_low:
772+
# data extends beyond view, take view as limit
773+
ilow = view_low
774+
else:
775+
# data stops within view, take best tick
776+
cond = locs <= data_low
777+
good_locs = locs[cond]
778+
if len(good_locs) > 0:
779+
# last tick prior or equal to first data point
780+
ilow = good_locs[-1]
781+
else:
782+
# No ticks (why not?), take first tick
783+
ilow = locs[0]
784+
if data_high >= view_high:
785+
# data extends beyond view, take view as limit
786+
ihigh = view_high
787+
else:
788+
# data stops within view, take best tick
789+
cond = locs >= data_high
790+
good_locs = locs[cond]
791+
if len(good_locs) > 0:
792+
# first tick after or equal to last data point
793+
ihigh = good_locs[0]
794+
else:
795+
# No ticks (why not?), take last tick
796+
ihigh = locs[-1]
797+
tick_tups = [ ti for ti in tick_tups
798+
if (ti[1] >= ilow) and (ti[1] <= ihigh)]
799+
800+
for tick, loc, label in tick_tups:
750801
if tick is None: continue
751802
if not mtransforms.interval_contains(interval, loc): continue
752803
tick.update_position(loc)

lib/matplotlib/spines.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def __init__(self,axes,spine_type,path,**kwargs):
5959
self.set_transform(self.axes.transData) # default transform
6060

6161
self._bounds = None # default bounds
62+
self._smart_bounds = False
6263

6364
# Defer initial position determination. (Not much support for
6465
# non-rectangular axes is currently implemented, and this lets
@@ -78,6 +79,20 @@ def __init__(self,axes,spine_type,path,**kwargs):
7879
# Note: This cannot be calculated until this is added to an Axes
7980
self._patch_transform = mtransforms.IdentityTransform()
8081

82+
def set_smart_bounds(self,value):
83+
"""set the spine and associated axis to have smart bounds"""
84+
self._smart_bounds = value
85+
86+
# also set the axis if possible
87+
if self.spine_type in ('left','right'):
88+
self.axes.yaxis.set_smart_bounds(value)
89+
elif self.spine_type in ('top','bottom'):
90+
self.axes.xaxis.set_smart_bounds(value)
91+
92+
def get_smart_bounds(self):
93+
"""get whether the spine has smart bounds"""
94+
return self._smart_bounds
95+
8196
def set_patch_circle(self,center,radius):
8297
"""set the spine to be circular"""
8398
self._patch_type = 'circle'
@@ -141,6 +156,26 @@ def cla(self):
141156
if self.axis is not None:
142157
self.axis.cla()
143158

159+
def is_frame_like(self):
160+
"""return True if directly on axes frame
161+
162+
This is useful for determining if a spine is the edge of an
163+
old style MPL plot. If so, this function will return True.
164+
"""
165+
self._ensure_position_is_set()
166+
position = self._position
167+
if cbook.is_string_like(position):
168+
if position=='center':
169+
position = ('axes',0.5)
170+
elif position=='zero':
171+
position = ('data',0)
172+
assert len(position)==2, "position should be 2-tuple"
173+
position_type, amount = position
174+
if position_type=='outward' and amount == 0:
175+
return True
176+
else:
177+
return False
178+
144179
def _adjust_location(self):
145180
"""automatically set spine bounds to the view interval"""
146181

@@ -154,6 +189,61 @@ def _adjust_location(self):
154189
low,high = self.axes.viewLim.intervalx
155190
else:
156191
raise ValueError('unknown spine spine_type: %s'%self.spine_type)
192+
193+
if self._smart_bounds:
194+
# attempt to set bounds in sophisticated way
195+
if low > high:
196+
# handle inverted limits
197+
low,high=high,low
198+
199+
viewlim_low = low
200+
viewlim_high = high
201+
202+
del low, high
203+
204+
if self.spine_type in ('left','right'):
205+
datalim_low,datalim_high = self.axes.dataLim.intervaly
206+
ticks = self.axes.get_yticks()
207+
elif self.spine_type in ('top','bottom'):
208+
datalim_low,datalim_high = self.axes.dataLim.intervalx
209+
ticks = self.axes.get_xticks()
210+
# handle inverted limits
211+
ticks = list(ticks)
212+
ticks.sort()
213+
ticks = np.array(ticks)
214+
if datalim_low > datalim_high:
215+
datalim_low, datalim_high = datalim_high, datalim_low
216+
217+
if datalim_low < viewlim_low:
218+
# Data extends past view. Clip line to view.
219+
low = viewlim_low
220+
else:
221+
# Data ends before view ends.
222+
cond = (ticks <= datalim_low) & (ticks >= viewlim_low)
223+
tickvals = ticks[cond]
224+
if len(tickvals):
225+
# A tick is less than or equal to lowest data point.
226+
low = tickvals[-1]
227+
else:
228+
# No tick is available
229+
low = datalim_low
230+
low = max(low,viewlim_low)
231+
232+
if datalim_high > viewlim_high:
233+
# Data extends past view. Clip line to view.
234+
high = viewlim_high
235+
else:
236+
# Data ends before view ends.
237+
cond = (ticks >= datalim_high) & (ticks <= viewlim_high)
238+
tickvals = ticks[cond]
239+
if len(tickvals):
240+
# A tick is greater than or equal to highest data point.
241+
high = tickvals[0]
242+
else:
243+
# No tick is available
244+
high = datalim_high
245+
high = min(high,viewlim_high)
246+
157247
else:
158248
low,high = self._bounds
159249

@@ -316,11 +406,16 @@ def get_spine_transform(self):
316406
raise ValueError("unknown spine_transform type: %s"%what)
317407

318408
def set_bounds( self, low, high ):
409+
"""Set the bounds of the spine."""
319410
if self.spine_type == 'circle':
320411
raise ValueError(
321412
'set_bounds() method incompatible with circular spines')
322413
self._bounds = (low, high)
323414

415+
def get_bounds( self ):
416+
"""Get the bounds of the spine."""
417+
return self._bounds
418+
324419
@classmethod
325420
def linear_spine(cls, axes, spine_type, **kwargs):
326421
"""

0 commit comments

Comments
 (0)