web-dev-qa-db-ger.com

Zeichnen Sie mit Python/Matplotlib ein (polares) Farbrad basierend auf einer Farbkarte

Ich versuche, ein Farbrad in Python zu erstellen, vorzugsweise mit Matplotlib. Folgendes funktioniert OK:

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt

xval = np.arange(0, 2*pi, 0.01)
yval = np.ones_like(xval)

colormap = plt.get_cmap('hsv')
norm = mpl.colors.Normalize(0.0, 2*np.pi)

ax = plt.subplot(1, 1, 1, polar=True)
ax.scatter(xval, yval, c=xval, s=300, cmap=colormap, norm=norm, linewidths=0)
ax.set_yticks([])

 Own attempt at creating a color wheel

Dieser Versuch hat jedoch zwei schwerwiegende Nachteile.

Erstens besteht das Farbrad beim Speichern der resultierenden Figur als Vektor ( figure_1.svg ) (wie erwartet) aus 621 verschiedenen Formen, die den verschiedenen (x, y) Werten entsprechen, die gezeichnet werden. Obwohl das Ergebnis wie ein Kreis aussieht, ist es nicht wirklich. Ich würde es sehr bevorzugen, einen tatsächlichen Kreis zu verwenden, der durch einige Pfadpunkte und Bézier-Kurven dazwischen definiert ist, wie z. matplotlib.patches.Circle . Dies scheint mir die "richtige" Methode zu sein, und das Ergebnis würde besser aussehen (keine Streifenbildung, besserer Verlauf, besseres Antialiasing).

Zweitens überlappen die endgültig geplotteten Markierungen (die letzten vor 2*pi) die ersten. Im Pixel-Rendering ist es sehr schwer zu erkennen, aber wenn Sie das vektorbasierte Rendering vergrößern, sehen Sie deutlich, dass die letzte Disc die ersten überlappt.

Ich habe versucht, verschiedene Markierungen (. oder |) zu verwenden, aber keine umgeht das zweite Problem.

Fazit: Kann ich in Python/Matplotlib einen Kreis zeichnen, der in der richtigen Vektor-/Bézier-Kurve definiert ist und dessen Kantenfarbe anhand einer Farbkarte definiert ist (oder, falls dies nicht der Fall ist, einem beliebigen Farbverlauf)?

15
EelkeSpaak

Eine Möglichkeit, die ich gefunden habe, besteht darin, eine Farbkarte zu erstellen und sie dann auf eine Polarachse zu projizieren. Hier ist ein funktionierendes Beispiel - es enthält jedoch einen bösen Hack (klar kommentiert). Ich bin mir sicher, dass es eine Möglichkeit gibt, Grenzen anzupassen oder (härter) eine eigene Transform zu schreiben, um das zu umgehen, aber das habe ich noch nicht ganz geschafft. Ich dachte, die Grenzen des Aufrufs von Normalize würden das tun, aber anscheinend nicht.

import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
import matplotlib as mpl

fig = plt.figure()

display_axes = fig.add_axes([0.1,0.1,0.8,0.8], projection='polar')
display_axes._direction = 2*np.pi ## This is a nasty hack - using the hidden field to 
                                  ## multiply the values such that 1 become 2*pi
                                  ## this field is supposed to take values 1 or -1 only!!

norm = mpl.colors.Normalize(0.0, 2*np.pi)

# Plot the colorbar onto the polar axis
# note - use orientation horizontal so that the gradient goes around
# the wheel rather than centre out
quant_steps = 2056
cb = mpl.colorbar.ColorbarBase(display_axes, cmap=cm.get_cmap('hsv',quant_steps),
                                   norm=norm,
                                   orientation='horizontal')

# aesthetics - get rid of border and axis labels                                   
cb.outline.set_visible(False)                                 
display_axes.set_axis_off()
plt.show() # Replace with plt.savefig if you want to save a file

Dies erzeugt

 colorwheel direct from matplotlib

Wenn Sie einen Ring anstelle eines Rads wünschen, verwenden Sie diesen vor plt.show() oder plt.savefig

display_axes.set_rlim([-1,1])

Das gibt

 color ring


Laut @EelkeSpaak in Kommentaren - wenn Sie die Grafik als SVG gemäß OP speichern, ist hier ein Tipp für das Arbeiten mit der resultierenden Grafik: Die kleinen Elemente des resultierenden SVG-Bildes berühren sich und überlappen sich nicht. Dies führt bei einigen Renderern (Inkscape, Adobe Reader, wahrscheinlich nicht gedruckt) zu schwachen grauen Linien. Eine einfache Lösung hierfür ist das Anwenden einer kleinen (z. B. 120%) Skalierung auf jedes der einzelnen Gradientenelemente unter Verwendung von z. Inkscape oder Illustrator. Beachten Sie, dass Sie die Transformation auf jedes Element separat anwenden müssen (die erwähnte Software bietet Funktionen, um dies automatisch zu tun), und nicht auf die gesamte Zeichnung, da sie sonst keine Auswirkungen hat.

14
J Richard Snape

Ich musste nur ein Farbrad erstellen und entschied mich, die Lösung von rsnape zu aktualisieren, damit sie mit matplotlib 2.1 kompatibel ist. Anstatt ein Farbbalkenobjekt auf einer Achse zu platzieren, können Sie auch ein polares farbiges Netz auf einem Polardiagramm zeichnen.

import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
import matplotlib as mpl

#If displaying in a Jupyter notebook:
%matplotlib inline 

#Generate a figure with a polar projection
fg = plt.figure(figsize=(8,8))
ax = fg.add_axes([0.1,0.1,0.8,0.8], projection='polar')

#define colormap normalization for 0 to 2*pi
norm = mpl.colors.Normalize(0, 2*np.pi) 

#Plot a color mesh on the polar plot
#with the color set by the angle

n = 200  #the number of secants for the mesh
t = np.linspace(0,2*np.pi,n)   #theta values
r = np.linspace(.6,1,2)        #raidus values change 0.6 to 0 for full circle
rg, tg = np.meshgrid(r,t)      #create a r,theta meshgrid
c = tg                         #define color values as theta value
im = ax.pcolormesh(t, r, c.T,norm=norm)  #plot the colormesh on axis with colormap
ax.set_yticklabels([])                   #turn of radial tick labels (yticks)
ax.tick_params(pad=15,labelsize=24)      #cosmetic changes to tick labels
ax.spines['polar'].set_visible(False)    #turn off the axis spine.

Es gibt dies:

A color wheel for the viridis colormap. Made with matplotlib 2.1.

2
toconnor