view-grapher.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. var grapher = grapher || {};
  2. var dagre = dagre || require('./dagre');
  3. grapher.Graph = class {
  4. constructor(compound, options) {
  5. this._isCompound = compound;
  6. this._options = options;
  7. this._nodes = new Map();
  8. this._edges = new Map();
  9. this._children = {};
  10. this._children['\x00'] = {};
  11. this._parent = {};
  12. }
  13. graph() {
  14. return this._options;
  15. }
  16. setNode(node) {
  17. this._nodes.set(node.name, node);
  18. if (this._isCompound) {
  19. this._parent[node.name] = '\x00';
  20. this._children[node.name] = {};
  21. this._children['\x00'][node.name] = true;
  22. }
  23. return this;
  24. }
  25. setEdge(edge) {
  26. if (!this._nodes.has(edge.v)) {
  27. throw new grapher.Error();
  28. }
  29. if (!this._nodes.has(edge.w)) {
  30. throw new grapher.Error();
  31. }
  32. const key = edge.v + ':' + edge.w;
  33. if (!this._edges.has(key)) {
  34. this._edges.set(key, { v: edge.v, w: edge.w, label: edge });
  35. }
  36. return this;
  37. }
  38. setParent(node, parent) {
  39. if (!this._isCompound) {
  40. throw new Error("Cannot set parent in a non-compound graph");
  41. }
  42. parent += "";
  43. for (let ancestor = parent; ancestor; ancestor = this.parent(ancestor)) {
  44. if (ancestor === node) {
  45. throw new Error("Setting " + parent + " as parent of " + node + " would create a cycle");
  46. }
  47. }
  48. delete this._children[this._parent[node]][node];
  49. this._parent[node] = parent;
  50. this._children[parent][node] = true;
  51. return this;
  52. }
  53. nodes() {
  54. return this._nodes;
  55. }
  56. hasNode(key) {
  57. return this._nodes.has(key);
  58. }
  59. node(key) {
  60. return this._nodes.get(key);
  61. }
  62. edges() {
  63. return this._edges;
  64. }
  65. parent(key) {
  66. if (this._isCompound) {
  67. const parent = this._parent[key];
  68. if (parent !== '\x00') {
  69. return parent;
  70. }
  71. }
  72. }
  73. children(key) {
  74. key = key === undefined ? '\x00' : key;
  75. if (this._isCompound) {
  76. const children = this._children[key];
  77. if (children) {
  78. return Object.keys(children);
  79. }
  80. }
  81. else if (key === '\x00') {
  82. return this.nodes().keys();
  83. }
  84. else if (this.hasNode(key)) {
  85. return [];
  86. }
  87. }
  88. build(document, origin) {
  89. const createGroup = (name) => {
  90. const element = document.createElementNS('http://www.w3.org/2000/svg', 'g');
  91. element.setAttribute('id', name);
  92. element.setAttribute('class', name);
  93. origin.appendChild(element);
  94. return element;
  95. };
  96. const clusterGroup = createGroup('clusters');
  97. const edgePathGroup = createGroup('edge-paths');
  98. const edgeLabelGroup = createGroup('edge-labels');
  99. const nodeGroup = createGroup('nodes');
  100. const edgePathGroupDefs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
  101. edgePathGroup.appendChild(edgePathGroupDefs);
  102. const marker = (id) => {
  103. const element = document.createElementNS('http://www.w3.org/2000/svg', 'marker');
  104. element.setAttribute('id', id);
  105. element.setAttribute('viewBox', '0 0 10 10');
  106. element.setAttribute('refX', 9);
  107. element.setAttribute('refY', 5);
  108. element.setAttribute('markerUnits', 'strokeWidth');
  109. element.setAttribute('markerWidth', 8);
  110. element.setAttribute('markerHeight', 6);
  111. element.setAttribute('orient', 'auto');
  112. const markerPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  113. markerPath.setAttribute('d', 'M 0 0 L 10 5 L 0 10 L 4 5 z');
  114. markerPath.style.setProperty('stroke-width', 1);
  115. element.appendChild(markerPath);
  116. return element;
  117. };
  118. edgePathGroupDefs.appendChild(marker("arrowhead-vee"));
  119. edgePathGroupDefs.appendChild(marker("arrowhead-vee-select"));
  120. for (const nodeId of this.nodes().keys()) {
  121. const node = this.node(nodeId);
  122. if (this.children(nodeId).length == 0) {
  123. // node
  124. node.build(document, nodeGroup);
  125. }
  126. else {
  127. // cluster
  128. node.rectangle = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
  129. if (node.rx) {
  130. node.rectangle.setAttribute('rx', node.rx);
  131. }
  132. if (node.ry) {
  133. node.rectangle.setAttribute('ry', node.ry);
  134. }
  135. node.element = document.createElementNS('http://www.w3.org/2000/svg', 'g');
  136. node.element.setAttribute('class', 'cluster');
  137. node.element.appendChild(node.rectangle);
  138. clusterGroup.appendChild(node.element);
  139. }
  140. }
  141. for (const edge of this.edges().values()) {
  142. edge.label.build(document, edgePathGroup, edgeLabelGroup);
  143. }
  144. }
  145. update() {
  146. dagre.layout(this);
  147. for (const nodeId of this.nodes().keys()) {
  148. const node = this.node(nodeId);
  149. if (this.children(nodeId).length == 0) {
  150. // node
  151. node.update();
  152. }
  153. else {
  154. // cluster
  155. const node = this.node(nodeId);
  156. node.element.setAttribute('transform', 'translate(' + node.x + ',' + node.y + ')');
  157. node.rectangle.setAttribute('x', - node.width / 2);
  158. node.rectangle.setAttribute('y', - node.height / 2 );
  159. node.rectangle.setAttribute('width', node.width);
  160. node.rectangle.setAttribute('height', node.height);
  161. }
  162. }
  163. for (const edge of this.edges().values()) {
  164. edge.label.update();
  165. }
  166. }
  167. };
  168. grapher.Node = class {
  169. constructor() {
  170. this._blocks = [];
  171. }
  172. header() {
  173. const block = new grapher.Node.Header();
  174. this._blocks.push(block);
  175. return block;
  176. }
  177. list() {
  178. const block = new grapher.Node.List();
  179. this._blocks.push(block);
  180. return block;
  181. }
  182. canvas() {
  183. const block = new grapher.Node.Canvas();
  184. this._blocks.push(block);
  185. return block;
  186. }
  187. build(document, parent) {
  188. this.element = document.createElementNS('http://www.w3.org/2000/svg', 'g');
  189. if (this.id) {
  190. this.element.setAttribute('id', this.id);
  191. }
  192. this.element.setAttribute('class', this.class ? 'node ' + this.class : 'node');
  193. this.element.style.opacity = 0;
  194. parent.appendChild(this.element);
  195. this.border = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  196. this.border.setAttribute('class', [ 'node', 'border' ].join(' '));
  197. for (let i = 0; i < this._blocks.length; i++) {
  198. const block = this._blocks[i];
  199. block.first = i === 0;
  200. block.last = i === this._blocks.length - 1;
  201. block.build(document, this.element);
  202. }
  203. this.element.appendChild(this.border);
  204. this.layout();
  205. }
  206. layout() {
  207. const width = Math.max(...this._blocks.map((block) => block.width));
  208. let height = 0;
  209. for (let i = 0; i < this._blocks.length; i++) {
  210. const block = this._blocks[i];
  211. block.y = height;
  212. // block.width = width;
  213. block.update(this.element, height, width, i == 0, i == this._blocks.length - 1);
  214. height = height + block.height;
  215. }
  216. this.border.setAttribute('d', grapher.Node.roundedRect(0, 0, width, height, true, true, true, true));
  217. const nodeBox = this.element.getBBox();
  218. this.width = nodeBox.width;
  219. this.height = nodeBox.height;
  220. }
  221. update() {
  222. this.element.setAttribute('transform', 'translate(' + (this.x - (this.width / 2)) + ',' + (this.y - (this.height / 2)) + ')');
  223. this.element.style.opacity = 1;
  224. }
  225. static roundedRect(x, y, width, height, r1, r2, r3, r4) {
  226. const radius = 5;
  227. r1 = r1 ? radius : 0;
  228. r2 = r2 ? radius : 0;
  229. r3 = r3 ? radius : 0;
  230. r4 = r4 ? radius : 0;
  231. return "M" + (x + r1) + "," + y +
  232. "h" + (width - r1 - r2) +
  233. "a" + r2 + "," + r2 + " 0 0 1 " + r2 + "," + r2 +
  234. "v" + (height - r2 - r3) +
  235. "a" + r3 + "," + r3 + " 0 0 1 " + -r3 + "," + r3 +
  236. "h" + (r3 + r4 - width) +
  237. "a" + r4 + "," + r4 + " 0 0 1 " + -r4 + "," + -r4 +
  238. 'v' + (-height + r4 + r1) +
  239. "a" + r1 + "," + r1 + " 0 0 1 " + r1 + "," + -r1 +
  240. "z";
  241. }
  242. };
  243. grapher.Node.Header = class {
  244. constructor() {
  245. this._entries = [];
  246. }
  247. add(id, classList, content, tooltip, handler) {
  248. const entry = new grapher.Node.Header.Entry(id, classList, content, tooltip, handler);
  249. this._entries.push(entry);
  250. return entry;
  251. }
  252. build(document, parent) {
  253. this._document = document;
  254. this.width = 0;
  255. this.height = 0;
  256. let x = 0;
  257. const y = 0;
  258. for (const entry of this._entries) {
  259. entry.x = x;
  260. entry.y = y;
  261. entry.build(document, parent);
  262. x += entry.width;
  263. this.height = Math.max(entry.height, this.height);
  264. this.width = Math.max(x, this.width);
  265. }
  266. if (!this.first) {
  267. this.line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
  268. parent.appendChild(this.line);
  269. }
  270. }
  271. update(parent, top, width) {
  272. const dx = width - this.width;
  273. for (let i = 0; i < this._entries.length; i++) {
  274. const entry = this._entries[i];
  275. if (i == 0) {
  276. entry.width = entry.width + dx;
  277. }
  278. else {
  279. entry.x = entry.x + dx;
  280. entry.tx = entry.tx + dx;
  281. }
  282. entry.y = entry.y + top;
  283. }
  284. for (let i = 0; i < this._entries.length; i++) {
  285. const entry = this._entries[i];
  286. entry.element.setAttribute('transform', 'translate(' + entry.x + ',' + entry.y + ')');
  287. const r1 = i == 0 && this.first;
  288. const r2 = i == this._entries.length - 1 && this.first;
  289. const r3 = i == this._entries.length - 1 && this.last;
  290. const r4 = i == 0 && this.last;
  291. entry.path.setAttribute('d', grapher.Node.roundedRect(0, 0, entry.width, entry.height, r1, r2, r3, r4));
  292. entry.text.setAttribute('x', 6);
  293. entry.text.setAttribute('y', entry.ty);
  294. }
  295. for (let i = 0; i < this._entries.length; i++) {
  296. const entry = this._entries[i];
  297. if (i != 0) {
  298. const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
  299. line.setAttribute('class', 'node');
  300. line.setAttribute('x1', entry.x);
  301. line.setAttribute('x2', entry.x);
  302. line.setAttribute('y1', top);
  303. line.setAttribute('y2', top + this.height);
  304. parent.appendChild(line);
  305. }
  306. }
  307. if (this.line) {
  308. this.line.setAttribute('class', 'node');
  309. this.line.setAttribute('x1', 0);
  310. this.line.setAttribute('x2', width);
  311. this.line.setAttribute('y1', top);
  312. this.line.setAttribute('y2', top);
  313. }
  314. }
  315. };
  316. grapher.Node.Header.Entry = class {
  317. constructor(id, classList, content, tooltip, handler) {
  318. this.id = id;
  319. this.classList = classList;
  320. this.content = content;
  321. this.tooltip = tooltip;
  322. this.handler = handler;
  323. this.events = {};
  324. }
  325. on(event, callback) {
  326. this.events[event] = this.events[event] || [];
  327. this.events[event].push(callback);
  328. }
  329. raise(event, data) {
  330. if (this.events && this.events[event]) {
  331. for (const callback of this.events[event]) {
  332. callback(this, data);
  333. }
  334. }
  335. }
  336. build(document, parent) {
  337. const yPadding = 4;
  338. const xPadding = 7;
  339. this.element = document.createElementNS('http://www.w3.org/2000/svg', 'g');
  340. parent.appendChild(this.element);
  341. this.path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  342. this.text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
  343. this.element.appendChild(this.path);
  344. this.element.appendChild(this.text);
  345. const classList = [ 'node-item' ];
  346. if (this.classList) {
  347. classList.push(...this.classList);
  348. }
  349. this.element.setAttribute('class', classList.join(' '));
  350. if (this.id) {
  351. this.element.setAttribute('id', this.id);
  352. }
  353. if (this.events.click) {
  354. this.element.addEventListener('click', () => this.raise('click'));
  355. }
  356. if (this.tooltip) {
  357. const titleElement = document.createElementNS('http://www.w3.org/2000/svg', 'title');
  358. titleElement.textContent = this.tooltip;
  359. this.element.appendChild(titleElement);
  360. }
  361. if (this.content) {
  362. this.text.textContent = this.content;
  363. }
  364. const boundingBox = this.text.getBBox();
  365. this.width = boundingBox.width + xPadding + xPadding;
  366. this.height = boundingBox.height + yPadding + yPadding;
  367. this.tx = xPadding;
  368. this.ty = yPadding - boundingBox.y;
  369. }
  370. };
  371. grapher.Node.List = class {
  372. constructor() {
  373. this._items = [];
  374. this.events = {};
  375. }
  376. add(id, name, value, tooltip, separator) {
  377. const item = new grapher.Node.List.Item(id, name, value, tooltip, separator);
  378. this._items.push(item);
  379. return item;
  380. }
  381. on(event, callback) {
  382. this.events[event] = this.events[event] || [];
  383. this.events[event].push(callback);
  384. }
  385. raise(event, data) {
  386. if (this.events && this.events[event]) {
  387. for (const callback of this.events[event]) {
  388. callback(this, data);
  389. }
  390. }
  391. }
  392. build(document, parent) {
  393. this._document = document;
  394. this.width = 0;
  395. this.height = 0;
  396. const x = 0;
  397. const y = 0;
  398. this.element = document.createElementNS('http://www.w3.org/2000/svg', 'g');
  399. this.element.setAttribute('class', 'node-attribute');
  400. if (this.events.click) {
  401. this.element.addEventListener('click', () => this.raise('click'));
  402. }
  403. this.element.setAttribute('transform', 'translate(' + x + ',' + y + ')');
  404. this.background = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  405. this.element.appendChild(this.background);
  406. parent.appendChild(this.element);
  407. this.height += 3;
  408. for (const item of this._items) {
  409. const yPadding = 1;
  410. const xPadding = 6;
  411. const textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
  412. if (item.id) {
  413. textElement.setAttribute('id', item.id);
  414. }
  415. textElement.setAttribute('xml:space', 'preserve');
  416. this.element.appendChild(textElement);
  417. if (item.tooltip) {
  418. const title = document.createElementNS('http://www.w3.org/2000/svg', 'title');
  419. title.textContent = item.tooltip;
  420. textElement.appendChild(title);
  421. }
  422. const textNameElement = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
  423. textNameElement.textContent = item.name;
  424. if (item.separator.trim() != '=') {
  425. textNameElement.style.fontWeight = 'bold';
  426. }
  427. textElement.appendChild(textNameElement);
  428. const textValueElement = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
  429. textValueElement.textContent = item.separator + item.value;
  430. textElement.appendChild(textValueElement);
  431. const size = textElement.getBBox();
  432. const width = xPadding + size.width + xPadding;
  433. this.width = Math.max(width, this.width);
  434. textElement.setAttribute('x', x + xPadding);
  435. textElement.setAttribute('y', this.height + yPadding - size.y);
  436. this.height += yPadding + size.height + yPadding;
  437. }
  438. this.height += 3;
  439. this.width = Math.max(75, this.width);
  440. if (!this.first) {
  441. this.line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
  442. this.line.setAttribute('class', 'node');
  443. this.element.appendChild(this.line);
  444. }
  445. }
  446. update(parent, top, width) {
  447. this.element.setAttribute('transform', 'translate(0,' + this.y + ')');
  448. this.background.setAttribute('d', grapher.Node.roundedRect(0, 0, width, this.height, this.first, this.first, this.last, this.last));
  449. if (this.line) {
  450. this.line.setAttribute('x1', 0);
  451. this.line.setAttribute('x2', width);
  452. this.line.setAttribute('y1', 0);
  453. this.line.setAttribute('y2', 0);
  454. }
  455. }
  456. };
  457. grapher.Node.List.Item = class {
  458. constructor(id, name, value, tooltip, separator) {
  459. this.id = id;
  460. this.name = name;
  461. this.value = value;
  462. this.tooltip = tooltip;
  463. this.separator = separator;
  464. }
  465. };
  466. grapher.Node.Canvas = class {
  467. constructor() {
  468. this.width = 0;
  469. this.height = 0;
  470. }
  471. build(/* document, parent */) {
  472. }
  473. update(/* parent, top, width , first, last */) {
  474. }
  475. };
  476. grapher.Edge = class {
  477. constructor(from, to) {
  478. this.from = from;
  479. this.to = to;
  480. }
  481. get arrowhead() {
  482. return 'vee';
  483. }
  484. build(document, edgePathGroupElement, edgeLabelGroupElement) {
  485. const createElement = (name) => {
  486. return document.createElementNS('http://www.w3.org/2000/svg', name);
  487. };
  488. this.element = createElement('path');
  489. if (this.id) {
  490. this.element.setAttribute('id', this.id);
  491. }
  492. this.element.setAttribute('class', this.class ? 'edge-path ' + this.class : 'edge-path');
  493. edgePathGroupElement.appendChild(this.element);
  494. if (this.label) {
  495. const tspan = createElement('tspan');
  496. tspan.setAttribute('xml:space', 'preserve');
  497. tspan.setAttribute('dy', '1em');
  498. tspan.setAttribute('x', '1');
  499. tspan.appendChild(document.createTextNode(this.label));
  500. this.labelElement = createElement('text');
  501. this.labelElement.appendChild(tspan);
  502. this.labelElement.style.opacity = 0;
  503. this.labelElement.setAttribute('class', 'edge-label');
  504. if (this.id) {
  505. this.labelElement.setAttribute('id', 'edge-label-' + this.id);
  506. }
  507. edgeLabelGroupElement.appendChild(this.labelElement);
  508. const edgeBox = this.labelElement.getBBox();
  509. this.width = edgeBox.width;
  510. this.height = edgeBox.height;
  511. }
  512. }
  513. update() {
  514. const edgePath = grapher.Edge._computeCurvePath(this, this.from, this.to);
  515. this.element.setAttribute('d', edgePath);
  516. if (this.labelElement) {
  517. this.labelElement.setAttribute('transform', 'translate(' + (this.x - (this.width / 2)) + ',' + (this.y - (this.height / 2)) + ')');
  518. this.labelElement.style.opacity = 1;
  519. }
  520. }
  521. static _computeCurvePath(edge, tail, head) {
  522. const points = edge.points.slice(1, edge.points.length - 1);
  523. points.unshift(grapher.Edge._intersectRect(tail, points[0]));
  524. points.push(grapher.Edge._intersectRect(head, points[points.length - 1]));
  525. const curve = new grapher.Edge.Curve(points);
  526. return curve.path.data;
  527. }
  528. static _intersectRect(node, point) {
  529. const x = node.x;
  530. const y = node.y;
  531. const dx = point.x - x;
  532. const dy = point.y - y;
  533. let w = node.width / 2;
  534. let h = node.height / 2;
  535. let sx;
  536. let sy;
  537. if (Math.abs(dy) * w > Math.abs(dx) * h) {
  538. if (dy < 0) {
  539. h = -h;
  540. }
  541. sx = dy === 0 ? 0 : h * dx / dy;
  542. sy = h;
  543. }
  544. else {
  545. if (dx < 0) {
  546. w = -w;
  547. }
  548. sx = w;
  549. sy = dx === 0 ? 0 : w * dy / dx;
  550. }
  551. return {
  552. x: x + sx,
  553. y: y + sy
  554. };
  555. }
  556. };
  557. grapher.Edge.Curve = class {
  558. constructor(points) {
  559. this._path = new grapher.Edge.Path();
  560. this._x0 = NaN;
  561. this._x1 = NaN;
  562. this._y0 = NaN;
  563. this._y1 = NaN;
  564. this._state = 0;
  565. for (let i = 0; i < points.length; i++) {
  566. const point = points[i];
  567. this.point(point.x, point.y);
  568. if (i === points.length - 1) {
  569. switch (this._state) {
  570. case 3:
  571. this.curve(this._x1, this._y1);
  572. this._path.lineTo(this._x1, this._y1);
  573. break;
  574. case 2:
  575. this._path.lineTo(this._x1, this._y1);
  576. break;
  577. }
  578. if (this._line || (this._line !== 0 && this._point === 1)) {
  579. this._path.closePath();
  580. }
  581. this._line = 1 - this._line;
  582. }
  583. }
  584. }
  585. get path() {
  586. return this._path;
  587. }
  588. point(x, y) {
  589. x = +x;
  590. y = +y;
  591. switch (this._state) {
  592. case 0:
  593. this._state = 1;
  594. if (this._line) {
  595. this._path.lineTo(x, y);
  596. }
  597. else {
  598. this._path.moveTo(x, y);
  599. }
  600. break;
  601. case 1:
  602. this._state = 2;
  603. break;
  604. case 2:
  605. this._state = 3;
  606. this._path.lineTo((5 * this._x0 + this._x1) / 6, (5 * this._y0 + this._y1) / 6);
  607. this.curve(x, y);
  608. break;
  609. default:
  610. this.curve(x, y);
  611. break;
  612. }
  613. this._x0 = this._x1;
  614. this._x1 = x;
  615. this._y0 = this._y1;
  616. this._y1 = y;
  617. }
  618. curve(x, y) {
  619. this._path.bezierCurveTo(
  620. (2 * this._x0 + this._x1) / 3,
  621. (2 * this._y0 + this._y1) / 3,
  622. (this._x0 + 2 * this._x1) / 3,
  623. (this._y0 + 2 * this._y1) / 3,
  624. (this._x0 + 4 * this._x1 + x) / 6,
  625. (this._y0 + 4 * this._y1 + y) / 6
  626. );
  627. }
  628. };
  629. grapher.Edge.Path = class {
  630. constructor() {
  631. this._x0 = null;
  632. this._y0 = null;
  633. this._x1 = null;
  634. this._y1 = null;
  635. this._data = '';
  636. }
  637. moveTo(x, y) {
  638. this._data += "M" + (this._x0 = this._x1 = +x) + "," + (this._y0 = this._y1 = +y);
  639. }
  640. lineTo(x, y) {
  641. this._data += "L" + (this._x1 = +x) + "," + (this._y1 = +y);
  642. }
  643. bezierCurveTo(x1, y1, x2, y2, x, y) {
  644. this._data += "C" + (+x1) + "," + (+y1) + "," + (+x2) + "," + (+y2) + "," + (this._x1 = +x) + "," + (this._y1 = +y);
  645. }
  646. closePath() {
  647. if (this._x1 !== null) {
  648. this._x1 = this._x0;
  649. this._y1 = this._y0;
  650. this._data += "Z";
  651. }
  652. }
  653. get data() {
  654. return this._data;
  655. }
  656. };
  657. if (typeof module !== 'undefined' && typeof module.exports === 'object') {
  658. module.exports.Graph = grapher.Graph;
  659. module.exports.Node = grapher.Node;
  660. module.exports.Edge = grapher.Edge;
  661. }