web-dev-qa-db-fra.com

Tracer une roue chromatique (polaire) basée sur une carte de couleurs utilisant Python/Matplotlib

J'essaie de créer une roue chromatique en Python, de préférence avec Matplotlib. Les travaux suivants fonctionnent bien:

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

Cependant, cette tentative présente deux inconvénients graves.

Premièrement, lors de la sauvegarde de la figure résultante en tant que vecteur ( figure_1.svg ), la roue de couleurs est composée (comme prévu) de 621 formes différentes, correspondant aux différentes valeurs (x, y) tracées. Bien que le résultat ressemble à un cercle, ce n'est pas vraiment le cas. Je préférerais beaucoup utiliser un cercle réel, défini par quelques points de chemin et des courbes de Bézier entre eux, comme par exemple. matplotlib.patches.Circle . Cela me semble la "bonne" façon de procéder, et le résultat serait plus joli (pas de bandes, meilleur gradient, meilleur anti-aliasing).

Deuxièmement, les marqueurs finaux (les derniers avant 2*pi) chevauchent les premiers. Il est très difficile de voir le rendu en pixels, mais si vous effectuez un zoom avant sur le rendu vectoriel, vous pouvez voir clairement que le dernier disque recouvre les premiers.

J'ai essayé d'utiliser différents marqueurs (. ou |), mais aucun d'entre eux ne contourne le second problème.

Ligne de fond: puis-je dessiner un cercle en Python/Matplotlib qui est défini dans le chemin de vecteur/Bézier approprié et qui a une couleur Edge définie selon une palette de couleurs (ou, à défaut, un dégradé de couleur arbitraire)?

15
EelkeSpaak

L'une des méthodes que j'ai trouvées consiste à créer une carte de couleurs puis à la projeter sur un axe polaire. Voici un exemple de travail - il inclut un bidouillage méchant, cependant (clairement commenté). Je suis sûr qu'il existe un moyen de modifier les limites ou (plus difficile) d'écrire votre propre variable Transform pour la contourner, mais je n'y suis pas encore parvenue. Je pensais que les limites de l'appel à Normalize feraient cela, mais apparemment pas.

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

Cela produit

 colorwheel direct from matplotlib

Si vous voulez une bague plutôt qu'une roue, utilisez ceci avant plt.show() ou plt.savefig

display_axes.set_rlim([-1,1])

Cela donne

 color ring


Selon @EelkeSpaak dans les commentaires - si vous enregistrez le graphique au format SVG conformément au PO, voici une astuce pour utiliser le graphique obtenu: Les petits éléments de l'image SVG obtenue sont touchants et ne se chevauchent pas. Cela conduit à de légères lignes grises dans certains moteurs de rendu (Inkscape, Adobe Reader, probablement pas en impression). Une solution simple consiste à appliquer une petite mise à l'échelle (par exemple 120%) à chacun des éléments de gradient individuels, en utilisant par exemple Inkscape ou Illustrator. Notez que vous devrez appliquer la transformation à chaque élément séparément (le logiciel mentionné fournit une fonctionnalité permettant de le faire automatiquement), plutôt qu'à l'ensemble du dessin, sinon cela n'aura aucun effet.

14
J Richard Snape

Je devais créer une roue chromatique et j'ai décidé de mettre à jour la solution de rsnape pour qu'elle soit compatible avec matplotlib 2.1. Plutôt que de placer un objet de barre de couleur sur un axe, vous pouvez tracer un maillage de couleur polaire sur un tracé polaire.

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.

Cela donne ceci:

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

2
toconnor