Search code examples
pythonmatplotlib3dlabelline

How to add labels to 3d plot


I have the following code which generates a 3D plot. I am trying to label the plot lines, but they are not ending up where I expect them.

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np

# Data
names = [
    "A", "B", "C", "D", "E", "F",
    "G", "H", "I", "J", "K", "L"
]
wins = [
    [0, 14, 20, 24, 29, 33, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39],
    [0, 7, 13, 17, 23, 27, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
    [0, 5, 8, 11, 15, 16, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19],
    [0, 7, 11, 17, 20, 25, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29],
    [0, 9, 14, 22, 29, 36, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42],
    [0, 6, 10, 16, 20, 24, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29],
    [0, 7, 13, 20, 26, 31, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34],
    [0, 10, 13, 18, 24, 29, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33],
    [0, 5, 11, 13, 16, 21, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26],
    [0, 12, 15, 18, 21, 25, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
    [0, 9, 11, 12, 17, 20, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26],
    [0, 15, 20, 25, 27, 33, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37],
]
weeks = [f"Week{i+1}" for i in range(18)]  # X-axis categories (nb of weeks)

# Convert names into numerical indices for the Y-axis
x = np.arange(len(weeks))      # X-axis 
y = np.arange(len(names))      # Y-axis 
wins_array = np.array(wins)    # wins accumulated per weeks and per names

# Create a 3D plot
fig = plt.figure(figsize=(18, 18))
ax = fig.add_subplot(111, projection='3d')

# Plot each line and add labels
for i, y_week in enumerate(y):
    ax.plot(x, wins_array[i], zs=y_week, zdir='x', label=weeks[i])
    # Add shaded area below the line
    ax.bar(x, wins_array[i], zs=y_week, zdir='x', alpha=0.1)
    # Add labels directly on the curves
    for j, my_label in enumerate(wins_array[i]):
        ax.text(i, my_label, y_week, f"{my_label}", color="red", fontsize=8, ha="center")

# Customize labels
ax.set_zlabel('Wins')              # Z-axis is wins
ax.set_xticks(y)                   # X-ticks are names
ax.set_xticklabels(names, rotation=45)  # Name labels on X-axis
ax.set_yticks(x)                   # Y-ticks are weeks
ax.set_yticklabels(weeks)          # Week labels on Y-axis

plt.show()

This is the current result:

The result I am trying to achieve is something like this:

How do I fix my code to achieve my desired result?


Solution

  • It is easier to separate the drawing and the text annotation:

    # Plot each line 
    for i, y_week in enumerate(y):
        ax.plot(x, wins_array[i], zs=y_week, zdir='x', label=weeks[i])
        # Add shaded area below the line
        ax.bar(x, wins_array[i], zs=y_week, zdir='x', alpha=0.1)
    
    # Add labels directly on the curves
    for i in y:
        for j, k in zip(x, wins_array[i]):
            ax.text(i, j, k, f"{k}", color="red", fontsize=8, ha="center")
    

    res