2016-06-10 15 views
6

Come usare matplotlib o trama pareggio pyqtgraph come questo: two dirrections widths lineCome disegnare "due direzioni larghezza delle linee" in matplotlib

Line AB è una strada a due direzioni, parte verde rappresenta la direzione dal punto A al punto B, la parte rossa rappresenta B a A, la larghezza di ciascuna parte rappresenta il volume del traffico. Le larghezze sono misurate in punti, non saranno modificate a diversi livelli di zoom o impostazioni dpi.

Questo è solo un esempio, in effetti ho centinaia di strade. Questo tipo di trama è molto comune in molti software di traffico. Ho cercato di usare patheffect di matplotlib ma risultato è frustrato:

from matplotlib import pyplot as plt 
import matplotlib.patheffects as path_effects 

x=[0,1,2,3] 
y=[1,0,0,-1] 
ab_width=20 
ba_width=30 

fig, axes= plt.subplots(1,1) 
center_line, = axes.plot(x,y,color='k',linewidth=2) 

center_line.set_path_effects(
[path_effects.SimpleLineShadow(offset=(0, -ab_width/2),shadow_color='g', alpha=1, linewidth=ab_width), 
path_effects.SimpleLineShadow(offset=(0, ba_width/2), shadow_color='r', alpha=1, linewidth=ba_width), 
path_effects.SimpleLineShadow(offset=(0, -ab_width), shadow_color='k', alpha=1, linewidth=2), 
path_effects.SimpleLineShadow(offset=(0, ba_width), shadow_color='k', alpha=1, linewidth=2), 
path_effects.Normal()]) 

axes.set_xlim(-1,4) 
axes.set_ylim(-1.5,1.5) 

enter image description here

Un'idea mi è venuta è quello di prendere ogni parte della linea come una linea stand-alone, e ricalcolare la sua posizione quando si cambia il livello di zoom, ma è troppo complicato e lento.

Se c'è un modo semplice per usare matplotlib o pyqtgraph disegnare quello che voglio? Ogni suggerimento sarà apprezzato!

+1

I tuoi dati non funzionano – Bart

+0

Mi dispiace, lo riparo. @ Bart – Macer

+0

No, ancora non funziona .. Perché non utilizzare il uploader di immagini fornito da stackoverflow? – Bart

risposta

4

Se è possibile avere ciascuna linea indipendente, questa operazione può essere eseguita facilmente con la funzione fill_between.

from matplotlib import pyplot as plt 
import numpy as np 

x=np.array([0,1,2,3]) 
y=np.array([1,0,0,-1]) 

y1width=-1 
y2width=3 
y1 = y + y1width 
y2 = y + y2width 

fig = plt.figure() 
ax = fig.add_subplot(111) 

plt.plot(x,y, 'k', x,y1, 'k',x,y2, 'k',linewidth=2) 
ax.fill_between(x, y1, y, color='g') 
ax.fill_between(x, y2, y, color='r') 

plt.xlim(-1,4) 
plt.ylim(-3,6) 
plt.show() 

Qui considerata la linea centrale come riferimento (così negativa y1width), ma potrebbe essere fatto in modo diverso. Il risultato è quindi:

<code>fill_between</code> result.

Se le linee sono 'complicati', alla fine si intersecano ad un certo punto, poi la parola chiave argomento interpolate=True deve essere utilizzato per riempire le regioni di crossover in modo corretto. Un altro argomento interessante probabilmente utile per il tuo caso d'uso è where, per condizionare la regione, ad esempio, where=y1 < 0. Per ulteriori informazioni è possibile consultare lo documentation.

+0

Grazie per la tua risposta, ma potresti non comprendere appieno la mia richiesta. La larghezza dovrebbe essere misurata in punti e non cambierà a diversi livelli di zoom. È utilizzato in un'applicazione interattiva, l'utente spesso ha bisogno di ingrandire e rimpicciolire e di osservare diverse linee. Nella tua risposta, le larghezze non sono fisse dopo lo zoom. Anche una piccola differenza in endpoint. – Macer

+0

Vero, non ho ottenuto la parte di "larghezza costante". A tale scopo è necessario catturare l'evento di zoom, impostare una scala appropriata per l'ombreggiatura (aumentando o diminuendo le larghezze originali) e ridisegnare. Segui [questo elenco] (https://gist.github.com/tacaswell/3144287) per vedere qualcosa correlato (correlato, non esattamente quello che stai cercando). Per quanto riguarda i finali, è qui che questo approccio "fill_between" non può più essere d'aiuto. – rll

3

Un modo per risolvere il problema è l'utilizzo di poligoni pieni, dell'algebra lineare e del calcolo. L'idea principale è disegnare un poligono lungo le coordinate x ee lungo le coordinate spostate per chiudere e riempire il poligono.

Questi sono i miei risultati: Filled polygons along path

Ed ecco il codice:

from __future__ import division 
import numpy 
from matplotlib import pyplot, patches 


def road(x, y, w, scale=0.005, **kwargs): 
    # Makes sure input coordinates are arrays. 
    x, y = numpy.asarray(x, dtype=float), numpy.asarray(y, dtype=float) 
    # Calculate derivative. 
    dx = x[2:] - x[:-2] 
    dy = y[2:] - y[:-2] 
    dy_dx = numpy.concatenate([ 
     [(y[1] - y[0])/(x[1] - x[0])], 
     dy/dx, 
     [(y[-1] - y[-2])/(x[-1] - x[-2])] 
    ]) 
    # Offsets the input coordinates according to the local derivative. 
    offset = -dy_dx + 1j 
    offset = w * scale * offset/abs(offset) 
    y_offset = y + w * scale 
    # 
    AB = zip(
     numpy.concatenate([x + offset.real, x[::-1]]), 
     numpy.concatenate([y + offset.imag, y[::-1]]), 
    ) 
    p = patches.Polygon(AB, **kwargs) 

    # Returns polygon. 
    return p 


if __name__ == '__main__': 
    # Some plot initializations 
    pyplot.close('all') 
    pyplot.ion() 

    # This is the list of coordinates of each point 
    x = [0, 1, 2, 3, 4] 
    y = [1, 0, 0, -1, 0] 

    # Creates figure and axes. 
    fig, ax = pyplot.subplots(1,1) 
    ax.axis('equal') 
    center_line, = ax.plot(x, y, color='k', linewidth=2) 

    AB = road(x, y, 20, color='g') 
    BA = road(x, y, -30, color='r') 
    ax.add_patch(AB) 
    ax.add_patch(BA) 

Il primo passo per calcolare il modo per compensare ogni punto di dati consiste nel calcolare la discreta derivato dy/dx. Mi piace usare la notazione complessa per gestire i vettori in Python, ovvero A = 1 - 1j. Ciò rende la vita più facile per alcune operazioni matematiche.

Il passaggio successivo consiste nel ricordare che la derivata fornisce la tangente alla curva e dall'algebra lineare che la normale alla tangente è n=-dy_dx + 1j, utilizzando la notazione complessa.

Il passo finale nella determinazione delle coordinate di offset è garantire che il vettore normale abbia dimensione unità n_norm = n/abs(n) e moltiplicare per la larghezza desiderata del poligono.

Ora che abbiamo tutte le coordinate per i punti nel poligono, il resto è abbastanza semplice. Usa patches.Polygon e aggiungili alla trama.

Questo codice consente inoltre di definire se si desidera che la patch si sovrapponga al percorso o al di sotto di essa. Basta dare un valore positivo o negativo per la larghezza. Se si desidera modificare la larghezza del poligono in base al livello di zoom e/o alla risoluzione, si regola il parametro scale. Ti dà anche la libertà di aggiungere ulteriori parametri alle patch come pattern di riempimento, trasparenza, ecc.

+0

Eccellente! Ma ancora non è possibile risolvere il problema "peso senza cambiamento dopo lo zoom". Forse non esiste un modo direttivo per usare matplotlib per risolvere il problema. Devo rilevare l'evento di zoom, ricalcolarlo e ridirigerlo. – Macer

Problemi correlati