No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 

420 líneas
13 KiB

  1. /*!
  2. * svg.select.js - An extension of svg.js which allows to select elements with mouse
  3. * @version 3.0.1
  4. * https://github.com/svgdotjs/svg.select.js
  5. *
  6. * @copyright Ulrich-Matthias Schäfer
  7. * @license MIT
  8. */;
  9. ;(function() {
  10. "use strict";
  11. function SelectHandler(el) {
  12. this.el = el;
  13. el.remember('_selectHandler', this);
  14. this.pointSelection = {isSelected: false};
  15. this.rectSelection = {isSelected: false};
  16. // helper list with position settings of each type of point
  17. this.pointsList = {
  18. lt: [ 0, 0 ],
  19. rt: [ 'width', 0 ],
  20. rb: [ 'width', 'height' ],
  21. lb: [ 0, 'height' ],
  22. t: [ 'width', 0 ],
  23. r: [ 'width', 'height' ],
  24. b: [ 'width', 'height' ],
  25. l: [ 0, 'height' ]
  26. };
  27. // helper function to get point coordinates based on settings above and an object (bbox in our case)
  28. this.pointCoord = function (setting, object, isPointCentered) {
  29. var coord = typeof setting !== 'string' ? setting : object[setting];
  30. // Top, bottom, right and left points are placed in the center of element width/height
  31. return isPointCentered ? coord / 2 : coord
  32. }
  33. this.pointCoords = function (point, object) {
  34. var settings = this.pointsList[point];
  35. return {
  36. x: this.pointCoord(settings[0], object, (point === 't' || point === 'b')),
  37. y: this.pointCoord(settings[1], object, (point === 'r' || point === 'l'))
  38. }
  39. }
  40. }
  41. SelectHandler.prototype.init = function (value, options) {
  42. var bbox = this.el.bbox();
  43. this.options = {};
  44. // store defaults list of points in order to verify users config
  45. var points = this.el.selectize.defaults.points;
  46. // Merging the defaults and the options-object together
  47. for (var i in this.el.selectize.defaults) {
  48. this.options[i] = this.el.selectize.defaults[i];
  49. if (options[i] !== undefined) {
  50. this.options[i] = options[i];
  51. }
  52. }
  53. // prepare & validate list of points to be added (or excluded)
  54. var pointsLists = ['points', 'pointsExclude'];
  55. for (var i in pointsLists) {
  56. var option = this.options[pointsLists[i]];
  57. if (typeof option === 'string') {
  58. if (option.length > 0) {
  59. // if set as comma separated string list => convert it into an array
  60. option = option.split(/\s*,\s*/i);
  61. } else {
  62. option = [];
  63. }
  64. } else if (typeof option === 'boolean' && pointsLists[i] === 'points') {
  65. // this is not needed, but let's have it for legacy support
  66. option = option ? points : [];
  67. }
  68. this.options[pointsLists[i]] = option;
  69. }
  70. // intersect correct all points options with users config (exclude unwanted points)
  71. // ES5 -> NO arrow functions nor Array.includes()
  72. this.options.points = [ points, this.options.points ].reduce(
  73. function (a, b) {
  74. return a.filter(
  75. function (c) {
  76. return b.indexOf(c) > -1;
  77. }
  78. )
  79. }
  80. );
  81. // exclude pointsExclude, if wanted
  82. this.options.points = [ this.options.points, this.options.pointsExclude ].reduce(
  83. function (a, b) {
  84. return a.filter(
  85. function (c) {
  86. return b.indexOf(c) < 0;
  87. }
  88. )
  89. }
  90. );
  91. this.parent = this.el.parent();
  92. this.nested = (this.nested || this.parent.group());
  93. this.nested.matrix(new SVG.Matrix(this.el).translate(bbox.x, bbox.y));
  94. // When deepSelect is enabled and the element is a line/polyline/polygon, draw only points for moving
  95. if (this.options.deepSelect && ['line', 'polyline', 'polygon'].indexOf(this.el.type) !== -1) {
  96. this.selectPoints(value);
  97. } else {
  98. this.selectRect(value);
  99. }
  100. this.observe();
  101. this.cleanup();
  102. };
  103. SelectHandler.prototype.selectPoints = function (value) {
  104. this.pointSelection.isSelected = value;
  105. // When set is already there we dont have to create one
  106. if (this.pointSelection.set) {
  107. return this;
  108. }
  109. // Create our set of elements
  110. this.pointSelection.set = this.parent.set();
  111. // draw the points and mark the element as selected
  112. this.drawPoints();
  113. return this;
  114. };
  115. // create the point-array which contains the 2 points of a line or simply the points-array of polyline/polygon
  116. SelectHandler.prototype.getPointArray = function () {
  117. var bbox = this.el.bbox();
  118. return this.el.array().valueOf().map(function (el) {
  119. return [el[0] - bbox.x, el[1] - bbox.y];
  120. });
  121. };
  122. // Draws a points
  123. SelectHandler.prototype.drawPoints = function () {
  124. var _this = this, array = this.getPointArray();
  125. // go through the array of points
  126. for (var i = 0, len = array.length; i < len; ++i) {
  127. var curriedEvent = (function (k) {
  128. return function (ev) {
  129. ev = ev || window.event;
  130. ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
  131. ev.stopPropagation();
  132. var x = ev.pageX || ev.touches[0].pageX;
  133. var y = ev.pageY || ev.touches[0].pageY;
  134. _this.el.fire('point', {x: x, y: y, i: k, event: ev});
  135. };
  136. })(i);
  137. // add every point to the set
  138. // add css-classes and a touchstart-event which fires our event for moving points
  139. var point = this.drawPoint(array[i][0], array[i][1])
  140. .addClass(this.options.classPoints)
  141. .addClass(this.options.classPoints + '_point')
  142. .on('touchstart', curriedEvent)
  143. .on('mousedown', curriedEvent)
  144. this.pointSelection.set.add(point);
  145. }
  146. };
  147. // The function to draw single point
  148. SelectHandler.prototype.drawPoint = function (cx, cy) {
  149. var pointType = this.options.pointType;
  150. switch (pointType) {
  151. case 'circle':
  152. return this.drawCircle(cx, cy);
  153. case 'rect':
  154. return this.drawRect(cx, cy);
  155. default:
  156. if (typeof pointType === 'function') {
  157. return pointType.call(this, cx, cy);
  158. }
  159. throw new Error('Unknown ' + pointType + ' point type!');
  160. }
  161. };
  162. // The function to draw the circle point
  163. SelectHandler.prototype.drawCircle = function (cx, cy) {
  164. return this.nested.circle(this.options.pointSize)
  165. .center(cx, cy);
  166. };
  167. // The function to draw the rect point
  168. SelectHandler.prototype.drawRect = function (cx, cy) {
  169. return this.nested.rect(this.options.pointSize, this.options.pointSize)
  170. .center(cx, cy);
  171. };
  172. // every time a point is moved, we have to update the positions of our point
  173. SelectHandler.prototype.updatePointSelection = function () {
  174. var array = this.getPointArray();
  175. this.pointSelection.set.each(function (i) {
  176. if (this.cx() === array[i][0] && this.cy() === array[i][1]) {
  177. return;
  178. }
  179. this.center(array[i][0], array[i][1]);
  180. });
  181. };
  182. SelectHandler.prototype.updateRectSelection = function () {
  183. var _this = this, bbox = this.el.bbox();
  184. this.rectSelection.set.get(0).attr({
  185. width: bbox.width,
  186. height: bbox.height
  187. });
  188. // set.get(1) is always in the upper left corner. no need to move it
  189. if (this.options.points.length) {
  190. this.options.points.map(function (point, index) {
  191. var coords = _this.pointCoords(point, bbox);
  192. _this.rectSelection.set.get(index + 1).center(coords.x, coords.y);
  193. });
  194. }
  195. if (this.options.rotationPoint) {
  196. var length = this.rectSelection.set.length();
  197. this.rectSelection.set.get(length - 1).center(bbox.width / 2, 20);
  198. }
  199. };
  200. SelectHandler.prototype.selectRect = function (value) {
  201. var _this = this, bbox = this.el.bbox();
  202. this.rectSelection.isSelected = value;
  203. // when set is already p
  204. this.rectSelection.set = this.rectSelection.set || this.parent.set();
  205. // helperFunction to create a mouse-down function which triggers the event specified in `eventName`
  206. function getMoseDownFunc(eventName) {
  207. return function (ev) {
  208. ev = ev || window.event;
  209. ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
  210. ev.stopPropagation();
  211. var x = ev.pageX || ev.touches[0].pageX;
  212. var y = ev.pageY || ev.touches[0].pageY;
  213. _this.el.fire(eventName, {x: x, y: y, event: ev});
  214. };
  215. }
  216. // create the selection-rectangle and add the css-class
  217. if (!this.rectSelection.set.get(0)) {
  218. this.rectSelection.set.add(this.nested.rect(bbox.width, bbox.height).addClass(this.options.classRect));
  219. }
  220. // Draw Points at the edges, if enabled
  221. if (this.options.points.length && this.rectSelection.set.length() < 2) {
  222. var ename ="touchstart", mname = "mousedown";
  223. this.options.points.map(function (point, index) {
  224. var coords = _this.pointCoords(point, bbox);
  225. var pointElement = _this.drawPoint(coords.x, coords.y)
  226. .attr('class', _this.options.classPoints + '_' + point)
  227. .on(mname, getMoseDownFunc(point))
  228. .on(ename, getMoseDownFunc(point));
  229. _this.rectSelection.set.add(pointElement);
  230. });
  231. this.rectSelection.set.each(function () {
  232. this.addClass(_this.options.classPoints);
  233. });
  234. }
  235. // draw rotationPint, if enabled
  236. if (this.options.rotationPoint && ((this.options.points && !this.rectSelection.set.get(9)) || (!this.options.points && !this.rectSelection.set.get(1)))) {
  237. var curriedEvent = function (ev) {
  238. ev = ev || window.event;
  239. ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
  240. ev.stopPropagation();
  241. var x = ev.pageX || ev.touches[0].pageX;
  242. var y = ev.pageY || ev.touches[0].pageY;
  243. _this.el.fire('rot', {x: x, y: y, event: ev});
  244. };
  245. var pointElement = this.drawPoint(bbox.width / 2, 20)
  246. .attr('class', this.options.classPoints + '_rot')
  247. .on("touchstart", curriedEvent)
  248. .on("mousedown", curriedEvent);
  249. this.rectSelection.set.add(pointElement);
  250. }
  251. };
  252. SelectHandler.prototype.handler = function () {
  253. var bbox = this.el.bbox();
  254. this.nested.matrix(new SVG.Matrix(this.el).translate(bbox.x, bbox.y));
  255. if (this.rectSelection.isSelected) {
  256. this.updateRectSelection();
  257. }
  258. if (this.pointSelection.isSelected) {
  259. this.updatePointSelection();
  260. }
  261. };
  262. SelectHandler.prototype.observe = function () {
  263. var _this = this;
  264. if (MutationObserver) {
  265. if (this.rectSelection.isSelected || this.pointSelection.isSelected) {
  266. this.observerInst = this.observerInst || new MutationObserver(function () {
  267. _this.handler();
  268. });
  269. this.observerInst.observe(this.el.node, {attributes: true});
  270. } else {
  271. try {
  272. this.observerInst.disconnect();
  273. delete this.observerInst;
  274. } catch (e) {
  275. }
  276. }
  277. } else {
  278. this.el.off('DOMAttrModified.select');
  279. if (this.rectSelection.isSelected || this.pointSelection.isSelected) {
  280. this.el.on('DOMAttrModified.select', function () {
  281. _this.handler();
  282. });
  283. }
  284. }
  285. };
  286. SelectHandler.prototype.cleanup = function () {
  287. //var _this = this;
  288. if (!this.rectSelection.isSelected && this.rectSelection.set) {
  289. // stop watching the element, remove the selection
  290. this.rectSelection.set.each(function () {
  291. this.remove();
  292. });
  293. this.rectSelection.set.clear();
  294. delete this.rectSelection.set;
  295. }
  296. if (!this.pointSelection.isSelected && this.pointSelection.set) {
  297. // Remove all points, clear the set, stop watching the element
  298. this.pointSelection.set.each(function () {
  299. this.remove();
  300. });
  301. this.pointSelection.set.clear();
  302. delete this.pointSelection.set;
  303. }
  304. if (!this.pointSelection.isSelected && !this.rectSelection.isSelected) {
  305. this.nested.remove();
  306. delete this.nested;
  307. }
  308. };
  309. SVG.extend(SVG.Element, {
  310. // Select element with mouse
  311. selectize: function (value, options) {
  312. // Check the parameters and reassign if needed
  313. if (typeof value === 'object') {
  314. options = value;
  315. value = true;
  316. }
  317. var selectHandler = this.remember('_selectHandler') || new SelectHandler(this);
  318. selectHandler.init(value === undefined ? true : value, options || {});
  319. return this;
  320. }
  321. });
  322. SVG.Element.prototype.selectize.defaults = {
  323. points: ['lt', 'rt', 'rb', 'lb', 't', 'r', 'b', 'l'], // which points to draw, default all
  324. pointsExclude: [], // easier option if to exclude few than rewrite all
  325. classRect: 'svg_select_boundingRect', // Css-class added to the rect
  326. classPoints: 'svg_select_points', // Css-class added to the points
  327. pointSize: 7, // size of point
  328. rotationPoint: true, // If true, rotation point is drawn. Needed for rotation!
  329. deepSelect: false, // If true, moving of single points is possible (only line, polyline, polyon)
  330. pointType: 'circle' // Point type: circle or rect, default circle
  331. };
  332. }());