1"""Visualize DesignSpaceDocument and resulting VariationModel.""" 2 3from __future__ import print_function, division, absolute_import 4from fontTools.misc.py23 import * 5from fontTools.varLib.models import VariationModel, supportScalar 6from fontTools.designspaceLib import DesignSpaceDocument 7from mpl_toolkits.mplot3d import axes3d 8from matplotlib import pyplot 9from itertools import cycle 10import math 11import logging 12import sys 13 14log = logging.getLogger(__name__) 15 16 17def stops(support, count=10): 18 a,b,c = support 19 20 return [a + (b - a) * i / count for i in range(count)] + \ 21 [b + (c - b) * i / count for i in range(count)] + \ 22 [c] 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( 29 [loc.get(axes[0], 0)], 30 [1.], 31 'o', 32 color=color, 33 **kwargs 34 ) 35 elif len(axes) == 2: 36 subplot.plot( 37 [loc.get(axes[0], 0)], 38 [loc.get(axes[1], 0)], 39 [1.], 40 'o', 41 color=color, 42 **kwargs 43 ) 44 else: 45 raise AssertionError(len(axes)) 46 47 48def plotLocations(locations, fig, names=None, **kwargs): 49 n = len(locations) 50 cols = math.ceil(n**.5) 51 rows = math.ceil(n / cols) 52 53 if names is None: 54 names = [None] * len(locations) 55 56 model = VariationModel(locations) 57 names = [names[model.reverseMapping[i]] for i in range(len(names))] 58 59 axes = sorted(locations[0].keys()) 60 if len(axes) == 1: 61 _plotLocations2D( 62 model, axes[0], fig, cols, rows, names=names, **kwargs 63 ) 64 elif len(axes) == 2: 65 _plotLocations3D( 66 model, axes, fig, cols, rows, names=names, **kwargs 67 ) 68 else: 69 raise ValueError("Only 1 or 2 axes are supported") 70 71 72def _plotLocations2D(model, axis, fig, cols, rows, names, **kwargs): 73 for i, (support, color, name) in enumerate( 74 zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names)) 75 ): 76 subplot = fig.add_subplot(rows, cols, i + 1) 77 if name is not None: 78 subplot.set_title(name) 79 subplot.set_xlabel(axis) 80 pyplot.xlim(-1.,+1.) 81 82 Xs = support.get(axis, (-1.,0.,+1.)) 83 X, Y = [], [] 84 for x in stops(Xs): 85 y = supportScalar({axis:x}, support) 86 X.append(x) 87 Y.append(y) 88 subplot.plot(X, Y, color=color, **kwargs) 89 90 _plotLocationsDots(model.locations, [axis], subplot) 91 92 93def _plotLocations3D(model, axes, fig, rows, cols, names, **kwargs): 94 ax1, ax2 = axes 95 96 for i, (support, color, name) in enumerate( 97 zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names)) 98 ): 99 axis3D = fig.add_subplot(rows, cols, i + 1, projection='3d') 100 if name is not None: 101 axis3D.set_title(name) 102 axis3D.set_xlabel(ax1) 103 axis3D.set_ylabel(ax2) 104 pyplot.xlim(-1.,+1.) 105 pyplot.ylim(-1.,+1.) 106 107 Xs = support.get(ax1, (-1.,0.,+1.)) 108 Ys = support.get(ax2, (-1.,0.,+1.)) 109 for x in stops(Xs): 110 X, Y, Z = [], [], [] 111 for y in Ys: 112 z = supportScalar({ax1:x, ax2:y}, support) 113 X.append(x) 114 Y.append(y) 115 Z.append(z) 116 axis3D.plot(X, Y, Z, color=color, **kwargs) 117 for y in stops(Ys): 118 X, Y, Z = [], [], [] 119 for x in Xs: 120 z = supportScalar({ax1:x, ax2:y}, support) 121 X.append(x) 122 Y.append(y) 123 Z.append(z) 124 axis3D.plot(X, Y, Z, color=color, **kwargs) 125 126 _plotLocationsDots(model.locations, [ax1, ax2], axis3D) 127 128 129def plotDocument(doc, fig, **kwargs): 130 doc.normalize() 131 locations = [s.location for s in doc.sources] 132 names = [s.name for s in doc.sources] 133 plotLocations(locations, fig, names, **kwargs) 134 135 136def main(args=None): 137 from fontTools import configLogger 138 139 if args is None: 140 args = sys.argv[1:] 141 142 # configure the library logger (for >= WARNING) 143 configLogger() 144 # comment this out to enable debug messages from logger 145 # log.setLevel(logging.DEBUG) 146 147 if len(args) < 1: 148 print("usage: fonttools varLib.plot source.designspace", file=sys.stderr) 149 print(" or") 150 print("usage: fonttools varLib.plot location1 location2 ...", file=sys.stderr) 151 sys.exit(1) 152 153 fig = pyplot.figure() 154 fig.set_tight_layout(True) 155 156 if len(args) == 1 and args[0].endswith('.designspace'): 157 doc = DesignSpaceDocument() 158 doc.read(args[0]) 159 plotDocument(doc, fig) 160 else: 161 axes = [chr(c) for c in range(ord('A'), ord('Z')+1)] 162 locs = [dict(zip(axes, (float(v) for v in s.split(',')))) for s in args] 163 plotLocations(locs, fig) 164 165 pyplot.show() 166 167if __name__ == '__main__': 168 import sys 169 sys.exit(main()) 170