Matplotlib axis with two scales shared origin

前端 未结 7 1376
忘了有多久
忘了有多久 2020-11-29 04:17

I need two overlay two datasets with different Y-axis scales in Matplotlib. The data contains both positive and negative values. I want the two axes to share one origin, but

7条回答
  •  生来不讨喜
    2020-11-29 04:34

    I needed to align two subplots but not at their zeros. And other solutions didn't quite work for me.

    The main code of my program looks like this. The subplots are not aligned. Further I only change align_yaxis function and keep all other code the same.

    import matplotlib.pyplot as plt
    
    def align_yaxis(ax1, v1, ax2, v2):
      return 0
    
    x  = range(10)
    y1 = [3.2, 1.3, -0.3, 0.4, 2.3, -0.9, 0.2, 0.1, 1.3, -3.4]
    y2, s = [], 100
    for i in y1:
        s *= 1 + i/100
        y2.append(s)
    
    fig = plt.figure()
    ax1 = fig.add_subplot()
    ax2 = ax1.twinx()
    
    ax1.axhline(y=0, color='k', linestyle='-', linewidth=0.5)
    ax1.bar(x, y1, color='tab:blue')
    ax2.plot(x, y2, color='tab:red')
    
    fig.tight_layout()
    align_yaxis(ax1, 0, ax2, 100)
    plt.show()
    

    Picture of not aligned subplots

    Using @HYRY's solution I get aligned subplots, but the second subplot is out of the figure. You can't see it.

    def align_yaxis(ax1, v1, ax2, v2):
        """adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
        _, y1 = ax1.transData.transform((0, v1))
        _, y2 = ax2.transData.transform((0, v2))
        inv = ax2.transData.inverted()
        _, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
        miny, maxy = ax2.get_ylim()
        ax2.set_ylim(miny+dy, maxy+dy)
    

    Picture without second subplot

    Using @drevicko's solution I also get aligned plot. But now the first subplot is out of the picture and first Y axis is quite weird.

    def align_yaxis(ax1, v1, ax2, v2):
        """adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
        _, y1 = ax1.transData.transform((0, v1))
        _, y2 = ax2.transData.transform((0, v2))
        adjust_yaxis(ax2,(y1-y2)/2,v2)
        adjust_yaxis(ax1,(y2-y1)/2,v1)
    
    def adjust_yaxis(ax,ydif,v):
        """shift axis ax by ydiff, maintaining point v at the same location"""
        inv = ax.transData.inverted()
        _, dy = inv.transform((0, 0)) - inv.transform((0, ydif))
        miny, maxy = ax.get_ylim()
        miny, maxy = miny - v, maxy - v
        if -miny>maxy or (-miny==maxy and dy > 0):
            nminy = miny
            nmaxy = miny*(maxy+dy)/(miny+dy)
        else:
            nmaxy = maxy
            nminy = maxy*(miny+dy)/(maxy+dy)
        ax.set_ylim(nminy+v, nmaxy+v)
    
    

    Picture without firstsubplot

    So I've tuned @drevicko's solution a little and got what I wanted.

    def align_yaxis(ax1, v1, ax2, v2):
        """adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
        _, y1 = ax1.transData.transform((0, v1))
        _, y2 = ax2.transData.transform((0, v2))
        adjust_yaxis(ax1,(y2 - y1)/2,v1)
        adjust_yaxis(ax2,(y1 - y2)/2,v2)
    
    def adjust_yaxis(ax,ydif,v):
        """shift axis ax by ydiff, maintaining point v at the same location"""
        inv = ax.transData.inverted()
        _, dy = inv.transform((0, 0)) - inv.transform((0, ydif))
        miny, maxy = ax.get_ylim()
    
        nminy = miny - v + dy - abs(dy)
        nmaxy = maxy - v + dy + abs(dy)
        ax.set_ylim(nminy+v, nmaxy+v)
    

    Subplots as I've expected them to look

提交回复
热议问题