diff --git a/package.json b/package.json index 2d6342d..6d785b4 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@testing-library/user-event": "^13.5.0", "autoprefixer": "^10.4.13", "bio-parsers": "^9.3.0", + "color-hash": "^2.0.2", "postcss": "^8.4.21", "qs": "^6.11.0", "rc-slider": "^10.1.1", diff --git a/src/App.js b/src/App.js index f870784..2569ebb 100644 --- a/src/App.js +++ b/src/App.js @@ -11,10 +11,11 @@ import { useVirtualizer, useWindowVirtualizer} from '@tanstack/react-virtual'; import Slider, {Range} from "rc-slider"; import {AiOutlineZoomIn,AiOutlineZoomOut} from 'react-icons/ai'; import {GiDna1} from 'react-icons/gi'; - +import ColorHash from 'color-hash'; import { useNavigate, useLocation } from "react-router-dom" import qs from "qs" +var colorHash = new ColorHash({lightness: [0.75, 0.9, 0.7,0.8]}); const useQueryState = query => { const location = useLocation() @@ -78,12 +79,14 @@ const Tooltip = ({ hoveredInfo }) => { {hoveredInfo && {hoveredInfo.label}} {hoveredInfo && hoveredInfo.product && (
{hoveredInfo.product}
+ )} + ); }; -const getColor = (feature) => { +const getColor = (feature, product) => { switch (feature.type) { case "CDS": switch (feature.name){ @@ -131,7 +134,7 @@ const getColor = (feature) => { return "#ff7f50"; default: - return "#c39797"; + return colorHash.hex(feature.name+ product+feature.type); } case "gene": return "blue"; @@ -142,7 +145,7 @@ const getColor = (feature) => { case "3'UTR": return "orange"; default: - return "black"; + return colorHash.hex(feature.name+ product+feature.type); } }; @@ -312,7 +315,7 @@ whereMouseCurrentlyIs,setWhereMouseCurrentlyIs}) => { const seqLength = locations.reduce((acc, location) => acc + location.end - location.start + 1, 0); const codonMap = []; - if (feature.type=="CDS" ){ + if (feature.type=="CDS" | feature.type=="mat_peptide"){ for (let j = rowStart; j < rowEnd; j++) { let positionSoFar = 0; @@ -431,18 +434,25 @@ const codonZoomThreshold = -2 const extraFeat=5*zoomFactor; const codonPad =15*zoomFactor; + const product = feature.notes ? feature.notes.product : ""; + let betterName = product ? product : feature.name; + const altName = betterName == feature.name ? "" : feature.name; + if (betterName=="Untitled Feature") betterName=feature.type; + + return ( { if (zoomLevel< codonZoomThreshold) setHoveredInfo({ label: `${feature.name}: ${feature.type}`, - product: feature.notes && feature.notes.product ? feature.notes.product : null, + product: altName, + locusTag: feature.notes && feature.notes.locus_tag ? feature.notes.locus_tag : null, })} } onMouseLeave={() => { @@ -452,7 +462,7 @@ const codonZoomThreshold = -2 /> - {feature.name == "Untitled Feature" ? feature.type : feature.name} + {betterName} { feature.codonMap.map((codon, j) => { @@ -461,8 +471,9 @@ const codonZoomThreshold = -2 zoomLevel> codonZoomThreshold && setHoveredInfo({ - label: `${codon.gene}: ${codon.aminoAcid}${codon.codonIndex+1}`, - product: feature.notes && feature.notes.product ? feature.notes.product : null, + label: `${betterName}: ${codon.aminoAcid}${codon.codonIndex+1}`, + product: altName, + locusTag: feature.notes && feature.notes.locus_tag ? feature.notes.locus_tag : null, }) } @@ -868,6 +879,7 @@ function GensploreView({genbankString, searchInput, setSearchInput}) { const virtualItems = rowVirtualizer.getVirtualItems(); const [centeredNucleotide, setCenteredNucleotide] = useState(null); + const setZoomLevel = (x) => { const middleRow = virtualItems[Math.floor(virtualItems.length / 2)].index @@ -1051,13 +1063,27 @@ function GensploreView({genbankString, searchInput, setSearchInput}) { const App = () => { + const addlExamples = [ + ["Monkeypox clade II","NC_063383.1"], + ["HIV-1","NC_001802.1"], + + + ] // option to either load from URL or upload a file const [genbankString, setGenbankString] = useState(null); const [loading, setLoading] = useState(false); const loadFromUrl = async (url) => { + setGenbankString(null); setLoading(true); const response = await fetch(url); + // check for errors + if (!response.ok) { + setLoading(false); + window.alert("Error loading file: for large Genbank files, try using the 'Load from file' option instead."); + return; + } + const text = await response.text(); setGenbankString(text); setLoading(false); @@ -1089,7 +1115,17 @@ const App = () => { const [beingDraggedOver, setBeingDraggedOver] = useState(false); const [genbankId, setGenbankId] = useState(null); + const loadFromGenbankId = async (id) => { + + const strippedOfWhitespace = id.replace(/\s/g, '') + // if no length, do nothing + if (strippedOfWhitespace.length<= 3) { + return + } + const url = `https://genbank-api.vercel.app/api/genbank/${strippedOfWhitespace}` + setGbUrl(url) + } // create UI for loading from URL or file @@ -1126,6 +1162,9 @@ onDrop={(e) => { }} + + + >
View code on GitHub @@ -1168,14 +1207,7 @@ onDrop={(e) => { + {addlExamples.map((example) => ( +
  • + +
  • + ))} +
    + - - - - )} ); }; + export default App; diff --git a/yarn.lock b/yarn.lock index 8b71bf4..b505b49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3187,6 +3187,11 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" +color-hash@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/color-hash/-/color-hash-2.0.2.tgz#abf735705da71874ddec7dcef50cd7479e7d64e9" + integrity sha512-6exeENAqBTuIR1wIo36mR8xVVBv6l1hSLd7Qmvf6158Ld1L15/dbahR9VUOiX7GmGJBCnQyS0EY+I8x+wa7egg== + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"