grapher.js 24 KB

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