2013-02-18 10 views
23

używam matplotlib wykreślić wykres punktowy:Jak poprawić rozmieszczenie etykiet dla wykresu punktowego matplotlib (kod, algorytm, wskazówki)?

enter image description here

i etykiety bańkę przy użyciu przezroczystego pudełka według końcówki na matplotlib: how to annotate point on a scatter automatically placed arrow?

Oto kod:

if show_annote: 
    for i in range(len(x)): 
     annote_text = annotes[i][0][0] # STK_ID 
     ax.annotate(annote_text, xy=(x[i], y[i]), xytext=(-10,3), 
      textcoords='offset points', ha='center', va='bottom', 
      bbox=dict(boxstyle='round,pad=0.2', fc='yellow', alpha=0.2), 
      fontproperties=ANNOTE_FONT) 

i powstała działka: enter image description here

Jednak wciąż istnieje możliwość wprowadzenia ulepszeń w celu zmniejszenia nakładania się (na przykład przesunięcie pola etykiety jest ustalone jako (-10,3)). Czy istnieją algorytmy, które mogą:

  1. dynamicznie zmieniać przesunięcie polu Etykieta według crowdedness jego sąsiedztwie
  2. dynamicznie umieść pole etykiety zdalnie i dodać linię ze strzałką bąbelkowej i etykiet polu
  3. nieco zmian beween orientacja etykiety
  4. nakładające się bańki label_box są lepsze niż label_box nachodzące na label_box?

po prostu chcę zrobić wykres łatwo ludzkie oczy comprehand, więc niektóre pokrywają jest w porządku, a nie sztywne ograniczenie jako http://en.wikipedia.org/wiki/Automatic_label_placement sugeruje. A ilość bąbelków na wykresie jest w większości przypadków mniejsza niż 150.

Uważam, że tak zwany Force-based label placementhttp://bl.ocks.org/MoritzStefaner/1377729 jest całkiem interesujący. Nie wiem, czy istnieje jakiś kod/pakiet Pythona do wdrożenia algorytmu.

Nie jestem człowiekiem akademickim i nie szukam optymalnego rozwiązania, a moje kody Pythona muszą oznaczać wiele wykresów, więc prędkość/pamięć jest w zakresie rozważań.

Szukam szybkiego i skutecznego rozwiązania. Każda pomoc (kod, algorytm, wskazówki, przemyślenia) na ten temat? Dzięki.

+1

Założę się, że możesz zrobić coś fajnego dzięki networkx i jego układowi graficznemu. – tacaswell

Odpowiedz

16

Jest trochę szorstko na krawędziach (nie wiem, jak skalować względne siły sieci sprężynowej w stosunku do siły odpychania, a ramka ograniczająca jest nieco spieprzona), ale jest to przyzwoity start:

import networkx as nx 

N = 15 
scatter_data = rand(3, N) 
G=nx.Graph() 

data_nodes = [] 
init_pos = {} 
for j, b in enumerate(scatter_data.T): 
    x, y, _ = b 
    data_str = 'data_{0}'.format(j) 
    ano_str = 'ano_{0}'.format(j) 
    G.add_node(data_str) 
    G.add_node(ano_str) 
    G.add_edge(data_str, ano_str) 
    data_nodes.append(data_str) 
    init_pos[data_str] = (x, y) 
    init_pos[ano_str] = (x, y) 

pos = nx.spring_layout(G, pos=init_pos, fixed=data_nodes) 
ax = gca() 
ax.scatter(scatter_data[0], scatter_data[1], c=scatter_data[2], s=scatter_data[2]*150) 

for j in range(N): 
    data_str = 'data_{0}'.format(j) 
    ano_str = 'ano_{0}'.format(j) 
    ax.annotate(ano_str, 
       xy=pos[data_str], xycoords='data', 
       xytext=pos[ano_str], textcoords='data', 
       arrowprops=dict(arrowstyle="->", 
           connectionstyle="arc3")) 

all_pos = np.vstack(pos.values()) 
mins = np.min(all_pos, 0) 
maxs = np.max(all_pos, 0) 

ax.set_xlim([mins[0], maxs[0]]) 
ax.set_ylim([mins[1], maxs[1]]) 

draw() 

sample image

Jak to działa trochę zależy, w jaki sposób dane są skupione.

+0

Tak, to przyzwoity start, wygląda na poprawę wyniku wyświetlania. Przyjrzę się siecix. Dzięki, – bigbug

12

Poniższa lista opiera się na tcaswell's answer.

Metody układu Networkx, takie jak nx.spring_layout przeskalują pozycje tak, aby pasowały do ​​kwadratu jednostki (domyślnie). Nawet pozycja ustalonego data_nodes jest przeskalowana. Tak więc, aby zastosować pos do oryginalnego scatter_data, należy wykonać operację unshifting i uncscaling.

Należy również zauważyć, że parametr nx.spring_layout ma parametr k, który steruje optymalną odległością między węzłami.Wraz ze wzrostem k wzrasta odległość między adnotacjami a punktami danych.

import numpy as np 
import matplotlib.pyplot as plt 
import networkx as nx 
np.random.seed(2016) 

N = 20 
scatter_data = np.random.rand(N, 3)*10 


def repel_labels(ax, x, y, labels, k=0.01): 
    G = nx.DiGraph() 
    data_nodes = [] 
    init_pos = {} 
    for xi, yi, label in zip(x, y, labels): 
     data_str = 'data_{0}'.format(label) 
     G.add_node(data_str) 
     G.add_node(label) 
     G.add_edge(label, data_str) 
     data_nodes.append(data_str) 
     init_pos[data_str] = (xi, yi) 
     init_pos[label] = (xi, yi) 

    pos = nx.spring_layout(G, pos=init_pos, fixed=data_nodes, k=k) 

    # undo spring_layout's rescaling 
    pos_after = np.vstack([pos[d] for d in data_nodes]) 
    pos_before = np.vstack([init_pos[d] for d in data_nodes]) 
    scale, shift_x = np.polyfit(pos_after[:,0], pos_before[:,0], 1) 
    scale, shift_y = np.polyfit(pos_after[:,1], pos_before[:,1], 1) 
    shift = np.array([shift_x, shift_y]) 
    for key, val in pos.items(): 
     pos[key] = (val*scale) + shift 

    for label, data_str in G.edges(): 
     ax.annotate(label, 
        xy=pos[data_str], xycoords='data', 
        xytext=pos[label], textcoords='data', 
        arrowprops=dict(arrowstyle="->", 
            shrinkA=0, shrinkB=0, 
            connectionstyle="arc3", 
            color='red'),) 
    # expand limits 
    all_pos = np.vstack(pos.values()) 
    x_span, y_span = np.ptp(all_pos, axis=0) 
    mins = np.min(all_pos-x_span*0.15, 0) 
    maxs = np.max(all_pos+y_span*0.15, 0) 
    ax.set_xlim([mins[0], maxs[0]]) 
    ax.set_ylim([mins[1], maxs[1]]) 

fig, ax = plt.subplots() 
ax.scatter(scatter_data[:, 0], scatter_data[:, 1], 
      c=scatter_data[:, 2], s=scatter_data[:, 2] * 150) 
labels = ['ano_{}'.format(i) for i in range(N)] 
repel_labels(ax, scatter_data[:, 0], scatter_data[:, 1], labels, k=0.008) 

plt.show() 

z k=0.011 daje

enter image description here iz k=0.008 daje enter image description here

+0

Musiałem zmienić pos.iteritems() w pętli for na pos.items(). Używam Python 3.5.2 i networkx v1.11. – equant

7

Inną opcją używając mojej biblioteki adjustText, napisany specjalnie do tego celu (https://github.com/Phlya/adjustText).

from adjustText import adjust_text 
np.random.seed(2016) 

N = 50 
scatter_data = np.random.rand(N, 3) 
fig, ax = plt.subplots() 
ax.scatter(scatter_data[:, 0], scatter_data[:, 1], 
      c=scatter_data[:, 2], s=scatter_data[:, 2] * 150) 
labels = ['ano_{}'.format(i) for i in range(N)] 
texts = [] 
for x, y, text in zip(scatter_data[:, 0], scatter_data[:, 1], labels): 
    texts.append(ax.text(x, y, text)) 
plt.show() 

enter image description here

np.random.seed(2016) 

N = 50 
scatter_data = np.random.rand(N, 3) 
fig, ax = plt.subplots() 
ax.scatter(scatter_data[:, 0], scatter_data[:, 1], 
      c=scatter_data[:, 2], s=scatter_data[:, 2] * 150) 
labels = ['ano_{}'.format(i) for i in range(N)] 
texts = [] 
for x, y, text in zip(scatter_data[:, 0], scatter_data[:, 1], labels): 
    texts.append(ax.text(x, y, text)) 
adjust_text(texts, force_text=0.05, arrowprops=dict(arrowstyle="-|>", 
                color='r', alpha=0.5)) 
plt.show() 

enter image description here

To nie odpychać od pęcherzyków, tylko ze swoich ośrodków i innych tekstów.

+0

Próbuję twój kod i mam problem, patrz poniżej. – bigbug

Powiązane problemy