1"""Visualize DesignSpaceDocument and resulting VariationModel.""" 2 3from fontTools.varLib.models import VariationModel, supportScalar 4from fontTools.designspaceLib import DesignSpaceDocument 5from matplotlib import pyplot 6from mpl_toolkits.mplot3d import axes3d 7from itertools import cycle 8import math 9import logging 10import sys 11 12log = logging.getLogger(__name__) 13 14 15def stops(support, count=10): 16 a, b, c = support 17 18 return ( 19 [a + (b - a) * i / count for i in range(count)] 20 + [b + (c - b) * i / count for i in range(count)] 21 + [c] 22 ) 23 24 25def _plotLocationsDots(locations, axes, subplot, **kwargs): 26 for loc, color in zip(locations, cycle(pyplot.cm.Set1.colors)): 27 if len(axes) == 1: 28 subplot.plot([loc.get(axes[0], 0)], [1.0], "o", color=color, **kwargs) 29 elif len(axes) == 2: 30 subplot.plot( 31 [loc.get(axes[0], 0)], 32 [loc.get(axes[1], 0)], 33 [1.0], 34 "o", 35 color=color, 36 **kwargs, 37 ) 38 else: 39 raise AssertionError(len(axes)) 40 41 42def plotLocations(locations, fig, names=None, **kwargs): 43 n = len(locations) 44 cols = math.ceil(n**0.5) 45 rows = math.ceil(n / cols) 46 47 if names is None: 48 names = [None] * len(locations) 49 50 model = VariationModel(locations) 51 names = [names[model.reverseMapping[i]] for i in range(len(names))] 52 53 axes = sorted(locations[0].keys()) 54 if len(axes) == 1: 55 _plotLocations2D(model, axes[0], fig, cols, rows, names=names, **kwargs) 56 elif len(axes) == 2: 57 _plotLocations3D(model, axes, fig, cols, rows, names=names, **kwargs) 58 else: 59 raise ValueError("Only 1 or 2 axes are supported") 60 61 62def _plotLocations2D(model, axis, fig, cols, rows, names, **kwargs): 63 subplot = fig.add_subplot(111) 64 for i, (support, color, name) in enumerate( 65 zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names)) 66 ): 67 if name is not None: 68 subplot.set_title(name) 69 subplot.set_xlabel(axis) 70 pyplot.xlim(-1.0, +1.0) 71 72 Xs = support.get(axis, (-1.0, 0.0, +1.0)) 73 X, Y = [], [] 74 for x in stops(Xs): 75 y = supportScalar({axis: x}, support) 76 X.append(x) 77 Y.append(y) 78 subplot.plot(X, Y, color=color, **kwargs) 79 80 _plotLocationsDots(model.locations, [axis], subplot) 81 82 83def _plotLocations3D(model, axes, fig, rows, cols, names, **kwargs): 84 ax1, ax2 = axes 85 86 axis3D = fig.add_subplot(111, projection="3d") 87 for i, (support, color, name) in enumerate( 88 zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names)) 89 ): 90 if name is not None: 91 axis3D.set_title(name) 92 axis3D.set_xlabel(ax1) 93 axis3D.set_ylabel(ax2) 94 pyplot.xlim(-1.0, +1.0) 95 pyplot.ylim(-1.0, +1.0) 96 97 Xs = support.get(ax1, (-1.0, 0.0, +1.0)) 98 Ys = support.get(ax2, (-1.0, 0.0, +1.0)) 99 for x in stops(Xs): 100 X, Y, Z = [], [], [] 101 for y in Ys: 102 z = supportScalar({ax1: x, ax2: y}, support) 103 X.append(x) 104 Y.append(y) 105 Z.append(z) 106 axis3D.plot(X, Y, Z, color=color, **kwargs) 107 for y in stops(Ys): 108 X, Y, Z = [], [], [] 109 for x in Xs: 110 z = supportScalar({ax1: x, ax2: y}, support) 111 X.append(x) 112 Y.append(y) 113 Z.append(z) 114 axis3D.plot(X, Y, Z, color=color, **kwargs) 115 116 _plotLocationsDots(model.locations, [ax1, ax2], axis3D) 117 118 119def plotDocument(doc, fig, **kwargs): 120 doc.normalize() 121 locations = [s.location for s in doc.sources] 122 names = [s.name for s in doc.sources] 123 plotLocations(locations, fig, names, **kwargs) 124 125 126def _plotModelFromMasters2D(model, masterValues, fig, **kwargs): 127 assert len(model.axisOrder) == 1 128 axis = model.axisOrder[0] 129 130 axis_min = min(loc.get(axis, 0) for loc in model.locations) 131 axis_max = max(loc.get(axis, 0) for loc in model.locations) 132 133 import numpy as np 134 135 X = np.arange(axis_min, axis_max, (axis_max - axis_min) / 100) 136 Y = [] 137 138 for x in X: 139 loc = {axis: x} 140 v = model.interpolateFromMasters(loc, masterValues) 141 Y.append(v) 142 143 subplot = fig.add_subplot(111) 144 subplot.plot(X, Y, "-", **kwargs) 145 146 147def _plotModelFromMasters3D(model, masterValues, fig, **kwargs): 148 assert len(model.axisOrder) == 2 149 axis1, axis2 = model.axisOrder[0], model.axisOrder[1] 150 151 axis1_min = min(loc.get(axis1, 0) for loc in model.locations) 152 axis1_max = max(loc.get(axis1, 0) for loc in model.locations) 153 axis2_min = min(loc.get(axis2, 0) for loc in model.locations) 154 axis2_max = max(loc.get(axis2, 0) for loc in model.locations) 155 156 import numpy as np 157 158 X = np.arange(axis1_min, axis1_max, (axis1_max - axis1_min) / 100) 159 Y = np.arange(axis2_min, axis2_max, (axis2_max - axis2_min) / 100) 160 X, Y = np.meshgrid(X, Y) 161 Z = [] 162 163 for row_x, row_y in zip(X, Y): 164 z_row = [] 165 Z.append(z_row) 166 for x, y in zip(row_x, row_y): 167 loc = {axis1: x, axis2: y} 168 v = model.interpolateFromMasters(loc, masterValues) 169 z_row.append(v) 170 Z = np.array(Z) 171 172 axis3D = fig.add_subplot(111, projection="3d") 173 axis3D.plot_surface(X, Y, Z, **kwargs) 174 175 176def plotModelFromMasters(model, masterValues, fig, **kwargs): 177 """Plot a variation model and set of master values corresponding 178 to the locations to the model into a pyplot figure. Variation 179 model must have axisOrder of size 1 or 2.""" 180 if len(model.axisOrder) == 1: 181 _plotModelFromMasters2D(model, masterValues, fig, **kwargs) 182 elif len(model.axisOrder) == 2: 183 _plotModelFromMasters3D(model, masterValues, fig, **kwargs) 184 else: 185 raise ValueError("Only 1 or 2 axes are supported") 186 187 188def main(args=None): 189 from fontTools import configLogger 190 191 if args is None: 192 args = sys.argv[1:] 193 194 # configure the library logger (for >= WARNING) 195 configLogger() 196 # comment this out to enable debug messages from logger 197 # log.setLevel(logging.DEBUG) 198 199 if len(args) < 1: 200 print("usage: fonttools varLib.plot source.designspace", file=sys.stderr) 201 print(" or") 202 print("usage: fonttools varLib.plot location1 location2 ...", file=sys.stderr) 203 print(" or") 204 print( 205 "usage: fonttools varLib.plot location1=value1 location2=value2 ...", 206 file=sys.stderr, 207 ) 208 sys.exit(1) 209 210 fig = pyplot.figure() 211 fig.set_tight_layout(True) 212 213 if len(args) == 1 and args[0].endswith(".designspace"): 214 doc = DesignSpaceDocument() 215 doc.read(args[0]) 216 plotDocument(doc, fig) 217 else: 218 axes = [chr(c) for c in range(ord("A"), ord("Z") + 1)] 219 if "=" not in args[0]: 220 locs = [dict(zip(axes, (float(v) for v in s.split(",")))) for s in args] 221 plotLocations(locs, fig) 222 else: 223 locations = [] 224 masterValues = [] 225 for arg in args: 226 loc, v = arg.split("=") 227 locations.append(dict(zip(axes, (float(v) for v in loc.split(","))))) 228 masterValues.append(float(v)) 229 model = VariationModel(locations, axes[: len(locations[0])]) 230 plotModelFromMasters(model, masterValues, fig) 231 232 pyplot.show() 233 234 235if __name__ == "__main__": 236 import sys 237 238 sys.exit(main()) 239