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

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 (30) hide show
  1. package/lib/cjs/index.cjs +307 -223
  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/module/GraphCanvas/GraphCanvas.js +15 -17
  8. package/lib/esm/module/GraphCanvas/GraphCanvas.js.map +1 -1
  9. package/lib/esm/module/GraphCanvas/constants/link-settings.js +0 -1
  10. package/lib/esm/module/GraphCanvas/constants/link-settings.js.map +1 -1
  11. package/lib/esm/module/GraphCanvas/constants/node-settings.js +10 -5
  12. package/lib/esm/module/GraphCanvas/constants/node-settings.js.map +1 -1
  13. package/lib/esm/module/GraphCanvas/lib/settings/node-settings-getter.js +20 -4
  14. package/lib/esm/module/GraphCanvas/lib/settings/node-settings-getter.js.map +1 -1
  15. package/lib/esm/module/GraphCanvas/slices/draw-links.js +7 -20
  16. package/lib/esm/module/GraphCanvas/slices/draw-links.js.map +1 -1
  17. package/lib/esm/module/GraphCanvas/slices/draw-nodes.js +25 -117
  18. package/lib/esm/module/GraphCanvas/slices/draw-nodes.js.map +1 -1
  19. package/lib/esm/module/GraphCanvas/slices/draw-text.js +1 -24
  20. package/lib/esm/module/GraphCanvas/slices/draw-text.js.map +1 -1
  21. package/lib/esm/module/GraphCanvas/slices/init-simulation.js +20 -8
  22. package/lib/esm/module/GraphCanvas/slices/init-simulation.js.map +1 -1
  23. package/lib/esm/module/GraphCanvas/slices/init-zoom.js +4 -2
  24. package/lib/esm/module/GraphCanvas/slices/init-zoom.js.map +1 -1
  25. package/lib/esm/module/GraphCanvas/slices/update-link-cache.js +19 -0
  26. package/lib/esm/module/GraphCanvas/slices/update-link-cache.js.map +1 -0
  27. package/lib/esm/module/GraphCanvas/slices/update-node-cache.js +136 -0
  28. package/lib/esm/module/GraphCanvas/slices/update-node-cache.js.map +1 -0
  29. package/lib/index.d.ts +28 -28
  30. 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,
@@ -1205,7 +1225,7 @@ function initDnd() {
1205
1225
  }
1206
1226
 
1207
1227
  function getDrawLink() {
1208
- return function drawLink(link, index) {
1228
+ return function drawLink(link) {
1209
1229
  if (!this.context ||
1210
1230
  typeof link.source !== "object" ||
1211
1231
  typeof link.target !== "object" ||
@@ -1221,16 +1241,9 @@ function getDrawLink() {
1221
1241
  if (!link.source._visible && !link.target._visible)
1222
1242
  return;
1223
1243
  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
- }
1244
+ const linkOptions = this.linkOptionsCache[id];
1245
+ if (!linkOptions)
1246
+ return;
1234
1247
  if (linkOptions.drawLink) {
1235
1248
  linkOptions.drawLink.call(this, link, linkOptions);
1236
1249
  return;
@@ -1327,7 +1340,9 @@ function getDrawLink() {
1327
1340
  let xEnd = link.target.x;
1328
1341
  let yEnd = link.target.y;
1329
1342
  let linkDistance = 0;
1330
- if (this.linkSettings.prettyDraw || this.linkSettings.particleFlexSpeed) {
1343
+ if (this.linkSettings.prettyDraw ||
1344
+ this.linkSettings.particleFlexSpeed ||
1345
+ (this.linkSettings.arrow && arrowAlpha > 0)) {
1331
1346
  const isHasArrow = this.linkSettings.arrow && arrowAlpha > 0;
1332
1347
  const position = calculateLinkPositionByNode(link, isHasArrow ? arrowSize : 0);
1333
1348
  if (position) {
@@ -1408,12 +1423,6 @@ function getDrawLink() {
1408
1423
  }
1409
1424
  /** Arrow */
1410
1425
  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
1426
  this.context.beginPath();
1418
1427
  this.context.globalAlpha = arrowAlpha;
1419
1428
  this.context.strokeStyle = arrowBorderColor;
@@ -1436,33 +1445,10 @@ function getDrawLink() {
1436
1445
  }
1437
1446
 
1438
1447
  const SPACE = " ";
1439
- function drawText({ context, id, textAlign, textColor, textFont, textStyle, textGap, textWeight, textSize, text, x, y, cachedNodeText, maxWidth, }) {
1448
+ function drawText({ context, textAlign, textColor, textFont, textStyle, textGap, textWeight, textSize, x, y, lines, }) {
1440
1449
  context.font = `${textStyle} normal ${textWeight} ${textSize}px ${textFont}`;
1441
1450
  context.fillStyle = textColor;
1442
1451
  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
1452
  lines.forEach((line, index) => {
1467
1453
  context.fillText(line, x, y + index * textSize + index * textGap);
1468
1454
  });
@@ -1504,21 +1490,14 @@ function getTextLines({ context, textAlign, textColor, textFont, textStyle, text
1504
1490
  }
1505
1491
 
1506
1492
  function getDrawNode(nodeRenders, textRenders) {
1507
- return function drawNode(node, index) {
1493
+ return function drawNode(node) {
1508
1494
  if (!this.context || !node.x || !node.y)
1509
1495
  return;
1510
1496
  if (node.visible != undefined && !node.visible)
1511
1497
  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
- }
1498
+ const nodeOptions = this.nodeOptionsCache[node.id];
1499
+ if (!nodeOptions)
1500
+ return;
1522
1501
  if (nodeOptions.nodeDraw && nodeOptions.textDraw) {
1523
1502
  nodeRenders.push(() => {
1524
1503
  if (nodeOptions.nodeDraw) {
@@ -1532,13 +1511,13 @@ function getDrawNode(nodeRenders, textRenders) {
1532
1511
  });
1533
1512
  return;
1534
1513
  }
1514
+ let radius = nodeOptions.radius;
1515
+ let width = nodeOptions.width;
1516
+ let height = nodeOptions.height;
1535
1517
  let alpha = nodeOptions.alpha;
1536
1518
  let color = nodeOptions.color;
1537
1519
  let borderColor = nodeOptions.borderColor;
1538
1520
  let borderWidth = nodeOptions.borderWidth;
1539
- let radiusInitial = nodeOptions.radius;
1540
- let widthInitial = nodeOptions.width;
1541
- let heightInitial = nodeOptions.height;
1542
1521
  let textAlpha = nodeOptions.textAlpha;
1543
1522
  let textSize = nodeOptions.textSize;
1544
1523
  let textShiftX = nodeOptions.textShiftX;
@@ -1585,9 +1564,9 @@ function getDrawNode(nodeRenders, textRenders) {
1585
1564
  color = highlightOptions.color;
1586
1565
  borderColor = highlightOptions.borderColor;
1587
1566
  borderWidth = highlightOptions.borderWidth;
1588
- radiusInitial = highlightOptions.radiusInitial;
1589
- widthInitial = highlightOptions.widthInitial;
1590
- heightInitial = highlightOptions.heightInitial;
1567
+ radius = highlightOptions.radiusInitial;
1568
+ width = highlightOptions.widthInitial;
1569
+ height = highlightOptions.heightInitial;
1591
1570
  textSize = highlightOptions.textSize;
1592
1571
  textShiftX = highlightOptions.textShiftX;
1593
1572
  textShiftY = highlightOptions.textShiftY;
@@ -1634,9 +1613,9 @@ function getDrawNode(nodeRenders, textRenders) {
1634
1613
  color = highlightOptions.color;
1635
1614
  borderColor = highlightOptions.borderColor;
1636
1615
  borderWidth = highlightOptions.borderWidth;
1637
- radiusInitial = highlightOptions.radiusInitial;
1638
- widthInitial = highlightOptions.widthInitial;
1639
- heightInitial = highlightOptions.heightInitial;
1616
+ radius = highlightOptions.radiusInitial;
1617
+ width = highlightOptions.widthInitial;
1618
+ height = highlightOptions.heightInitial;
1640
1619
  textSize = highlightOptions.textSize;
1641
1620
  textShiftX = highlightOptions.textShiftX;
1642
1621
  textShiftY = highlightOptions.textShiftY;
@@ -1645,78 +1624,6 @@ function getDrawNode(nodeRenders, textRenders) {
1645
1624
  labelWeight = highlightOptions.labelWeight;
1646
1625
  }
1647
1626
  }
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
1627
  /** Node parameters */
1721
1628
  node._radius = radius;
1722
1629
  node._borderWidth = borderWidth;
@@ -1747,6 +1654,7 @@ function getDrawNode(nodeRenders, textRenders) {
1747
1654
  this.context.lineWidth = borderWidth;
1748
1655
  this.context.strokeStyle = borderColor;
1749
1656
  this.context.fillStyle = color;
1657
+ const labelLines = this.cachedNodeLabel[node.id];
1750
1658
  switch (nodeOptions.shape) {
1751
1659
  case "circle": {
1752
1660
  if (!node.image) {
@@ -1767,13 +1675,11 @@ function getDrawNode(nodeRenders, textRenders) {
1767
1675
  this.context.stroke();
1768
1676
  }
1769
1677
  }
1770
- if (node.label) {
1678
+ if (node.label && labelLines) {
1771
1679
  this.context.globalAlpha = labelAlpha;
1772
1680
  drawText({
1773
1681
  context: this.context,
1774
- cachedNodeText: this.cachedNodeLabel,
1775
- id: node.id,
1776
- text: node.label,
1682
+ lines: labelLines,
1777
1683
  textAlign: nodeOptions.labelAlign,
1778
1684
  textColor: nodeOptions.labelColor,
1779
1685
  textFont: nodeOptions.labelFont,
@@ -1781,7 +1687,6 @@ function getDrawNode(nodeRenders, textRenders) {
1781
1687
  textSize: labelSize,
1782
1688
  textStyle: nodeOptions.labelStyle,
1783
1689
  textWeight: labelWeight,
1784
- maxWidth: nodeOptions.labelWidth,
1785
1690
  x: node.x,
1786
1691
  y: node.y + labelSize / 3,
1787
1692
  });
@@ -1807,13 +1712,11 @@ function getDrawNode(nodeRenders, textRenders) {
1807
1712
  this.context.stroke();
1808
1713
  }
1809
1714
  }
1810
- if (node.label) {
1715
+ if (node.label && labelLines) {
1811
1716
  this.context.globalAlpha = labelAlpha;
1812
1717
  drawText({
1813
1718
  context: this.context,
1814
- cachedNodeText: this.cachedNodeLabel,
1815
- id: node.id,
1816
- text: node.label,
1719
+ lines: labelLines,
1817
1720
  textAlign: nodeOptions.labelAlign,
1818
1721
  textColor: nodeOptions.labelColor,
1819
1722
  textFont: nodeOptions.labelFont,
@@ -1821,7 +1724,6 @@ function getDrawNode(nodeRenders, textRenders) {
1821
1724
  textSize: labelSize,
1822
1725
  textStyle: nodeOptions.labelStyle,
1823
1726
  textWeight: labelWeight,
1824
- maxWidth: nodeOptions.labelWidth,
1825
1727
  x: node.x,
1826
1728
  y: node.y + labelSize / 3,
1827
1729
  });
@@ -1832,20 +1734,16 @@ function getDrawNode(nodeRenders, textRenders) {
1832
1734
  if (this.nodeSettings.textNodeDebug) {
1833
1735
  this.context.strokeRect(node.x - width / 2, node.y - height / 2, width, height);
1834
1736
  }
1835
- const lines = this.cachedNodeLabel[node.id];
1836
- if (nodeOptions.label && lines)
1737
+ if (nodeOptions.label && labelLines)
1837
1738
  drawText({
1838
- id: node.id,
1839
- cachedNodeText: this.cachedNodeLabel,
1739
+ lines: labelLines,
1840
1740
  context: this.context,
1841
- text: nodeOptions.label,
1842
1741
  textAlign: nodeOptions.labelAlign,
1843
1742
  textColor: nodeOptions.labelColor,
1844
1743
  textFont: nodeOptions.labelFont,
1845
1744
  textSize: labelSize,
1846
1745
  x: node.x,
1847
- y: node.y + textSize / 4 - (lines.length - 1) * (textSize / 2),
1848
- maxWidth: nodeOptions.labelWidth,
1746
+ y: node.y + textSize / 4 - (labelLines.length - 1) * (textSize / 2),
1849
1747
  textStyle: nodeOptions.labelStyle,
1850
1748
  textWeight,
1851
1749
  textGap: nodeOptions.labelGap,
@@ -1882,7 +1780,8 @@ function getDrawNode(nodeRenders, textRenders) {
1882
1780
  });
1883
1781
  }
1884
1782
  /** Text draw */
1885
- if (nodeOptions.textVisible && nodeOptions.text) {
1783
+ const textLines = this.cachedNodeText[node.id];
1784
+ if (nodeOptions.textVisible && nodeOptions.text && textLines) {
1886
1785
  textRenders.push(() => {
1887
1786
  if (nodeOptions.textDraw) {
1888
1787
  nodeOptions.textDraw.bind(this)(node, {
@@ -1910,17 +1809,14 @@ function getDrawNode(nodeRenders, textRenders) {
1910
1809
  y += height / 2;
1911
1810
  }
1912
1811
  drawText({
1913
- id: node.id,
1914
- cachedNodeText: this.cachedNodeText,
1812
+ lines: textLines,
1915
1813
  context: this.context,
1916
- text: nodeOptions.text,
1917
1814
  textAlign: nodeOptions.textAlign,
1918
1815
  textColor: nodeOptions.textColor,
1919
1816
  textFont: nodeOptions.textFont,
1920
1817
  textSize,
1921
1818
  x: node.x + textShiftX,
1922
1819
  y,
1923
- maxWidth: nodeOptions.textWidth,
1924
1820
  textStyle: nodeOptions.textStyle,
1925
1821
  textWeight,
1926
1822
  textGap: nodeOptions.textGap,
@@ -2349,9 +2245,12 @@ function initCollideForce(forceUpdate) {
2349
2245
  const radius = nodeRadiusGetter({
2350
2246
  radiusFlexible: this.nodeSettings.nodeRadiusFlexible,
2351
2247
  radiusInitial: nodeOptions.radius,
2352
- radiusCoefficient: this.nodeSettings.nodeRadiusCoefficient,
2353
- radiusFactor: this.nodeSettings.nodeRadiusFactor,
2354
2248
  linkCount: node.linkCount,
2249
+ radiusIncrementByStep: this.nodeSettings.nodeRadiusIncrementByStep,
2250
+ radiusLinkCountForStep: this.nodeSettings.nodeRadiusLinkCountForStep,
2251
+ radiusMaxLinearSteps: this.nodeSettings.nodeRadiusMaxLinearSteps,
2252
+ radiusLogFactor: this.nodeSettings.nodeRadiusLogFactor,
2253
+ radiusLinkCountDividerForLog: this.nodeSettings.nodeRadiusLinkCountDividerForLog,
2355
2254
  });
2356
2255
  return radius + this.forceSettings.collideAdditionalRadius;
2357
2256
  }
@@ -2360,9 +2259,12 @@ function initCollideForce(forceUpdate) {
2360
2259
  heightInitial: nodeOptions.width,
2361
2260
  widthInitial: nodeOptions.height,
2362
2261
  linkCount: node.linkCount,
2363
- sizeCoefficient: this.nodeSettings.nodeSizeCoefficient,
2364
- sizeFactor: this.nodeSettings.nodeSizeFactor,
2365
2262
  sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2263
+ sizeIncrementByStep: this.nodeSettings.nodeSizeIncrementByStep,
2264
+ sizeLinkCountForStep: this.nodeSettings.nodeSizeLinkCountForStep,
2265
+ sizeMaxLinearSteps: this.nodeSettings.nodeSizeMaxLinearSteps,
2266
+ sizeLogFactor: this.nodeSettings.nodeSizeLogFactor,
2267
+ sizeLinkCountDividerForLog: this.nodeSettings.nodeSizeLinkCountDividerForLog,
2366
2268
  });
2367
2269
  return (Math.sqrt(width ** 2 + height ** 2) / 2 +
2368
2270
  this.forceSettings.collideAdditionalRadius);
@@ -2372,9 +2274,12 @@ function initCollideForce(forceUpdate) {
2372
2274
  heightInitial: nodeOptions.width,
2373
2275
  widthInitial: nodeOptions.height,
2374
2276
  linkCount: node.linkCount,
2375
- sizeCoefficient: this.nodeSettings.nodeSizeCoefficient,
2376
- sizeFactor: this.nodeSettings.nodeSizeFactor,
2377
2277
  sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2278
+ sizeIncrementByStep: this.nodeSettings.nodeSizeIncrementByStep,
2279
+ sizeLinkCountForStep: this.nodeSettings.nodeSizeLinkCountForStep,
2280
+ sizeMaxLinearSteps: this.nodeSettings.nodeSizeMaxLinearSteps,
2281
+ sizeLogFactor: this.nodeSettings.nodeSizeLogFactor,
2282
+ sizeLinkCountDividerForLog: this.nodeSettings.nodeSizeLinkCountDividerForLog,
2378
2283
  });
2379
2284
  if (this.context && nodeOptions.text) {
2380
2285
  const textInfo = getTextLines({
@@ -2404,8 +2309,11 @@ function initCollideForce(forceUpdate) {
2404
2309
  const radius = nodeRadiusGetter({
2405
2310
  radiusFlexible: this.nodeSettings.nodeRadiusFlexible,
2406
2311
  radiusInitial: nodeOptions.radius,
2407
- radiusCoefficient: this.nodeSettings.nodeRadiusCoefficient,
2408
- radiusFactor: this.nodeSettings.nodeRadiusFactor,
2312
+ radiusIncrementByStep: this.nodeSettings.nodeRadiusIncrementByStep,
2313
+ radiusLinkCountForStep: this.nodeSettings.nodeRadiusLinkCountForStep,
2314
+ radiusMaxLinearSteps: this.nodeSettings.nodeRadiusMaxLinearSteps,
2315
+ radiusLogFactor: this.nodeSettings.nodeRadiusLogFactor,
2316
+ radiusLinkCountDividerForLog: this.nodeSettings.nodeRadiusLinkCountDividerForLog,
2409
2317
  linkCount: node.linkCount,
2410
2318
  });
2411
2319
  return radius + this.forceSettings.collideAdditionalRadius;
@@ -2418,6 +2326,145 @@ function initCollideForce(forceUpdate) {
2418
2326
  }
2419
2327
  }
2420
2328
 
2329
+ function updateLinkCache() {
2330
+ this.linkOptionsCache = {};
2331
+ for (let i = 0; i < this.links.length; i++) {
2332
+ const link = this.links[i];
2333
+ const { sourceId, targetId } = extractLinkPointIds(link);
2334
+ const linkOptions = linkIterationExtractor(link, i, this.links, this, this.linkSettings.options ?? {}, linkOptionsGetter);
2335
+ const id = `${targetId}${sourceId}`;
2336
+ this.linkOptionsCache[id] = linkOptions;
2337
+ }
2338
+ }
2339
+
2340
+ function updateNodeCache() {
2341
+ this.nodeOptionsCache = {};
2342
+ if (!this.context)
2343
+ return;
2344
+ for (let i = 0; i < this.nodes.length; i++) {
2345
+ const node = this.nodes[i];
2346
+ const nodeOptions = nodeIterationExtractor(node, i, this.nodes, this, this.nodeSettings.options ?? {}, nodeOptionsGetter);
2347
+ const radius = nodeOptions.shape === "circle"
2348
+ ? nodeRadiusGetter({
2349
+ radiusFlexible: this.nodeSettings.nodeRadiusFlexible,
2350
+ radiusInitial: nodeOptions.radius,
2351
+ radiusIncrementByStep: this.nodeSettings.nodeRadiusIncrementByStep,
2352
+ radiusLinkCountForStep: this.nodeSettings.nodeRadiusLinkCountForStep,
2353
+ radiusMaxLinearSteps: this.nodeSettings.nodeRadiusMaxLinearSteps,
2354
+ radiusLogFactor: this.nodeSettings.nodeRadiusLogFactor,
2355
+ radiusLinkCountDividerForLog: this.nodeSettings.nodeRadiusLinkCountDividerForLog,
2356
+ linkCount: node.linkCount,
2357
+ })
2358
+ : nodeOptions.radius;
2359
+ let width = nodeOptions.width;
2360
+ let height = nodeOptions.height;
2361
+ let labelSize = nodeOptions.labelSize;
2362
+ if (nodeOptions.shape === "square") {
2363
+ const size = nodeSizeGetter({
2364
+ heightInitial: nodeOptions.height,
2365
+ widthInitial: nodeOptions.width,
2366
+ linkCount: node.linkCount,
2367
+ sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2368
+ sizeIncrementByStep: this.nodeSettings.nodeSizeIncrementByStep,
2369
+ sizeLinkCountForStep: this.nodeSettings.nodeSizeLinkCountForStep,
2370
+ sizeMaxLinearSteps: this.nodeSettings.nodeSizeMaxLinearSteps,
2371
+ sizeLogFactor: this.nodeSettings.nodeSizeLogFactor,
2372
+ sizeLinkCountDividerForLog: this.nodeSettings.nodeSizeLinkCountDividerForLog,
2373
+ });
2374
+ width = size.width;
2375
+ height = size.height;
2376
+ }
2377
+ if (nodeOptions.shape === "text") {
2378
+ width = nodeOptions.width;
2379
+ const size = nodeSizeGetter({
2380
+ heightInitial: nodeOptions.height,
2381
+ widthInitial: width,
2382
+ linkCount: node.linkCount,
2383
+ sizeFlexible: this.nodeSettings.nodeSizeFlexible,
2384
+ sizeIncrementByStep: this.nodeSettings.nodeSizeIncrementByStep,
2385
+ sizeLinkCountForStep: this.nodeSettings.nodeSizeLinkCountForStep,
2386
+ sizeMaxLinearSteps: this.nodeSettings.nodeSizeMaxLinearSteps,
2387
+ sizeLogFactor: this.nodeSettings.nodeSizeLogFactor,
2388
+ sizeLinkCountDividerForLog: this.nodeSettings.nodeSizeLinkCountDividerForLog,
2389
+ });
2390
+ labelSize *= size.additionalSizeCoefficient;
2391
+ }
2392
+ /** label in not text shape */
2393
+ if (nodeOptions.shape !== "text" && nodeOptions.label) {
2394
+ this.context.font = `${nodeOptions.labelStyle} normal ${nodeOptions.labelWeight} ${nodeOptions.labelSize}px ${nodeOptions.labelFont}`;
2395
+ this.context.fillStyle = nodeOptions.labelColor;
2396
+ this.context.textAlign = nodeOptions.labelAlign;
2397
+ if (nodeOptions.labelWidth == undefined ||
2398
+ this.context.measureText(nodeOptions.label).width <= nodeOptions.labelWidth) {
2399
+ this.cachedNodeLabel[node.id] = [nodeOptions.label];
2400
+ }
2401
+ const { lines } = getTextLines({
2402
+ context: this.context,
2403
+ maxWidth: nodeOptions.labelWidth,
2404
+ text: nodeOptions.label,
2405
+ textAlign: nodeOptions.labelAlign,
2406
+ textColor: nodeOptions.labelColor,
2407
+ textFont: nodeOptions.labelFont,
2408
+ textSize: nodeOptions.labelSize,
2409
+ textStyle: nodeOptions.labelStyle,
2410
+ textWeight: nodeOptions.labelWeight,
2411
+ });
2412
+ this.cachedNodeLabel[node.id] = lines;
2413
+ }
2414
+ /** label in text shape */
2415
+ if (nodeOptions.shape === "text" && nodeOptions.label) {
2416
+ const textInfo = getTextLines({
2417
+ context: this.context,
2418
+ text: nodeOptions.label,
2419
+ textAlign: nodeOptions.labelAlign,
2420
+ textColor: nodeOptions.labelColor,
2421
+ textFont: nodeOptions.labelFont,
2422
+ textSize: labelSize,
2423
+ maxWidth: nodeOptions.labelWidth,
2424
+ textStyle: nodeOptions.labelStyle,
2425
+ textWeight: nodeOptions.textWeight,
2426
+ });
2427
+ const textNodeParameters = [textInfo.currentMaxSize, labelSize];
2428
+ const lines = textInfo.lines;
2429
+ const textSizeCoefficient = labelSize / textNodeParameters[1];
2430
+ const maxSize = textNodeParameters[0] * textSizeCoefficient;
2431
+ height =
2432
+ lines.length * labelSize +
2433
+ (lines.length - 1) * nodeOptions.labelGap +
2434
+ nodeOptions.labelYPadding;
2435
+ width = maxSize + nodeOptions.labelXPadding;
2436
+ this.cachedNodeLabel[node.id] = lines;
2437
+ }
2438
+ nodeOptions.width = width;
2439
+ nodeOptions.height = height;
2440
+ nodeOptions.radius = radius;
2441
+ nodeOptions.labelSize = labelSize;
2442
+ /** text */
2443
+ if (nodeOptions.text) {
2444
+ this.context.font = `${nodeOptions.textStyle} normal ${nodeOptions.textWeight} ${nodeOptions.textSize}px ${nodeOptions.textFont}`;
2445
+ this.context.fillStyle = nodeOptions.textColor;
2446
+ this.context.textAlign = nodeOptions.textAlign;
2447
+ if (nodeOptions.textWidth == undefined ||
2448
+ this.context.measureText(nodeOptions.text).width <= nodeOptions.textWidth) {
2449
+ this.cachedNodeText[node.id] = [nodeOptions.text];
2450
+ }
2451
+ const { lines } = getTextLines({
2452
+ context: this.context,
2453
+ maxWidth: nodeOptions.textWidth,
2454
+ text: nodeOptions.text,
2455
+ textAlign: nodeOptions.textAlign,
2456
+ textColor: nodeOptions.textColor,
2457
+ textFont: nodeOptions.textFont,
2458
+ textSize: nodeOptions.textSize,
2459
+ textStyle: nodeOptions.textStyle,
2460
+ textWeight: nodeOptions.textWeight,
2461
+ });
2462
+ this.cachedNodeText[node.id] = lines;
2463
+ }
2464
+ this.nodeOptionsCache[node.id] = nodeOptions;
2465
+ }
2466
+ }
2467
+
2421
2468
  function initZoom(currentZoom) {
2422
2469
  if (!this.area)
2423
2470
  throw new Error("bad init data");
@@ -2426,8 +2473,8 @@ function initZoom(currentZoom) {
2426
2473
  .on("zoom", (event) => {
2427
2474
  this.listeners.onZoom?.call?.(this, event);
2428
2475
  this.areaTransform = event.transform;
2429
- this.linkOptionsCache = {};
2430
- this.nodeOptionsCache = {};
2476
+ updateLinkCache.call(this);
2477
+ updateNodeCache.call(this);
2431
2478
  if (!this.simulationWorking && !this.highlightWorking)
2432
2479
  requestAnimationFrame(() => this.draw());
2433
2480
  });
@@ -2474,7 +2521,6 @@ class GraphCanvas {
2474
2521
  eventAbortController;
2475
2522
  cachedNodeText = {};
2476
2523
  cachedNodeLabel = {};
2477
- cachedTextNodeParameters = {};
2478
2524
  linkOptionsCache = {};
2479
2525
  nodeOptionsCache = {};
2480
2526
  isDragging = false;
@@ -2537,26 +2583,19 @@ class GraphCanvas {
2537
2583
  if (options.highlightSettings) {
2538
2584
  this.highlightSettings = highlightSettingsGetter(options.highlightSettings, this.highlightSettings);
2539
2585
  if (clearCache) {
2540
- this.linkOptionsCache = {};
2541
- this.cachedNodeText = {};
2542
- this.cachedNodeLabel = {};
2543
- this.cachedTextNodeParameters = {};
2544
- this.nodeOptionsCache = {};
2586
+ this.clearCache(["nodeOptionsCache", "linkOptionsCache"]);
2545
2587
  }
2546
2588
  }
2547
2589
  if (options.linkSettings) {
2548
2590
  this.linkSettings = linkSettingsGetter(options.linkSettings, this.linkSettings);
2549
2591
  if (clearCache) {
2550
- this.linkOptionsCache = {};
2592
+ this.clearCache(["linkOptionsCache"]);
2551
2593
  }
2552
2594
  }
2553
2595
  if (options.nodeSettings) {
2554
2596
  this.nodeSettings = nodeSettingsGetter(options.nodeSettings, this.nodeSettings);
2555
2597
  if (clearCache) {
2556
- this.cachedNodeText = {};
2557
- this.cachedNodeLabel = {};
2558
- this.cachedTextNodeParameters = {};
2559
- this.nodeOptionsCache = {};
2598
+ this.clearCache(["nodeOptionsCache"]);
2560
2599
  }
2561
2600
  initCollideForce.call(this, true);
2562
2601
  }
@@ -2583,16 +2622,18 @@ class GraphCanvas {
2583
2622
  }
2584
2623
  clearCache(keys) {
2585
2624
  if (!keys) {
2586
- this.nodeOptionsCache = {};
2587
- this.linkOptionsCache = {};
2588
- this.cachedNodeText = {};
2589
- this.cachedNodeLabel = {};
2590
- this.cachedTextNodeParameters = {};
2625
+ updateNodeCache.call(this);
2626
+ updateLinkCache.call(this);
2591
2627
  }
2592
2628
  else {
2593
2629
  for (let i = 0; i < keys.length; i++) {
2594
2630
  const key = keys[i];
2595
- this[key] = {};
2631
+ if (key === "nodeOptionsCache") {
2632
+ updateNodeCache.call(this);
2633
+ }
2634
+ else {
2635
+ updateLinkCache.call(this);
2636
+ }
2596
2637
  }
2597
2638
  }
2598
2639
  }
@@ -2676,6 +2717,8 @@ class GraphCanvas {
2676
2717
  }
2677
2718
  init() {
2678
2719
  initArea.call(this);
2720
+ updateNodeCache.call(this);
2721
+ updateLinkCache.call(this);
2679
2722
  initSimulation.call(this);
2680
2723
  initDnd.call(this);
2681
2724
  initZoom.call(this);
@@ -3266,12 +3309,6 @@ const HIGHLIGHT_BY_LINK_FOR_ARROW_CONTROLS = [
3266
3309
  ];
3267
3310
 
3268
3311
  const LINK_SETTINGS_CONTROLS = [
3269
- {
3270
- id: "cacheOptions",
3271
- type: "checkbox",
3272
- label: "Кеширование вычисляемых настроек",
3273
- initialValue: LINK_SETTINGS.cacheOptions,
3274
- },
3275
3312
  {
3276
3313
  id: "prettyDraw",
3277
3314
  type: "checkbox",
@@ -3479,12 +3516,6 @@ const LINK_OPTIONS_PARTICLE_CONTROLS = [
3479
3516
  ];
3480
3517
 
3481
3518
  const NODE_SETTINGS_CONTROLS = [
3482
- {
3483
- id: "cacheOptions",
3484
- type: "checkbox",
3485
- initialValue: NODE_SETTINGS.cacheOptions,
3486
- label: "Кеширование вычисляемых настроек",
3487
- },
3488
3519
  {
3489
3520
  id: "textNodeDebug",
3490
3521
  type: "checkbox",
@@ -3495,49 +3526,103 @@ const NODE_SETTINGS_CONTROLS = [
3495
3526
  id: "nodeRadiusFlexible",
3496
3527
  type: "checkbox",
3497
3528
  initialValue: NODE_SETTINGS.nodeRadiusFlexible,
3498
- label: "Гибкий радиус (только для круга)",
3529
+ label: "Гибкий радиус",
3499
3530
  },
3500
3531
  {
3501
- id: "nodeRadiusCoefficient",
3502
- initialValue: NODE_SETTINGS.nodeRadiusCoefficient,
3532
+ id: "nodeRadiusLinkCountForStep",
3533
+ initialValue: NODE_SETTINGS.nodeRadiusLinkCountForStep,
3503
3534
  max: 100,
3504
3535
  min: 0.1,
3505
3536
  step: 0.1,
3506
3537
  type: "range",
3507
- label: "Количество связей для увеличения радиуса (только для круга)",
3538
+ label: "Количество связей для увеличения линейного шага радиуса",
3508
3539
  },
3509
3540
  {
3510
- id: "nodeRadiusFactor",
3511
- initialValue: NODE_SETTINGS.nodeRadiusFactor,
3541
+ id: "nodeRadiusIncrementByStep",
3542
+ initialValue: NODE_SETTINGS.nodeRadiusIncrementByStep,
3512
3543
  max: 50,
3513
3544
  min: 0.1,
3514
3545
  step: 0.1,
3515
3546
  type: "range",
3516
- label: "Коэффициент увеличения радиуса (только для круга)",
3547
+ label: "Размер шага линейного увеличения радиуса",
3548
+ },
3549
+ {
3550
+ id: "nodeRadiusMaxLinearSteps",
3551
+ initialValue: NODE_SETTINGS.nodeRadiusMaxLinearSteps,
3552
+ max: 50,
3553
+ min: 1,
3554
+ step: 1,
3555
+ type: "range",
3556
+ label: "Максимальное число шагов линейного увеличения радиуса",
3557
+ },
3558
+ {
3559
+ id: "nodeRadiusLogFactor",
3560
+ initialValue: NODE_SETTINGS.nodeRadiusLogFactor,
3561
+ max: 50,
3562
+ min: 1,
3563
+ step: 1,
3564
+ type: "range",
3565
+ label: "Число умножаемое на логарифм от количества связей для увеличения радиуса",
3566
+ },
3567
+ {
3568
+ id: "nodeRadiusLinkCountDividerForLog",
3569
+ initialValue: NODE_SETTINGS.nodeRadiusLinkCountDividerForLog,
3570
+ max: 50,
3571
+ min: 1,
3572
+ step: 1,
3573
+ type: "range",
3574
+ label: "Делитель количества связей для вычисления логарифма увеличения радиуса.",
3517
3575
  },
3518
3576
  {
3519
3577
  id: "nodeSizeFlexible",
3520
3578
  type: "checkbox",
3521
3579
  initialValue: NODE_SETTINGS.nodeSizeFlexible,
3522
- label: "Гибкий размер (только не для круга)",
3580
+ label: "Гибкий размер",
3581
+ },
3582
+ {
3583
+ id: "nodeSizeLinkCountForStep",
3584
+ initialValue: NODE_SETTINGS.nodeSizeLinkCountForStep,
3585
+ max: 100,
3586
+ min: 0.1,
3587
+ step: 0.1,
3588
+ type: "range",
3589
+ label: "Количество связей для увеличения линейного шага размера",
3523
3590
  },
3524
3591
  {
3525
- id: "nodeSizeCoefficient",
3526
- initialValue: NODE_SETTINGS.nodeSizeCoefficient,
3592
+ id: "nodeSizeIncrementByStep",
3593
+ initialValue: NODE_SETTINGS.nodeSizeIncrementByStep,
3527
3594
  max: 50,
3528
3595
  min: 0.1,
3529
3596
  step: 0.1,
3530
3597
  type: "range",
3531
- label: "Количество связей для увеличения размера (только не для круга)",
3598
+ label: "Размер шага линейного увеличения размера",
3532
3599
  },
3533
3600
  {
3534
- id: "nodeSizeFactor",
3535
- initialValue: NODE_SETTINGS.nodeSizeFactor,
3536
- max: 20,
3537
- min: 0.01,
3538
- step: 0.01,
3601
+ id: "nodeSizeMaxLinearSteps",
3602
+ initialValue: NODE_SETTINGS.nodeSizeMaxLinearSteps,
3603
+ max: 50,
3604
+ min: 1,
3605
+ step: 1,
3606
+ type: "range",
3607
+ label: "Максимальное число шагов линейного увеличения размера",
3608
+ },
3609
+ {
3610
+ id: "nodeSizeLogFactor",
3611
+ initialValue: NODE_SETTINGS.nodeSizeLogFactor,
3612
+ max: 50,
3613
+ min: 1,
3614
+ step: 1,
3615
+ type: "range",
3616
+ label: "Число умножаемое на логарифм от количества связей для увеличения размера",
3617
+ },
3618
+ {
3619
+ id: "nodeSizeLinkCountDividerForLog",
3620
+ initialValue: NODE_SETTINGS.nodeSizeLinkCountDividerForLog,
3621
+ max: 50,
3622
+ min: 1,
3623
+ step: 1,
3539
3624
  type: "range",
3540
- label: "Коэффициент увеличения размера (только не для круга)",
3625
+ label: "Делитель количества связей для вычисления логарифма увеличения размера.",
3541
3626
  },
3542
3627
  {
3543
3628
  id: "textScaleMin",
@@ -3601,7 +3686,6 @@ const NODE_OPTIONS_NODE_CONTROLS = [
3601
3686
  initialValue: NODE_OPTIONS.shape,
3602
3687
  label: "Форма",
3603
3688
  options: [
3604
- { label: "Картинки", value: "icon" },
3605
3689
  { label: "Круг", value: "circle" },
3606
3690
  { label: "Прямоугольник", value: "square" },
3607
3691
  { label: "Текст", value: "text" },