Skip to content

Commit

Permalink
[entropy] don't update if not on screen
Browse files Browse the repository at this point in the history
Use an intersection observer to detect when the entropy panel is visible
on the screen (viewport). When it's offscreen we don't update entropy
data (an expensive calculation). When it comes back onscreen we
recalculate the entropy data if it has become stale.

This results in slightly strange behaviour when the entropy panel will
be shown with no bars and they'll be drawn after a slight delay (while
the data's recalculated). The wins are much improved performance when
the entropy panel is not on screen (which is common).
  • Loading branch information
jameshadfield committed Oct 28, 2024
1 parent e6731a5 commit 281bc90
Show file tree
Hide file tree
Showing 6 changed files with 30 additions and 6 deletions.
2 changes: 1 addition & 1 deletion src/actions/entropy.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const updateEntropyVisibility = debounce((dispatch, getState) => {
controls.animationPlayPauseButton !== "Play"
) {return;}

if (!controls.panelsToDisplay.includes("entropy")) {
if (!controls.panelsToDisplay.includes("entropy") || entropy.onScreen===false) {
if (entropy.bars===undefined) {
return; // no need to dispatch another action - the state's already been invalidated
}
Expand Down
1 change: 1 addition & 0 deletions src/actions/recomputeReduxState.js
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,7 @@ export const createStateFromQueryOrJSONs = ({
const [entropyBars, entropyMaxYVal] = calcEntropyInView(tree.nodes, tree.visibility, entropy.selectedCds, entropy.showCounts);
entropy.bars = entropyBars;
entropy.maxYVal = entropyMaxYVal;
entropy.onScreen = true;
}

/* update frequencies if they exist (not done for new JSONs) */
Expand Down
1 change: 1 addition & 0 deletions src/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const CHANGE_TREE_ROOT_IDX = "CHANGE_TREE_ROOT_IDX";
export const TOGGLE_NARRATIVE = "TOGGLE_NARRATIVE";
export const ENTROPY_DATA = "ENTROPY_DATA";
export const ENTROPY_COUNTS_TOGGLE = "ENTROPY_COUNTS_TOGGLE";
export const ENTROPY_ONSCREEN_CHANGE = "ENTROPY_ONSCREEN_CHANGE";
export const PAGE_CHANGE = "PAGE_CHANGE";
export const MIDDLEWARE_ONLY_ANIMATION_STARTED = "MIDDLEWARE_ONLY_ANIMATION_STARTED";
export const URL_QUERY_CHANGE_WITH_COMPUTED_STATE = "URL_QUERY_CHANGE_WITH_COMPUTED_STATE";
Expand Down
1 change: 1 addition & 0 deletions src/components/entropy/entropyD3.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,7 @@ EntropyChart.prototype._highlightSelectedBars = function _highlightSelectedBars(
/* draw the bars (for each base / aa) */
EntropyChart.prototype._drawBars = function _drawBars() {
if (!this.okToDrawBars) {return;}

this._groups.mainBars.selectAll("*").remove();

// bars may be undefined (indicating the underlying data became stale)
Expand Down
26 changes: 22 additions & 4 deletions src/components/entropy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { tabGroup, tabGroupMember, tabGroupMemberSelected } from "../../globalSt
import EntropyChart from "./entropyD3";
import InfoPanel from "./infoPanel";
import { changeEntropyCdsSelection, showCountsNotEntropy } from "../../actions/entropy";
import { ENTROPY_DATA } from "../../actions/types";
import { ENTROPY_DATA, ENTROPY_ONSCREEN_CHANGE } from "../../actions/types";
import { timerStart, timerEnd } from "../../util/perf";
import { encodeColorByGenotype } from "../../util/getGenotype";
import { nucleotide_gene } from "../../util/globals";
Expand Down Expand Up @@ -70,6 +70,7 @@ const getStyles = (width) => {
maxYVal: state.entropy.maxYVal,
showCounts: state.entropy.showCounts,
loaded: state.entropy.loaded,
onScreen: state.entropy.onScreen,
colorBy: state.controls.colorBy,
/**
* Note that zoomMin & zoomMax only represent the state when changed by a URL
Expand Down Expand Up @@ -178,13 +179,15 @@ class Entropy extends React.Component {
</div>
);
}
requestRecalculationOfBars() {
requestRecalculationOfBars(onScreen) {
/* The <Entropy> component is now responsible for requesting a recalculation of the underlying entropy data
as this allows us to skip calculations when (e.g.) the panel is not rendered */
this.props.dispatch((dispatch, getState) => {
const { entropy, tree } = getState();
const [bars, maxYVal] = calcEntropyInView(tree.nodes, tree.visibility, entropy.selectedCds, entropy.showCounts);
dispatch({type: ENTROPY_DATA, data: bars, maxYVal});
const action = {type: ENTROPY_DATA, data: bars, maxYVal};
if (onScreen!==undefined) action.onScreen = onScreen;
dispatch(action);
});
}
setUp(props) {
Expand All @@ -209,9 +212,24 @@ class Entropy extends React.Component {
}
this.setState({chart});
}
visibilityOnScreenChange(entries) {
if (entries.length!==1) {
return console.error(`Unexpected IntersectionObserver callback entries of length`, entries.length);
}
const onScreen = entries[0].isIntersecting;
if (onScreen===this.props.onScreen) return; // can happen when component initially rendered
// if gone off screen or come back on screen with the bars still valid then it's a simple toggle action
if (!onScreen || this.props.bars) {
return this.props.dispatch({type: ENTROPY_ONSCREEN_CHANGE, onScreen})
}
// else if back on screen and the bars are invalid then we want to regenerate them
this.requestRecalculationOfBars(onScreen);
}
componentDidMount() {
if (this.props.loaded) {
this.setUp(this.props);
this.setUp(this.props);
const observer = new IntersectionObserver(this.visibilityOnScreenChange.bind(this), {threshold: 0.0});
observer.observe(this.d3entropy)
}
}
UNSAFE_componentWillReceiveProps(nextProps) {
Expand Down
5 changes: 4 additions & 1 deletion src/reducers/entropy.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ const Entropy = (state = defaultEntropyState(), action) => {
return Object.assign({}, state, {
loaded: true,
bars: action.data,
maxYVal: action.maxYVal
maxYVal: action.maxYVal,
onScreen: Object.hasOwn(action, 'onScreen') ? action.onScreen : state.onScreen,
});
case types.ENTROPY_ONSCREEN_CHANGE:
return {...state, onScreen: action.onScreen};
case types.ENTROPY_COUNTS_TOGGLE:
return Object.assign({}, state, {
showCounts: action.showCounts
Expand Down

0 comments on commit 281bc90

Please sign in to comment.