Project

General

Profile

« Previous | Next » 

Revision 3406fef9

Added by Andreas Kohlbecker over 5 years ago

  • ID 3406fef993edb5c9716cb5b4549a0bf26ce4b65f
  • Child bab39794

initial version of the taxonGraph visualization using cytoscape.js with dagre v0.6.4

View differences:

.gitignore
1
.idea
cytoscape-dagre.js
1
/*!
2
Copyright (c) The Cytoscape Consortium
3

  
4
Permission is hereby granted, free of charge, to any person obtaining a copy of
5
this software and associated documentation files (the “Software”), to deal in
6
the Software without restriction, including without limitation the rights to
7
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8
of the Software, and to permit persons to whom the Software is furnished to do
9
so, subject to the following conditions:
10

  
11
The above copyright notice and this permission notice shall be included in all
12
copies or substantial portions of the Software.
13

  
14
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
SOFTWARE.
21
*/
22

  
23
;(function(){ 'use strict';
24

  
25
  // registers the extension on a cytoscape lib ref
26
  var register = function( cytoscape, dagre ){
27
    if( !cytoscape || !dagre ){ return; } // can't register if cytoscape unspecified
28

  
29
    var isFunction = function(o){ return typeof o === 'function'; };
30

  
31
    // default layout options
32
    var defaults = {
33
      // dagre algo options, uses default value on undefined
34
      nodeSep: undefined, // the separation between adjacent nodes in the same rank
35
      edgeSep: undefined, // the separation between adjacent edges in the same rank
36
      rankSep: undefined, // the separation between adjacent nodes in the same rank
37
      rankDir: undefined, // 'TB' for top to bottom flow, 'LR' for left to right
38
      minLen: function( edge ){ return 1; }, // number of ranks to keep between the source and target of the edge
39
      edgeWeight: function( edge ){ return 1; }, // higher weight edges are generally made shorter and straighter than lower weight edges
40

  
41
      // general layout options
42
      fit: true, // whether to fit to viewport
43
      padding: 30, // fit padding
44
      animate: false, // whether to transition the node positions
45
      animationDuration: 500, // duration of animation in ms if enabled
46
      animationEasing: undefined, // easing of animation if enabled
47
      boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
48
      ready: function(){}, // on layoutready
49
      stop: function(){} // on layoutstop
50
    };
51

  
52
    // constructor
53
    // options : object containing layout options
54
    function DagreLayout( options ){
55
      var opts = this.options = {};
56
      for( var i in defaults ){ opts[i] = defaults[i]; }
57
      for( var i in options ){ opts[i] = options[i]; }
58
    }
59

  
60
    // runs the layout
61
    DagreLayout.prototype.run = function(){
62
      var options = this.options;
63
      var layout = this;
64

  
65
      var cy = options.cy; // cy is automatically populated for us in the constructor
66
      var eles = options.eles;
67

  
68
      var getVal = function( ele, val ){
69
        return isFunction(val) ? val.apply( ele, [ ele ] ) : val;
70
      };
71

  
72
      var bb = options.boundingBox || { x1: 0, y1: 0, w: cy.width(), h: cy.height() };
73
      if( bb.x2 === undefined ){ bb.x2 = bb.x1 + bb.w; }
74
      if( bb.w === undefined ){ bb.w = bb.x2 - bb.x1; }
75
      if( bb.y2 === undefined ){ bb.y2 = bb.y1 + bb.h; }
76
      if( bb.h === undefined ){ bb.h = bb.y2 - bb.y1; }
77

  
78
      var g = new dagre.graphlib.Graph({
79
        multigraph: true,
80
        compound: true
81
      });
82

  
83
      var gObj = {};
84
      var setGObj = function( name, val ){
85
        if( val != null ){
86
          gObj[ name ] = val;
87
        }
88
      };
89

  
90
      setGObj( 'nodesep', options.nodeSep );
91
      setGObj( 'edgesep', options.edgeSep );
92
      setGObj( 'ranksep', options.rankSep );
93
      setGObj( 'rankdir', options.rankDir );
94

  
95
      g.setGraph( gObj );
96

  
97
      g.setDefaultEdgeLabel(function() { return {}; });
98
      g.setDefaultNodeLabel(function() { return {}; });
99

  
100
      // add nodes to dagre
101
      var nodes = eles.nodes();
102
      for( var i = 0; i < nodes.length; i++ ){
103
        var node = nodes[i];
104
        var nbb = node.boundingBox();
105

  
106
        g.setNode( node.id(), {
107
          width: nbb.w,
108
          height: nbb.h,
109
          name: node.id()
110
        } );
111

  
112
        // console.log( g.node(node.id()) );
113
      }
114

  
115
      // set compound parents
116
      for( var i = 0; i < nodes.length; i++ ){
117
        var node = nodes[i];
118

  
119
        if( node.isChild() ){
120
          g.setParent( node.id(), node.parent().id() );
121
        }
122
      }
123

  
124
      // add edges to dagre
125
      var edges = eles.edges().stdFilter(function( edge ){
126
        return !edge.source().isParent() && !edge.target().isParent(); // dagre can't handle edges on compound nodes
127
      });
128
      for( var i = 0; i < edges.length; i++ ){
129
        var edge = edges[i];
130

  
131
        g.setEdge( edge.source().id(), edge.target().id(), {
132
          minlen: getVal( edge, options.minLen ),
133
          weight: getVal( edge, options.edgeWeight ),
134
          name: edge.id()
135
        }, edge.id() );
136

  
137
        // console.log( g.edge(edge.source().id(), edge.target().id(), edge.id()) );
138
      }
139

  
140
      dagre.layout( g );
141

  
142
      var gNodeIds = g.nodes();
143
      for( var i = 0; i < gNodeIds.length; i++ ){
144
        var id = gNodeIds[i];
145
        var n = g.node( id );
146

  
147
        cy.getElementById(id).scratch().dagre = n;
148
      }
149

  
150
      var dagreBB;
151

  
152
      if( options.boundingBox ){
153
        dagreBB = { x1: Infinity, x2: -Infinity, y1: Infinity, y2: -Infinity };
154
        nodes.forEach(function( node ){
155
          var dModel = node.scratch().dagre;
156

  
157
          dagreBB.x1 = Math.min( dagreBB.x1, dModel.x );
158
          dagreBB.x2 = Math.max( dagreBB.x2, dModel.x );
159

  
160
          dagreBB.y1 = Math.min( dagreBB.y1, dModel.y );
161
          dagreBB.y2 = Math.max( dagreBB.y2, dModel.y );
162
        });
163

  
164
        dagreBB.w = dagreBB.x2 - dagreBB.x1;
165
        dagreBB.h = dagreBB.y2 - dagreBB.y1;
166
      } else {
167
        dagreBB = bb;
168
      }
169

  
170
      var constrainPos = function( p ){
171
        if( options.boundingBox ){
172
          var xPct = dagreBB.w === 0 ? 0 : (p.x - dagreBB.x1) / dagreBB.w;
173
          var yPct = dagreBB.h === 0 ? 0 : (p.y - dagreBB.y1) / dagreBB.h;
174

  
175
          return {
176
            x: bb.x1 + xPct * bb.w,
177
            y: bb.y1 + yPct * bb.h
178
          };
179
        } else {
180
          return p;
181
        }
182
      };
183

  
184
      nodes.layoutPositions(layout, options, function( ele ){
185
        ele = typeof ele === "object" ? ele : this;
186
        var dModel = ele.scratch().dagre;
187

  
188
        return constrainPos({
189
          x: dModel.x,
190
          y: dModel.y
191
        });
192
      });
193

  
194
      return this; // chaining
195
    };
196

  
197
    cytoscape('layout', 'dagre', DagreLayout);
198

  
199
  };
200

  
201
  if( typeof module !== 'undefined' && module.exports ){ // expose as a commonjs module
202
    module.exports = function( cytoscape, dagre ){
203
      register( cytoscape, dagre || require('dagre') );
204
    };
205
  } else if( typeof define !== 'undefined' && define.amd ){ // expose as an amd/requirejs module
206
    define('cytoscape-dagre', function(){
207
      return register;
208
    });
209
  }
210

  
211
  if( typeof cytoscape !== 'undefined' && typeof dagre !== 'undefined' ){ // expose to global cytoscape (i.e. window.cytoscape)
212
    register( cytoscape, dagre );
213
  }
214

  
215
})();
cytoscape.js
1
(function webpackUniversalModuleDefinition(root, factory) {
2
	if(typeof exports === 'object' && typeof module === 'object')
3
		module.exports = factory();
4
	else if(typeof define === 'function' && define.amd)
5
		define([], factory);
6
	else if(typeof exports === 'object')
7
		exports["cytoscape"] = factory();
8
	else
9
		root["cytoscape"] = factory();
10
})(typeof self !== 'undefined' ? self : this, function() {
11
return /******/ (function(modules) { // webpackBootstrap
12
/******/ 	// The module cache
13
/******/ 	var installedModules = {};
14
/******/
15
/******/ 	// The require function
16
/******/ 	function __webpack_require__(moduleId) {
17
/******/
18
/******/ 		// Check if module is in cache
19
/******/ 		if(installedModules[moduleId]) {
20
/******/ 			return installedModules[moduleId].exports;
21
/******/ 		}
22
/******/ 		// Create a new module (and put it into the cache)
23
/******/ 		var module = installedModules[moduleId] = {
24
/******/ 			i: moduleId,
25
/******/ 			l: false,
26
/******/ 			exports: {}
27
/******/ 		};
28
/******/
29
/******/ 		// Execute the module function
30
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
31
/******/
32
/******/ 		// Flag the module as loaded
33
/******/ 		module.l = true;
34
/******/
35
/******/ 		// Return the exports of the module
36
/******/ 		return module.exports;
37
/******/ 	}
38
/******/
39
/******/
40
/******/ 	// expose the modules object (__webpack_modules__)
41
/******/ 	__webpack_require__.m = modules;
42
/******/
43
/******/ 	// expose the module cache
44
/******/ 	__webpack_require__.c = installedModules;
45
/******/
46
/******/ 	// define getter function for harmony exports
47
/******/ 	__webpack_require__.d = function(exports, name, getter) {
48
/******/ 		if(!__webpack_require__.o(exports, name)) {
49
/******/ 			Object.defineProperty(exports, name, {
50
/******/ 				configurable: false,
51
/******/ 				enumerable: true,
52
/******/ 				get: getter
53
/******/ 			});
54
/******/ 		}
55
/******/ 	};
56
/******/
57
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
58
/******/ 	__webpack_require__.n = function(module) {
59
/******/ 		var getter = module && module.__esModule ?
60
/******/ 			function getDefault() { return module['default']; } :
61
/******/ 			function getModuleExports() { return module; };
62
/******/ 		__webpack_require__.d(getter, 'a', getter);
63
/******/ 		return getter;
64
/******/ 	};
65
/******/
66
/******/ 	// Object.prototype.hasOwnProperty.call
67
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
68
/******/
69
/******/ 	// __webpack_public_path__
70
/******/ 	__webpack_require__.p = "";
71
/******/
72
/******/ 	// Load entry module and return exports
73
/******/ 	return __webpack_require__(__webpack_require__.s = 20);
74
/******/ })
75
/************************************************************************/
76
/******/ ([
77
/* 0 */
78
/***/ (function(module, exports, __webpack_require__) {
79

  
80
"use strict";
81

  
82

  
83
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
84

  
85
/*global HTMLElement DocumentTouch */
86

  
87
var window = __webpack_require__(3);
88
var navigator = window ? window.navigator : null;
89
var document = window ? window.document : null;
90

  
91
var typeofstr = _typeof('');
92
var typeofobj = _typeof({});
93
var typeoffn = _typeof(function () {});
94
var typeofhtmlele = typeof HTMLElement === 'undefined' ? 'undefined' : _typeof(HTMLElement);
95

  
96
var instanceStr = function instanceStr(obj) {
97
  return obj && obj.instanceString && is.fn(obj.instanceString) ? obj.instanceString() : null;
98
};
99

  
100
var is = {
101
  defined: function defined(obj) {
102
    return obj != null; // not undefined or null
103
  },
104

  
105
  string: function string(obj) {
106
    return obj != null && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) == typeofstr;
107
  },
108

  
109
  fn: function fn(obj) {
110
    return obj != null && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === typeoffn;
111
  },
112

  
113
  array: function array(obj) {
114
    return Array.isArray ? Array.isArray(obj) : obj != null && obj instanceof Array;
115
  },
116

  
117
  plainObject: function plainObject(obj) {
118
    return obj != null && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === typeofobj && !is.array(obj) && obj.constructor === Object;
119
  },
120

  
121
  object: function object(obj) {
122
    return obj != null && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === typeofobj;
123
  },
124

  
125
  number: function number(obj) {
126
    return obj != null && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === _typeof(1) && !isNaN(obj);
127
  },
128

  
129
  integer: function integer(obj) {
130
    return is.number(obj) && Math.floor(obj) === obj;
131
  },
132

  
133
  bool: function bool(obj) {
134
    return obj != null && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === _typeof(true);
135
  },
136

  
137
  htmlElement: function htmlElement(obj) {
138
    if ('undefined' === typeofhtmlele) {
139
      return undefined;
140
    } else {
141
      return null != obj && obj instanceof HTMLElement;
142
    }
143
  },
144

  
145
  elementOrCollection: function elementOrCollection(obj) {
146
    return is.element(obj) || is.collection(obj);
147
  },
148

  
149
  element: function element(obj) {
150
    return instanceStr(obj) === 'collection' && obj._private.single;
151
  },
152

  
153
  collection: function collection(obj) {
154
    return instanceStr(obj) === 'collection' && !obj._private.single;
155
  },
156

  
157
  core: function core(obj) {
158
    return instanceStr(obj) === 'core';
159
  },
160

  
161
  style: function style(obj) {
162
    return instanceStr(obj) === 'style';
163
  },
164

  
165
  stylesheet: function stylesheet(obj) {
166
    return instanceStr(obj) === 'stylesheet';
167
  },
168

  
169
  event: function event(obj) {
170
    return instanceStr(obj) === 'event';
171
  },
172

  
173
  thread: function thread(obj) {
174
    return instanceStr(obj) === 'thread';
175
  },
176

  
177
  fabric: function fabric(obj) {
178
    return instanceStr(obj) === 'fabric';
179
  },
180

  
181
  emptyString: function emptyString(obj) {
182
    if (obj === undefined || obj === null) {
183
      // null is empty
184
      return true;
185
    } else if (obj === '' || obj.match(/^\s+$/)) {
186
      return true; // empty string is empty
187
    }
188

  
189
    return false; // otherwise, we don't know what we've got
190
  },
191

  
192
  nonemptyString: function nonemptyString(obj) {
193
    if (obj && is.string(obj) && obj !== '' && !obj.match(/^\s+$/)) {
194
      return true;
195
    }
196

  
197
    return false;
198
  },
199

  
200
  domElement: function domElement(obj) {
201
    if (typeof HTMLElement === 'undefined') {
202
      return false; // we're not in a browser so it doesn't matter
203
    } else {
204
      return obj instanceof HTMLElement;
205
    }
206
  },
207

  
208
  boundingBox: function boundingBox(obj) {
209
    return is.plainObject(obj) && is.number(obj.x1) && is.number(obj.x2) && is.number(obj.y1) && is.number(obj.y2);
210
  },
211

  
212
  promise: function promise(obj) {
213
    return is.object(obj) && is.fn(obj.then);
214
  },
215

  
216
  touch: function touch() {
217
    return window && ('ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch);
218
  },
219

  
220
  gecko: function gecko() {
221
    return window && (typeof InstallTrigger !== 'undefined' || 'MozAppearance' in document.documentElement.style);
222
  },
223

  
224
  webkit: function webkit() {
225
    return window && (typeof webkitURL !== 'undefined' || 'WebkitAppearance' in document.documentElement.style);
226
  },
227

  
228
  chromium: function chromium() {
229
    return window && typeof chrome !== 'undefined';
230
  },
231

  
232
  khtml: function khtml() {
233
    return navigator && navigator.vendor.match(/kde/i); // probably a better way to detect this...
234
  },
235

  
236
  khtmlEtc: function khtmlEtc() {
237
    return is.khtml() || is.webkit() || is.chromium();
238
  },
239

  
240
  ms: function ms() {
241
    return navigator && navigator.userAgent.match(/msie|trident|edge/i); // probably a better way to detect this...
242
  },
243

  
244
  windows: function windows() {
245
    return navigator && navigator.appVersion.match(/Win/i);
246
  },
247

  
248
  mac: function mac() {
249
    return navigator && navigator.appVersion.match(/Mac/i);
250
  },
251

  
252
  linux: function linux() {
253
    return navigator && navigator.appVersion.match(/Linux/i);
254
  },
255

  
256
  unix: function unix() {
257
    return navigator && navigator.appVersion.match(/X11/i);
258
  }
259
};
260

  
261
module.exports = is;
262

  
263
/***/ }),
264
/* 1 */
265
/***/ (function(module, exports, __webpack_require__) {
266

  
267
"use strict";
268

  
269

  
270
/*global console */
271

  
272
var is = __webpack_require__(0);
273
var math = __webpack_require__(2);
274

  
275
var util = {
276

  
277
  MAX_INT: Number.MAX_SAFE_INTEGER || 9007199254740991,
278

  
279
  trueify: function trueify() {
280
    return true;
281
  },
282

  
283
  falsify: function falsify() {
284
    return false;
285
  },
286

  
287
  zeroify: function zeroify() {
288
    return 0;
289
  },
290

  
291
  noop: function noop() {},
292

  
293
  error: function error(msg) {
294
    /* eslint-disable */
295
    if (console.error) {
296
      console.error.apply(console, arguments);
297

  
298
      if (console.trace) {
299
        console.trace();
300
      }
301
    } else {
302
      console.log.apply(console, arguments);
303

  
304
      if (console.trace) {
305
        console.trace();
306
      }
307
    }
308
    /* eslint-enable */
309
  },
310

  
311
  clone: function clone(obj) {
312
    return this.extend({}, obj);
313
  },
314

  
315
  // gets a shallow copy of the argument
316
  copy: function copy(obj) {
317
    if (obj == null) {
318
      return obj;
319
    }if (is.array(obj)) {
320
      return obj.slice();
321
    } else if (is.plainObject(obj)) {
322
      return this.clone(obj);
323
    } else {
324
      return obj;
325
    }
326
  },
327

  
328
  copyArray: function copyArray(arr) {
329
    return arr.slice();
330
  },
331

  
332
  clonePosition: function clonePosition(pos) {
333
    return { x: pos.x, y: pos.y };
334
  },
335

  
336
  uuid: function uuid(a, b // placeholders
337
  ) {
338
    for ( // loop :)
339
    b = a = ''; // b - result , a - numeric letiable
340
    a++ < 36; //
341
    b += a * 51 & 52 // if "a" is not 9 or 14 or 19 or 24
342
    ? //  return a random number or 4
343
    (a ^ 15 // if "a" is not 15
344
    ? // genetate a random number from 0 to 15
345
    8 ^ Math.random() * (a ^ 20 ? 16 : 4) // unless "a" is 20, in which case a random number from 8 to 11
346
    : 4 //  otherwise 4
347
    ).toString(16) : '-' //  in other cases (if "a" is 9,14,19,24) insert "-"
348
    ) {}
349
    return b;
350
  }
351

  
352
};
353

  
354
util.makeBoundingBox = math.makeBoundingBox.bind(math);
355

  
356
util._staticEmptyObject = {};
357

  
358
util.staticEmptyObject = function () {
359
  return util._staticEmptyObject;
360
};
361

  
362
util.extend = Object.assign != null ? Object.assign.bind(Object) : function (tgt) {
363
  var args = arguments;
364

  
365
  for (var i = 1; i < args.length; i++) {
366
    var obj = args[i];
367

  
368
    if (obj == null) {
369
      continue;
370
    }
371

  
372
    var keys = Object.keys(obj);
373

  
374
    for (var j = 0; j < keys.length; j++) {
375
      var k = keys[j];
376

  
377
      tgt[k] = obj[k];
378
    }
379
  }
380

  
381
  return tgt;
382
};
383

  
384
util.assign = util.extend;
385

  
386
util.default = function (val, def) {
387
  if (val === undefined) {
388
    return def;
389
  } else {
390
    return val;
391
  }
392
};
393

  
394
util.removeFromArray = function (arr, ele, manyCopies) {
395
  for (var i = arr.length; i >= 0; i--) {
396
    if (arr[i] === ele) {
397
      arr.splice(i, 1);
398

  
399
      if (!manyCopies) {
400
        break;
401
      }
402
    }
403
  }
404
};
405

  
406
util.clearArray = function (arr) {
407
  arr.splice(0, arr.length);
408
};
409

  
410
util.push = function (arr, otherArr) {
411
  for (var i = 0; i < otherArr.length; i++) {
412
    var el = otherArr[i];
413

  
414
    arr.push(el);
415
  }
416
};
417

  
418
util.getPrefixedProperty = function (obj, propName, prefix) {
419
  if (prefix) {
420
    propName = this.prependCamel(prefix, propName); // e.g. (labelWidth, source) => sourceLabelWidth
421
  }
422

  
423
  return obj[propName];
424
};
425

  
426
util.setPrefixedProperty = function (obj, propName, prefix, value) {
427
  if (prefix) {
428
    propName = this.prependCamel(prefix, propName); // e.g. (labelWidth, source) => sourceLabelWidth
429
  }
430

  
431
  obj[propName] = value;
432
};
433

  
434
[__webpack_require__(21), __webpack_require__(22), { memoize: __webpack_require__(13) }, __webpack_require__(23), __webpack_require__(24), __webpack_require__(25), __webpack_require__(27)].forEach(function (req) {
435
  util.extend(util, req);
436
});
437

  
438
module.exports = util;
439

  
440
/***/ }),
441
/* 2 */
442
/***/ (function(module, exports, __webpack_require__) {
443

  
444
"use strict";
445

  
446

  
447
var math = {};
448

  
449
math.arePositionsSame = function (p1, p2) {
450
  return p1.x === p2.x && p1.y === p2.y;
451
};
452

  
453
math.copyPosition = function (p) {
454
  return { x: p.x, y: p.y };
455
};
456

  
457
math.modelToRenderedPosition = function (p, zoom, pan) {
458
  return {
459
    x: p.x * zoom + pan.x,
460
    y: p.y * zoom + pan.y
461
  };
462
};
463

  
464
math.renderedToModelPosition = function (p, zoom, pan) {
465
  return {
466
    x: (p.x - pan.x) / zoom,
467
    y: (p.y - pan.y) / zoom
468
  };
469
};
470

  
471
math.array2point = function (arr) {
472
  return {
473
    x: arr[0],
474
    y: arr[1]
475
  };
476
};
477

  
478
math.deg2rad = function (deg) {
479
  return Math.PI * deg / 180;
480
};
481

  
482
math.getAngleFromDisp = function (dispX, dispY) {
483
  return Math.atan2(dispY, dispX) - Math.PI / 2;
484
};
485

  
486
math.log2 = Math.log2 || function (n) {
487
  return Math.log(n) / Math.log(2);
488
};
489

  
490
math.signum = function (x) {
491
  if (x > 0) {
492
    return 1;
493
  } else if (x < 0) {
494
    return -1;
495
  } else {
496
    return 0;
497
  }
498
};
499

  
500
math.dist = function (p1, p2) {
501
  return Math.sqrt(math.sqdist(p1, p2));
502
};
503

  
504
math.sqdist = function (p1, p2) {
505
  var dx = p2.x - p1.x;
506
  var dy = p2.y - p1.y;
507

  
508
  return dx * dx + dy * dy;
509
};
510

  
511
// from http://en.wikipedia.org/wiki/Bézier_curve#Quadratic_curves
512
math.qbezierAt = function (p0, p1, p2, t) {
513
  return (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2;
514
};
515

  
516
math.qbezierPtAt = function (p0, p1, p2, t) {
517
  return {
518
    x: math.qbezierAt(p0.x, p1.x, p2.x, t),
519
    y: math.qbezierAt(p0.y, p1.y, p2.y, t)
520
  };
521
};
522

  
523
math.lineAt = function (p0, p1, t, d) {
524
  var vec = {
525
    x: p1.x - p0.x,
526
    y: p1.y - p0.y
527
  };
528

  
529
  var vecDist = math.dist(p0, p1);
530

  
531
  var normVec = {
532
    x: vec.x / vecDist,
533
    y: vec.y / vecDist
534
  };
535

  
536
  t = t == null ? 0 : t;
537

  
538
  d = d != null ? d : t * vecDist;
539

  
540
  return {
541
    x: p0.x + normVec.x * d,
542
    y: p0.y + normVec.y * d
543
  };
544
};
545

  
546
math.lineAtDist = function (p0, p1, d) {
547
  return math.lineAt(p0, p1, undefined, d);
548
};
549

  
550
// get angle at A via cosine law
551
math.triangleAngle = function (A, B, C) {
552
  var a = math.dist(B, C);
553
  var b = math.dist(A, C);
554
  var c = math.dist(A, B);
555

  
556
  return Math.acos((a * a + b * b - c * c) / (2 * a * b));
557
};
558

  
559
math.bound = function (min, val, max) {
560
  return Math.max(min, Math.min(max, val));
561
};
562

  
563
// makes a full bb (x1, y1, x2, y2, w, h) from implicit params
564
math.makeBoundingBox = function (bb) {
565
  if (bb == null) {
566
    return {
567
      x1: Infinity,
568
      y1: Infinity,
569
      x2: -Infinity,
570
      y2: -Infinity,
571
      w: 0,
572
      h: 0
573
    };
574
  } else if (bb.x1 != null && bb.y1 != null) {
575
    if (bb.x2 != null && bb.y2 != null && bb.x2 >= bb.x1 && bb.y2 >= bb.y1) {
576
      return {
577
        x1: bb.x1,
578
        y1: bb.y1,
579
        x2: bb.x2,
580
        y2: bb.y2,
581
        w: bb.x2 - bb.x1,
582
        h: bb.y2 - bb.y1
583
      };
584
    } else if (bb.w != null && bb.h != null && bb.w >= 0 && bb.h >= 0) {
585
      return {
586
        x1: bb.x1,
587
        y1: bb.y1,
588
        x2: bb.x1 + bb.w,
589
        y2: bb.y1 + bb.h,
590
        w: bb.w,
591
        h: bb.h
592
      };
593
    }
594
  }
595
};
596

  
597
math.updateBoundingBox = function (bb1, bb2) {
598
  // update bb1 with bb2 bounds
599

  
600
  bb1.x1 = Math.min(bb1.x1, bb2.x1);
601
  bb1.x2 = Math.max(bb1.x2, bb2.x2);
602
  bb1.w = bb1.x2 - bb1.x1;
603

  
604
  bb1.y1 = Math.min(bb1.y1, bb2.y1);
605
  bb1.y2 = Math.max(bb1.y2, bb2.y2);
606
  bb1.h = bb1.y2 - bb1.y1;
607
};
608

  
609
math.expandBoundingBoxByPoint = function (bb, x, y) {
610
  bb.x1 = Math.min(bb.x1, x);
611
  bb.x2 = Math.max(bb.x2, x);
612
  bb.w = bb.x2 - bb.x1;
613

  
614
  bb.y1 = Math.min(bb.y1, y);
615
  bb.y2 = Math.max(bb.y2, y);
616
  bb.h = bb.y2 - bb.y1;
617
};
618

  
619
math.expandBoundingBox = function (bb, padding) {
620
  bb.x1 -= padding;
621
  bb.x2 += padding;
622
  bb.y1 -= padding;
623
  bb.y2 += padding;
624
  bb.w = bb.x2 - bb.x1;
625
  bb.h = bb.y2 - bb.y1;
626

  
627
  return bb;
628
};
629

  
630
math.boundingBoxesIntersect = function (bb1, bb2) {
631
  // case: one bb to right of other
632
  if (bb1.x1 > bb2.x2) {
633
    return false;
634
  }
635
  if (bb2.x1 > bb1.x2) {
636
    return false;
637
  }
638

  
639
  // case: one bb to left of other
640
  if (bb1.x2 < bb2.x1) {
641
    return false;
642
  }
643
  if (bb2.x2 < bb1.x1) {
644
    return false;
645
  }
646

  
647
  // case: one bb above other
648
  if (bb1.y2 < bb2.y1) {
649
    return false;
650
  }
651
  if (bb2.y2 < bb1.y1) {
652
    return false;
653
  }
654

  
655
  // case: one bb below other
656
  if (bb1.y1 > bb2.y2) {
657
    return false;
658
  }
659
  if (bb2.y1 > bb1.y2) {
660
    return false;
661
  }
662

  
663
  // otherwise, must have some overlap
664
  return true;
665
};
666

  
667
math.inBoundingBox = function (bb, x, y) {
668
  return bb.x1 <= x && x <= bb.x2 && bb.y1 <= y && y <= bb.y2;
669
};
670

  
671
math.pointInBoundingBox = function (bb, pt) {
672
  return this.inBoundingBox(bb, pt.x, pt.y);
673
};
674

  
675
math.boundingBoxInBoundingBox = function (bb1, bb2) {
676
  return math.inBoundingBox(bb1, bb2.x1, bb2.y1) && math.inBoundingBox(bb1, bb2.x2, bb2.y2);
677
};
678

  
679
math.roundRectangleIntersectLine = function (x, y, nodeX, nodeY, width, height, padding) {
680

  
681
  var cornerRadius = this.getRoundRectangleRadius(width, height);
682

  
683
  var halfWidth = width / 2;
684
  var halfHeight = height / 2;
685

  
686
  // Check intersections with straight line segments
687
  var straightLineIntersections = void 0;
688

  
689
  // Top segment, left to right
690
  {
691
    var topStartX = nodeX - halfWidth + cornerRadius - padding;
692
    var topStartY = nodeY - halfHeight - padding;
693
    var topEndX = nodeX + halfWidth - cornerRadius + padding;
694
    var topEndY = topStartY;
695

  
696
    straightLineIntersections = this.finiteLinesIntersect(x, y, nodeX, nodeY, topStartX, topStartY, topEndX, topEndY, false);
697

  
698
    if (straightLineIntersections.length > 0) {
699
      return straightLineIntersections;
700
    }
701
  }
702

  
703
  // Right segment, top to bottom
704
  {
705
    var rightStartX = nodeX + halfWidth + padding;
706
    var rightStartY = nodeY - halfHeight + cornerRadius - padding;
707
    var rightEndX = rightStartX;
708
    var rightEndY = nodeY + halfHeight - cornerRadius + padding;
709

  
710
    straightLineIntersections = this.finiteLinesIntersect(x, y, nodeX, nodeY, rightStartX, rightStartY, rightEndX, rightEndY, false);
711

  
712
    if (straightLineIntersections.length > 0) {
713
      return straightLineIntersections;
714
    }
715
  }
716

  
717
  // Bottom segment, left to right
718
  {
719
    var bottomStartX = nodeX - halfWidth + cornerRadius - padding;
720
    var bottomStartY = nodeY + halfHeight + padding;
721
    var bottomEndX = nodeX + halfWidth - cornerRadius + padding;
722
    var bottomEndY = bottomStartY;
723

  
724
    straightLineIntersections = this.finiteLinesIntersect(x, y, nodeX, nodeY, bottomStartX, bottomStartY, bottomEndX, bottomEndY, false);
725

  
726
    if (straightLineIntersections.length > 0) {
727
      return straightLineIntersections;
728
    }
729
  }
730

  
731
  // Left segment, top to bottom
732
  {
733
    var leftStartX = nodeX - halfWidth - padding;
734
    var leftStartY = nodeY - halfHeight + cornerRadius - padding;
735
    var leftEndX = leftStartX;
736
    var leftEndY = nodeY + halfHeight - cornerRadius + padding;
737

  
738
    straightLineIntersections = this.finiteLinesIntersect(x, y, nodeX, nodeY, leftStartX, leftStartY, leftEndX, leftEndY, false);
739

  
740
    if (straightLineIntersections.length > 0) {
741
      return straightLineIntersections;
742
    }
743
  }
744

  
745
  // Check intersections with arc segments
746
  var arcIntersections = void 0;
747

  
748
  // Top Left
749
  {
750
    var topLeftCenterX = nodeX - halfWidth + cornerRadius;
751
    var topLeftCenterY = nodeY - halfHeight + cornerRadius;
752
    arcIntersections = this.intersectLineCircle(x, y, nodeX, nodeY, topLeftCenterX, topLeftCenterY, cornerRadius + padding);
753

  
754
    // Ensure the intersection is on the desired quarter of the circle
755
    if (arcIntersections.length > 0 && arcIntersections[0] <= topLeftCenterX && arcIntersections[1] <= topLeftCenterY) {
756
      return [arcIntersections[0], arcIntersections[1]];
757
    }
758
  }
759

  
760
  // Top Right
761
  {
762
    var topRightCenterX = nodeX + halfWidth - cornerRadius;
763
    var topRightCenterY = nodeY - halfHeight + cornerRadius;
764
    arcIntersections = this.intersectLineCircle(x, y, nodeX, nodeY, topRightCenterX, topRightCenterY, cornerRadius + padding);
765

  
766
    // Ensure the intersection is on the desired quarter of the circle
767
    if (arcIntersections.length > 0 && arcIntersections[0] >= topRightCenterX && arcIntersections[1] <= topRightCenterY) {
768
      return [arcIntersections[0], arcIntersections[1]];
769
    }
770
  }
771

  
772
  // Bottom Right
773
  {
774
    var bottomRightCenterX = nodeX + halfWidth - cornerRadius;
775
    var bottomRightCenterY = nodeY + halfHeight - cornerRadius;
776
    arcIntersections = this.intersectLineCircle(x, y, nodeX, nodeY, bottomRightCenterX, bottomRightCenterY, cornerRadius + padding);
777

  
778
    // Ensure the intersection is on the desired quarter of the circle
779
    if (arcIntersections.length > 0 && arcIntersections[0] >= bottomRightCenterX && arcIntersections[1] >= bottomRightCenterY) {
780
      return [arcIntersections[0], arcIntersections[1]];
781
    }
782
  }
783

  
784
  // Bottom Left
785
  {
786
    var bottomLeftCenterX = nodeX - halfWidth + cornerRadius;
787
    var bottomLeftCenterY = nodeY + halfHeight - cornerRadius;
788
    arcIntersections = this.intersectLineCircle(x, y, nodeX, nodeY, bottomLeftCenterX, bottomLeftCenterY, cornerRadius + padding);
789

  
790
    // Ensure the intersection is on the desired quarter of the circle
791
    if (arcIntersections.length > 0 && arcIntersections[0] <= bottomLeftCenterX && arcIntersections[1] >= bottomLeftCenterY) {
792
      return [arcIntersections[0], arcIntersections[1]];
793
    }
794
  }
795

  
796
  return []; // if nothing
797
};
798

  
799
math.inLineVicinity = function (x, y, lx1, ly1, lx2, ly2, tolerance) {
800
  var t = tolerance;
801

  
802
  var x1 = Math.min(lx1, lx2);
803
  var x2 = Math.max(lx1, lx2);
804
  var y1 = Math.min(ly1, ly2);
805
  var y2 = Math.max(ly1, ly2);
806

  
807
  return x1 - t <= x && x <= x2 + t && y1 - t <= y && y <= y2 + t;
808
};
809

  
810
math.inBezierVicinity = function (x, y, x1, y1, x2, y2, x3, y3, tolerance) {
811

  
812
  var bb = {
813
    x1: Math.min(x1, x3, x2) - tolerance,
814
    x2: Math.max(x1, x3, x2) + tolerance,
815
    y1: Math.min(y1, y3, y2) - tolerance,
816
    y2: Math.max(y1, y3, y2) + tolerance
817
  };
818

  
819
  // if outside the rough bounding box for the bezier, then it can't be a hit
820
  if (x < bb.x1 || x > bb.x2 || y < bb.y1 || y > bb.y2) {
821
    // console.log('bezier out of rough bb')
822
    return false;
823
  } else {
824
    // console.log('do more expensive check');
825
    return true;
826
  }
827
};
828
math.solveQuadratic = function (a, b, c, val) {
829
  c -= val;
830

  
831
  var r = b * b - 4 * a * c;
832

  
833
  if (r < 0) {
834
    return [];
835
  }
836

  
837
  var sqrtR = Math.sqrt(r);
838
  var denom = 2 * a;
839
  var root1 = (-b + sqrtR) / denom;
840
  var root2 = (-b - sqrtR) / denom;
841

  
842
  return [root1, root2];
843
};
844

  
845
math.solveCubic = function (a, b, c, d, result) {
846

  
847
  // Solves a cubic function, returns root in form [r1, i1, r2, i2, r3, i3], where
848
  // r is the real component, i is the imaginary component
849

  
850
  // An implementation of the Cardano method from the year 1545
851
  // http://en.wikipedia.org/wiki/Cubic_function#The_nature_of_the_roots
852

  
853
  b /= a;
854
  c /= a;
855
  d /= a;
856

  
857
  var discriminant = void 0,
858
      q = void 0,
859
      r = void 0,
860
      dum1 = void 0,
861
      s = void 0,
862
      t = void 0,
863
      term1 = void 0,
864
      r13 = void 0;
865

  
866
  q = (3.0 * c - b * b) / 9.0;
867
  r = -(27.0 * d) + b * (9.0 * c - 2.0 * (b * b));
868
  r /= 54.0;
869

  
870
  discriminant = q * q * q + r * r;
871
  result[1] = 0;
872
  term1 = b / 3.0;
873

  
874
  if (discriminant > 0) {
875
    s = r + Math.sqrt(discriminant);
876
    s = s < 0 ? -Math.pow(-s, 1.0 / 3.0) : Math.pow(s, 1.0 / 3.0);
877
    t = r - Math.sqrt(discriminant);
878
    t = t < 0 ? -Math.pow(-t, 1.0 / 3.0) : Math.pow(t, 1.0 / 3.0);
879
    result[0] = -term1 + s + t;
880
    term1 += (s + t) / 2.0;
881
    result[4] = result[2] = -term1;
882
    term1 = Math.sqrt(3.0) * (-t + s) / 2;
883
    result[3] = term1;
884
    result[5] = -term1;
885
    return;
886
  }
887

  
888
  result[5] = result[3] = 0;
889

  
890
  if (discriminant === 0) {
891
    r13 = r < 0 ? -Math.pow(-r, 1.0 / 3.0) : Math.pow(r, 1.0 / 3.0);
892
    result[0] = -term1 + 2.0 * r13;
893
    result[4] = result[2] = -(r13 + term1);
894
    return;
895
  }
896

  
897
  q = -q;
898
  dum1 = q * q * q;
899
  dum1 = Math.acos(r / Math.sqrt(dum1));
900
  r13 = 2.0 * Math.sqrt(q);
901
  result[0] = -term1 + r13 * Math.cos(dum1 / 3.0);
902
  result[2] = -term1 + r13 * Math.cos((dum1 + 2.0 * Math.PI) / 3.0);
903
  result[4] = -term1 + r13 * Math.cos((dum1 + 4.0 * Math.PI) / 3.0);
904

  
905
  return;
906
};
907

  
908
math.sqdistToQuadraticBezier = function (x, y, x1, y1, x2, y2, x3, y3) {
909

  
910
  // Find minimum distance by using the minimum of the distance
911
  // function between the given point and the curve
912

  
913
  // This gives the coefficients of the resulting cubic equation
914
  // whose roots tell us where a possible minimum is
915
  // (Coefficients are divided by 4)
916

  
917
  var a = 1.0 * x1 * x1 - 4 * x1 * x2 + 2 * x1 * x3 + 4 * x2 * x2 - 4 * x2 * x3 + x3 * x3 + y1 * y1 - 4 * y1 * y2 + 2 * y1 * y3 + 4 * y2 * y2 - 4 * y2 * y3 + y3 * y3;
918

  
919
  var b = 1.0 * 9 * x1 * x2 - 3 * x1 * x1 - 3 * x1 * x3 - 6 * x2 * x2 + 3 * x2 * x3 + 9 * y1 * y2 - 3 * y1 * y1 - 3 * y1 * y3 - 6 * y2 * y2 + 3 * y2 * y3;
920

  
921
  var c = 1.0 * 3 * x1 * x1 - 6 * x1 * x2 + x1 * x3 - x1 * x + 2 * x2 * x2 + 2 * x2 * x - x3 * x + 3 * y1 * y1 - 6 * y1 * y2 + y1 * y3 - y1 * y + 2 * y2 * y2 + 2 * y2 * y - y3 * y;
922

  
923
  var d = 1.0 * x1 * x2 - x1 * x1 + x1 * x - x2 * x + y1 * y2 - y1 * y1 + y1 * y - y2 * y;
924

  
925
  // debug("coefficients: " + a / a + ", " + b / a + ", " + c / a + ", " + d / a);
926

  
927
  var roots = [];
928

  
929
  // Use the cubic solving algorithm
930
  this.solveCubic(a, b, c, d, roots);
931

  
932
  var zeroThreshold = 0.0000001;
933

  
934
  var params = [];
935

  
936
  for (var index = 0; index < 6; index += 2) {
937
    if (Math.abs(roots[index + 1]) < zeroThreshold && roots[index] >= 0 && roots[index] <= 1.0) {
938
      params.push(roots[index]);
939
    }
940
  }
941

  
942
  params.push(1.0);
943
  params.push(0.0);
944

  
945
  var minDistanceSquared = -1;
946

  
947
  var curX = void 0,
948
      curY = void 0,
949
      distSquared = void 0;
950
  for (var i = 0; i < params.length; i++) {
951
    curX = Math.pow(1.0 - params[i], 2.0) * x1 + 2.0 * (1 - params[i]) * params[i] * x2 + params[i] * params[i] * x3;
952

  
953
    curY = Math.pow(1 - params[i], 2.0) * y1 + 2 * (1.0 - params[i]) * params[i] * y2 + params[i] * params[i] * y3;
954

  
955
    distSquared = Math.pow(curX - x, 2) + Math.pow(curY - y, 2);
956
    // debug('distance for param ' + params[i] + ": " + Math.sqrt(distSquared));
957
    if (minDistanceSquared >= 0) {
958
      if (distSquared < minDistanceSquared) {
959
        minDistanceSquared = distSquared;
960
      }
961
    } else {
962
      minDistanceSquared = distSquared;
963
    }
964
  }
965

  
966
  return minDistanceSquared;
967
};
968

  
969
math.sqdistToFiniteLine = function (x, y, x1, y1, x2, y2) {
970
  var offset = [x - x1, y - y1];
971
  var line = [x2 - x1, y2 - y1];
972

  
973
  var lineSq = line[0] * line[0] + line[1] * line[1];
974
  var hypSq = offset[0] * offset[0] + offset[1] * offset[1];
975

  
976
  var dotProduct = offset[0] * line[0] + offset[1] * line[1];
977
  var adjSq = dotProduct * dotProduct / lineSq;
978

  
979
  if (dotProduct < 0) {
980
    return hypSq;
981
  }
982

  
983
  if (adjSq > lineSq) {
984
    return (x - x2) * (x - x2) + (y - y2) * (y - y2);
985
  }
986

  
987
  return hypSq - adjSq;
988
};
989

  
990
math.pointInsidePolygonPoints = function (x, y, points) {
991
  var x1 = void 0,
992
      y1 = void 0,
993
      x2 = void 0,
994
      y2 = void 0;
995
  var y3 = void 0;
996

  
997
  // Intersect with vertical line through (x, y)
998
  var up = 0;
999
  // let down = 0;
1000
  for (var i = 0; i < points.length / 2; i++) {
1001
    x1 = points[i * 2];
1002
    y1 = points[i * 2 + 1];
1003

  
1004
    if (i + 1 < points.length / 2) {
1005
      x2 = points[(i + 1) * 2];
1006
      y2 = points[(i + 1) * 2 + 1];
1007
    } else {
1008
      x2 = points[(i + 1 - points.length / 2) * 2];
1009
      y2 = points[(i + 1 - points.length / 2) * 2 + 1];
1010
    }
1011

  
1012
    if (x1 == x && x2 == x) {
1013
      // then ignore
1014
    } else if (x1 >= x && x >= x2 || x1 <= x && x <= x2) {
1015

  
1016
      y3 = (x - x1) / (x2 - x1) * (y2 - y1) + y1;
1017

  
1018
      if (y3 > y) {
1019
        up++;
1020
      }
1021

  
1022
      // if( y3 < y ){
1023
      // down++;
1024
      // }
1025
    } else {
1026
      continue;
1027
    }
1028
  }
1029

  
1030
  if (up % 2 === 0) {
1031
    return false;
1032
  } else {
1033
    return true;
1034
  }
1035
};
1036

  
1037
math.pointInsidePolygon = function (x, y, basePoints, centerX, centerY, width, height, direction, padding) {
1038

  
1039
  //let direction = arguments[6];
1040
  var transformedPoints = new Array(basePoints.length);
1041

  
1042
  // Gives negative angle
1043
  var angle = void 0;
1044

  
1045
  if (direction[0] != null) {
1046
    angle = Math.atan(direction[1] / direction[0]);
1047

  
1048
    if (direction[0] < 0) {
1049
      angle = angle + Math.PI / 2;
1050
    } else {
1051
      angle = -angle - Math.PI / 2;
1052
    }
1053
  } else {
1054
    angle = direction;
1055
  }
1056

  
1057
  var cos = Math.cos(-angle);
1058
  var sin = Math.sin(-angle);
1059

  
1060
  //    console.log("base: " + basePoints);
1061
  for (var i = 0; i < transformedPoints.length / 2; i++) {
1062
    transformedPoints[i * 2] = width / 2 * (basePoints[i * 2] * cos - basePoints[i * 2 + 1] * sin);
1063

  
1064
    transformedPoints[i * 2 + 1] = height / 2 * (basePoints[i * 2 + 1] * cos + basePoints[i * 2] * sin);
1065

  
1066
    transformedPoints[i * 2] += centerX;
1067
    transformedPoints[i * 2 + 1] += centerY;
1068
  }
1069

  
1070
  var points = void 0;
1071

  
1072
  if (padding > 0) {
1073
    var expandedLineSet = this.expandPolygon(transformedPoints, -padding);
1074

  
1075
    points = this.joinLines(expandedLineSet);
1076
  } else {
1077
    points = transformedPoints;
1078
  }
1079

  
1080
  return math.pointInsidePolygonPoints(x, y, points);
1081
};
1082

  
1083
math.joinLines = function (lineSet) {
1084

  
1085
  var vertices = new Array(lineSet.length / 2);
1086

  
1087
  var currentLineStartX = void 0,
1088
      currentLineStartY = void 0,
1089
      currentLineEndX = void 0,
1090
      currentLineEndY = void 0;
1091
  var nextLineStartX = void 0,
1092
      nextLineStartY = void 0,
1093
      nextLineEndX = void 0,
1094
      nextLineEndY = void 0;
1095

  
1096
  for (var i = 0; i < lineSet.length / 4; i++) {
1097
    currentLineStartX = lineSet[i * 4];
1098
    currentLineStartY = lineSet[i * 4 + 1];
1099
    currentLineEndX = lineSet[i * 4 + 2];
1100
    currentLineEndY = lineSet[i * 4 + 3];
1101

  
1102
    if (i < lineSet.length / 4 - 1) {
1103
      nextLineStartX = lineSet[(i + 1) * 4];
1104
      nextLineStartY = lineSet[(i + 1) * 4 + 1];
1105
      nextLineEndX = lineSet[(i + 1) * 4 + 2];
1106
      nextLineEndY = lineSet[(i + 1) * 4 + 3];
1107
    } else {
1108
      nextLineStartX = lineSet[0];
1109
      nextLineStartY = lineSet[1];
1110
      nextLineEndX = lineSet[2];
1111
      nextLineEndY = lineSet[3];
1112
    }
1113

  
1114
    var intersection = this.finiteLinesIntersect(currentLineStartX, currentLineStartY, currentLineEndX, currentLineEndY, nextLineStartX, nextLineStartY, nextLineEndX, nextLineEndY, true);
1115

  
1116
    vertices[i * 2] = intersection[0];
1117
    vertices[i * 2 + 1] = intersection[1];
1118
  }
1119

  
1120
  return vertices;
1121
};
1122

  
1123
math.expandPolygon = function (points, pad) {
1124

  
1125
  var expandedLineSet = new Array(points.length * 2);
1126

  
1127
  var currentPointX = void 0,
1128
      currentPointY = void 0,
1129
      nextPointX = void 0,
1130
      nextPointY = void 0;
1131

  
1132
  for (var i = 0; i < points.length / 2; i++) {
1133
    currentPointX = points[i * 2];
1134
    currentPointY = points[i * 2 + 1];
1135

  
1136
    if (i < points.length / 2 - 1) {
1137
      nextPointX = points[(i + 1) * 2];
1138
      nextPointY = points[(i + 1) * 2 + 1];
1139
    } else {
1140
      nextPointX = points[0];
1141
      nextPointY = points[1];
1142
    }
1143

  
1144
    // Current line: [currentPointX, currentPointY] to [nextPointX, nextPointY]
1145

  
1146
    // Assume CCW polygon winding
1147

  
1148
    var offsetX = nextPointY - currentPointY;
1149
    var offsetY = -(nextPointX - currentPointX);
1150

  
1151
    // Normalize
1152
    var offsetLength = Math.sqrt(offsetX * offsetX + offsetY * offsetY);
1153
    var normalizedOffsetX = offsetX / offsetLength;
1154
    var normalizedOffsetY = offsetY / offsetLength;
1155

  
1156
    expandedLineSet[i * 4] = currentPointX + normalizedOffsetX * pad;
1157
    expandedLineSet[i * 4 + 1] = currentPointY + normalizedOffsetY * pad;
1158
    expandedLineSet[i * 4 + 2] = nextPointX + normalizedOffsetX * pad;
1159
    expandedLineSet[i * 4 + 3] = nextPointY + normalizedOffsetY * pad;
1160
  }
1161

  
1162
  return expandedLineSet;
1163
};
1164

  
1165
math.intersectLineEllipse = function (x, y, centerX, centerY, ellipseWradius, ellipseHradius) {
1166

  
1167
  var dispX = centerX - x;
1168
  var dispY = centerY - y;
1169

  
1170
  dispX /= ellipseWradius;
1171
  dispY /= ellipseHradius;
1172

  
1173
  var len = Math.sqrt(dispX * dispX + dispY * dispY);
1174

  
1175
  var newLength = len - 1;
1176

  
1177
  if (newLength < 0) {
1178
    return [];
1179
  }
1180

  
1181
  var lenProportion = newLength / len;
1182

  
1183
  return [(centerX - x) * lenProportion + x, (centerY - y) * lenProportion + y];
1184
};
1185

  
1186
math.checkInEllipse = function (x, y, width, height, centerX, centerY, padding) {
1187
  x -= centerX;
1188
  y -= centerY;
1189

  
1190
  x /= width / 2 + padding;
1191
  y /= height / 2 + padding;
1192

  
1193
  return x * x + y * y <= 1;
1194
};
1195

  
1196
// Returns intersections of increasing distance from line's start point
1197
math.intersectLineCircle = function (x1, y1, x2, y2, centerX, centerY, radius) {
1198

  
1199
  // Calculate d, direction vector of line
1200
  var d = [x2 - x1, y2 - y1]; // Direction vector of line
1201
  var f = [x1 - centerX, y1 - centerY];
1202

  
1203
  var a = d[0] * d[0] + d[1] * d[1];
1204
  var b = 2 * (f[0] * d[0] + f[1] * d[1]);
1205
  var c = f[0] * f[0] + f[1] * f[1] - radius * radius;
1206

  
1207
  var discriminant = b * b - 4 * a * c;
1208

  
1209
  if (discriminant < 0) {
1210
    return [];
1211
  }
1212

  
1213
  var t1 = (-b + Math.sqrt(discriminant)) / (2 * a);
1214
  var t2 = (-b - Math.sqrt(discriminant)) / (2 * a);
1215

  
1216
  var tMin = Math.min(t1, t2);
1217
  var tMax = Math.max(t1, t2);
1218
  var inRangeParams = [];
1219

  
1220
  if (tMin >= 0 && tMin <= 1) {
1221
    inRangeParams.push(tMin);
1222
  }
1223

  
1224
  if (tMax >= 0 && tMax <= 1) {
1225
    inRangeParams.push(tMax);
1226
  }
1227

  
1228
  if (inRangeParams.length === 0) {
1229
    return [];
1230
  }
1231

  
1232
  var nearIntersectionX = inRangeParams[0] * d[0] + x1;
1233
  var nearIntersectionY = inRangeParams[0] * d[1] + y1;
1234

  
1235
  if (inRangeParams.length > 1) {
1236

  
1237
    if (inRangeParams[0] == inRangeParams[1]) {
1238
      return [nearIntersectionX, nearIntersectionY];
1239
    } else {
1240

  
1241
      var farIntersectionX = inRangeParams[1] * d[0] + x1;
1242
      var farIntersectionY = inRangeParams[1] * d[1] + y1;
1243

  
1244
      return [nearIntersectionX, nearIntersectionY, farIntersectionX, farIntersectionY];
1245
    }
1246
  } else {
1247
    return [nearIntersectionX, nearIntersectionY];
1248
  }
1249
};
1250

  
1251
math.findCircleNearPoint = function (centerX, centerY, radius, farX, farY) {
1252

  
1253
  var displacementX = farX - centerX;
1254
  var displacementY = farY - centerY;
1255
  var distance = Math.sqrt(displacementX * displacementX + displacementY * displacementY);
1256

  
1257
  var unitDisplacementX = displacementX / distance;
1258
  var unitDisplacementY = displacementY / distance;
1259

  
1260
  return [centerX + unitDisplacementX * radius, centerY + unitDisplacementY * radius];
1261
};
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff