segments.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. import { Dom, ObjectExt, FunctionExt } from '@antv/x6-common';
  2. import { Point, Line } from '@antv/x6-geometry';
  3. import { View } from '../../view/view';
  4. import { ToolsView } from '../../view/tool';
  5. import * as Util from './util';
  6. export class Segments extends ToolsView.ToolItem {
  7. constructor() {
  8. super(...arguments);
  9. this.handles = [];
  10. }
  11. get vertices() {
  12. return this.cellView.cell.getVertices();
  13. }
  14. update() {
  15. this.render();
  16. return this;
  17. }
  18. onRender() {
  19. Dom.addClass(this.container, this.prefixClassName('edge-tool-segments'));
  20. this.resetHandles();
  21. const edgeView = this.cellView;
  22. const vertices = [...this.vertices];
  23. vertices.unshift(edgeView.sourcePoint);
  24. vertices.push(edgeView.targetPoint);
  25. for (let i = 0, l = vertices.length; i < l - 1; i += 1) {
  26. const vertex = vertices[i];
  27. const nextVertex = vertices[i + 1];
  28. const handle = this.renderHandle(vertex, nextVertex, i);
  29. this.stamp(handle.container);
  30. this.handles.push(handle);
  31. }
  32. return this;
  33. }
  34. renderHandle(vertex, nextVertex, index) {
  35. const handle = this.options.createHandle({
  36. index,
  37. graph: this.graph,
  38. guard: (evt) => this.guard(evt),
  39. attrs: this.options.attrs || {},
  40. });
  41. if (this.options.processHandle) {
  42. this.options.processHandle(handle);
  43. }
  44. this.updateHandle(handle, vertex, nextVertex);
  45. this.container.appendChild(handle.container);
  46. this.startHandleListening(handle);
  47. return handle;
  48. }
  49. startHandleListening(handle) {
  50. handle.on('change', this.onHandleChange, this);
  51. handle.on('changing', this.onHandleChanging, this);
  52. handle.on('changed', this.onHandleChanged, this);
  53. }
  54. stopHandleListening(handle) {
  55. handle.off('change', this.onHandleChange, this);
  56. handle.off('changing', this.onHandleChanging, this);
  57. handle.off('changed', this.onHandleChanged, this);
  58. }
  59. resetHandles() {
  60. const handles = this.handles;
  61. this.handles = [];
  62. if (handles) {
  63. handles.forEach((handle) => {
  64. this.stopHandleListening(handle);
  65. handle.remove();
  66. });
  67. }
  68. }
  69. shiftHandleIndexes(delta) {
  70. const handles = this.handles;
  71. for (let i = 0, n = handles.length; i < n; i += 1) {
  72. handles[i].options.index += delta;
  73. }
  74. }
  75. resetAnchor(type, anchor) {
  76. const edge = this.cellView.cell;
  77. const options = {
  78. ui: true,
  79. toolId: this.cid,
  80. };
  81. if (anchor) {
  82. edge.prop([type, 'anchor'], anchor, options);
  83. }
  84. else {
  85. edge.removeProp([type, 'anchor'], options);
  86. }
  87. }
  88. snapHandle(handle, position, data) {
  89. const axis = handle.options.axis;
  90. const index = handle.options.index;
  91. const edgeView = this.cellView;
  92. const edge = edgeView.cell;
  93. const vertices = edge.getVertices();
  94. const prev = vertices[index - 2] || data.sourceAnchor;
  95. const next = vertices[index + 1] || data.targetAnchor;
  96. const snapRadius = this.options.snapRadius;
  97. if (Math.abs(position[axis] - prev[axis]) < snapRadius) {
  98. position[axis] = prev[axis];
  99. }
  100. else if (Math.abs(position[axis] - next[axis]) < snapRadius) {
  101. position[axis] = next[axis];
  102. }
  103. return position;
  104. }
  105. onHandleChanging({ handle, e, }) {
  106. const graph = this.graph;
  107. const options = this.options;
  108. const edgeView = this.cellView;
  109. const anchorFn = options.anchor;
  110. const axis = handle.options.axis;
  111. const index = handle.options.index - 1;
  112. const data = this.getEventData(e);
  113. const evt = this.normalizeEvent(e);
  114. const coords = graph.snapToGrid(evt.clientX, evt.clientY);
  115. const position = this.snapHandle(handle, coords.clone(), data);
  116. const vertices = ObjectExt.cloneDeep(this.vertices);
  117. let vertex = vertices[index];
  118. let nextVertex = vertices[index + 1];
  119. // First Segment
  120. const sourceView = edgeView.sourceView;
  121. const sourceBBox = edgeView.sourceBBox;
  122. let changeSourceAnchor = false;
  123. let deleteSourceAnchor = false;
  124. if (!vertex) {
  125. vertex = edgeView.sourceAnchor.toJSON();
  126. vertex[axis] = position[axis];
  127. if (sourceBBox.containsPoint(vertex)) {
  128. changeSourceAnchor = true;
  129. }
  130. else {
  131. vertices.unshift(vertex);
  132. this.shiftHandleIndexes(1);
  133. deleteSourceAnchor = true;
  134. }
  135. }
  136. else if (index === 0) {
  137. if (sourceBBox.containsPoint(vertex)) {
  138. vertices.shift();
  139. this.shiftHandleIndexes(-1);
  140. changeSourceAnchor = true;
  141. }
  142. else {
  143. vertex[axis] = position[axis];
  144. deleteSourceAnchor = true;
  145. }
  146. }
  147. else {
  148. vertex[axis] = position[axis];
  149. }
  150. if (typeof anchorFn === 'function' && sourceView) {
  151. if (changeSourceAnchor) {
  152. const sourceAnchorPosition = data.sourceAnchor.clone();
  153. sourceAnchorPosition[axis] = position[axis];
  154. const sourceAnchor = FunctionExt.call(anchorFn, edgeView, sourceAnchorPosition, sourceView, edgeView.sourceMagnet || sourceView.container, 'source', edgeView, this);
  155. this.resetAnchor('source', sourceAnchor);
  156. }
  157. if (deleteSourceAnchor) {
  158. this.resetAnchor('source', data.sourceAnchorDef);
  159. }
  160. }
  161. // Last segment
  162. const targetView = edgeView.targetView;
  163. const targetBBox = edgeView.targetBBox;
  164. let changeTargetAnchor = false;
  165. let deleteTargetAnchor = false;
  166. if (!nextVertex) {
  167. nextVertex = edgeView.targetAnchor.toJSON();
  168. nextVertex[axis] = position[axis];
  169. if (targetBBox.containsPoint(nextVertex)) {
  170. changeTargetAnchor = true;
  171. }
  172. else {
  173. vertices.push(nextVertex);
  174. deleteTargetAnchor = true;
  175. }
  176. }
  177. else if (index === vertices.length - 2) {
  178. if (targetBBox.containsPoint(nextVertex)) {
  179. vertices.pop();
  180. changeTargetAnchor = true;
  181. }
  182. else {
  183. nextVertex[axis] = position[axis];
  184. deleteTargetAnchor = true;
  185. }
  186. }
  187. else {
  188. nextVertex[axis] = position[axis];
  189. }
  190. if (typeof anchorFn === 'function' && targetView) {
  191. if (changeTargetAnchor) {
  192. const targetAnchorPosition = data.targetAnchor.clone();
  193. targetAnchorPosition[axis] = position[axis];
  194. const targetAnchor = FunctionExt.call(anchorFn, edgeView, targetAnchorPosition, targetView, edgeView.targetMagnet || targetView.container, 'target', edgeView, this);
  195. this.resetAnchor('target', targetAnchor);
  196. }
  197. if (deleteTargetAnchor) {
  198. this.resetAnchor('target', data.targetAnchorDef);
  199. }
  200. }
  201. if (!Point.equalPoints(vertices, this.vertices)) {
  202. this.cellView.cell.setVertices(vertices, { ui: true, toolId: this.cid });
  203. }
  204. this.updateHandle(handle, vertex, nextVertex, 0);
  205. if (!options.stopPropagation) {
  206. edgeView.notifyMouseMove(evt, coords.x, coords.y);
  207. }
  208. }
  209. onHandleChange({ handle, e }) {
  210. const options = this.options;
  211. const handles = this.handles;
  212. const edgeView = this.cellView;
  213. const index = handle.options.index;
  214. if (!Array.isArray(handles)) {
  215. return;
  216. }
  217. for (let i = 0, n = handles.length; i < n; i += 1) {
  218. if (i !== index) {
  219. handles[i].hide();
  220. }
  221. }
  222. this.focus();
  223. this.setEventData(e, {
  224. sourceAnchor: edgeView.sourceAnchor.clone(),
  225. targetAnchor: edgeView.targetAnchor.clone(),
  226. sourceAnchorDef: ObjectExt.cloneDeep(this.cell.prop(['source', 'anchor'])),
  227. targetAnchorDef: ObjectExt.cloneDeep(this.cell.prop(['target', 'anchor'])),
  228. });
  229. this.cell.startBatch('move-segment', { ui: true, toolId: this.cid });
  230. if (!options.stopPropagation) {
  231. const normalizedEvent = this.normalizeEvent(e);
  232. const coords = this.graph.snapToGrid(normalizedEvent.clientX, normalizedEvent.clientY);
  233. edgeView.notifyMouseDown(normalizedEvent, coords.x, coords.y);
  234. }
  235. }
  236. onHandleChanged({ e }) {
  237. const options = this.options;
  238. const edgeView = this.cellView;
  239. if (options.removeRedundancies) {
  240. edgeView.removeRedundantLinearVertices({ ui: true, toolId: this.cid });
  241. }
  242. const normalizedEvent = this.normalizeEvent(e);
  243. const coords = this.graph.snapToGrid(normalizedEvent.clientX, normalizedEvent.clientY);
  244. this.render();
  245. this.blur();
  246. this.cell.stopBatch('move-segment', { ui: true, toolId: this.cid });
  247. if (!options.stopPropagation) {
  248. edgeView.notifyMouseUp(normalizedEvent, coords.x, coords.y);
  249. }
  250. edgeView.checkMouseleave(normalizedEvent);
  251. options.onChanged && options.onChanged({ edge: edgeView.cell, edgeView });
  252. }
  253. updateHandle(handle, vertex, nextVertex, offset = 0) {
  254. const precision = this.options.precision || 0;
  255. const vertical = Math.abs(vertex.x - nextVertex.x) < precision;
  256. const horizontal = Math.abs(vertex.y - nextVertex.y) < precision;
  257. if (vertical || horizontal) {
  258. const segmentLine = new Line(vertex, nextVertex);
  259. const length = segmentLine.length();
  260. if (length < this.options.threshold) {
  261. handle.hide();
  262. }
  263. else {
  264. const position = segmentLine.getCenter();
  265. const axis = vertical ? 'x' : 'y';
  266. position[axis] += offset || 0;
  267. const angle = segmentLine.vector().vectorAngle(new Point(1, 0));
  268. handle.updatePosition(position.x, position.y, angle, this.cellView);
  269. handle.show();
  270. handle.options.axis = axis;
  271. }
  272. }
  273. else {
  274. handle.hide();
  275. }
  276. }
  277. onRemove() {
  278. this.resetHandles();
  279. }
  280. }
  281. (function (Segments) {
  282. class Handle extends View {
  283. constructor(options) {
  284. super();
  285. this.options = options;
  286. this.render();
  287. this.delegateEvents({
  288. mousedown: 'onMouseDown',
  289. touchstart: 'onMouseDown',
  290. });
  291. }
  292. render() {
  293. this.container = View.createElement('rect', true);
  294. const attrs = this.options.attrs;
  295. if (typeof attrs === 'function') {
  296. const defaults = Segments.getDefaults();
  297. this.setAttrs(Object.assign(Object.assign({}, defaults.attrs), attrs(this)));
  298. }
  299. else {
  300. this.setAttrs(attrs);
  301. }
  302. this.addClass(this.prefixClassName('edge-tool-segment'));
  303. }
  304. updatePosition(x, y, angle, view) {
  305. const p = view.getClosestPoint(new Point(x, y)) || new Point(x, y);
  306. let matrix = Dom.createSVGMatrix().translate(p.x, p.y);
  307. if (!p.equals({ x, y })) {
  308. const line = new Line(x, y, p.x, p.y);
  309. let deg = line.vector().vectorAngle(new Point(1, 0));
  310. if (deg !== 0) {
  311. deg += 90;
  312. }
  313. matrix = matrix.rotate(deg);
  314. }
  315. else {
  316. matrix = matrix.rotate(angle);
  317. }
  318. this.setAttrs({
  319. transform: Dom.matrixToTransformString(matrix),
  320. cursor: angle % 180 === 0 ? 'row-resize' : 'col-resize',
  321. });
  322. }
  323. onMouseDown(evt) {
  324. if (this.options.guard(evt)) {
  325. return;
  326. }
  327. this.trigger('change', { e: evt, handle: this });
  328. evt.stopPropagation();
  329. evt.preventDefault();
  330. this.options.graph.view.undelegateEvents();
  331. this.delegateDocumentEvents({
  332. mousemove: 'onMouseMove',
  333. touchmove: 'onMouseMove',
  334. mouseup: 'onMouseUp',
  335. touchend: 'onMouseUp',
  336. touchcancel: 'onMouseUp',
  337. }, evt.data);
  338. }
  339. onMouseMove(evt) {
  340. this.emit('changing', { e: evt, handle: this });
  341. }
  342. onMouseUp(evt) {
  343. this.emit('changed', { e: evt, handle: this });
  344. this.undelegateDocumentEvents();
  345. this.options.graph.view.delegateEvents();
  346. }
  347. show() {
  348. this.container.style.display = '';
  349. }
  350. hide() {
  351. this.container.style.display = 'none';
  352. }
  353. }
  354. Segments.Handle = Handle;
  355. })(Segments || (Segments = {}));
  356. (function (Segments) {
  357. Segments.config({
  358. name: 'segments',
  359. precision: 0.5,
  360. threshold: 40,
  361. snapRadius: 10,
  362. stopPropagation: true,
  363. removeRedundancies: true,
  364. attrs: {
  365. width: 20,
  366. height: 8,
  367. x: -10,
  368. y: -4,
  369. rx: 4,
  370. ry: 4,
  371. fill: '#333',
  372. stroke: '#fff',
  373. 'stroke-width': 2,
  374. },
  375. createHandle: (options) => new Segments.Handle(options),
  376. anchor: Util.getAnchor,
  377. });
  378. })(Segments || (Segments = {}));
  379. //# sourceMappingURL=segments.js.map