kopia lustrzana https://github.com/marceloprates/prettymaps
169 wiersze
5.3 KiB
Python
169 wiersze
5.3 KiB
Python
import math
|
|
|
|
from matplotlib import text as mtext
|
|
import numpy as np
|
|
|
|
|
|
class CurvedText(mtext.Text):
|
|
"""
|
|
A text object that follows an arbitrary curve.
|
|
"""
|
|
|
|
def __init__(self, x, y, text, axes, **kwargs):
|
|
super(CurvedText, self).__init__(x[0], y[0], " ", **kwargs)
|
|
|
|
axes.add_artist(self)
|
|
|
|
##saving the curve:
|
|
self.__x = x
|
|
self.__y = y
|
|
self.__zorder = self.get_zorder()
|
|
|
|
##creating the text objects
|
|
self.__Characters = []
|
|
for c in text:
|
|
if c == " ":
|
|
##make this an invisible 'a':
|
|
t = mtext.Text(0, 0, "a")
|
|
t.set_alpha(0.0)
|
|
else:
|
|
t = mtext.Text(0, 0, c, **kwargs)
|
|
|
|
# resetting unnecessary arguments
|
|
t.set_ha("center")
|
|
t.set_rotation(0)
|
|
t.set_zorder(self.__zorder + 1)
|
|
|
|
self.__Characters.append((c, t))
|
|
axes.add_artist(t)
|
|
|
|
##overloading some member functions, to assure correct functionality
|
|
##on update
|
|
def set_zorder(self, zorder):
|
|
super(CurvedText, self).set_zorder(zorder)
|
|
self.__zorder = self.get_zorder()
|
|
for c, t in self.__Characters:
|
|
t.set_zorder(self.__zorder + 1)
|
|
|
|
def draw(self, renderer, *args, **kwargs):
|
|
"""
|
|
Overload of the Text.draw() function. Do not do
|
|
do any drawing, but update the positions and rotation
|
|
angles of self.__Characters.
|
|
"""
|
|
self.update_positions(renderer)
|
|
|
|
def update_positions(self, renderer):
|
|
"""
|
|
Update positions and rotations of the individual text elements.
|
|
"""
|
|
|
|
# preparations
|
|
|
|
##determining the aspect ratio:
|
|
##from https://stackoverflow.com/a/42014041/2454357
|
|
|
|
##data limits
|
|
xlim = self.axes.get_xlim()
|
|
ylim = self.axes.get_ylim()
|
|
## Axis size on figure
|
|
figW, figH = self.axes.get_figure().get_size_inches()
|
|
## Ratio of display units
|
|
_, _, w, h = self.axes.get_position().bounds
|
|
##final aspect ratio
|
|
aspect = ((figW * w) / (figH * h)) * (ylim[1] - ylim[0]) / (xlim[1] - xlim[0])
|
|
|
|
# points of the curve in figure coordinates:
|
|
x_fig, y_fig = (
|
|
np.array(l)
|
|
for l in zip(
|
|
*self.axes.transData.transform(
|
|
[(i, j) for i, j in zip(self.__x, self.__y)]
|
|
)
|
|
)
|
|
)
|
|
|
|
# point distances in figure coordinates
|
|
x_fig_dist = x_fig[1:] - x_fig[:-1]
|
|
y_fig_dist = y_fig[1:] - y_fig[:-1]
|
|
r_fig_dist = np.sqrt(x_fig_dist ** 2 + y_fig_dist ** 2)
|
|
|
|
# arc length in figure coordinates
|
|
l_fig = np.insert(np.cumsum(r_fig_dist), 0, 0)
|
|
|
|
# angles in figure coordinates
|
|
rads = np.arctan2((y_fig[1:] - y_fig[:-1]), (x_fig[1:] - x_fig[:-1]))
|
|
degs = np.rad2deg(rads)
|
|
|
|
rel_pos = 10
|
|
for c, t in self.__Characters:
|
|
# finding the width of c:
|
|
t.set_rotation(0)
|
|
t.set_va("center")
|
|
bbox1 = t.get_window_extent(renderer=renderer)
|
|
w = bbox1.width
|
|
h = bbox1.height
|
|
|
|
# ignore all letters that don't fit:
|
|
if rel_pos + w / 2 > l_fig[-1]:
|
|
t.set_alpha(0.0)
|
|
rel_pos += w
|
|
continue
|
|
|
|
elif c != " ":
|
|
t.set_alpha(1.0)
|
|
|
|
# finding the two data points between which the horizontal
|
|
# center point of the character will be situated
|
|
# left and right indices:
|
|
il = np.where(rel_pos + w / 2 >= l_fig)[0][-1]
|
|
ir = np.where(rel_pos + w / 2 <= l_fig)[0][0]
|
|
|
|
# if we exactly hit a data point:
|
|
if ir == il:
|
|
ir += 1
|
|
|
|
# how much of the letter width was needed to find il:
|
|
used = l_fig[il] - rel_pos
|
|
rel_pos = l_fig[il]
|
|
|
|
# relative distance between il and ir where the center
|
|
# of the character will be
|
|
fraction = (w / 2 - used) / r_fig_dist[il]
|
|
|
|
##setting the character position in data coordinates:
|
|
##interpolate between the two points:
|
|
x = self.__x[il] + fraction * (self.__x[ir] - self.__x[il])
|
|
y = self.__y[il] + fraction * (self.__y[ir] - self.__y[il])
|
|
|
|
# getting the offset when setting correct vertical alignment
|
|
# in data coordinates
|
|
t.set_va(self.get_va())
|
|
bbox2 = t.get_window_extent(renderer=renderer)
|
|
|
|
bbox1d = self.axes.transData.inverted().transform(bbox1)
|
|
bbox2d = self.axes.transData.inverted().transform(bbox2)
|
|
dr = np.array(bbox2d[0] - bbox1d[0])
|
|
|
|
# the rotation/stretch matrix
|
|
rad = rads[il]
|
|
rot_mat = np.array(
|
|
[
|
|
[math.cos(rad), math.sin(rad) * aspect],
|
|
[-math.sin(rad) / aspect, math.cos(rad)],
|
|
]
|
|
)
|
|
|
|
##computing the offset vector of the rotated character
|
|
drp = np.dot(dr, rot_mat)
|
|
|
|
# setting final position and rotation:
|
|
t.set_position(np.array([x, y]) + drp)
|
|
t.set_rotation(degs[il])
|
|
|
|
t.set_va("center")
|
|
t.set_ha("center")
|
|
|
|
# updating rel_pos to right edge of character
|
|
rel_pos += w - used
|