-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Nick Rosenau
authored and
Nick Rosenau
committed
Nov 15, 2023
1 parent
13fcc68
commit 74efa1a
Showing
4 changed files
with
342 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
import { useState } from "react"; | ||
import React = require("react"); | ||
import { Button } from "semantic-ui-react"; | ||
import { randomID } from "../sequence"; | ||
import { PrimerModal } from "./PrimerModal"; | ||
|
||
export interface Primer { | ||
seq: any; | ||
temp: number; | ||
GCContent: string; | ||
start: number; | ||
end: number; | ||
rev: boolean | ||
} | ||
export const PrimerDesign = (props) => { | ||
|
||
const [openModal, setOpenModal] = useState(false); | ||
const [primers, setPrimers] = useState<Primer[] | null>(null) | ||
const [oldPrimerSelect, setOldPrimerSelect] = useState<string>("") | ||
const [editButton, setEditButton] = useState(false) | ||
const [target, setTarget] = useState<any>(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 | ||
|
||
for(let i=0; i< sequenceLength; i++) { | ||
const nucleotide = sequence[i] | ||
if(nucleotide === 'G' || nucleotide === 'C'){ | ||
gcCount++ | ||
} | ||
} | ||
|
||
return gcCount / sequenceLength * 100; | ||
} | ||
|
||
const checkValidSeq = (sequence: string) => { | ||
sequence = sequence.toUpperCase() | ||
return /^[ATCG]*$/i.test(sequence); | ||
} | ||
|
||
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 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() | ||
|
||
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] | ||
} | ||
} | ||
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] | ||
} | ||
|
||
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) | ||
} | ||
|
||
|
||
} | ||
|
||
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(!annotations.find((annotation:any) => annotation.id === target?.id)){ | ||
setTarget(targetAnnotation) | ||
annotations = [...annotations, targetAnnotation] | ||
} | ||
|
||
|
||
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]) | ||
|
||
return( | ||
<div style={{position: 'absolute', top: 14, left: 250, zIndex: 10, width: '200px'}}> | ||
{props.primerSelect !== "" && !editButton && ( | ||
<Button color='blue' onClick={() => { | ||
handlePrimerDesign(props.primerSelect); | ||
setOpenModal(true) | ||
}}>Create Primers</Button> | ||
)} | ||
{props.primerSelect !== "" && editButton && ( | ||
<Button color='green' onClick={() => { | ||
setOpenModal(true) | ||
}}>Edit Primers</Button> | ||
)} | ||
{primers && ( | ||
<PrimerModal open={openModal} closeModal={() => setOpenModal(false)} data={primers} addPrimers={(data:Primer[]) => addPrimers(data)} removePrimers={removePrimers} /> | ||
)} | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
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) | ||
|
||
|
||
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]); | ||
|
||
|
||
return ( | ||
<div> | ||
<Modal | ||
open={props.open} | ||
> | ||
<Modal.Header>Primer Information</Modal.Header> | ||
<Modal.Content> | ||
<p>Forward Primer Sequence: {props.data[0].seq}</p> | ||
<p>Forward Primer Length: {props.data[0].seq.length} bp</p> | ||
<p>Forward Primer GC Content: {props.data[0].GCContent}</p> | ||
<p>Forward Primer Temp: {props.data[0].temp} °C</p> | ||
|
||
<br></br> | ||
<p>Reverse Primer Sequence: {props.data[1].seq}</p> | ||
<p>Reverse Primer Length: {props.data[1].seq.length} bp</p> | ||
<p>Reverse Primer GC Content: {props.data[1].GCContent}</p> | ||
<p>Reverse Primer Temp: {props.data[1].temp} °C</p> | ||
<Button color={buttonRevColor} onClick={() => { | ||
if(buttonRev === 'Remove Primers'){ | ||
props.removePrimers(); | ||
} | ||
else{ | ||
props.addPrimers([props.data[0], props.data[1]]); | ||
} | ||
handleButtonChangeRev()}}>{buttonRev}</Button> | ||
</Modal.Content> | ||
<Modal.Actions> | ||
<Button color='red' onClick={props.closeModal}> | ||
Close | ||
</Button> | ||
</Modal.Actions> | ||
</Modal> | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.