0

I have a background picture on my 3d plot. I want to draw Line using stem or Line3D on top of the image. But stem got covered by the image. How to solve this?

# Controls for print and animation
i_Shape3d = 1           # Set Shape of 3d Plot: 0=Bar3d, 1=Line3d
i_print = 0             # Set i_print to 0 or 1 for printing each step.
i_StillOrAnimate = 0    # 0 for still, 1 for animation
i_Language = 1          # 0 for English, 1 for Chinese
i_df_PGKopitian = df_PGKopitian.copy()
if i_StillOrAnimate:
    # Extract not all states from data_AbdProj for Animation
    df_PGKopitian = df_PGKopitian.copy()
else:
    # Extract just 1 state from data_AbdProj for still plot
    li_State = ["PPINANG"]
    i_State = li_State[0]
    df_PGKopitian = df_PGKopitian[
        df_PGKopitian['state'].isin(li_State)]

# Set Up Options of color for Line3D Plot
i_norm = colors.Normalize(
            vmin=min(df_PGKopitian['rating']), 
            vmax=max(df_PGKopitian['rating']))
i_cmap = cm.get_cmap('tab10')
d_Colors = dict([(i, i_cmap(i_norm(i))) 
                for i in set(df_PGKopitian['rating'])])

# Establish the x, y, z coordinates for the 3D Plot:
n_plot = df_PGKopitian['name']
c_plot = df_PGKopitian['city']
s_plot = df_PGKopitian['state']
x_plot = df_PGKopitian['longitude']
y_plot = df_PGKopitian['latitude']
z_plot = df_PGKopitian['rating']
p_plot = df_PGKopitian['photo']

# Setting the view point range
m_elev, m_azim, m_rota = 40, 270, 0
d_elev, d_azim, d_rota = 0, 0, 0
d_elev, d_azim, d_rota = 10, 10, 10

# Time Array
n_Time = len(df_PGKopitian)
r_theta = int(np.ceil(n_Time / 1))    # Establish number of cycle
i_theta = np.linspace(0, 2*np.pi, 
                      int(np.ceil(n_Time/r_theta)), 
                      endpoint=False) # Establish a single cycle
theta = np.empty(0)             
for i in range(r_theta): 
    theta = np.append(theta, i_theta) # Setting up cycles
x_theta = np.cos(theta)               # Convert to x-axis
y_theta = np.sin(theta)               # Convert to y-axis
SuffixMap_theta = 4 / n_Time          # for 4 Images

# Annotation Preparations
Ann_bbox = dict(boxstyle ="round", fc ="0.8")
Ann_arrowprops = {'arrowstyle': '-|>',
     'connectionstyle': 'angle, angleA = 0, angleB = 90, rad = 10'}

# Create the 3D scatter plot using the Axes3D() function:
fig = plt.figure(figsize=plt.figaspect(1.))
ax = fig.add_subplot(111, projection='3d')

# Plot the color bar for z-axis
c_map = cm.ScalarMappable(cmap='tab10')
c_map.set_array([min(z_plot),max(z_plot)])
c_map.set_clim(vmin=min(z_plot),vmax=max(z_plot))
clb = fig.colorbar(c_map, cax = fig.add_axes([0.85, 0.37, 0.03, 0.5]))
clb_Label =    ('Rating' if not(i_Language) else '星级\n(Rating)')
clb_Labely =   (1.1 if not(i_Language)     else 1.15)
clb.set_label(clb_Label, 
              labelpad=-15, 
              y=clb_Labely, rotation=0)

def animate_func(num):
#num=3
    # Establish the current State
    i_State = df_PGKopitian['state'].iloc[num]
    j_PGKopitiam = df_PGKopitian[df_PGKopitian['state']
                                           == i_State]
    
    # Establish the suffix of Map
    # i_SuffixMap = 0
    i_SuffixMap = int(np.ceil((num+1) * SuffixMap_theta))
    df_ImageSfx = df_Images[df_Images['SUFFIX'] == i_SuffixMap]
    
    # print datetime of each animation frame
    print(f"{datetime.datetime.now()}: " +
          " ".join('' for i in range(len(str(n_Time))-len(str(num+1))+1)) +
          f"#{num+1}/{n_Time}; {i_State}")
    
    # Load the image using the mpimg.imread() function:
    if i_print: print(f"{num}: Load image by mpimg.imread")
    imgMap = mpimg.imread(f"{FDir_MapImages}map_" + 
                          f"{i_State}" + 
                          f"{i_SuffixMap:02}" +
                          f"_rotated.png", 
                          format='png')
    
    # Construct matrix of pixels based on the image
    if i_print: print(f"{num}: Construct matrix of pixels")
    scale_grids = 1.
    imgMap_xgrids = np.outer(
        np.linspace(df_ImageSfx.at[i_State,'IMAGE LONGITUDE LEFT, x1'], 
                    df_ImageSfx.at[i_State,'IMAGE LONGITUDE RIGHT, x2'], 
                    int(imgMap.shape[0]*scale_grids)), 
        np.ones(1)          )
    imgMap_ygrids = np.outer(
        np.ones(1), 
        np.linspace(df_ImageSfx.at[i_State,'IMAGE LATITUDE BOTTOM, y1'], 
                    df_ImageSfx.at[i_State,'IMAGE LATITUDE TOP, y2'], 
                    int(imgMap.shape[1]*scale_grids))
                            )
    imgMap_zgrids = np.outer(
        np.linspace(0, 0, int(imgMap.shape[0]*scale_grids)), 
        np.linspace(0, 0, int(imgMap.shape[1]*scale_grids)))

    # Clear up the Figure
    ax.clear() # Clears the figure to update the line, points,
               # title, and axes

    # Adding Figure Labels
    if i_print: print(f"{num}: Adding Figure Labels")
    if not(i_Language):
        Title_Plot = (i_State + "\nPenang Kopitiam & Cafes" 
                         + "\n(4-Star Rating & Above)")
        Title_Fontsize = 14
        Title_Pad = '-30'
    else:
        # Set default font
        rcParams['font.sans-serif'] = ['SimHei'] 
        rcParams['font.family'] ='sans-serif'
        # solve the problem that minus sign '-' is displayed as square
        rcParams['axes.unicode_minus'] = False 

        Title_Plot = (f"{df_ImageSfx.at[i_State, '中文']}茶餐室" +
                      f"\n(4星级以上)")
        Title_Fontsize = 15
        Title_Pad = '-20'
    ax.set_title(Title_Plot,
                 loc='center', pad = Title_Pad, 
                 fontsize=Title_Fontsize, fontweight = 15,
                 color = d_Colors.get(z_plot.iloc[num]),
                 rotation = m_rota)
    ax.set_xlabel('Longitude')
    ax.set_ylabel('Latitude')
    
    # Turn off the Z-Axis Markers & Tickers
    if i_print: print(f"{num}: Turn off Z-Axis Markers & Tickers")
    ax.zaxis.clear()
    ax.set_zticks([])
    ax.xaxis.clear()
    ax.set_xticks([])
    ax.yaxis.clear()
    ax.set_yticks([])

    # Adjust the 3D scaling
    if i_print: print(f"{num}: Adjust the 3D scaling")
    m = df_ImageSfx.at[
        i_State,'IMAGE ASPECT RATIO, (y2-y1)/(x2-x1)']
    ax.set_box_aspect(((1/m if m > 1 else 1),
                       (m if m < 1 else 1),
                       1))
    ax.set_proj_type('persp')

    # Setting the view point
    if i_print: print(f"{num}: Setting the view point")
    ax.view_init(azim=m_azim+d_azim*x_theta[num-1],
                 elev=m_elev+d_elev*y_theta[num-1])

    # Plot the Image on the base of the 3D plot
    if i_print: print(f"{num}: Plot the Image on the base of the 3D plot")
    ax.plot_surface(imgMap_xgrids, imgMap_ygrids, imgMap_zgrids, 
                    facecolors=imgMap, rstride=5, cstride=5,
                    cmap='gist_ncar')
    
    # Finding the row of starting & ending datapoints, 
    # which are of the same state as the current "num"
    if i_print: print(f"{num}: Find the row of start & end " + 
                      f"of datapoints which are of the same state")
    for i_row in range(len(s_plot)):
        if s_plot.iloc[i_row] == i_State:
            if ((i_row and s_plot.iloc[i_row-1] != i_State) 
                or not(i_row)):      
                r_Stt_STATE = i_row
            if ((i_row<len(s_plot)-1 and s_plot.iloc[i_row+1] != i_State) 
                or i_row==len(s_plot)-1): 
                r_End_STATE = i_row
    #print(f"{i_State} has {len(s.iloc[r_Stt_STATE:r_End_STATE+1])} rows of data")
    
    # Adjust the xlim3d, ylim3d & box_aspect based on the datapoints
    if i_print: print(f"{num}: Adjust the xlim3d, ylim3d & box_aspect")
    xlim3d_min = (min(x_plot.iloc[r_Stt_STATE:r_End_STATE+1]) 
                       + df_ImageSfx.at[i_State,'IMAGE LONGITUDE LEFT, x1'])/2
    xlim3d_max = (max(x_plot.iloc[r_Stt_STATE:r_End_STATE+1]) 
                       + df_ImageSfx.at[i_State,'IMAGE LONGITUDE RIGHT, x2'])/2
    ylim3d_min = (min(y_plot.iloc[r_Stt_STATE:r_End_STATE+1]) 
                       + df_ImageSfx.at[i_State,'IMAGE LATITUDE BOTTOM, y1'])/2
    ylim3d_max = (max(y_plot.iloc[r_Stt_STATE:r_End_STATE+1]) 
                       + df_ImageSfx.at[i_State,'IMAGE LATITUDE TOP, y2'])/2
    zlim3d_max = max(z_plot.iloc[r_Stt_STATE:r_End_STATE+1])
    slope_lim3d = (ylim3d_max - ylim3d_min) / (xlim3d_max - xlim3d_min)
    ax.set_xlim3d(xlim3d_min, xlim3d_max)
    ax.set_ylim3d(ylim3d_min, ylim3d_max)
    ax.set_zlim3d(0,          zlim3d_max)
    ax.set_box_aspect((
        (1/slope_lim3d if slope_lim3d > 1 else 1),
        (  slope_lim3d if slope_lim3d < 1 else 1), 0.2))
    
    # Plot the hotspots by bar3D
    if i_Shape3d==0 and False:
        if i_print: print(f"{num}: Plot the hotspots by bar3D")
        xpos_bar3D = x_plot.iloc[r_Stt_STATE:num+1]
        ypos_bar3D = y_plot.iloc[r_Stt_STATE:num+1]
        zpos_bar3D = np.ones_like(xpos_bar3D.shape)
        dx_bar3D   = dy_bar3D = np.ones_like(zpos_bar3D) * (0.05 * 
            (df_ImageSfx.at[i_State,'IMAGE LONGITUDE RIGHT, x2'] - 
             df_ImageSfx.at[i_State,'IMAGE LONGITUDE LEFT, x1']) / 
            (xlim3d_max - xlim3d_min))
        dz_bar3D   = z_plot.iloc[r_Stt_STATE:num+1]
        co_bar3D   = dz_bar3D.map(d_Colors)
        xpos_bar3D -= dx_bar3D/2
        ypos_bar3D -= dx_bar3D/2
        
        ax.bar3d(xpos_bar3D,       # Datapoints base's x-positions
                 ypos_bar3D,       # Datapoints base's y-positions
                 zpos_bar3D,       # Datapoints base's z-positions
                 dx_bar3D,         # Shape of bar in x-direction
                 dy_bar3D,         # Shape of bar in y-direction
                 dz_bar3D,         # Shape of bar in z-direction
                 color = co_bar3D, # calors of bar according to z data
                 shade = True,
                 zsort='average')
    
    for ni, ci, xi, yi, zi, pi in zip(n_plot.iloc[r_Stt_STATE:num+1], 
                                      c_plot.iloc[r_Stt_STATE:num+1], 
                                      x_plot.iloc[r_Stt_STATE:num+1], 
                                      y_plot.iloc[r_Stt_STATE:num+1], 
                                      z_plot.iloc[r_Stt_STATE:num+1],
                                      p_plot.iloc[r_Stt_STATE:num+1]):

        # Plot the hotspots by bar3D
        if i_Shape3d==0 and False:
            if i_print: print(f"{num}: Plot the hotspots by bar3D")
            dx_bar3D = dy_bar3D = 0.05
            #dx_bar3D = dy_bar3D = (0.05 * 
            #    (xlim3d_max - xlim3d_min) / 
            #    (df_Images.at[i_State,'x2'] - df_Images.at[i_State,'x1']))
            dz_bar3D = zi - 0
            xpos_bar3D = xi - dx_bar3D/2
            ypos_bar3D = yi - dy_bar3D/2
            zpos_bar3D = 0
            
            ax.bar3d(xpos_bar3D,        # Datapoints base's x-positions
                     ypos_bar3D,        # Datapoints base's y-positions
                     zpos_bar3D,        # Datapoints base's z-positions
                     dx_bar3D,          # Shape of bar in x-direction
                     dy_bar3D,          # Shape of bar in y-direction
                     dz_bar3D,          # Shape of bar in z-direction
                     color = d_Colors.get(zi), # calors of bar 
                                               # according to z data
                     shade = True,
                     zsort='average')
        
        # Plot the hotspots by Line3D
        elif i_Shape3d==1 and True:
            if i_print: print(f"{num}: Plot the hotspots by Line3D")
            if i_print: print(f"   {ni}; {xi}; {yi}; {zi}; {d_Colors.get(zi)}")
            line = art3d.Line3D(*zip((xi, yi, 0), (xi, yi, zi)), 
                                c= d_Colors.get(zi), 
                                marker='o', markevery=(1, 1),
                                ls = '-', lw = 1.)
            ax.add_line(line)
            ax.stem([xi], [yi], [zi], 
                    markerfmt='o', bottom=0)

        # Annotation
        if i_Shape3d in [0, 1] and ni == n_plot.iloc[-1]:
            xp, yp, _ = proj3d.proj_transform(xi, yi, zi, 
                                              ax.get_proj())
            Ann_offset = (15, 15+165*(0.035-yp)/0.115)
            if i_print: print(f"   xp: {xp}; yp: {yp}")

            if not(i_Language):

                n_txt_Annotate = ni.split(" ")

                txt_Annotate = f"{ni}\n{ci}\n{zi}star"
                xp_rightLimit = 0.03
            else:
                txt_Annotate = f"{ni}\n{ci}\n{zi}星级"
                xp_rightLimit = 0.035

            Insert the name of the Kopitiam into the Annotation BBox
            ab_text = AnnotationBbox(
                        TextArea(txt_Annotate), 
                        xy=(xp, yp),
                        xybox=(Ann_offset[0] * 
                               (1 if xp < xp_rightLimit else -1), 
                               Ann_offset[1]),
                        xycoords='data',
                        boxcoords="offset points",
                        box_alignment = ((0 if xp < xp_rightLimit else 1), 
                                         0.5),
                        pad=0.5,
                        bboxprops=Ann_bbox,
                        arrowprops=Ann_arrowprops
                                     )
            ax.add_artist(ab_text)
    
    Test_Annotation = 0
    if Test_Annotation == 1:
        # y_min = -0.08; y_max = 0.035
        # offsets total: xx, 15+165/0.115
        p_xy = ((-0.08, -0.08), (-0.04, -0.08), (0.03, 0.035))
        p_ff = ((15, 15), (15, 180), (15, 15))
        for i in range(len(p_xy)):
            ax.annotate(f"{p_xy[i][0]:.2f},\n{p_xy[i][1]:.2f}", 
                        xy=(p_xy[i][0], p_xy[i][1]), 
                        xytext =(p_ff[i][0] * (1 if p_xy[i][0]<0.03 else -1), 
                                 p_ff[i][1]),
                        ha=("left" if p_xy[i][0]<0.03 else "right"),
                        textcoords = 'offset points',
                        bbox = Ann_bbox, 
                        arrowprops = Ann_arrowprops
                       )

Stems being covered by the background image

I've tried looking at the imshow, plotsurface. Searching through the website. But no one seems to have a similar problem like I did.

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
  • You can set `ax.computed_zorder=False` and manually set the order for the graphical elements. See e.g. https://stackoverflow.com/questions/37611023/3d-parametric-curve-in-matplotlib-does-not-respect-zorder-workaround – JohanC Mar 11 '23 at 10:54

0 Answers0