Skip to content

Commit

Permalink
Skip (some) tree animations for big trees
Browse files Browse the repository at this point in the history
For big trees, attempting to animate changes results in poor performance
and we're better off making all SVG changes in a single pass. "big" here
depends on both the browser's performance and the size of the tree. Here
we just use the latter as it's simpler to code, but in the future we may
measure performance as the user interacts with the viz and update these
flags accordingly. (This is one of the reasons we calculate these in
middleware.)

For future, we should add nuance to this performance flag. For instance,
viewing a 2k tree and zooming into a clade has poor performance and we'd
be better off skipping the animation, however once in a small clade it
works well. For that same tree (viewing all 2k tips) filtering to a
clade still looks good with the animation.

4000 tips is arbitrary, it was chosen after testing a variety of
datasets locally.

Note that there is a big difference between not setting a transition
(within d3) and setting a transition of 0ms - the former is much faster.
See <#1878> for more details
here. I didn't implement this change for `modifySVGInStages` as it uses
a more complex scheduling system; but the performance here is terrible
for really large trees so we should revisit this in future work.

Performance changes
-------------------

Auspice built in production mode on Chrome 130.0 on a Apple M3 pro chip.
Datasets: a large ncov tree (23k tips) (see
<#1878> for more), and a flu
tree of 2k tips. Both only rendering the tree panel.

Flu performance is unchanged, as 2k tips isn't enough to trigger this
performance toggle. For posterity:
* Zooming into clade 2a. Initial frame ~160ms, then 30fps.
* Filtering to clade 2a. Initial frame ~115ms, then 60fps.
* Animation: 60fps

ncov performance:
* Zoom into clade 21M.
  * Previously: Initial frame 1500ms, second (final frame) 430ms later,
    main blocked for a further 700ms
  * Now:  Single frame of 921ms. Main blocked for a further 550ms.
  * Change: Time to correct tree reduced by ~1000ms (50%). Time until
    main is no longer blocked: reduced by ~1100ms (45%).

* Filter to clade 21M.
  * Previously: Initial frame 1050ms, second frame 930ms later, main
    blocked for a further 660ms
  * Now: Single frame of 1018ms, main blocked for a further 660ms
  * Change: Time to correct tree reduced by ~950ms (50%). Time until
    main is no longer blocked: reduced by ~950ms (35%).

* Animate. This is unchanged - Auspice sets the transition time to 0
  (see end of this commit message). We get around 4 frames at 70ms each
  (14fps) then a long commit phase blocking for ~650ms. For unknown
  reasons we trigger a timer flush but it doesn't show up in the
  performance snapshot, which is why these times are unchanged by this
  commit.

Note that the timings for the initial frame are slightly variable in
chrome. The JS time / blinkng pipeline parts shown on the main thread
seem pretty consistent, but when the frame is drawn (relative to when
the commit phase starts) is more variable. So take the above results
with a grain of salt.

P.S. there is a somewhat parallel implementation of this concept in
Auspice where PhyloTree sets a transition time of zero if subsequent
updates come in quick enough, but that is buggy. Firstly, if an update
takes a long time the main thread will be blocked so the next update
will be indistinguishable from one which was triggered an acceptable
time later. Secondly, it only works for animation where we have a stream
of updates.
  • Loading branch information
jameshadfield committed Nov 6, 2024
1 parent c21f210 commit f09e0cb
Show file tree
Hide file tree
Showing 4 changed files with 19 additions and 18 deletions.
19 changes: 9 additions & 10 deletions src/components/tree/phyloTree/change.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,12 @@ const createUpdateCall = (treeElem, properties) => (selection) => {

const genericSelectAndModify = (svg, treeElem, updateCall, transitionTime) => {
// console.log("general svg update for", treeElem);
svg.selectAll(treeElem)
.filter((d) => d.update)
.transition().duration(transitionTime)
.call(updateCall);
if (!transitionTime) {
/* https://github.com/d3/d3-timer#timerFlush */
timerFlush();
// console.log("\t\t--FLUSHING TIMER--");
let selection = svg.selectAll(treeElem)
.filter((d) => d.update);
if (transitionTime) {
selection = selection.transition().duration(transitionTime);
}
selection.call(updateCall);
};

/* use D3 to select and modify elements, such that a given element is only ever modified _once_
Expand Down Expand Up @@ -271,7 +268,8 @@ export const change = function change({
branchThickness = undefined,
/* other data */
focus = undefined,
scatterVariables = undefined
scatterVariables = undefined,
performanceFlags = {},
}) {
// console.log("\n** phylotree.change() (time since last run:", Date.now() - this.timeLastRenderRequested, "ms) **\n\n");
timerStart("phylotree.change()");
Expand All @@ -280,10 +278,11 @@ export const change = function change({
const svgPropsToUpdate = new Set(); /* which SVG properties shall be changed. E.g. "fill", "stroke" */
const useModifySVGInStages = newLayout; /* use modifySVGInStages rather than modifySVG. Not used often. */


/* calculate dt */
const idealTransitionTime = 500;
let transitionTime = idealTransitionTime;
if ((Date.now() - this.timeLastRenderRequested) < idealTransitionTime * 2) {
if ((Date.now() - this.timeLastRenderRequested) < idealTransitionTime * 2 || performanceFlags.get("skipTreeAnimation")===true) {
transitionTime = 0;
}

Expand Down
11 changes: 5 additions & 6 deletions src/components/tree/phyloTree/labels.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { timerFlush } from "d3-timer";
import { NODE_VISIBLE } from "../../../util/globals";
import { numericToDateObject, prettifyDate } from "../../../util/dateHelpers";
import { getTraitFromNode } from "../../../util/treeMiscHelpers";
Expand Down Expand Up @@ -108,16 +107,16 @@ export const updateBranchLabels = function updateBranchLabels(dt) {
);
const labelSize = branchLabelSize(this.params.branchLabelKey);
const fontWeight = branchLabelFontWeight(this.params.branchLabelKey);
this.groups.branchLabels
let selection = this.groups.branchLabels
.selectAll(".branchLabel")
.transition()
.duration(dt)
.attr("x", (d) => d.xTip - 5)
if (dt) {
selection = selection.transition().duration(dt);
}
selection.attr("x", (d) => d.xTip - 5)
.attr("y", (d) => d.yTip - this.params.branchLabelPadY)
.style("visibility", visibility)
.style("font-weight", fontWeight)
.style("font-size", labelSize);
if (!dt) timerFlush();
};

export const removeBranchLabels = function removeBranchLabels() {
Expand Down
1 change: 1 addition & 0 deletions src/components/tree/reactD3Interface/change.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export const changePhyloTreeViaPropsComparison = (mainTree, phylotree, oldProps,
const change = Object.keys(args).length;
if (change) {
args.animationInProgress = newProps.animationPlayPauseButton === "Pause";
args.performanceFlags = newProps.performanceFlags;
// console.log('\n\n** ', phylotree.id, 'changePhyloTreeViaPropsComparison **', args);
phylotree.change(args);
}
Expand Down
6 changes: 4 additions & 2 deletions src/middleware/performanceFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ export const performanceFlags = (_store) => (next) => (action) => {
case types.URL_QUERY_CHANGE_WITH_COMPUTED_STATE: /* fallthrough */
case types.CLEAN_START: {
modifiedAction = {...action};
modifiedAction.controls.performanceFlags = calculate()
modifiedAction.controls.performanceFlags = calculate(action)
}
}
return next(modifiedAction || action); // send action to other middleware / reducers
};

function calculate() {
function calculate({tree}) {
const toggles = new Map();
const totalTipCount = tree?.nodes?.[0]?.fullTipCount;
toggles.set("skipTreeAnimation", totalTipCount > 4000);
return toggles;
}

0 comments on commit f09e0cb

Please sign in to comment.