index.esm.js 24 KB

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