From 4a2956b7781fdfcf8f1bcd0fa1acca5599361067 Mon Sep 17 00:00:00 2001 From: Nick Rosenau Date: Thu, 16 Nov 2023 12:13:10 -0500 Subject: [PATCH] forgot to run prettier. --- src/PrimerDesign/PrimerDesign.tsx | 491 ++++++++++++++++-------------- src/PrimerDesign/PrimerModal.tsx | 109 ++++--- src/SeqViz.tsx | 23 +- 3 files changed, 338 insertions(+), 285 deletions(-) diff --git a/src/PrimerDesign/PrimerDesign.tsx b/src/PrimerDesign/PrimerDesign.tsx index a08e132c1..4f20236d3 100644 --- a/src/PrimerDesign/PrimerDesign.tsx +++ b/src/PrimerDesign/PrimerDesign.tsx @@ -1,263 +1,310 @@ import { useState } from "react"; -import React = require("react"); import { Button } from "semantic-ui-react"; + import { randomID } from "../sequence"; import { PrimerModal } from "./PrimerModal"; +import React = require("react"); + export interface Primer { seq: any; temp: number; GCContent: string; start: number; end: number; - rev: boolean + rev: boolean; } -export const PrimerDesign = (props) => { - - const [openModal, setOpenModal] = useState(false); - const [primers, setPrimers] = useState(null) - const [oldPrimerSelect, setOldPrimerSelect] = useState("") - const [editButton, setEditButton] = useState(false) - const [target, setTarget] = useState(null) - - const getPrimerTemp = (sequence: string) => { - let atCount = 0, gcCount = 0; - - for (let i = 0; i < sequence.length; i++) { - let nucleotide = sequence[i].toUpperCase(); - if (nucleotide === 'A' || nucleotide === 'T') { - atCount++; - } else if (nucleotide === 'G' || nucleotide === 'C') { - gcCount++; - } +export const PrimerDesign = props => { + const [openModal, setOpenModal] = useState(false); + const [primers, setPrimers] = useState(null); + const [oldPrimerSelect, setOldPrimerSelect] = useState(""); + const [editButton, setEditButton] = useState(false); + const [target, setTarget] = useState(null); + + const getPrimerTemp = (sequence: string) => { + let atCount = 0, + gcCount = 0; + + for (let i = 0; i < sequence.length; i++) { + let nucleotide = sequence[i].toUpperCase(); + if (nucleotide === "A" || nucleotide === "T") { + atCount++; + } else if (nucleotide === "G" || nucleotide === "C") { + gcCount++; } - - return 2 * atCount + 4 * gcCount; } - const getGCContent = (sequence: string) => { - let gcCount = 0; - sequence = sequence.toUpperCase() - const sequenceLength = sequence.length + return 2 * atCount + 4 * gcCount; + }; - for(let i=0; i< sequenceLength; i++) { - const nucleotide = sequence[i] - if(nucleotide === 'G' || nucleotide === 'C'){ - gcCount++ - } - } + const getGCContent = (sequence: string) => { + let gcCount = 0; + sequence = sequence.toUpperCase(); + const sequenceLength = sequence.length; - return gcCount / sequenceLength * 100; + for (let i = 0; i < sequenceLength; i++) { + const nucleotide = sequence[i]; + if (nucleotide === "G" || nucleotide === "C") { + gcCount++; + } } - const checkValidSeq = (sequence: string) => { - sequence = sequence.toUpperCase() - return /^[ATCG]*$/i.test(sequence); - } + return (gcCount / sequenceLength) * 100; + }; - const reverseComplement = (seq:string) => { - return seq.split('').map(nucleotide => { - switch (nucleotide) { - case 'A': return 'T'; - case 'T': return 'A'; - case 'C': return 'G'; - case 'G': return 'C'; - default: return nucleotide; - } - }).reverse().join('').toLowerCase(); - }; + const checkValidSeq = (sequence: string) => { + sequence = sequence.toUpperCase(); + return /^[ATCG]*$/i.test(sequence); + }; - const getReversePrimer = (shift=0, len=25) => { - const start = props.selection.end - const end = props.selection.end + (len + shift) - let sequence = props.seq.slice(start, end) - sequence = sequence.toUpperCase() + const reverseComplement = (seq: string) => { + return seq + .split("") + .map(nucleotide => { + switch (nucleotide) { + case "A": + return "T"; + case "T": + return "A"; + case "C": + return "G"; + case "G": + return "C"; + default: + return nucleotide; + } + }) + .reverse() + .join("") + .toLowerCase(); + }; - return [reverseComplement(sequence), start, end] - } + const getReversePrimer = (shift = 0, len = 25) => { + const start = props.selection.end; + const end = props.selection.end + (len + shift); + let sequence = props.seq.slice(start, end); + sequence = sequence.toUpperCase(); - const getForwardPrimer = (shift=0, len=25) => { - const start = props.selection.start - (len + shift) - const end = props.selection.start - const forwardPrimer = props.seq.slice(start, end) - return [forwardPrimer, start, end] - } + return [reverseComplement(sequence), start, end]; + }; + + const getForwardPrimer = (shift = 0, len = 25) => { + const start = props.selection.start - (len + shift); + const end = props.selection.start; + const forwardPrimer = props.seq.slice(start, end); + return [forwardPrimer, start, end]; + }; - const shiftPrimers = (temp : number, GCContent: number, remainingBp: string, orientation: string) => { - let count = 0 - let len = 25 - let seq = 'ATCG' - let start = 0 - let end = 0 - while((70 <= temp || temp <= 55 || 60 < GCContent || GCContent < 40) && count < remainingBp.length){ - if(orientation === 'rev'){ - if(len > 18){ - const result = getReversePrimer(1, len-1) - seq = result[0] - start = result[1] - end = result[2] - len -= 1 - } - else{ - const result = getReversePrimer(1) - seq = result[0] - start = result[1] - end = result[2] - } + const shiftPrimers = (temp: number, GCContent: number, remainingBp: string, orientation: string) => { + let count = 0; + let len = 25; + let seq = "ATCG"; + let start = 0; + let end = 0; + while ((70 <= temp || temp <= 55 || 60 < GCContent || GCContent < 40) && count < remainingBp.length) { + if (orientation === "rev") { + if (len > 18) { + const result = getReversePrimer(1, len - 1); + seq = result[0]; + start = result[1]; + end = result[2]; + len -= 1; + } else { + const result = getReversePrimer(1); + seq = result[0]; + start = result[1]; + end = result[2]; } - else{ - if(len > 18){ - const result = getForwardPrimer(1, len-1) - seq = result[0] - start = result[1] - end = result[2] - len -= 1 - } - else{ - const result = getForwardPrimer(1) - seq = result[0] - start = result[1] - end = result[2] - } + } else { + if (len > 18) { + const result = getForwardPrimer(1, len - 1); + seq = result[0]; + start = result[1]; + end = result[2]; + len -= 1; + } else { + const result = getForwardPrimer(1); + seq = result[0]; + start = result[1]; + end = result[2]; } - - temp = getPrimerTemp(seq) - GCContent = getGCContent(seq) - count += 1 } - return [seq, temp, GCContent, start, end] + temp = getPrimerTemp(seq); + GCContent = getGCContent(seq); + count += 1; } - const handlePrimerDesign = (sequence:string) => { - - if(sequence !== oldPrimerSelect){ - if(primers != null){ - removePrimers() - } - setPrimers(null) - const validate = checkValidSeq(sequence) - - if(validate){ - - let forward = getForwardPrimer() - let rev = getReversePrimer() - - let GCContentFwd = getGCContent(forward[0]) - let GCContentRev = getGCContent(rev[0]) - - let fwdTemp = getPrimerTemp(forward[0]) - let revTemp = getPrimerTemp(rev[0]) - - let startFwd = 0 - let endFwd = 0 - let startRev = 0 - let endRev = 0 - - const fwdBackwards = props.seq.slice(0, props.selection.start - 25) - const revOnwards = props.seq.slice(props.selection.end + 25, props.seq.length - 1) - - - if((70 <= fwdTemp || fwdTemp <= 55 || 60 < GCContentFwd || GCContentFwd < 40 || Math.abs(fwdTemp - revTemp) > 5)){ - const result:any = shiftPrimers(fwdTemp, GCContentFwd, fwdBackwards, 'fwd') - forward = result[0] - fwdTemp = result[1] - GCContentFwd = result[2] - startFwd = result[3] - endFwd = result[4] - } - if((70 <= revTemp || revTemp <= 55 || 60 < GCContentRev || GCContentRev < 40 || Math.abs(fwdTemp - revTemp) > 5)){ - const result:any = shiftPrimers(revTemp, GCContentRev, revOnwards, 'rev') - rev = result[0] - revTemp = result[1] - GCContentRev = result[2] - startRev = result[3] - endRev = result[4] - } - - setOldPrimerSelect(sequence) - setPrimers([{'seq': forward, 'temp': fwdTemp, 'GCContent': Math.round(GCContentFwd).toString() + "%", 'start': startFwd, 'end': endFwd, rev: false}, - {'seq': rev, 'temp': revTemp, 'GCContent': Math.round(GCContentRev).toString() + "%", 'start': startRev, 'end': endRev, rev: true}]) - setOpenModal(true) - return - } - throw 'Invalid DNA sequence' - } - else{ - setOpenModal(true) + return [seq, temp, GCContent, start, end]; + }; + + const handlePrimerDesign = (sequence: string) => { + if (sequence !== oldPrimerSelect) { + if (primers != null) { + removePrimers(); } - - - } + setPrimers(null); + const validate = checkValidSeq(sequence); - const addPrimers = (primers:Primer[]) => { - - let annotations = [...props.annotations] - - primers.forEach((primer:Primer) => { - const primerAnnotation = { - id: randomID(), - color: primer.rev ? 'blue' : 'red', - direction: primer.rev ? -1 : 1, - end: primer.end, - name: primer.rev ? 'primer-rev' : 'primer-fwd', - start: primer.start - } - annotations = [...annotations, primerAnnotation] - }) - - const targetAnnotation = { - id: randomID(), - color: 'green', - direction: 1, - end: props.selection.end, - name: 'target', - start: props.selection.start + if (validate) { + let forward = getForwardPrimer(); + let rev = getReversePrimer(); + + let GCContentFwd = getGCContent(forward[0]); + let GCContentRev = getGCContent(rev[0]); + + let fwdTemp = getPrimerTemp(forward[0]); + let revTemp = getPrimerTemp(rev[0]); + + let startFwd = 0; + let endFwd = 0; + let startRev = 0; + let endRev = 0; + + const fwdBackwards = props.seq.slice(0, props.selection.start - 25); + const revOnwards = props.seq.slice(props.selection.end + 25, props.seq.length - 1); + + if ( + 70 <= fwdTemp || + fwdTemp <= 55 || + 60 < GCContentFwd || + GCContentFwd < 40 || + Math.abs(fwdTemp - revTemp) > 5 + ) { + const result: any = shiftPrimers(fwdTemp, GCContentFwd, fwdBackwards, "fwd"); + forward = result[0]; + fwdTemp = result[1]; + GCContentFwd = result[2]; + startFwd = result[3]; + endFwd = result[4]; } - if(!annotations.find((annotation:any) => annotation.id === target?.id)){ - setTarget(targetAnnotation) - annotations = [...annotations, targetAnnotation] + if ( + 70 <= revTemp || + revTemp <= 55 || + 60 < GCContentRev || + GCContentRev < 40 || + Math.abs(fwdTemp - revTemp) > 5 + ) { + const result: any = shiftPrimers(revTemp, GCContentRev, revOnwards, "rev"); + rev = result[0]; + revTemp = result[1]; + GCContentRev = result[2]; + startRev = result[3]; + endRev = result[4]; } - - - props.setAnnotations(annotations) + + setOldPrimerSelect(sequence); + setPrimers([ + { + seq: forward, + temp: fwdTemp, + GCContent: Math.round(GCContentFwd).toString() + "%", + start: startFwd, + end: endFwd, + rev: false, + }, + { + seq: rev, + temp: revTemp, + GCContent: Math.round(GCContentRev).toString() + "%", + start: startRev, + end: endRev, + rev: true, + }, + ]); + setOpenModal(true); + return; + } + throw "Invalid DNA sequence"; + } else { + setOpenModal(true); } + }; + + const addPrimers = (primers: Primer[]) => { + let annotations = [...props.annotations]; + + primers.forEach((primer: Primer) => { + const primerAnnotation = { + id: randomID(), + color: primer.rev ? "blue" : "red", + direction: primer.rev ? -1 : 1, + end: primer.end, + name: primer.rev ? "primer-rev" : "primer-fwd", + start: primer.start, + }; + annotations = [...annotations, primerAnnotation]; + }); - const removePrimers = () => { - - const annotations = props.annotations.filter((annotation:any) => !annotation.name.includes('primer') && !annotation.name.includes('target')) - setEditButton(false) - props.setAnnotations(annotations) + const targetAnnotation = { + id: randomID(), + color: "green", + direction: 1, + end: props.selection.end, + name: "target", + start: props.selection.start, + }; + if (!annotations.find((annotation: any) => annotation.id === target?.id)) { + setTarget(targetAnnotation); + annotations = [...annotations, targetAnnotation]; } - React.useEffect(() => { - if(props.selection?.start && primers){ - const findOne = primers.find((primer:Primer) => primer.start === props.selection?.start && primer.end === props.selection?.end) - if(findOne || (target?.start === props.selection?.start && target?.end === props.selection?.end)){ - setEditButton(true) - } - else{ - setEditButton(false) - } + props.setAnnotations(annotations); + }; + + const removePrimers = () => { + const annotations = props.annotations.filter( + (annotation: any) => !annotation.name.includes("primer") && !annotation.name.includes("target") + ); + setEditButton(false); + props.setAnnotations(annotations); + }; + + React.useEffect(() => { + if (props.selection?.start && primers) { + const findOne = primers.find( + (primer: Primer) => primer.start === props.selection?.start && primer.end === props.selection?.end + ); + if (findOne || (target?.start === props.selection?.start && target?.end === props.selection?.end)) { + setEditButton(true); + } else { + setEditButton(false); } - }, [props.selection]) + } + }, [props.selection]); - return( -
- {props.primerSelect !== "" && !editButton && ( - - )} - {props.primerSelect !== "" && editButton && ( - - )} - {primers && ( - setOpenModal(false)} data={primers} addPrimers={(data:Primer[]) => addPrimers(data)} removePrimers={removePrimers} /> - )} -
- ) -} \ No newline at end of file + setOpenModal(true); + }} + > + Create Primers + + )} + {props.primerSelect !== "" && editButton && ( + + )} + {primers && ( + setOpenModal(false)} + data={primers} + addPrimers={(data: Primer[]) => addPrimers(data)} + removePrimers={removePrimers} + /> + )} + + ); +}; diff --git a/src/PrimerDesign/PrimerModal.tsx b/src/PrimerDesign/PrimerModal.tsx index 4e3c50649..f83bd3645 100644 --- a/src/PrimerDesign/PrimerModal.tsx +++ b/src/PrimerDesign/PrimerModal.tsx @@ -1,63 +1,62 @@ import { useEffect, useState } from "react"; -import React = require("react"); import { Button, Modal, SemanticCOLORS } from "semantic-ui-react"; -export const PrimerModal = (props) => { - const [buttonRev, setButtonRev] = useState("Add Primers") - const [buttonRevColor, setButtonRevColor] = useState("blue" as SemanticCOLORS) +import React = require("react"); +export const PrimerModal = props => { + const [buttonRev, setButtonRev] = useState("Add Primers"); + const [buttonRevColor, setButtonRevColor] = useState("blue" as SemanticCOLORS); - const handleButtonChangeRev = () => { - if(buttonRev === 'Add Primers'){ - setButtonRevColor('red') - setButtonRev('Remove Primers') - } - else{ - setButtonRevColor('blue') - setButtonRev('Add Primers') - } + const handleButtonChangeRev = () => { + if (buttonRev === "Add Primers") { + setButtonRevColor("red"); + setButtonRev("Remove Primers"); + } else { + setButtonRevColor("blue"); + setButtonRev("Add Primers"); } + }; + + useEffect(() => { + setButtonRev("Add Primers"); + setButtonRevColor("blue"); + }, [props.data]); - useEffect(() => { - - setButtonRev('Add Primers'); - setButtonRevColor('blue'); - - }, [props.data]); + return ( +
+ + Primer Information + +

Forward Primer Sequence: {props.data[0].seq}

+

Forward Primer Length: {props.data[0].seq.length} bp

+

Forward Primer GC Content: {props.data[0].GCContent}

+

Forward Primer Temp: {props.data[0].temp} °C

- - return ( -
- - Primer Information - -

Forward Primer Sequence: {props.data[0].seq}

-

Forward Primer Length: {props.data[0].seq.length} bp

-

Forward Primer GC Content: {props.data[0].GCContent}

-

Forward Primer Temp: {props.data[0].temp} °C

- -

-

Reverse Primer Sequence: {props.data[1].seq}

-

Reverse Primer Length: {props.data[1].seq.length} bp

-

Reverse Primer GC Content: {props.data[1].GCContent}

-

Reverse Primer Temp: {props.data[1].temp} °C

- -
- - - -
-
- ); -}; \ No newline at end of file +

+

Reverse Primer Sequence: {props.data[1].seq}

+

Reverse Primer Length: {props.data[1].seq.length} bp

+

Reverse Primer GC Content: {props.data[1].GCContent}

+

Reverse Primer Temp: {props.data[1].temp} °C

+ +
+ + + +
+
+ ); +}; diff --git a/src/SeqViz.tsx b/src/SeqViz.tsx index d5e0fd49d..51315f9aa 100644 --- a/src/SeqViz.tsx +++ b/src/SeqViz.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import seqparse, { ParseOptions, parseFile } from "seqparse"; +import { PrimerDesign } from "./PrimerDesign/PrimerDesign"; import SeqViewerContainer, { CustomChildrenProps, SeqVizChildRefs } from "./SeqViewerContainer"; import { COLORS, colorByIndex } from "./colors"; import digest from "./digest"; @@ -19,7 +20,6 @@ import { isEqual } from "./isEqual"; import search from "./search"; import { Selection } from "./selectionContext"; import { complement, directionality, guessType, randomID } from "./sequence"; -import { PrimerDesign } from "./PrimerDesign/PrimerDesign"; /** `SeqViz` props. See the README for more details. One of `seq`, `file` or `accession` is required. */ export interface SeqVizProps { @@ -205,7 +205,7 @@ export default class SeqViz extends React.Component { translations: [], viewer: "both", zoom: { circular: 0, linear: 50 }, - primerDesign: false + primerDesign: false, }; constructor(props: SeqVizProps) { @@ -236,7 +236,7 @@ export default class SeqViz extends React.Component { }); } } - + // Check if an accession was passed, we'll query it here if so const { accession } = this.props; if (!accession || !accession.length) { @@ -462,15 +462,22 @@ export default class SeqViz extends React.Component { circular: typeof zoom?.circular == "number" ? Math.min(Math.max(zoom.circular, 0), 100) : 0, linear: typeof zoom?.linear == "number" ? Math.min(Math.max(zoom.linear, 0), 100) : 50, }, - primerDesign: false + primerDesign: false, }; return (
- {this.props.primerDesign && this.props.selection?.start && { - this.setState({annotations: data}) - }} /> } + {this.props.primerDesign && this.props.selection?.start && ( + { + this.setState({ annotations: data }); + }} + /> + )}
);