UNPKG

plotboilerplate

Version:

A simple javascript plotting boilerplate for 2d stuff.

981 lines 116 kB
"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