Cómo obtener tags en la parte superior de la barra en el gráfico de barras polar / radial en Matplotlib, Python3

Quiero crear un gráfico de barras radiales. Tengo el siguiente código de Python3:

lObjectsALLcnts = [1, 1, 1, 2, 2, 3, 5, 14, 15, 20, 32, 33, 51, 1, 1, 2, 2, 3, 3, 3, 3, 3, 4, 6, 7, 7, 10, 10, 14, 14, 14, 17, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 5, 5, 6, 14, 14, 27, 27, 1, 1, 2, 3, 4, 4, 5]` lObjectsALLlbls = ['DuctPipe', 'Column', 'Protrusion', 'Tree', 'Pole', 'Bar', 'Undefined', 'EarthingConductor', 'Grooves', 'UtilityPipe', 'Cables', 'RainPipe', 'Moulding', 'Intrusion', 'PowerPlug', 'UtilityBox', 'Balcony', 'Lighting', 'Lock', 'Doorbell', 'Alarm', 'LetterBox', 'Grate', 'Undefined', 'CableBox', 'Canopy', 'Vent', 'PowerBox', 'UtilityHole', 'Recess', 'Protrusion', 'Shutter', 'Handrail', 'Lock', 'Mirror', 'SecuritySpike', 'Bench', 'Intrusion', 'Picture', 'Showcase', 'Camera', 'Undefined', 'Stair', 'Protrusion', 'Alarm', 'Graffiti', 'Lighting', 'Ornaments', 'SecurityBar', 'Grate', 'Vent', 'Lighting', 'UtilityHole', 'Intrusion', 'Undefined', 'Protrusion'] iN = len(lObjectsALLcnts) arrCnts = np.array(lObjectsALLcnts) theta=np.arange(0,2*np.pi,2*np.pi/iN) width = (2*np.pi)/iN *0.9 fig = plt.figure(figsize=(8, 8)) ax = fig.add_axes([0.1, 0.1, 0.75, 0.75], polar=True) bars = ax.bar(theta, arrCnts, width=width, bottom=50) ax.set_xticks(theta) plt.axis('off') 

que crea la siguiente imagen:

radialbartchart_nolabels

Después de crear esto, me gustaría agregar tags, pero tengo algunos problemas para encontrar las coordenadas correctas. Las tags deben girarse a lo largo de las direcciones de las barras.

Lo mejor que he encontrado es agregar el siguiente código:

 rotations = [np.degrees(i) for i in theta] for i in rotations: i = int(i) for x, bar, rotation, label in zip(theta, bars, rotations, lObjectsALLlbls): height = bar.get_height() + 50 ax.text(x + bar.get_width()/2, height, label, ha='center', va='bottom', rotation=rotation) 

que crea lo siguiente:

radialbarchart_wlabels

¿Pueden ayudarme algunos a encontrar las coordenadas correctas para las tags? He estado buscando respuestas como Agregar tags de valor en un gráfico de barras de matplotlib y traducirlas al gráfico de barras polar. Pero sin éxito.

Gracias por adelantado,

Un lector de mucho tiempo en StackOverflow, pero por primera vez no pude encontrar una respuesta.

El problema con el que se encuentra es que el cuadro delimitador de texto se expande para alojar el texto girado completo, pero ese cuadro aún está definido en coordenadas cartesianas. La imagen de abajo muestra dos textos con alineación horizontal “izquierda” y alineación vertical “abajo”; el problema es que el texto girado tiene su borde del recuadro delimitador mucho más alejado del texto.

introduzca la descripción de la imagen aquí

Lo que desea es más bien que el texto gire alrededor de un borde de su propio entorno como se muestra a continuación.

introduzca la descripción de la imagen aquí

Esto se puede lograr usando el argumento rotation_mode="anchor" para matplotlib.text.Text , que dirige exactamente la funcionalidad anterior.

 ax.text(..., rotation_mode="anchor") 

En este ejemplo:

 from matplotlib import pyplot as plt import numpy as np lObjectsALLcnts = [1, 1, 1, 2, 2, 3, 5, 14, 15, 20, 32, 33, 51, 1, 1, 2, 2, 3, 3, 3, 3, 3, 4, 6, 7, 7, 10, 10, 14, 14, 14, 17, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 5, 5, 6, 14, 14, 27, 27, 1, 1, 2, 3, 4, 4, 5] lObjectsALLlbls = ['DuctPipe', 'Column', 'Protrusion', 'Tree', 'Pole', 'Bar', 'Undefined', 'EarthingConductor', 'Grooves', 'UtilityPipe', 'Cables', 'RainPipe', 'Moulding', 'Intrusion', 'PowerPlug', 'UtilityBox', 'Balcony', 'Lighting', 'Lock', 'Doorbell', 'Alarm', 'LetterBox', 'Grate', 'Undefined', 'CableBox', 'Canopy', 'Vent', 'PowerBox', 'UtilityHole', 'Recess', 'Protrusion', 'Shutter', 'Handrail', 'Lock', 'Mirror', 'SecuritySpike', 'Bench', 'Intrusion', 'Picture', 'Showcase', 'Camera', 'Undefined', 'Stair', 'Protrusion', 'Alarm', 'Graffiti', 'Lighting', 'Ornaments', 'SecurityBar', 'Grate', 'Vent', 'Lighting', 'UtilityHole', 'Intrusion', 'Undefined', 'Protrusion'] iN = len(lObjectsALLcnts) arrCnts = np.array(lObjectsALLcnts) theta=np.arange(0,2*np.pi,2*np.pi/iN) width = (2*np.pi)/iN *0.9 bottom = 50 fig = plt.figure(figsize=(8,8)) ax = fig.add_axes([0.1, 0.1, 0.75, 0.75], polar=True) bars = ax.bar(theta, arrCnts, width=width, bottom=bottom) plt.axis('off') rotations = np.rad2deg(theta) for x, bar, rotation, label in zip(theta, bars, rotations, lObjectsALLlbls): lab = ax.text(x,bottom+bar.get_height() , label, ha='left', va='center', rotation=rotation, rotation_mode="anchor",) plt.show() 

introduzca la descripción de la imagen aquí

Tenga en cuenta que esto utiliza las 50 unidades dadas de espacio inferior. Puede boost este número un poco para tener más espacio entre las barras y el texto.


La siguiente versión inicial de esta respuesta está desactualizada. Lo mantendré aquí como referencia.

El problema con el que se encuentra es que el cuadro delimitador de texto se expande para alojar el texto girado completo, pero ese cuadro aún está definido en coordenadas cartesianas. La imagen de abajo muestra dos textos con alineación horizontal “izquierda” y alineación vertical “abajo”; el problema es que el texto girado tiene su borde del recuadro delimitador mucho más alejado del texto.

introduzca la descripción de la imagen aquí

Una solución fácil puede ser definir la alineación horizontal y vertical como “centro”, ya que el pivote del texto permanece igual independientemente de su rotación.

introduzca la descripción de la imagen aquí

El problema sería obtener una buena estimación de la distancia entre el centro del texto y la parte superior de la barra.

Uno podría tomar la mitad del número de letras en el texto y multiplicarlo con algún factor. Esto debería ser encontrado por prueba y error.

 bottom = 50 rotations = np.rad2deg(theta) y0,y1 = ax.get_ylim() for x, bar, rotation, label in zip(theta, bars, rotations, lObjectsALLlbls): offset = (bottom+bar.get_height())/(y1-y0) h =offset + len(label)/2.*0.032 lab = ax.text(x, h, label, transform=ax.get_xaxis_transform(), ha='center', va='center') lab.set_rotation(rotation) 

También puede intentar averiguar qué tan grande es el texto representado y usar esta información para averiguar las coordenadas,

 bottom = 50 rotations = np.rad2deg(theta) y0,y1 = ax.get_ylim() for x, bar, rotation, label in zip(theta, bars, rotations, lObjectsALLlbls): offset = (bottom+bar.get_height())/(y1-y0) lab = ax.text(0, 0, label, transform=None, ha='center', va='center') renderer = ax.figure.canvas.get_renderer() bbox = lab.get_window_extent(renderer=renderer) invb = ax.transData.inverted().transform([[0,0],[bbox.width,0] ]) lab.set_position((x,offset+(invb[1][0]-invb[0][0])/2.*2.7 ) ) lab.set_transform(ax.get_xaxis_transform()) lab.set_rotation(rotation) 

introduzca la descripción de la imagen aquí

Código completo para su reproducción:

 import numpy as np import matplotlib.pyplot as plt lObjectsALLcnts = [1, 1, 1, 2, 2, 3, 5, 14, 15, 20, 32, 33, 51, 1, 1, 2, 2, 3, 3, 3, 3, 3, 4, 6, 7, 7, 10, 10, 14, 14, 14, 17, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 5, 5, 6, 14, 14, 27, 27, 1, 1, 2, 3, 4, 4, 5] lObjectsALLlbls = ['DuctPipe', 'Column', 'Protrusion', 'Tree', 'Pole', 'Bar', 'Undefined', 'EarthingConductor', 'Grooves', 'UtilityPipe', 'Cables', 'RainPipe', 'Moulding', 'Intrusion', 'PowerPlug', 'UtilityBox', 'Balcony', 'Lighting', 'Lock', 'Doorbell', 'Alarm', 'LetterBox', 'Grate', 'Undefined', 'CableBox', 'Canopy', 'Vent', 'PowerBox', 'UtilityHole', 'Recess', 'Protrusion', 'Shutter', 'Handrail', 'Lock', 'Mirror', 'SecuritySpike', 'Bench', 'Intrusion', 'Picture', 'Showcase', 'Camera', 'Undefined', 'Stair', 'Protrusion', 'Alarm', 'Graffiti', 'Lighting', 'Ornaments', 'SecurityBar', 'Grate', 'Vent', 'Lighting', 'UtilityHole', 'Intrusion', 'Undefined', 'Protrusion'] iN = len(lObjectsALLcnts) arrCnts = np.array(lObjectsALLcnts) theta=np.arange(0,2*np.pi,2*np.pi/iN) width = (2*np.pi)/iN *0.9 bottom = 50 fig = plt.figure(figsize=(8,8)) ax = fig.add_axes([0.1, 0.1, 0.75, 0.75], polar=True) bars = ax.bar(theta, arrCnts, width=width, bottom=bottom) plt.axis('off') rotations = np.rad2deg(theta) y0,y1 = ax.get_ylim() for x, bar, rotation, label in zip(theta, bars, rotations, lObjectsALLlbls): offset = (bottom+bar.get_height())/(y1-y0) lab = ax.text(0, 0, label, transform=None, ha='center', va='center') renderer = ax.figure.canvas.get_renderer() bbox = lab.get_window_extent(renderer=renderer) invb = ax.transData.inverted().transform([[0,0],[bbox.width,0] ]) lab.set_position((x,offset+(invb[1][0]-invb[0][0])/2.*2.7 ) ) lab.set_transform(ax.get_xaxis_transform()) lab.set_rotation(rotation) plt.show()