From f8639e5063c199172993384df0fd5e2b7cdb11a5 Mon Sep 17 00:00:00 2001 From: shreyasun Date: Fri, 22 Sep 2023 14:33:07 -0700 Subject: [PATCH 1/5] Adding processing file of node colors to server to accept user-specified node to be colored separately from the other nodes. Added functions to process those node names and style them differently --- src/components/TubeMap.js | 1 + src/components/TubeMapContainer.js | 5 +++- src/server.mjs | 39 ++++++++++++++++++++++++++++++ src/util/tubemap.js | 30 ++++++++++++++++++++--- 4 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/components/TubeMap.js b/src/components/TubeMap.js index 292b6a58..694939c5 100644 --- a/src/components/TubeMap.js +++ b/src/components/TubeMap.js @@ -40,6 +40,7 @@ class TubeMap extends Component { tubeMap.setTransparentNodesFlag(visOptions.transparentNodes); tubeMap.setShowReadsFlag(visOptions.showReads); tubeMap.setSoftClipsFlag(visOptions.showSoftClips); + tubeMap.setColoredNodes(visOptions.coloredNodes); for (let key of Object.keys(visOptions.colorSchemes)) { // Apply color-by-mapping-quality parameter to all the schemes. diff --git a/src/components/TubeMapContainer.js b/src/components/TubeMapContainer.js index 8c063a6a..85d48374 100644 --- a/src/components/TubeMapContainer.js +++ b/src/components/TubeMapContainer.js @@ -119,7 +119,7 @@ class TubeMapContainer extends Component { tracks={this.state.tracks} reads={this.state.reads} region={this.state.region} - visOptions={this.props.visOptions} + visOptions={{coloredNodes: this.state.coloredNodes, ...this.props.visOptions}} /> @@ -188,13 +188,16 @@ class TubeMapContainer extends Component { const reads = readsArr.flat(); const region = json.region; + const coloredNodes = json.coloredNodes; this.setState({ isLoading: false, nodes, tracks, reads, region, + coloredNodes }); + } } catch (error) { this.handleFetchError( diff --git a/src/server.mjs b/src/server.mjs index 674785bb..54338bdc 100644 --- a/src/server.mjs +++ b/src/server.mjs @@ -970,6 +970,43 @@ function processRegionFile(req, res, next) { lineReader.on("close", () => { console.timeEnd("processing region file"); + processNodeColorsFile(req, res, next); + }); + } catch (error) { + return next(error); + } +} + +function processNodeColorsFile(req, res, next) { + try { + console.time("processing node colors file"); + const nodeColorsFile = `${req.chunkDir}/nodeColors.tsv`; + if (!isAllowedPath(nodeColorsFile)) { + throw new BadRequestError( + "Path to node colors file not allowed: " + nodeColorsFile + ); + } + + req.coloredNodes = []; + + // check if file exists + if (!fs.existsSync(nodeColorsFile)) { + cleanUpAndSendResult(req, res, next); + return; + } + + const lineReader = rl.createInterface({ + input: fs.createReadStream(nodeColorsFile), + }); + + lineReader.on("line", (line) => { + console.log("Node name: " + line); + const nodeName = line.replace("\n", ""); + req.coloredNodes.push(nodeName); + }); + + lineReader.on("close", () => { + console.timeEnd("processing node colors file"); cleanUpAndSendResult(req, res, next); }); } catch (error) { @@ -1001,6 +1038,8 @@ function cleanUpAndSendResult(req, res, next) { result.graph = req.graph; result.gam = req.withGam === true ? req.gamResults : []; result.region = req.region; + result.coloredNodes = req.coloredNodes; + // add new nodenames field res.json(result); console.timeEnd("request-duration"); } catch (error) { diff --git a/src/util/tubemap.js b/src/util/tubemap.js index a7b4e368..2b485c15 100644 --- a/src/util/tubemap.js +++ b/src/util/tubemap.js @@ -156,6 +156,10 @@ const config = { }, }; + + + + // variables for storing info which can be directly translated into drawing instructions let trackRectangles = []; let trackCurves = []; @@ -386,6 +390,10 @@ export function setNodeWidthOption(value) { } } +export function setColoredNodes(value){ + config.coloredNodes = value; +} + // sets callback function that would generate React popup of track information. The callback would // accept an array argument of track attribute pairs containing attribute name as a string and attribute value // as a string or number, to be displayed. @@ -3132,7 +3140,7 @@ function drawNodes(dNodes, groupNode) { } }); - //let nodeGroup = svg.append("g").attr("class", "node") + console.log("config:", config) groupNode .selectAll("node") @@ -3145,14 +3153,28 @@ function drawNodes(dNodes, groupNode) { .on("mouseout", nodeMouseOut) .on("dblclick", nodeDoubleClick) .on("click", nodeSingleClick) - .style("fill", config.transparentNodesFlag ? "none" : "#fff") - .style("fill-opacity", config.showExonsFlag ? "0.4" : "0.6") - .style("stroke", "black") + .style("fill", (d) => colorNodes(d.name)["fill"]) + .style("fill-opacity", (d) => colorNodes(d.name)["fill-opacity"]) + .style("stroke", (d) => colorNodes(d.name)["outline"]) .style("stroke-width", "2px") .append("svg:title") .text((d) => getPopUpNodeText(d)); } +function colorNodes(nodeName){ + let nodesColors = {}; + if (config.coloredNodes.includes(nodeName)){ + nodesColors["fill"] = "#ffc0cb" + nodesColors["fill-opacity"] = "0.4" + nodesColors["outline"] = "#ff0000" + } else { + nodesColors["fill"] = "#ffffff" + nodesColors["fill-opacity"] = "0.4" + nodesColors["outline"] = "#000000" + } + return nodesColors; +} + function getPopUpNodeText(node) { return `Node ID: ${node.name}` + (node.switched ? ` (reversed)` : ``) + `\n`; } From 463c53171b518528c5e7764d1cd362ffff87f7c4 Mon Sep 17 00:00:00 2001 From: shreyasun Date: Wed, 27 Sep 2023 09:43:43 -0700 Subject: [PATCH 2/5] Updated readme to include information about how to color nodes --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 03b2fbda..38fb34b9 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ This will save some time during the interactive visualization, especially if the The net result needs to be one or more chunk directories on disk, referenced from a BED file. -To generate each chunk, you can use the `prepare_chunks.sh` script. You ought to run it from the directory containing your input files and where your output chunks will be stored (i.e. the `dataPath` in `sequenceTubeMpas/src/config.json`), which defaults to the `exampleData` directory in the repo. +To generate each chunk, you can use the `prepare_chunks.sh` script. You ought to run it from the directory containing your input files and where your output chunks will be stored (i.e. the `dataPath` in `sequenceTubeMaps/src/config.json`), which defaults to the `exampleData` directory in the repo. For example: @@ -162,6 +162,23 @@ Note each column is seperated by tabs This BED file needs to be in the `dataPath` directory, or it can be hosted on the web along with its chunk directories and accessed via URL. +If you want certain nodes of the graph to be colored, place the node names to be colored in a `nodeColors.tsv` file, with a node name on each line, within output directory of the chunk. When rendered, these specified nodes will be colored differently than other nodes. + +You can use `prepare_chunks.sh` script to generate this additional `nodeColors.tsv` by adding an additional option. Here is an example: + +``` +cd exampleData/ +../scripts/prepare_chunk.sh -x mygraph.xg -h mygraph.gbwt -r chr1:1-100 -d 'Region A' -o chunk-chr1-1-100 -g mygam1.gam -g mygam2.gam -n "1 2 3" >> mychunks.bed +``` + +Adding this additional `n` flag will allow a string space delimited input of node names which will be outputted to `nodeColors.tsv`. + +``` +1 +2 +3 +``` + ##### Pre-made subgraphs You may want to look at a graph that has already been extracted from a larger graph. From fc6f09dd1cf436fe4cd3ba2af1a9728a149fcf80 Mon Sep 17 00:00:00 2001 From: shreyasun Date: Wed, 27 Sep 2023 09:44:44 -0700 Subject: [PATCH 3/5] Updated script to accept a an argument for nodes to be colored and place those nodes into a file --- scripts/prepare_chunks.sh | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/prepare_chunks.sh b/scripts/prepare_chunks.sh index af4c00dd..9ba1898e 100755 --- a/scripts/prepare_chunks.sh +++ b/scripts/prepare_chunks.sh @@ -8,7 +8,7 @@ function usage() { exit 1 } -while getopts x:h:g:r:o:d: flag +while getopts x:h:g:r:o:d:n: flag do case "${flag}" in x) GRAPH_FILE=${OPTARG};; @@ -17,6 +17,7 @@ do r) REGION=${OPTARG};; o) OUTDIR=${OPTARG};; d) DESC="${OPTARG}";; + n) NODE_COLORS="${OPTARG}";; *) usage ;; @@ -56,6 +57,7 @@ echo >&2 "Graph File: " $GRAPH_FILE echo >&2 "Haplotype File: " $HAPLOTYPE_FILE echo >&2 "Region: " $REGION echo >&2 "Output Directory: " $OUTDIR +echo >&2 "Node colors: " $NODE_COLORS rm -fr $OUTDIR mkdir -p $OUTDIR @@ -78,6 +80,15 @@ for GAM_FILE in "${GAM_FILES[@]}"; do vg_chunk_params+=(-a $GAM_FILE) done +# construct node file +if [[ ! -z "${NODE_COLORS}" ]] ; then + node_names_arr=($NODE_COLORS); + for NODENAME in "${node_names_arr[@]}"; do + echo >&2 "$NODENAME" + printf "$NODENAME\n" >> $OUTDIR/nodeColors.tsv + done +fi + # Call vg chunk vg chunk "${vg_chunk_params[@]}" > $OUTDIR/chunk.vg @@ -88,4 +99,6 @@ done # Print BED line cat $OUTDIR/regions.tsv | cut -f1-3 | tr -d "\n" +cat $OUTDIR/nodeColors.tsv + printf "\t${DESC}\t${OUTDIR}\n" From e269899c87855cd66f32212c4ad6974b13313806 Mon Sep 17 00:00:00 2001 From: Adam Novak Date: Wed, 27 Sep 2023 12:39:46 -0700 Subject: [PATCH 4/5] Apply minor suggestions --- scripts/prepare_chunks.sh | 2 -- src/server.mjs | 1 - src/util/tubemap.js | 2 ++ 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/prepare_chunks.sh b/scripts/prepare_chunks.sh index 9ba1898e..4f81f9ec 100755 --- a/scripts/prepare_chunks.sh +++ b/scripts/prepare_chunks.sh @@ -99,6 +99,4 @@ done # Print BED line cat $OUTDIR/regions.tsv | cut -f1-3 | tr -d "\n" -cat $OUTDIR/nodeColors.tsv - printf "\t${DESC}\t${OUTDIR}\n" diff --git a/src/server.mjs b/src/server.mjs index 54338bdc..fc0377e5 100644 --- a/src/server.mjs +++ b/src/server.mjs @@ -1039,7 +1039,6 @@ function cleanUpAndSendResult(req, res, next) { result.gam = req.withGam === true ? req.gamResults : []; result.region = req.region; result.coloredNodes = req.coloredNodes; - // add new nodenames field res.json(result); console.timeEnd("request-duration"); } catch (error) { diff --git a/src/util/tubemap.js b/src/util/tubemap.js index 2b485c15..61224c0c 100644 --- a/src/util/tubemap.js +++ b/src/util/tubemap.js @@ -3161,6 +3161,8 @@ function drawNodes(dNodes, groupNode) { .text((d) => getPopUpNodeText(d)); } +// Given a node name, return an object with "fill", "fill-opacity", and "outline" +// keys describing what colors should be used to draw it. function colorNodes(nodeName){ let nodesColors = {}; if (config.coloredNodes.includes(nodeName)){ From 88de97e84dd268ed21476f44950264e4a612c8af Mon Sep 17 00:00:00 2001 From: Adam Novak Date: Wed, 27 Sep 2023 14:33:12 -0700 Subject: [PATCH 5/5] Port node coloring setup over to local chunk script --- scripts/prepare_local_chunk.sh | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/prepare_local_chunk.sh b/scripts/prepare_local_chunk.sh index 63cad98a..ebb289b3 100755 --- a/scripts/prepare_local_chunk.sh +++ b/scripts/prepare_local_chunk.sh @@ -8,7 +8,7 @@ function usage() { exit 1 } -while getopts x:g:r:o:d: flag +while getopts x:g:r:o:d:n: flag do case "${flag}" in x) GRAPH_FILE=${OPTARG};; @@ -16,6 +16,7 @@ do r) REGION=${OPTARG};; o) OUTDIR=${OPTARG};; d) DESC="${OPTARG}";; + n) NODE_COLORS="${OPTARG}";; *) usage ;; @@ -54,6 +55,7 @@ fi echo >&2 "Graph File: " $GRAPH_FILE echo >&2 "Region: " $REGION echo >&2 "Output Directory: " $OUTDIR +echo >&2 "Node colors: " $NODE_COLORS rm -fr $OUTDIR mkdir -p $OUTDIR @@ -92,6 +94,15 @@ for GAM_FILE in "${GAM_FILES[@]}"; do GAM_NUM=$((GAM_NUM + 1)) done +# construct node file +if [[ ! -z "${NODE_COLORS}" ]] ; then + node_names_arr=($NODE_COLORS); + for NODENAME in "${node_names_arr[@]}"; do + echo >&2 "$NODENAME" + printf "$NODENAME\n" >> $OUTDIR/nodeColors.tsv + done +fi + # Make the empty but required annotation file. We have no haplotypes to put in it. touch "${OUTDIR}/chunk_0_${REGION_CONTIG}_${REGION_START}_${REGION_END}.annotate.txt" printf "\tchunk_0_${REGION_CONTIG}_${REGION_START}_${REGION_END}.annotate.txt\n" >> $OUTDIR/regions.tsv