diff --git a/angular.json b/angular.json index d63e535..893400c 100644 --- a/angular.json +++ b/angular.json @@ -217,12 +217,12 @@ "configurations": { "production": { "tsConfig": "projects/ngx-sequence-viewer/tsconfig.lib.prod.json" - }, + }, "development": { "tsConfig": "projects/ngx-sequence-viewer/tsconfig.lib.json" } }, - "defaultConfiguration": "production" + "defaultConfiguration": "development" }, "test": { "builder": "@angular-devkit/build-angular:karma", diff --git a/projects/ngx-sequence-viewer/package.json b/projects/ngx-sequence-viewer/package.json index 069fc05..0b5ad02 100644 --- a/projects/ngx-sequence-viewer/package.json +++ b/projects/ngx-sequence-viewer/package.json @@ -1,6 +1,6 @@ { "name": "ngx-sequence-viewer", - "version": "0.0.6", + "version": "0.0.9", "peerDependencies": { "@angular/common": "^17.3.0", "@angular/core": "^17.3.0" diff --git a/projects/utils/fasta.spec.ts b/projects/ngx-sequence-viewer/src/lib/colors.spec.ts similarity index 100% rename from projects/utils/fasta.spec.ts rename to projects/ngx-sequence-viewer/src/lib/colors.spec.ts diff --git a/projects/ngx-sequence-viewer/src/lib/ngx-sequence-viewer.component.html b/projects/ngx-sequence-viewer/src/lib/ngx-sequence-viewer.component.html index 8c96ff6..d78f34c 100644 --- a/projects/ngx-sequence-viewer/src/lib/ngx-sequence-viewer.component.html +++ b/projects/ngx-sequence-viewer/src/lib/ngx-sequence-viewer.component.html @@ -9,7 +9,7 @@
Index
- @if(sequences.length > 1) { + @if(sequences!.length > 1) {
Consensus
} @@ -30,7 +30,7 @@ - @if(sequences.length > 1) { + @if(sequences!.length > 1) { } @@ -81,7 +81,7 @@ [style.color]="style['color']" (mouseenter)="onMouseEnter($event, indexService.keys[j])" (mousedown)="onMouseDown($event, indexService.keys[j])"> - {{ value ? value : sequences[i][j] }} + {{ value ? value : sequences![i][j] }} } } diff --git a/projects/ngx-sequence-viewer/src/lib/ngx-sequence-viewer.component.ts b/projects/ngx-sequence-viewer/src/lib/ngx-sequence-viewer.component.ts index 7c61d90..504f8b5 100644 --- a/projects/ngx-sequence-viewer/src/lib/ngx-sequence-viewer.component.ts +++ b/projects/ngx-sequence-viewer/src/lib/ngx-sequence-viewer.component.ts @@ -7,7 +7,7 @@ import { CommonModule } from '@angular/common'; import { ColorMap, ZAPPO } from './colors'; import { SelectionService } from './services/selection.service'; import { IndexService } from './services/index.service'; -import { parseFasta } from './utils'; +import { FASTA } from './utils'; // export interface Locus { // // Define start position (for both point and range loci) @@ -133,11 +133,12 @@ export class NgxSequenceViewerComponent implements OnChanges { @Input() public sequence?: string; - public sequences!: string[]; + @Input() + public sequences?: string[]; public get first(): string { // Just return first sequence - return this.sequences[0]; + return (this.sequences as string[])[0]; } public get length(): number { @@ -178,7 +179,7 @@ export class NgxSequenceViewerComponent implements OnChanges { const styles: Record> = {}; // Define sequences: index, consensus, input sequences const consensus = this.consensus.map(([aa]) => aa).join(''); - const sequences = [consensus, ...this.sequences]; + const sequences = [consensus, ...this.sequences || []]; // Loop through each row (sequence) for (let i = 0; i < sequences.length; i++) { // Update index @@ -328,10 +329,18 @@ export class NgxSequenceViewerComponent implements OnChanges { * If expectations are not met, then an error is thrown. */ public setSequences(): void { + // Case sequences are provided + if (this.sequences && this.labels) { + // Check whether the size of sequences and labels match + if (this.sequences.length !== this.labels.length) { + // Otherwise, throw an error + throw new Error('Number of sequences does not match number of labels'); + } + } // Case fasta file is provided - if (this.fasta) { + else if (this.fasta) { // Attempt to parse fasta file - const parsed = parseFasta(this.fasta); + const parsed = FASTA.parse(this.fasta); // Set sequences and labels this.sequences = parsed.map((entry) => entry.sequence); this.labels = this.labels || parsed.map((entry) => entry.label); @@ -401,14 +410,16 @@ export class NgxSequenceViewerComponent implements OnChanges { public setLogo(): void { // Initialize logo this.logo = []; + // define sequences + const sequences = this.sequences || []; // Define number of sequences - const count = this.sequences.length; + const count = sequences.length; // Loop through each position in the alignment for (let i = 0; i < this.length; i++) { // Define a position in the logo let position: { [aa: string]: number } = {}; // Loop through each sequence in the alignment - for (const sequence of this.sequences) { + for (const sequence of sequences) { // Get amino acid in current position const aa = sequence[i]; // Update count of amino acid in position diff --git a/projects/utils/mmcif.spec.ts b/projects/ngx-sequence-viewer/src/lib/utils.spec.ts similarity index 100% rename from projects/utils/mmcif.spec.ts rename to projects/ngx-sequence-viewer/src/lib/utils.spec.ts diff --git a/projects/ngx-sequence-viewer/src/lib/utils.ts b/projects/ngx-sequence-viewer/src/lib/utils.ts index 659192a..bcee1a4 100644 --- a/projects/ngx-sequence-viewer/src/lib/utils.ts +++ b/projects/ngx-sequence-viewer/src/lib/utils.ts @@ -1,9 +1,39 @@ -/** Parse fasta file -* -* @param {string} text - Input text, in fasta format. -* @returns {{ sequence: string, label: string }[]} - Parsed sequences and labels. -*/ -export function parseFasta(text: string): { sequence: string, label: string }[] { +export abstract class Parser { + + protected abstract parseText(text: string): T; + + protected parseFile(file: Blob): Promise { + // Cast input file to string + const reader = new FileReader(); + // Read file as text + reader.readAsText(file, 'utf-8'); + // Return promise + return new Promise((resolve, reject) => { + // Resolve promise with parsed text + reader.onload = () => resolve(this.parseText('' + reader.result)); + // Reject promise with error + reader.onerror = error => reject(error); + }); + + } + + public parse(input: string): T; + public parse(input: Blob): Promise; + public parse(input: Blob | string): T | Promise { + // Case input is not a string + if (typeof input !== 'string') { + // Then parse file + return this.parseFile(input); + } + // Otherwise, just parse text + return this.parseText('' + input); + } +} + +export type Sequence = { sequence: string, label: string }; + +class FastaParser extends Parser { + protected override parseText(text: string): Sequence[] { // Split line by newline character const lines = text.split(/[\n\r]+/); // Define output @@ -28,4 +58,8 @@ export function parseFasta(text: string): { sequence: string, label: string }[] } // Return parsed sequences and labels return parsed; - } \ No newline at end of file + } +} + +// Export single instance of fasta parser +export const FASTA = new FastaParser(); diff --git a/projects/ngx-sequence-viewer/tsconfig.lib.prod.json b/projects/ngx-sequence-viewer/tsconfig.lib.prod.json index 06de549..a2ca365 100644 --- a/projects/ngx-sequence-viewer/tsconfig.lib.prod.json +++ b/projects/ngx-sequence-viewer/tsconfig.lib.prod.json @@ -2,7 +2,8 @@ { "extends": "./tsconfig.lib.json", "compilerOptions": { - "declarationMap": false + "declarationMap": false, + }, "angularCompilerOptions": { "compilationMode": "partial" diff --git a/projects/utils/fasta.ts b/projects/utils/fasta.ts deleted file mode 100644 index 3a60c70..0000000 --- a/projects/utils/fasta.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Parser } from './parser'; - -export type Sequence = { sequence: string, label: string }; - -class FastaParser extends Parser { - - public override parseText(text: string): Sequence[] { - // Split line by newline character - const lines = text.split(/[\n\r]+/); - // Define output - const parsed: { sequence: string, label: string }[] = []; - // Define current index - let index = -1; - // Loop through each line - for (let line of lines) { - // Sanitize line - line = line.trim(); - // In case line starts with '>' character, then define new sequence entry - if (line.startsWith('>')) { - // Define new sequence entry - parsed.push({ sequence: '', label: line.slice(1) }); - // Update index - index++ - } - // In case index (0) has been defined beforehand, then current line is sequence - else if (index > -1) parsed[index].sequence += line; - // Otherwise, fine is not fasta formatted and an error is thrown - else throw new Error('Provided text is not in fasta format'); - } - // Return parsed sequences and labels - return parsed; - } - -} - -export const FASTA = new FastaParser(); \ No newline at end of file diff --git a/projects/utils/index.ts b/projects/utils/index.ts deleted file mode 100644 index b79a817..0000000 --- a/projects/utils/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { MMCIF, Residues, Residue } from './mmcif'; -export { FASTA, Sequence } from './fasta'; diff --git a/projects/utils/mmcif.ts b/projects/utils/mmcif.ts deleted file mode 100644 index 1d497e2..0000000 --- a/projects/utils/mmcif.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { Parser } from './parser'; - -export interface Residue { - // Define sequence number - authSeqId: number; - // Define residue's insertion code - pdbInsCode: string; - // Define residue's name (one letter code) - authCompId: string; -} - -export type Residues = { - [model: number]: { - [chain: string]: Residue[] - } -} - -class MMCIFParser extends Parser { - - readonly ThreeToOne = { - 'ALA': 'A', 'ARG': 'R', 'ASN': 'N', 'ASP': 'D', 'CYS': 'C', - 'GLN': 'Q', 'GLU': 'E', 'GLY': 'G', 'HIS': 'H', 'ILE': 'I', - 'LEU': 'L', 'LYS': 'K', 'MET': 'M', 'PHE': 'F', 'PRO': 'P', - 'SER': 'S', 'THR': 'T', 'TRP': 'W', 'TYR': 'Y', 'VAL': 'V' - } - - readonly OneToThree = { - 'A': 'ALA', 'R': 'ARG', 'N': 'ASN', 'D': 'ASP', 'C': 'CYS', - 'Q': 'GLN', 'E': 'GLU', 'G': 'GLY', 'H': 'HIS', 'I': 'ILE', - 'L': 'LEU', 'K': 'LYS', 'M': 'MET', 'F': 'PHE', 'P': 'PRO', - 'S': 'SER', 'T': 'THR', 'W': 'TRP', 'Y': 'TYR', 'V': 'VAL' - }; - - public override parseText(text: string): Residues { - // Split text in lines - const lines = text.split('\n'); - // Initialize table, each item is a column - const table: Record = {}; - // Initialize column to index mapping - const columns: Record = {}; - // Loop through each line - for (let i = 0; i < lines.length; i++) { - // Sanitize line - let line = lines[i] = (lines[i]).trim(); - // Check for lines starting with _atom - if (line.startsWith('_atom_site.')) { - // Initialize index for culumns - let j = 0; - // Loop through each following line - for (j; i + j < lines.length; j++) { - // Sanitize line - line = lines[i + j] = (lines[i + j]).trim(); - // Loop through each column in table - if (line.startsWith('_atom_site.')) { - // Initialize column key - const column = columns[j] = line; - // Initialize column values - table[column] = []; - } - // Otherwise, break loop - else break; - } - // Update index - i = i + j; - } - // Otherwise, check if columns are defined - else if (table['_atom_site.id']) { - // Loop through each row - for (let j = 0; i + j < lines.length; j++) { - // Define current line - line = lines[i + j] = (lines[i + j]).trim(); - // Case line does not contain stop character - if (line !== '#') { - // Split line in columns - const values = line.split(/\s+/); - // Loop through each value - for (const [index, value] of values.entries()) { - // Get column key - const column = columns[index]; - // Add value to column - table[column].push(value.replace(/\?/g, '')); - } - } - // Otherwise, break all loops - else i = j = lines.length; - } - } - } - // Initialize residues - const residues: Residues = {}; - // Define length of table (as number of items in first column) - const length = table['_atom_site.id'].length; - // Group each item in table by `author_asym_id` - for (let i = 0; i < length; i++) { - // Get chain identifier - const authAsymId = '' + table['_atom_site.auth_asym_id'][i]; - // Get model number - const pdbxPDBModelNum = parseInt('' + table['_atom_site.pdbx_PDB_model_num'][i]); - // Get residue name - const authCompId = '' + table['_atom_site.label_comp_id'][i]; - // Get residue number - const authSeqId = parseInt('' + table['_atom_site.auth_seq_id'][i]); - // Get residue insertion code - const pdbxPDBInsCode = '' + table['_atom_site.pdbx_PDB_ins_code'][i]; - // Initialize model - residues[pdbxPDBModelNum] = residues[pdbxPDBModelNum] || {}; - // Initialize chain - const residueList = residues[pdbxPDBModelNum][authAsymId] = residues[pdbxPDBModelNum][authAsymId] || []; - // Initialize residue - const currentResidue = { authSeqId, pdbInsCode: pdbxPDBInsCode, authCompId: authCompId }; - const previousResidue = residueList.length > 0 ? residueList[residueList.length - 1] : undefined; - // Define previous residue - if (!previousResidue || previousResidue.authSeqId !== authSeqId || previousResidue.pdbInsCode !== pdbxPDBInsCode) { - // Add residue to chain - residueList.push(currentResidue); - } - } - // Return residues - return residues; - } -} - -export const MMCIF = new MMCIFParser(); \ No newline at end of file diff --git a/projects/utils/parser.spec.ts b/projects/utils/parser.spec.ts deleted file mode 100644 index e69de29..0000000 diff --git a/projects/utils/parser.ts b/projects/utils/parser.ts deleted file mode 100644 index 76e78e4..0000000 --- a/projects/utils/parser.ts +++ /dev/null @@ -1,31 +0,0 @@ -export abstract class Parser { - - protected abstract parseText(text: string): T; - - protected parseFile(file: Blob): Promise { - // Cast input file to string - const reader = new FileReader(); - // Read file as text - reader.readAsText(file, 'utf-8'); - // Return promise - return new Promise((resolve, reject) => { - // Resolve promise with parsed text - reader.onload = () => resolve(this.parseText('' + reader.result)); - // Reject promise with error - reader.onerror = error => reject(error); - }); - - } - - public parse(input: string): T; - public parse(input: Blob): Promise; - public parse(input: Blob | string): T | Promise { - // Case input is not a string - if (typeof input !== 'string') { - // Then parse file - return this.parseFile(input); - } - // Otherwise, just parse text - return this.parseText('' + input); - } -} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index d0466db..b668a82 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,15 +20,9 @@ "@ngx-sequence-viewer": [ "./projects/ngx-sequence-viewer/src/public-api" ], - // "ngx-sequence-viewer": [ - // "./dist/ngx-sequence-viewer" - // ], - // "ngx-features-viewer": [ - // "./dist/ngx-features-viewer", - // ], - // "ngx-structure-viewer": [ - // "./dist/ngx-structure-viewer" - // ] + "@utils": [ + "./projects/utils/index" + ] }, "sourceMap": true, "declaration": false,