You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
209 lines
7.1 KiB
209 lines
7.1 KiB
2 years ago
|
import math
|
||
|
import numpy as np
|
||
|
|
||
|
# import matplotlib
|
||
|
# import matplotlib.pyplot as plt
|
||
|
# import matplotlib.ticker as tckr
|
||
|
# import matplotlib.transforms as mtransforms
|
||
|
# import matplotlib.mlab as mlab
|
||
|
|
||
|
# An alpha version of the Talbot, Lin, Hanrahan tick mark generator for matplotlib.
|
||
|
# Described in "An Extension of Wilkinson's Algorithm for Positioning Tick Labels on Axes"
|
||
|
# by Justin Talbot, Sharon Lin, and Pat Hanrahan, InfoVis 2010.
|
||
|
|
||
|
# Implementation by Justin Talbot
|
||
|
# This implementation is in the public domain.
|
||
|
# Report bugs to jtalbot@stanford.edu
|
||
|
|
||
|
# A shortcoming:
|
||
|
# The weights used in the paper were designed for static plots where the extent of
|
||
|
# the tick marks unioned with the extent of the data defines the extent of the plot.
|
||
|
# In a plot where the extent of the plot is defined by the user (e.g. an interactive
|
||
|
# plot supporting panning and zooming), the weights don't work as well. In particular,
|
||
|
# you would want to retune them assuming that the tick labels must be inside
|
||
|
# the provided view range. You probably want higher weighting on simplicity and lower
|
||
|
# on coverage and possibly density. But I haven't experimented in any detail with this.
|
||
|
#
|
||
|
# If you do intend on using this for static plots in matplotlib, you should set
|
||
|
# only_inside to False in the call to Extended.extended. And then you should
|
||
|
# manually set your view extent to include the min and max ticks if they are outside
|
||
|
# the data range. This should produce the same results as the paper.
|
||
|
|
||
|
# class Extended(tckr.Locator):
|
||
|
class Extended:
|
||
|
# density is labels per inch
|
||
|
def __init__(self, density=1, steps=None, figure=None, range=(0, 1), axis="x"):
|
||
|
"""
|
||
|
Keyword args:
|
||
|
"""
|
||
|
self._density = density
|
||
|
self._figure = figure
|
||
|
self._axis = axis
|
||
|
self.range = range
|
||
|
|
||
|
if steps is None:
|
||
|
self._steps = [1, 5, 2, 2.5, 4, 3]
|
||
|
else:
|
||
|
self._steps = steps
|
||
|
|
||
|
def coverage(self, dmin, dmax, lmin, lmax):
|
||
|
range = dmax - dmin
|
||
|
return 1 - 0.5 * (
|
||
|
math.pow(dmax - lmax, 2) + math.pow(dmin - lmin, 2)
|
||
|
) / math.pow(0.1 * range, 2)
|
||
|
|
||
|
def coverage_max(self, dmin, dmax, span):
|
||
|
range = dmax - dmin
|
||
|
if span > range:
|
||
|
half = (span - range) / 2.0
|
||
|
return 1 - math.pow(half, 2) / math.pow(0.1 * range, 2)
|
||
|
else:
|
||
|
return 1
|
||
|
|
||
|
def density(self, k, m, dmin, dmax, lmin, lmax):
|
||
|
r = (k - 1.0) / (lmax - lmin)
|
||
|
rt = (m - 1.0) / (max(lmax, dmax) - min(lmin, dmin))
|
||
|
return 2 - max(r / rt, rt / r)
|
||
|
|
||
|
def density_max(self, k, m):
|
||
|
if k >= m:
|
||
|
return 2 - (k - 1.0) / (m - 1.0)
|
||
|
else:
|
||
|
return 1
|
||
|
|
||
|
def simplicity(self, q, Q, j, lmin, lmax, lstep):
|
||
|
eps = 1e-10
|
||
|
n = len(Q)
|
||
|
i = Q.index(q) + 1
|
||
|
v = (
|
||
|
1
|
||
|
if (
|
||
|
(lmin % lstep < eps or (lstep - lmin % lstep) < eps)
|
||
|
and lmin <= 0
|
||
|
and lmax >= 0
|
||
|
)
|
||
|
else 0
|
||
|
)
|
||
|
return (n - i) / (n - 1.0) + v - j
|
||
|
|
||
|
def simplicity_max(self, q, Q, j):
|
||
|
n = len(Q)
|
||
|
i = Q.index(q) + 1
|
||
|
v = 1
|
||
|
return (n - i) / (n - 1.0) + v - j
|
||
|
|
||
|
def legibility(self, lmin, lmax, lstep):
|
||
|
return 1
|
||
|
|
||
|
def legibility_max(self, lmin, lmax, lstep):
|
||
|
return 1
|
||
|
|
||
|
def extended(
|
||
|
self,
|
||
|
dmin,
|
||
|
dmax,
|
||
|
m,
|
||
|
Q=[1, 5, 2, 2.5, 4, 3],
|
||
|
only_inside=False,
|
||
|
w=[0.25, 0.2, 0.5, 0.05],
|
||
|
):
|
||
|
n = len(Q)
|
||
|
best_score = -2.0
|
||
|
|
||
|
j = 1.0
|
||
|
while j < float("infinity"):
|
||
|
for q in Q:
|
||
|
sm = self.simplicity_max(q, Q, j)
|
||
|
|
||
|
if w[0] * sm + w[1] + w[2] + w[3] < best_score:
|
||
|
j = float("infinity")
|
||
|
break
|
||
|
|
||
|
k = 2.0
|
||
|
while k < float("infinity"):
|
||
|
dm = self.density_max(k, m)
|
||
|
|
||
|
if w[0] * sm + w[1] + w[2] * dm + w[3] < best_score:
|
||
|
break
|
||
|
|
||
|
delta = (dmax - dmin) / (k + 1.0) / j / q
|
||
|
z = math.ceil(math.log(delta, 10))
|
||
|
|
||
|
while z < float("infinity"):
|
||
|
step = j * q * math.pow(10, z)
|
||
|
cm = self.coverage_max(dmin, dmax, step * (k - 1.0))
|
||
|
|
||
|
if w[0] * sm + w[1] * cm + w[2] * dm + w[3] < best_score:
|
||
|
break
|
||
|
|
||
|
min_start = math.floor(dmax / step) * j - (k - 1.0) * j
|
||
|
max_start = math.ceil(dmin / step) * j
|
||
|
|
||
|
if min_start > max_start:
|
||
|
z = z + 1
|
||
|
break
|
||
|
|
||
|
for start in range(int(min_start), int(max_start) + 1):
|
||
|
lmin = start * (step / j)
|
||
|
lmax = lmin + step * (k - 1.0)
|
||
|
lstep = step
|
||
|
|
||
|
s = self.simplicity(q, Q, j, lmin, lmax, lstep)
|
||
|
c = self.coverage(dmin, dmax, lmin, lmax)
|
||
|
d = self.density(k, m, dmin, dmax, lmin, lmax)
|
||
|
l = self.legibility(lmin, lmax, lstep)
|
||
|
|
||
|
score = w[0] * s + w[1] * c + w[2] * d + w[3] * l
|
||
|
|
||
|
if score > best_score and (
|
||
|
not only_inside or (lmin >= dmin and lmax <= dmax)
|
||
|
):
|
||
|
best_score = score
|
||
|
best = (lmin, lmax, lstep, q, k)
|
||
|
z = z + 1
|
||
|
k = k + 1
|
||
|
j = j + 1
|
||
|
return best
|
||
|
|
||
|
def __call__(self):
|
||
|
vmin, vmax = self.range # self.axis.get_view_interval()
|
||
|
fsize = {"x": 5.0, "y": 4.0}
|
||
|
size = fsize[self._axis] # self._figure.get_size_inches()[self._axis]
|
||
|
# density * size gives target number of intervals,
|
||
|
# density * size + 1 gives target number of tick marks,
|
||
|
# the density function converts this back to a density in data units (not inches)
|
||
|
# should probably make this cleaner.
|
||
|
best = self.extended(
|
||
|
vmin,
|
||
|
vmax,
|
||
|
self._density * size + 1.0,
|
||
|
only_inside=True,
|
||
|
w=[0.25, 0.2, 0.5, 0.05],
|
||
|
)
|
||
|
locs = np.arange(best[4]) * best[2] + best[0]
|
||
|
return locs
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
pass
|
||
|
# fig = plt.figure()
|
||
|
# ax = fig.add_subplot(111)
|
||
|
# ax.plot(10*np.random.randn(100), 10*np.random.randn(100), 'o')
|
||
|
#
|
||
|
# xmin, xmax = ax.xaxis.get_data_interval()
|
||
|
# xrange = xmax-xmin
|
||
|
# xmin, xmax = (xmin - xrange * 0.05, xmax + xrange * 0.05)
|
||
|
#
|
||
|
# ymin, ymax = ax.yaxis.get_data_interval()
|
||
|
# yrange = ymax-ymin
|
||
|
# ymin, ymax = (ymin - yrange * 0.05, ymax + yrange * 0.05)
|
||
|
#
|
||
|
# ax.xaxis.set_view_interval(xmin, xmax, ignore=True)
|
||
|
# ax.yaxis.set_view_interval(ymin, ymax, ignore=True)
|
||
|
# ax.xaxis.set_major_locator(Extended(density=0.5, figure=fig, which=0))
|
||
|
# ax.yaxis.set_major_locator(Extended(density=0.5, figure=fig, which=1))
|
||
|
#
|
||
|
# ax.set_title('Talbot, Lin, Hanrahan 2010')
|
||
|
#
|
||
|
# plt.show()
|