Commit 9d138dce authored by Joe Mason's avatar Joe Mason Committed by Commit Bot

[PM] Apply graph boundaries as a counter-force.

* boundaryForce now updates node velocities to pull them back across the
  boundary instead of teleporting them, which causes jitter as other
  forces continue to act after the teleport.
* Rescale targetYPositionStrength to a max of 1 so that boundaryForce
  can match its strength without increasing oscillations.
* Add a linkStrengthScalingFactor so that some link strengths can be
  rescaled along with targetYPositionStrength.
* Add a 20-pixel border to the bottom edge of the graph so that the
  boundary force doesn't cram process nodes against the edge of the
  screen.

Bug: 1086231
Change-Id: Idb4fe5acc05c4a8c0a7cfa7b5143c4f2d4f78f5e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2215403
Commit-Queue: Joe Mason <joenotcharles@chromium.org>
Reviewed-by: default avatarSigurður Ásgeirsson <siggi@chromium.org>
Reviewed-by: default avatardpapad <dpapad@chromium.org>
Auto-Submit: Joe Mason <joenotcharles@chromium.org>
Cr-Commit-Position: refs/heads/master@{#772407}
parent a95e3a91
...@@ -3,24 +3,41 @@ ...@@ -3,24 +3,41 @@
// found in the LICENSE file. // found in the LICENSE file.
// Target y position for page nodes. // Target y position for page nodes.
const kPageNodesTargetY = 20; const /** number */ kPageNodesTargetY = 20;
// Range occupied by page nodes at the top of the graph view. // Range occupied by page nodes at the top of the graph view.
const kPageNodesYRange = 100; const /** number */ kPageNodesYRange = 100;
// Border to leave between process nodes and the bottom of the graph view.
const /** number */ kProcessNodesYBorder = 20;
// Range occupied by process nodes at the bottom of the graph view. // Range occupied by process nodes at the bottom of the graph view.
const kProcessNodesYRange = 100; const /** number */ kProcessNodesYRange = 100;
// Range occupied by worker nodes at the bottom of the graph view, above // Range occupied by worker nodes at the bottom of the graph view, above
// process nodes. // process nodes.
const kWorkerNodesYRange = 200; const /** number */ kWorkerNodesYRange = 200;
// Target y position for frame nodes. // Target y position for frame nodes.
const kFrameNodesTargetY = kPageNodesYRange + 50; const /** number */ kFrameNodesTargetY = kPageNodesYRange + 50;
// Range that frame nodes cannot enter at the top/bottom of the graph view. // Range that frame nodes cannot enter at the top/bottom of the graph view.
const kFrameNodesTopMargin = kPageNodesYRange; const /** number */ kFrameNodesTopMargin = kPageNodesYRange;
const kFrameNodesBottomMargin = kWorkerNodesYRange + 50; const /** number */ kFrameNodesBottomMargin = kWorkerNodesYRange + 50;
// The maximum strength of a Y-force.
// According to https://github.com/d3/d3-force#positioning, strength values
// outside the range [0,1] are "not recommended".
const /** number */ kMaxYStrength = 1;
// The strength of a high Y-force. This is appropriate for forces that
// strongly pull towards an attractor, but can still be overridden by the
// strongest force.
const /** number */ kHighYStrength = 0.9;
// The strength of a weak Y-force. This is appropriate for forces that exert
// some influence but can be easily overridden.
const /** number */ kWeakYStrength = 0.1;
class ToolTip { class ToolTip {
/** /**
...@@ -273,8 +290,16 @@ class GraphNode { ...@@ -273,8 +290,16 @@ class GraphNode {
* @return {number} The strength of the force that pulls the node towards * @return {number} The strength of the force that pulls the node towards
* its target y position. * its target y position.
*/ */
targetYPositionStrength() { get targetYPositionStrength() {
return 0.1; return kWeakYStrength;
}
/**
* @return {number} A scaling factor applied to the strength of links to this
* node.
*/
get linkStrengthScalingFactor() {
return 1;
} }
/** /**
...@@ -287,12 +312,12 @@ class GraphNode { ...@@ -287,12 +312,12 @@ class GraphNode {
} }
/** @return {number} The strength of the repulsion force with other nodes. */ /** @return {number} The strength of the repulsion force with other nodes. */
manyBodyStrength() { get manyBodyStrength() {
return -200; return -200;
} }
/** @return {!Array<number>} */ /** @return {!Array<number>} */
linkTargets() { get linkTargets() {
return []; return [];
} }
...@@ -322,8 +347,18 @@ class PageNode extends GraphNode { ...@@ -322,8 +347,18 @@ class PageNode extends GraphNode {
} }
/** @override */ /** @override */
targetYPositionStrength() { get targetYPositionStrength() {
return 10; // Gravitate strongly towards the top of the graph. Can be overridden by
// the bounding force which uses kMaxYStrength.
return kHighYStrength;
}
/** @override */
get linkStrengthScalingFactor() {
// Give links from frame nodes to page nodes less weight than links between
// frame nodes, so the that Y forces pulling page nodes into their area can
// dominate over link forces pulling them towards frame nodes.
return 0.5;
} }
/** override */ /** override */
...@@ -332,7 +367,7 @@ class PageNode extends GraphNode { ...@@ -332,7 +367,7 @@ class PageNode extends GraphNode {
} }
/** override */ /** override */
manyBodyStrength() { get manyBodyStrength() {
return -600; return -600;
} }
} }
...@@ -362,7 +397,7 @@ class FrameNode extends GraphNode { ...@@ -362,7 +397,7 @@ class FrameNode extends GraphNode {
} }
/** override */ /** override */
linkTargets() { get linkTargets() {
// Only link to the page if there isn't a parent frame. // Only link to the page if there isn't a parent frame.
return [ return [
this.frame.parentFrameId || this.frame.pageId, this.frame.processId this.frame.parentFrameId || this.frame.pageId, this.frame.processId
...@@ -386,17 +421,29 @@ class ProcessNode extends GraphNode { ...@@ -386,17 +421,29 @@ class ProcessNode extends GraphNode {
} }
/** @return {number} */ /** @return {number} */
targetYPositionStrength() { get targetYPositionStrength() {
return 10; // Gravitate strongly towards the bottom of the graph. Can be overridden by
// the bounding force which uses kMaxYStrength.
return kHighYStrength;
}
/** @override */
get linkStrengthScalingFactor() {
// Give links to process nodes less weight than links between frame nodes,
// so the that Y forces pulling process nodes into their area can dominate
// over link forces pulling them towards frame nodes.
return 0.5;
} }
/** override */ /** override */
allowedYRange(graphHeight) { allowedYRange(graphHeight) {
return [graphHeight - kProcessNodesYRange, graphHeight]; return [
graphHeight - kProcessNodesYRange, graphHeight - kProcessNodesYBorder
];
} }
/** override */ /** override */
manyBodyStrength() { get manyBodyStrength() {
return -600; return -600;
} }
} }
...@@ -417,8 +464,10 @@ class WorkerNode extends GraphNode { ...@@ -417,8 +464,10 @@ class WorkerNode extends GraphNode {
} }
/** @return {number} */ /** @return {number} */
targetYPositionStrength() { get targetYPositionStrength() {
return 10; // Gravitate strongly towards the worker area of the graph. Can be
// overridden by the bounding force which uses kMaxYStrength.
return kHighYStrength;
} }
/** override */ /** override */
...@@ -429,12 +478,12 @@ class WorkerNode extends GraphNode { ...@@ -429,12 +478,12 @@ class WorkerNode extends GraphNode {
} }
/** override */ /** override */
manyBodyStrength() { get manyBodyStrength() {
return -600; return -600;
} }
/** override */ /** override */
linkTargets() { get linkTargets() {
// Link the process, in addition to all the client and child workers. // Link the process, in addition to all the client and child workers.
return [ return [
this.worker.processId, ...this.worker.clientFrameIds, this.worker.processId, ...this.worker.clientFrameIds,
...@@ -459,12 +508,16 @@ function boundingForce(graphHeight) { ...@@ -459,12 +508,16 @@ function boundingForce(graphHeight) {
for (let i = 0; i < n; ++i) { for (let i = 0; i < n; ++i) {
const bound = bounds[i]; const bound = bounds[i];
const node = nodes[i]; const node = nodes[i];
const yOld = node.y;
const yNew = Math.max(bound[0], Math.min(yOld, bound[1])); // Calculate where the node will end up after movement. If it will be out
if (yOld !== yNew) { // of bounds apply a counter-force to bring it back in.
node.y = yNew; const yNextPosition = node.y + node.vy;
// Zero the velocity of clamped nodes. const yBoundedPosition =
node.vy = 0; Math.max(bound[0], Math.min(yNextPosition, bound[1]));
if (yNextPosition !== yBoundedPosition) {
// Do not include alpha because we want to be strongly repelled from
// the boundary even if alpha has decayed.
node.vy += (yBoundedPosition - yNextPosition) * kMaxYStrength;
} }
} }
} }
...@@ -575,7 +628,17 @@ class Graph { ...@@ -575,7 +628,17 @@ class Graph {
simulation.on('tick', this.onTick_.bind(this)); simulation.on('tick', this.onTick_.bind(this));
const linkForce = d3.forceLink().id(d => d.id); const linkForce = d3.forceLink().id(d => d.id);
simulation.force('link', linkForce); const defaultStrength = linkForce.strength();
// Override the default link strength function to apply scaling factors
// from the source and target nodes to the link strength. This lets
// different node types balance link forces with other forces that act on
// them.
simulation.force(
'link',
linkForce.strength(
l => defaultStrength(l) * l.source.linkStrengthScalingFactor *
l.target.linkStrengthScalingFactor));
// Sets the repulsion force between nodes (positive number is attraction, // Sets the repulsion force between nodes (positive number is attraction,
// negative number is repulsion). // negative number is repulsion).
...@@ -955,7 +1018,7 @@ class Graph { ...@@ -955,7 +1018,7 @@ class Graph {
* @private * @private
*/ */
addNodeLinks_(node) { addNodeLinks_(node) {
const linkTargets = node.linkTargets(); const linkTargets = node.linkTargets;
for (const linkTarget of linkTargets) { for (const linkTarget of linkTargets) {
const target = this.nodes_.get(linkTarget); const target = this.nodes_.get(linkTarget);
if (target) { if (target) {
...@@ -1015,7 +1078,7 @@ class Graph { ...@@ -1015,7 +1078,7 @@ class Graph {
* @private * @private
*/ */
getTargetYPositionStrength_(d) { getTargetYPositionStrength_(d) {
return d.targetYPositionStrength(); return d.targetYPositionStrength;
} }
/** /**
...@@ -1023,7 +1086,7 @@ class Graph { ...@@ -1023,7 +1086,7 @@ class Graph {
* @private * @private
*/ */
getManyBodyStrength_(d) { getManyBodyStrength_(d) {
return d.manyBodyStrength(); return d.manyBodyStrength;
} }
/** /**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment