简体   繁体   中英

How to organize the position of the legend, colorbar and image in the plot?

I'm trying to plot some data, and I don't like the organization of the items. For example, I would like to have a bigger image and a smaller colorbar. When I modify the size of the figure still not proportional. And I also would like to tag each borehole to the legend, so I can identify it.

This is the image I have now: 在此处输入图像描述

and this is the code:

# Create data
l = [2, 3, 4, 5,6]
n = 20
labels = [item for item in l for i in range(n)]
random.shuffle(labels,random.random)
labels =np.array(labels)
label_unique = np.unique(labels)

n = 100
x = np.linspace(613000, 615000, num=n) + np.random.uniform(-5, 5, size=n)
y = np.linspace(7763800, 7765800, num=n) + np.random.uniform(-5, 5, size=n)
z = np.linspace(1230, 1260, num=n) + np.random.uniform(-5, 5, size=n)
cpt1 = pd.DataFrame(list(zip(x, y, z,labels)),
              columns=['x','y', 'z','labels'])

l = [2, 3, 4, 5,6]
n = 60
labels = [item for item in l for i in range(n)]
random.shuffle(labels,random.random)
labels =np.array(labels)
label_unique = np.unique(labels)


cpt2 = pd.DataFrame(list(zip(x, y, z,labels)),
              columns=['x','y', 'z','labels'])

n = 400
x = np.linspace(613000, 615000, num=n) + np.random.uniform(-7, 7, size=n)
y = np.linspace(7763800, 7765800, num=n) + np.random.uniform(-7, 7, size=n)
z = np.linspace(1230, 1260, num=n) + np.random.uniform(-7, 7, size=n)
l = [2, 3, 4, 5,6]
n = 80
labels = [item for item in l for i in range(n)]
random.shuffle(labels,random.random)
labels =np.array(labels)
label_unique = np.unique(labels)

cpt3 = pd.DataFrame(list(zip(x, y, z,labels)),
              columns=['x','y', 'z','labels'])

cpt = [cpt1,cpt2,cpt3]

legend = cpt1.columns.values.tolist()


fig = plt.figure(figsize = (20, 9))
ax = plt.axes(projection ="3d")

# Add x, y gridlines
ax.grid(b = True, color ='grey',
        linestyle ='-.', linewidth = 0.3,
        alpha = 0.2)


# Creating color map
my_cmap = plt.get_cmap('hsv')
for  count, c in enumerate(cpt):
    x = c.x
    y = c.y
    z = c.z
    colorz = c.labels



    # Creating plot
    sctt = ax.scatter3D(x, y, z,
                        alpha = 0.8,
                        c = colorz,
                        cmap = my_cmap,
                        marker ='^',label = legend[count])

ax.set_xlabel('X-axis', fontweight ='bold')
ax.set_ylabel('Y-axis', fontweight ='bold')
ax.set_zlabel('Z-axis', fontweight ='bold')
fig.colorbar(sctt, ax = ax, shrink = 0.3, aspect = 5,orientation="horizontal")
plt.legend(bbox_to_anchor=(1.5,1), loc="upper left")
plt.show()

Two parts to the question, and at least three parts to this answer.

Setting up the imports and the synthetic data. Whenever you find yourself retyping or copy/pasting a complicated line with different parameters, you should probably make a function for it. It's okay for functions to be small.



from matplotlib import pyplot as plt
import pandas as pd
import numpy as np

# Fake data of about the right shape
def syncoords(pars, n):
    '''pars: tuple or list of min, max, abs_variance'''
    return np.linspace(pars[0], pars[1], num=n) + \
        np.random.uniform(-1*pars[2], pars[2], size=n)

def synbore(n, xparams, yparams, zparams, zonevalues):
    '''create n entries for x,y,z, and zone from parameter tuples
       xyzparams: tuple of min, max, abs_variance
       zonevalues: list of zone values'''
    return pd.DataFrame({'x': syncoords(xparams, n),
                         'y': syncoords(yparams, n),
                         'z': syncoords(zparams, n),
                         'Zone': np.random.choice(zonevalues, size=n)})


boreparams = [['melaza', 10,
               (61300, 61500, 5), (77638, 77658, 5), (5023, 5400, .5),
               [2,3,4,5,6]],
              ['miel',   23,
               (45000, 45555, 5), (69712, 68800, 5), (4701, 5100, .7),
               [2,3,4,5,6]],
              ['jalea',  50,
               (50432, 50000, 6), (38200, 38600, 6), (5050, 5600, .9),
               [4,5,6,7,8]]] 

Here I changed from your list of dataframes because I always want my data to "travel with" its ID strings. When I have two lists, it's so easy for edits and updates to get them out of order with each other. I like dictionaries:


# I like my data to travel with its ID, which dictionaries are great for. 
# boredict entries: {"ID": xyzZone_dataframe}
# easy to make a dict from a list of (k, v) pairs, 
# so a lambda function to do that:
boredict = dict(map(lambda l:(l[0],
                              synbore(l[1],l[2],l[3],l[4],l[5])),
                    boreparams))

# Get ready to plot
fig = plt.figure(figsize=(11, 8.5)) # Or A? papersize
ax = plt.axes(projection ="3d")
ax.set_xlabel('X-axis', fontweight ='bold')
ax.set_ylabel('Y-axis', fontweight ='bold')
ax.set_zlabel('Z-axis', fontweight ='bold')
ax.grid(b = True, color ='grey',
        linestyle ='-.', linewidth = 0.3,
        alpha = 0.2)

# TODO: collect the max-min of all the Zones so one colormap works for all
# see https://matplotlib.org/stable/tutorials/colors/colormapnorms.html
# and https://matplotlib.org/stable/tutorials/colors/colorbar_only.html

for bname in boredict:
    # plot the actual bore data in 3D+colormap
    bdata = boredict[bname]
    sctt = ax.scatter3D(bdata.x, bdata.y, bdata.z,
                        alpha = 0.8,
                        c = bdata.Zone,
                        cmap = plt.get_cmap('hsv'),
                        marker ='^')
    # and a star to match the bore with the legend 
    ax.scatter3D(bdata.x[-1:], bdata.y[-1:], bdata.z[-1:] + 25,
                 marker = 'v',
                 s = 80, 
                 label = bname) 

And the actual plot layout management. 3D plots need a lot of whitespace for the corners to rotate in, but you can trim padding off the colorbar ( pad = 0 ) and off the figure itself, using subplots_adjust . I liked a bigger but skinnier colorbar, too.


fig.colorbar(sctt, ax = ax,
             shrink = 0.4, aspect = 16, pad = 0, 
             orientation="horizontal")

plt.legend(bbox_to_anchor=(1.1, .8), loc="upper left")
fig.subplots_adjust(left=0, right=1,bottom=0,top=1) #reduce whitespace around the fig
plt.show()

用颜色编码的钻孔数据的 3D 散点图,每个钻孔顶部都有星星,以匹配图例条目。

There's one more thing this plot is going to need -- Here we create a colorbar based on the last dataframe to be plotted in the loop, and only that dataframe. But maybe the dataframes have different Zone data ranges. We want a colorbar that applies accurately to all the data at once, That means looking at all the data twice. once to figure out what the colorbar ranges will be and then again to plot them all with the overall colorbar, I put a #TODO comment in the code where you'd do this. with links to the previous questions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM