-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
wip: sequence display ok. todo: fragment display
- Loading branch information
1 parent
6681b33
commit 04fe712
Showing
12 changed files
with
465 additions
and
50 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { SVGMassFragmentation } from '../src/components/SVGMassFragmentation'; | ||
|
||
export default function Fragmentation() { | ||
return ( | ||
<> | ||
<h1>Fragmentation</h1> | ||
<div | ||
style={{ | ||
border: '1px solid red', | ||
overflow: 'clip', | ||
}} | ||
> | ||
<SVGMassFragmentation /> | ||
</div> | ||
</> | ||
); | ||
} |
This file was deleted.
Oops, something went wrong.
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,7 @@ | ||
import React from 'react'; | ||
import { createRoot } from 'react-dom/client'; | ||
|
||
import Fragmentation from './Fragmentation'; | ||
|
||
const root = createRoot(document.getElementById('root')); | ||
root.render(<Fragmentation />); |
This file was deleted.
Oops, something went wrong.
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,209 @@ | ||
import { groupsObject } from 'chemical-groups'; | ||
import * as Nucleotide from 'nucleotide'; | ||
import * as Peptide from 'peptide'; | ||
|
||
const ALTERNATIVES = ['', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹']; | ||
const SYMBOLS = ['Θ', 'Δ', 'Λ', 'Φ', 'Ω', 'Γ', 'Χ']; | ||
|
||
let currentSymbol = 0; | ||
|
||
/** | ||
* Code that allows to split a sequence of amino acids or nucleotides natural or non natural | ||
* @param {string} [sequence] | ||
* @param {object} [options={}] | ||
* @param {string} [options.kind] - peptide, rna, ds-dna or dna. Default if contains U: rna, otherwise ds-dna | ||
* @param {string} [options.fivePrime=monophosphate] - alcohol, monophosphate, diphosphate, triphosphate | ||
* @param {string} [options.circular=false] | ||
*/ | ||
|
||
export function appendResidues(data, sequence, options = {}) { | ||
const { kind = 'peptide' } = options; | ||
|
||
currentSymbol = 0; | ||
// we normalize the sequence to 3 letter codes | ||
|
||
if (kind === 'peptide') { | ||
sequence = Peptide.sequenceToMF(sequence); | ||
} else { | ||
sequence = Nucleotide.sequenceToMF(sequence, options); | ||
} | ||
|
||
const result = { | ||
begin: '', | ||
end: '', | ||
residues: [], | ||
}; | ||
|
||
const STATE_BEGIN = 0; | ||
const STATE_MIDDLE = 1; | ||
const STATE_END = 2; | ||
|
||
let parenthesisLevel = 0; | ||
let state = STATE_BEGIN; // as long as we don't have an uppercase followed by 2 lowercases | ||
for (let i = 0; i < sequence.length; i++) { | ||
let currentChar = sequence.charAt(i); | ||
let nextChar = i < sequence.length - 1 ? sequence.charAt(i + 1) : ''; | ||
let nextNextChar = i < sequence.length - 2 ? sequence.charAt(i + 2) : ''; | ||
|
||
if ( | ||
state === STATE_BEGIN && | ||
currentChar.match(/[A-Z]/) && | ||
nextChar.match(/[a-z]/) && | ||
nextNextChar.match(/[a-z]/) && | ||
parenthesisLevel === 0 | ||
) { | ||
state = STATE_MIDDLE; | ||
} | ||
|
||
if ( | ||
state === STATE_MIDDLE && | ||
!sequence.substring(i).match(/[A-Z][a-z][a-z]/) && | ||
!currentChar.match(/[a-z]/) && | ||
parenthesisLevel === 0 | ||
) { | ||
state = STATE_END; | ||
} else if ( | ||
currentChar.match(/[A-Z]/) && | ||
nextChar.match(/[a-z]/) && | ||
nextNextChar.match(/[a-z]/) && | ||
parenthesisLevel === 0 | ||
) { | ||
result.residues.push(''); | ||
} | ||
|
||
switch (state) { | ||
case STATE_BEGIN: | ||
result.begin = result.begin + currentChar; | ||
break; | ||
case STATE_MIDDLE: | ||
result.residues[result.residues.length - 1] = | ||
result.residues[result.residues.length - 1] + currentChar; | ||
break; | ||
case STATE_END: | ||
result.end = result.end + currentChar; | ||
break; | ||
default: | ||
} | ||
|
||
if (currentChar === '(') { | ||
parenthesisLevel++; | ||
} else if (currentChar === ')') { | ||
parenthesisLevel--; | ||
} | ||
} | ||
|
||
// we process all the residues | ||
let alternatives = {}; | ||
let replacements = {}; | ||
for (let i = 0; i < result.residues.length; i++) { | ||
let label = result.residues[i]; | ||
let residue = { | ||
value: label, | ||
results: { | ||
begin: [], | ||
end: [], | ||
}, | ||
}; | ||
residue.fromBegin = i + 1; | ||
residue.fromEnd = result.residues.length - i; | ||
residue.kind = 'residue'; | ||
if (label.includes('(')) { | ||
getModifiedReplacement(label, residue, alternatives, replacements); | ||
} else if (groupsObject[label] && groupsObject[label].oneLetter) { | ||
residue.label = groupsObject[label].oneLetter; | ||
} else { | ||
getUnknownReplacement(label, residue, replacements); | ||
} | ||
result.residues[i] = residue; | ||
} | ||
result.begin = removeStartEndParenthesis(result.begin); | ||
result.end = removeStartEndParenthesis(result.end); | ||
if (result.begin.length > 2) { | ||
let label = options.kind === 'peptide' ? 'Nter' : "5'"; | ||
replacements[result.begin] = { | ||
label, | ||
}; | ||
result.begin = label; | ||
} | ||
if (result.end.length > 2) { | ||
let label = options.kind === 'peptide' ? 'Cter' : "3'"; | ||
replacements[result.end] = { | ||
label, | ||
}; | ||
result.end = label; | ||
} | ||
|
||
result.begin = { label: result.begin, kind: 'begin' }; | ||
result.end = { label: result.end, kind: 'end' }; | ||
result.alternatives = alternatives; | ||
result.replacements = replacements; | ||
|
||
result.all = [result.begin].concat(result.residues, [result.end]); | ||
|
||
result.all.forEach((entry) => { | ||
entry.info = { | ||
nbOver: 0, | ||
nbUnder: 0, | ||
}; | ||
}); | ||
|
||
data.residues = result; | ||
} | ||
|
||
function getUnknownReplacement(unknownResidue, residue, replacements) { | ||
if (!replacements[unknownResidue]) { | ||
replacements[unknownResidue] = { | ||
label: SYMBOLS[currentSymbol] || '?', | ||
id: unknownResidue, | ||
}; | ||
} | ||
currentSymbol++; | ||
residue.replaced = true; | ||
residue.label = replacements[unknownResidue].label; | ||
} | ||
|
||
function getModifiedReplacement( | ||
modifiedResidue, | ||
residue, | ||
alternatives, | ||
replacements, | ||
) { | ||
if (!replacements[modifiedResidue]) { | ||
let position = modifiedResidue.indexOf('('); | ||
let residueCode = modifiedResidue.substring(0, position); | ||
let modification = removeStartEndParenthesis( | ||
modifiedResidue.substring(position), | ||
); | ||
|
||
if ( | ||
groupsObject[residueCode] && | ||
groupsObject[residueCode].alternativeOneLetter | ||
) { | ||
let alternativeOneLetter = groupsObject[residueCode].alternativeOneLetter; | ||
|
||
if (!alternatives[alternativeOneLetter]) { | ||
alternatives[alternativeOneLetter] = { count: 1 }; | ||
} else { | ||
alternatives[alternativeOneLetter].count++; | ||
} | ||
replacements[modifiedResidue] = { | ||
label: | ||
ALTERNATIVES[alternatives[alternativeOneLetter].count - 1] + | ||
alternativeOneLetter, | ||
residue: residueCode, | ||
modification, | ||
}; | ||
} else { | ||
getUnknownReplacement(modifiedResidue, residue, replacements); | ||
} | ||
} | ||
residue.replaced = true; | ||
residue.label = replacements[modifiedResidue].label; | ||
} | ||
|
||
function removeStartEndParenthesis(mf) { | ||
if (mf[0] === '(' && mf[mf.length - 1] === ')') { | ||
return mf.substring(1, mf.length - 1); | ||
} | ||
return mf; | ||
} |
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,120 @@ | ||
export function appendResults(data, analysisResult, options = {}) { | ||
const numberResidues = data.residues.residues.length; | ||
const { merge = {}, filter = {} } = options; | ||
|
||
let results = JSON.parse(JSON.stringify(analysisResult)); | ||
results = results.filter((result) => !result.type.match(/^-B[0-9]$/)); | ||
// we calculate all the lines based on the results | ||
for (let result of results) { | ||
let parts = result.type.split(/:|(?=[a-z])/); // we may have ':' but not mandatory | ||
if (parts.length === 2) { | ||
result.internal = true; | ||
if (parts[1].match(/^[abcd][1-9]/)) { | ||
[parts[0], parts[1]] = [parts[1], parts[0]]; | ||
} | ||
result.to = getNumber(parts[0]) - 1; | ||
result.from = numberResidues - getNumber(parts[1]); | ||
} else { | ||
if (parts[0].match(/^[abcd][1-9]/)) { | ||
result.fromBegin = true; | ||
result.position = getNumber(parts[0]) - 1; | ||
} | ||
if (parts[0].match(/^[wxyz][1-9]/)) { | ||
result.fromEnd = true; | ||
result.position = numberResidues - 1 - getNumber(parts[0]); | ||
} | ||
} | ||
|
||
if (result.fromEnd) result.color = 'red'; | ||
if (result.fromBegin) result.color = 'blue'; | ||
if (result.internal) { | ||
switch (result.type.substring(0, 1)) { | ||
case 'a': | ||
result.color = 'green'; | ||
break; | ||
case 'b': | ||
result.color = 'orange'; | ||
break; | ||
case 'c': | ||
result.color = 'cyan'; | ||
break; | ||
default: | ||
result.color = 'green'; | ||
} | ||
} | ||
} | ||
|
||
if (merge.charge) { | ||
const unique = {}; | ||
for (let result of results) { | ||
if (!unique[result.type]) { | ||
unique[result.type] = []; | ||
} | ||
unique[result.type].push(result); | ||
} | ||
results = []; | ||
for (let key in unique) { | ||
let current = unique[key][0]; | ||
current.similarity = unique[key].reduce( | ||
(previous, item) => previous + item.similarity, | ||
0, | ||
); | ||
current.similarity = current.similarity / unique[key].length; | ||
results.push(current); | ||
current.charge = ''; | ||
} | ||
} | ||
|
||
for (let result of results) { | ||
if (result.similarity > 0.95) { | ||
result.textColor = 'black'; | ||
} else if (result.similarity > 0.9) { | ||
result.textColor = '#333'; | ||
} else if (result.similariy > 0.8) { | ||
result.textColor = '#666'; | ||
} else { | ||
result.textColor = '#999'; | ||
} | ||
} | ||
|
||
results = filterResults(results, filter); | ||
|
||
// sort by residue length | ||
results.sort((a, b) => a.length - b.length); | ||
data.results = results; | ||
} | ||
|
||
function getNumber(text) { | ||
return Number(text.replace(/^.([0-9]+).*$/, '$1')); | ||
} | ||
|
||
function filterResults(results, filter) { | ||
if (!filter) return; | ||
let { | ||
minRelativeQuantity = 0, | ||
minSimilarity = 0, | ||
minQuantity = 0, | ||
showInternals = true, | ||
} = filter; | ||
|
||
if (minRelativeQuantity) { | ||
minQuantity = | ||
Math.max(...results.map((entry) => entry.quantity)) * minRelativeQuantity; | ||
} | ||
|
||
if (minSimilarity) { | ||
results = results.filter( | ||
(result) => !result.similarity || result.similarity >= minSimilarity, | ||
); | ||
} | ||
if (minQuantity) { | ||
results = results.filter( | ||
(result) => !result.quantity || result.quantity >= minQuantity, | ||
); | ||
} | ||
|
||
if (!showInternals) { | ||
results = results.filter((result) => !result.internal); | ||
} | ||
return results; | ||
} |
Oops, something went wrong.