Matplotlib 2D bar with scatter not lining up as expected

Viewed 150

I am trying to create a 2D bar chart similar to what shown in the docs.

In the code below, I expected the scatter points to be at the bottom of each bar, but it looks like it is plotting everything along y=0. I also expected the tops of the bars to vary/step, not the bottoms.

(And I also want to have the bars be the same color as the scatters, but I haven't started on that problem yet.)

x = np.arange(25)
y = -x
z = x/15
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.bar(x, y, z, zdir='y', alpha=0.8)
ax.scatter(x, y, s=45, c=z, cmap='viridis')
ax.set(ylabel='y')
ax.set(xlabel='x')

![enter image description here

1 Answers

Have a look at the code below:

import numpy as np
import matplotlib
import matplotlib.pyplot as plt

# Data generation
# I changed y=-x to y=x. In the former case,
# the bottoms of the bars varied. This is because
# you plot them with negative values! So, the bars
# are being drawn from the x,y-plane at zero to an
# x,y-plane BELOW the zero plane. If you use y=x 
# instead, the bars are drawn from the x,y-plan at
# zero to an x,y-plane ABOVE zero!
x = np.arange(25)
y = x
z = x/15

# Initiate figure and axes
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

# Generate the viridis color map with as many color
# values as the size of the x-array (or the y-array)
cmap = matplotlib.cm.get_cmap('viridis')
colors = np.zeros((x.size, 4))

for i in range(x.size):
    colors[i,:] = cmap(i / x.size)
    
# Loop over the different x- and y-values. Plot each
# combination separately with a zs value equal to the
# current y-value. Use the iterator 'i' to extract the
# proper color from the 'colors' matrix
for i, (xval, yval) in enumerate(zip(x, y)):
    # Even though we only have one x-value and one y-value,
    # these must be arrays (otherwise Axes.bar throws and
    # error)!
    ax.bar([xval], [yval], zs=yval, zdir='y', color=colors[i], alpha=0.8)

ax.scatter(x, y, s=45, c=z, cmap='viridis')

ax.set(ylabel='y')
ax.set(xlabel='x')

plt.show()

To start with, the bottoms of the bars vary/step because the values in your y array are negative! This means that the bars are being drawn for the x,y-plane at z=0 to an x,y-plane at a negative z-value. So, this behaviour is as expected. If you want the bars to vary/step at the top, you should change the line y = -x to y = x.

The issue regarding your scatter points is also solved by changing the y array to be positive, since the bars will then point 'upwards' while originating from the x,y-plane at z=0.

For the colors of the bars, we first store the viridis colormap (or any other colormap you want to use) in a variable called cmap. Then, we allocate a color matrix with as many rows as you have data points and four columns (r, g, b, a). Using a for loop, we obtain the colors from the aforementioned colormap. Note that you need to supply a normalized value to the colormap (i.e. a value in the range [0, 1]) in order to get a color. For this normalized value, we simply use the value of the iteration variable divided by the total size of your data. We use the color in the call to Axes.bar.

If I run the code above, I get the following output: Output plot

Just as a side node, if you want to plot the scatter points at the top of the bars, you need to supply a value for the z-value of the scatter points (in this case, that value would simply equal to value in your y-array). Currently, you plot the scatter points in 2D (as you only supply x- and y-values, all scatter points are located at the x,y-plane at z=0). Say we would change the call to Axes.scatter to:

# For a 3D situation, you need to give three dimensions to the Axes.scatter 
# function in order for it to plot in 3D (previously, you only had 'x, y').
# Since the z-value should equal the y-value in this case, we now have 
# 'x, y, y'.
ax.scatter(x, y, y, s=45, c=z, cmap='viridis')

After this change, the output looks as follows:

Output plot for which ax.scatter was varied

Related