Skip to content

Commit eae43db

Browse files
Improve vertical refinement in Graph Editor layout (#2789)
This changelist improves the `refineLayerY` method in the Graph Editor layout engine, addressing a downward drift bias in vertical node placement that was caused by the use of a single top-to-bottom sweep for overlap resolution. Overlaps are now resolved with a symmetric bidirectional technique, where top-to-bottom and bottom-to-top passes are run independently and their results are averaged, producing balanced placement without an explicit re-centering step. Additionally, the median calculation now properly averages the two middle values for even-sized neighbor sets.
1 parent ab5f2fd commit eae43db

1 file changed

Lines changed: 39 additions & 19 deletions

File tree

source/MaterialXGraphEditor/Layout.cpp

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -451,9 +451,7 @@ void Layout::refineLayerY(int layerIndex, bool preferDownstream)
451451
{
452452
std::vector<int>& layer = _layers[layerIndex];
453453

454-
// Compute ideal Y for each node and track desired center.
455-
float idealCenterSum = 0.0f;
456-
int idealCount = 0;
454+
// Compute ideal Y for each node based on the median neighbor center.
457455
for (int nodeId : layer)
458456
{
459457
Node& n = _nodes[nodeId];
@@ -472,13 +470,21 @@ void Layout::refineLayerY(int layerIndex, bool preferDownstream)
472470
neighborYs.push_back(nn.y + nn.height * 0.5f);
473471
}
474472
std::sort(neighborYs.begin(), neighborYs.end());
475-
float medianY = neighborYs[neighborYs.size() / 2];
473+
size_t mid = neighborYs.size() / 2;
474+
float medianY = (neighborYs.size() % 2 != 0) ?
475+
neighborYs[mid] :
476+
(neighborYs[mid - 1] + neighborYs[mid]) * 0.5f;
476477
n.y = medianY - n.height * 0.5f;
477-
idealCenterSum += medianY;
478-
idealCount++;
479478
}
480479

481-
// Resolve overlaps while maintaining order.
480+
// Save median-placed positions.
481+
std::vector<float> medianY(layer.size());
482+
for (size_t i = 0; i < layer.size(); ++i)
483+
{
484+
medianY[i] = _nodes[layer[i]].y;
485+
}
486+
487+
// Top-to-bottom pass.
482488
for (size_t i = 1; i < layer.size(); ++i)
483489
{
484490
Node& prev = _nodes[layer[i - 1]];
@@ -490,20 +496,34 @@ void Layout::refineLayerY(int layerIndex, bool preferDownstream)
490496
}
491497
}
492498

493-
// Center the layer around the desired position.
494-
if (idealCount > 0 && layer.size() > 1)
499+
// Save top-to-bottom results.
500+
std::vector<float> downY(layer.size());
501+
for (size_t i = 0; i < layer.size(); ++i)
495502
{
496-
float idealCenter = idealCenterSum / idealCount;
497-
float actualCenterSum = 0.0f;
498-
for (int nodeId : layer)
499-
{
500-
actualCenterSum += _nodes[nodeId].y + _nodes[nodeId].height * 0.5f;
501-
}
502-
float actualCenter = actualCenterSum / static_cast<float>(layer.size());
503-
float shift = idealCenter - actualCenter;
504-
for (int nodeId : layer)
503+
downY[i] = _nodes[layer[i]].y;
504+
}
505+
506+
// Restore median positions for bottom-to-top pass.
507+
for (size_t i = 0; i < layer.size(); ++i)
508+
{
509+
_nodes[layer[i]].y = medianY[i];
510+
}
511+
512+
// Bottom-to-top pass.
513+
for (int i = static_cast<int>(layer.size()) - 2; i >= 0; --i)
514+
{
515+
Node& above = _nodes[layer[i]];
516+
Node& below = _nodes[layer[i + 1]];
517+
float maxY = below.y - NODE_SPACING - above.height;
518+
if (above.y > maxY)
505519
{
506-
_nodes[nodeId].y += shift;
520+
above.y = maxY;
507521
}
508522
}
523+
524+
// Average the two passes for symmetric spreading.
525+
for (size_t i = 0; i < layer.size(); ++i)
526+
{
527+
_nodes[layer[i]].y = (downY[i] + _nodes[layer[i]].y) * 0.5f;
528+
}
509529
}

0 commit comments

Comments
 (0)