summaryrefslogtreecommitdiffstats
path: root/contrib/python/fonttools/fontTools/varLib/plot.py
diff options
context:
space:
mode:
authorshumkovnd <[email protected]>2023-11-10 14:39:34 +0300
committershumkovnd <[email protected]>2023-11-10 16:42:24 +0300
commit77eb2d3fdcec5c978c64e025ced2764c57c00285 (patch)
treec51edb0748ca8d4a08d7c7323312c27ba1a8b79a /contrib/python/fonttools/fontTools/varLib/plot.py
parentdd6d20cadb65582270ac23f4b3b14ae189704b9d (diff)
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/fonttools/fontTools/varLib/plot.py')
-rw-r--r--contrib/python/fonttools/fontTools/varLib/plot.py238
1 files changed, 238 insertions, 0 deletions
diff --git a/contrib/python/fonttools/fontTools/varLib/plot.py b/contrib/python/fonttools/fontTools/varLib/plot.py
new file mode 100644
index 00000000000..e0a7ca50d3f
--- /dev/null
+++ b/contrib/python/fonttools/fontTools/varLib/plot.py
@@ -0,0 +1,238 @@
+"""Visualize DesignSpaceDocument and resulting VariationModel."""
+
+from fontTools.varLib.models import VariationModel, supportScalar
+from fontTools.designspaceLib import DesignSpaceDocument
+from matplotlib import pyplot
+from mpl_toolkits.mplot3d import axes3d
+from itertools import cycle
+import math
+import logging
+import sys
+
+log = logging.getLogger(__name__)
+
+
+def stops(support, count=10):
+ a, b, c = support
+
+ return (
+ [a + (b - a) * i / count for i in range(count)]
+ + [b + (c - b) * i / count for i in range(count)]
+ + [c]
+ )
+
+
+def _plotLocationsDots(locations, axes, subplot, **kwargs):
+ for loc, color in zip(locations, cycle(pyplot.cm.Set1.colors)):
+ if len(axes) == 1:
+ subplot.plot([loc.get(axes[0], 0)], [1.0], "o", color=color, **kwargs)
+ elif len(axes) == 2:
+ subplot.plot(
+ [loc.get(axes[0], 0)],
+ [loc.get(axes[1], 0)],
+ [1.0],
+ "o",
+ color=color,
+ **kwargs,
+ )
+ else:
+ raise AssertionError(len(axes))
+
+
+def plotLocations(locations, fig, names=None, **kwargs):
+ n = len(locations)
+ cols = math.ceil(n**0.5)
+ rows = math.ceil(n / cols)
+
+ if names is None:
+ names = [None] * len(locations)
+
+ model = VariationModel(locations)
+ names = [names[model.reverseMapping[i]] for i in range(len(names))]
+
+ axes = sorted(locations[0].keys())
+ if len(axes) == 1:
+ _plotLocations2D(model, axes[0], fig, cols, rows, names=names, **kwargs)
+ elif len(axes) == 2:
+ _plotLocations3D(model, axes, fig, cols, rows, names=names, **kwargs)
+ else:
+ raise ValueError("Only 1 or 2 axes are supported")
+
+
+def _plotLocations2D(model, axis, fig, cols, rows, names, **kwargs):
+ subplot = fig.add_subplot(111)
+ for i, (support, color, name) in enumerate(
+ zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names))
+ ):
+ if name is not None:
+ subplot.set_title(name)
+ subplot.set_xlabel(axis)
+ pyplot.xlim(-1.0, +1.0)
+
+ Xs = support.get(axis, (-1.0, 0.0, +1.0))
+ X, Y = [], []
+ for x in stops(Xs):
+ y = supportScalar({axis: x}, support)
+ X.append(x)
+ Y.append(y)
+ subplot.plot(X, Y, color=color, **kwargs)
+
+ _plotLocationsDots(model.locations, [axis], subplot)
+
+
+def _plotLocations3D(model, axes, fig, rows, cols, names, **kwargs):
+ ax1, ax2 = axes
+
+ axis3D = fig.add_subplot(111, projection="3d")
+ for i, (support, color, name) in enumerate(
+ zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names))
+ ):
+ if name is not None:
+ axis3D.set_title(name)
+ axis3D.set_xlabel(ax1)
+ axis3D.set_ylabel(ax2)
+ pyplot.xlim(-1.0, +1.0)
+ pyplot.ylim(-1.0, +1.0)
+
+ Xs = support.get(ax1, (-1.0, 0.0, +1.0))
+ Ys = support.get(ax2, (-1.0, 0.0, +1.0))
+ for x in stops(Xs):
+ X, Y, Z = [], [], []
+ for y in Ys:
+ z = supportScalar({ax1: x, ax2: y}, support)
+ X.append(x)
+ Y.append(y)
+ Z.append(z)
+ axis3D.plot(X, Y, Z, color=color, **kwargs)
+ for y in stops(Ys):
+ X, Y, Z = [], [], []
+ for x in Xs:
+ z = supportScalar({ax1: x, ax2: y}, support)
+ X.append(x)
+ Y.append(y)
+ Z.append(z)
+ axis3D.plot(X, Y, Z, color=color, **kwargs)
+
+ _plotLocationsDots(model.locations, [ax1, ax2], axis3D)
+
+
+def plotDocument(doc, fig, **kwargs):
+ doc.normalize()
+ locations = [s.location for s in doc.sources]
+ names = [s.name for s in doc.sources]
+ plotLocations(locations, fig, names, **kwargs)
+
+
+def _plotModelFromMasters2D(model, masterValues, fig, **kwargs):
+ assert len(model.axisOrder) == 1
+ axis = model.axisOrder[0]
+
+ axis_min = min(loc.get(axis, 0) for loc in model.locations)
+ axis_max = max(loc.get(axis, 0) for loc in model.locations)
+
+ import numpy as np
+
+ X = np.arange(axis_min, axis_max, (axis_max - axis_min) / 100)
+ Y = []
+
+ for x in X:
+ loc = {axis: x}
+ v = model.interpolateFromMasters(loc, masterValues)
+ Y.append(v)
+
+ subplot = fig.add_subplot(111)
+ subplot.plot(X, Y, "-", **kwargs)
+
+
+def _plotModelFromMasters3D(model, masterValues, fig, **kwargs):
+ assert len(model.axisOrder) == 2
+ axis1, axis2 = model.axisOrder[0], model.axisOrder[1]
+
+ axis1_min = min(loc.get(axis1, 0) for loc in model.locations)
+ axis1_max = max(loc.get(axis1, 0) for loc in model.locations)
+ axis2_min = min(loc.get(axis2, 0) for loc in model.locations)
+ axis2_max = max(loc.get(axis2, 0) for loc in model.locations)
+
+ import numpy as np
+
+ X = np.arange(axis1_min, axis1_max, (axis1_max - axis1_min) / 100)
+ Y = np.arange(axis2_min, axis2_max, (axis2_max - axis2_min) / 100)
+ X, Y = np.meshgrid(X, Y)
+ Z = []
+
+ for row_x, row_y in zip(X, Y):
+ z_row = []
+ Z.append(z_row)
+ for x, y in zip(row_x, row_y):
+ loc = {axis1: x, axis2: y}
+ v = model.interpolateFromMasters(loc, masterValues)
+ z_row.append(v)
+ Z = np.array(Z)
+
+ axis3D = fig.add_subplot(111, projection="3d")
+ axis3D.plot_surface(X, Y, Z, **kwargs)
+
+
+def plotModelFromMasters(model, masterValues, fig, **kwargs):
+ """Plot a variation model and set of master values corresponding
+ to the locations to the model into a pyplot figure. Variation
+ model must have axisOrder of size 1 or 2."""
+ if len(model.axisOrder) == 1:
+ _plotModelFromMasters2D(model, masterValues, fig, **kwargs)
+ elif len(model.axisOrder) == 2:
+ _plotModelFromMasters3D(model, masterValues, fig, **kwargs)
+ else:
+ raise ValueError("Only 1 or 2 axes are supported")
+
+
+def main(args=None):
+ from fontTools import configLogger
+
+ if args is None:
+ args = sys.argv[1:]
+
+ # configure the library logger (for >= WARNING)
+ configLogger()
+ # comment this out to enable debug messages from logger
+ # log.setLevel(logging.DEBUG)
+
+ if len(args) < 1:
+ print("usage: fonttools varLib.plot source.designspace", file=sys.stderr)
+ print(" or")
+ print("usage: fonttools varLib.plot location1 location2 ...", file=sys.stderr)
+ print(" or")
+ print(
+ "usage: fonttools varLib.plot location1=value1 location2=value2 ...",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+ fig = pyplot.figure()
+ fig.set_tight_layout(True)
+
+ if len(args) == 1 and args[0].endswith(".designspace"):
+ doc = DesignSpaceDocument()
+ doc.read(args[0])
+ plotDocument(doc, fig)
+ else:
+ axes = [chr(c) for c in range(ord("A"), ord("Z") + 1)]
+ if "=" not in args[0]:
+ locs = [dict(zip(axes, (float(v) for v in s.split(",")))) for s in args]
+ plotLocations(locs, fig)
+ else:
+ locations = []
+ masterValues = []
+ for arg in args:
+ loc, v = arg.split("=")
+ locations.append(dict(zip(axes, (float(v) for v in loc.split(",")))))
+ masterValues.append(float(v))
+ model = VariationModel(locations, axes[: len(locations[0])])
+ plotModelFromMasters(model, masterValues, fig)
+
+ pyplot.show()
+
+
+if __name__ == "__main__":
+ import sys
+
+ sys.exit(main())