Search code examples
pythonmatplotlibcoordinatesrectanglesdimensions

How to add dimension coordinates instead of regular graphs coordinates in Rectangles in matplotlib.patches.rectangle


enter image description here
I am plotting multiple rectangles using matplotlib.patches.Rectangle. I need to add dimensions for each. Instead of regular X coordinates of graph i need to mention dimensions of each rectangle. In the given picture I need to show x coordinates at the borders not the standard 250, 500, 750..

part1 = matplotlib.patches.Rectangle((ip.iloc[i,7], ip.iloc[i,8]), ip.iloc[i,3], ip.iloc[i,4], color =np.random.rand(3)) 
ax.add_patch(part1)
plt.text(ip.iloc[i,7]+(0.5*ip.iloc[i,4]), (ip.iloc[i,8]+(0.5*ip.iloc[i,3])),ip.iloc[i,0],rotation='vertical', color = 'white',fontsize=8)

Solution

  • CASE 1: SINGLE GROUP

    If you have a ip dataframe with a 'x' and a 'y' columns with the coordinates of your rectangles which belong to a single group, you can set your ticks with ax.set_xticks(ip['x']) and ax.set_yticks(ip['y']). In this way you will get all ticks, except the last one which you have to include with max(ip['x']) + ip.iloc[np.argmax(ip['x']), ip.columns.get_loc('width')] and max(ip['y']) + ip.iloc[np.argmax(ip['y']), ip.columns.get_loc('height')].
    With your ip dataframe, those columns should be the 8th (7 python index) and the 9th (8 python index) ones. Check the code below as a reference.

    Code

    import matplotlib
    import matplotlib.pyplot as plt
    import pandas as pd
    import numpy as np
    
    ip = pd.DataFrame({'x': [0, 260, 520, 0, 260, 520],
                       'y': [0, 0, 0, 120, 120, 120],
                       'width': [260, 260, 230, 260, 260, 230],
                       'height': [120, 120, 120, 130, 130, 130],
                       'text': np.random.randint(0, 1000, 6)})
    
    fig, ax = plt.subplots()
    
    for i in range(len(ip)):
        part1 = matplotlib.patches.Rectangle((ip.iloc[i,0], ip.iloc[i,1]), ip.iloc[i,2], ip.iloc[i,3], color=np.random.rand(3))
        ax.add_patch(part1)
        plt.text(ip.iloc[i,0]+(0.5*ip.iloc[i,2]), (ip.iloc[i,1]+(0.5*ip.iloc[i,3])), ip.iloc[i,4], rotation='vertical', color='white', fontsize=8)
    
    xticks = list(ip['x'])
    xticks.append(max(ip['x']) + ip.iloc[np.argmax(ip['x']), ip.columns.get_loc('width')])
    yticks = list(ip['y'])
    yticks.append(max(ip['y']) + ip.iloc[np.argmax(ip['y']), ip.columns.get_loc('height')])
    ax.set_xticks(xticks)
    ax.set_yticks(yticks)
    
    ax.set_xlim([min(ip['x']), max(ip['x']) + ip.iloc[np.argmax(ip['x']), ip.columns.get_loc('width')]])
    ax.set_ylim([min(ip['y']), max(ip['y']) + ip.iloc[np.argmax(ip['y']), ip.columns.get_loc('height')]])
    plt.show()
    

    Dataframe

         x    y  width  height  text
    0    0    0    260     120   372
    1  260    0    260     120   543
    2  520    0    230     120   174
    3    0  120    260     130   140
    4  260  120    260     130    27
    5  520  120    230     130   800
    

    Result

    enter image description here


    CASE 2: DOUBLE GRUOP

    If you have two different groups of areas, as in your image, one below 300 on y and the other above this threshold, you can duplicate the x axis in order to have the lower ticks on the bottom axis (ax1) and the upper ticks on the top axis (ax2). Check the code below for reference.
    Please, note that here I used a different approach in order to simplify a bit the code and the readability.

    Code

    import matplotlib
    import matplotlib.pyplot as plt
    import pandas as pd
    import numpy as np
    
    ip = pd.DataFrame({'x': [0, 260, 520, 0, 260, 520, 0, 250, 500, 0],
                       'y': [0, 0, 0, 120, 120, 120, 300, 300, 300, 410],
                       'width': [260, 260, 230, 260, 260, 230, 200, 170, 150, 250],
                       'height': [120, 120, 120, 130, 130, 130, 110, 110, 110, 120],
                       'text': np.random.randint(0, 1000, 10)})
    
    fig, ax1 = plt.subplots()
    ax2 = ax1.twiny()
    
    for i in range(len(ip)):
        part1 = matplotlib.patches.Rectangle((ip.iloc[i,0], ip.iloc[i,1]), ip.iloc[i,2], ip.iloc[i,3], color=np.random.rand(3))
        ax1.add_patch(part1)
        ax1.text(ip.iloc[i,0]+(0.5*ip.iloc[i,2]), (ip.iloc[i,1]+(0.5*ip.iloc[i,3])), ip.iloc[i,4], rotation='vertical', color='white', fontsize=8)
    
    lower_ip = ip[ip['y'] < 300]
    upper_ip = ip[ip['y'] >= 300]
    
    xticks1 = list(lower_ip['x'] + lower_ip['width']) # select the right-side limit of each lower area
    xticks1.extend(lower_ip['x'])                     # add the left-side limit of each lower area
    xticks1 = set(xticks1)                            # filter by unique values
    xticks1 = list(xticks1)                           # convert back to list
    xticks1.sort()                                    # sort in increasing order
    
    xticks2 = list(upper_ip['x'] + upper_ip['width']) # select the right-side limit of each upper area
    xticks2.extend(upper_ip['x'])                     # add the left-side limit of each upper area
    xticks2 = set(xticks2)                            # filter by unique values
    xticks2 = list(xticks2)                           # convert back to list
    xticks2.sort()                                    # sort in increasing order
    
    # set equal min value for both axes
    if xticks1[0] > xticks2[0]:
        xticks2.append(xticks1[0])
    elif xticks1[0] < xticks2[0]:
        xticks1.append(xticks2[0])
    
    # set equal max value for both axes
    if xticks1[-1] > xticks2[-1]:
        xticks2.append(xticks1[-1])
    elif xticks1[-1] < xticks2[-1]:
        xticks1.append(xticks2[-1])
    
    # set lower and upper x ticks
    ax1.set_xticks(xticks1)
    ax2.set_xticks(xticks2)
    
    yticks = list(ip['y'] + ip['height']) # select the down-side limit of each area
    yticks.extend(ip['y'])                # add the up-side limit of each area
    yticks = set(yticks)                  # filter by unique values
    yticks = list(yticks)                 # convert back to list
    yticks.sort()                         # sort in increasing order
    ax1.set_yticks(yticks)                # set y ticks
    
    ax1.set_xlim([min(ip['x']), max(ip['x']) + ip.iloc[np.argmax(ip['x']), ip.columns.get_loc('width')]])
    ax1.set_ylim([min(ip['y']), max(ip['y']) + ip.iloc[np.argmax(ip['y']), ip.columns.get_loc('height')]])
    plt.show()
    

    Dataframe

         x    y  width  height  text
    0    0    0    260     120   457
    1  260    0    260     120   217
    2  520    0    230     120   467
    3    0  120    260     130   495
    4  260  120    260     130   941
    5  520  120    230     130   998
    6    0  300    200     110    50
    7  250  300    170     110   623
    8  500  300    150     110   934
    9    0  410    250     120   366
    

    Result

    enter image description here