2016-10-13 2 views
0

Diese Frage ist eine Folge zu this. Wie kann ich die Anmerkungen so gestalten, dass sie immer noch lesbar sind, wenn die markierten Punkte genau oder nahezu deckungsgleich sind? Ich brauche eine programmatische Lösung, Hand-Tuning der Offsets ist keine Option. Probe mit hässlichen Etiketten:Pyplot Label Scatter Plot mit übereinstimmenden Punkten/überlappenden Annotationen

import numpy as np 
import matplotlib.pyplot as plt 

np.random.seed(0) 
N = 10 
data = np.random.random((N, 4)) 
data[1, :2] = data[0, :2] 
data[-1, :2] = data[-2, :2] + .01 
labels = ['point{0}'.format(i) for i in range(N)] 
plt.subplots_adjust(bottom = 0.1) 
plt.scatter(
    data[:, 0], data[:, 1], marker = 'o', c = data[:, 2], s = data[:, 3]*1500, 
    cmap = plt.get_cmap('Spectral')) 
for label, x, y in zip(labels, data[:, 0], data[:, 1]): 
    plt.annotate(
     label, 
     xy = (x, y), xytext = (-20, 20), 
     textcoords = 'offset points', ha = 'right', va = 'bottom', 
     bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5), 
     arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0')) 

plt.show() 

enter image description here

+0

Ich bin mir ziemlich sicher, dass matplotlib wird dies automatisch für Sie nicht tun. Sie müssen die Winkel für die Beschriftungen iterativ so bestimmen, dass sie sich nicht schneiden. Das ist eine schwierige Aufgabe, zumal es keine Garantie für eine gültige Lösung gibt. Wenn ein Punkt von anderen Punkten umgeben ist, können Sie sein Label nicht wirklich innerhalb eines bestimmten Radius platzieren. Ich glaube, du musst zuerst eine Heuristik erstellen, die du dann implementieren kannst. –

Antwort

1

den Abstand zwischen dem letzten Punkt nehmen, stellen einen Schwellenwert halten und dann drehen die x, y Text entsprechend. Siehe unten.

import numpy as np 
import matplotlib.pyplot as plt 

np.random.seed(0) 
N = 10 
data = np.random.random((N, 4)) 
data[1, :2] = data[0, :2] 
data[-1, :2] = data[-2, :2] + .01 
labels = ['point{0}'.format(i) for i in range(N)] 
plt.subplots_adjust(bottom = 0.1) 
plt.scatter(
    data[:, 0], data[:, 1], marker = 'o', c = data[:, 2], s = data[:, 3]*1500, 
    cmap = plt.get_cmap('Spectral')) 

old_x = old_y = 1e9 # make an impossibly large initial offset 
thresh = .1 #make a distance threshold 

for label, x, y in zip(labels, data[:, 0], data[:, 1]): 
    #calculate distance 
    d = ((x-old_x)**2+(y-old_y)**2)**(.5) 

    #if distance less than thresh then flip the arrow 
    flip = 1 
    if d < .1: flip=-2 

    plt.annotate(
     label, 
     xy = (x, y), xytext = (-20*flip, 20*flip), 
     textcoords = 'offset points', ha = 'right', va = 'bottom', 
     bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5), 
     arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0')) 
    old_x = x 
    old_y = y 

plt.show() 

die Ergebnisse:

enter image description here

+0

Ich denke, das ist definitiv in die richtige Richtung. Ihr Test auf Nähe funktioniert nur, wenn die nahegelegenen Punkte im Datenfeld aufeinander folgen. Zum Beispiel ändern Sie Daten [1,: 2] = Daten [0,: 2] zu Daten [5,: 2] = Daten [0,: 2]. Diese Lösung muss nicht kugelsicher sein, wenn es 10 Punkte übereinander gibt, wird es wie eine heiße Sauerei aussehen, egal was passiert. – user2133814

+0

Ich arbeite an einer Lösung mit scipy räumlichen KDTree – user2133814

0

Hier ist, was ich mit endete. Nicht perfekt für alle Situationen, und es funktioniert nicht einmal reibungslos für dieses Beispielproblem, aber ich denke, es ist gut genug für meine Bedürfnisse. Danke Dan für deine Antwort, die mich in die richtige Richtung weist.

import numpy as np 
import matplotlib.pyplot as plt 
from scipy.spatial import cKDTree 


def get_label_xy(tree, thresh, data, i): 
    neighbors = tree.query_ball_point([data[i, 0], data[i, 1]], thresh) 
    if len(neighbors) == 1: 
     xy = (-30, 30) 
    else: 
     mean = np.mean(data[:, :2][neighbors], axis=0) 

     if mean[0] == data[i, 0] and mean[1] == data[i, 1]: 
      if i < np.max(neighbors): 
       xy = (-30, 30) 
      else: 
       xy = (30, -30) 
     else: 
      angle = np.arctan2(data[i, 1] - mean[1], data[i, 0] - mean[0]) 

      if angle > np.pi/2: 
       xy = (-30, 30) 
      elif angle > 0: 
       xy = (30, 30) 
      elif angle > -np.pi/2: 
       xy = (30, -30) 
      else: 
       xy = (-30, -30) 
    return xy 


def labeled_scatter_plot(data, labels): 
    plt.subplots_adjust(bottom = 0.1) 
    plt.scatter(
     data[:, 0], data[:, 1], marker = 'o', c = data[:, 2], s = data[:, 3]*1500, 
     cmap = plt.get_cmap('Spectral')) 

    tree = cKDTree(data[:, :2]) 
    thresh = .1 

    for i in range(data.shape[0]): 
     xy = get_label_xy(tree, thresh, data, i) 

     plt.annotate(
      labels[i], 
      xy = data[i, :2], xytext = xy, 
      textcoords = 'offset points', ha = 'center', va = 'center', 
      bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5), 
      arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0')) 


np.random.seed(0) 
N = 10 
data = np.random.random((N, 4)) 
data[1, :2] = data[0, :2] 
data[-1, :2] = data[-2, :2] + .01 
data[5, :2] = data[4, :2] + [.05, 0] 
data[6, :2] = data[4, :2] + [.05, .05] 
data[7, :2] = data[4, :2] + [0, .05] 
labels = ['point{0}'.format(i) for i in range(N)] 

labeled_scatter_plot(data, labels) 

plt.show() 

enter image description here

Verwandte Themen