Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable removing node sequences when reads are present #435

Merged
merged 5 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class App extends Component {
APIInterface: new ServerAPI(props.apiUrl)
};
}


/**
* Set which API implementation to query for graph data.
Expand Down Expand Up @@ -251,6 +252,7 @@ class App extends Component {
APIInterface={this.state.APIInterface}
/>
<CustomizationAccordion
enableCompressedNodes={this.state.viewTarget.removeSequences}
visOptions={this.state.visOptions}
tracks={
this.state.dataOrigin === dataOriginTypes.API
Expand Down
5 changes: 2 additions & 3 deletions src/components/CustomizationAccordion.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ class VisualizationOptions extends Component {
<Input
type="checkbox"
checked={visOptions.compressedView}
disabled={this.props.enableCompressedNodes}
onChange={() => toggleFlag("compressedView")}
/>
Compressed view
Expand Down Expand Up @@ -249,16 +250,14 @@ class VisualizationOptions extends Component {
</Collapse>
</Card>




</div>
</Container>
);
}
}

VisualizationOptions.propTypes = {
enableCompressedNodes: PropTypes.bool,
handleMappingQualityCutoffChange: PropTypes.func.isRequired,
setColorSetting: PropTypes.func.isRequired,
tracks: PropTypes.array.isRequired,
Expand Down
4 changes: 2 additions & 2 deletions src/components/HeaderForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ class HeaderForm extends Component {
region: this.state.region,
dataType: this.state.dataType,
simplify: this.state.simplify && !readsExist(this.state.tracks),
removeSequences: this.state.removeSequences && !readsExist(this.state.tracks)
removeSequences: this.state.removeSequences
});

handleGoButton = () => {
Expand Down Expand Up @@ -975,7 +975,7 @@ class HeaderForm extends Component {
{DataPositionFormRowComponent}
</div>
<div className="d-flex justify-content-end align-items-start flex-shrink-0">
{!readsExist(this.state.tracks) && (
{(
<>
<Button
onClick={this.togglePopup}
Expand Down
17 changes: 14 additions & 3 deletions src/components/TubeMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,15 @@ class TubeMap extends Component {

updateVisOptions() {
const visOptions = this.props.visOptions;
visOptions.compressedView
? tubeMap.setNodeWidthOption(1)
: tubeMap.setNodeWidthOption(0);
if (this.props.nodeSequences){
// If node sequences aren't removed
visOptions.compressedView
? tubeMap.setNodeWidthOption("compressed")
: tubeMap.setNodeWidthOption("normal");
} else{
// If node sequences are removed
tubeMap.setNodeWidthOption("fixed");
}
tubeMap.setMergeNodesFlag(visOptions.removeRedundantNodes);
tubeMap.setTransparentNodesFlag(visOptions.transparentNodes);
tubeMap.setShowReadsFlag(visOptions.showReads);
Expand Down Expand Up @@ -69,6 +75,11 @@ TubeMap.propTypes = {
reads: PropTypes.array.isRequired,
region: PropTypes.array.isRequired,
visOptions: PropTypes.object.isRequired,
nodeSequences: PropTypes.bool
};

TubeMap.defaultProps = {
nodeSequences: true
};

export default TubeMap;
1 change: 1 addition & 0 deletions src/components/TubeMapContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ class TubeMapContainer extends Component {
coloredNodes: this.state.coloredNodes,
...this.props.visOptions,
}}
nodeSequences={!this.props.viewTarget.removeSequences}
/>
</div>
</div>
Expand Down
9 changes: 4 additions & 5 deletions src/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,8 @@ function removeNodeSequencesInPlace(graph){
return;
}
graph.node.forEach(function(node) {
node.sequence = "";
node.sequenceLength = node.sequence.length;
delete node.sequence;
})
}

Expand Down Expand Up @@ -542,9 +543,6 @@ async function getChunkedData(req, res, next) {
// client is going to send removeSequences = true if they don't want sequences of nodes to be displayed
req.removeSequences = false;
if (req.body.removeSequences) {
if (readsExist(req.body.tracks)) {
throw new BadRequestError("Can't remove node sequences if read tracks exist.");
}
req.removeSequences = true;
}

Expand Down Expand Up @@ -1599,11 +1597,12 @@ const fetchAndValidate = async (url, maxBytes, existingLocation = null) => {
"If-None-Match": ETagMap.get(url) || "-1",
};
}
let controller = timeoutController(config.fetchTimeout);
const options = {
method: "GET",
credentials: "omit",
cache: "default",
signal: timeoutController(config.fetchTimeout).signal,
signal: controller.signal,
headers: fetchHeader,
};

Expand Down
51 changes: 34 additions & 17 deletions src/util/tubemap.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,11 @@ const config = {
clickableNodesFlag: false,
showExonsFlag: false,
// Options for the width of sequence nodes:
// 0...scale node width linear with number of bases within node
// 1...scale node width with log2 of number of bases within node
// 2...scale node width with log10 of number of bases within node
nodeWidthOption: 0,
nodeIntervalThreshold: 50,
// normal...scale node width linear with number of bases within node
// compressed...scale node width with log2 of number of bases within node
// small...scale node width with 1% of number of bases within node
// fixed...set fixed node width to 1 base
nodeWidthOption: "normal",
showReads: true,
showSoftClips: true,
colorSchemes: {},
Expand Down Expand Up @@ -377,8 +377,14 @@ export function setColorSet(fileID, newColor) {
}

// sets which option should be used for calculating the node width from its sequence length
/*
- normal: Node lengths are computed based on the sequence length, and the sequences are displayed
- compressed: Node lengths are computed based on the log of the sequence length, and the sequences aren't displayed
- small: Node lengths are computed based on the sequence length / 100, and the sequences aren't displayed
- fixed: Node lengths are set to 1 base unit, and the sequences aren't displayed
*/
export function setNodeWidthOption(value) {
if (value === 0 || value === 1 || value === 2) {
if (["normal", "compressed", "small", "fixed"].includes(value)) {
if (config.nodeWidthOption !== value) {
config.nodeWidthOption = value;
if (svg !== undefined) {
Expand Down Expand Up @@ -546,9 +552,9 @@ function createTubeMap() {
// all drawn nodes are grouped
let nodeGroup = svg.append("g").attr("class", "node");
drawNodes(dNodes, nodeGroup);
if (config.nodeWidthOption === 0) drawLabels(dNodes);
if (config.nodeWidthOption === "normal") drawLabels(dNodes);
if (trackForRuler !== undefined) drawRuler();
if (config.nodeWidthOption === 0) drawMismatches(); // TODO: call this before drawLabels and fix d3 data/append/enter stuff
if (config.nodeWidthOption === "normal") drawMismatches(); // TODO: call this before drawLabels and fix d3 data/append/enter stuff
if (DEBUG) {
console.log(`number of tracks: ${numberOfTracks}`);
console.log(`number of nodes: ${numberOfNodes}`);
Expand Down Expand Up @@ -3453,7 +3459,7 @@ export function coverage(node, allReads) {

// draw seqence labels for nodes
function drawLabels(dNodes) {
if (config.nodeWidthOption === 0) {
if (config.nodeWidthOption === "normal") {
svg
.selectAll("text")
.data(dNodes)
Expand Down Expand Up @@ -3512,7 +3518,7 @@ function drawRuler() {

// How often should we have a tick in bp?
let markingInterval = 100;
if (config.nodeWidthOption === 0) markingInterval = 20;
if (config.nodeWidthOption === "normal") markingInterval = 20;
// How close may markings be in image space?
const markingClearance = 80;

Expand Down Expand Up @@ -3550,7 +3556,7 @@ function drawRuler() {
: // Otherwise, add them to the left side
indexIntoVisitToMark;

if (config.nodeWidthOption !== 0 && !is_region) {
if (config.nodeWidthOption !== "normal" && !is_region) {
// Actually always mark at an edge of the node, if we are scaling the node nonlinearly
// and if we are not highlighting the input region
offsetIntoNodeForward = currentNodeIsReverse
Expand Down Expand Up @@ -3633,7 +3639,7 @@ function drawRuler() {
currentNodeIsReverse
);

if (config.nodeWidthOption === 0 || !alreadyMarkedNode) {
if (config.nodeWidthOption === "normal" || !alreadyMarkedNode) {
// This is a mark we are not filtering due to node compression.
// Make the mark
ticks.push([nextUnmarkedIndex, xCoordOfMarking]);
Expand Down Expand Up @@ -4255,7 +4261,7 @@ export function vgExtractNodes(vg) {
vg.node.forEach((node) => {
result.push({
name: `${node.id}`,
sequenceLength: node.sequence.length,
sequenceLength: node.sequenceLength ?? node.sequence.length,
seq: node.sequence,
});
});
Expand All @@ -4271,19 +4277,27 @@ function generateNodeWidth() {
});

switch (config.nodeWidthOption) {
case 1:
case "compressed":
nodes.forEach((node) => {
node.width = 1 + Math.log(node.sequenceLength) / Math.log(2);
node.pixelWidth = Math.round((node.width - 1) * 8.401);
});
break;
case 2:
case "small":
nodes.forEach((node) => {
node.width = node.sequenceLength / 100;
node.pixelWidth = Math.round((node.width - 1) * 8.401);
});
break;
default:
case "fixed":
// when there's no reads in the node, it should be a little wider
nodes.forEach((node) => {
console.log("node.sequenceLength:", node.sequenceLength);
node.width = 10;
node.pixelWidth = Math.round((node.width) * 8.401);
});
break;
case "normal":
nodes.forEach((node) => {
node.width = node.sequenceLength;

Expand All @@ -4293,7 +4307,7 @@ function generateNodeWidth() {
.attr("x", 0)
.attr("y", 100)
.attr("id", "dummytext")
.text(node.seq.substr(1))
.text(node.seq ? node.seq.substr(1) : "A")
.attr("font-family", fonts)
.attr("font-size", "14px")
.attr("fill", "black")
Expand All @@ -4306,6 +4320,9 @@ function generateNodeWidth() {
}
document.getElementById("dummytext").remove();
});
break;
default:
throw new Error(`${config.nodeWidthOption} not implemented`)
}
}

Expand Down
Loading