Source: PlotBoilerplate.js

"use strict";
/**
 * @classdesc The main class of the PlotBoilerplate.
 *
 * @requires Vertex
 * @requires Line
 * @requires Vector
 * @requires Polygon
 * @requires PBImage
 * @requires VEllipse
 * @requires Circle
 * @requires MouseHandler
 * @requires KeyHandler
 * @requires VertexAttr
 * @requires CubicBezierCurve
 * @requires BezierPath
 * @requires Triangle
 * @requires drawutils
 * @requires drawutilsgl
 *
 * @author   Ikaros Kappler
 * @date     2018-10-23
 * @modified 2018-11-19 Added multi-select and multi-drag.
 * @modified 2018-12-04 Added basic SVG export.
 * @modified 2018-12-09 Extended the constructor (canvas).
 * @modified 2018-12-18 Added the config.redrawOnResize param.
 * @modified 2018-12-18 Added the config.defaultCanvas{Width,Height} params.
 * @modified 2018-12-19 Added CSS scaling.
 * @modified 2018-12-28 Removed the unused 'drawLabel' param. Added the 'enableMouse' and 'enableKeys' params.
 * @modified 2018-12-29 Added the 'drawOrigin' param.
 * @modified 2018-12-29 Renamed the 'autoCenterOffset' param to 'autoAdjustOffset'. Added the params 'offsetAdjustXPercent' and 'offsetAdjustYPercent'.
 * @modified 2019-01-14 Added params 'drawBezierHandleLines' and 'drawBezierHandlePoints'. Added the 'redraw' praam to the add() function.
 * @modified 2019-01-16 Added params 'drawHandleLines' and 'drawHandlePoints'. Added the new params to the dat.gui interface.
 * @modified 2019-01-30 Added the 'Vector' type (extending the Line class).
 * @modified 2019-01-30 Added the 'PBImage' type (a wrapper for images).
 * @modified 2019-02-02 Added the 'canvasWidthFactor' and 'canvasHeightFactor' params.
 * @modified 2019-02-03 Removed the drawBackgroundImage() function, with had no purpose at all. Just add an image to the drawables-list.
 * @modified 2019-02-06 Vertices (instace of Vertex) can now be added. Added the 'draggable' attribute to the vertex attributes.
 * @modified 2019-02-10 Fixed a draggable-bug in PBImage handling (scaling was not possible).
 * @modified 2019-02-10 Added the 'enableTouch' option (default is true).
 * @modified 2019-02-14 Added the console for debugging (setConsole(object)).
 * @modified 2019-02-19 Added two new constants: DEFAULT_CLICK_TOLERANCE and DEFAULT_TOUCH_TOLERANCE.
 * @modified 2019-02-19 Added the second param to the locatePointNear(Vertex,Number) function.
 * @modified 2019-02-20 Removed the 'loadFile' entry from the GUI as it was experimental and never in use.
 * @modified 2019-02-23 Removed the 'rebuild' function as it had no purpose.
 * @modified 2019-02-23 Added scaling of the click-/touch-tolerance with the CSS scale.
 * @modified 2019-03-23 Added JSDoc tags. Changed the default value of config.drawOrigin to false.
 * @modified 2019-04-03 Fixed the touch-drag position detection for canvas elements that are not located at document position (0,0).
 * @modified 2019-04-03 Tweaked the fit-to-parent function to work with paddings and borders.
 * @modified 2019-04-28 Added the preClear callback param (called before the canvas was cleared on redraw and before any elements are drawn).
 * @modified 2019-09-18 Added basics for WebGL support (strictly experimental).
 * @modified 2019-10-03 Added the .beginDrawCycle call in the redraw function.
 * @modified 2019-11-06 Added fetch.num, fetch.val, fetch.bool, fetch.func functions.
 * @modified 2019-11-13 Fixed an issue with the mouse-sensitive area around vertices (were affected by zoom).
 * @modified 2019-11-13 Added the 'enableMouseWheel' param.
 * @modified 2019-11-18 Added the Triangle class as a regular drawable element.
 * @modified 2019-11-18 The add function now works with arrays, too.
 * @modified 2019-11-18 Added the _handleColor helper function to determine the render color of non-draggable vertices.
 * @modified 2019-11-19 Fixed a bug in the resizeCanvas function; retina resolution was not possible.
 * @modified 2019-12-04 Added relative positioned zooming.
 * @modified 2019-12-04 Added offsetX and offsetY params.
 * @modified 2019-12-04 Added an 'Set to fullsize retina' button to the GUI config.
 * @modified 2019-12-07 Added the drawConfig for lines, polygons, ellipse, triangles, bezier curves and image control lines.
 * @modified 2019-12-08 Fixed a css scale bug in the viewport() function.
 * @modified 2019-12-08 Added the drawconfig UI panel (line colors and line widths).
 * @modified 2020-02-06 Added handling for the end- and end-control-points of non-cirular Bézier paths (was still missing).
 * @modified 2020-02-06 Fixed a drag-amount bug in the move handling of end points of Bezier paths (control points was not properly moved when non circular).
 * @modified 2020-03-28 Ported this class from vanilla-JS to Typescript.
 * @modified 2020-03-29 Fixed the enableSVGExport flag (read enableEport before).
 * @modified 2020-05-09 Included the Cirlcle class.
 * @modified 2020-06-22 Added the rasterScaleX and rasterScaleY config params.
 * @modified 2020-06-03 Fixed the selectedVerticesOnPolyon(Polygon) function: non-selectable vertices were selected too, before.
 * @modified 2020-07-06 Replacing Touchy.js by AlloyFinger.js
 * @modified 2020-07-27 Added the getVertexNear(XYCoords,number) function
 * @modified 2020-07-27 Extended the remove(Drawable) function: vertices are now removed, too.
 * @modified 2020-07-28 Added PlotBoilerplate.revertMousePosition(number,number) –  the inverse function of transformMousePosition(...).
 * @modified 2020-07-31 Added PlotBoilerplate.getDraggedElementCount() to check wether any elements are currently being dragged.
 * @modified 2020-08-19 Added the VertexAttributes.visible attribute to make vertices invisible.
 * @modified 2020-11-17 Added pure click handling (no dragEnd and !wasMoved jiggliny any more) to the PlotBoilerplate.
 * @version  1.9.2
 *
 * @file PlotBoilerplate
 * @fileoverview The main class.
 * @public
 **/
Object.defineProperty(exports, "__esModule", { value: true });
var draw_1 = require("./draw");
var drawgl_1 = require("./drawgl");
var BezierPath_1 = require("./BezierPath");
var Bounds_1 = require("./Bounds");
var Circle_1 = require("./Circle");
var Grid_1 = require("./Grid");
var KeyHandler_1 = require("./KeyHandler");
var Line_1 = require("./Line");
var MouseHandler_1 = require("./MouseHandler");
var PBImage_1 = require("./PBImage");
var Polygon_1 = require("./Polygon");
var SVGBuilder_1 = require("./SVGBuilder");
var Triangle_1 = require("./Triangle");
var VEllipse_1 = require("./VEllipse");
var Vector_1 = require("./Vector");
var Vertex_1 = require("./Vertex");
var VertexAttr_1 = require("./VertexAttr");
var PlotBoilerplate = /** @class */ (function () {
    /**
     * The constructor.
     *
     * @constructor
     * @name PlotBoilerplate
     * @public
     * @param {object} config={} - The configuration.
     * @param {HTMLCanvasElement} config.canvas - Your canvas element in the DOM (required).
     * @param {boolean=} [config.fullSize=true] - If set to true the canvas will gain full window size.
     * @param {boolean=} [config.fitToParent=true] - If set to true the canvas will gain the size of its parent container (overrides fullSize).
     * @param {number=}  [config.scaleX=1.0] - The initial x-zoom. Default is 1.0.
     * @param {number=}  [config.scaleY=1.0] - The initial y-zoom. Default is 1.0.
     * @param {number=}  [config.offsetX=1.0] - The initial x-offset. Default is 0.0. Note that autoAdjustOffset=true overrides these values.
     * @param {number=}  [config.offsetY=1.0] - The initial y-offset. Default is 0.0. Note that autoAdjustOffset=true overrides these values.
     * @param {boolean=} [config.rasterGrid=true] - If set to true the background grid will be drawn rastered.
     * @param {boolean=} [config.rasterScaleX=1.0] - Define the default horizontal raster scale (default=1.0).
     * @param {boolean=} [config.rasterScaleY=1.0] - Define the default vertical raster scale (default=1.0).
     * @param {number=}  [config.rasterAdjustFactor=1.0] - The exponential limit for wrapping down the grid. (2.0 means: halve the grid each 2.0*n zoom step).
     * @param {boolean=} [config.drawOrigin=false] - Draw a crosshair at (0,0).
     * @param {boolean=} [config.autoAdjustOffset=true] -  When set to true then the origin of the XY plane will
     *                         be re-adjusted automatically (see the params
     *                         offsetAdjust{X,Y}Percent for more).
     * @param {number=}  [config.offsetAdjustXPercent=50] - The x-fallback position for the origin after
     *                         resizing the canvas.
     * @param {number=}  [config.offsetAdjustYPercent=50] - The y-fallback position for the origin after
     *                         resizing the canvas.
     * @param {number=}  [config.defaultCanvasWidth=1024] - The canvas size fallback (width) if no automatic resizing
     *                         is switched on.
     * @param {number=}  [config.defaultCanvasHeight=768] - The canvas size fallback (height) if no automatic resizing
     *                         is switched on.
     * @param {number=}  [config.canvasWidthFactor=1.0] - Scaling factor (width) upon the canvas size.
     *                         In combination with cssScale{X,Y} this can be used to obtain
     *                         sub pixel resolutions for retina displays.
     * @param {number=}  [config.canvasHeightFactor=1.0] - Scaling factor (height) upon the canvas size.
     *                         In combination with cssScale{X,Y} this can be used to obtain
     *                         sub pixel resolutions for retina displays.
     * @param {number=}  [config.cssScaleX=1.0] - Visually resize the canvas (horizontally) using CSS transforms (scale).
     * @param {number=}  [config.cssScaleY=1.0] - Visually resize the canvas (vertically) using CSS transforms (scale).
     * @param {boolan=}  [config.cssUniformScale=true] - CSS scale x and y obtaining aspect ratio.
     * @param {boolean=} [config.autoDetectRetina=true] - When set to true (default) the canvas will try to use the display's pixel ratio.
     * @param {string=}  [config.backgroundColor=#ffffff] - The backround color.
     * @param {boolean=} [config.redrawOnResize=true] - Switch auto-redrawing on resize on/off (some applications
     *                         might want to prevent automatic redrawing to avoid data loss from the draw buffer).
     * @param {boolean=} [config.drawBezierHandleLines=true] - Indicates if Bézier curve handles should be drawn (used for
     *                         editors, no required in pure visualizations).
     * @param {boolean=} [config.drawBezierHandlePoints=true] - Indicates if Bézier curve handle points should be drawn.
     * @param {function=} [config.preClear=null] - A callback function that will be triggered just before the
     *                         draw function clears the canvas (before anything else was drawn).
     * @param {function=} [config.preDraw=null] - A callback function that will be triggered just before the draw
     *                         function starts.
     * @param {function=} [config.postDraw=null] - A callback function that will be triggered right after the drawing
     *                         process finished.
     * @param {boolean=} [config.enableMouse=true] - Indicates if the application should handle mouse events for you.
     * @param {boolean=} [config.enableTouch=true] - Indicates if the application should handle touch events for you.
     * @param {boolean=} [config.enableKeys=true] - Indicates if the application should handle key events for you.
     * @param {boolean=} [config.enableMouseWheel=true] - Indicates if the application should handle mouse wheel events for you.
     * @param {boolean=} [config.enableGL=false] - Indicates if the application should use the experimental WebGL features (not recommended).
     * @param {boolean=} [config.enableSVGExport=true] - Indicates if the SVG export should be enabled (default is true).
     *                                                   Note that changes from the postDraw hook might not be visible in the export.
     */
    function PlotBoilerplate(config) {
        // This should be in some static block ...
        VertexAttr_1.VertexAttr.model = { bezierAutoAdjust: false,
            renderTime: 0,
            selectable: true,
            isSelected: false,
            draggable: true,
            visible: true
        };
        if (typeof config.canvas == 'undefined')
            throw "No canvas specified.";
        /**
         * A global config that's attached to the dat.gui control interface.
         *
         * @member {Object}
         * @memberof PlotBoilerplate
         * @instance
         */
        this.config = {
            canvas: config.canvas,
            fullSize: PlotBoilerplate.utils.fetch.val(config, 'fullSize', true),
            fitToParent: PlotBoilerplate.utils.fetch.bool(config, 'fitToParent', true),
            scaleX: PlotBoilerplate.utils.fetch.num(config, 'scaleX', 1.0),
            scaleY: PlotBoilerplate.utils.fetch.num(config, 'scaleY', 1.0),
            offsetX: PlotBoilerplate.utils.fetch.num(config, 'offsetX', 0.0),
            offsetY: PlotBoilerplate.utils.fetch.num(config, 'offsetY', 0.0),
            rasterGrid: PlotBoilerplate.utils.fetch.bool(config, 'rasterGrid', true),
            rasterScaleX: PlotBoilerplate.utils.fetch.num(config, 'rasterScaleX', 1.0),
            rasterScaleY: PlotBoilerplate.utils.fetch.num(config, 'rasterScaleY', 1.0),
            rasterAdjustFactor: PlotBoilerplate.utils.fetch.num(config, 'rasterAdjustdFactror', 2.0),
            drawOrigin: PlotBoilerplate.utils.fetch.bool(config, 'drawOrigin', false),
            autoAdjustOffset: PlotBoilerplate.utils.fetch.val(config, 'autoAdjustOffset', true),
            offsetAdjustXPercent: PlotBoilerplate.utils.fetch.num(config, 'offsetAdjustXPercent', 50),
            offsetAdjustYPercent: PlotBoilerplate.utils.fetch.num(config, 'offsetAdjustYPercent', 50),
            backgroundColor: config.backgroundColor || '#ffffff',
            redrawOnResize: PlotBoilerplate.utils.fetch.bool(config, 'redrawOnResize', true),
            defaultCanvasWidth: PlotBoilerplate.utils.fetch.num(config, 'defaultCanvasWidth', PlotBoilerplate.DEFAULT_CANVAS_WIDTH),
            defaultCanvasHeight: PlotBoilerplate.utils.fetch.num(config, 'defaultCanvasHeight', PlotBoilerplate.DEFAULT_CANVAS_HEIGHT),
            canvasWidthFactor: PlotBoilerplate.utils.fetch.num(config, 'canvasWidthFactor', 1.0),
            canvasHeightFactor: PlotBoilerplate.utils.fetch.num(config, 'canvasHeightFactor', 1.0),
            cssScaleX: PlotBoilerplate.utils.fetch.num(config, 'cssScaleX', 1.0),
            cssScaleY: PlotBoilerplate.utils.fetch.num(config, 'cssScaleY', 1.0),
            cssUniformScale: PlotBoilerplate.utils.fetch.bool(config, 'cssUniformScale', true),
            saveFile: function () { _self.hooks.saveFile(_self); },
            setToRetina: function () { _self._setToRetina(); },
            autoDetectRetina: PlotBoilerplate.utils.fetch.bool(config, 'autoDetectRetina', true),
            enableSVGExport: PlotBoilerplate.utils.fetch.bool(config, 'enableSVGExport', true),
            // Listeners/observers
            preClear: PlotBoilerplate.utils.fetch.func(config, 'preClear', null),
            preDraw: PlotBoilerplate.utils.fetch.func(config, 'preDraw', null),
            postDraw: PlotBoilerplate.utils.fetch.func(config, 'postDraw', null),
            // Interaction
            enableMouse: PlotBoilerplate.utils.fetch.bool(config, 'enableMouse', true),
            enableTouch: PlotBoilerplate.utils.fetch.bool(config, 'enableTouch', true),
            enableKeys: PlotBoilerplate.utils.fetch.bool(config, 'enableKeys', true),
            enableMouseWheel: PlotBoilerplate.utils.fetch.bool(config, 'enableMouseWheel', true),
            // Experimental (and unfinished)
            enableGL: PlotBoilerplate.utils.fetch.bool(config, 'enableGL', false)
        }; // END confog
        /**
         * Configuration for drawing things.
         *
         * @member {Object}
         * @memberof PlotBoilerplate
         * @instance
         */
        this.drawConfig = {
            drawVertices: true,
            drawBezierHandleLines: PlotBoilerplate.utils.fetch.bool(config, 'drawBezierHandleLines', true),
            drawBezierHandlePoints: PlotBoilerplate.utils.fetch.bool(config, 'drawBezierHandlePoints', true),
            drawHandleLines: PlotBoilerplate.utils.fetch.bool(config, 'drawHandleLines', true),
            drawHandlePoints: PlotBoilerplate.utils.fetch.bool(config, 'drawHandlePoints', true),
            drawGrid: PlotBoilerplate.utils.fetch.bool(config, 'drawGrid', true),
            bezier: {
                color: '#00a822',
                lineWidth: 2,
                handleLine: {
                    color: 'rgba(180,180,180,0.5)',
                    lineWidth: 1
                }
            },
            polygon: {
                color: '#0022a8',
                lineWidth: 1
            },
            triangle: {
                color: '#6600ff',
                lineWidth: 1
            },
            ellipse: {
                color: '#2222a8',
                lineWidth: 1
            },
            circle: {
                color: '#22a8a8',
                lineWidth: 2
            },
            vertex: {
                color: '#a8a8a8',
                lineWidth: 1
            },
            selectedVertex: {
                color: '#c08000',
                lineWidth: 2
            },
            line: {
                color: '#a844a8',
                lineWidth: 1
            },
            vector: {
                color: '#ff44a8',
                lineWidth: 1
            },
            image: {
                color: '#a8a8a8',
                lineWidth: 1
            }
        }; // END drawConfig
        // +---------------------------------------------------------------------------------
        // | Object members.
        // +-------------------------------
        this.canvas = typeof config.canvas == 'string' ? document.querySelector(config.canvas) : config.canvas;
        if (this.config.enableGL) {
            this.ctx = this.canvas.getContext('webgl'); // webgl-experimental?
            this.draw = new drawgl_1.drawutilsgl(this.ctx, false);
            // PROBLEM: same instance of fill and draw when using WebGL. Shader program cannot be duplicated on the same context
            this.fill = this.draw.copyInstance(true);
            console.warn('Initialized with experimental mode enableGL=true. Note that this is not yet fully implemented.');
        }
        else {
            this.ctx = this.canvas.getContext('2d');
            this.draw = new draw_1.drawutils(this.ctx, false);
            this.fill = new draw_1.drawutils(this.ctx, true);
        }
        this.draw.scale.set(this.config.scaleX, this.config.scaleY);
        this.fill.scale.set(this.config.scaleX, this.config.scaleY);
        this.grid = new Grid_1.Grid(new Vertex_1.Vertex(0, 0), new Vertex_1.Vertex(50, 50));
        this.canvasSize = { width: PlotBoilerplate.DEFAULT_CANVAS_WIDTH, height: PlotBoilerplate.DEFAULT_CANVAS_HEIGHT };
        this.vertices = [];
        this.selectPolygon = null;
        this.draggedElements = [];
        this.drawables = [];
        this.console = console;
        this.hooks = {
            // This is changable from the outside
            saveFile: PlotBoilerplate._saveFile
        };
        var _self = this;
        // TODO: this should be placed in the caller and work for modules/global, too!
        globalThis.addEventListener('resize', function () { return _self.resizeCanvas(); });
        this.resizeCanvas();
        if (config.autoDetectRetina) {
            this._setToRetina();
        }
        this.installInputListeners();
        // Apply the configured CSS scale.
        this.updateCSSscale();
        // Init	
        this.redraw();
        // Gain focus
        this.canvas.focus();
    }
    ; // END constructor
    /**
     * This function opens a save-as file dialog and – once an output file is
     * selected – stores the current canvas contents as an SVG image.
     *
     * It is the default hook for saving files and can be overwritten.
     *
     * @method _saveFile
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     * @private
     **/
    PlotBoilerplate._saveFile = function (pb) {
        var svgCode = new SVGBuilder_1.SVGBuilder().build(pb.drawables, { canvasSize: pb.canvasSize, offset: pb.draw.offset, zoom: pb.draw.scale });
        var blob = new Blob([svgCode], { type: "image/svg;charset=utf-8" });
        // See documentation for FileSaver.js for usage.
        //    https://github.com/eligrey/FileSaver.js
        if (typeof globalThis["saveAs"] != "function")
            throw "Cannot save file; did you load the ./utils/savefile helper function an the eligrey/SaveFile library?";
        var _saveAs = globalThis["saveAs"];
        _saveAs(blob, "plotboilerplate.svg");
    };
    ;
    /**
     * This function sets the canvas resolution to factor 2.0 (or the preferred pixel ratio of your device) for retina displays.
     * Please not that in non-GL mode this might result in very slow rendering as the canvas buffer size may increase.
     *
     * @method _setToRetina
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     * @private
     **/
    PlotBoilerplate.prototype._setToRetina = function () {
        this.config.autoDetectRetina = true;
        var pixelRatio = globalThis.devicePixelRatio || 1;
        this.config.cssScaleX = this.config.cssScaleY = 1.0 / pixelRatio; // 0.5;
        this.config.canvasWidthFactor = this.config.canvasHeightFactor = pixelRatio; // 2.0;
        this.resizeCanvas();
        this.updateCSSscale();
    };
    ;
    /**
     * Set the current zoom and draw offset to fit the given bounds.
     *
     * This method currently restores the aspect zoom ratio.
     *
     **/
    PlotBoilerplate.prototype.fitToView = function (bounds) {
        //const viewport:Bounds = this.viewport();
        var canvasCenter = new Vertex_1.Vertex(this.canvasSize.width / 2.0, this.canvasSize.height / 2.0);
        var canvasRatio = this.canvasSize.width / this.canvasSize.height;
        var ratio = bounds.width / bounds.height;
        // Find the new draw offset
        var center = new Vertex_1.Vertex(bounds.max.x - bounds.width / 2.0, bounds.max.y - bounds.height / 2.0)
            .inv()
            .addXY(this.canvasSize.width / 2.0, this.canvasSize.height / 2.0);
        this.setOffset(center);
        if (canvasRatio < ratio) {
            var newUniformZoom = this.canvasSize.width / bounds.width;
            this.setZoom(newUniformZoom, newUniformZoom, canvasCenter);
        }
        else {
            var newUniformZoom = this.canvasSize.height / bounds.height;
            this.setZoom(newUniformZoom, newUniformZoom, canvasCenter);
        }
        this.redraw();
    };
    ;
    /**
     * Set the console for this instance.
     *
     * @method setConsole
     * @param {Console} con - The new console object (default is globalThis.console).
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     **/
    PlotBoilerplate.prototype.setConsole = function (con) {
        this.console = con;
    };
    ;
    /**
     * Update the CSS scale for the canvas depending onf the cssScale{X,Y} settings.<br>
     * <br>
     * This function is usually only used inernally.
     *
     * @method updateCSSscale
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     * @private
     **/
    PlotBoilerplate.prototype.updateCSSscale = function () {
        if (this.config.cssUniformScale) {
            PlotBoilerplate.utils.setCSSscale(this.canvas, this.config.cssScaleX, this.config.cssScaleX);
        }
        else {
            PlotBoilerplate.utils.setCSSscale(this.canvas, this.config.cssScaleX, this.config.cssScaleY);
        }
    };
    ;
    /**
     * Add a drawable object.<br>
     * <br>
     * This must be either:<br>
     * <pre>
     *  * a Vertex
     *  * a Line
     *  * a Vector
     *  * a VEllipse
     *  * a Circle
     *  * a Polygon
     *  * a Triangle
     *  * a BezierPath
     *  * a BPImage
     * </pre>
     *
     * @param {Drawable|Drawable[]} drawable - The drawable (of one of the allowed class instance) to add.
     * @param {boolean} [redraw=true] - If true the function will trigger redraw after the drawable(s) was/were added.
     * @method add
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     **/
    PlotBoilerplate.prototype.add = function (drawable, redraw) {
        if (Array.isArray(drawable)) {
            var arr = drawable;
            // for( var i in arr )
            for (var i = 0; i < arr.length; i++)
                this.add(arr[i]);
        }
        else if (drawable instanceof Vertex_1.Vertex) {
            this.drawables.push(drawable);
            this.vertices.push(drawable);
        }
        else if (drawable instanceof Line_1.Line) {
            // Add some lines
            this.drawables.push(drawable);
            this.vertices.push(drawable.a);
            this.vertices.push(drawable.b);
        }
        else if (drawable instanceof Vector_1.Vector) {
            this.drawables.push(drawable);
            this.vertices.push(drawable.a);
            this.vertices.push(drawable.b);
        }
        else if (drawable instanceof VEllipse_1.VEllipse) {
            this.vertices.push(drawable.center);
            this.vertices.push(drawable.axis);
            this.drawables.push(drawable);
            drawable.center.listeners.addDragListener(function (e) {
                drawable.axis.add(e.params.dragAmount);
            });
        }
        else if (drawable instanceof Circle_1.Circle) {
            this.vertices.push(drawable.center);
            this.drawables.push(drawable);
        }
        else if (drawable instanceof Polygon_1.Polygon) {
            this.drawables.push(drawable);
            // for( var i in drawable.vertices )
            for (var i = 0; i < drawable.vertices.length; i++)
                this.vertices.push(drawable.vertices[i]);
        }
        else if (drawable instanceof Triangle_1.Triangle) {
            this.drawables.push(drawable);
            this.vertices.push(drawable.a);
            this.vertices.push(drawable.b);
            this.vertices.push(drawable.c);
        }
        else if (drawable instanceof BezierPath_1.BezierPath) {
            this.drawables.push(drawable);
            var bezierPath = drawable;
            for (var i = 0; i < bezierPath.bezierCurves.length; i++) {
                if (!drawable.adjustCircular && i == 0)
                    this.vertices.push(bezierPath.bezierCurves[i].startPoint);
                this.vertices.push(bezierPath.bezierCurves[i].endPoint);
                this.vertices.push(bezierPath.bezierCurves[i].startControlPoint);
                this.vertices.push(bezierPath.bezierCurves[i].endControlPoint);
                bezierPath.bezierCurves[i].startControlPoint.attr.selectable = false;
                bezierPath.bezierCurves[i].endControlPoint.attr.selectable = false;
            }
            PlotBoilerplate.utils.enableBezierPathAutoAdjust(drawable);
        }
        else if (drawable instanceof PBImage_1.PBImage) {
            this.vertices.push(drawable.upperLeft);
            this.vertices.push(drawable.lowerRight);
            this.drawables.push(drawable);
            // Todo: think about a IDragEvent interface
            drawable.upperLeft.listeners.addDragListener(function (e) {
                drawable.lowerRight.add(e.params.dragAmount);
            });
            drawable.lowerRight.attr.selectable = false;
        }
        else {
            throw "Cannot add drawable of unrecognized type: " + (typeof drawable) + ".";
        }
        // This is a workaround for backwards compatibility when the 'redraw' param was not yet present.
        if (redraw || typeof redraw == 'undefined')
            this.redraw();
    };
    ;
    /**
     * Remove a drawable object.<br>
     * <br>
     * This must be either:<br>
     * <pre>
     *  * a Vertex
     *  * a Line
     *  * a Vector
     *  * a VEllipse
     *  * a Circle
     *  * a Polygon
     *  * a BezierPath
     *  * a BPImage
     *  * a Triangle
     * </pre>
     *
     * @param {Object} drawable - The drawable (of one of the allowed class instance) to remove.
     * @param {boolean} [redraw=false]
     * @method remove
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     **/
    PlotBoilerplate.prototype.remove = function (drawable, redraw, removeWithVertices) {
        if (drawable instanceof Vertex_1.Vertex)
            this.removeVertex(drawable, false);
        for (var i = 0; i < this.drawables.length; i++) {
            if (this.drawables[i] === drawable) {
                this.drawables.splice(i, 1);
                if (removeWithVertices) {
                    // Check if some listeners need to be removed
                    if (drawable instanceof Line_1.Line) {
                        // Add some lines
                        this.removeVertex(drawable.a, false);
                        this.removeVertex(drawable.b, false);
                    }
                    else if (drawable instanceof Vector_1.Vector) {
                        this.removeVertex(drawable.a, false);
                        this.removeVertex(drawable.b, false);
                    }
                    else if (drawable instanceof VEllipse_1.VEllipse) {
                        this.removeVertex(drawable.center, false);
                        this.removeVertex(drawable.axis, false);
                    }
                    else if (drawable instanceof Circle_1.Circle) {
                        this.removeVertex(drawable.center, false);
                    }
                    else if (drawable instanceof Polygon_1.Polygon) {
                        // for( var i in drawable.vertices )
                        for (var i = 0; i < drawable.vertices.length; i++)
                            this.removeVertex(drawable.vertices[i], false);
                    }
                    else if (drawable instanceof Triangle_1.Triangle) {
                        this.removeVertex(drawable.a, false);
                        this.removeVertex(drawable.b, false);
                        this.removeVertex(drawable.c, false);
                    }
                    else if (drawable instanceof BezierPath_1.BezierPath) {
                        PlotBoilerplate.utils.disableBezierPathAutoAdjust(drawable);
                        for (var i = 0; i < drawable.bezierCurves.length; i++) {
                            this.removeVertex(drawable.bezierCurves[i].startPoint, false);
                            this.removeVertex(drawable.bezierCurves[i].startControlPoint, false);
                            this.removeVertex(drawable.bezierCurves[i].endControlPoint, false);
                            if (i + 1 == drawable.bezierCurves.length) {
                                this.removeVertex(drawable.bezierCurves[i].endPoint, false);
                            }
                        }
                    }
                    else if (drawable instanceof PBImage_1.PBImage) {
                        this.removeVertex(drawable.upperLeft, false);
                        this.removeVertex(drawable.lowerRight, false);
                    }
                } // END removeWithVertices
                if (redraw)
                    this.redraw();
                return;
            }
        }
    };
    ;
    /**
     * Remove a vertex from the vertex list.<br>
     *
     * @param {Vertex} vert - The vertex to remove.
     * @param {boolean} [redraw=false]
     * @method removeVertex
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     **/
    PlotBoilerplate.prototype.removeVertex = function (vert, redraw) {
        for (var i = 0; i < this.vertices.length; i++) {
            if (this.vertices[i] === vert) {
                this.vertices.splice(i, 1);
                if (redraw)
                    this.redraw();
                return;
            }
        }
    };
    ;
    /**
     * Find the vertex near the given position.
     *
     * The position is the absolute vertex position, not the x-y-coordinates on the canvas.
     *
     * @param {XYCoords} position - The position of the vertex to search for.
     * @param {number} pixelTolerance - A radius around the position to include into the search.
     *                                  Note that the tolerance will be scaled up/down when zoomed.
     * @return The vertex near the given position or undefined if none was found there.
     **/
    PlotBoilerplate.prototype.getVertexNear = function (pixelPosition, pixelTolerance) {
        var p = this.locatePointNear(this.transformMousePosition(pixelPosition.x, pixelPosition.y), pixelTolerance / Math.min(this.config.cssScaleX, this.config.cssScaleY));
        if (p && p.typeName == "vertex")
            return this.vertices[p.vindex];
        return undefined;
    };
    ;
    /**
     * Draw the grid with the current config settings.<br>
     *
     * This function is usually only used internally.
     *
     * @method drawGrid
     * @private
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     **/
    PlotBoilerplate.prototype.drawGrid = function () {
        var gScale = {
            x: Grid_1.Grid.utils.mapRasterScale(this.config.rasterAdjustFactor, this.draw.scale.x) * this.config.rasterScaleX / this.config.cssScaleX,
            y: Grid_1.Grid.utils.mapRasterScale(this.config.rasterAdjustFactor, this.draw.scale.y) * this.config.rasterScaleY / this.config.cssScaleY
        };
        var gSize = { width: this.grid.size.x * gScale.x, height: this.grid.size.y * gScale.y };
        var cs = { width: this.canvasSize.width / 2, height: this.canvasSize.height / 2 };
        var offset = this.draw.offset.clone().inv();
        offset.x = (Math.round(offset.x + cs.width) / Math.round(gSize.width)) * (gSize.width) / this.draw.scale.x + (((this.draw.offset.x - cs.width) / this.draw.scale.x) % gSize.width);
        offset.y = (Math.round(offset.y + cs.height) / Math.round(gSize.height)) * (gSize.height) / this.draw.scale.y + (((this.draw.offset.y - cs.height) / this.draw.scale.x) % gSize.height);
        if (this.drawConfig.drawGrid) {
            if (this.config.rasterGrid) // TODO: move config member to drawConfig
                this.draw.raster(offset, (this.canvasSize.width) / this.draw.scale.x, (this.canvasSize.height) / this.draw.scale.y, gSize.width, gSize.height, 'rgba(0,128,255,0.125)');
            else
                this.draw.grid(offset, (this.canvasSize.width) / this.draw.scale.x, (this.canvasSize.height) / this.draw.scale.y, gSize.width, gSize.height, 'rgba(0,128,255,0.095)');
        }
    };
    ;
    /**
     * Draw the origin with the current config settings.<br>
     *
     * This function is usually only used internally.
     *
     * @method drawOrigin
     * @private
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     **/
    PlotBoilerplate.prototype.drawOrigin = function () {
        // Add a crosshair to mark the origin
        this.draw.crosshair({ x: 0, y: 0 }, 10, '#000000');
    };
    ;
    /**
     * This is just a tiny helper function to determine the render color of vertices.
     **/
    PlotBoilerplate.prototype._handleColor = function (h, color) {
        return h.attr.isSelected ? this.drawConfig.selectedVertex.color : (h.attr.draggable ? color : 'rgba(128,128,128,0.5)');
    };
    /**
     * Draw all drawables.
     *
     * This function is usually only used internally.
     *
     * @method drawDrawables
     * @private
     * @param {number} renderTime - The current render time. It will be used to distinct
     *                              already draw vertices from non-draw-yet vertices.
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     **/
    PlotBoilerplate.prototype.drawDrawables = function (renderTime) {
        for (var i in this.drawables) {
            var d = this.drawables[i];
            if (d instanceof BezierPath_1.BezierPath) {
                for (var c in d.bezierCurves) {
                    this.draw.cubicBezier(d.bezierCurves[c].startPoint, d.bezierCurves[c].endPoint, d.bezierCurves[c].startControlPoint, d.bezierCurves[c].endControlPoint, this.drawConfig.bezier.color, this.drawConfig.bezier.lineWidth);
                    if (this.drawConfig.drawBezierHandlePoints && this.drawConfig.drawHandlePoints) {
                        if (!d.bezierCurves[c].startPoint.attr.bezierAutoAdjust) {
                            if (d.bezierCurves[c].startPoint.attr.visible)
                                this.draw.diamondHandle(d.bezierCurves[c].startPoint, 7, this._handleColor(d.bezierCurves[c].startPoint, this.drawConfig.vertex.color));
                            d.bezierCurves[c].startPoint.attr.renderTime = renderTime;
                        }
                        if (!d.bezierCurves[c].endPoint.attr.bezierAutoAdjust) {
                            if (d.bezierCurves[c].endPoint.attr.visible)
                                this.draw.diamondHandle(d.bezierCurves[c].endPoint, 7, this._handleColor(d.bezierCurves[c].endPoint, this.drawConfig.vertex.color));
                            d.bezierCurves[c].endPoint.attr.renderTime = renderTime;
                        }
                        if (d.bezierCurves[c].startControlPoint.attr.visible)
                            this.draw.circleHandle(d.bezierCurves[c].startControlPoint, 3, this._handleColor(d.bezierCurves[c].startControlPoint, '#008888'));
                        if (d.bezierCurves[c].endControlPoint.attr.visible)
                            this.draw.circleHandle(d.bezierCurves[c].endControlPoint, 3, this._handleColor(d.bezierCurves[c].endControlPoint, '#008888'));
                        d.bezierCurves[c].startControlPoint.attr.renderTime = renderTime;
                        d.bezierCurves[c].endControlPoint.attr.renderTime = renderTime;
                    }
                    else {
                        d.bezierCurves[c].startPoint.attr.renderTime = renderTime;
                        d.bezierCurves[c].endPoint.attr.renderTime = renderTime;
                        d.bezierCurves[c].startControlPoint.attr.renderTime = renderTime;
                        d.bezierCurves[c].endControlPoint.attr.renderTime = renderTime;
                    }
                    if (this.drawConfig.drawBezierHandleLines && this.drawConfig.drawHandleLines) {
                        this.draw.line(d.bezierCurves[c].startPoint, d.bezierCurves[c].startControlPoint, this.drawConfig.bezier.handleLine.color, this.drawConfig.bezier.handleLine.lineWidth);
                        this.draw.line(d.bezierCurves[c].endPoint, d.bezierCurves[c].endControlPoint, this.drawConfig.bezier.handleLine.color, this.drawConfig.bezier.handleLine.lineWidth);
                    }
                }
            }
            else if (d instanceof Polygon_1.Polygon) {
                this.draw.polygon(d, this.drawConfig.polygon.color, this.drawConfig.polygon.lineWidth);
                if (!this.drawConfig.drawHandlePoints) {
                    for (var i in d.vertices) {
                        d.vertices[i].attr.renderTime = renderTime;
                    }
                }
            }
            else if (d instanceof Triangle_1.Triangle) {
                this.draw.polyline([d.a, d.b, d.c], false, this.drawConfig.triangle.color, this.drawConfig.triangle.lineWidth);
                if (!this.drawConfig.drawHandlePoints)
                    d.a.attr.renderTime = d.b.attr.renderTime = d.c.attr.renderTime = renderTime;
            }
            else if (d instanceof VEllipse_1.VEllipse) {
                if (this.drawConfig.drawHandleLines) {
                    this.draw.line(d.center.clone().add(0, d.axis.y - d.center.y), d.axis, '#c8c8c8');
                    this.draw.line(d.center.clone().add(d.axis.x - d.center.x, 0), d.axis, '#c8c8c8');
                }
                this.draw.ellipse(d.center, Math.abs(d.axis.x - d.center.x), Math.abs(d.axis.y - d.center.y), this.drawConfig.ellipse.color, this.drawConfig.ellipse.lineWidth);
                if (!this.drawConfig.drawHandlePoints) {
                    d.center.attr.renderTime = renderTime;
                    d.axis.attr.renderTime = renderTime;
                }
            }
            else if (d instanceof Circle_1.Circle) {
                this.draw.circle(d.center, d.radius, this.drawConfig.circle.color, this.drawConfig.circle.lineWidth);
            }
            else if (d instanceof Vertex_1.Vertex) {
                if (this.drawConfig.drawVertices &&
                    (!d.attr.selectable || !d.attr.draggable) && d.attr.visible) {
                    // Draw as special point (grey)
                    this.draw.circleHandle(d, 7, this.drawConfig.vertex.color);
                    d.attr.renderTime = renderTime;
                }
            }
            else if (d instanceof Line_1.Line) {
                this.draw.line(d.a, d.b, this.drawConfig.line.color, this.drawConfig.line.lineWidth);
                if (!this.drawConfig.drawHandlePoints || !d.a.attr.selectable)
                    d.a.attr.renderTime = renderTime;
                if (!this.drawConfig.drawHandlePoints || !d.b.attr.selectable)
                    d.b.attr.renderTime = renderTime;
            }
            else if (d instanceof Vector_1.Vector) {
                this.draw.arrow(d.a, d.b, this.drawConfig.vector.color); // , this.drawConfig.vector.lineWidth );
                if (this.drawConfig.drawHandlePoints && d.b.attr.selectable && d.b.attr.visible) {
                    this.draw.circleHandle(d.b, 3, '#a8a8a8');
                }
                else {
                    d.b.attr.renderTime = renderTime;
                }
                if (!this.drawConfig.drawHandlePoints || !d.a.attr.selectable)
                    d.a.attr.renderTime = renderTime;
                if (!this.drawConfig.drawHandlePoints || !d.b.attr.selectable)
                    d.b.attr.renderTime = renderTime;
            }
            else if (d instanceof PBImage_1.PBImage) {
                if (this.drawConfig.drawHandleLines)
                    this.draw.line(d.upperLeft, d.lowerRight, this.drawConfig.image.color, this.drawConfig.image.lineWidth);
                this.fill.image(d.image, d.upperLeft, d.lowerRight.clone().sub(d.upperLeft));
                if (this.drawConfig.drawHandlePoints) {
                    this.draw.circleHandle(d.lowerRight, 3, this.drawConfig.image.color);
                    d.lowerRight.attr.renderTime = renderTime;
                }
            }
            else {
                this.console.error('Cannot draw object. Unknown class.'); //  ' + d.constructor.name + '.' );
            }
        }
    };
    ;
    /**
     * Draw the select-polygon (if there is one).
     *
     * This function is usually only used internally.
     *
     * @method drawSelectPolygon
     * @private
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     **/
    PlotBoilerplate.prototype.drawSelectPolygon = function () {
        // Draw select polygon?
        if (this.selectPolygon != null && this.selectPolygon.vertices.length > 0) {
            this.draw.polygon(this.selectPolygon, '#888888');
            this.draw.crosshair(this.selectPolygon.vertices[0], 3, '#008888');
        }
    };
    ;
    /**
     * Draw all vertices that were not yet drawn with the given render time.<br>
     * <br>
     * This function is usually only used internally.
     *
     * @method drawVertices
     * @private
     * @param {number} renderTime - The current render time. It is used to distinct
     *                              already draw vertices from non-draw-yet vertices.
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     **/
    PlotBoilerplate.prototype.drawVertices = function (renderTime) {
        // Draw all vertices as small squares if they were not already drawn by other objects
        for (var i in this.vertices) {
            if (this.drawConfig.drawVertices && this.vertices[i].attr.renderTime != renderTime && this.vertices[i].attr.visible) {
                this.draw.squareHandle(this.vertices[i], 5, this._handleColor(this.vertices[i], 'rgb(0,128,192)'));
            }
        }
    };
    ;
    /**
     * Trigger redrawing of all objects.<br>
     * <br>
     * Usually this function is automatically called when objects change.
     *
     * @method redraw
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     **/
    PlotBoilerplate.prototype.redraw = function () {
        var renderTime = new Date().getTime();
        if (this.config.preClear)
            this.config.preClear();
        this.clear();
        if (this.config.preDraw)
            this.config.preDraw();
        // Tell the drawing library that a new drawing cycle begins (required for the GL lib).
        this.draw.beginDrawCycle();
        this.fill.beginDrawCycle();
        this.drawGrid();
        if (this.config.drawOrigin)
            this.drawOrigin();
        this.drawDrawables(renderTime);
        this.drawVertices(renderTime);
        this.drawSelectPolygon();
        if (this.config.postDraw)
            this.config.postDraw();
    };
    ; // END redraw
    /**
     * This function clears the canvas with the configured background color.<br>
     * <br>
     * This function is usually only used internally.
     *
     * @method clear
     * @private
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     **/
    PlotBoilerplate.prototype.clear = function () {
        // Note that elements might have an alpha channel. Clear the scene first.
        this.draw.clear(this.config.backgroundColor);
    };
    ;
    /**
     * Clear the selection.<br>
     * <br>
     * This function is usually only used internally.
     *
     * @method clearSelection
     * @private
     * @param {boolean=} [redraw=false] - Indicates if the redraw function should be triggered.
     * @instance
     * @memberof PlotBoilerplate
     * @return {PlotBoilerplate} this
     **/
    PlotBoilerplate.prototype.clearSelection = function (redraw) {
        for (var i in this.vertices)
            this.vertices[i].attr.isSelected = false;
        if (redraw)
            this.redraw();
        return this;
    };
    ;
    /**
     * Get the current view port.
     *
     * @method viewPort
     * @instance
     * @memberof PlotBoilerplate
     * @return {Bounds} The current viewport.
     **/
    PlotBoilerplate.prototype.viewport = function () {
        return new Bounds_1.Bounds(this.transformMousePosition(0, 0), this.transformMousePosition(this.canvasSize.width * this.config.cssScaleX, this.canvasSize.height * this.config.cssScaleY));
    };
    ;
    /**
     * Trigger the saveFile.hook.
     *
     * @method saveFile
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     **/
    PlotBoilerplate.prototype.saveFile = function () {
        this.hooks.saveFile(this);
    };
    ;
    /**
     * Get the available inner space of the given container.
     *
     * Size minus padding minus border.
     **/
    PlotBoilerplate.prototype.getAvailableContainerSpace = function () {
        var _self = this;
        var container = _self.canvas.parentNode; // Element | Document | DocumentFragment;
        var canvas = _self.canvas;
        canvas.style.display = 'none';
        var padding = parseFloat(globalThis.getComputedStyle(container, null).getPropertyValue('padding')) || 0, border = parseFloat(globalThis.getComputedStyle(canvas, null).getPropertyValue('border-width')) || 0, pl = parseFloat(globalThis.getComputedStyle(container, null).getPropertyValue('padding-left')) || padding, pr = parseFloat(globalThis.getComputedStyle(container, null).getPropertyValue('padding-right')) || padding, pt = parseFloat(globalThis.getComputedStyle(container, null).getPropertyValue('padding-top')) || padding, pb = parseFloat(globalThis.getComputedStyle(container, null).getPropertyValue('padding-bottom')) || padding, bl = parseFloat(globalThis.getComputedStyle(canvas, null).getPropertyValue('border-left-width')) || border, br = parseFloat(globalThis.getComputedStyle(canvas, null).getPropertyValue('border-right-width')) || border, bt = parseFloat(globalThis.getComputedStyle(canvas, null).getPropertyValue('border-top-width')) || border, bb = parseFloat(globalThis.getComputedStyle(canvas, null).getPropertyValue('border-bottom-width')) || border;
        var w = container.clientWidth;
        var h = container.clientHeight;
        canvas.style.display = 'block';
        return { width: (w - pl - pr - bl - br), height: (h - pt - pb - bt - bb) };
    };
    ;
    /**
     * This function resizes the canvas to the required settings (toggles fullscreen).<br>
     * <br>
     * This function is usually only used internally but feel free to call it if resizing required.
     *
     * @method resizeCanvas
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     **/
    PlotBoilerplate.prototype.resizeCanvas = function () {
        var _self = this;
        var _setSize = function (w, h) {
            w *= _self.config.canvasWidthFactor;
            h *= _self.config.canvasHeightFactor;
            _self.canvas.width = w;
            _self.canvas.height = h;
            _self.canvasSize.width = w;
            _self.canvasSize.height = h;
            if (_self.config.autoAdjustOffset) {
                _self.draw.offset.x = _self.fill.offset.x = _self.config.offsetX = w * (_self.config.offsetAdjustXPercent / 100);
                _self.draw.offset.y = _self.fill.offset.y = _self.config.offsetY = h * (_self.config.offsetAdjustYPercent / 100);
            }
        };
        if (_self.config.fullSize && !_self.config.fitToParent) {
            // Set editor size
            var width = globalThis.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
            var height = globalThis.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
            _self.canvas.style.position = 'absolute';
            _self.canvas.style.width = (_self.config.canvasWidthFactor * width) + 'px';
            _self.canvas.style.height = (_self.config.canvasWidthFactor * height) + 'px';
            _self.canvas.style.top = '0px';
            _self.canvas.style.left = '0px';
            _setSize(width, height);
        }
        else if (_self.config.fitToParent) {
            // Set editor size
            _self.canvas.style.position = 'absolute';
            var space = this.getAvailableContainerSpace();
            _self.canvas.style.width = (_self.config.canvasWidthFactor * space.width) + 'px';
            _self.canvas.style.height = (_self.config.canvasHeightFactor * space.height) + 'px';
            _self.canvas.style.top = null;
            _self.canvas.style.left = null;
            _setSize(space.width, space.height);
        }
        else {
            _self.canvas.style.width = null;
            _self.canvas.style.height = null;
            _setSize(_self.config.defaultCanvasWidth, _self.config.defaultCanvasHeight);
        }
        if (_self.config.redrawOnResize)
            _self.redraw();
    };
    ;
    /**
     *  Add all vertices inside the polygon to the current selection.<br>
     *
     * @method selectVerticesInPolygon
     * @param {Polygon} polygon - The polygonal selection area.
     * @instance
     * @memberof PlotBoilerplate
     * @return {void}
     **/
    PlotBoilerplate.prototype.selectVerticesInPolygon = function (polygon) {
        for (var i in this.vertices) {
            if (this.vertices[i].attr.selectable && polygon.containsVert(this.vertices[i]))
                this.vertices[i].attr.isSelected = true;
        }
    };
    ;
    /**
     * (Helper) Locates the point (index) at the passed position. Using an internal tolerance of 7 pixels.
     *
     * The result is an object { type : 'bpath', pindex, cindex, pid }
     *
     * Returns false if no point is near the passed position.
     *
     * @method locatePointNear
     * @param {Vertex} point - The polygonal selection area.
     * @param {number=} [tolerance=7] - The tolerance to use identtifying vertices.
     * @private
     * @return {IDraggable} Or false if none found.
     **/
    PlotBoilerplate.prototype.locatePointNear = function (point, tolerance) {
        var _self = this;
        // var tolerance = 7;
        if (typeof tolerance == 'undefined')
            tolerance = 7;
        // Apply the zoom (the tolerant area should not shrink or grow when zooming)
        tolerance /= _self.draw.scale.x;
        // Search in vertices
        // for( var vindex in _self.vertices ) {
        for (var vindex = 0; vindex < _self.vertices.length; vindex++) {
            var vert = _self.vertices[vindex];
            if ((vert.attr.draggable || vert.attr.selectable) && vert.distance(point) < tolerance) {
                // { type : 'vertex', vindex : vindex };
                return new PlotBoilerplate.Draggable(vert, PlotBoilerplate.Draggable.VERTEX).setVIndex(vindex);
            }
        }
        return null;
    };
    /**
     * Handle left-click event.<br>
     *
     * @method handleClick
     * @param {number} x - The click X position on the canvas.
     * @param {number} y - The click Y position on the canvas.
     * @private
     * @return {void}
     **/
    PlotBoilerplate.prototype.handleClick = function (e) {
        var _self = this;
        // const x:number = e.params.pos.x;
        //const y:number = e.params.pos.y;
        var p = this.locatePointNear(_self.transformMousePosition(e.params.pos.x, e.params.pos.y), PlotBoilerplate.DEFAULT_CLICK_TOLERANCE / Math.min(_self.config.cssScaleX, _self.config.cssScaleY));
        if (p) {
            _self.vertices[p.vindex].listeners.fireClickEvent(e);
            if (this.keyHandler && this.keyHandler.isDown('shift')) {
                if (p.typeName == 'bpath') {
                    var vert = _self.paths[p.pindex].bezierCurves[p.cindex].getPointByID(p.pid);
                    if (vert.attr.selectable)
                        vert.attr.isSelected = !vert.attr.isSelected;
                }
                else if (p.typeName == 'vertex') {
                    var vert = _self.vertices[p.vindex];
                    if (vert.attr.selectable)
                        vert.attr.isSelected = !vert.attr.isSelected;
                }
                _self.redraw();
            }
            else if (this.keyHandler.isDown('y') /* && p.type=='bpath' && (p.pid==BezierPath.START_POINT || p.pid==BezierPath.END_POINT) */) {
                _self.vertices[p.vindex].attr.bezierAutoAdjust = !_self.vertices[p.vindex].attr.bezierAutoAdjust;
                _self.redraw();
            }
        }
        else if (_self.selectPolygon != null) {
            var vert = _self.transformMousePosition(e.params.pos.x, e.params.pos.y);
            _self.selectPolygon.vertices.push(new Vertex_1.Vertex(vert.x, vert.y));
            _self.redraw();
        }
    };
    /**
     * Transforms the given x-y-(mouse-)point to coordinates respecting the view offset
     * and the zoom settings.
     *
     * @method transformMousePosition
     * @param {number} x - The x position relative to the canvas.
     * @param {number} y - The y position relative to the canvas.
     * @instance
     * @memberof PlotBoilerplate
     * @return {XYCoords} A simple object <pre>{ x : Number, y : Number }</pre> with the transformed coordinates.
     **/
    PlotBoilerplate.prototype.transformMousePosition = function (x, y) {
        return { x: (x / this.config.cssScaleX - this.config.offsetX) / (this.config.scaleX),
            y: (y / this.config.cssScaleY - this.config.offsetY) / (this.config.scaleY) };
    };
    ;
    /**
     * Revert a transformed mouse position back to canvas coordinates.
     *
     * This is the inverse function of `transformMousePosition`.
     *
     * @method revertMousePosition
     * @param {number} x - The x component of the position to revert.
     * @param {number} y - The y component of the position to revert.
     * @instance
     * @memberof PlotBoilerplate
     * @return {XYCoords} The canvas coordinates for the given position.
     **/
    PlotBoilerplate.prototype.revertMousePosition = function (x, y) {
        return { x: x / this.config.cssScaleX + this.config.offsetX,
            y: y / this.config.cssScaleY + this.config.offsetY };
    };
    ;
    /**
     * Determine if any elements are currently being dragged (on mouse move or touch move).
     *
     * @method getDraggedElementCount
     * @instance
     * @memberof PlotBoilerplate
     * @return {number} The number of elements that are currently being dragged.
     **/
    PlotBoilerplate.prototype.getDraggedElementCount = function () {
        return this.draggedElements.length;
    };
    ;
    /**
     * (Helper) The mouse-down handler.
     *
     * It selects vertices for dragging.
     *
     * @method mouseDownHandler.
     * @param {XMouseEvent} e - The event to handle
     * @private
     * @return {void}
     **/
    PlotBoilerplate.prototype.mouseDownHandler = function (e) {
        var _self = this;
        if (e.which != 1)
            return; // Only react on left mouse or touch events
        var p = _self.locatePointNear(_self.transformMousePosition(e.params.pos.x, e.params.pos.y), PlotBoilerplate.DEFAULT_CLICK_TOLERANCE / Math.min(_self.config.cssScaleX, _self.config.cssScaleY));
        if (!p)
            return;
        // Drag all selected elements?
        if (p.typeName == 'vertex' && _self.vertices[p.vindex].attr.isSelected) {
            // Multi drag
            // for( var i in _self.vertices ) {
            for (var i = 0; i < _self.vertices.length; i++) {
                if (_self.vertices[i].attr.isSelected) {
                    _self.draggedElements.push(new PlotBoilerplate.Draggable(_self.vertices[i], PlotBoilerplate.Draggable.VERTEX).setVIndex(i));
                    _self.vertices[i].listeners.fireDragStartEvent(e);
                }
            }
        }
        else {
            // Single drag
            if (!_self.vertices[p.vindex].attr.draggable)
                return;
            _self.draggedElements.push(p);
            if (p.typeName == 'bpath')
                _self.paths[p.pindex].bezierCurves[p.cindex].getPointByID(p.pid).listeners.fireDragStartEvent(e);
            else if (p.typeName == 'vertex')
                _self.vertices[p.vindex].listeners.fireDragStartEvent(e);
        }
        _self.redraw();
    };
    ;
    /**
     * The mouse-drag handler.
     *
     * It moves selected elements around or performs the panning if the ctrl-key if
     * hold down.
     *
     * @method mouseDragHandler.
     * @param {XMouseEvent} e - The event to handle
     * @private
     * @return {void}
     **/
    PlotBoilerplate.prototype.mouseDragHandler = function (e) {
        var _self = this;
        var oldDragAmount = { x: e.params.dragAmount.x, y: e.params.dragAmount.y };
        e.params.dragAmount.x /= _self.config.cssScaleX;
        e.params.dragAmount.y /= _self.config.cssScaleY;
        // Important note to: this.keyHandler.isDown('ctrl')
        //    We should not use this for any input.
        //    Reason: most browsers use [Ctrl]+[t] to create new browser tabs.
        //            If so, the key-up event for [Ctrl] will be fired in the _new tab_,
        //            not this one. So this tab will never receive any [Ctrl-down] events
        //            until next keypress; the implication is, that [Ctrl] would still
        //            considered to be pressed which is not true.
        if (this.keyHandler.isDown('alt') || this.keyHandler.isDown('spacebar')) {
            _self.setOffset(_self.draw.offset.clone().add(e.params.dragAmount));
            _self.redraw();
        }
        else {
            // Convert drag amount by scaling
            // Warning: this possibly invalidates the dragEvent for other listeners!
            //          Rethink the solution when other features are added.
            e.params.dragAmount.x /= _self.draw.scale.x;
            e.params.dragAmount.y /= _self.draw.scale.y;
            for (var i in _self.draggedElements) {
                var p = _self.draggedElements[i];
                if (p.typeName == 'bpath') {
                    _self.paths[p.pindex].moveCurvePoint(p.cindex, p.pid, new Vertex_1.Vertex(e.params.dragAmount.x, e.params.dragAmount.y));
                    _self.paths[p.pindex].bezierCurves[p.cindex].getPointByID(p.pid).listeners.fireDragEvent(e);
                }
                else if (p.typeName == 'vertex') {
                    if (!_self.vertices[p.vindex].attr.draggable)
                        continue;
                    _self.vertices[p.vindex].add(e.params.dragAmount);
                    _self.vertices[p.vindex].listeners.fireDragEvent(e);
                }
            }
        }
        // Restore old event values!
        e.params.dragAmount.x = oldDragAmount.x;
        e.params.dragAmount.y = oldDragAmount.y;
        _self.redraw();
    };
    ;
    /**
     * The mouse-up handler.
     *
     * It clears the dragging-selection.
     *
     * @method mouseUpHandler.
     * @param {XMouseEvent} e - The event to handle
     * @private
     * @return {void}
     **/
    PlotBoilerplate.prototype.mouseUpHandler = function (e) {
        var _self = this;
        if (e.which != 1)
            return; // Only react on left mouse;
        if (!e.params.wasDragged) {
            _self.handleClick(e); // e.params.pos.x, e.params.pos.y );
        }
        for (var i in _self.draggedElements) {
            var p = _self.draggedElements[i];
            if (p.typeName == 'bpath') {
                _self.paths[p.pindex].bezierCurves[p.cindex].getPointByID(p.pid).listeners.fireDragEndEvent(e);
            }
            else if (p.typeName == 'vertex') {
                _self.vertices[p.vindex].listeners.fireDragEndEvent(e);
            }
        }
        _self.draggedElements = [];
        _self.redraw();
    };
    ;
    /**
     * The mouse-wheel handler.
     *
     * It performs the zooming.
     *
     * @method mouseWheelHandler.
     * @param {XMouseEvent} e - The event to handle
     * @private
     * @return {void}
     **/
    PlotBoilerplate.prototype.mouseWheelHandler = function (e) {
        var zoomStep = 1.25; // Make configurable?
        // CHANGED replaced _self by this
        var _self = this;
        var we = e;
        if (we.deltaY < 0) {
            _self.setZoom(_self.config.scaleX * zoomStep, _self.config.scaleY * zoomStep, new Vertex_1.Vertex(e.params.pos.x, e.params.pos.y));
        }
        else if (we.deltaY > 0) {
            _self.setZoom(_self.config.scaleX / zoomStep, _self.config.scaleY / zoomStep, new Vertex_1.Vertex(e.params.pos.x, e.params.pos.y));
        }
        e.preventDefault();
        _self.redraw();
    };
    ;
    /**
     * Set the new draw offset.
     *
     * Note: the function will not trigger any redraws.
     *
     * @param {Vertex} newOffset - The new draw offset to use.
     **/
    PlotBoilerplate.prototype.setOffset = function (newOffset) {
        this.draw.offset.set(newOffset);
        this.fill.offset.set(newOffset);
        this.config.offsetX = newOffset.x;
        this.config.offsetY = newOffset.y;
    };
    ;
    /**
    * Set a new zoom value (and re-adjust the draw offset).
    *
    * Note: the function will not trigger any redraws.
    *
    * @param {number} zoomFactorX - The new horizontal zoom value.
    * @param {number} zoomFactorY - The new vertical zoom value.
    * @param {Vertex} interactionPos - The position of mouse/touch interaction.
    **/
    PlotBoilerplate.prototype.setZoom = function (zoomFactorX, zoomFactorY, interactionPos) {
        var oldPos = this.transformMousePosition(interactionPos.x, interactionPos.y);
        this.draw.scale.x = this.fill.scale.x = this.config.scaleX = Math.max(zoomFactorX, 0.01);
        this.draw.scale.y = this.fill.scale.y = this.config.scaleY = Math.max(zoomFactorY, 0.01);
        var newPos = this.transformMousePosition(interactionPos.x, interactionPos.y);
        var newOffsetX = this.draw.offset.x + (newPos.x - oldPos.x) * this.draw.scale.x;
        var newOffsetY = this.draw.offset.y + (newPos.y - oldPos.y) * this.draw.scale.y;
        this.setOffset({ x: newOffsetX, y: newOffsetY });
    };
    PlotBoilerplate.prototype.installInputListeners = function () {
        var _self = this;
        if (this.config.enableMouse) {
            // Install a mouse handler on the canvas.
            new MouseHandler_1.MouseHandler(this.canvas)
                .down(function (e) { _self.mouseDownHandler(e); })
                .drag(function (e) { _self.mouseDragHandler(e); })
                .up(function (e) { _self.mouseUpHandler(e); });
        }
        else {
            _self.console.log('Mouse interaction disabled.');
        }
        if (this.config.enableMouseWheel) {
            // Install a mouse handler on the canvas.
            new MouseHandler_1.MouseHandler(this.canvas)
                .wheel(function (e) { _self.mouseWheelHandler(e); });
        }
        else {
            _self.console.log('Mouse wheel interaction disabled.');
        }
        if (this.config.enableTouch) {
            // Install a touch handler on the canvas.
            var relPos_1 = function (pos) {
                return { x: pos.x - _self.canvas.offsetLeft,
                    y: pos.y - _self.canvas.offsetTop
                };
            };
            if (globalThis["AlloyFinger"] && typeof globalThis["AlloyFinger"] == "function") {
                try {
                    // Do not include AlloyFinger itself to the library
                    // (17kb, but we want to keep this lib as tiny as possible).
                    var AF = globalThis["AlloyFinger"];
                    var touchMovePos = null;
                    var touchDownPos = null;
                    var draggedElement = null;
                    var multiTouchStartScale = null;
                    var clearTouch_1 = function () {
                        touchMovePos = null;
                        touchDownPos = null;
                        draggedElement = null;
                        multiTouchStartScale = null;
                        _self.draggedElements = [];
                    };
                    var af = new AF(this.canvas, {
                        touchStart: function (e) {
                            if (e.touches.length == 1) {
                                touchMovePos = new Vertex_1.Vertex(relPos_1({ x: e.touches[0].clientX, y: e.touches[0].clientY }));
                                touchDownPos = new Vertex_1.Vertex(relPos_1({ x: e.touches[0].clientX, y: e.touches[0].clientY }));
                                draggedElement = _self.locatePointNear(_self.transformMousePosition(touchMovePos.x, touchMovePos.y), PlotBoilerplate.DEFAULT_TOUCH_TOLERANCE / Math.min(_self.config.cssScaleX, _self.config.cssScaleY));
                                if (draggedElement && draggedElement.typeName == 'vertex') {
                                    var draggingVertex = _self.vertices[draggedElement.vindex];
                                    var fakeEvent = { params: { isTouchEvent: true, dragAmount: { x: 0, y: 0 }, wasDragged: false, mouseDownPos: touchDownPos.clone(), mouseDragPos: touchDownPos.clone(), vertex: draggingVertex } };
                                    _self.draggedElements = [draggedElement];
                                    draggingVertex.listeners.fireDragStartEvent(fakeEvent);
                                }
                            }
                        },
                        touchMove: function (e) {
                            if (e.touches.length == 1 && draggedElement) {
                                e.preventDefault();
                                e.stopPropagation();
                                var rel = relPos_1({ x: e.touches[0].clientX, y: e.touches[0].clientY }); //  points[0] );
                                var trans = _self.transformMousePosition(rel.x, rel.y);
                                var diff = new Vertex_1.Vertex(_self.transformMousePosition(touchMovePos.x, touchMovePos.y)).difference(trans);
                                if (draggedElement.typeName == 'vertex') {
                                    if (!_self.vertices[draggedElement.vindex].attr.draggable)
                                        return;
                                    _self.vertices[draggedElement.vindex].add(diff);
                                    var draggingVertex = _self.vertices[draggedElement.vindex];
                                    var fakeEvent = { isTouchEvent: true, params: { dragAmount: diff.clone(), wasDragged: true, mouseDownPos: touchDownPos.clone(), mouseDragPos: touchDownPos.clone().add(diff), vertex: draggingVertex } };
                                    draggingVertex.listeners.fireDragEvent(fakeEvent);
                                    _self.redraw();
                                }
                                touchMovePos = new Vertex_1.Vertex(rel);
                            }
                            else if (e.touches.length == 2) {
                                // If at least two fingers touch and move, then change the draw offset (panning).
                                e.preventDefault();
                                e.stopPropagation();
                                _self.setOffset(_self.draw.offset.clone().addXY(e.deltaX, e.deltaY)); // Apply zoom?
                                _self.redraw();
                            }
                        },
                        touchEnd: function (e) {
                            // Note: e.touches.length is 0 here
                            if (draggedElement && draggedElement.typeName == 'vertex') {
                                var draggingVertex = _self.vertices[draggedElement.vindex];
                                var fakeEvent = { isTouchEvent: true, params: { dragAmount: { x: 0, y: 0 }, wasDragged: false, mouseDownPos: touchDownPos.clone(), mouseDragPos: touchDownPos.clone(), vertex: draggingVertex } };
                                // var rel : XYCoords = relPos( { x : e.touches[0].clientX, y : e.touches[0].clientY } ); //  points[0] );
                                // var trans : XYCoords = _self.transformMousePosition( rel.x, rel.y ); 
                                // var diff : Vertex = new Vertex(_self.transformMousePosition( touchMovePos.x, touchMovePos.y )).difference(trans);
                                // Check if vertex was moved
                                if (touchMovePos && touchDownPos && touchDownPos.distance(touchMovePos) < 0.001) {
                                    // if( e.touches.length == 1 && diff.x == 0 && diff.y == 0 ) {
                                    draggingVertex.listeners.fireClickEvent(fakeEvent);
                                }
                                else {
                                    draggingVertex.listeners.fireDragEndEvent(fakeEvent);
                                }
                            }
                            clearTouch_1();
                        },
                        touchCancel: function (e) {
                            clearTouch_1();
                        },
                        multipointStart: function (e) {
                            multiTouchStartScale = _self.draw.scale.clone();
                        },
                        multipointEnd: function (e) {
                            multiTouchStartScale = null;
                        },
                        pinch: function (e) {
                            // For pinching there must be at least two touch items
                            var fingerA = new Vertex_1.Vertex(e.touches.item(0).clientX, e.touches.item(0).clientY);
                            var fingerB = new Vertex_1.Vertex(e.touches.item(1).clientX, e.touches.item(1).clientY);
                            var center = new Line_1.Line(fingerA, fingerB).vertAt(0.5);
                            _self.setZoom(multiTouchStartScale.x * e.zoom, multiTouchStartScale.y * e.zoom, center);
                            _self.redraw();
                        }
                    });
                }
                catch (e) {
                    console.error("Failed to initialize AlloyFinger!");
                    console.error(e);
                }
                ;
            }
            else if (globalThis["Touchy"] && typeof globalThis["Touchy"] == "function") {
                console.error('[Deprecation] Found Touchy which is not supported any more. Please use AlloyFinger instead.');
                // Convert absolute touch positions to relative DOM element position (relative to canvas)
            }
            else {
                console.warn("Cannot initialize the touch handler. AlloyFinger is missig. Did you include it?");
            }
        }
        else {
            _self.console.log('Touch interaction disabled.');
        }
        if (this.config.enableKeys) {
            // Install key handler
            this.keyHandler = new KeyHandler_1.KeyHandler({ trackAll: true })
                .down('escape', function () {
                _self.clearSelection(true);
            })
                .down('shift', function () {
                _self.selectPolygon = new Polygon_1.Polygon();
                _self.redraw();
            })
                .up('shift', function () {
                // Find and select vertices in the drawn area
                if (_self.selectPolygon == null)
                    return;
                _self.selectVerticesInPolygon(_self.selectPolygon);
                _self.selectPolygon = null;
                _self.redraw();
            });
        } // END IF enableKeys?
        else {
            _self.console.log('Keyboard interaction disabled.');
        }
    };
    /**
     * Creates a control GUI (a dat.gui instance) for this
     * plot boilerplate instance.
     *
     * @method createGUI
     * @instance
     * @memberof PlotBoilerplate
     * @return {dat.gui.GUI}
     **/
    PlotBoilerplate.prototype.createGUI = function () {
        // This function moved to the helper utils.
        // We do not want to include the whole dat.GUI package.
        if (globalThis["utils"] && typeof globalThis["utils"].createGUI == "function")
            return globalThis["utils"].createGUI(this);
        else
            throw "Cannot create dat.GUI instance; did you load the ./utils/creategui helper function an the dat.GUI library?";
    };
    ;
    var _a;
    /** @constant {number} */
    PlotBoilerplate.DEFAULT_CANVAS_WIDTH = 1024;
    /** @constant {number} */
    PlotBoilerplate.DEFAULT_CANVAS_HEIGHT = 768;
    /** @constant {number} */
    PlotBoilerplate.DEFAULT_CLICK_TOLERANCE = 8;
    /** @constant {number} */
    PlotBoilerplate.DEFAULT_TOUCH_TOLERANCE = 32;
    /**
     * A wrapper class for draggable items (mostly vertices).
     * @private
     **/
    PlotBoilerplate.Draggable = (_a = /** @class */ (function () {
            function class_1(item, typeName) {
                this.item = item;
                this.typeName = typeName;
            }
            ;
            class_1.prototype.isVertex = function () { return this.typeName == PlotBoilerplate.Draggable.VERTEX; };
            ;
            class_1.prototype.setVIndex = function (vindex) { this.vindex = vindex; return this; };
            ;
            return class_1;
        }()),
        _a.VERTEX = 'vertex',
        _a);
    /**
     * A set of helper functions.
     **/
    PlotBoilerplate.utils = {
        /**
         * Merge the elements in the 'extension' object into the 'base' object based on
         * the keys of 'base'.
         *
         * @param {Object} base
         * @param {Object} extension
         * @return {Object} base extended by the new attributes.
         **/
        safeMergeByKeys: function (base, extension) {
            for (var k in extension) {
                if (!extension.hasOwnProperty(k))
                    continue;
                if (base.hasOwnProperty(k)) {
                    var typ = typeof base[k];
                    try {
                        if (typ == 'boolean')
                            base[k] = !!JSON.parse(extension[k]);
                        else if (typ == 'number')
                            base[k] = JSON.parse(extension[k]) * 1;
                        else if (typ == 'function' && typeof extension[k] == 'function')
                            base[k] = extension[k];
                        else
                            base[k] = extension[k];
                    }
                    catch (e) {
                        console.error('error in key ', k, extension[k], e);
                    }
                }
                else {
                    base[k] = extension[k];
                }
            }
            return base;
        },
        /**
         * A helper function to scale elements (usually the canvas) using CSS.
         *
         * transform-origin is at (0,0).
         *
         * @param {HTMLElement} element - The DOM element to scale.
         * @param {number} scaleX The - X scale factor.
         * @param {number} scaleY The - Y scale factor.
         * @return {void}
         **/
        setCSSscale: function (element, scaleX, scaleY) {
            element.style['transform-origin'] = '0 0';
            if (scaleX == 1.0 && scaleY == 1.0)
                element.style.transform = null;
            else
                element.style.transform = 'scale(' + scaleX + ',' + scaleY + ')';
        },
        // A helper for fetching data from objects.
        fetch: {
            /**
             * A helper function to the the object property value specified by the given key.
             *
             * @param {any} object   - The object to get the property's value from. Must not be null.
             * @param {string} key      - The key of the object property (the name).
             * @param {any}    fallback - A default value if the key does not exist.
             **/
            val: function (obj, key, fallback) {
                if (!obj.hasOwnProperty(key))
                    return fallback;
                if (typeof obj[key] == 'undefined')
                    return fallback;
                return obj[key];
            },
            /**
             * A helper function to the the object property numeric value specified by the given key.
             *
             * @param {any}    object   - The object to get the property's value from. Must not be null.
             * @param {string} key      - The key of the object property (the name).
             * @param {number} fallback - A default value if the key does not exist.
             * @return {number}
             **/
            num: function (obj, key, fallback) {
                if (!obj.hasOwnProperty(key))
                    return fallback;
                if (typeof obj[key] === 'number')
                    return obj[key];
                else {
                    try {
                        return JSON.parse(obj[key]) * 1;
                    }
                    catch (e) {
                        return fallback;
                    }
                }
            },
            /**
             * A helper function to the the object property boolean value specified by the given key.
             *
             * @param {any}     object   - The object to get the property's value from. Must not be null.
             * @param {string}  key      - The key of the object property (the name).
             * @param {boolean} fallback - A default value if the key does not exist.
             * @return {boolean}
             **/
            bool: function (obj, key, fallback) {
                if (!obj.hasOwnProperty(key))
                    return fallback;
                if (typeof obj[key] == 'boolean')
                    return obj[key];
                else {
                    try {
                        return !!JSON.parse(obj[key]);
                    }
                    catch (e) {
                        return fallback;
                    }
                }
            },
            /**
             * A helper function to the the object property function-value specified by the given key.
             *
             * @param {any}      object   - The object to get the property's value from. Must not be null.
             * @param {string}   key      - The key of the object property (the name).
             * @param {function} fallback - A default value if the key does not exist.
             * @return {function}
             **/
            func: function (obj, key, fallback) {
                if (!obj.hasOwnProperty(key))
                    return fallback;
                if (typeof obj[key] !== 'function')
                    return fallback;
                return obj[key];
            }
        },
        /**
         * Installs vertex listeners to the path's vertices so that controlpoints
         * move with their path points when dragged.
         *
         * Bézier path points with attr.bezierAutoAdjust==true will have their
         * two control points audo-updated if moved, too (keep path connections smooth).
         *
         * @param {BezierPath} bezierPath - The path to use auto-adjustment for.
         **/
        enableBezierPathAutoAdjust: function (bezierPath) {
            for (var i = 0; i < bezierPath.bezierCurves.length; i++) {
                // This should be wrapped into the BezierPath implementation.
                bezierPath.bezierCurves[i].startPoint.listeners.addDragListener(function (e) {
                    var cindex = bezierPath.locateCurveByStartPoint(e.params.vertex);
                    bezierPath.bezierCurves[cindex].startPoint.addXY(-e.params.dragAmount.x, -e.params.dragAmount.y);
                    bezierPath.moveCurvePoint(cindex * 1, bezierPath.START_POINT, e.params.dragAmount);
                    bezierPath.updateArcLengths();
                });
                bezierPath.bezierCurves[i].startControlPoint.listeners.addDragListener(function (e) {
                    var cindex = bezierPath.locateCurveByStartControlPoint(e.params.vertex);
                    if (!bezierPath.bezierCurves[cindex].startPoint.attr.bezierAutoAdjust)
                        return;
                    bezierPath.adjustPredecessorControlPoint(cindex * 1, true, // obtain handle length?
                    false // update arc lengths
                    );
                    bezierPath.updateArcLengths();
                });
                bezierPath.bezierCurves[i].endControlPoint.listeners.addDragListener(function (e) {
                    var cindex = bezierPath.locateCurveByEndControlPoint(e.params.vertex);
                    if (!bezierPath.bezierCurves[cindex % bezierPath.bezierCurves.length].endPoint.attr.bezierAutoAdjust)
                        return;
                    bezierPath.adjustSuccessorControlPoint(cindex * 1, true, // obtain handle length?
                    false // update arc lengths
                    );
                    bezierPath.updateArcLengths();
                });
                if (i + 1 == bezierPath.bezierCurves.length) { // && !bezierPath.adjustCircular ) { 
                    // Move last control point with the end point (if not circular)
                    bezierPath.bezierCurves[bezierPath.bezierCurves.length - 1].endPoint.listeners.addDragListener(function (e) {
                        if (!bezierPath.adjustCircular) {
                            var cindex = bezierPath.locateCurveByEndPoint(e.params.vertex);
                            bezierPath.moveCurvePoint(cindex * 1, bezierPath.END_CONTROL_POINT, new Vertex_1.Vertex({ x: e.params.dragAmount.x, y: e.params.dragAmount.y }));
                        }
                        bezierPath.updateArcLengths();
                    });
                }
            } // END for
        },
        /**
         * Removes vertex listeners from the path's vertices. This needs to be called
         * when BezierPaths are removed from the canvas.
         *
         * Sorry, this is not yet implemented.
         *
         * @param {BezierPath} bezierPath - The path to use un-auto-adjustment for.
         **/
        disableBezierPathAutoAdjust: function (bezierPath) {
            // How to determine which listeners are mine???
            /*
              for( var i = 0; i < bezierPath.bezierCurves.length; i++ ) {
            // Just try to remove listeners from all vertices on the Bézier path.
            // No matter if there are not listeners installed for some reason.
            bezierPath.bezierCurves[i].startPoint.listeners.removeDragListener( );
            }
            */
        }
    }; // END utils
    return PlotBoilerplate;
}()); // END class PlotBoilerplate
exports.PlotBoilerplate = PlotBoilerplate;
//# sourceMappingURL=PlotBoilerplate.js.map