index.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  1. 'use strict';
  2. var minDom = require('min-dom');
  3. var tinySvg = require('tiny-svg');
  4. var minDash = require('min-dash');
  5. var Hammer = require('hammerjs');
  6. var EscapeUtil = require('diagram-js/lib/util/EscapeUtil');
  7. var GraphicsUtil = require('diagram-js/lib/util/GraphicsUtil');
  8. var MINIMAP_VIEWBOX_PADDING = 50;
  9. var RANGE = { min: 0.2, max: 4 },
  10. NUM_STEPS = 10;
  11. var DELTA_THRESHOLD = 0.1;
  12. var LOW_PRIORITY = 250;
  13. /**
  14. * A minimap that reflects and lets you navigate the diagram.
  15. */
  16. function Minimap(
  17. config, injector, eventBus,
  18. canvas, elementRegistry) {
  19. var self = this;
  20. this._canvas = canvas;
  21. this._elementRegistry = elementRegistry;
  22. this._eventBus = eventBus;
  23. this._injector = injector;
  24. this._state = {
  25. isOpen: undefined,
  26. isDragging: false,
  27. initialDragPosition: null,
  28. offsetViewport: null,
  29. cachedViewbox: null,
  30. dragger: null,
  31. svgClientRect: null,
  32. parentClientRect: null,
  33. zoomDelta: 0
  34. };
  35. this._init();
  36. var documentManager = new Hammer.Manager(document);
  37. documentManager.add(new Hammer.Pan());
  38. documentManager.on('panmove', onMousemove);
  39. documentManager.on('panend', onMouseup);
  40. var svgManager = new Hammer.Manager(this._svg);
  41. svgManager.add(new Hammer.Pan());
  42. svgManager.on('panstart', mousedown(true));
  43. svgManager.add(new Hammer.Tap());
  44. svgManager.on('tap', function(event) {
  45. centerViewbox(getPoint(event));
  46. });
  47. var viewportDomManager = new Hammer.Manager(this._viewportDom);
  48. viewportDomManager.add(new Hammer.Pan());
  49. viewportDomManager.on('panstart', mousedown(false));
  50. this.toggle((config && config.open) || false);
  51. function centerViewbox(point) {
  52. // getBoundingClientRect might return zero-dimensional when called for the first time
  53. if (!self._state._svgClientRect || isZeroDimensional(self._state._svgClientRect)) {
  54. self._state._svgClientRect = self._svg.getBoundingClientRect();
  55. }
  56. var diagramPoint = mapMousePositionToDiagramPoint({
  57. x: point.x - self._state._svgClientRect.left,
  58. y: point.y - self._state._svgClientRect.top
  59. }, self._svg, self._lastViewbox);
  60. setViewboxCenteredAroundPoint(diagramPoint, self._canvas);
  61. self._update();
  62. }
  63. function mousedown(center) {
  64. return function onMousedown(event) {
  65. var point = getPoint(event);
  66. // getBoundingClientRect might return zero-dimensional when called for the first time
  67. if (!self._state._svgClientRect || isZeroDimensional(self._state._svgClientRect)) {
  68. self._state._svgClientRect = self._svg.getBoundingClientRect();
  69. }
  70. if (center) {
  71. centerViewbox(point);
  72. }
  73. var diagramPoint = mapMousePositionToDiagramPoint({
  74. x: point.x - self._state._svgClientRect.left,
  75. y: point.y - self._state._svgClientRect.top
  76. }, self._svg, self._lastViewbox);
  77. var viewbox = canvas.viewbox();
  78. var offsetViewport = getOffsetViewport(diagramPoint, viewbox);
  79. var initialViewportDomRect = self._viewportDom.getBoundingClientRect();
  80. // take border into account (regardless of width)
  81. var offsetViewportDom = {
  82. x: point.x - initialViewportDomRect.left + 1,
  83. y: point.y - initialViewportDomRect.top + 1
  84. };
  85. // init dragging
  86. minDash.assign(self._state, {
  87. cachedViewbox: viewbox,
  88. initialDragPosition: {
  89. x: point.x,
  90. y: point.y
  91. },
  92. isDragging: true,
  93. offsetViewport: offsetViewport,
  94. offsetViewportDom: offsetViewportDom,
  95. viewportClientRect: self._viewport.getBoundingClientRect(),
  96. parentClientRect: self._parent.getBoundingClientRect()
  97. });
  98. minDom.event.bind(document, 'mousemove', onMousemove);
  99. minDom.event.bind(document, 'mouseup', onMouseup);
  100. };
  101. }
  102. function onMousemove(event) {
  103. var point = getPoint(event);
  104. // set viewbox if dragging active
  105. if (self._state.isDragging) {
  106. // getBoundingClientRect might return zero-dimensional when called for the first time
  107. if (!self._state._svgClientRect || isZeroDimensional(self._state._svgClientRect)) {
  108. self._state._svgClientRect = self._svg.getBoundingClientRect();
  109. }
  110. // update viewport DOM
  111. var offsetViewportDom = self._state.offsetViewportDom,
  112. viewportClientRect = self._state.viewportClientRect,
  113. parentClientRect = self._state.parentClientRect;
  114. minDash.assign(self._viewportDom.style, {
  115. top: (point.y - offsetViewportDom.y - parentClientRect.top) + 'px',
  116. left: (point.x - offsetViewportDom.x - parentClientRect.left) + 'px'
  117. });
  118. // update overlay
  119. var clipPath = getOverlayClipPath(parentClientRect, {
  120. top: point.y - offsetViewportDom.y - parentClientRect.top,
  121. left: point.x - offsetViewportDom.x - parentClientRect.left,
  122. width: viewportClientRect.width,
  123. height: viewportClientRect.height
  124. });
  125. minDash.assign(self._overlay.style, {
  126. clipPath: clipPath
  127. });
  128. var diagramPoint = mapMousePositionToDiagramPoint({
  129. x: point.x - self._state._svgClientRect.left,
  130. y: point.y - self._state._svgClientRect.top
  131. }, self._svg, self._lastViewbox);
  132. setViewboxCenteredAroundPoint({
  133. x: diagramPoint.x - self._state.offsetViewport.x,
  134. y: diagramPoint.y - self._state.offsetViewport.y
  135. }, self._canvas);
  136. }
  137. }
  138. function onMouseup(event) {
  139. var point = getPoint(event);
  140. if (self._state.isDragging) {
  141. // treat event as click
  142. if (self._state.initialDragPosition.x === point.x
  143. && self._state.initialDragPosition.y === point.y) {
  144. centerViewbox(event);
  145. }
  146. self._update();
  147. // end dragging
  148. minDash.assign(self._state, {
  149. cachedViewbox: null,
  150. initialDragPosition: null,
  151. isDragging: false,
  152. offsetViewport: null,
  153. offsetViewportDom: null
  154. });
  155. minDom.event.unbind(document, 'mousemove', onMousemove);
  156. minDom.event.unbind(document, 'mouseup', onMouseup);
  157. }
  158. }
  159. // dragging viewport scrolls canvas
  160. minDom.event.bind(this._viewportDom, 'mousedown', mousedown(false));
  161. minDom.event.bind(this._svg, 'mousedown', mousedown(true));
  162. minDom.event.bind(this._parent, 'wheel', function(event) {
  163. // stop propagation and handle scroll differently
  164. event.preventDefault();
  165. event.stopPropagation();
  166. // only zoom in on ctrl; this aligns with diagram-js navigation behavior
  167. if (!event.ctrlKey) {
  168. return;
  169. }
  170. // getBoundingClientRect might return zero-dimensional when called for the first time
  171. if (!self._state._svgClientRect || isZeroDimensional(self._state._svgClientRect)) {
  172. self._state._svgClientRect = self._svg.getBoundingClientRect();
  173. }
  174. // disallow zooming through viewport outside of minimap as it is very confusing
  175. if (!isPointInside(event, self._state._svgClientRect)) {
  176. return;
  177. }
  178. var factor = event.deltaMode === 0 ? 0.020 : 0.32;
  179. var delta = (
  180. Math.sqrt(
  181. Math.pow(event.deltaY, 2) +
  182. Math.pow(event.deltaX, 2)
  183. ) * sign(event.deltaY) * -factor
  184. );
  185. // add until threshold reached
  186. self._state.zoomDelta += delta;
  187. if (Math.abs(self._state.zoomDelta) > DELTA_THRESHOLD) {
  188. var direction = delta > 0 ? 1 : -1;
  189. var currentLinearZoomLevel = Math.log(canvas.zoom()) / Math.log(10);
  190. // zoom with half the step size of stepZoom
  191. var stepSize = getStepSize(RANGE, NUM_STEPS * 2);
  192. // snap to a proximate zoom step
  193. var newLinearZoomLevel = Math.round(currentLinearZoomLevel / stepSize) * stepSize;
  194. // increase or decrease one zoom step in the given direction
  195. newLinearZoomLevel += stepSize * direction;
  196. // calculate the absolute logarithmic zoom level based on the linear zoom level
  197. // (e.g. 2 for an absolute x2 zoom)
  198. var newLogZoomLevel = Math.pow(10, newLinearZoomLevel);
  199. canvas.zoom(cap(RANGE, newLogZoomLevel), diagramPoint);
  200. // reset
  201. self._state.zoomDelta = 0;
  202. var diagramPoint = mapMousePositionToDiagramPoint({
  203. x: event.clientX - self._state._svgClientRect.left,
  204. y: event.clientY - self._state._svgClientRect.top
  205. }, self._svg, self._lastViewbox);
  206. setViewboxCenteredAroundPoint(diagramPoint, self._canvas);
  207. self._update();
  208. }
  209. });
  210. minDom.event.bind(this._toggle, 'click', function(event) {
  211. event.preventDefault();
  212. event.stopPropagation();
  213. self.toggle();
  214. });
  215. // add shape on shape/connection added
  216. eventBus.on([ 'shape.added', 'connection.added' ], function(context) {
  217. var element = context.element;
  218. self._addElement(element);
  219. self._update();
  220. });
  221. // remove shape on shape/connection removed
  222. eventBus.on([ 'shape.removed', 'connection.removed' ], function(context) {
  223. var element = context.element;
  224. self._removeElement(element);
  225. self._update();
  226. });
  227. // update on elements changed
  228. eventBus.on('elements.changed', LOW_PRIORITY, function(context) {
  229. var elements = context.elements;
  230. elements.forEach(function(element) {
  231. self._updateElement(element);
  232. });
  233. self._update();
  234. });
  235. // update on element ID update
  236. eventBus.on('element.updateId', function(context) {
  237. var element = context.element,
  238. newId = context.newId;
  239. self._updateElementId(element, newId);
  240. });
  241. // update on viewbox changed
  242. eventBus.on('canvas.viewbox.changed', function() {
  243. if (!self._state.isDragging) {
  244. self._update();
  245. }
  246. });
  247. eventBus.on('canvas.resized', function() {
  248. // only update if present in DOM
  249. if (document.body.contains(self._parent)) {
  250. if (!self._state.isDragging) {
  251. self._update();
  252. }
  253. self._state._svgClientRect = self._svg.getBoundingClientRect();
  254. }
  255. });
  256. eventBus.on([ 'root.set', 'plane.set' ], function(event) {
  257. self._clear();
  258. var element = event.element || event.plane.rootElement;
  259. element.children.forEach(function(el) {
  260. self._addElement(el);
  261. });
  262. self._update();
  263. });
  264. }
  265. Minimap.$inject = [
  266. 'config.minimap',
  267. 'injector',
  268. 'eventBus',
  269. 'canvas',
  270. 'elementRegistry'
  271. ];
  272. Minimap.prototype._init = function() {
  273. var canvas = this._canvas,
  274. container = canvas.getContainer();
  275. // create parent div
  276. var parent = this._parent = document.createElement('div');
  277. minDom.classes(parent).add('djs-minimap');
  278. container.appendChild(parent);
  279. // create toggle
  280. var toggle = this._toggle = document.createElement('div');
  281. minDom.classes(toggle).add('toggle');
  282. parent.appendChild(toggle);
  283. // create map
  284. var map = this._map = document.createElement('div');
  285. minDom.classes(map).add('map');
  286. parent.appendChild(map);
  287. // create svg
  288. var svg = this._svg = tinySvg.create('svg');
  289. tinySvg.attr(svg, { width: '100%', height: '100%' });
  290. tinySvg.append(map, svg);
  291. // add groups
  292. var elementsGroup = this._elementsGroup = tinySvg.create('g');
  293. tinySvg.append(svg, elementsGroup);
  294. var viewportGroup = this._viewportGroup = tinySvg.create('g');
  295. tinySvg.append(svg, viewportGroup);
  296. // add viewport SVG
  297. var viewport = this._viewport = tinySvg.create('rect');
  298. tinySvg.classes(viewport).add('viewport');
  299. tinySvg.append(viewportGroup, viewport);
  300. // prevent drag propagation
  301. minDom.event.bind(parent, 'mousedown', function(event) {
  302. event.stopPropagation();
  303. });
  304. // add viewport DOM
  305. var viewportDom = this._viewportDom = document.createElement('div');
  306. minDom.classes(viewportDom).add('viewport-dom');
  307. this._parent.appendChild(viewportDom);
  308. // add overlay
  309. var overlay = this._overlay = document.createElement('div');
  310. minDom.classes(overlay).add('overlay');
  311. this._parent.appendChild(overlay);
  312. };
  313. Minimap.prototype._update = function() {
  314. var viewbox = this._canvas.viewbox(),
  315. innerViewbox = viewbox.inner,
  316. outerViewbox = viewbox.outer;
  317. if (!validViewbox(viewbox)) {
  318. return;
  319. }
  320. var x, y, width, height;
  321. var widthDifference = outerViewbox.width - innerViewbox.width,
  322. heightDifference = outerViewbox.height - innerViewbox.height;
  323. // update viewbox
  324. // x
  325. if (innerViewbox.width < outerViewbox.width) {
  326. x = innerViewbox.x - widthDifference / 2;
  327. width = outerViewbox.width;
  328. if (innerViewbox.x + innerViewbox.width < outerViewbox.width) {
  329. x = Math.min(0, innerViewbox.x);
  330. }
  331. } else {
  332. x = innerViewbox.x;
  333. width = innerViewbox.width;
  334. }
  335. // y
  336. if (innerViewbox.height < outerViewbox.height) {
  337. y = innerViewbox.y - heightDifference / 2;
  338. height = outerViewbox.height;
  339. if (innerViewbox.y + innerViewbox.height < outerViewbox.height) {
  340. y = Math.min(0, innerViewbox.y);
  341. }
  342. } else {
  343. y = innerViewbox.y;
  344. height = innerViewbox.height;
  345. }
  346. // apply some padding
  347. x = x - MINIMAP_VIEWBOX_PADDING;
  348. y = y - MINIMAP_VIEWBOX_PADDING;
  349. width = width + MINIMAP_VIEWBOX_PADDING * 2;
  350. height = height + MINIMAP_VIEWBOX_PADDING * 2;
  351. this._lastViewbox = {
  352. x: x,
  353. y: y,
  354. width: width,
  355. height: height
  356. };
  357. tinySvg.attr(this._svg, {
  358. viewBox: x + ', ' + y + ', ' + width + ', ' + height
  359. });
  360. // update viewport SVG
  361. tinySvg.attr(this._viewport, {
  362. x: viewbox.x,
  363. y: viewbox.y,
  364. width: viewbox.width,
  365. height: viewbox.height
  366. });
  367. // update viewport DOM
  368. var parentClientRect = this._state._parentClientRect = this._parent.getBoundingClientRect();
  369. var viewportClientRect = this._viewport.getBoundingClientRect();
  370. var withoutParentOffset = {
  371. top: viewportClientRect.top - parentClientRect.top,
  372. left: viewportClientRect.left - parentClientRect.left,
  373. width: viewportClientRect.width,
  374. height: viewportClientRect.height
  375. };
  376. minDash.assign(this._viewportDom.style, {
  377. top: withoutParentOffset.top + 'px',
  378. left: withoutParentOffset.left + 'px',
  379. width: withoutParentOffset.width + 'px',
  380. height: withoutParentOffset.height + 'px'
  381. });
  382. // update overlay
  383. var clipPath = getOverlayClipPath(parentClientRect, withoutParentOffset);
  384. minDash.assign(this._overlay.style, {
  385. clipPath: clipPath
  386. });
  387. };
  388. Minimap.prototype.open = function() {
  389. minDash.assign(this._state, { isOpen: true });
  390. minDom.classes(this._parent).add('open');
  391. var translate = this._injector.get('translate', false) || function(s) { return s; };
  392. minDom.attr(this._toggle, 'title', translate('Close minimap'));
  393. this._update();
  394. this._eventBus.fire('minimap.toggle', { open: true });
  395. };
  396. Minimap.prototype.close = function() {
  397. minDash.assign(this._state, { isOpen: false });
  398. minDom.classes(this._parent).remove('open');
  399. var translate = this._injector.get('translate', false) || function(s) { return s; };
  400. minDom.attr(this._toggle, 'title', translate('Open minimap'));
  401. this._eventBus.fire('minimap.toggle', { open: false });
  402. };
  403. Minimap.prototype.toggle = function(open) {
  404. var currentOpen = this.isOpen();
  405. if (typeof open === 'undefined') {
  406. open = !currentOpen;
  407. }
  408. if (open == currentOpen) {
  409. return;
  410. }
  411. if (open) {
  412. this.open();
  413. } else {
  414. this.close();
  415. }
  416. };
  417. Minimap.prototype.isOpen = function() {
  418. return this._state.isOpen;
  419. };
  420. Minimap.prototype._updateElement = function(element) {
  421. try {
  422. // if parent is null element has been removed, if parent is undefined parent is root
  423. if (element.parent !== undefined && element.parent !== null) {
  424. this._removeElement(element);
  425. this._addElement(element);
  426. }
  427. } catch (error) {
  428. console.warn('Minimap#_updateElement errored', error);
  429. }
  430. };
  431. Minimap.prototype._updateElementId = function(element, newId) {
  432. try {
  433. var elementGfx = minDom.query('#' + EscapeUtil.escapeCSS(element.id), this._elementsGroup);
  434. if (elementGfx) {
  435. elementGfx.id = newId;
  436. }
  437. } catch (error) {
  438. console.warn('Minimap#_updateElementId errored', error);
  439. }
  440. };
  441. /**
  442. * Checks if an element is on the currently active plane.
  443. */
  444. Minimap.prototype.isOnActivePlane = function(element) {
  445. var canvas = this._canvas;
  446. // diagram-js@8
  447. if (canvas.findRoot) {
  448. return canvas.findRoot(element) === canvas.getRootElement();
  449. }
  450. // diagram-js>=7.4.0
  451. if (canvas.findPlane) {
  452. return canvas.findPlane(element) === canvas.getActivePlane();
  453. }
  454. // diagram-js<7.4.0
  455. return true;
  456. };
  457. /**
  458. * Adds an element to the minimap.
  459. */
  460. Minimap.prototype._addElement = function(element) {
  461. var self = this;
  462. this._removeElement(element);
  463. if (!this.isOnActivePlane(element)) {
  464. return;
  465. }
  466. var parent,
  467. x, y;
  468. var newElementGfx = this._createElement(element);
  469. var newElementParentGfx = minDom.query('#' + EscapeUtil.escapeCSS(element.parent.id), this._elementsGroup);
  470. if (newElementGfx) {
  471. var elementGfx = this._elementRegistry.getGraphics(element);
  472. var parentGfx = this._elementRegistry.getGraphics(element.parent);
  473. var index = getIndexOfChildInParentChildren(elementGfx, parentGfx);
  474. // index can be 0
  475. if (index !== 'undefined') {
  476. if (newElementParentGfx) {
  477. // in cases of doubt add as last child
  478. if (newElementParentGfx.childNodes.length > index) {
  479. insertChildAtIndex(newElementGfx, newElementParentGfx, index);
  480. } else {
  481. insertChildAtIndex(newElementGfx, newElementParentGfx, newElementParentGfx.childNodes.length - 1);
  482. }
  483. } else {
  484. this._elementsGroup.appendChild(newElementGfx);
  485. }
  486. } else {
  487. // index undefined
  488. this._elementsGroup.appendChild(newElementGfx);
  489. }
  490. if (isConnection(element)) {
  491. parent = element.parent;
  492. x = 0;
  493. y = 0;
  494. if (typeof parent.x !== 'undefined' && typeof parent.y !== 'undefined') {
  495. x = -parent.x;
  496. y = -parent.y;
  497. }
  498. tinySvg.attr(newElementGfx, { transform: 'translate(' + x + ' ' + y + ')' });
  499. } else {
  500. x = element.x;
  501. y = element.y;
  502. if (newElementParentGfx) {
  503. parent = element.parent;
  504. x -= parent.x;
  505. y -= parent.y;
  506. }
  507. tinySvg.attr(newElementGfx, { transform: 'translate(' + x + ' ' + y + ')' });
  508. }
  509. if (element.children && element.children.length) {
  510. element.children.forEach(function(child) {
  511. self._addElement(child);
  512. });
  513. }
  514. return newElementGfx;
  515. }
  516. };
  517. Minimap.prototype._removeElement = function(element) {
  518. var elementGfx = this._svg.getElementById(element.id);
  519. if (elementGfx) {
  520. tinySvg.remove(elementGfx);
  521. }
  522. };
  523. Minimap.prototype._createElement = function(element) {
  524. var gfx = this._elementRegistry.getGraphics(element),
  525. visual;
  526. if (gfx) {
  527. visual = GraphicsUtil.getVisual(gfx);
  528. if (visual) {
  529. var elementGfx = tinySvg.clone(visual);
  530. tinySvg.attr(elementGfx, { id: element.id });
  531. return elementGfx;
  532. }
  533. }
  534. };
  535. Minimap.prototype._clear = function() {
  536. tinySvg.clear(this._elementsGroup);
  537. };
  538. function isConnection(element) {
  539. return element.waypoints;
  540. }
  541. function getOffsetViewport(diagramPoint, viewbox) {
  542. var viewboxCenter = {
  543. x: viewbox.x + (viewbox.width / 2),
  544. y: viewbox.y + (viewbox.height / 2)
  545. };
  546. return {
  547. x: diagramPoint.x - viewboxCenter.x,
  548. y: diagramPoint.y - viewboxCenter.y
  549. };
  550. }
  551. function mapMousePositionToDiagramPoint(position, svg, lastViewbox) {
  552. // firefox returns 0 for clientWidth and clientHeight
  553. var boundingClientRect = svg.getBoundingClientRect();
  554. // take different aspect ratios of default layers bounding box and minimap into account
  555. var bBox =
  556. fitAspectRatio(lastViewbox, boundingClientRect.width / boundingClientRect.height);
  557. // map click position to diagram position
  558. var diagramX = map(position.x, 0, boundingClientRect.width, bBox.x, bBox.x + bBox.width),
  559. diagramY = map(position.y, 0, boundingClientRect.height, bBox.y, bBox.y + bBox.height);
  560. return {
  561. x: diagramX,
  562. y: diagramY
  563. };
  564. }
  565. function setViewboxCenteredAroundPoint(point, canvas) {
  566. // get cached viewbox to preserve zoom
  567. var cachedViewbox = canvas.viewbox(),
  568. cachedViewboxWidth = cachedViewbox.width,
  569. cachedViewboxHeight = cachedViewbox.height;
  570. canvas.viewbox({
  571. x: point.x - cachedViewboxWidth / 2,
  572. y: point.y - cachedViewboxHeight / 2,
  573. width: cachedViewboxWidth,
  574. height: cachedViewboxHeight
  575. });
  576. }
  577. function fitAspectRatio(bounds, targetAspectRatio) {
  578. var aspectRatio = bounds.width / bounds.height;
  579. // assigning to bounds throws exception in IE11
  580. var newBounds = minDash.assign({}, {
  581. x: bounds.x,
  582. y: bounds.y,
  583. width: bounds.width,
  584. height: bounds.height
  585. });
  586. if (aspectRatio > targetAspectRatio) {
  587. // height needs to be fitted
  588. var height = newBounds.width * (1 / targetAspectRatio),
  589. y = newBounds.y - ((height - newBounds.height) / 2);
  590. minDash.assign(newBounds, {
  591. y: y,
  592. height: height
  593. });
  594. } else if (aspectRatio < targetAspectRatio) {
  595. // width needs to be fitted
  596. var width = newBounds.height * targetAspectRatio,
  597. x = newBounds.x - ((width - newBounds.width) / 2);
  598. minDash.assign(newBounds, {
  599. x: x,
  600. width: width
  601. });
  602. }
  603. return newBounds;
  604. }
  605. function map(x, inMin, inMax, outMin, outMax) {
  606. var inRange = inMax - inMin,
  607. outRange = outMax - outMin;
  608. return (x - inMin) * outRange / inRange + outMin;
  609. }
  610. /**
  611. * Returns index of child in children of parent.
  612. *
  613. * g
  614. * '- g.djs-element // parentGfx
  615. * '- g.djs-children
  616. * '- g
  617. * '-g.djs-element // childGfx
  618. */
  619. function getIndexOfChildInParentChildren(childGfx, parentGfx) {
  620. var childrenGroup = minDom.query('.djs-children', parentGfx.parentNode);
  621. if (!childrenGroup) {
  622. return;
  623. }
  624. var childrenArray = [].slice.call(childrenGroup.childNodes);
  625. var indexOfChild = -1;
  626. childrenArray.forEach(function(childGroup, index) {
  627. if (minDom.query('.djs-element', childGroup) === childGfx) {
  628. indexOfChild = index;
  629. }
  630. });
  631. return indexOfChild;
  632. }
  633. function insertChildAtIndex(childGfx, parentGfx, index) {
  634. var childContainer = getChildContainer(parentGfx);
  635. var childrenArray = [].slice.call(childContainer.childNodes);
  636. var childAtIndex = childrenArray[index];
  637. if (childAtIndex) {
  638. parentGfx.insertBefore(childGfx, childAtIndex.nextSibling);
  639. } else {
  640. parentGfx.appendChild(childGfx);
  641. }
  642. }
  643. function getChildContainer(parentGfx) {
  644. var container = minDom.query('.children', parentGfx);
  645. if (!container) {
  646. container = tinySvg.create('g', { class: 'children' });
  647. tinySvg.append(parentGfx, container);
  648. }
  649. return container;
  650. }
  651. function isZeroDimensional(clientRect) {
  652. return clientRect.width === 0 && clientRect.height === 0;
  653. }
  654. function isPointInside(point, rect) {
  655. return point.x > rect.left
  656. && point.x < rect.left + rect.width
  657. && point.y > rect.top
  658. && point.y < rect.top + rect.height;
  659. }
  660. var sign = Math.sign || function(n) {
  661. return n >= 0 ? 1 : -1;
  662. };
  663. /**
  664. * Get step size for given range and number of steps.
  665. *
  666. * @param {Object} range - Range.
  667. * @param {number} range.min - Range minimum.
  668. * @param {number} range.max - Range maximum.
  669. */
  670. function getStepSize(range, steps) {
  671. var minLinearRange = Math.log(range.min) / Math.log(10),
  672. maxLinearRange = Math.log(range.max) / Math.log(10);
  673. var absoluteLinearRange = Math.abs(minLinearRange) + Math.abs(maxLinearRange);
  674. return absoluteLinearRange / steps;
  675. }
  676. function cap(range, scale) {
  677. return Math.max(range.min, Math.min(range.max, scale));
  678. }
  679. function getOverlayClipPath(outer, inner) {
  680. var coordinates = [
  681. toCoordinatesString(inner.left, inner.top),
  682. toCoordinatesString(inner.left + inner.width, inner.top),
  683. toCoordinatesString(inner.left + inner.width, inner.top + inner.height),
  684. toCoordinatesString(inner.left, inner.top + inner.height),
  685. toCoordinatesString(inner.left, outer.height),
  686. toCoordinatesString(outer.width, outer.height),
  687. toCoordinatesString(outer.width, 0),
  688. toCoordinatesString(0, 0),
  689. toCoordinatesString(0, outer.height),
  690. toCoordinatesString(inner.left, outer.height)
  691. ].join(', ');
  692. return 'polygon(' + coordinates + ')';
  693. }
  694. function toCoordinatesString(x, y) {
  695. return x + 'px ' + y + 'px';
  696. }
  697. function validViewbox(viewBox) {
  698. return minDash.every(viewBox, function(value) {
  699. // check deeper structures like inner or outer viewbox
  700. if (minDash.isObject(value)) {
  701. return validViewbox(value);
  702. }
  703. return minDash.isNumber(value) && isFinite(value);
  704. });
  705. }
  706. function getPoint(event) {
  707. if (event.center) {
  708. return event.center;
  709. }
  710. return {
  711. x: event.clientX,
  712. y: event.clientY
  713. };
  714. }
  715. var index = {
  716. __init__: [ 'minimap' ],
  717. minimap: [ 'type', Minimap ]
  718. };
  719. module.exports = index;