plotboilerplate
Version:
A simple javascript plotting boilerplate for 2d stuff.
981 lines • 116 kB
JavaScript
"use strict";
/**
* @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.
* @modified 2020-12-11 Added the `removeAll(boolean)` function.
* @modified 2020-12-17 Added the `CircleSector` drawable.
* @modified 2021-01-04 Avoiding multiple redraw call on adding multiple Drawables (array).
* @modified 2021-01-08 Added param `draw:DraLib<void>` to the methods `drawVertices`, `drawGrid` and `drawSelectPolygon`.
* @modified 2021-01-08 Added the customizable `drawAll(...)` function.
* @modified 2021-01-09 Added the `drawDrawable(...)` function.
* @modified 2021-01-10 Added the `eventCatcher` element (used to track mouse events on SVGs).
* @modified 2021-01-26 Fixed SVG resizing.
* @modified 2021-01-26 Replaced the old SVGBuilder by the new `drawutilssvg` library.
* @modified 2021-02-08 Fixed a lot of es2015 compatibility issues.
* @modified 2021-02-18 Adding `adjustOffset(boolean)` function.
* @modified 2021-03-01 Updated the `PlotBoilerplate.draw(...)` function: ellipses are now rotate-able.
* @modified 2021-03-03 Added the `VEllipseSector` drawable.
* @modified 2021-03-29 Clearing `currentClassName` and `currentId` after drawing each drawable.
* @modified 2021-04-25 Extending `remove` to accept arrays of drawables.
* @modified 2021-11-16 Adding the `PBText` drawable.
* @modified 2022-08-01 Added `title` to the params.
* @modified 2022-10-25 Added the `origin` to the default draw config.
* @modified 2022-11-06 Adding an XML declaration to the SVG export routine.
* @modified 2022-11-23 Added the `drawRaster` (default=true) option to the config/drawconfig.
* @modified 2023-02-04 Fixed a bug in the `drawDrawable` function; fill's current classname was not set.
* @modified 2023-02-10 Fixing an issue of the `style.position` setting when `fitToParent=true` from `absolute` to `static` (default).
* @modified 2023-02-10 Cleaning up most type errors in the main class (mostly null checks).
* @modified 2023-02-10 Adding `enableZoom` and `enablePan` (both default true) to have the option to disable these functions.
* @modified 2023-09-29 Adding proper dicionary key and value types to the params of `PlotBoilerplate.utils.safeMergeByKeys` (was `object` before).
* @modified 2024-07-08 Adding `PlotBoilerplate.getGUI()` to retrieve the GUI instance.
* @modified 2024-08-25 Extending main class `PlotBoilerplate` optional param `isBackdropFiltersEnabled`.
* @modified 2024-12-02 Adding the `triggerRedraw` to the `removeAll` method.
* @modified 2025-05-07 Changing the return type of `removeVertex` from `void` to `boolean`.
* @modified 2025-05-07 Handling content changes now with `contentChangeListeners`.
* @modified 2025-05-07 Added `PlogBoilerplate.addContentChangeListener` and `.removeContentChangeListener`.
* @modified 2025-05-07 Moving full vectors now by default when vector point a is moved.
* @modified 2025-05-20 Applying `lineWith` parameter in the draw routine for vectors (had been missing).
*
* @version 1.21.1
*
* @file PlotBoilerplate
* @fileoverview The main class.
* @public
**/
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PlotBoilerplate = void 0;
var alloyfinger_typescript_1 = require("alloyfinger-typescript");
var draw_1 = require("./draw");
var drawgl_1 = require("./drawgl");
var drawutilssvg_1 = require("./drawutilssvg");
var BezierPath_1 = require("./BezierPath");
var Bounds_1 = require("./Bounds");
var Circle_1 = require("./Circle");
var CircleSector_1 = require("./CircleSector");
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 Triangle_1 = require("./Triangle");
var VEllipse_1 = require("./VEllipse");
var VEllipseSector_1 = require("./VEllipseSector");
var Vector_1 = require("./Vector");
var Vertex_1 = require("./Vertex");
var VertexAttr_1 = require("./VertexAttr");
var PBText_1 = require("./PBText");
/**
* @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 Drawable
* @requires DrawConfig
* @requires IHooks
* @requires PBParams
* @requires Triangle
* @requires drawutils
* @requires drawutilsgl
* @requires SVGSerializable
* @requires XYCoords
* @requires XYDimension
*/
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.enablePan=true] - (default true) Set to false if you want to disable panning completely.
* @param {boolean=} [config.enableZoom=true] - (default true) Set to false if you want to disable zooming completely.
* @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.
* @param {string=} [config.title=null] - Specify any hover tile here. It will be attached as a `title` attribute to the most elevated element.
*/
function PlotBoilerplate(config, drawConfig) {
var _b, _c;
/**
* A list of content change listeners.
*/
this.contentChangeListeners = [];
/**
* A discrete timestamp to identify single render cycles.
* Note that using system time milliseconds is not a safe way to identify render frames, as on modern powerful machines
* multiple frames might be rendered within each millisecond.
* @member {number}
* @memberof plotboilerplate
* @instance
* @private
*/
this.renderTime = 0;
/**
* A storage variable for retrieving the GUI instance once it was created.
*/
this._gui = null;
// 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
*/
var f = PlotBoilerplate.utils.fetch;
this.config = {
canvas: config.canvas,
fullSize: f.val(config, "fullSize", true),
fitToParent: f.bool(config, "fitToParent", true),
scaleX: f.num(config, "scaleX", 1.0),
scaleY: f.num(config, "scaleY", 1.0),
offsetX: f.num(config, "offsetX", 0.0),
offsetY: f.num(config, "offsetY", 0.0),
rasterGrid: f.bool(config, "rasterGrid", true),
drawRaster: f.bool(config, "drawRaster", true),
rasterScaleX: f.num(config, "rasterScaleX", 1.0),
rasterScaleY: f.num(config, "rasterScaleY", 1.0),
rasterAdjustFactor: f.num(config, "rasterAdjustdFactror", 2.0),
drawOrigin: f.bool(config, "drawOrigin", false),
autoAdjustOffset: f.val(config, "autoAdjustOffset", true),
offsetAdjustXPercent: f.num(config, "offsetAdjustXPercent", 50),
offsetAdjustYPercent: f.num(config, "offsetAdjustYPercent", 50),
backgroundColor: config.backgroundColor || "#ffffff",
redrawOnResize: f.bool(config, "redrawOnResize", true),
defaultCanvasWidth: f.num(config, "defaultCanvasWidth", PlotBoilerplate.DEFAULT_CANVAS_WIDTH),
defaultCanvasHeight: f.num(config, "defaultCanvasHeight", PlotBoilerplate.DEFAULT_CANVAS_HEIGHT),
canvasWidthFactor: f.num(config, "canvasWidthFactor", 1.0),
canvasHeightFactor: f.num(config, "canvasHeightFactor", 1.0),
cssScaleX: f.num(config, "cssScaleX", 1.0),
cssScaleY: f.num(config, "cssScaleY", 1.0),
cssUniformScale: f.bool(config, "cssUniformScale", true),
saveFile: function () {
_self.hooks.saveFile(_self);
},
setToRetina: function () {
_self._setToRetina();
},
autoDetectRetina: f.bool(config, "autoDetectRetina", true),
enableSVGExport: f.bool(config, "enableSVGExport", true),
// Listeners/observers
preClear: f.func(config, "preClear", null),
preDraw: f.func(config, "preDraw", null),
postDraw: f.func(config, "postDraw", null),
// Interaction
enableMouse: f.bool(config, "enableMouse", true),
enableTouch: f.bool(config, "enableTouch", true),
enableKeys: f.bool(config, "enableKeys", true),
enableMouseWheel: f.bool(config, "enableMouseWheel", true),
enableZoom: f.bool(config, "enableZoom", true), // default=true
enablePan: f.bool(config, "enablePan", true), // default=true
// Experimental (and unfinished)
enableGL: f.bool(config, "enableGL", false),
isBackdropFiltersEnabled: f.bool(config, "isBackdropFiltersEnabled", true)
}; // END confog
/**
* Configuration for drawing things.
*
* @member {Object}
* @memberof PlotBoilerplate
* @instance
*/
this.drawConfig = {
drawVertices: true,
drawBezierHandleLines: f.bool(config, "drawBezierHandleLines", true),
drawBezierHandlePoints: f.bool(config, "drawBezierHandlePoints", true),
drawHandleLines: f.bool(config, "drawHandleLines", true),
drawHandlePoints: f.bool(config, "drawHandlePoints", true),
drawGrid: f.bool(config, "drawGrid", true),
drawRaster: f.bool(config, "drawRaster", true),
bezier: {
color: "#00a822",
lineWidth: 2,
handleLine: {
color: "rgba(180,180,180,0.5)",
lineWidth: 1
},
pathVertex: {
color: "#B400FF",
lineWidth: 1,
fill: true
},
controlVertex: {
color: "#B8D438",
lineWidth: 1,
fill: true
}
},
// bezierPath: {
// color: "#0022a8",
// lineWidth: 1
// },
polygon: {
color: "#0022a8",
lineWidth: 1
},
triangle: {
color: "#6600ff",
lineWidth: 1
},
ellipse: {
color: "#2222a8",
lineWidth: 1
},
ellipseSector: {
color: "#a822a8",
lineWidth: 2
},
circle: {
color: "#22a8a8",
lineWidth: 2
},
circleSector: {
color: "#2280a8",
lineWidth: 1
},
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
},
text: {
color: "rgba(192,0,128,0.5)",
lineWidth: 1,
fill: true,
anchor: true
},
origin: {
color: "#000000"
}
}; // END drawConfig
// +---------------------------------------------------------------------------------
// | Object members.
// +-------------------------------
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 };
var canvasElement = typeof config.canvas === "string" ? document.querySelector(config.canvas) : config.canvas;
if (typeof canvasElement === "undefined") {
throw "Cannot initialize PlotBoilerplate with a null canvas (element \"".concat(config.canvas, " not found).");
}
// Which renderer to use: Canvas2D, WebGL (experimental) or SVG?
if (canvasElement.tagName.toLowerCase() === "canvas") {
this.canvas = canvasElement;
this.eventCatcher = this.canvas;
if (this.config.enableGL && typeof drawgl_1.drawutilsgl === "undefined") {
console.warn("Cannot use webgl. Package was compiled without experimental gl support. Please use plotboilerplate-glsupport.min.js instead.");
console.warn("Disabling GL and falling back to Canvas2D.");
this.config.enableGL = false;
}
if (this.config.enableGL) {
// Override the case 'null' here. If GL is not supported, well then nothing works.
var ctx = this.canvas.getContext("webgl"); // webgl-experimental?
this.draw = new drawgl_1.drawutilsgl(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 {
// Override the case 'null' here. If context creation is not supported, well then nothing works.
var ctx = this.canvas.getContext("2d");
this.draw = new draw_1.drawutils(ctx, false);
this.fill = new draw_1.drawutils(ctx, true);
}
}
else if (canvasElement.tagName.toLowerCase() === "svg") {
if (typeof drawutilssvg_1.drawutilssvg === "undefined")
throw "The svg draw library is not yet integrated part of PlotBoilerplate. Please include ./src/js/utils/helpers/drawutils.svg into your document.";
this.canvas = canvasElement;
this.draw = new drawutilssvg_1.drawutilssvg(this.canvas, new Vertex_1.Vertex(), // offset
new Vertex_1.Vertex(), // scale
this.canvasSize, false, // fillShapes=false
this.drawConfig, false // isSecondary=false
);
this.fill = this.draw.copyInstance(true); // fillShapes=true
if (this.canvas.parentElement) {
this.eventCatcher = document.createElement("div");
this.eventCatcher.style.position = "absolute";
this.eventCatcher.style.left = "0";
this.eventCatcher.style.top = "0";
this.eventCatcher.style.cursor = "pointer";
this.canvas.parentElement.style.position = "relative";
this.canvas.parentElement.appendChild(this.eventCatcher);
}
else {
this.eventCatcher = document.body;
}
}
else {
throw "Element is neither a canvas nor an svg element.";
}
// At this point the event cacher element is deinfed and located at highest elevation.
// Set `title` attribut?
if (config.title) {
this.eventCatcher.setAttribute("title", config.title);
}
this.draw.scale.set((_b = this.config.scaleX) !== null && _b !== void 0 ? _b : 1.0, this.config.scaleY);
this.fill.scale.set((_c = this.config.scaleX) !== null && _c !== void 0 ? _c : 1.0, this.config.scaleY);
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;
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) {
// Create fake SVG node
var svgNode = document.createElementNS("http://www.w3.org/2000/svg", "svg");
// Draw everything to fake node.
var tosvgDraw = new drawutilssvg_1.drawutilssvg(svgNode, pb.draw.offset, pb.draw.scale, pb.canvasSize, false, // fillShapes=false
pb.drawConfig);
var tosvgFill = tosvgDraw.copyInstance(true); // fillShapes=true
tosvgDraw.beginDrawCycle(0);
tosvgFill.beginDrawCycle(0);
if (pb.config.preClear) {
pb.config.preClear();
}
tosvgDraw.clear(pb.config.backgroundColor || "white");
if (pb.config.preDraw) {
pb.config.preDraw(tosvgDraw, tosvgFill);
}
pb.drawAll(0, tosvgDraw, tosvgFill);
pb.drawVertices(0, tosvgDraw);
if (pb.config.postDraw)
pb.config.postDraw(tosvgDraw, tosvgFill);
tosvgDraw.endDrawCycle(0);
tosvgFill.endDrawCycle(0);
// Full support in all browsers \o/
// https://caniuse.com/xml-serializer
var serializer = new XMLSerializer();
var svgCode = serializer.serializeToString(svgNode);
// Add: '<?xml version="1.0" encoding="utf-8"?>\n' ?
var blob = new Blob(['<?xml version="1.0" encoding="utf-8"?>\n' + 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 and 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;
this.config.canvasWidthFactor = this.config.canvasHeightFactor = pixelRatio;
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) {
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 () {
var _b, _c, _d, _e;
if (this.config.cssUniformScale) {
PlotBoilerplate.utils.setCSSscale(this.canvas, (_b = this.config.cssScaleX) !== null && _b !== void 0 ? _b : 1.0, (_c = this.config.cssScaleX) !== null && _c !== void 0 ? _c : 1.0);
}
else {
PlotBoilerplate.utils.setCSSscale(this.canvas, (_d = this.config.cssScaleX) !== null && _d !== void 0 ? _d : 1.0, (_e = this.config.cssScaleY) !== null && _e !== void 0 ? _e : 1.0);
}
};
/**
* Adds a new content change listener to this instance. Adding duplicates is not possible.
*
* @param {PBContentChangeListener} listener - The listenre to add.
* @method addContentChangeListener
* @instance
* @memberof PlotBoilerplate
* @returns {void}
*/
PlotBoilerplate.prototype.addContentChangeListener = function (listener) {
for (var i in this.contentChangeListeners) {
if (this.contentChangeListeners[i] === listener) {
return;
}
}
this.contentChangeListeners.push(listener);
};
/**
* Removes an existing content change listener from this instance.
*
* @param {PBContentChangeListener} listener - The listenre to add.
* @method removeContentChangeListener
* @instance
* @memberof PlotBoilerplate
* @returns {void}
*/
PlotBoilerplate.prototype.removeContentChangeListener = function (listener) {
for (var i = 0; i < this.contentChangeListeners.length; i++) {
if (this.contentChangeListeners[i] === listener) {
this.contentChangeListeners.splice(i, 1);
return;
}
}
};
PlotBoilerplate.prototype._fireContentChanged = function (addedDrawables, removedDrawables) {
for (var i in this.contentChangeListeners) {
var listener = this.contentChangeListeners[i];
if (listener && typeof listener === "function") {
listener({
type: addedDrawables.length > 0 ? "DRAWABLES_ADDED" : "DRAWABLES_REMOVED",
addedDrawables: addedDrawables,
removedDrawables: removedDrawables
});
}
}
};
/**
* Add a drawable object.<br>
* <br>
* This must be either:<br>
* <pre>
* * a Vertex
* * a Line
* * a Vector
* * a VEllipse
* * a VEllipseSector
* * 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, doNotFireEvent) {
if (Array.isArray(drawable)) {
var arr = drawable;
for (var i = 0; i < arr.length; i++) {
this.add(arr[i], false, doNotFireEvent);
}
// !doNotFireEvent && this._fireContentChanged(arr, []);
}
else {
var addedDrawables = [drawable];
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);
addedDrawables.push(drawable.a, drawable.b);
}
else if (drawable instanceof Vector_1.Vector) {
this.drawables.push(drawable);
this.vertices.push(drawable.a);
this.vertices.push(drawable.b);
addedDrawables.push(drawable.a, drawable.b);
drawable.a.listeners.addDragListener(function (event) {
drawable.b.add(event.params.dragAmount);
});
}
else if (drawable instanceof VEllipse_1.VEllipse) {
this.vertices.push(drawable.center);
this.vertices.push(drawable.axis);
addedDrawables.push(drawable.center, drawable.axis);
this.drawables.push(drawable);
drawable.center.listeners.addDragListener(function (event) {
drawable.axis.add(event.params.dragAmount);
});
}
else if (drawable instanceof VEllipseSector_1.VEllipseSector) {
this.vertices.push(drawable.ellipse.center);
this.vertices.push(drawable.ellipse.axis);
addedDrawables.push(drawable.ellipse.center, drawable.ellipse.axis);
this.drawables.push(drawable);
drawable.ellipse.center.listeners.addDragListener(function (event) {
drawable.ellipse.axis.add(event.params.dragAmount);
});
}
else if (drawable instanceof Circle_1.Circle) {
this.vertices.push(drawable.center);
addedDrawables.push(drawable.center);
this.drawables.push(drawable);
}
else if (drawable instanceof CircleSector_1.CircleSector) {
this.vertices.push(drawable.circle.center);
addedDrawables.push(drawable.circle.center);
this.drawables.push(drawable);
}
else if (drawable instanceof Polygon_1.Polygon) {
this.drawables.push(drawable);
for (var i = 0; i < drawable.vertices.length; i++) {
this.vertices.push(drawable.vertices[i]);
addedDrawables.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);
addedDrawables.push(drawable.a, drawable.b, 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);
addedDrawables.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);
addedDrawables.push(bezierPath.bezierCurves[i].endPoint, bezierPath.bezierCurves[i].startControlPoint, 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);
addedDrawables.push(drawable.upperLeft, 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 if (drawable instanceof PBText_1.PBText) {
this.vertices.push(drawable.anchor);
addedDrawables.push(drawable.anchor);
this.drawables.push(drawable);
drawable.anchor.attr.selectable = false;
}
else {
throw "Cannot add drawable of unrecognized type: " + typeof drawable + ".";
}
!doNotFireEvent && this._fireContentChanged(addedDrawables, []);
}
// 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 {Drawable|Array<Drawable>} drawable - The drawable (of one of the allowed class instance) to remove.
* @param {boolean} [redraw=false]
* @param {removeWidth}
* @method remove
* @instance
* @memberof PlotBoilerplate
* @return {void}
**/
PlotBoilerplate.prototype.remove = function (drawable, redraw, removeWithVertices, doNotFireEvent) {
if (Array.isArray(drawable)) {
var removedDrawables_1 = [];
for (var i = 0; i < drawable.length; i++) {
if (this.remove(drawable[i], false, removeWithVertices, true)) {
removedDrawables_1.push(drawable[i]);
}
}
if (redraw) {
this.redraw();
}
!doNotFireEvent && this._fireContentChanged([], removedDrawables_1);
return removedDrawables_1.length > 0;
}
if (drawable instanceof Vertex_1.Vertex) {
var wasRemoved_1 = this.removeVertex(drawable, false, false);
if (redraw) {
this.redraw();
}
!doNotFireEvent && this._fireContentChanged([], [drawable]);
return wasRemoved_1;
}
var wasRemoved = false;
var removedDrawables = [];
for (var i = 0; i < this.drawables.length; i++) {
if (this.drawables[i] === drawable || this.drawables[i].uid === drawable.uid) {
this.drawables.splice(i, 1);
removedDrawables.push(drawable);
if (removeWithVertices) {
// Check if some listeners need to be removed
if (drawable instanceof Line_1.Line) {
// Add some lines
this.removeVertex(drawable.a, false, true);
this.removeVertex(drawable.b, false, true);
removedDrawables.push(drawable.a, drawable.b);
}
else if (drawable instanceof Vector_1.Vector) {
this.removeVertex(drawable.a, false, true);
this.removeVertex(drawable.b, false, true);
removedDrawables.push(drawable.a, drawable.b);
}
else if (drawable instanceof VEllipse_1.VEllipse) {
this.removeVertex(drawable.center, false, true);
this.removeVertex(drawable.axis, false, true);
removedDrawables.push(drawable.center, drawable.axis);
}
else if (drawable instanceof VEllipseSector_1.VEllipseSector) {
this.removeVertex(drawable.ellipse.center, false, true);
this.removeVertex(drawable.ellipse.axis, false, true);
removedDrawables.push(drawable.ellipse.center, drawable.ellipse.axis);
}
else if (drawable instanceof Circle_1.Circle) {
this.removeVertex(drawable.center, false, true);
}
else if (drawable instanceof CircleSector_1.CircleSector) {
this.removeVertex(drawable.circle.center, false, true);
removedDrawables.push(drawable.circle.center);
}
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, true);
removedDrawables.push(drawable.vertices[i]);
}
}
else if (drawable instanceof Triangle_1.Triangle) {
this.removeVertex(drawable.a, false, true);
this.removeVertex(drawable.b, false, true);
this.removeVertex(drawable.c, false, true);
removedDrawables.push(drawable.a, drawable.b, drawable.c);
}
else if (drawable instanceof BezierPath_1.BezierPath) {
for (var i = 0; i < drawable.bezierCurves.length; i++) {
this.removeVertex(drawable.bezierCurves[i].startPoint, false, true);
this.removeVertex(drawable.bezierCurves[i].startControlPoint, false, true);
this.removeVertex(drawable.bezierCurves[i].endControlPoint, false, true);
removedDrawables.push(drawable.bezierCurves[i].startPoint, drawable.bezierCurves[i].startControlPoint, drawable.bezierCurves[i].endControlPoint);
if (i + 1 == drawable.bezierCurves.length) {
this.removeVertex(drawable.bezierCurves[i].endPoint, false, true);
removedDrawables.push(drawable.bezierCurves[i].endPoint);
}
}
}
else if (drawable instanceof PBImage_1.PBImage) {
this.removeVertex(drawable.upperLeft, false, true);
this.removeVertex(drawable.lowerRight, false, true);
removedDrawables.push(drawable.upperLeft, drawable.lowerRight);
}
else if (drawable instanceof PBText_1.PBText) {
this.removeVertex(drawable.anchor, false, true);
removedDrawables.push(drawable.anchor);
}
} // END removeWithVertices
if (redraw) {
this.redraw();
}
!doNotFireEvent && this._fireContentChanged([], removedDrawables);
wasRemoved = true;
} // END if
} // END for
return wasRemoved;
};
/**
* 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 {boolean}
**/
PlotBoilerplate.prototype.removeVertex = function (vert, redraw, doNotFireEvent) {
for (var i = 0; i < this.vertices.length; i++) {
if (this.vertices[i] === vert) {
this.vertices.splice(i, 1);
if (redraw) {
this.redraw();
}
!doNotFireEvent && this._fireContentChanged([], [vert]);
return true;
}
}
return false;
};
/**
* Remove all elements.
*
* If you want to keep the vertices, pass `true`.
*
* @method removeAll
* @param {boolean=false} keepVertices
* @param {boolean=true} triggerRedraw - By default this method triggers the redraw routine; passing `false` will suppress redrawing.
* @instance
* @memberof PlotBoilerplate
* @return {void}
*/
PlotBoilerplate.prototype.removeAll = function (keepVertices, triggerRedraw) {
var removedDrawables = this.drawables;
this.drawables = [];
if (!Boolean(keepVertices)) {
removedDrawables = removedDrawables.concat(this.vertices);
this.vertices = [];
}
if (triggerRedraw || typeof triggerRedraw === "undefined") {
this.redraw();
}
removedDrawables.length > 0 && this._fireContentChanged([], removedDrawables);
};
/**
* 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.getVertexN