Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I curve text in a polar plot?

Hello Matplotlib Experts,

How do I curve text in a matplotlib polar plot? In my attempt below, my code rotates each char individually, but doing so would remove the natural spacing of each font. Can somebody describe a solution for passing ax.text in matplotlib?

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

def curveText(text, height, minTheta, maxTheta, ax):
    interval = np.arange(minTheta, maxTheta, .022)
    if( maxTheta <= np.pi):
        progression = interval[::-1]
        rotation = interval[::-1] - np.arctan(np.tan(np.pi/2))
    else:
        progression = interval
        rotation = interval - np.arctan(np.tan(np.pi/2)) - np.pi

    ## Render each letter individually
    for i, rot, t in zip(progression, rotation, text):
        ax.text(i, height, t, fontsize=11,rotation=np.degrees(rot), ha='center', va='center')

def buildCircularHeatMap( data=None, label=None, cmaps=None, categorymap=None, vmin=0, vmax=None ):
    (xDim, yDim) = data.shape
    if cmaps == None:
        cmaps = [mpl.cm.get_cmap()] * yDim
    BOTTOM = xDim / 100 * 120
    #FONTSIZE = 1 if xDim/100*8 < 1 else xDim/100*8
    theta = np.linspace(0.0, 2 * np.pi - 5 * np.pi/180, xDim, endpoint=False)
    width = (2*np.pi - 5 * np.pi/180)/xDim
    ax = plt.subplot(111, polar=True)
    ax.grid(False)
    ax.set_yticklabels([])
    ax.set_xticklabels([])
    categorysum = np.zeros(len(categorymap))
    for x in label:
        categorysum[int(float( x )) - 1] += 1
    categorysum = categorysum/np.sum(categorysum)*2*np.pi

    ## Build Face Color Values
    for i in range(yDim):
        cmap_scalar = mpl.cm.ScalarMappable(cmap=cmaps[i])
        cmap_scalar.set_clim(vmin=vmin, vmax=vmax)
        facecolor = cmap_scalar.to_rgba(data[:,i])
        _ = ax.text(2 * np.pi - 5 * np.pi/180, BOTTOM+i*10, str(i), fontsize=11, rotation=np.degrees(270))
        bars = ax.bar(theta, np.ones(xDim)*10, width=width, bottom=BOTTOM+i*10)
        for j, b in enumerate(bars):
            b.set_facecolor( facecolor[j] )

    ## Build CCS Label
    for th, l, bar in zip(theta, label, bars):
        rot = np.arctan(np.tan(th))
        ax.text(th,BOTTOM+yDim*10+bar.get_height()+5, l, rotation_mode='anchor',
             rotation=np.degrees(rot), fontsize=11, ha='center', va='center')

    ## Build Category Label
    categoryColor = np.asarray([int(float(c)) for c in label])
    bars = ax.bar(theta, np.ones(xDim)*20, width=width, bottom=BOTTOM+yDim*10 + 30)
    for j, b in enumerate(bars):
        b.set_facecolor(np.asarray([0.0,0.0,0.0]))
        if categoryColor[j] % 2 == 0:
            b.set_alpha(0.07)
        else:
            b.set_alpha(0.0)

    for i in range(len(categorymap)):
        c = i + 1
        t = theta[categoryColor==c]
        mi = np.min(t)
        ma = np.max(t)
        rad = (ma-mi)/2+mi
        curveText(categorymap[c], BOTTOM+yDim*10+40, mi, ma, ax)

if __name__ == "__main__":
    categorymap={
        1: "Infectious & parasitic dieases",
        2: "Neoplasms",
        3: "Endocrine; nutritional; and metabolic diseases and immunity disorders",
        4: "Diseases of the blood and blood-forming organs",
        5: "Mental Illness",
        6: "Nervous system disorders",
        7: "Circulatory disorders",
        8: "Respiratory disorders",
        9: "Digestive disorders",
        10: "Genitourinary disorders",
        11: "Complications of pregnancy; childbirth; and the puerperium",
        12: "Skin and subcutaneous tissue disorder",
        13: "Musculoskeletal system and connective tissue disorder",
        14: "Congenital anomalies",
        15: "Certain conditions originating in the perinatal period",
        16: "Injury and poisoning",
        17: "Ill-defined status",
        18: "Unclassified"
    }
    data = np.random.standard_normal((180, 3))
    colormaps = [mpl.cm.get_cmap("Reds"), mpl.cm.get_cmap("Oranges"), mpl.cm.get_cmap("Greens"), mpl.cm.get_cmap("Blues")]
    labels = sorted([ '{:.2f}'.format(np.abs(i)) for i in np.random.random_sample(180) * 18 + 1 ])
    fig = plt.figure(figsize=(11,11))
    buildCircularHeatMap(data=data, label=labels, cmaps=colormaps, categorymap=categorymap)
    plt.show()

Polar plot with ugly spacing

In the link below, Thomas's answer seems only applicable for cartesian coordinates and my current attempt should be similar to Daan.

Curved text rendering in matplotlib

like image 525
user1462442 Avatar asked May 16 '20 22:05

user1462442


1 Answers

As @Makdous suggested above, Curved text rendering in matplotlib is a nice implementation of the problem. I read through the code, and you're right, it is in cartesian coordinates, but I think you could just modify it a bit and get it working using these formulas:

Cartestian to Polar conversions

You can also use this one line function I wrote:

from typing import Tuple
from math import sqrt, degrees, atan2
def cartesian_to_polar(x: float, y: float)-> Tuple[float, float]:
    return sqrt(x**2 + y ** 2), degrees(atan2(y,x))

Or, if you have polar coordinates and want to make it work with the script linked in the other response, you can use this:

from math import cos, sin, radians
def polar_to_cartesian(r: float, theta: float)-> Tuple[float, float]:
    return r * cos(radians(theta)), r * sin(radians(theta))

Depending on how you implemented it, you could feed it in the coordinates you have, then convert it appropriately to arrive at cartesian coordinates and run the linked script, then convert the points back to polar coordinates and plot it.

like image 160
v0rtex20k Avatar answered Nov 20 '22 08:11

v0rtex20k