Python >> Tutoriel Python >  >> Python Tag >> Matplotlib

Dessinez des lignes courbes pour connecter des points dans matplotlib

Voici une approche utilisant les courbes de Bézier.

La séquence [...., i-indent, i, i + 0.8, ...] mettra des points de contrôle à chaque position entière i et un peu d'espace avant et après. Le graphique ci-dessous utilise indent=0.8; indent=0 créerait des lignes droites; avec indent>1 les courbes se croiseraient davantage. D'autres variations rendront les courbes plus ou moins "cornées".

import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches
import numpy as np

n_teams = 4
n_weeks = 4
t = np.array([[1, 2, 4, 3],
              [4, 3, 3, 2],
              [3, 4, 1, 4],
              [2, 1, 2, 1]])
fig, ax = plt.subplots(figsize=(10, 4), facecolor='#1b1b1b')
ax.set_facecolor('#1b1b1b')

indent = 0.8
for tj in t:
    ax.scatter(np.arange(len(tj)), tj, marker='o', color='#4F535C', s=100, zorder=3)
    # create bezier curves
    verts = [(i + d, tij) for i, tij in enumerate(tj) for d in (-indent, 0, indent)][1:-1]
    codes = [Path.MOVETO] + [Path.CURVE4] * (len(verts) - 1)
    path = Path(verts, codes)
    patch = patches.PathPatch(path, facecolor='none', lw=2, edgecolor='#4F535C')
    ax.add_patch(patch)
ax.set_xticks([])
ax.set_yticks([])
ax.autoscale() # sets the xlim and ylim for the added patches
plt.show()

Une version colorée pourrait ressembler à :

colors = ['crimson', 'skyblue', 'lime', 'gold']
for tj, color in zip(t, colors):
    ax.scatter(np.arange(len(tj)), tj, marker='o', color=color, s=100, zorder=3)
    verts = [(i + d, tij) for i, tij in enumerate(tj) for d in (-indent, 0, indent)][1:-1]
    codes = [Path.MOVETO] + [Path.CURVE4] * (len(verts) - 1)
    path = Path(verts, codes)
    patch = patches.PathPatch(path, facecolor='none', lw=2, edgecolor=color)
    ax.add_patch(patch)

Le graphique suivant compare différentes valeurs pour indent :


Vous pouvez le faire en personnalisant le connectionstyle argument de FancyArrowPatch . La documentation n'explique pas fraction et angle de bar eh bien, je les dessine par énumération.

import matplotlib.pyplot as plt


x1, y1 = 0.3, 0.2
x2, y2 = 0.8, 0.6


fig, axs = plt.subplots(2, 2)

axs[0, 0].plot([x1, x2], [y2, y1], ".")
axs[0, 0].annotate("",
            xy=(x1, y2), xycoords='data',
            xytext=(x2, y1), textcoords='data',
            arrowprops=dict(arrowstyle="-", color="0.5",
                            connectionstyle="bar,angle=180,fraction=-0.3",
                            ),
            )


axs[0, 1].plot([x1, x2], [y1, y2], ".")
axs[0, 1].annotate("",
            xy=(x1, y1), xycoords='data',
            xytext=(x2, y2), textcoords='data',
            arrowprops=dict(arrowstyle="-", color="0.5",
                            connectionstyle="bar,angle=180,fraction=-0.3",
                            ),
            )

axs[1, 0].plot([x1, x2], [y2, y1], ".")
axs[1, 0].annotate("",
            xy=(x1, y2), xycoords='data',
            xytext=(x2, y1), textcoords='data',
            arrowprops=dict(arrowstyle="-", color="0.5",
                            connectionstyle="bar,angle=90,fraction=-0.3",
                            ),
            )

axs[1, 1].plot([x1, x2], [y1, y2], ".")
axs[1, 1].annotate("",
            xy=(x1, y1), xycoords='data',
            xytext=(x2, y2), textcoords='data',
            arrowprops=dict(arrowstyle="-", color="0.5",
                            connectionstyle="bar,angle=270,fraction=-0.3",
                            ),
            )


for ax in axs.flat:
    ax.set(xlim=(0, 1), ylim=(0, 1), aspect=1)

fig.tight_layout(pad=0.2)

plt.show()

Annotations - Annotation avec une flèche et une démonstration de style de connexion pour référence.

Revenons à votre problème. J'utilise deux if pour juger de la position de (x1, y1) et (x2, y2) pour vérifier quel connectionstyle ils devraient utiliser.

import matplotlib.pyplot as plt
import numpy as np

n_teams = 4
n_weeks = 4

fig, ax = plt.subplots(figsize=(6,6))

t = np.array([
    [1, 2, 4, 3],
    [4, 3, 3, 2],
    [3, 4, 1, 4],
    [2, 1, 2, 1]
])

fig.patch.set_facecolor('#1b1b1b')

for nw in range(n_weeks):
    ax.scatter([nw] * n_weeks, t[:, nw], marker='o', color='#4F535C', s=100, zorder=2)
    
ax.axis('off')
    
for team in t:
    x1, x2 = 0, 1
    
    for rank in range(0, len(team) - 1):
        y1 = n_weeks - team[rank] + 1
        y2 = n_weeks - team[rank + 1] + 1

        if (x1 < x2 and y1 > y2):
            ax.annotate("",
                    xy=(x1, y1), xycoords='data',
                    xytext=(x2, y2), textcoords='data',
                    arrowprops=dict(arrowstyle="-", color="0.5",
                                    connectionstyle="bar,angle=180,fraction=-0.2",
                                    ),
                    )

        if (x1 < x2 and y1 < y2):
            ax.annotate("",
                        xy=(x1, y1), xycoords='data',
                        xytext=(x2, y2), textcoords='data',
                        arrowprops=dict(arrowstyle="-", color="0.5",
                                        connectionstyle="bar,angle=270,fraction=-0.4",
                                        ),
                        )

        x1 += 1
        x2 += 1

plt.show()

Voici un exemple d'énumération :

import matplotlib.pyplot as plt


x1, y1 = 0.3, 0.3
x2, y2 = 0.6, 0.6

fig, axs = plt.subplots(5, 5)

angle = 0

for ax in axs.flat:
    ax.plot([x1, x2], [y1, y2], ".")
    ax.annotate("",
                xy=(x1, y1), xycoords='data',
                xytext=(x2, y2), textcoords='data',
                arrowprops=dict(arrowstyle="-", color="0.5",
                                connectionstyle=f"bar,angle={angle},fraction=-0.3",
                                ),
                )
    ax.set_title(angle)
    angle += 15
    ax.set(xlim=(0, 1), ylim=(0, 1), aspect=1)

fig.tight_layout(pad=0.2)

plt.show()