Skip to content

Commit 122cbdc

Browse files
Merge pull request #69 from mskilab-org/perf/scatterplot-gpu-depth-culling
Perf/scatterplot gpu depth culling
2 parents 5cd83f9 + 6003b09 commit 122cbdc

File tree

12 files changed

+950
-475
lines changed

12 files changed

+950
-475
lines changed

src/components/cytobandsPlot/index.js

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import { PropTypes } from "prop-types";
33
import * as d3 from "d3";
44
import { connect } from "react-redux";
55
import { withTranslation } from "react-i18next";
6+
import { throttle } from "lodash";
67
import { measureText } from "../../helpers/utility";
78
import { createChromosomePaths } from "../../helpers/cytobandsUtil";
89
import Wrapper from "./index.style";
910
import settingsActions from "../../redux/settings/actions";
11+
import { store } from "../../redux/store";
1012

1113
const { updateDomains, updateHoveredLocation } = settingsActions;
1214

@@ -46,10 +48,7 @@ class CytobandsPlot extends Component {
4648
nextProps.height !== this.props.height ||
4749
nextState.selectedCytoband?.id !== this.state.selectedCytoband?.id ||
4850
nextState.tooltip.x !== this.state.tooltip.x ||
49-
nextState.tooltip.y !== this.state.tooltip.y ||
50-
nextProps.hoveredLocation !== this.props.hoveredLocation ||
51-
nextProps.hoveredLocationPanelIndex !==
52-
this.props.hoveredLocationPanelIndex
51+
nextState.tooltip.y !== this.state.tooltip.y
5352
);
5453
}
5554

@@ -80,42 +79,51 @@ class CytobandsPlot extends Component {
8079
.translate(-s[0], 0)
8180
);
8281
});
82+
83+
// Subscribe to Redux store for hover updates (bypassing React)
84+
this.unsubscribeHover = store.subscribe(() => {
85+
const state = store.getState();
86+
const { hoveredLocation, hoveredLocationPanelIndex } = state.Settings;
87+
this.updateHoverLine(hoveredLocation, hoveredLocationPanelIndex);
88+
});
8389
}
8490

8591
componentDidUpdate(prevProps, prevState) {
86-
const {
87-
domains,
88-
hoveredLocationPanelIndex,
89-
hoveredLocation,
90-
chromoBins,
91-
zoomedByCmd,
92-
} = this.props;
92+
const { domains, zoomedByCmd } = this.props;
9393

94-
this.panels.forEach((panel, index) => {
95-
let domain = domains[index];
96-
var s = [
97-
panel.panelGenomeScale(domain[0]),
98-
panel.panelGenomeScale(domain[1]),
99-
];
100-
d3.select(this.plotContainer)
101-
.select(`#panel-rect-${index}`)
102-
.attr("preserveAspectRatio", "xMinYMin meet")
103-
.call(
104-
panel.zoom.filter(
105-
(event) => !zoomedByCmd || (!event.button && event.metaKey)
106-
)
107-
);
108-
d3.select(this.plotContainer)
109-
.select(`#panel-rect-${index}`)
110-
.call(
111-
panel.zoom.filter(
112-
(event) => !zoomedByCmd || (!event.button && event.metaKey)
113-
).transform,
114-
d3.zoomIdentity
115-
.scale(panel.panelWidth / (s[1] - s[0]))
116-
.translate(-s[0], 0)
117-
);
118-
});
94+
// Only run expensive zoom transforms if domains changed
95+
const domainsChanged = prevProps.domains.toString() !== domains.toString();
96+
if (domainsChanged) {
97+
this.panels.forEach((panel, index) => {
98+
let domain = domains[index];
99+
var s = [
100+
panel.panelGenomeScale(domain[0]),
101+
panel.panelGenomeScale(domain[1]),
102+
];
103+
d3.select(this.plotContainer)
104+
.select(`#panel-rect-${index}`)
105+
.attr("preserveAspectRatio", "xMinYMin meet")
106+
.call(
107+
panel.zoom.filter(
108+
(event) => !zoomedByCmd || (!event.button && event.metaKey)
109+
)
110+
);
111+
d3.select(this.plotContainer)
112+
.select(`#panel-rect-${index}`)
113+
.call(
114+
panel.zoom.filter(
115+
(event) => !zoomedByCmd || (!event.button && event.metaKey)
116+
).transform,
117+
d3.zoomIdentity
118+
.scale(panel.panelWidth / (s[1] - s[0]))
119+
.translate(-s[0], 0)
120+
);
121+
});
122+
}
123+
}
124+
125+
updateHoverLine(hoveredLocation, hoveredLocationPanelIndex) {
126+
const { chromoBins } = this.props;
119127

120128
if (this.panels[hoveredLocationPanelIndex]) {
121129
d3.select(this.plotContainer)
@@ -152,6 +160,13 @@ class CytobandsPlot extends Component {
152160
}
153161
}
154162

163+
componentWillUnmount() {
164+
// Unsubscribe from hover updates
165+
if (this.unsubscribeHover) {
166+
this.unsubscribeHover();
167+
}
168+
}
169+
155170
zooming(event, index) {
156171
let panel = this.panels[index];
157172
let newDomain = event.transform
@@ -204,12 +219,12 @@ class CytobandsPlot extends Component {
204219
this.zooming(event, index);
205220
}
206221

207-
handleMouseMove = (e, panelIndex) => {
222+
handleMouseMove = throttle((e, panelIndex) => {
208223
this.props.updateHoveredLocation(
209224
this.panels[panelIndex].xScale.invert(d3.pointer(e)[0]),
210225
panelIndex
211226
);
212-
};
227+
}, 16, { leading: true, trailing: false });
213228

214229
handleMouseOut = (e, panelIndex) => {
215230
this.props.updateHoveredLocation(null, panelIndex);

src/components/genesPlotHiglass/index.js

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import { PropTypes } from "prop-types";
33
import * as d3 from "d3";
44
import { connect } from "react-redux";
55
import { withTranslation } from "react-i18next";
6+
import { throttle } from "lodash";
67
import { filterGenesByOverlap, measureText } from "../../helpers/utility";
78
import Wrapper from "./index.style";
89
import settingsActions from "../../redux/settings/actions";
10+
import { store } from "../../redux/store";
911

1012
const { updateDomains, updateHoveredLocation } = settingsActions;
1113

@@ -38,12 +40,9 @@ class GenesPlot extends Component {
3840
shouldComponentUpdate(nextProps, nextState) {
3941
return (
4042
nextProps.domains.toString() !== this.props.domains.toString() ||
41-
nextProps.genesList.toString() !== this.props.genesList.toString() ||
43+
nextProps.genesList !== this.props.genesList ||
4244
nextProps.width !== this.props.width ||
43-
nextProps.height !== this.props.height ||
44-
nextProps.hoveredLocation !== this.props.hoveredLocation ||
45-
nextProps.hoveredLocationPanelIndex !==
46-
this.props.hoveredLocationPanelIndex
45+
nextProps.height !== this.props.height
4746
);
4847
}
4948

@@ -74,42 +73,51 @@ class GenesPlot extends Component {
7473
.translate(-s[0], 0)
7574
);
7675
});
76+
77+
// Subscribe to Redux store for hover updates (bypassing React)
78+
this.unsubscribeHover = store.subscribe(() => {
79+
const state = store.getState();
80+
const { hoveredLocation, hoveredLocationPanelIndex } = state.Settings;
81+
this.updateHoverLine(hoveredLocation, hoveredLocationPanelIndex);
82+
});
7783
}
7884

7985
componentDidUpdate(prevProps, prevState) {
80-
const {
81-
domains,
82-
hoveredLocationPanelIndex,
83-
hoveredLocation,
84-
chromoBins,
85-
zoomedByCmd,
86-
} = this.props;
86+
const { domains, zoomedByCmd } = this.props;
8787

88-
this.panels.forEach((panel, index) => {
89-
let domain = domains[index];
90-
var s = [
91-
panel.panelGenomeScale(domain[0]),
92-
panel.panelGenomeScale(domain[1]),
93-
];
94-
d3.select(this.plotContainer)
95-
.select(`#panel-rect-${index}`)
96-
.attr("preserveAspectRatio", "xMinYMin meet")
97-
.call(
98-
panel.zoom.filter(
99-
(event) => !zoomedByCmd || (!event.button && event.metaKey)
100-
)
101-
);
102-
d3.select(this.plotContainer)
103-
.select(`#panel-rect-${index}`)
104-
.call(
105-
panel.zoom.filter(
106-
(event) => !zoomedByCmd || (!event.button && event.metaKey)
107-
).transform,
108-
d3.zoomIdentity
109-
.scale(panel.panelWidth / (s[1] - s[0]))
110-
.translate(-s[0], 0)
111-
);
112-
});
88+
// Only run expensive zoom transforms if domains changed
89+
const domainsChanged = prevProps.domains.toString() !== domains.toString();
90+
if (domainsChanged) {
91+
this.panels.forEach((panel, index) => {
92+
let domain = domains[index];
93+
var s = [
94+
panel.panelGenomeScale(domain[0]),
95+
panel.panelGenomeScale(domain[1]),
96+
];
97+
d3.select(this.plotContainer)
98+
.select(`#panel-rect-${index}`)
99+
.attr("preserveAspectRatio", "xMinYMin meet")
100+
.call(
101+
panel.zoom.filter(
102+
(event) => !zoomedByCmd || (!event.button && event.metaKey)
103+
)
104+
);
105+
d3.select(this.plotContainer)
106+
.select(`#panel-rect-${index}`)
107+
.call(
108+
panel.zoom.filter(
109+
(event) => !zoomedByCmd || (!event.button && event.metaKey)
110+
).transform,
111+
d3.zoomIdentity
112+
.scale(panel.panelWidth / (s[1] - s[0]))
113+
.translate(-s[0], 0)
114+
);
115+
});
116+
}
117+
}
118+
119+
updateHoverLine(hoveredLocation, hoveredLocationPanelIndex) {
120+
const { chromoBins } = this.props;
113121

114122
if (this.panels[hoveredLocationPanelIndex]) {
115123
d3.select(this.plotContainer)
@@ -146,6 +154,13 @@ class GenesPlot extends Component {
146154
}
147155
}
148156

157+
componentWillUnmount() {
158+
// Unsubscribe from hover updates
159+
if (this.unsubscribeHover) {
160+
this.unsubscribeHover();
161+
}
162+
}
163+
149164
zooming(event, index) {
150165
let panel = this.panels[index];
151166
let newDomain = event.transform
@@ -198,12 +213,12 @@ class GenesPlot extends Component {
198213
this.zooming(event, index);
199214
}
200215

201-
handleMouseMove = (e, panelIndex) => {
216+
handleMouseMove = throttle((e, panelIndex) => {
202217
this.props.updateHoveredLocation(
203218
this.panels[panelIndex].xScale.invert(d3.pointer(e)[0]),
204219
panelIndex
205220
);
206-
};
221+
}, 16, { leading: true, trailing: false });
207222

208223
handleMouseOut = (e, panelIndex) => {
209224
this.props.updateHoveredLocation(null, panelIndex);

src/components/genomePanel/index.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,40 @@ class GenomePanel extends Component {
4747
};
4848
}
4949

50+
shouldComponentUpdate(nextProps, nextState) {
51+
// Check state changes
52+
if (nextState !== this.state) return true;
53+
54+
// Check domain changes (zoom/pan)
55+
const domainsChanged =
56+
nextProps.domains?.toString() !== this.props.domains?.toString();
57+
if (domainsChanged) return true;
58+
59+
// Check commonRangeY by reference (memoized in utility.js)
60+
if (nextProps.commonRangeY !== this.props.commonRangeY) return true;
61+
62+
// Check data changes
63+
if (nextProps.genome !== this.props.genome) return true;
64+
65+
// Check visibility/loading state
66+
if (
67+
nextProps.loading !== this.props.loading ||
68+
nextProps.visible !== this.props.visible ||
69+
nextProps.inViewport !== this.props.inViewport ||
70+
nextProps.error !== this.props.error
71+
)
72+
return true;
73+
74+
// Check dimension changes
75+
if (
76+
nextProps.height !== this.props.height ||
77+
nextProps.chromoBins !== this.props.chromoBins
78+
)
79+
return true;
80+
81+
return false;
82+
}
83+
5084
componentDidMount() {
5185
this.updateWidth();
5286
window.addEventListener("resize", this.updateWidth);

0 commit comments

Comments
 (0)