Skip to content

Commit

Permalink
Merge pull request #435 from vgteam/readsPresentNodeSequences
Browse files Browse the repository at this point in the history
Enable removing node sequences when reads are present
  • Loading branch information
adamnovak committed May 20, 2024
2 parents 5b1a3f5 + cebe7bd commit 15cd82b
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 30 deletions.
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

0 comments on commit 15cd82b

Please sign in to comment.