-
-
+
+
+
+
@@ -382,15 +390,17 @@ No Pathway Found
-
+
No Pathway Found
class="flex flex-col mx-4 bg-gray-50"
>
-
- Pathway {{ selectedPathway$.value + 1 }}:
+
+ Pathway {{ selectedPathway + 1 }}:
{{ pathway.reactions[0].primaryPrecursor!.name }}
@@ -417,7 +427,7 @@ No Pathway Found
-
+
@@ -426,7 +436,7 @@
No Pathway Found
{{
- reactions![selectedPathway$.value].length
+ reactions![selectedPathway].length
}}
Predicted Steps
@@ -436,7 +446,7 @@
No Pathway Found
- {{ deltaGs![selectedPathway$.value].gibbsEnergy }}±{{ deltaGs![selectedPathway$.value].std | number:'1.1-1' }}kJ/mol
+ {{ deltaGs![selectedPathway].gibbsEnergy | number: '1.1-2' }}±{{ deltaGs![selectedPathway].std | number:'1.1-1' }}kJ/mol
Delta G
@@ -471,13 +481,17 @@
No Pathway Found
-
-
+
- Reaction {{selectedPathway$.value + 1}}.{{ i + 1 }}
-
+ Reaction {{selectedPathway + 1}}.{{ i + 1 }}
Delta G Calculation
@@ -495,7 +509,11 @@
No Pathway Found
@@ -510,7 +528,7 @@
No Pathway Found
Reaction Rule:
-
{{reaction.reactionRule}}
+
{{reaction.reactionRule ? reaction.reactionRule : 'N/A'}}
@@ -638,13 +656,16 @@
No Pathway Found
-
- -80
- +50
+ {{ novostoicService.thermoFeasibilityMin }}
+ +{{ novostoicService.thermoFeasibilityMax }}
diff --git a/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.scss b/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.scss
index 7c20353..d615dd9 100644
--- a/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.scss
+++ b/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.scss
@@ -1,20 +1,27 @@
-$predicted_color: #C79807;
+$infeasible_color: #C79807;
$indicator_color: var(--surface-500);
.strip_pattern {
- background: repeating-linear-gradient(white, white 3.9px, black 4px, black 7.9px);;
+ background: repeating-linear-gradient(transparent, transparent 3.9px, black 4px, black 7.9px);;
}
.strip_pattern_to_right_thermo_infeasible {
- background: repeating-linear-gradient(to right, white, white 3px, $predicted_color 3.1px, $predicted_color 6.1px);
+ background: repeating-linear-gradient(to right, transparent, transparent 3px, $infeasible_color 3.1px, $infeasible_color 6.1px);
}
.strip_pattern_to_right_thermo_feasible {
- background: repeating-linear-gradient(to right, white, white 3px, $indicator_color 3.1px, $indicator_color 6.1px);
+ background: repeating-linear-gradient(to right, transparent, transparent 3px, $indicator_color 3.1px, $indicator_color 6.1px);
}
.strip_pattern_border {
- border-image: url("/assets/border.png") 2 round;
+
+ &.thermodynamic_feasible {
+ background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='6' ry='6' stroke='%239FA9B7FF' stroke-width='2' stroke-dasharray='8%2c 7' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e");
+ }
+
+ &.thermodynamic_infeasible {
+ background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='6' ry='6' stroke='%23C79807FF' stroke-width='3' stroke-dasharray='9%2c 13' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e");
+ }
}
.primary_precursor_container {
@@ -102,11 +109,11 @@ $indicator_color: var(--surface-500);
}
.thermo_infeasible::before {
- border-left-color: $predicted_color !important;
+ border-left-color: $infeasible_color !important;
}
.next_thermo_infeasible::after {
- background: $predicted_color !important;
+ background: $infeasible_color !important;
}
.pathway_dialog {
@@ -138,3 +145,17 @@ $indicator_color: var(--surface-500);
@apply border-t;
}
}
+
+.has-indicator::after {
+ content: 'Click the whole pathway to learn more';
+ @apply absolute;
+ @apply bg-white;
+ @apply text-center;
+ @apply block;
+ @apply bottom-0;
+ @apply left-0;
+ @apply w-full;
+ @apply py-1;
+ @apply border border-solid border-y border-[--surface-d];
+ @apply shadow-sm;
+ }
\ No newline at end of file
diff --git a/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.ts b/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.ts
index 878c2b7..b59fa0d 100644
--- a/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.ts
+++ b/src/app/components/novostoic/pathway-search-result/pathway-search-result.component.ts
@@ -43,7 +43,7 @@ export class PathwaySearchResultComponent extends JobResult {
reactants: reaction.reactants.filter((reactant) => {
return reactant.molecule.name !== reaction.primaryPrecursor?.name;
}),
- isThermodynamicalInfeasible: reaction.deltaG.gibbsEnergy > 20,
+ isThermodynamicalInfeasible: reaction.deltaG.gibbsEnergy > this.novostoicService.thermoFeasibilityMax,
}))
}
})
@@ -52,8 +52,10 @@ export class PathwaySearchResultComponent extends JobResult {
tap((data) => console.log('response', data))
);
- visible$ = new BehaviorSubject(false);
- selectedPathway$ = new BehaviorSubject(0);
+ visible: boolean = false;
+ highlightPathway: number | null = null;
+ selectedPathway: number = 0;
+ selectedReactionIdx: number | null = null;
pathwayDeltaGs$ = this.response$.pipe(
map((response) => response.pathways.map((pathway) => pathway.reactions.reduce((p, v) => ({
@@ -124,8 +126,8 @@ export class PathwaySearchResultComponent extends JobResult {
]).pipe(map(([intermediates, cofactors]) => intermediates.length + cofactors.length));
selectedThermoFeasibleMode$ = new BehaviorSubject<'all' | 'any' | null>(null);
- feasibleRangeMin$ = new BehaviorSubject
(-80);
- feasibleRangeMax$ = new BehaviorSubject(20);
+ feasibleRangeMin$ = new BehaviorSubject(this.novostoicService.thermoFeasibilityMin);
+ feasibleRangeMax$ = new BehaviorSubject(this.novostoicService.thermoFeasibilityMax);
feasibleRange$ = combineLatest([
this.feasibleRangeMin$,
this.feasibleRangeMax$,
@@ -144,26 +146,26 @@ export class PathwaySearchResultComponent extends JobResult {
const intermediatesSet = new Set(intermediates.map((intermediate) => intermediate.name));
const cofactorsSet = new Set(cofactors.map((cofactor) => cofactor.name));
return response.pathways.map((pathway) => {
- let intermediatesMatch = true;
- let cofactorsMatch = true;
+ let intermediatesMatch = false;
+ let cofactorsMatch = false;
let thermodynamicalMatch = true;
let thermodynamicalRangeMatch = true;
pathway.reactions.forEach((reaction) => {
- if (intermediatesSet.size && intermediatesMatch && reaction.targetMolecule) {
- intermediatesMatch = intermediatesSet.has(reaction.targetMolecule.name);
+ if (reaction.targetMolecule) {
+ intermediatesMatch ||= intermediatesSet.size > 0 ? intermediatesSet.has(reaction.targetMolecule.name) : true;
}
- if (cofactorsSet.size && cofactorsMatch) {
+
+ if (cofactorsSet.size) {
reaction.reactants.forEach((reactant) => {
- if (cofactorsMatch && cofactorsSet.has(reactant.molecule.name)) {
- cofactorsMatch = true;
- }
+ cofactorsMatch ||= cofactorsSet.has(reactant.molecule.name);
});
reaction.products.forEach((product) => {
- if (cofactorsMatch && cofactorsSet.has(product.molecule.name)) {
- cofactorsMatch = true;
- }
+ cofactorsMatch ||= cofactorsSet.has(product.molecule.name);
});
+ } else {
+ cofactorsMatch = true;
}
+
if (mode && thermodynamicalMatch) {
thermodynamicalMatch = mode === 'any'
? thermodynamicalMatch && !reaction.isThermodynamicalInfeasible
@@ -178,6 +180,8 @@ export class PathwaySearchResultComponent extends JobResult {
thermodynamicalMatch = !thermodynamicalMatch;
}
+ console.log('pathway', pathway, intermediatesMatch, cofactorsMatch, thermodynamicalMatch, thermodynamicalRangeMatch);
+
return {
...pathway,
match: intermediatesMatch && cofactorsMatch && thermodynamicalMatch && thermodynamicalRangeMatch,
@@ -258,7 +262,7 @@ export class PathwaySearchResultComponent extends JobResult {
);
constructor(
- private novostoicService: NovostoicService,
+ protected novostoicService: NovostoicService,
private route: ActivatedRoute,
) {
super(novostoicService)
@@ -267,8 +271,8 @@ export class PathwaySearchResultComponent extends JobResult {
resetFilters() {
this.intermediatesFilters$.next([]);
this.coFactorsFilters$.next([]);
- this.feasibleRangeMax$.next(20);
- this.feasibleRangeMin$.next(-80);
+ this.feasibleRangeMax$.next(this.novostoicService.thermoFeasibilityMax);
+ this.feasibleRangeMin$.next(this.novostoicService.thermoFeasibilityMin);
this.selectedThermoFeasibleMode$.next(null);
}
diff --git a/src/app/components/novostoic/stoichiometry-reaction/stoichiometry-reaction.component.html b/src/app/components/novostoic/stoichiometry-reaction/stoichiometry-reaction.component.html
index d6b3581..ec3daa6 100644
--- a/src/app/components/novostoic/stoichiometry-reaction/stoichiometry-reaction.component.html
+++ b/src/app/components/novostoic/stoichiometry-reaction/stoichiometry-reaction.component.html
@@ -6,23 +6,11 @@
(mouseenter)="primaryPrecursorOverlay.show($event)"
(mouseleave)="primaryPrecursorOverlay.hide()"
>
-
-
-
+
@@ -36,27 +24,10 @@
(mouseleave)="coreactantOverlay.hide()"
>{{ reactant.amount | number: '1.1-2' }} {{
reactant.molecule.name }}
-
-
-
-
- Co-Reactant
-
-
-
-
-
-
-
+
@@ -77,27 +48,10 @@
[class]="moleculeStyleClass"
>{{ reactant.amount | number: '1.1-2' }} {{
reactant.molecule.name }}
-
-
-
-
- Co-Product
-
-
-
-
-
-
-
+
+
@@ -109,40 +63,11 @@
(mouseenter)="targetMoleculeOverlay.show($event)"
(mouseleave)="targetMoleculeOverlay.hide()"
>
-
-
-
+
-
-
-
-
- Common Name
- {{ molecule.name }}
-
-
- SMILES
- {{ molecule.smiles }}
-
-
- KEGG ID
- {{ molecule.kegg_id }}
-
-
+
\ No newline at end of file
diff --git a/src/app/models/dg-predictor.ts b/src/app/models/dg-predictor.ts
index 44f1b1d..366ed60 100644
--- a/src/app/models/dg-predictor.ts
+++ b/src/app/models/dg-predictor.ts
@@ -1,10 +1,11 @@
import { FormArray, FormControl, FormGroup, Validators } from "@angular/forms";
+import { debounceTime } from "rxjs";
interface ReactionFormControl {
type: FormControl;
+ containNovelMolecule?: FormControl;
reactionSmiles?: FormControl;
- moleculeNumber?: FormControl;
- moleculeInchiOrSmiles?: FormControl;
+ novelMolecules?: FormArray;
reactionKeggId?: FormControl;
}
@@ -12,12 +13,15 @@ export class ThermodynamicalFeasibilityRequest {
form = new FormGroup({
ph: new FormControl(5, [Validators.required]),
ionicStrength: new FormControl(0.3, [Validators.required]),
- reactions: new FormArray([this.createReactionKeggIDFormControl()]),
+ reactions: new FormArray([
+ this.createReactionKeggIDFormControl()
+ ]),
agreeToSubscription: new FormControl(false),
subscriberEmail: new FormControl("", [Validators.email]),
reactionInputType: new FormControl("keggId", [Validators.required]),
});
+ private uniqueNovelMolecule = new Set();
private createReactionSmilesFormControl() {
return new FormGroup({
type: new FormControl("smiles"),
@@ -28,18 +32,22 @@ export class ThermodynamicalFeasibilityRequest {
private createReactionKeggIDFormControl() {
const formGroup = new FormGroup({
type: new FormControl("keggId"),
- moleculeNumber: new FormControl("", []),
- moleculeInchiOrSmiles: new FormControl("", []),
+ containNovelMolecule: new FormControl(false),
+ novelMolecules: new FormArray([] as any[]),
reactionKeggId: new FormControl("", [Validators.required]),
});
- formGroup.controls["moleculeInchiOrSmiles"]?.valueChanges.subscribe((value) => {
+ formGroup.controls["containNovelMolecule"]?.valueChanges.subscribe((value) => {
if (value) {
- formGroup.controls["moleculeNumber"]?.addValidators([Validators.required]);
+ this.addNovelMolecule(formGroup);
} else {
- formGroup.controls["moleculeNumber"]?.clearValidators();
+ formGroup.controls["novelMolecules"]?.controls.forEach((_, i) => {
+ this.removeNovelMolecule(formGroup, i);
+ });
}
- formGroup.controls["moleculeNumber"]?.updateValueAndValidity();
+ formGroup.controls["novelMolecules"]?.controls.forEach((control) => {
+ control.updateValueAndValidity();
+ });
});
return formGroup;
@@ -70,14 +78,21 @@ export class ThermodynamicalFeasibilityRequest {
reactions: [
{
type: "keggId",
- moleculeNumber: "00001",
- moleculeInchiOrSmiles: "InChI=1S/C14H12O/c15-14-8-4-7-13(11-14)10-9-12-5-2-1-3-6-12/h1-11,15H/b10-9+",
+ containNovelMolecule: true,
+ novelMolecules: [
+ {
+ iid: "iid-0.123456789",
+ alias: "N00001",
+ value: "InChI=1S/C14H12O/c15-14-8-4-7-13(11-14)10-9-12-5-2-1-3-6-12/h1-11,15H/b10-9+",
+ moleculeExisted: false
+ }
+ ],
reactionKeggId: "C01745 + C00004 <=> N00001 + C00003 + C00001",
}
],
agreeToSubscription: false,
subscriberEmail: "",
- reactionInputType: "smiles",
+ reactionInputType: "keggId",
});
return request;
@@ -98,6 +113,62 @@ export class ThermodynamicalFeasibilityRequest {
}
}
+ addNovelMolecule(subform: FormGroup) {
+ const group = new FormGroup({
+ iid: new FormControl(`iid-${Math.random()}`),
+ alias: new FormControl("", [Validators.required]),
+ value: new FormControl("", [Validators.required]),
+ moleculeExisted: new FormControl(false),
+ })
+
+ // listen to value changes and update the molecule index
+ group.controls['value'].valueChanges
+ .subscribe((value) => {
+ let aliasSet = false;
+
+ this.uniqueNovelMolecule.clear();
+ group.controls['moleculeExisted'].setValue(false);
+
+ this.form.controls["reactions"].controls.forEach((reaction) => {
+ reaction.controls['novelMolecules']?.controls.forEach((g) => {
+ if (g.controls['iid'] === group.controls['iid']) {
+ return;
+ }
+
+ const moleculeValue = g.controls['value'].value;
+ this.uniqueNovelMolecule.add(moleculeValue);
+ if (moleculeValue === value) {
+ group.controls['alias'].setValue(g.controls['alias'].value);
+ group.controls['moleculeExisted'].setValue(true);
+ aliasSet = true;
+ }
+ });
+ });
+
+ if (!aliasSet) {
+ const moleculeIndex = `${this.uniqueNovelMolecule.size + 1}`.padStart(5, "0");
+ group.controls['alias'].setValue(`N${moleculeIndex}`);
+ }
+ });
+
+ const moleculeIndex = `${this.uniqueNovelMolecule.size + 1}`.padStart(5, "0");
+ group.controls['alias'].setValue(`N${moleculeIndex}`);
+
+ subform.controls['novelMolecules']!.push(group);
+ }
+
+ removeNovelMolecule(subform: FormGroup, idx: number) {
+ subform.controls["novelMolecules"]?.controls.forEach((group) => {
+ group.controls['value'].clearValidators();
+ group.controls['alias'].clearValidators();
+ });
+
+ subform.controls['novelMolecules']!.removeAt(idx);
+ if (!subform.controls['novelMolecules']!.length) {
+ this.addNovelMolecule(subform);
+ }
+ }
+
clearAllReactions() {
this.clearAllInputHelper(this.form.controls["reactions"]);
}
@@ -114,10 +185,11 @@ export class ThermodynamicalFeasibilityRequest {
add_info: {},
}
- if (reaction.moleculeInchiOrSmiles) {
- payload["add_info"] = {
- ['N' + reaction.moleculeNumber]: reaction.moleculeInchiOrSmiles,
- };
+ if (reaction.containNovelMolecule) {
+ payload['add_info'] = {};
+ reaction.novelMolecules?.forEach((group, i) => {
+ payload["add_info"][group.alias] = group.value;
+ });
}
return payload;
diff --git a/src/app/models/job-result.ts b/src/app/models/job-result.ts
index 430e956..7d7af9e 100644
--- a/src/app/models/job-result.ts
+++ b/src/app/models/job-result.ts
@@ -1,4 +1,4 @@
-import { timer, switchMap, tap, takeWhile, BehaviorSubject, skipUntil, filter, map, delay, shareReplay } from "rxjs";
+import { timer, switchMap, tap, takeWhile, BehaviorSubject, skipUntil, filter, map, delay, shareReplay, share, combineLatest } from "rxjs";
import { JobStatus, JobType } from "../api/mmli-backend/v1";
import { NovostoicService } from "../services/novostoic.service";
@@ -7,13 +7,14 @@ export class JobResult {
jobType: JobType;
isLoading$ = new BehaviorSubject(true);
+ resultLoaded$ = new BehaviorSubject(false);
statusResponse$ = timer(0, 10000).pipe(
switchMap(() => this.service.getResultStatus(
this.jobType,
this.jobId,
)),
- tap(() => this.isLoading$.next(true)),
+ tap(() => this.resultLoaded$.value ? null : this.isLoading$.next(true)),
takeWhile((data) =>
data.phase === JobStatus.Processing
|| data.phase === JobStatus.Queued
@@ -26,6 +27,7 @@ export class JobResult {
switchMap(() => this.service.getResult(this.jobType, this.jobId)),
delay(1000),
tap(() => this.isLoading$.next(false)),
+ tap(() => this.resultLoaded$.next(true)),
tap((data) => { console.log('result: ', data) }),
shareReplay(1),
);
diff --git a/src/app/services/novostoic.service.ts b/src/app/services/novostoic.service.ts
index ee7ab74..ba871c6 100644
--- a/src/app/services/novostoic.service.ts
+++ b/src/app/services/novostoic.service.ts
@@ -23,6 +23,9 @@ export class NovostoicService {
submitted_at: "2020-01-01 10:10:10"
};
+ readonly thermoFeasibilityMax = 50;
+ readonly thermoFeasibilityMin = -8000;
+
constructor(
private apiService: DefaultService,