vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

607 lines
20 KiB

9 years ago
  1. let util = require("../../../../../util");
  2. /**
  3. * The Base Class for all edges.
  4. *
  5. */
  6. class EdgeBase {
  7. /**
  8. * @param {Object} options
  9. * @param {Object} body
  10. * @param {Label} labelModule
  11. */
  12. constructor(options, body, labelModule) {
  13. this.body = body;
  14. this.labelModule = labelModule;
  15. this.options = {};
  16. this.setOptions(options);
  17. this.colorDirty = true;
  18. this.color = {};
  19. this.selectionWidth = 2;
  20. this.hoverWidth = 1.5;
  21. this.fromPoint = this.from;
  22. this.toPoint = this.to;
  23. }
  24. /**
  25. * Connects a node to itself
  26. */
  27. connect() {
  28. this.from = this.body.nodes[this.options.from];
  29. this.to = this.body.nodes[this.options.to];
  30. }
  31. /**
  32. *
  33. * @returns {boolean} always false
  34. */
  35. cleanup() {
  36. return false;
  37. }
  38. /**
  39. *
  40. * @param {Object} options
  41. */
  42. setOptions(options) {
  43. this.options = options;
  44. this.from = this.body.nodes[this.options.from];
  45. this.to = this.body.nodes[this.options.to];
  46. this.id = this.options.id;
  47. }
  48. /**
  49. * Redraw a edge as a line
  50. * Draw this edge in the given canvas
  51. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  52. *
  53. * @param {CanvasRenderingContext2D} ctx
  54. * @param {Array} values
  55. * @param {boolean} selected
  56. * @param {boolean} hover
  57. * @param {Node} viaNode
  58. * @private
  59. */
  60. drawLine(ctx, values, selected, hover, viaNode) {
  61. // set style
  62. ctx.strokeStyle = this.getColor(ctx, values, selected, hover);
  63. ctx.lineWidth = values.width;
  64. if (values.dashes !== false) {
  65. this._drawDashedLine(ctx, values, viaNode);
  66. }
  67. else {
  68. this._drawLine(ctx, values, viaNode);
  69. }
  70. }
  71. /**
  72. *
  73. * @param {CanvasRenderingContext2D} ctx
  74. * @param {Array} values
  75. * @param {Node} viaNode
  76. * @param {{x: number, y: number}} [fromPoint]
  77. * @param {{x: number, y: number}} [toPoint]
  78. * @private
  79. */
  80. _drawLine(ctx, values, viaNode, fromPoint, toPoint) {
  81. if (this.from != this.to) {
  82. // draw line
  83. this._line(ctx, values, viaNode, fromPoint, toPoint);
  84. }
  85. else {
  86. let [x,y,radius] = this._getCircleData(ctx);
  87. this._circle(ctx, values, x, y, radius);
  88. }
  89. }
  90. /**
  91. *
  92. * @param {CanvasRenderingContext2D} ctx
  93. * @param {Array} values
  94. * @param {Node} viaNode
  95. * @param {{x: number, y: number}} [fromPoint] TODO: Remove in next major release
  96. * @param {{x: number, y: number}} [toPoint] TODO: Remove in next major release
  97. * @private
  98. */
  99. _drawDashedLine(ctx, values, viaNode, fromPoint, toPoint) { // eslint-disable-line no-unused-vars
  100. ctx.lineCap = 'round';
  101. let pattern = [5,5];
  102. if (Array.isArray(values.dashes) === true) {
  103. pattern = values.dashes;
  104. }
  105. // only firefox and chrome support this method, else we use the legacy one.
  106. if (ctx.setLineDash !== undefined) {
  107. ctx.save();
  108. // set dash settings for chrome or firefox
  109. ctx.setLineDash(pattern);
  110. ctx.lineDashOffset = 0;
  111. // draw the line
  112. if (this.from != this.to) {
  113. // draw line
  114. this._line(ctx, values, viaNode);
  115. }
  116. else {
  117. let [x,y,radius] = this._getCircleData(ctx);
  118. this._circle(ctx, values, x, y, radius);
  119. }
  120. // restore the dash settings.
  121. ctx.setLineDash([0]);
  122. ctx.lineDashOffset = 0;
  123. ctx.restore();
  124. }
  125. else { // unsupporting smooth lines
  126. if (this.from != this.to) {
  127. // draw line
  128. ctx.dashedLine(this.from.x, this.from.y, this.to.x, this.to.y, pattern);
  129. }
  130. else {
  131. let [x,y,radius] = this._getCircleData(ctx);
  132. this._circle(ctx, values, x, y, radius);
  133. }
  134. // draw shadow if enabled
  135. this.enableShadow(ctx, values);
  136. ctx.stroke();
  137. // disable shadows for other elements.
  138. this.disableShadow(ctx, values);
  139. }
  140. }
  141. /**
  142. *
  143. * @param {Node} nearNode
  144. * @param {CanvasRenderingContext2D} ctx
  145. * @param {Object} options
  146. * @returns {{x: number, y: number}}
  147. */
  148. findBorderPosition(nearNode, ctx, options) {
  149. if (this.from != this.to) {
  150. return this._findBorderPosition(nearNode, ctx, options);
  151. }
  152. else {
  153. return this._findBorderPositionCircle(nearNode, ctx, options);
  154. }
  155. }
  156. /**
  157. *
  158. * @param {CanvasRenderingContext2D} ctx
  159. * @returns {{from: ({x: number, y: number, t: number}|*), to: ({x: number, y: number, t: number}|*)}}
  160. */
  161. findBorderPositions(ctx) {
  162. let from = {};
  163. let to = {};
  164. if (this.from != this.to) {
  165. from = this._findBorderPosition(this.from, ctx);
  166. to = this._findBorderPosition(this.to, ctx);
  167. }
  168. else {
  169. let [x,y] = this._getCircleData(ctx).slice(0, 2);
  170. from = this._findBorderPositionCircle(this.from, ctx, {x, y, low:0.25, high:0.6, direction:-1});
  171. to = this._findBorderPositionCircle(this.from, ctx, {x, y, low:0.6, high:0.8, direction:1});
  172. }
  173. return {from, to};
  174. }
  175. /**
  176. *
  177. * @param {CanvasRenderingContext2D} ctx
  178. * @returns {Array.<number>} x, y, radius
  179. * @private
  180. */
  181. _getCircleData(ctx) {
  182. let x, y;
  183. let node = this.from;
  184. let radius = this.options.selfReferenceSize;
  185. if (ctx !== undefined) {
  186. if (node.shape.width === undefined) {
  187. node.shape.resize(ctx);
  188. }
  189. }
  190. // get circle coordinates
  191. if (node.shape.width > node.shape.height) {
  192. x = node.x + node.shape.width * 0.5;
  193. y = node.y - radius;
  194. }
  195. else {
  196. x = node.x + radius;
  197. y = node.y - node.shape.height * 0.5;
  198. }
  199. return [x,y,radius];
  200. }
  201. /**
  202. * Get a point on a circle
  203. * @param {number} x
  204. * @param {number} y
  205. * @param {number} radius
  206. * @param {number} percentage - Value between 0 (line start) and 1 (line end)
  207. * @return {Object} point
  208. * @private
  209. */
  210. _pointOnCircle(x, y, radius, percentage) {
  211. let angle = percentage * 2 * Math.PI;
  212. return {
  213. x: x + radius * Math.cos(angle),
  214. y: y - radius * Math.sin(angle)
  215. }
  216. }
  217. /**
  218. * This function uses binary search to look for the point where the circle crosses the border of the node.
  219. * @param {Node} node
  220. * @param {CanvasRenderingContext2D} ctx
  221. * @param {Object} options
  222. * @returns {*}
  223. * @private
  224. */
  225. _findBorderPositionCircle(node, ctx, options) {
  226. let x = options.x;
  227. let y = options.y;
  228. let low = options.low;
  229. let high = options.high;
  230. let direction = options.direction;
  231. let maxIterations = 10;
  232. let iteration = 0;
  233. let radius = this.options.selfReferenceSize;
  234. let pos, angle, distanceToBorder, distanceToPoint, difference;
  235. let threshold = 0.05;
  236. let middle = (low + high) * 0.5;
  237. while (low <= high && iteration < maxIterations) {
  238. middle = (low + high) * 0.5;
  239. pos = this._pointOnCircle(x, y, radius, middle);
  240. angle = Math.atan2((node.y - pos.y), (node.x - pos.x));
  241. distanceToBorder = node.distanceToBorder(ctx, angle);
  242. distanceToPoint = Math.sqrt(Math.pow(pos.x - node.x, 2) + Math.pow(pos.y - node.y, 2));
  243. difference = distanceToBorder - distanceToPoint;
  244. if (Math.abs(difference) < threshold) {
  245. break; // found
  246. }
  247. else if (difference > 0) { // distance to nodes is larger than distance to border --> t needs to be bigger if we're looking at the to node.
  248. if (direction > 0) {
  249. low = middle;
  250. }
  251. else {
  252. high = middle;
  253. }
  254. }
  255. else {
  256. if (direction > 0) {
  257. high = middle;
  258. }
  259. else {
  260. low = middle;
  261. }
  262. }
  263. iteration++;
  264. }
  265. pos.t = middle;
  266. return pos;
  267. }
  268. /**
  269. * Get the line width of the edge. Depends on width and whether one of the
  270. * connected nodes is selected.
  271. * @param {boolean} selected
  272. * @param {boolean} hover
  273. * @returns {number} width
  274. * @private
  275. */
  276. getLineWidth(selected, hover) {
  277. if (selected === true) {
  278. return Math.max(this.selectionWidth, 0.3 / this.body.view.scale);
  279. }
  280. else {
  281. if (hover === true) {
  282. return Math.max(this.hoverWidth, 0.3 / this.body.view.scale);
  283. }
  284. else {
  285. return Math.max(this.options.width, 0.3 / this.body.view.scale);
  286. }
  287. }
  288. }
  289. /**
  290. *
  291. * @param {CanvasRenderingContext2D} ctx
  292. * @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
  293. * @param {boolean} selected - Unused
  294. * @param {boolean} hover - Unused
  295. * @returns {string}
  296. */
  297. getColor(ctx, values, selected, hover) { // eslint-disable-line no-unused-vars
  298. if (values.inheritsColor !== false) {
  299. // when this is a loop edge, just use the 'from' method
  300. if ((values.inheritsColor === 'both') && (this.from.id !== this.to.id)) {
  301. let grd = ctx.createLinearGradient(this.from.x, this.from.y, this.to.x, this.to.y);
  302. let fromColor, toColor;
  303. fromColor = this.from.options.color.highlight.border;
  304. toColor = this.to.options.color.highlight.border;
  305. if ((this.from.selected === false) && (this.to.selected === false)) {
  306. fromColor = util.overrideOpacity(this.from.options.color.border, values.opacity);
  307. toColor = util.overrideOpacity(this.to.options.color.border, values.opacity);
  308. }
  309. else if ((this.from.selected === true) && (this.to.selected === false)) {
  310. toColor = this.to.options.color.border;
  311. }
  312. else if ((this.from.selected === false) && (this.to.selected === true)) {
  313. fromColor = this.from.options.color.border;
  314. }
  315. grd.addColorStop(0, fromColor);
  316. grd.addColorStop(1, toColor);
  317. // -------------------- this returns -------------------- //
  318. return grd;
  319. }
  320. if (values.inheritsColor === "to") {
  321. return util.overrideOpacity(this.to.options.color.border, values.opacity);
  322. } else { // "from"
  323. return util.overrideOpacity(this.from.options.color.border, values.opacity);
  324. }
  325. } else {
  326. return util.overrideOpacity(values.color, values.opacity);
  327. }
  328. }
  329. /**
  330. * Draw a line from a node to itself, a circle
  331. *
  332. * @param {CanvasRenderingContext2D} ctx
  333. * @param {Array} values
  334. * @param {number} x
  335. * @param {number} y
  336. * @param {number} radius
  337. * @private
  338. */
  339. _circle(ctx, values, x, y, radius) {
  340. // draw shadow if enabled
  341. this.enableShadow(ctx, values);
  342. // draw a circle
  343. ctx.beginPath();
  344. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  345. ctx.stroke();
  346. // disable shadows for other elements.
  347. this.disableShadow(ctx, values);
  348. }
  349. /**
  350. * Calculate the distance between a point (x3,y3) and a line segment from
  351. * (x1,y1) to (x2,y2).
  352. * x3,y3 is the point.
  353. * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
  354. * @param {number} x1
  355. * @param {number} y1
  356. * @param {number} x2
  357. * @param {number} y2
  358. * @param {number} x3
  359. * @param {number} y3
  360. * @param {Node} via
  361. * @param {Array} values
  362. * @returns {number}
  363. * @private
  364. */
  365. getDistanceToEdge(x1, y1, x2, y2, x3, y3, via, values) { // eslint-disable-line no-unused-vars
  366. let returnValue = 0;
  367. if (this.from != this.to) {
  368. returnValue = this._getDistanceToEdge(x1, y1, x2, y2, x3, y3, via)
  369. }
  370. else {
  371. let [x,y,radius] = this._getCircleData(undefined);
  372. let dx = x - x3;
  373. let dy = y - y3;
  374. returnValue = Math.abs(Math.sqrt(dx * dx + dy * dy) - radius);
  375. }
  376. if (this.labelModule.size.left < x3 &&
  377. this.labelModule.size.left + this.labelModule.size.width > x3 &&
  378. this.labelModule.size.top < y3 &&
  379. this.labelModule.size.top + this.labelModule.size.height > y3) {
  380. return 0;
  381. }
  382. else {
  383. return returnValue;
  384. }
  385. }
  386. /**
  387. *
  388. * @param {number} x1
  389. * @param {number} y1
  390. * @param {number} x2
  391. * @param {number} y2
  392. * @param {number} x3
  393. * @param {number} y3
  394. * @returns {number}
  395. * @private
  396. */
  397. _getDistanceToLine(x1, y1, x2, y2, x3, y3) {
  398. let px = x2 - x1;
  399. let py = y2 - y1;
  400. let something = px * px + py * py;
  401. let u = ((x3 - x1) * px + (y3 - y1) * py) / something;
  402. if (u > 1) {
  403. u = 1;
  404. }
  405. else if (u < 0) {
  406. u = 0;
  407. }
  408. let x = x1 + u * px;
  409. let y = y1 + u * py;
  410. let dx = x - x3;
  411. let dy = y - y3;
  412. //# Note: If the actual distance does not matter,
  413. //# if you only want to compare what this function
  414. //# returns to other results of this function, you
  415. //# can just return the squared distance instead
  416. //# (i.e. remove the sqrt) to gain a little performance
  417. return Math.sqrt(dx * dx + dy * dy);
  418. }
  419. /**
  420. * @param {CanvasRenderingContext2D} ctx
  421. * @param {string} position
  422. * @param {Node} viaNode
  423. * @param {boolean} selected
  424. * @param {boolean} hover
  425. * @param {Array} values
  426. * @returns {{point: *, core: {x: number, y: number}, angle: *, length: number, type: *}}
  427. */
  428. getArrowData(ctx, position, viaNode, selected, hover, values) {
  429. // set lets
  430. let angle;
  431. let arrowPoint;
  432. let node1;
  433. let node2;
  434. let guideOffset;
  435. let scaleFactor;
  436. let type;
  437. let lineWidth = values.width;
  438. if (position === 'from') {
  439. node1 = this.from;
  440. node2 = this.to;
  441. guideOffset = 0.1;
  442. scaleFactor = values.fromArrowScale;
  443. type = values.fromArrowType;
  444. }
  445. else if (position === 'to') {
  446. node1 = this.to;
  447. node2 = this.from;
  448. guideOffset = -0.1;
  449. scaleFactor = values.toArrowScale;
  450. type = values.toArrowType;
  451. }
  452. else {
  453. node1 = this.to;
  454. node2 = this.from;
  455. scaleFactor = values.middleArrowScale;
  456. type = values.middleArrowType;
  457. }
  458. // if not connected to itself
  459. if (node1 != node2) {
  460. if (position !== 'middle') {
  461. // draw arrow head
  462. if (this.options.smooth.enabled === true) {
  463. arrowPoint = this.findBorderPosition(node1, ctx, { via: viaNode });
  464. let guidePos = this.getPoint(Math.max(0.0, Math.min(1.0, arrowPoint.t + guideOffset)), viaNode);
  465. angle = Math.atan2((arrowPoint.y - guidePos.y), (arrowPoint.x - guidePos.x));
  466. } else {
  467. angle = Math.atan2((node1.y - node2.y), (node1.x - node2.x));
  468. arrowPoint = this.findBorderPosition(node1, ctx);
  469. }
  470. } else {
  471. angle = Math.atan2((node1.y - node2.y), (node1.x - node2.x));
  472. arrowPoint = this.getPoint(0.5, viaNode); // this is 0.6 to account for the size of the arrow.
  473. }
  474. } else {
  475. // draw circle
  476. let [x,y,radius] = this._getCircleData(ctx);
  477. if (position === 'from') {
  478. arrowPoint = this.findBorderPosition(this.from, ctx, { x, y, low: 0.25, high: 0.6, direction: -1 });
  479. angle = arrowPoint.t * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI;
  480. } else if (position === 'to') {
  481. arrowPoint = this.findBorderPosition(this.from, ctx, { x, y, low: 0.6, high: 1.0, direction: 1 });
  482. angle = arrowPoint.t * -2 * Math.PI + 1.5 * Math.PI - 1.1 * Math.PI;
  483. } else {
  484. arrowPoint = this._pointOnCircle(x, y, radius, 0.175);
  485. angle = 3.9269908169872414; // === 0.175 * -2 * Math.PI + 1.5 * Math.PI + 0.1 * Math.PI;
  486. }
  487. }
  488. let length = 15 * scaleFactor + 3 * lineWidth; // 3* lineWidth is the width of the edge.
  489. var xi = arrowPoint.x - length * 0.9 * Math.cos(angle);
  490. var yi = arrowPoint.y - length * 0.9 * Math.sin(angle);
  491. let arrowCore = { x: xi, y: yi };
  492. return { point: arrowPoint, core: arrowCore, angle: angle, length: length, type: type };
  493. }
  494. /**
  495. *
  496. * @param {CanvasRenderingContext2D} ctx
  497. * @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
  498. * @param {boolean} selected
  499. * @param {boolean} hover
  500. * @param {Object} arrowData
  501. */
  502. drawArrowHead(ctx, values, selected, hover, arrowData) {
  503. // set style
  504. ctx.strokeStyle = this.getColor(ctx, values, selected, hover);
  505. ctx.fillStyle = ctx.strokeStyle;
  506. ctx.lineWidth = values.width;
  507. if (arrowData.type && arrowData.type.toLowerCase() === 'circle') {
  508. // draw circle at the end of the line
  509. ctx.circleEndpoint(arrowData.point.x, arrowData.point.y, arrowData.angle, arrowData.length);
  510. } else {
  511. // draw arrow at the end of the line
  512. ctx.arrowEndpoint(arrowData.point.x, arrowData.point.y, arrowData.angle, arrowData.length);
  513. }
  514. // draw shadow if enabled
  515. this.enableShadow(ctx, values);
  516. ctx.fill();
  517. // disable shadows for other elements.
  518. this.disableShadow(ctx, values);
  519. }
  520. /**
  521. *
  522. * @param {CanvasRenderingContext2D} ctx
  523. * @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
  524. */
  525. enableShadow(ctx, values) {
  526. if (values.shadow === true) {
  527. ctx.shadowColor = values.shadowColor;
  528. ctx.shadowBlur = values.shadowSize;
  529. ctx.shadowOffsetX = values.shadowX;
  530. ctx.shadowOffsetY = values.shadowY;
  531. }
  532. }
  533. /**
  534. *
  535. * @param {CanvasRenderingContext2D} ctx
  536. * @param {{toArrow: boolean, toArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), toArrowType: *, middleArrow: boolean, middleArrowScale: (number|allOptions.edges.arrows.middle.scaleFactor|{number}|Array), middleArrowType: (allOptions.edges.arrows.middle.type|{string}|string|*), fromArrow: boolean, fromArrowScale: (allOptions.edges.arrows.to.scaleFactor|{number}|allOptions.edges.arrows.middle.scaleFactor|allOptions.edges.arrows.from.scaleFactor|Array|number), fromArrowType: *, arrowStrikethrough: (*|boolean|allOptions.edges.arrowStrikethrough|{boolean}), color: undefined, inheritsColor: (string|string|string|allOptions.edges.color.inherit|{string, boolean}|Array|*), opacity: *, hidden: *, length: *, shadow: *, shadowColor: *, shadowSize: *, shadowX: *, shadowY: *, dashes: (*|boolean|Array|allOptions.edges.dashes|{boolean, array}), width: *}} values
  537. */
  538. disableShadow(ctx, values) {
  539. if (values.shadow === true) {
  540. ctx.shadowColor = 'rgba(0,0,0,0)';
  541. ctx.shadowBlur = 0;
  542. ctx.shadowOffsetX = 0;
  543. ctx.shadowOffsetY = 0;
  544. }
  545. }
  546. }
  547. export default EdgeBase;