Przeglądaj źródła

download msa && 3D structure

bioinsilico 2 lat temu
rodzic
commit
842bc2cb0d

+ 8 - 8
package-lock.json

@@ -1,18 +1,18 @@
 {
   "name": "@rcsb/rcsb-saguaro-3d",
-  "version": "2.3.7",
+  "version": "2.4.0-data-provider.1",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "@rcsb/rcsb-saguaro-3d",
-      "version": "2.3.7",
+      "version": "2.4.0-data-provider.1",
       "license": "MIT",
       "dependencies": {
         "@rcsb/rcsb-api-tools": "^4.1.1",
         "@rcsb/rcsb-molstar": "^2.5.11",
         "@rcsb/rcsb-saguaro": "^2.5.5",
-        "@rcsb/rcsb-saguaro-app": "^4.6.0-data-provider.7",
+        "@rcsb/rcsb-saguaro-app": "file:../rcsb-saguaro-app/rcsb-rcsb-saguaro-app-4.6.0-data-provider.7.tgz",
         "http-server": "^14.1.1",
         "molstar": "^3.29.0"
       },
@@ -2856,8 +2856,9 @@
     },
     "node_modules/@rcsb/rcsb-saguaro-app": {
       "version": "4.6.0-data-provider.7",
-      "resolved": "https://registry.npmjs.org/@rcsb/rcsb-saguaro-app/-/rcsb-saguaro-app-4.6.0-data-provider.7.tgz",
-      "integrity": "sha512-ya/HzZ8gLoGf3SI0aAMF5sMrv11L2IhERpKLoGBcusnNQc5VdPvo7h2EKZn8N4fdHUJTni1JpqPPuvwTTQy4cw==",
+      "resolved": "file:../rcsb-saguaro-app/rcsb-rcsb-saguaro-app-4.6.0-data-provider.7.tgz",
+      "integrity": "sha512-WvAXt2j0SsmLGth/rMGEfqvKre+yol0TMBv5xwahM22iqOVfo8TFB6FYfWhTzzIywhWmDH7wN++Ui/kmz9hclg==",
+      "license": "MIT",
       "dependencies": {
         "@rcsb/rcsb-api-tools": "^4.1.1",
         "@rcsb/rcsb-charts": "^0.0.1",
@@ -14428,9 +14429,8 @@
       }
     },
     "@rcsb/rcsb-saguaro-app": {
-      "version": "4.6.0-data-provider.7",
-      "resolved": "https://registry.npmjs.org/@rcsb/rcsb-saguaro-app/-/rcsb-saguaro-app-4.6.0-data-provider.7.tgz",
-      "integrity": "sha512-ya/HzZ8gLoGf3SI0aAMF5sMrv11L2IhERpKLoGBcusnNQc5VdPvo7h2EKZn8N4fdHUJTni1JpqPPuvwTTQy4cw==",
+      "version": "file:../rcsb-saguaro-app/rcsb-rcsb-saguaro-app-4.6.0-data-provider.7.tgz",
+      "integrity": "sha512-WvAXt2j0SsmLGth/rMGEfqvKre+yol0TMBv5xwahM22iqOVfo8TFB6FYfWhTzzIywhWmDH7wN++Ui/kmz9hclg==",
       "requires": {
         "@rcsb/rcsb-api-tools": "^4.1.1",
         "@rcsb/rcsb-charts": "^0.0.1",

+ 1 - 1
package.json

@@ -86,7 +86,7 @@
     "@rcsb/rcsb-api-tools": "^4.1.1",
     "@rcsb/rcsb-molstar": "^2.5.11",
     "@rcsb/rcsb-saguaro": "^2.5.5",
-    "@rcsb/rcsb-saguaro-app": "^4.6.0-data-provider.7",
+    "@rcsb/rcsb-saguaro-app": "file:../rcsb-saguaro-app/rcsb-rcsb-saguaro-app-4.6.0-data-provider.7.tgz",
     "http-server": "^14.1.1",
     "molstar": "^3.29.0"
   },

+ 58 - 0
src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/MsaPfvComponents/MsaUiSequenceAlignmentDownload.tsx

@@ -0,0 +1,58 @@
+import * as React from "react";
+import {DataContainer} from "../../../../../Utils/DataContainer";
+import {
+    RcsbFvModulePublicInterface
+} from "@rcsb/rcsb-saguaro-app/build/dist/RcsbFvWeb/RcsbFvModule/RcsbFvModuleInterface";
+import {RcsbFvStateInterface} from "../../../../../RcsbFvState/RcsbFvStateInterface";
+import {download, getFullDate, textToFile} from "../../../../../Utils/Download";
+
+export interface MsaUiSequenceAlignmentDownloadInterface {
+    rcsbFvContainer: DataContainer<RcsbFvModulePublicInterface>;
+    stateManager: RcsbFvStateInterface;
+}
+
+export class MsaUiSequenceAlignmentDownload extends React.Component<MsaUiSequenceAlignmentDownloadInterface>{
+
+    render() {
+        return <div title={"Download MSA of selected sequences"} onClick={()=>this.click()} style={{cursor: "pointer"}}>EXPORT MSA</div>;
+    }
+
+    private async click(): Promise<void> {
+        const targetAlignments =  await this.props.rcsbFvContainer.get()?.getAlignmentResponse();
+        const targets = targetAlignments?.target_alignment?.map(ta=>ta?.target_id)
+        if(!targets)
+            return;
+        const length = this.props.rcsbFvContainer.get()?.getFv().getBoardConfig().length;
+        if(!length)
+            return;
+        const msa: string[] = [];
+        targetAlignments?.target_alignment?.forEach(ta=>{
+            if(ta && this.props.stateManager.assemblyModelSate.getMap().has(ta.target_id ?? "none")){
+                const sequence = ta.target_sequence;
+                if(!sequence)
+                    return;
+                if(!ta.aligned_regions)
+                    return;
+                msa.push(`>${ta.target_id}|${ta.aligned_regions[0]?.query_begin}\n`);
+                const sequenceAlignment: string[] = [];
+                let prev = ta.aligned_regions[0]?.query_begin ?? Number.MIN_SAFE_INTEGER;
+                if(prev>1)
+                    sequenceAlignment.push("-".repeat(prev-1));
+                for(const ar of ta.aligned_regions ?? []){
+                    if(!ar)
+                        continue;
+                    if(ar.query_begin > prev) {
+                        sequenceAlignment.push("-".repeat(ar.query_begin - prev - 1));
+                    }
+                    sequenceAlignment.push(sequence.substring(ar.target_begin-1,ar.target_end));
+                    prev = ar.query_end;
+                }
+                if(prev < length)
+                    sequenceAlignment.push("-".repeat(length-prev));
+                msa.push(`${sequenceAlignment.join("")}\n`);
+            }
+        });
+        download( textToFile(msa), `sequence_alignment_${getFullDate()}.fasta` );
+    }
+
+}

+ 3 - 3
src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/MsaPfvComponents/MsaUiSortComponent.tsx

@@ -11,8 +11,7 @@ export interface MsaUiSortInterface {
     rcsbFvContainer: DataContainer<RcsbFvModulePublicInterface>;
     stateManager: RcsbFvStateInterface;
 }
-export class MsaUiSortComponent extends React.Component<MsaUiSortInterface, {}>{
-
+export class MsaUiSortComponent extends React.Component<MsaUiSortInterface>{
 
     render() {
         return <div title={"PIN selected entities to top"} onClick={()=>this.click()} style={{cursor: "pointer"}}>PIN ACTIVE</div>;
@@ -26,7 +25,7 @@ export class MsaUiSortComponent extends React.Component<MsaUiSortInterface, {}>{
             return;
 
         const headerShift: number|undefined = this.props.rcsbFvContainer.get()?.getFv().getBoardData().findIndex((d:RcsbFvRowConfigInterface<{},{},{},{targetId:string}>)=>d.metadata?.targetId);
-        if(!headerShift || headerShift<0)
+        if(typeof headerShift === "undefined" || headerShift<0)
             return;
         const threshold: number = targets.findIndex(
             target => !this.props.stateManager.assemblyModelSate.getMap().has(target)
@@ -42,4 +41,5 @@ export class MsaUiSortComponent extends React.Component<MsaUiSortInterface, {}>{
             )
         }
     }
+
 }

+ 18 - 0
src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/MsaPfvComponents/MsaUiStructureDownload.tsx

@@ -0,0 +1,18 @@
+import React from "react";
+import {RcsbFvStateInterface} from "../../../../../RcsbFvState/RcsbFvStateInterface";
+
+export interface MsaUiStructureDownloadInterface {
+    stateManager: RcsbFvStateInterface;
+}
+
+export class MsaUiStructureDownload extends React.Component<MsaUiStructureDownloadInterface>{
+
+    render() {
+        return <div title={"Download 3D structures"} onClick={()=>this.click()} style={{cursor: "pointer"}}>EXPORT 3D</div>;
+    }
+
+    private async click(): Promise<void> {
+        this.props.stateManager.next<"structure-download", undefined>({view:"ui-view",type:"structure-download"});
+    }
+
+}

+ 17 - 5
src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/MsaPfvManagerFactory.ts

@@ -8,7 +8,7 @@ import {
     RcsbFvAdditionalConfig,
     RcsbFvModulePublicInterface
 } from "@rcsb/rcsb-saguaro-app/build/dist/RcsbFvWeb/RcsbFvModule/RcsbFvModuleInterface";
-import {TagDelimiter, buildSequenceIdentityAlignmentFv} from "@rcsb/rcsb-saguaro-app";
+import {TagDelimiter} from "@rcsb/rcsb-saguaro-app";
 
 import {
     AlignmentRequestContextType
@@ -19,10 +19,11 @@ import {MsaRowMarkComponent} from "./MsaPfvComponents/MsaRowMarkComponent";
 import {
     PolymerEntityInstanceInterface
 } from "@rcsb/rcsb-saguaro-app/build/dist/RcsbCollectTools/DataCollectors/PolymerEntityInstancesCollector";
-import {SearchQuery} from "@rcsb/rcsb-api-tools/build/RcsbSearch/Types/SearchQueryInterface";
 import {DataContainer} from "../../../../Utils/DataContainer";
 import {MsaUiSortComponent} from "./MsaPfvComponents/MsaUiSortComponent";
 import {ActionMethods} from "@rcsb/rcsb-saguaro-app/build/dist/RcsbFvUI/Helper/ActionMethods";
+import {MsaUiSequenceAlignmentDownload} from "./MsaPfvComponents/MsaUiSequenceAlignmentDownload";
+import {MsaUiStructureDownload} from "./MsaPfvComponents/MsaUiStructureDownload";
 
 export interface MsaPfvManagerInterface<T extends any[]> {
     id:string;
@@ -109,12 +110,23 @@ class MsaPfvManager<T extends any[],R,L> extends AbstractPfvManager<{id:string},
                     rcsbFvContainer: this.rcsbFvContainer,
                     stateManager: this.stateManager
                 }
+            },{
+                component: MsaUiSequenceAlignmentDownload,
+                props:{
+                    rcsbFvContainer: this.rcsbFvContainer,
+                    stateManager: this.stateManager
+                }
+            },{
+                component: MsaUiStructureDownload,
+                props:{
+                    stateManager: this.stateManager
+                }
             }]
         }];
-        const module:RcsbFvModulePublicInterface = await this.config.buildMsaAlignmentFv(...args);
-        this.rcsbFvContainer.set(module);
+        this.module = await this.config.buildMsaAlignmentFv(...args);
+        this.rcsbFvContainer.set(this.module);
         await this.readyStateLoad();
-        return module;
+        return this.module;
     }
 
     private async readyStateLoad(): Promise<void> {

+ 1 - 1
src/RcsbFvState/RcsbFvStateInterface.ts

@@ -10,7 +10,7 @@ import {Subject, Subscription} from "rxjs";
 
 export type RcsbFvStateType<T="feature-click",D=undefined> = {
     type: "feature-click"|"selection-change"|"hover-change"|"model-change"|"representation-change"|"pfv-change"|T;
-    view: "1d-view" | "3d-view";
+    view: "1d-view" | "3d-view" | "ui-view";
     data?:D;
 };
 

+ 9 - 1
src/RcsbFvStructure/StructureViewerBehaviour/MsaBehaviour.ts

@@ -84,7 +84,7 @@ class MsaBehaviour<R,L> implements StructureViewerBehaviourInterface {
     }
 
     private subscribe(): Subscription {
-        return this.stateManager.subscribe<"model-change"|"representation-change"|"feature-click",AlignmentDataType & {tag:"polymer"|"non-polymer";isHidden:boolean;} & SelectedRegion[]>(async o=>{
+        return this.stateManager.subscribe<"model-change"|"representation-change"|"feature-click"|"structure-download",AlignmentDataType & {tag:"polymer"|"non-polymer";isHidden:boolean;} & SelectedRegion[]>(async o=>{
             if(o.type == "model-change" && o.view == "1d-view" && o.data)
                 await this.modelChange(o.data);
             if(o.type == "representation-change" && o.view == "1d-view" && o.data)
@@ -97,6 +97,8 @@ class MsaBehaviour<R,L> implements StructureViewerBehaviourInterface {
                 await this.featureClick(o.data)
             if(o.type == "selection-change" && o.view == "3d-view")
                 await this.isSelectionEmpty();
+            if(o.type == "structure-download" && o.view == "ui-view")
+                this.downloadStructures();
         });
     }
 
@@ -239,4 +241,10 @@ class MsaBehaviour<R,L> implements StructureViewerBehaviourInterface {
            return `${pdb.entryId}${TagDelimiter.entity}${pdb.entityId}`;
     }
 
+    private downloadStructures(): void{
+        this.structureViewer.exportLoadedStructures().then(()=>{
+            console.info("Download structures");
+        });
+    }
+
 }

+ 1 - 0
src/RcsbFvStructure/StructureViewerInterface.ts

@@ -76,6 +76,7 @@ export interface ViewerActionManagerInterface<R,L> {
     displayComponent(componentLabel: string): boolean;
     displayComponent(componentLabel: string, visibilityFlag: boolean): void;
     resetCamera(): void;
+    exportLoadedStructures(): Promise<void>;
 }
 
 export interface ViewerModelMapManagerInterface<R,L> {

+ 4 - 0
src/RcsbFvStructure/StructureViewers/MolstarViewer/MolstarActionManager.ts

@@ -324,6 +324,10 @@ export class MolstarActionManager<P,L> implements ViewerActionManagerInterface<L
         this.viewer.plugin.managers.camera.reset();
     }
 
+    public async exportLoadedStructures(): Promise<void> {
+        await this.viewer.exportLoadedStructures();
+    }
+
 }
 
 function getStructureWithModelId(structures: StructureRef[], modelId: string): Structure|undefined{

+ 3 - 0
src/RcsbFvStructure/StructureViewers/StructureViewer.ts

@@ -139,4 +139,7 @@ export class StructureViewer<R,L,S> implements StructureViewerInterface<R,L,S> {
         this.actionManager.resetCamera();
     }
 
+    public async exportLoadedStructures(): Promise<void>{
+        await this.actionManager.exportLoadedStructures();
+    }
 }

+ 79 - 0
src/Utils/Download.ts

@@ -0,0 +1,79 @@
+
+export function getFullDate(): string {
+    const date = new Date();
+    const year = date.getFullYear();
+    const month = date.getMonth()+1;
+    const day = date.getDate();
+    const hour = date.getHours();
+    const min = date.getMinutes();
+    const sec = date.getSeconds();
+    return `${year}-${month}-${day}-${hour}-${min}-${sec}`;
+}
+
+export function textToFile(text: string|string[]): File {
+    return new File(Array.isArray(text) ? text : [text],"file.txt");
+}
+
+export function download(data: Blob | string, downloadName = 'download') {
+    // using ideas from https://github.com/eligrey/FileSaver.js/blob/master/FileSaver.js
+
+    if (!data) return;
+
+    if ('download' in HTMLAnchorElement.prototype) {
+        const a = document.createElement('a');
+        a.download = downloadName;
+        a.rel = 'noopener';
+
+        if (typeof data === 'string') {
+            a.href = data;
+            click(a);
+        } else {
+            a.href = URL.createObjectURL(data);
+            setTimeout(() => URL.revokeObjectURL(a.href), 4E4); // 40s
+            setTimeout(() => click(a));
+        }
+    } else if (typeof navigator !== 'undefined' && (navigator as any).msSaveOrOpenBlob) {
+        // native saveAs in IE 10+
+        (navigator as any).msSaveOrOpenBlob(data, downloadName);
+    } else {
+        const ua = window.navigator.userAgent;
+        const isSafari = /Safari/i.test(ua);
+        const isChromeIos = /CriOS\/[\d]+/.test(ua);
+
+        const open = (str: string) => {
+            openUrl(isChromeIos ? str : str.replace(/^data:[^;]*;/, 'data:attachment/file;'));
+        };
+
+        if ((isSafari || isChromeIos) && FileReader) {
+            if (data instanceof Blob) {
+                // no downloading of blob urls in Safari
+                const reader = new FileReader();
+                reader.onloadend = () => open(reader.result as string);
+                reader.readAsDataURL(data);
+            } else {
+                open(data);
+            }
+        } else {
+            const url = URL.createObjectURL(typeof data === 'string' ? new Blob([data]) : data);
+            location.href = url;
+            setTimeout(() => URL.revokeObjectURL(url), 4E4); // 40s
+        }
+    }
+}
+
+function openUrl(url: string) {
+    const opened = window.open(url, '_blank');
+    if (!opened) {
+        window.location.href = url;
+    }
+}
+
+function click(node: HTMLAnchorElement) {
+    try {
+        node.dispatchEvent(new MouseEvent('click'));
+    } catch (e) {
+        const evt = document.createEvent('MouseEvents');
+        evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
+        node.dispatchEvent(evt);
+    }
+}

+ 1 - 1
src/examples/alignment-provider/providers/AlignmentReference.ts

@@ -134,7 +134,7 @@ function buildRegions(alignment:AlignmentRefType): AlignedRegion[] {
                     query_begin: begIndex,
                     target_begin: begPos,
                     query_end: n,
-                    target_end: alignment[n-1] as number
+                    target_end: begPos+(n-begIndex)
                 });
                 begIndex = 0;
                 begPos = 0;

+ 2 - 1
src/examples/alignment-provider/providers/ExternalAlignmentProvider.ts

@@ -1,5 +1,5 @@
 import {
-    AlignmentResponse,
+    AlignmentResponse, GroupReference,
     SequenceReference,
 } from "@rcsb/rcsb-api-tools/build/RcsbGraphQL/Types/Borrego/GqlTypes";
 import {
@@ -1814,6 +1814,7 @@ export const dataProvider: RcsbModuleDataProviderInterface = {
         collector: new RcsbStructuralAlignmentProvider(structuralAlignment),
         context:{
             queryId: "structural-alignment",
+            group: GroupReference.MatchingUniprotAccession,
             to: SequenceReference.PdbInstance
         }
     }