@krainovsd/graph 0.12.0-beta.3 → 0.12.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/lib/cjs/index.cjs +303 -270
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/esm/constants/link-controls.js +0 -6
  4. package/lib/esm/constants/link-controls.js.map +1 -1
  5. package/lib/esm/constants/node-controls.js +71 -24
  6. package/lib/esm/constants/node-controls.js.map +1 -1
  7. package/lib/esm/index.js +1 -1
  8. package/lib/esm/module/GraphCanvas/GraphCanvas.js +15 -17
  9. package/lib/esm/module/GraphCanvas/GraphCanvas.js.map +1 -1
  10. package/lib/esm/module/GraphCanvas/constants/link-settings.js +0 -1
  11. package/lib/esm/module/GraphCanvas/constants/link-settings.js.map +1 -1
  12. package/lib/esm/module/GraphCanvas/constants/node-settings.js +10 -5
  13. package/lib/esm/module/GraphCanvas/constants/node-settings.js.map +1 -1
  14. package/lib/esm/module/GraphCanvas/lib/settings/node-settings-getter.js +20 -4
  15. package/lib/esm/module/GraphCanvas/lib/settings/node-settings-getter.js.map +1 -1
  16. package/lib/esm/module/GraphCanvas/lib/utils/is-overlaps-node.js +1 -1
  17. package/lib/esm/module/GraphCanvas/lib/utils/is-overlaps-node.js.map +1 -1
  18. package/lib/esm/module/GraphCanvas/lib/utils/link-by-pointer-getter.js +4 -1
  19. package/lib/esm/module/GraphCanvas/lib/utils/link-by-pointer-getter.js.map +1 -1
  20. package/lib/esm/module/GraphCanvas/slices/draw-links.js +7 -20
  21. package/lib/esm/module/GraphCanvas/slices/draw-links.js.map +1 -1
  22. package/lib/esm/module/GraphCanvas/slices/draw-nodes.js +29 -117
  23. package/lib/esm/module/GraphCanvas/slices/draw-nodes.js.map +1 -1
  24. package/lib/esm/module/GraphCanvas/slices/draw-text.js +1 -24
  25. package/lib/esm/module/GraphCanvas/slices/draw-text.js.map +1 -1
  26. package/lib/esm/module/GraphCanvas/slices/init-simulation.js +7 -55
  27. package/lib/esm/module/GraphCanvas/slices/init-simulation.js.map +1 -1
  28. package/lib/esm/module/GraphCanvas/slices/init-zoom.js +4 -2
  29. package/lib/esm/module/GraphCanvas/slices/init-zoom.js.map +1 -1
  30. package/lib/esm/module/GraphCanvas/slices/update-link-cache.js +19 -0
  31. package/lib/esm/module/GraphCanvas/slices/update-link-cache.js.map +1 -0
  32. package/lib/esm/module/GraphCanvas/slices/update-node-cache.js +136 -0
  33. package/lib/esm/module/GraphCanvas/slices/update-node-cache.js.map +1 -0
  34. package/lib/index.d.ts +28 -28
  35. package/package.json +1 -1
package/lib/cjs/index.cjs CHANGED
@@ -113,7 +113,6 @@ const HIGHLIGHT_SETTINGS = {
113
113
  };
114
114
 
115
115
  const LINK_SETTINGS = {
116
- cacheOptions: true,
117
116
  prettyDraw: true,
118
117
  linkScaleSwitch: 1,
119
118
  linkColorAfterScaleSwitch: "#000000FF",
@@ -142,13 +141,18 @@ const LINK_OPTIONS = {
142
141
  };
143
142
 
144
143
  const NODE_SETTINGS = {
145
- cacheOptions: true,
146
144
  nodeRadiusFlexible: true,
147
- nodeRadiusCoefficient: 5,
148
- nodeRadiusFactor: 1,
145
+ nodeRadiusLinkCountForStep: 5,
146
+ nodeRadiusIncrementByStep: 1,
147
+ nodeRadiusMaxLinearSteps: 5,
148
+ nodeRadiusLinkCountDividerForLog: 25,
149
+ nodeRadiusLogFactor: 2.5,
149
150
  nodeSizeFlexible: true,
150
- nodeSizeCoefficient: 5,
151
- nodeSizeFactor: 0.1,
151
+ nodeSizeLinkCountForStep: 5,
152
+ nodeSizeIncrementByStep: 0.1,
153
+ nodeSizeMaxLinearSteps: 5,
154
+ nodeSizeLinkCountDividerForLog: 25,
155
+ nodeSizeLogFactor: 2.5,
152
156
  textScaleMin: 1.5,
153
157
  textScaleMax: 18,
154
158
  textSizeMin: 3.5,
@@ -378,13 +382,29 @@ function nodeTextSizeGetter(transform, nodeSettings) {
378
382
  }
379
383
  return { textSize, textShiftY };
380
384
  }
381
- function nodeRadiusGetter({ radiusFlexible, radiusInitial, linkCount, radiusCoefficient, radiusFactor, }) {
382
- return ((radiusFlexible && linkCount ? linkCount / radiusCoefficient : 0) * radiusFactor + radiusInitial);
385
+ function nodeRadiusGetter({ radiusFlexible, linkCount, radiusInitial, radiusIncrementByStep, radiusLinkCountDividerForLog, radiusLinkCountForStep, radiusLogFactor, radiusMaxLinearSteps, }) {
386
+ if (!radiusFlexible || !linkCount)
387
+ return radiusInitial;
388
+ const steps = linkCount / radiusLinkCountForStep;
389
+ if (steps < radiusMaxLinearSteps) {
390
+ return steps * radiusIncrementByStep + radiusInitial;
391
+ }
392
+ return (radiusInitial +
393
+ radiusMaxLinearSteps * radiusIncrementByStep +
394
+ radiusLogFactor * Math.log10(linkCount / radiusLinkCountDividerForLog));
383
395
  }
384
- function nodeSizeGetter({ heightInitial, linkCount, sizeCoefficient, sizeFactor, sizeFlexible, widthInitial, }) {
396
+ function nodeSizeGetter({ heightInitial, linkCount, sizeFlexible, widthInitial, sizeIncrementByStep, sizeLinkCountDividerForLog, sizeLinkCountForStep, sizeLogFactor, sizeMaxLinearSteps, }) {
385
397
  let additionalSizeCoefficient = 1;
386
398
  if (sizeFlexible && linkCount != undefined) {
387
- additionalSizeCoefficient += (linkCount / sizeCoefficient) * sizeFactor;
399
+ const steps = linkCount / sizeLinkCountForStep;
400
+ if (steps < sizeMaxLinearSteps) {
401
+ additionalSizeCoefficient += steps * sizeIncrementByStep;
402
+ }
403
+ else {
404
+ additionalSizeCoefficient +=
405
+ sizeMaxLinearSteps * sizeIncrementByStep +
406
+ sizeLogFactor * Math.log10(linkCount / sizeLinkCountDividerForLog);
407
+ }
388
408
  }
389
409
  return {
390
410
  width: widthInitial * additionalSizeCoefficient,
@@ -416,7 +436,7 @@ function pointerGetter(mouseEvent, areaRect, areaTransform) {
416
436
  }
417
437
 
418
438
  function isOverlapsNode(node, pointerX, pointerY, radius) {
419
- if (node.x == undefined || node.y == undefined)
439
+ if (node.x == undefined || node.y == undefined || node.visible === false)
420
440
  return false;
421
441
  switch (node._shape) {
422
442
  case "circle": {
@@ -912,7 +932,10 @@ function linkByPointerGetter({ areaRect, areaTransform, mouseEvent, links, linkH
912
932
  });
913
933
  }
914
934
  function isNearLink(mouseX, mouseY, link, threshold = 2) {
915
- if (!jsHelpers.isObject(link.source) || !jsHelpers.isObject(link.target))
935
+ if (!jsHelpers.isObject(link.source) ||
936
+ !jsHelpers.isObject(link.target) ||
937
+ link.source.visible === false ||
938
+ link.target.visible === false)
916
939
  return false;
917
940
  const x1 = link.source.x;
918
941
  const y1 = link.source.y;
@@ -1205,7 +1228,7 @@ function initDnd() {
1205
1228
  }
1206
1229
 
1207
1230
  function getDrawLink() {
1208
- return function drawLink(link, index) {
1231
+ return function drawLink(link) {
1209
1232
  if (!this.context ||
1210
1233
  typeof link.source !== "object" ||
1211
1234
  typeof link.target !== "object" ||
@@ -1221,16 +1244,9 @@ function getDrawLink() {
1221
1244
  if (!link.source._visible && !link.target._visible)
1222
1245
  return;
1223
1246
  const id = `${link.target.id}${link.source.id}`;
1224
- let linkOptions;
1225
- if (this.linkSettings.cacheOptions && this.linkOptionsCache[id]) {
1226
- linkOptions = this.linkOptionsCache[id];
1227
- }
1228
- else {
1229
- linkOptions = linkIterationExtractor(link, index, this.links, this, this.linkSettings.options ?? {}, linkOptionsGetter);
1230
- if (this.linkSettings.cacheOptions) {
1231
- this.linkOptionsCache[id] = linkOptions;
1232
- }
1233
- }
1247
+ const linkOptions = this.linkOptionsCache[id];
1248
+ if (!linkOptions)
1249
+ return;
1234
1250
  if (linkOptions.drawLink) {
1235
1251
  linkOptions.drawLink.call(this, link, linkOptions);
1236
1252
  return;
@@ -1327,7 +1343,9 @@ function getDrawLink() {
1327
1343
  let xEnd = link.target.x;
1328
1344
  let yEnd = link.target.y;
1329
1345
  let linkDistance = 0;
1330
- if (this.linkSettings.prettyDraw || this.linkSettings.particleFlexSpeed) {
1346
+ if (this.linkSettings.prettyDraw ||
1347
+ this.linkSettings.particleFlexSpeed ||
1348
+ (this.linkSettings.arrow && arrowAlpha > 0)) {
1331
1349
  const isHasArrow = this.linkSettings.arrow && arrowAlpha > 0;
1332
1350
  const position = calculateLinkPositionByNode(link, isHasArrow ? arrowSize : 0);
1333
1351
  if (position) {
@@ -1408,12 +1426,6 @@ function getDrawLink() {
1408
1426
  }
1409
1427
  /** Arrow */
1410
1428
  if (this.linkSettings.arrow && arrowAlpha > 0) {
1411
- const { x1: xStart, x2: xEnd, y1: yStart, y2: yEnd, } = calculateLinkPositionByNode(link) ?? {
1412
- x1: 0,
1413
- x2: 0,
1414
- y1: 0,
1415
- y2: 0,
1416
- };
1417
1429
  this.context.beginPath();
1418
1430
  this.context.globalAlpha = arrowAlpha;
1419
1431
  this.context.strokeStyle = arrowBorderColor;
@@ -1436,33 +1448,10 @@ function getDrawLink() {
1436
1448
  }
1437
1449
 
1438
1450
  const SPACE = " ";
1439
- function drawText({ context, id, textAlign, textColor, textFont, textStyle, textGap, textWeight, textSize, text, x, y, cachedNodeText, maxWidth, }) {
1451
+ function drawText({ context, textAlign, textColor, textFont, textStyle, textGap, textWeight, textSize, x, y, lines, }) {
1440
1452
  context.font = `${textStyle} normal ${textWeight} ${textSize}px ${textFont}`;
1441
1453
  context.fillStyle = textColor;
1442
1454
  context.textAlign = textAlign;
1443
- if (cachedNodeText[id] != undefined) {
1444
- cachedNodeText[id].forEach((line, index) => {
1445
- context.fillText(line, x, y + index * textSize + index * textGap);
1446
- });
1447
- return;
1448
- }
1449
- if (maxWidth == undefined || context.measureText(text).width <= maxWidth) {
1450
- cachedNodeText[id] = [text];
1451
- context.fillText(text, x, y);
1452
- return;
1453
- }
1454
- const { lines } = getTextLines({
1455
- context,
1456
- maxWidth,
1457
- text,
1458
- textAlign,
1459
- textColor,
1460
- textFont,
1461
- textSize,
1462
- textStyle,
1463
- textWeight,
1464
- });
1465
- cachedNodeText[id] = lines;
1466
1455
  lines.forEach((line, index) => {
1467
1456
  context.fillText(line, x, y + index * textSize + index * textGap);
1468
1457
  });
@@ -1504,21 +1493,18 @@ function getTextLines({ context, textAlign, textColor, textFont, textStyle, text
1504
1493
  }
1505
1494
 
1506
1495
  function getDrawNode(nodeRenders, textRenders) {
1507
- return function drawNode(node, index) {
1496
+ return function drawNode(node) {
1508
1497
  if (!this.context || !node.x || !node.y)
1509
1498
  return;
1510
- if (node.visible != undefined && !node.visible)
1499
+ if (node.visible != undefined && !node.visible) {
1500
+ node._radius = 0;
1501
+ node._width = 0;
1502
+ node._height = 0;
1511
1503
  return;
1512
- let nodeOptions;
1513
- if (this.nodeSettings.cacheOptions && this.nodeOptionsCache[node.id]) {
1514
- nodeOptions = this.nodeOptionsCache[node.id];
1515
- }
1516
- else {
1517
- nodeOptions = nodeIterationExtractor(node, index, this.nodes, this, this.nodeSettings.options ?? {}, nodeOptionsGetter);
1518
- if (this.nodeSettings.cacheOptions) {
1519
- this.nodeOptionsCache[node.id] = nodeOptions;
1520
- }
1521
1504
  }
1505
+ const nodeOptions = this.nodeOptionsCache[node.id];
1506
+ if (!nodeOptions)
1507
+ return;
1522
1508
  if (nodeOptions.nodeDraw && nodeOptions.textDraw) {
1523
1509
  nodeRenders.push(() => {
1524
1510
  if (nodeOptions.nodeDraw) {
@@ -1532,13 +1518,13 @@ function getDrawNode(nodeRenders, textRenders) {
1532
1518
  });
1533
1519
  return;
1534
1520
  }
1521
+ let radius = nodeOptions.radius;
1522
+ let width = nodeOptions.width;
1523
+ let height = nodeOptions.height;
1535
1524
  let alpha = nodeOptions.alpha;
1536
1525
  let color = nodeOptions.color;
1537
1526
  let borderColor = nodeOptions.borderColor;
1538
1527
  let borderWidth = nodeOptions.borderWidth;
1539
- let radiusInitial = nodeOptions.radius;
1540
- let widthInitial = nodeOptions.width;
1541
- let heightInitial = nodeOptions.height;
1542
1528
  let textAlpha = nodeOptions.textAlpha;
1543
1529
  let textSize = nodeOptions.textSize;
1544
1530
  let textShiftX = nodeOptions.textShiftX;
@@ -1585,9 +1571,9 @@ function getDrawNode(nodeRenders, textRenders) {
1585
1571
  color = highlightOptions.color;
1586
1572
  borderColor = highlightOptions.borderColor;
1587
1573
  borderWidth = highlightOptions.borderWidth;
1588
- radiusInitial = highlightOptions.radiusInitial;
1589
- widthInitial = highlightOptions.widthInitial;
1590
- heightInitial = highlightOptions.heightInitial;
1574
+ radius = highlightOptions.radiusInitial;
1575
+ width = highlightOptions.widthInitial;
1576
+ height = highlightOptions.heightInitial;
1591
1577
  textSize = highlightOptions.textSize;
1592
1578
  textShiftX = highlightOptions.textShiftX;
1593
1579
  textShiftY = highlightOptions.textShiftY;
@@ -1634,9 +1620,9 @@ function getDrawNode(nodeRenders, textRenders) {
1634
1620
  color = highlightOptions.color;
1635
1621
  borderColor = highlightOptions.borderColor;
1636
1622
  borderWidth = highlightOptions.borderWidth;
1637
- radiusInitial = highlightOptions.radiusInitial;
1638
- widthInitial = highlightOptions.widthInitial;
1639
- heightInitial = highlightOptions.heightInitial;
1623
+ radius = highlightOptions.radiusInitial;
1624
+ width = highlightOptions.widthInitial;
1625
+ height = highlightOptions.heightInitial;
1640
1626
  textSize = highlightOptions.textSize;
1641
1627
  textShiftX = highlightOptions.textShiftX;
1642
1628
  textShiftY = highlightOptions.textShiftY;
@@ -1645,78 +1631,6 @@ function getDrawNode(nodeRenders, textRenders) {
1645
1631
  labelWeight = highlightOptions.labelWeight;
1646
1632
  }
1647
1633
  }
1648
- /** Flex radius */
1649
- const radius = nodeOptions.shape === "circle"
1650
- ? nodeRadiusGetter({
1651
- radiusFlexible: this.nodeSettings.nodeRadiusFlexible,
1652
- radiusInitial,
1653
- radiusCoefficient: this.nodeSettings.nodeRadiusCoefficient,
1654
- radiusFactor: this.nodeSettings.nodeRadiusFactor,
1655
- linkCount: node.linkCount,
1656
- })
1657
- : radiusInitial;
1658
- /** Flex size */
1659
- let height = heightInitial;
1660
- let width = widthInitial;
1661
- if (nodeOptions.shape === "square") {
1662
- const size = nodeSizeGetter({
1663
- heightInitial,
1664
- widthInitial,
1665
- linkCount: node.linkCount,
1666
- sizeCoefficient: this.nodeSettings.nodeSizeCoefficient,
1667
- sizeFactor: this.nodeSettings.nodeSizeFactor,
1668
- sizeFlexible: this.nodeSettings.nodeSizeFlexible,
1669
- });
1670
- width = size.width;
1671
- height = size.height;
1672
- }
1673
- if (nodeOptions.shape === "text") {
1674
- width = nodeOptions.width;
1675
- const size = nodeSizeGetter({
1676
- heightInitial,
1677
- widthInitial: width,
1678
- linkCount: node.linkCount,
1679
- sizeCoefficient: this.nodeSettings.nodeSizeCoefficient,
1680
- sizeFactor: this.nodeSettings.nodeSizeFactor,
1681
- sizeFlexible: this.nodeSettings.nodeSizeFlexible,
1682
- });
1683
- labelSize *= size.additionalSizeCoefficient;
1684
- }
1685
- /** Size by text in textNode */
1686
- if (nodeOptions.shape === "text" && nodeOptions.label) {
1687
- let lines;
1688
- let textNodeParameters;
1689
- const cachedLines = this.cachedNodeLabel[node.id];
1690
- const cachedTextNodeParameters = this.cachedTextNodeParameters[node.id];
1691
- if (cachedLines != undefined && cachedTextNodeParameters != undefined) {
1692
- lines = cachedLines;
1693
- textNodeParameters = cachedTextNodeParameters;
1694
- }
1695
- else {
1696
- const textInfo = getTextLines({
1697
- context: this.context,
1698
- text: nodeOptions.label,
1699
- textAlign: nodeOptions.labelAlign,
1700
- textColor: nodeOptions.labelColor,
1701
- textFont: nodeOptions.labelFont,
1702
- textSize: labelSize,
1703
- maxWidth: nodeOptions.labelWidth,
1704
- textStyle: nodeOptions.labelStyle,
1705
- textWeight,
1706
- });
1707
- textNodeParameters = [textInfo.currentMaxSize, labelSize];
1708
- lines = textInfo.lines;
1709
- this.cachedNodeLabel[node.id] = lines;
1710
- this.cachedTextNodeParameters[node.id] = textNodeParameters;
1711
- }
1712
- const textSizeCoefficient = labelSize / textNodeParameters[1];
1713
- const maxSize = textNodeParameters[0] * textSizeCoefficient;
1714
- height =
1715
- lines.length * labelSize +
1716
- (lines.length - 1) * nodeOptions.labelGap +
1717
- nodeOptions.labelYPadding;
1718
- width = maxSize + nodeOptions.labelXPadding;
1719
- }
1720
1634
  /** Node parameters */
1721
1635
  node._radius = radius;
1722
1636
  node._borderWidth = borderWidth;
@@ -1747,6 +1661,7 @@ function getDrawNode(nodeRenders, textRenders) {
1747
1661
  this.context.lineWidth = borderWidth;
1748
1662
  this.context.strokeStyle = borderColor;
1749
1663
  this.context.fillStyle = color;
1664
+ const labelLines = this.cachedNodeLabel[node.id];
1750
1665
  switch (nodeOptions.shape) {
1751
1666
  case "circle": {
1752
1667
  if (!node.image) {
@@ -1767,13 +1682,11 @@ function getDrawNode(nodeRenders, textRenders) {
1767
1682
  this.context.stroke();
1768
1683
  }
1769
1684
  }
1770
- if (node.label) {
1685
+ if (node.label && labelLines) {
1771
1686
  this.context.globalAlpha = labelAlpha;
1772
1687
  drawText({
1773
1688
  context: this.context,
1774
- cachedNodeText: this.cachedNodeLabel,
1775
- id: node.id,
1776
- text: node.label,
1689
+ lines: labelLines,
1777
1690
  textAlign: nodeOptions.labelAlign,
1778
1691
  textColor: nodeOptions.labelColor,
1779
1692
  textFont: nodeOptions.labelFont,
@@ -1781,7 +1694,6 @@ function getDrawNode(nodeRenders, textRenders) {
1781
1694
  textSize: labelSize,
1782
1695
  textStyle: nodeOptions.labelStyle,
1783
1696
  textWeight: labelWeight,
1784
- maxWidth: nodeOptions.labelWidth,
1785
1697
  x: node.x,
1786
1698
  y: node.y + labelSize / 3,
1787
1699
  });
@@ -1807,13 +1719,11 @@ function getDrawNode(nodeRenders, textRenders) {
1807
1719
  this.context.stroke();
1808
1720
  }
1809
1721
  }
1810
- if (node.label) {
1722
+ if (node.label && labelLines) {
1811
1723
  this.context.globalAlpha = labelAlpha;
1812
1724
  drawText({
1813
1725
  context: this.context,
1814
- cachedNodeText: this.cachedNodeLabel,
1815
- id: node.id,
1816
- text: node.label,
1726
+ lines: labelLines,
1817
1727
  textAlign: nodeOptions.labelAlign,
1818
1728
  textColor: nodeOptions.labelColor,
1819
1729
  textFont: nodeOptions.labelFont,
@@ -1821,7 +1731,6 @@ function getDrawNode(nodeRenders, textRenders) {
1821
1731
  textSize: labelSize,
1822
1732
  textStyle: nodeOptions.labelStyle,
1823
1733
  textWeight: labelWeight,
1824
- maxWidth: nodeOptions.labelWidth,
1825
1734
  x: node.x,
1826
1735
  y: node.y + labelSize / 3,
1827
1736
  });
@@ -1832,20 +1741,16 @@ function getDrawNode(nodeRenders, textRenders) {
1832
1741
  if (this.nodeSettings.textNodeDebug) {
1833
1742
  this.context.strokeRect(node.x - width / 2, node.y - height / 2, width, height);
1834
1743
  }
1835
- const lines = this.cachedNodeLabel[node.id];
1836
- if (nodeOptions.label && lines)
1744
+ if (nodeOptions.label && labelLines)
1837
1745
  drawText({
1838
- id: node.id,
1839
- cachedNodeText: this.cachedNodeLabel,
1746
+ lines: labelLines,
1840
1747
  context: this.context,
1841
- text: nodeOptions.label,
1842
1748
  textAlign: nodeOptions.labelAlign,
1843
1749
  textColor: nodeOptions.labelColor,
1844
1750
  textFont: nodeOptions.labelFont,
1845
1751
  textSize: labelSize,
1846
1752
  x: node.x,
1847
- y: node.y + textSize / 4 - (lines.length - 1) * (textSize / 2),
1848
- maxWidth: nodeOptions.labelWidth,
1753
+ y: node.y + textSize / 4 - (labelLines.length - 1) * (textSize / 2),
1849
1754
  textStyle: nodeOptions.labelStyle,
1850
1755
  textWeight,
1851
1756
  textGap: nodeOptions.labelGap,
@@ -1882,7 +1787,8 @@ function getDrawNode(nodeRenders, textRenders) {
1882
1787
  });
1883
1788
  }
1884
1789
  /** Text draw */
1885
- if (nodeOptions.textVisible && nodeOptions.text) {
1790
+ const textLines = this.cachedNodeText[node.id];
1791
+ if (nodeOptions.textVisible && nodeOptions.text && textLines) {
1886
1792
  textRenders.push(() => {
1887
1793
  if (nodeOptions.textDraw) {
1888
1794
  nodeOptions.textDraw.bind(this)(node, {
@@ -1910,17 +1816,14 @@ function getDrawNode(nodeRenders, textRenders) {
1910
1816
  y += height / 2;
1911
1817
  }
1912
1818
  drawText({
1913
- id: node.id,
1914
- cachedNodeText: this.cachedNodeText,
1819
+ lines: textLines,
1915
1820
  context: this.context,
1916
- text: nodeOptions.text,
1917
1821
  textAlign: nodeOptions.textAlign,
1918
1822
  textColor: nodeOptions.textColor,
1919
1823
  textFont: nodeOptions.textFont,
1920
1824
  textSize,
1921
1825
  x: node.x + textShiftX,
1922
1826
  y,
1923
- maxWidth: nodeOptions.textWidth,
1924
1827
  textStyle: nodeOptions.textStyle,
1925
1828
  textWeight,
1926
1829
  textGap: nodeOptions.textGap,
@@ -2340,75 +2243,29 @@ function initCollideForce(forceUpdate) {
2340
2243
  else if (!this.simulation.force("collide") || forceUpdate) {
2341
2244
  this.simulation.force("collide", d3Force.forceCollide()
2342
2245
  .radius((node, index) => {
2343
- const nodeOptions = nodeIterationExtractor(node, index, this.nodes, this, this.nodeSettings.options ?? {}, nodeOptionsGetter);
2246
+ const nodeOptions = this.nodeOptionsCache[node.id];
2247
+ if (!nodeOptions)
2248
+ return 0;
2344
2249
  switch (nodeOptions.shape) {
2345
2250
  case "circle": {
2346
2251
  if (this.forceSettings.collideRadius) {
2347
2252
  return nodeIterationExtractor(node, index, this.nodes, this, this.forceSettings.collideRadius, undefined);
2348
2253
  }
2349
- const radius = nodeRadiusGetter({
2350
- radiusFlexible: this.nodeSettings.nodeRadiusFlexible,
2351
- radiusInitial: nodeOptions.radius,
2352
- radiusCoefficient: this.nodeSettings.nodeRadiusCoefficient,
2353
- radiusFactor: this.nodeSettings.nodeRadiusFactor,
2354
- linkCount: node.linkCount,
2355
- });
2356
- return radius + this.forceSettings.collideAdditionalRadius;
2254
+ return nodeOptions.radius + this.forceSettings.collideAdditionalRadius;
2357
2255
  }
2358
2256
  case "square": {
2359
- const { height, width } = nodeSizeGetter({
2360
- heightInitial: nodeOptions.width,
2361
- widthInitial: nodeOptions.height,
2362
- linkCount: node.linkCount,
2363
- sizeCoefficient: this.nodeSettings.nodeSizeCoefficient,
2364
- sizeFactor: this.nodeSettings.nodeSizeFactor,
2365
- sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2366
- });
2367
- return (Math.sqrt(width ** 2 + height ** 2) / 2 +
2257
+ return (Math.sqrt(nodeOptions.width ** 2 + nodeOptions.height ** 2) / 2 +
2368
2258
  this.forceSettings.collideAdditionalRadius);
2369
2259
  }
2370
2260
  case "text": {
2371
- let { height, width } = nodeSizeGetter({
2372
- heightInitial: nodeOptions.width,
2373
- widthInitial: nodeOptions.height,
2374
- linkCount: node.linkCount,
2375
- sizeCoefficient: this.nodeSettings.nodeSizeCoefficient,
2376
- sizeFactor: this.nodeSettings.nodeSizeFactor,
2377
- sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2378
- });
2379
- if (this.context && nodeOptions.text) {
2380
- const textInfo = getTextLines({
2381
- context: this.context,
2382
- text: nodeOptions.text,
2383
- textAlign: nodeOptions.textAlign,
2384
- textColor: nodeOptions.textColor,
2385
- textFont: nodeOptions.textFont,
2386
- textSize: nodeOptions.textSize,
2387
- maxWidth: width,
2388
- textStyle: nodeOptions.textStyle,
2389
- textWeight: nodeOptions.textWeight,
2390
- });
2391
- height =
2392
- textInfo.lines.length * nodeOptions.textSize +
2393
- (textInfo.lines.length - 1) * nodeOptions.textGap +
2394
- nodeOptions.labelYPadding;
2395
- width = textInfo.currentMaxSize + nodeOptions.labelXPadding;
2396
- }
2397
- return (Math.sqrt(width ** 2 + height ** 2) / 2 +
2261
+ return (Math.sqrt(nodeOptions.width ** 2 + nodeOptions.height ** 2) / 2 +
2398
2262
  this.forceSettings.collideAdditionalRadius);
2399
2263
  }
2400
2264
  default: {
2401
2265
  if (this.forceSettings.collideRadius) {
2402
2266
  return nodeIterationExtractor(node, index, this.nodes, this, this.forceSettings.collideRadius, undefined);
2403
2267
  }
2404
- const radius = nodeRadiusGetter({
2405
- radiusFlexible: this.nodeSettings.nodeRadiusFlexible,
2406
- radiusInitial: nodeOptions.radius,
2407
- radiusCoefficient: this.nodeSettings.nodeRadiusCoefficient,
2408
- radiusFactor: this.nodeSettings.nodeRadiusFactor,
2409
- linkCount: node.linkCount,
2410
- });
2411
- return radius + this.forceSettings.collideAdditionalRadius;
2268
+ return nodeOptions.radius + this.forceSettings.collideAdditionalRadius;
2412
2269
  }
2413
2270
  }
2414
2271
  })
@@ -2418,6 +2275,145 @@ function initCollideForce(forceUpdate) {
2418
2275
  }
2419
2276
  }
2420
2277
 
2278
+ function updateLinkCache() {
2279
+ this.linkOptionsCache = {};
2280
+ for (let i = 0; i < this.links.length; i++) {
2281
+ const link = this.links[i];
2282
+ const { sourceId, targetId } = extractLinkPointIds(link);
2283
+ const linkOptions = linkIterationExtractor(link, i, this.links, this, this.linkSettings.options ?? {}, linkOptionsGetter);
2284
+ const id = `${targetId}${sourceId}`;
2285
+ this.linkOptionsCache[id] = linkOptions;
2286
+ }
2287
+ }
2288
+
2289
+ function updateNodeCache() {
2290
+ this.nodeOptionsCache = {};
2291
+ if (!this.context)
2292
+ return;
2293
+ for (let i = 0; i < this.nodes.length; i++) {
2294
+ const node = this.nodes[i];
2295
+ const nodeOptions = nodeIterationExtractor(node, i, this.nodes, this, this.nodeSettings.options ?? {}, nodeOptionsGetter);
2296
+ const radius = nodeOptions.shape === "circle"
2297
+ ? nodeRadiusGetter({
2298
+ radiusFlexible: this.nodeSettings.nodeRadiusFlexible,
2299
+ radiusInitial: nodeOptions.radius,
2300
+ radiusIncrementByStep: this.nodeSettings.nodeRadiusIncrementByStep,
2301
+ radiusLinkCountForStep: this.nodeSettings.nodeRadiusLinkCountForStep,
2302
+ radiusMaxLinearSteps: this.nodeSettings.nodeRadiusMaxLinearSteps,
2303
+ radiusLogFactor: this.nodeSettings.nodeRadiusLogFactor,
2304
+ radiusLinkCountDividerForLog: this.nodeSettings.nodeRadiusLinkCountDividerForLog,
2305
+ linkCount: node.linkCount,
2306
+ })
2307
+ : nodeOptions.radius;
2308
+ let width = nodeOptions.width;
2309
+ let height = nodeOptions.height;
2310
+ let labelSize = nodeOptions.labelSize;
2311
+ if (nodeOptions.shape === "square") {
2312
+ const size = nodeSizeGetter({
2313
+ heightInitial: nodeOptions.height,
2314
+ widthInitial: nodeOptions.width,
2315
+ linkCount: node.linkCount,
2316
+ sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2317
+ sizeIncrementByStep: this.nodeSettings.nodeSizeIncrementByStep,
2318
+ sizeLinkCountForStep: this.nodeSettings.nodeSizeLinkCountForStep,
2319
+ sizeMaxLinearSteps: this.nodeSettings.nodeSizeMaxLinearSteps,
2320
+ sizeLogFactor: this.nodeSettings.nodeSizeLogFactor,
2321
+ sizeLinkCountDividerForLog: this.nodeSettings.nodeSizeLinkCountDividerForLog,
2322
+ });
2323
+ width = size.width;
2324
+ height = size.height;
2325
+ }
2326
+ if (nodeOptions.shape === "text") {
2327
+ width = nodeOptions.width;
2328
+ const size = nodeSizeGetter({
2329
+ heightInitial: nodeOptions.height,
2330
+ widthInitial: width,
2331
+ linkCount: node.linkCount,
2332
+ sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2333
+ sizeIncrementByStep: this.nodeSettings.nodeSizeIncrementByStep,
2334
+ sizeLinkCountForStep: this.nodeSettings.nodeSizeLinkCountForStep,
2335
+ sizeMaxLinearSteps: this.nodeSettings.nodeSizeMaxLinearSteps,
2336
+ sizeLogFactor: this.nodeSettings.nodeSizeLogFactor,
2337
+ sizeLinkCountDividerForLog: this.nodeSettings.nodeSizeLinkCountDividerForLog,
2338
+ });
2339
+ labelSize *= size.additionalSizeCoefficient;
2340
+ }
2341
+ /** label in not text shape */
2342
+ if (nodeOptions.shape !== "text" && nodeOptions.label) {
2343
+ this.context.font = `${nodeOptions.labelStyle} normal ${nodeOptions.labelWeight} ${nodeOptions.labelSize}px ${nodeOptions.labelFont}`;
2344
+ this.context.fillStyle = nodeOptions.labelColor;
2345
+ this.context.textAlign = nodeOptions.labelAlign;
2346
+ if (nodeOptions.labelWidth == undefined ||
2347
+ this.context.measureText(nodeOptions.label).width <= nodeOptions.labelWidth) {
2348
+ this.cachedNodeLabel[node.id] = [nodeOptions.label];
2349
+ }
2350
+ const { lines } = getTextLines({
2351
+ context: this.context,
2352
+ maxWidth: nodeOptions.labelWidth,
2353
+ text: nodeOptions.label,
2354
+ textAlign: nodeOptions.labelAlign,
2355
+ textColor: nodeOptions.labelColor,
2356
+ textFont: nodeOptions.labelFont,
2357
+ textSize: nodeOptions.labelSize,
2358
+ textStyle: nodeOptions.labelStyle,
2359
+ textWeight: nodeOptions.labelWeight,
2360
+ });
2361
+ this.cachedNodeLabel[node.id] = lines;
2362
+ }
2363
+ /** label in text shape */
2364
+ if (nodeOptions.shape === "text" && nodeOptions.label) {
2365
+ const textInfo = getTextLines({
2366
+ context: this.context,
2367
+ text: nodeOptions.label,
2368
+ textAlign: nodeOptions.labelAlign,
2369
+ textColor: nodeOptions.labelColor,
2370
+ textFont: nodeOptions.labelFont,
2371
+ textSize: labelSize,
2372
+ maxWidth: nodeOptions.labelWidth,
2373
+ textStyle: nodeOptions.labelStyle,
2374
+ textWeight: nodeOptions.textWeight,
2375
+ });
2376
+ const textNodeParameters = [textInfo.currentMaxSize, labelSize];
2377
+ const lines = textInfo.lines;
2378
+ const textSizeCoefficient = labelSize / textNodeParameters[1];
2379
+ const maxSize = textNodeParameters[0] * textSizeCoefficient;
2380
+ height =
2381
+ lines.length * labelSize +
2382
+ (lines.length - 1) * nodeOptions.labelGap +
2383
+ nodeOptions.labelYPadding;
2384
+ width = maxSize + nodeOptions.labelXPadding;
2385
+ this.cachedNodeLabel[node.id] = lines;
2386
+ }
2387
+ nodeOptions.width = node.visible === false ? 0 : width;
2388
+ nodeOptions.height = node.visible === false ? 0 : height;
2389
+ nodeOptions.radius = node.visible === false ? 0 : radius;
2390
+ nodeOptions.labelSize = labelSize;
2391
+ /** text */
2392
+ if (nodeOptions.text) {
2393
+ this.context.font = `${nodeOptions.textStyle} normal ${nodeOptions.textWeight} ${nodeOptions.textSize}px ${nodeOptions.textFont}`;
2394
+ this.context.fillStyle = nodeOptions.textColor;
2395
+ this.context.textAlign = nodeOptions.textAlign;
2396
+ if (nodeOptions.textWidth == undefined ||
2397
+ this.context.measureText(nodeOptions.text).width <= nodeOptions.textWidth) {
2398
+ this.cachedNodeText[node.id] = [nodeOptions.text];
2399
+ }
2400
+ const { lines } = getTextLines({
2401
+ context: this.context,
2402
+ maxWidth: nodeOptions.textWidth,
2403
+ text: nodeOptions.text,
2404
+ textAlign: nodeOptions.textAlign,
2405
+ textColor: nodeOptions.textColor,
2406
+ textFont: nodeOptions.textFont,
2407
+ textSize: nodeOptions.textSize,
2408
+ textStyle: nodeOptions.textStyle,
2409
+ textWeight: nodeOptions.textWeight,
2410
+ });
2411
+ this.cachedNodeText[node.id] = lines;
2412
+ }
2413
+ this.nodeOptionsCache[node.id] = nodeOptions;
2414
+ }
2415
+ }
2416
+
2421
2417
  function initZoom(currentZoom) {
2422
2418
  if (!this.area)
2423
2419
  throw new Error("bad init data");
@@ -2426,8 +2422,8 @@ function initZoom(currentZoom) {
2426
2422
  .on("zoom", (event) => {
2427
2423
  this.listeners.onZoom?.call?.(this, event);
2428
2424
  this.areaTransform = event.transform;
2429
- this.linkOptionsCache = {};
2430
- this.nodeOptionsCache = {};
2425
+ updateLinkCache.call(this);
2426
+ updateNodeCache.call(this);
2431
2427
  if (!this.simulationWorking && !this.highlightWorking)
2432
2428
  requestAnimationFrame(() => this.draw());
2433
2429
  });
@@ -2474,7 +2470,6 @@ class GraphCanvas {
2474
2470
  eventAbortController;
2475
2471
  cachedNodeText = {};
2476
2472
  cachedNodeLabel = {};
2477
- cachedTextNodeParameters = {};
2478
2473
  linkOptionsCache = {};
2479
2474
  nodeOptionsCache = {};
2480
2475
  isDragging = false;
@@ -2537,26 +2532,19 @@ class GraphCanvas {
2537
2532
  if (options.highlightSettings) {
2538
2533
  this.highlightSettings = highlightSettingsGetter(options.highlightSettings, this.highlightSettings);
2539
2534
  if (clearCache) {
2540
- this.linkOptionsCache = {};
2541
- this.cachedNodeText = {};
2542
- this.cachedNodeLabel = {};
2543
- this.cachedTextNodeParameters = {};
2544
- this.nodeOptionsCache = {};
2535
+ this.clearCache(["nodeOptionsCache", "linkOptionsCache"]);
2545
2536
  }
2546
2537
  }
2547
2538
  if (options.linkSettings) {
2548
2539
  this.linkSettings = linkSettingsGetter(options.linkSettings, this.linkSettings);
2549
2540
  if (clearCache) {
2550
- this.linkOptionsCache = {};
2541
+ this.clearCache(["linkOptionsCache"]);
2551
2542
  }
2552
2543
  }
2553
2544
  if (options.nodeSettings) {
2554
2545
  this.nodeSettings = nodeSettingsGetter(options.nodeSettings, this.nodeSettings);
2555
2546
  if (clearCache) {
2556
- this.cachedNodeText = {};
2557
- this.cachedNodeLabel = {};
2558
- this.cachedTextNodeParameters = {};
2559
- this.nodeOptionsCache = {};
2547
+ this.clearCache(["nodeOptionsCache"]);
2560
2548
  }
2561
2549
  initCollideForce.call(this, true);
2562
2550
  }
@@ -2583,16 +2571,18 @@ class GraphCanvas {
2583
2571
  }
2584
2572
  clearCache(keys) {
2585
2573
  if (!keys) {
2586
- this.nodeOptionsCache = {};
2587
- this.linkOptionsCache = {};
2588
- this.cachedNodeText = {};
2589
- this.cachedNodeLabel = {};
2590
- this.cachedTextNodeParameters = {};
2574
+ updateNodeCache.call(this);
2575
+ updateLinkCache.call(this);
2591
2576
  }
2592
2577
  else {
2593
2578
  for (let i = 0; i < keys.length; i++) {
2594
2579
  const key = keys[i];
2595
- this[key] = {};
2580
+ if (key === "nodeOptionsCache") {
2581
+ updateNodeCache.call(this);
2582
+ }
2583
+ else {
2584
+ updateLinkCache.call(this);
2585
+ }
2596
2586
  }
2597
2587
  }
2598
2588
  }
@@ -2676,6 +2666,8 @@ class GraphCanvas {
2676
2666
  }
2677
2667
  init() {
2678
2668
  initArea.call(this);
2669
+ updateNodeCache.call(this);
2670
+ updateLinkCache.call(this);
2679
2671
  initSimulation.call(this);
2680
2672
  initDnd.call(this);
2681
2673
  initZoom.call(this);
@@ -3266,12 +3258,6 @@ const HIGHLIGHT_BY_LINK_FOR_ARROW_CONTROLS = [
3266
3258
  ];
3267
3259
 
3268
3260
  const LINK_SETTINGS_CONTROLS = [
3269
- {
3270
- id: "cacheOptions",
3271
- type: "checkbox",
3272
- label: "Кеширование вычисляемых настроек",
3273
- initialValue: LINK_SETTINGS.cacheOptions,
3274
- },
3275
3261
  {
3276
3262
  id: "prettyDraw",
3277
3263
  type: "checkbox",
@@ -3479,12 +3465,6 @@ const LINK_OPTIONS_PARTICLE_CONTROLS = [
3479
3465
  ];
3480
3466
 
3481
3467
  const NODE_SETTINGS_CONTROLS = [
3482
- {
3483
- id: "cacheOptions",
3484
- type: "checkbox",
3485
- initialValue: NODE_SETTINGS.cacheOptions,
3486
- label: "Кеширование вычисляемых настроек",
3487
- },
3488
3468
  {
3489
3469
  id: "textNodeDebug",
3490
3470
  type: "checkbox",
@@ -3495,49 +3475,103 @@ const NODE_SETTINGS_CONTROLS = [
3495
3475
  id: "nodeRadiusFlexible",
3496
3476
  type: "checkbox",
3497
3477
  initialValue: NODE_SETTINGS.nodeRadiusFlexible,
3498
- label: "Гибкий радиус (только для круга)",
3478
+ label: "Гибкий радиус",
3499
3479
  },
3500
3480
  {
3501
- id: "nodeRadiusCoefficient",
3502
- initialValue: NODE_SETTINGS.nodeRadiusCoefficient,
3481
+ id: "nodeRadiusLinkCountForStep",
3482
+ initialValue: NODE_SETTINGS.nodeRadiusLinkCountForStep,
3503
3483
  max: 100,
3504
3484
  min: 0.1,
3505
3485
  step: 0.1,
3506
3486
  type: "range",
3507
- label: "Количество связей для увеличения радиуса (только для круга)",
3487
+ label: "Количество связей для увеличения линейного шага радиуса",
3508
3488
  },
3509
3489
  {
3510
- id: "nodeRadiusFactor",
3511
- initialValue: NODE_SETTINGS.nodeRadiusFactor,
3490
+ id: "nodeRadiusIncrementByStep",
3491
+ initialValue: NODE_SETTINGS.nodeRadiusIncrementByStep,
3512
3492
  max: 50,
3513
3493
  min: 0.1,
3514
3494
  step: 0.1,
3515
3495
  type: "range",
3516
- label: "Коэффициент увеличения радиуса (только для круга)",
3496
+ label: "Размер шага линейного увеличения радиуса",
3497
+ },
3498
+ {
3499
+ id: "nodeRadiusMaxLinearSteps",
3500
+ initialValue: NODE_SETTINGS.nodeRadiusMaxLinearSteps,
3501
+ max: 50,
3502
+ min: 1,
3503
+ step: 1,
3504
+ type: "range",
3505
+ label: "Максимальное число шагов линейного увеличения радиуса",
3506
+ },
3507
+ {
3508
+ id: "nodeRadiusLogFactor",
3509
+ initialValue: NODE_SETTINGS.nodeRadiusLogFactor,
3510
+ max: 50,
3511
+ min: 1,
3512
+ step: 1,
3513
+ type: "range",
3514
+ label: "Число умножаемое на логарифм от количества связей для увеличения радиуса",
3515
+ },
3516
+ {
3517
+ id: "nodeRadiusLinkCountDividerForLog",
3518
+ initialValue: NODE_SETTINGS.nodeRadiusLinkCountDividerForLog,
3519
+ max: 50,
3520
+ min: 1,
3521
+ step: 1,
3522
+ type: "range",
3523
+ label: "Делитель количества связей для вычисления логарифма увеличения радиуса.",
3517
3524
  },
3518
3525
  {
3519
3526
  id: "nodeSizeFlexible",
3520
3527
  type: "checkbox",
3521
3528
  initialValue: NODE_SETTINGS.nodeSizeFlexible,
3522
- label: "Гибкий размер (только не для круга)",
3529
+ label: "Гибкий размер",
3523
3530
  },
3524
3531
  {
3525
- id: "nodeSizeCoefficient",
3526
- initialValue: NODE_SETTINGS.nodeSizeCoefficient,
3532
+ id: "nodeSizeLinkCountForStep",
3533
+ initialValue: NODE_SETTINGS.nodeSizeLinkCountForStep,
3534
+ max: 100,
3535
+ min: 0.1,
3536
+ step: 0.1,
3537
+ type: "range",
3538
+ label: "Количество связей для увеличения линейного шага размера",
3539
+ },
3540
+ {
3541
+ id: "nodeSizeIncrementByStep",
3542
+ initialValue: NODE_SETTINGS.nodeSizeIncrementByStep,
3527
3543
  max: 50,
3528
3544
  min: 0.1,
3529
3545
  step: 0.1,
3530
3546
  type: "range",
3531
- label: "Количество связей для увеличения размера (только не для круга)",
3547
+ label: "Размер шага линейного увеличения размера",
3532
3548
  },
3533
3549
  {
3534
- id: "nodeSizeFactor",
3535
- initialValue: NODE_SETTINGS.nodeSizeFactor,
3536
- max: 20,
3537
- min: 0.01,
3538
- step: 0.01,
3550
+ id: "nodeSizeMaxLinearSteps",
3551
+ initialValue: NODE_SETTINGS.nodeSizeMaxLinearSteps,
3552
+ max: 50,
3553
+ min: 1,
3554
+ step: 1,
3555
+ type: "range",
3556
+ label: "Максимальное число шагов линейного увеличения размера",
3557
+ },
3558
+ {
3559
+ id: "nodeSizeLogFactor",
3560
+ initialValue: NODE_SETTINGS.nodeSizeLogFactor,
3561
+ max: 50,
3562
+ min: 1,
3563
+ step: 1,
3564
+ type: "range",
3565
+ label: "Число умножаемое на логарифм от количества связей для увеличения размера",
3566
+ },
3567
+ {
3568
+ id: "nodeSizeLinkCountDividerForLog",
3569
+ initialValue: NODE_SETTINGS.nodeSizeLinkCountDividerForLog,
3570
+ max: 50,
3571
+ min: 1,
3572
+ step: 1,
3539
3573
  type: "range",
3540
- label: "Коэффициент увеличения размера (только не для круга)",
3574
+ label: "Делитель количества связей для вычисления логарифма увеличения размера.",
3541
3575
  },
3542
3576
  {
3543
3577
  id: "textScaleMin",
@@ -3601,7 +3635,6 @@ const NODE_OPTIONS_NODE_CONTROLS = [
3601
3635
  initialValue: NODE_OPTIONS.shape,
3602
3636
  label: "Форма",
3603
3637
  options: [
3604
- { label: "Картинки", value: "icon" },
3605
3638
  { label: "Круг", value: "circle" },
3606
3639
  { label: "Прямоугольник", value: "square" },
3607
3640
  { label: "Текст", value: "text" },