From 52a7061ac63d9fdce39f40efdde13382e221779c Mon Sep 17 00:00:00 2001 From: shreyasun Date: Wed, 8 May 2024 08:26:12 -0700 Subject: [PATCH 1/4] Reconfigured how the node width is computed for different options, and added option for node sequences --- src/components/HeaderForm.js | 4 +-- src/components/TubeMap.js | 15 ++++++++-- src/components/TubeMapContainer.js | 1 + src/server.mjs | 3 +- src/util/tubemap.js | 46 +++++++++++++++++++++--------- 5 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/components/HeaderForm.js b/src/components/HeaderForm.js index a0a8166e..b30cd8ab 100644 --- a/src/components/HeaderForm.js +++ b/src/components/HeaderForm.js @@ -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// && !readsExist(this.state.tracks) }); handleGoButton = () => { @@ -975,7 +975,7 @@ class HeaderForm extends Component { {DataPositionFormRowComponent}
- {!readsExist(this.state.tracks) && ( + {/*!readsExist(this.state.tracks) &&*/ ( <>
diff --git a/src/server.mjs b/src/server.mjs index 5c66b80b..f13acfd1 100644 --- a/src/server.mjs +++ b/src/server.mjs @@ -542,9 +542,10 @@ 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; } diff --git a/src/util/tubemap.js b/src/util/tubemap.js index de56073f..9b66e3fe 100644 --- a/src/util/tubemap.js +++ b/src/util/tubemap.js @@ -142,10 +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, + // 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: {}, @@ -376,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) { @@ -545,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}`); @@ -3451,7 +3458,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) @@ -3474,7 +3481,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; @@ -3512,7 +3519,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 @@ -3588,7 +3595,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]); @@ -4185,19 +4192,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 = 1 + Math.log(node.sequenceLength) / Math.log(2); + node.pixelWidth = Math.round((node.width) * 8.401); + }); + break; + case "normal": nodes.forEach((node) => { node.width = node.sequenceLength; @@ -4220,6 +4235,9 @@ function generateNodeWidth() { } document.getElementById("dummytext").remove(); }); + break; + default: + throw new Error(`${config.nodeWidthOption} not implemented`) } } From 608d693124f03db7cf83f940eddff6dbec1f8651 Mon Sep 17 00:00:00 2001 From: shreyasun Date: Tue, 14 May 2024 09:17:56 -0700 Subject: [PATCH 2/4] Set up correct view when removing node sequences when reads are present --- src/components/TubeMap.js | 2 ++ src/server.mjs | 6 ++++-- src/util/tubemap.js | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/TubeMap.js b/src/components/TubeMap.js index 6520f9ec..4b4a1358 100644 --- a/src/components/TubeMap.js +++ b/src/components/TubeMap.js @@ -37,10 +37,12 @@ class TubeMap extends Component { updateVisOptions() { const visOptions = this.props.visOptions; 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); diff --git a/src/server.mjs b/src/server.mjs index f13acfd1..8fd1fd18 100644 --- a/src/server.mjs +++ b/src/server.mjs @@ -420,7 +420,8 @@ function removeNodeSequencesInPlace(graph){ return; } graph.node.forEach(function(node) { - node.sequence = ""; + node.sequenceLength = node.sequence.length; + delete node.sequence; }) } @@ -1584,11 +1585,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, }; diff --git a/src/util/tubemap.js b/src/util/tubemap.js index 9b66e3fe..7d8e3fe1 100644 --- a/src/util/tubemap.js +++ b/src/util/tubemap.js @@ -4176,7 +4176,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, }); }); @@ -4208,7 +4208,7 @@ function generateNodeWidth() { // 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 = 1 + Math.log(node.sequenceLength) / Math.log(2); + node.width = 10; node.pixelWidth = Math.round((node.width) * 8.401); }); break; From 8ab67b780e80580fdc9a48c5831910615820e4ad Mon Sep 17 00:00:00 2001 From: shreyasun Date: Tue, 14 May 2024 19:46:21 -0700 Subject: [PATCH 3/4] Added new option to customization accordian to disable checkbox if removing node sequences --- src/App.js | 2 ++ src/components/CustomizationAccordion.js | 5 ++--- src/components/HeaderForm.js | 4 ++-- src/server.mjs | 4 ---- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/App.js b/src/App.js index a9772cab..271741fd 100644 --- a/src/App.js +++ b/src/App.js @@ -71,6 +71,7 @@ class App extends Component { APIInterface: new ServerAPI(props.apiUrl) }; } + /** * Set which API implementation to query for graph data. @@ -251,6 +252,7 @@ class App extends Component { APIInterface={this.state.APIInterface} /> toggleFlag("compressedView")} /> Compressed view @@ -249,9 +250,6 @@ class VisualizationOptions extends Component { - - - ); @@ -259,6 +257,7 @@ class VisualizationOptions extends Component { } VisualizationOptions.propTypes = { + enableCompressedNodes: PropTypes.bool, handleMappingQualityCutoffChange: PropTypes.func.isRequired, setColorSetting: PropTypes.func.isRequired, tracks: PropTypes.array.isRequired, diff --git a/src/components/HeaderForm.js b/src/components/HeaderForm.js index b30cd8ab..2043f226 100644 --- a/src/components/HeaderForm.js +++ b/src/components/HeaderForm.js @@ -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 = () => { @@ -975,7 +975,7 @@ class HeaderForm extends Component { {DataPositionFormRowComponent}
- {/*!readsExist(this.state.tracks) &&*/ ( + {( <>