real - matplotlib python
Produzca una gráfica de coordenadas ecuatoriales entre RA y DEC con python (2)
Intento generar una gráfica de coordenadas ecuatoriales que se parezca más o menos a esta:
(La figura está tomada de este artículo y muestra la posición de los MC grandes y pequeños en coordenadas ecuatoriales)
Cosas importantes que debe tener en cuenta sobre esta trama:
- El eje
theta
(es decir: la ascensión recta) está en h: m: s (horas, minutos, segundos) como está acostumbrado en astronomía, en lugar de grados, como lo hace la opciónpolar
predeterminada enmatplotlib
. - El eje
r
(es decir, la declinación) aumenta hacia afuera desde -90º y la cuadrícula se centra en (0h, -90º). - La trama está recortada , lo que significa que solo una parte de ella se muestra en oposición a todo el círculo (como
matplotlib
hace de manera predeterminada).
Utilizando la opción polar=True
en matplotlib
, el gráfico más cercano que he logrado producir es el siguiente ( MWE
, archivo de datos aquí ; algunos puntos no están presentes en comparación con la imagen anterior, ya que el archivo de datos es un poco más pequeño):
También necesito agregar una tercera columna de datos a la trama, por lo que agrego una barra de color y coloreo cada punto según una matriz z
:
Entonces, lo que más necesito en este momento es una forma de recortar la trama. Basado principalmente en esta pregunta y este ejemplo, @cphlewis estuvo bastante cerca de su respuesta , pero aún faltan varias cosas (mencionadas en su respuesta).
Cualquier ayuda y / o sugerencias con este tema serán muy apreciadas.
MWE
(Note que uso gridspec
para posicionar la subtrama porque necesito generar varios de estos en el mismo archivo de imagen de salida)
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
def skip_comments(f):
''''''
Read lines that DO NOT start with a # symbol.
''''''
for line in f:
if not line.strip().startswith(''#''):
yield line
def get_data_bb():
''''''RA, DEC data file.
''''''
# Path to data file.
out_file = ''bb_cat.dat''
# Read data file
with open(out_file) as f:
ra, dec = [], []
for line in skip_comments(f):
ra.append(float(line.split()[0]))
dec.append(float(line.split()[1]))
return ra, dec
# Read RA, DEC data from file.
ra, dec = get_data_bb()
# Convert RA from decimal degrees to radians.
ra = [x / 180.0 * 3.141593 for x in ra]
# Make plot.
fig = plt.figure(figsize=(20, 20))
gs = gridspec.GridSpec(4, 2)
# Position plot in figure using gridspec.
ax = plt.subplot(gs[0], polar=True)
ax.set_ylim(-90, -55)
# Set x,y ticks
angs = np.array([330., 345., 0., 15., 30., 45., 60., 75., 90., 105., 120.])
plt.xticks(angs * np.pi / 180., fontsize=8)
plt.yticks(np.arange(-80, -59, 10), fontsize=8)
ax.set_rlabel_position(120)
ax.set_xticklabels([''$22^h$'', ''$23^h$'', ''$0^h$'', ''$1^h$'', ''$2^h$'', ''$3^h$'',
''$4^h$'', ''$5^h$'', ''$6^h$'', ''$7^h$'', ''$8^h$''], fontsize=10)
ax.set_yticklabels([''$-80^{/circ}$'', ''$-70^{/circ}$'', ''$-60^{/circ}$''],
fontsize=10)
# Plot points.
ax.scatter(ra, dec, marker=''o'', c=''k'', s=1, lw=0.)
# Use this block to generate colored points with a colorbar.
#cm = plt.cm.get_cmap(''RdYlBu_r'')
#z = np.random.random((len(ra), 1)) # RGB values
#SC = ax.scatter(ra, dec, marker=''o'', c=z, s=10, lw=0., cmap=cm)
# Colorbar
#cbar = plt.colorbar(SC, shrink=1., pad=0.05)
#cbar.ax.tick_params(labelsize=8)
#cbar.set_label(''colorbar'', fontsize=8)
# Output png file.
fig.tight_layout()
plt.savefig(ra_dec_plot.png'', dpi=300)
Masticar el ejemplo de AxisArtist es realmente bastante prometedor (esto combina dos ejemplos de AxisArtist; no me sorprendería si AxisArtist se escribiera con argumentos de RA en mente):
Aún por hacer:
- La declinación debe ejecutarse desde -90 en el origen hasta 0
- Ser capaz de usar y agregar una barra de colores
- ajuste los límites si traza fuera de ellos
estético:
- Fuente Serif en etiquetas de eje
- Líneas de grilla discontinuas para la ascensión
¿Algo más?
"""
An experimental support for curvilinear grid.
"""
import numpy as np
import mpl_toolkits.axisartist.angle_helper as angle_helper
import matplotlib.cm as cmap
from matplotlib.projections import PolarAxes
from matplotlib.transforms import Affine2D
from mpl_toolkits.axisartist import SubplotHost
from mpl_toolkits.axisartist import GridHelperCurveLinear
def curvelinear_test2(fig):
"""
polar projection, but in a rectangular box.
"""
global ax1
# see demo_curvelinear_grid.py for details
tr = Affine2D().scale(np.pi/180., 1.) + PolarAxes.PolarTransform()
extreme_finder = angle_helper.ExtremeFinderCycle(10, 60,
lon_cycle = 360,
lat_cycle = None,
lon_minmax = None,
lat_minmax = (0, np.inf),
)
grid_locator1 = angle_helper.LocatorHMS(12) #changes theta gridline count
tick_formatter1 = angle_helper.FormatterHMS()
grid_locator2 = angle_helper.LocatorDMS(6)
tick_formatter2 = angle_helper.FormatterDMS()
grid_helper = GridHelperCurveLinear(tr,
extreme_finder=extreme_finder,
grid_locator1=grid_locator1,
tick_formatter1=tick_formatter1,
grid_locator2=grid_locator2,
tick_formatter2=tick_formatter2
)
ax1 = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper)
# make ticklabels of right and top axis visible.
ax1.axis["right"].major_ticklabels.set_visible(True)
ax1.axis["top"].major_ticklabels.set_visible(True)
ax1.axis["bottom"].major_ticklabels.set_visible(True) #Turn off?
# let right and bottom axis show ticklabels for 1st coordinate (angle)
ax1.axis["right"].get_helper().nth_coord_ticks=0
ax1.axis["bottom"].get_helper().nth_coord_ticks=0
fig.add_subplot(ax1)
grid_helper = ax1.get_grid_helper()
ax1.set_aspect(1.)
ax1.set_xlim(-4,15) # moves the origin left-right in ax1
ax1.set_ylim(-3, 20) # moves the origin up-down
ax1.set_ylabel(''90$^/circ$ + Declination'')
ax1.set_xlabel(''Ascension'')
ax1.grid(True)
#ax1.grid(linestyle=''--'', which=''x'') # either keyword applies to both
#ax1.grid(linestyle='':'', which=''y'') # sets of gridlines
return tr
import matplotlib.pyplot as plt
fig = plt.figure(1, figsize=(5, 5))
fig.clf()
tr = curvelinear_test2(fig) # tr.transform_point((x, 0)) is always (0,0)
# => (theta, r) in but (r, theta) out...
r_test = [0, 1.2, 2.8, 3.8, 5, 8, 10, 13.3, 17] # distance from origin
deg_test = [0, -7, 12, 28, 45, 70, 79, 90, 100] # degrees ascension
out_test = tr.transform(zip(deg_test, r_test))
sizes = [40, 30, 10, 30, 80, 33, 12, 48, 45]
#hues = [.9, .3, .2, .8, .6, .1, .4, .5,.7] # Oddly, floats-to-colormap worked for a while.
hues = np.random.random((9,3)) #RGB values
ax1.scatter(out_test[:,0], #ax1 is a global
out_test[:,1],
s=sizes,
c=hues,
#cmap=cmap.RdYlBu_r,
zorder=9) #on top of gridlines
plt.show()
Obtener la barra de color se puede hacer fusionando el código OP con la excelente respuesta de @ cphlewis . Lo publiqué como una solución llave en mano a petición del OP en el chat . La primera versión del código simplemente agrega una barra de color, la versión final (en EDIT 2) hace una traducción afín de los ejes y corrige algunos parámetros / simplifica el código para adaptarse exactamente a la especificación OP.
"""
An experimental support for curvilinear grid.
"""
import numpy as np
import mpl_toolkits.axisartist.angle_helper as angle_helper
import matplotlib.cm as cmap
from matplotlib.projections import PolarAxes
from matplotlib.transforms import Affine2D
from mpl_toolkits.axisartist import SubplotHost
from mpl_toolkits.axisartist import GridHelperCurveLinear
def curvelinear_test2(fig):
"""
polar projection, but in a rectangular box.
"""
global ax1
# see demo_curvelinear_grid.py for details
tr = Affine2D().scale(np.pi/180., 1.) + PolarAxes.PolarTransform()
extreme_finder = angle_helper.ExtremeFinderCycle(10, 60,
lon_cycle = 360,
lat_cycle = None,
lon_minmax = None,
lat_minmax = (0, np.inf),
)
grid_locator1 = angle_helper.LocatorHMS(12) #changes theta gridline count
tick_formatter1 = angle_helper.FormatterHMS()
grid_locator2 = angle_helper.LocatorDMS(6)
tick_formatter2 = angle_helper.FormatterDMS()
grid_helper = GridHelperCurveLinear(tr,
extreme_finder=extreme_finder,
grid_locator1=grid_locator1,
tick_formatter1=tick_formatter1,
grid_locator2=grid_locator2,
tick_formatter2=tick_formatter2
)
ax1 = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper)
# make ticklabels of right and top axis visible.
ax1.axis["right"].major_ticklabels.set_visible(True)
ax1.axis["top"].major_ticklabels.set_visible(True)
ax1.axis["bottom"].major_ticklabels.set_visible(True) #Turn off?
# let right and bottom axis show ticklabels for 1st coordinate (angle)
ax1.axis["right"].get_helper().nth_coord_ticks=0
ax1.axis["bottom"].get_helper().nth_coord_ticks=0
fig.add_subplot(ax1)
grid_helper = ax1.get_grid_helper()
ax1.set_aspect(1.)
ax1.set_xlim(-4,15) # moves the origin left-right in ax1
ax1.set_ylim(-3, 20) # moves the origin up-down
ax1.set_ylabel(''90$^/circ$ + Declination'')
ax1.set_xlabel(''Ascension'')
ax1.grid(True)
#ax1.grid(linestyle=''--'', which=''x'') # either keyword applies to both
#ax1.grid(linestyle='':'', which=''y'') # sets of gridlines
return tr
import matplotlib.pyplot as plt
fig = plt.figure(1, figsize=(5, 5))
fig.clf()
tr = curvelinear_test2(fig) # tr.transform_point((x, 0)) is always (0,0)
# => (theta, r) in but (r, theta) out...
r_test = [0, 1.2, 2.8, 3.8, 5, 8, 10, 13.3, 17] # distance from origin
deg_test = [0, -7, 12, 28, 45, 70, 79, 90, 100] # degrees ascension
out_test = tr.transform(zip(deg_test, r_test))
sizes = [40, 30, 10, 30, 80, 33, 12, 48, 45]
#hues = [.9, .3, .2, .8, .6, .1, .4, .5,.7] # Oddly, floats-to-colormap worked for a while.
hues = np.random.random((9,3)) #RGB values
# Use this block to generate colored points with a colorbar.
cm = plt.cm.get_cmap(''RdYlBu_r'')
z = np.random.random((len(r_test), 1)) # RGB values
SC = ax1.scatter(out_test[:,0], #ax1 is a global
out_test[:,1],
s=sizes,
c=z,
cmap=cm,
zorder=9) #on top of gridlines
# Colorbar
cbar = plt.colorbar(SC, shrink=1., pad=0.05)
cbar.ax.tick_params(labelsize=8)
cbar.set_label(''colorbar'', fontsize=8)
plt.show()
EDITAR
Un poco de parámetros de ordenamiento, agregar datos OP, eliminar la redundancia produce el siguiente gráfico. Todavía necesito centrar los datos en -90 en lugar de 0 - en el momento en que se hackea, pero estoy seguro de que curvelinear_test2()
se puede cambiar para dar cuenta de ello ...
EDIT 2
Después del comentario de OP sobre la versión intermedia en esta respuesta, una versión final como la que se muestra a continuación da la trama al final de la publicación, con -90 en el eje de declinación y la demo de subplot.
"""
An experimental support for curvilinear grid.
"""
import numpy as np
import mpl_toolkits.axisartist.angle_helper as angle_helper
import matplotlib.cm as cmap
from matplotlib.projections import PolarAxes
from matplotlib.transforms import Affine2D
from mpl_toolkits.axisartist import SubplotHost
from mpl_toolkits.axisartist import GridHelperCurveLinear
def curvelinear_test2(fig, rect=111):
"""
polar projection, but in a rectangular box.
"""
# see demo_curvelinear_grid.py for details
tr = Affine2D().translate(0,90) + Affine2D().scale(np.pi/180., 1.) + PolarAxes.PolarTransform()
extreme_finder = angle_helper.ExtremeFinderCycle(10, 60,
lon_cycle = 360,
lat_cycle = None,
lon_minmax = None,
lat_minmax = (-90, np.inf),
)
grid_locator1 = angle_helper.LocatorHMS(12) #changes theta gridline count
tick_formatter1 = angle_helper.FormatterHMS()
grid_helper = GridHelperCurveLinear(tr,
extreme_finder=extreme_finder,
grid_locator1=grid_locator1,
tick_formatter1=tick_formatter1
)
ax1 = SubplotHost(fig, rect, grid_helper=grid_helper)
# make ticklabels of right and top axis visible.
ax1.axis["right"].major_ticklabels.set_visible(True)
ax1.axis["top"].major_ticklabels.set_visible(True)
ax1.axis["bottom"].major_ticklabels.set_visible(True) #Turn off?
# let right and bottom axis show ticklabels for 1st coordinate (angle)
ax1.axis["right"].get_helper().nth_coord_ticks=0
ax1.axis["bottom"].get_helper().nth_coord_ticks=0
fig.add_subplot(ax1)
grid_helper = ax1.get_grid_helper()
# You may or may not need these - they set the view window explicitly rather than using the
# default as determined by matplotlib with extreme finder.
ax1.set_aspect(1.)
ax1.set_xlim(-4,25) # moves the origin left-right in ax1
ax1.set_ylim(-3, 30) # moves the origin up-down
ax1.set_ylabel(''Declination'')
ax1.set_xlabel(''Ascension'')
ax1.grid(True)
#ax1.grid(linestyle=''--'', which=''x'') # either keyword applies to both
#ax1.grid(linestyle='':'', which=''y'') # sets of gridlines
return ax1,tr
def skip_comments(f):
''''''
Read lines that DO NOT start with a # symbol.
''''''
for line in f:
if not line.strip().startswith(''#''):
yield line
def get_data_bb():
''''''RA, DEC data file.
''''''
# Path to data file.
out_file = ''bb_cat.dat''
# Read data file
with open(out_file) as f:
ra, dec = [], []
for line in skip_comments(f):
ra.append(float(line.split()[0]))
dec.append(float(line.split()[1]))
return ra, dec
import matplotlib.pyplot as plt
fig = plt.figure(1, figsize=(5, 5))
fig.clf()
ax1, tr = curvelinear_test2(fig,121) # tr.transform_point((x, 0)) is always (0,0)
# => (theta, r) in but (r, theta) out...
# Read RA, DEC data from file.
ra, dec = get_data_bb()
out_test = tr.transform(zip(ra, dec))
# Use this block to generate colored points with a colorbar.
cm = plt.cm.get_cmap(''RdYlBu_r'')
z = np.random.random((len(ra), 1)) # RGB values
SC = ax1.scatter(out_test[:,0], #ax1 is a global
out_test[:,1],
marker = ''o'',
c=z,
cmap=cm,
lw = 0.,
zorder=9) #on top of gridlines
# Colorbar
cbar = plt.colorbar(SC, shrink=1., pad=0.1)
cbar.ax.tick_params(labelsize=8)
cbar.set_label(''colorbar'', fontsize=8)
ax2, tr = curvelinear_test2(fig,122) # tr.transform_point((x, 0)) is always (0,0)
# => (theta, r) in but (r, theta) out...
# Read RA, DEC data from file.
ra, dec = get_data_bb()
out_test = tr.transform(zip(ra, dec))
# Use this block to generate colored points with a colorbar.
cm = plt.cm.get_cmap(''RdYlBu_r'')
z = np.random.random((len(ra), 1)) # RGB values
SC = ax2.scatter(out_test[:,0], #ax1 is a global
out_test[:,1],
marker = ''o'',
c=z,
cmap=cm,
lw = 0.,
zorder=9) #on top of gridlines
# Colorbar
cbar = plt.colorbar(SC, shrink=1., pad=0.1)
cbar.ax.tick_params(labelsize=8)
cbar.set_label(''colorbar'', fontsize=8)
plt.show()
Parcela final: