# Source code for fontTools.varLib.plot

    """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())