为什么 Cytoscape 在这种特定情况下会进入 "infinite" 循环?

Why does Cytoscape go into an "infinite" loop in this specific situation?

如果我使用 Object.assign 克隆布局配置并尝试使用该布局,cytoscape 会很快导致内存不足错误。我可以通过定义与第一个基本相同的第二个布局而不是克隆来解决这个问题,但我很想知道问题背后的原因,或者它是否可能是 cytoscape 中的错误。

使用此示例代码,在加载页面后立即单击 add and layout 2,它将 hang/run 内存不足。 (让你的任务管理器方便地杀死你的选项卡或浏览器。)添加节点和 运行 克隆布局的不同组合大多会挂起,但并非总是如此。

let cy
const layout1 = {
  name: 'euler',
  springLength: edge => 80,
  mass: node => 4,
  randomize: true,
  animate: false,
  gravity: -1.2,
  maxIterations: 1000,
  maxSimulationTime: 4000,
}

const layout2 = Object.assign({}, layout1, {
  fit: false,
  animate: true,
  randomize: false,
})

document.addEventListener('DOMContentLoaded', function() {
  cy = cytoscape({
    container: document.getElementById('cy'),
    layout: layout1,
    style: [
      {
        selector: 'node',
        style: {
          label: 'data(id)',
        },
      },
    ],
    elements: [
      { data: { id: 'a' } },
      { data: { id: 'b' } },
      { data: { id: 'a_b', source: 'a', target: 'b' } },
    ],
  })
})

function add() {
  cy.add([
    { data: { id: 'c' } },
    { data: { id: 'd' } },
    { data: { id: 'c_d', source: 'c', target: 'd' } },
  ])
  // cy.layout(layout2).run()
}

function doLayout1() {
  cy.layout(layout1).run()
}

function doLayout2() {
  cy.layout(layout2).run()
}

function addAndLayout2() {
  add()
  doLayout2()
}
<!DOCTYPE html>
    <html>
      <head>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.5.0/cytoscape.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/cytoscape-euler@1.2.1/cytoscape-euler.min.js"></script>
      </head>
    
      <style>
        body {
          height: 100%;
        }
        #cy {
          height: 100%;
          flex-grow: 1;
        }
        .main {
          height: 100vh;
          display: flex;
          flex: 1;
        }
      </style>
    
      <body>
        <button onclick="add()">add nodes</button>
        <button onclick="doLayout1()">layout 1</button>
        <button onclick="doLayout2()">layout 2</button>
        <button onclick="addAndLayout2()">add and layout 2</button>
        <div class="main">
          <div id="cy"></div>
        </div>
      </body>
    </html>

这与Ojbect.assign无关(即使你没有正确复制对象,它也不应该挂起)。

原因是 randomize 选项。对于这个特定的图形,当随机化选项设置为 false 时,布局永远不会结束。只需从第二个布局中删除 randomize: false,或者在添加新节点之后和在 运行 宁 layout2、运行 random 布局之前(或者只是随机化节点手动) -- layout2 将终止。

问题是:布局必须在某个点终止(最坏的情况是达到最大迭代次数)。但是这个特殊的布局永远不会终止。

有趣的是,这个简单的图形结果也是其他一些布局算法(对于 randomized: false)最糟糕的情况之一。我试过了cose-bilkent。它还需要更长的时间并在达到最大迭代次数时终止(将 numIter 选项设置为较低的数字将导致提前终止,质量更差)——但结果确实很糟糕。