

import matplotlib.pyplot as plt
import matplotlib
import types

    
def set_superior_locator(locator,superiorlocator):
    """
    This function adds the capability to a locator to have a "superior".  If it has a superior, the locator
    will be careful not to draw on top of locations that have already been taken care of in the superiorlocator.
    
    The main use of this is to tell a minor locator who the major locator is, so that the minor locator will not
    draw ticks and/or grid lines on top of places where the major locator has already done it.
    
    It would be better for this function to be a member of the Locator class.  It would fit in much more cleanly
    this way.
    """   
    
    def newcall(self):
        """
        This is our own hook into __call__ for the locator.
        """
        superiorlocs = self._superiorlocator() if self._superiorlocator else None
        locs         =  self._oldcall()
              
        if superiorlocs != None:
            locs_orig = locs[:]
            locs = []
            # at this point, we have all of the major tick locations, and all of the minor ones.
            # eliminate all entries in the minorlocs that are a duplicate of the entries in majorlocs.
            def IsEqualTol(a,b,tol=1.0e-10):
                #if abs(2.0*(a-b)/(a+b)) < tol: return True
                if abs(a-b) < tol*(abs(a)+0.01): return True  # ref: J. Shinnerl, Relative Error and Significant Digits
                return False
                    
            
            
            # this code is horrible.  Surely there's a much better way of culling out duplicates from the two
            # lists.      
            for mi in locs_orig:        
                for mj in superiorlocs:                   
                     if IsEqualTol(mi,mj): break
                     if mj > mi+1.0e-5: break    # assumes locs_orig and superiorlocs always ascending sorted. (tested and seems to be true)
                if not IsEqualTol(mi,mj):
                    locs.append(mi)
            
        return locs

    # perform the dynamic mixin.  We save off a reference to the superior locator, and then
    # hook into the __call__ attribute, so that we get to manipulate the results that are 
    # returned.
    locator._superiorlocator = superiorlocator
    if not hasattr(locator,'_oldcall'):
        locator._oldcall        = locator.__call__
        locator.__call__        = types.MethodType(newcall,locator)



def draw_basic_plot(use_superior_minor_locator):
    plt.plot([1,2,3,2,6],zorder=5)
    axes = plt.gca()
    axis = axes.get_yaxis()
    axes.set_ylim(6,0)
    
    axincr = 2.0
    nminors = 4
    
    majorlocator = matplotlib.ticker.MultipleLocator(axincr)
    axis.set_major_locator(majorlocator)
           
    minorlocator = matplotlib.ticker.MultipleLocator(axincr/nminors)
    axis.set_minor_locator(minorlocator)
    
    axis.grid(b=True,which='major',linewidth=3,linestyle='-',color='blue')
    axis.grid(b=True,which='minor',linewidth=1,linestyle='-',color='red')
    if hasattr(axis,'set_tick_params'): # older versions of matplotlib don't have set_tick_params
       axis.set_tick_params(which='major',width=8,size=20,color='magenta')    
       axis.set_tick_params(which='minor',width=3,size=10,color='cyan')  
    
    # here, call our function that does the magic
    if use_superior_minor_locator:
        set_superior_locator(minorlocator,majorlocator)
        

# draw a plot 
plt.subplot(121)
draw_basic_plot(False)

# draw an identical plot with the new locator
plt.subplot(122)
draw_basic_plot(True)

plt.show()


