2012-02-10 12 views
20

C'è un modo per ottenere matplotlib per tracciare un cerchio perfetto? Sembrano più ovali.Perché Matplotlib traccia i miei cerchi come ovali?

+1

Avete controllato la risoluzione video per assicurarsi che sia lo stesso della risoluzione del tuo monitor? Potrebbe essere che tutto ciò che stai visualizzando è distorto. –

risposta

29

Solo per espandere la risposta corretta di DSM. Per impostazione predefinita, i grafici hanno più pixel lungo un asse rispetto all'altro. Quando aggiungi un cerchio, viene tradizionalmente aggiunto in unità di dati. Se i tuoi assi hanno un intervallo simmetrico, significa che un passo lungo l'asse x comporterà un diverso numero di pixel rispetto a un passo lungo l'asse y. Quindi un cerchio simmetrico in unità di dati è asimmetrico nelle tue unità Pixel (ciò che vedi effettivamente).

Come indicato correttamente dal DSM, è possibile forzare gli assi xey ad avere un numero uguale di pixel per unità di dati. Questo viene fatto usando i metodi plt.axis("equal") o ax.axis("equal") (dove ax è un'istanza di Axes).

È inoltre possibile disegnare un Ellipse in modo che venga ridimensionato in modo appropriato per assomigliare ad un cerchio sulla trama. Ecco un esempio di un caso del genere:

import matplotlib.pyplot as plt 
from matplotlib.patches import Ellipse, Circle 


fig = plt.figure() 
ax1 = fig.add_subplot(211) 
# calculate asymmetry of x and y axes: 
x0, y0 = ax1.transAxes.transform((0, 0)) # lower left in pixels 
x1, y1 = ax1.transAxes.transform((1, 1)) # upper right in pixes 
dx = x1 - x0 
dy = y1 - y0 
maxd = max(dx, dy) 
width = .15 * maxd/dx 
height = .15 * maxd/dy 

# a circle you expect to be a circle, but it is not 
ax1.add_artist(Circle((.5, .5), .15)) 
# an ellipse you expect to be an ellipse, but it's a circle 
ax1.add_artist(Ellipse((.75, .75), width, height)) 
ax2 = fig.add_subplot(212) 

ax2.axis('equal') 
# a circle you expect to be a circle, and it is 
ax2.add_artist(Circle((.5, .5), .15)) 
# an ellipse you expect to be an ellipse, and it is 
ax2.add_artist(Ellipse((.75, .75), width, height)) 

fig.savefig('perfectCircle1.png') 

conseguente in questa figura:

enter image description here

In alternativa, è possibile regolare la tua figura in modo che il Axes sono quadrati:

# calculate dimensions of axes 1 in figure units 
x0, y0, dx, dy = ax1.get_position().bounds 
maxd = max(dx, dy) 
width = 6 * maxd/dx 
height = 6 * maxd/dy 

fig.set_size_inches((width, height)) 

fig.savefig('perfectCircle2.png') 

risultante in:

enter image description here

Nota come il secondo asse, che ha l'opzione axis("equal"), ora ha lo stesso intervallo per gli assi xe y. La figura è stata ridimensionata in modo che le unità di data di ciascuna siano rappresentate dallo stesso numero di pixel.

È anche possibile regolare gli assi in modo che siano quadrati, anche se la cifra non lo è. Oppure puoi cambiare la trasformazione predefinita per il cerchio in None, il che significa che le unità utilizzate sono pixel. Al momento sto avendo difficoltà a fare questo (il cerchio è un cerchio, ma non dove voglio che sia).

+0

Questo è molto più dettagliato del mio (era) che dovrebbe diventare la risposta canonica. – DSM

+0

Risposta eccellente - un TL generalmente utile; DR è sicuro di fare matplotlib.pyplot.axis ("uguale") subito :-) –

0

Ho riscontrato lo stesso problema oggi e penso che potrei avere una soluzione più flessibile. Rimangono due problemi principali con la risposta precedente (se non si utilizza la funzione di uguale aspetto). Innanzitutto se ridimensioni l'intero grafico, la proporzione non sarà la stessa poiché il numero di pixel cambierà. Secondo punto, questo trucco non funziona se non si ha lo stesso limite per gli xaxis e gli yaxis.

Questa soluzione utilizza mpl con un oggetto personalizzato. Infatti, ogni volta che si modifica uno degli assi lim o le dimensioni del grafico, mpl chiamerà una funzione interna che prenderà il valore di larghezza e altezza dell'ellisse moltiplicato per il valore della funzione di trasformazione. Dal momento che il valore della larghezza e l'altezza viene memorizzato nell'oggetto ellisse, un modo è quello di creare un oggetto personalizzato con un valore aggiornato ogni volta che la funzione viene chiamata, in base alle proprietà ascia attuali:

import matplotlib.pyplot as plt 
from matplotlib.patches import Ellipse 

class GraphDist() : 
    def __init__(self, size, ax, x=True) : 
     self.size = size 
     self.ax = ax 
     self.x = x 

    @property 
    def dist_real(self) : 
     x0, y0 = self.ax.transAxes.transform((0, 0)) # lower left in pixels 
     x1, y1 = self.ax.transAxes.transform((1, 1)) # upper right in pixes 
     value = x1 - x0 if self.x else y1 - y0 
     return value 

    @property 
    def dist_abs(self) : 
     bounds = self.ax.get_xlim() if self.x else self.ax.get_ylim() 
     return bounds[0] - bounds[1] 

    @property 
    def value(self) : 
     return (self.size/self.dist_real) * self.dist_abs 

    def __mul__(self, obj) : 
     return self.value * obj 

fig = plt.figure() 
ax = fig.add_subplot(111) 
ax.set_xlim((0,10)) 
ax.set_ylim((0,5)) 
width = GraphDist(10, ax, True) 
height = GraphDist(10, ax, False) 
ax.add_artist(Ellipse((1, 3), width, height)) 
plt.show() 
Problemi correlati