view-grapher.js 24 KB

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