Skip to content

Commit

Permalink
unmodeled track builder
Browse files Browse the repository at this point in the history
  • Loading branch information
bioinsilico committed May 16, 2024
1 parent f7ac825 commit f669790
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

[Semantic Versioning](https://semver.org/)

## [6.1.0] - 2024-05-16
### New feature
- New track builder `UnmodeledTrackBuilder` that merges unmodeled tracks from instances

## [6.0.14] - 2024-04-02
### Dependency update
- rcsb-charts v0.2.23
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rcsb/rcsb-saguaro-app",
"version": "6.0.14",
"version": "6.1.0",
"description": "RCSB 1D Saguaro Web App",
"main": "build/app.js",
"files": [
Expand Down
9 changes: 9 additions & 0 deletions src/RcsbAnnotationConfig/RcsbAnnotationConfig.ac.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
],
"instance_order": [
"MA_QA_METRIC_LOCAL_TYPE_PLDDT",
"UNMODELED",
"SECONDARY_STRUCTURE",
"UNOBSERVED",
"ZERO_OCCUPANCY_RESIDUE_XYZ",
Expand Down Expand Up @@ -122,6 +123,14 @@
"DISORDER_BINDING"
],
"config": [{
"type": "UNMODELED",
"display": "block-area",
"color": {
"colors": ["#BBB", "#999"],
"thresholds": [0.999]
},
"title": "UNMODELED"
},{
"type": "TRANSIT_PEPTIDE",
"color": "#189f3e",
"display": "block",
Expand Down
10 changes: 7 additions & 3 deletions src/RcsbFvExamples/EntitySummaryFv.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import {buildEntitySummaryFv} from "../RcsbFvWeb/RcsbFvBuilder";
import {getJsonFromUrl, onLoad} from "./utils/events";

buildEntitySummaryFv("pfv", "select", "AF_AFQ8WZ42F1_1").then((module)=>{
console.log(module)
});
onLoad(()=>{
const args: {entityId:string} = getJsonFromUrl().entityId ? getJsonFromUrl() : {entityId:"3HBX_1"};
buildEntitySummaryFv("pfv", "select", args.entityId).then((module)=>{
console.log(module)
});
});
2 changes: 1 addition & 1 deletion src/RcsbFvExamples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<div id="select" ></div>
<div id="selectBis" ></div>
<div id="carousel" ></div>
<div id="pfv" ></div>
<div id="pfv" style="margin-top: 50px"></div>
<div id="pfv_2" ></div>
<div id="pfv_3" ></div>

Expand Down
17 changes: 17 additions & 0 deletions src/RcsbFvExamples/utils/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

export function getJsonFromUrl() {
const url = location.search;
var query = url.substring(1);
var result: any = {};
query.split("&").forEach(function(part) {
var item = part.split("=");
result[item[0]] = decodeURIComponent(item[1]);
});
return result;
}

export function onLoad(f:()=>void){
document.addEventListener("DOMContentLoaded", function(event) {
f();
});
}
19 changes: 18 additions & 1 deletion src/RcsbFvWeb/RcsbFvModule/RcsbFvEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ import {CollectAlignmentInterface} from "../../RcsbCollectTools/AlignmentCollect
import {Assertions} from "../../RcsbUtils/Helpers/Assertions";
import assertDefined = Assertions.assertDefined;
import {TagDelimiter} from "@rcsb/rcsb-api-tools/build/RcsbUtils/TagDelimiter";
import {RcsbFvBoardConfigInterface} from "@rcsb/rcsb-saguaro/lib/RcsbFv/RcsbFvConfig/RcsbFvConfigInterface";
import {addUnmodeledTrackBuilder, UnmodeledTrackBuilder} from "../../RcsbUtils/TrackGenerators/UnmodeledTrackBuilder";

export class RcsbFvEntity extends RcsbFvAbstractModule {

private readonly unmodeledTrackBuilder = new UnmodeledTrackBuilder();

protected async protectedBuild(): Promise<void> {
const buildConfig: RcsbFvModuleBuildInterface = this.buildConfig;
assertDefined(buildConfig.entityId);
Expand All @@ -35,7 +39,13 @@ export class RcsbFvEntity extends RcsbFvAbstractModule {
titleSuffix: this.titleSuffix.bind(this),
filters: buildConfig.additionalConfig?.filters,
annotationProcessing:buildConfig.additionalConfig?.annotationProcessing,
externalTrackBuilder: buildConfig.additionalConfig?.externalTrackBuilder
rcsbContext:{
entityId: buildConfig.entityId
},
externalTrackBuilder: addUnmodeledTrackBuilder(
this.unmodeledTrackBuilder,
buildConfig.additionalConfig?.externalTrackBuilder
)
};
const annotationsFeatures: AnnotationFeatures[] = await this.annotationCollector.collect(annotationsRequestContext);
await this.buildAnnotationsTrack(annotationsRequestContext,annotationsFeatures);
Expand All @@ -45,6 +55,13 @@ export class RcsbFvEntity extends RcsbFvAbstractModule {
return void 0;
}

protected async getBoardConfig(): Promise<RcsbFvBoardConfigInterface> {
return {
... this.boardConfigData,
tooltipGenerator: this.unmodeledTrackBuilder.getTooltip()
};
}

protected concatAlignmentAndAnnotationTracks(): void {
const buildConfig: RcsbFvModuleBuildInterface = this.buildConfig;
this.rowConfigData = !buildConfig.additionalConfig?.hideAlignments ?
Expand Down
19 changes: 18 additions & 1 deletion src/RcsbFvWeb/RcsbFvModule/RcsbFvUniprotEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ import {CollectAlignmentInterface} from "../../RcsbCollectTools/AlignmentCollect
import {Assertions} from "../../RcsbUtils/Helpers/Assertions";
import assertDefined = Assertions.assertDefined;
import {TagDelimiter} from "@rcsb/rcsb-api-tools/build/RcsbUtils/TagDelimiter";
import {addUnmodeledTrackBuilder, UnmodeledTrackBuilder} from "../../RcsbUtils/TrackGenerators/UnmodeledTrackBuilder";
import {RcsbFvBoardConfigInterface} from "@rcsb/rcsb-saguaro/lib/RcsbFv/RcsbFvConfig/RcsbFvConfigInterface";

export class RcsbFvUniprotEntity extends RcsbFvAbstractModule {

private readonly unmodeledTrackBuilder = new UnmodeledTrackBuilder();

protected async protectedBuild(): Promise<void> {
const buildConfig: RcsbFvModuleBuildInterface = this.buildConfig;
const upAcc: string | undefined = buildConfig.upAcc;
Expand Down Expand Up @@ -48,7 +52,13 @@ export class RcsbFvUniprotEntity extends RcsbFvAbstractModule {
filters:additionalConfig?.filters instanceof Array ? additionalConfig.filters.concat(filters) : filters,
titleSuffix: this.titleSuffix.bind(this),
annotationProcessing:buildConfig.additionalConfig?.annotationProcessing,
externalTrackBuilder: buildConfig.additionalConfig?.externalTrackBuilder
rcsbContext:{
entityId: buildConfig.entityId
},
externalTrackBuilder: addUnmodeledTrackBuilder(
this.unmodeledTrackBuilder,
buildConfig.additionalConfig?.externalTrackBuilder
)
};
const annotationsFeatures: AnnotationFeatures[] = await this.annotationCollector.collect(annotationsRequestContext);
await this.buildAnnotationsTrack(annotationsRequestContext,annotationsFeatures);
Expand All @@ -59,6 +69,13 @@ export class RcsbFvUniprotEntity extends RcsbFvAbstractModule {

}

protected async getBoardConfig(): Promise<RcsbFvBoardConfigInterface> {
return {
... this.boardConfigData,
tooltipGenerator: this.unmodeledTrackBuilder.getTooltip()
};
}

protected concatAlignmentAndAnnotationTracks(): void {
this.rowConfigData = [this.referenceTrack].concat(this.alignmentTracks).concat(this.annotationTracks);
}
Expand Down
141 changes: 141 additions & 0 deletions src/RcsbUtils/TrackGenerators/UnmodeledTrackBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import {
AnnotationFeatures,
Feature,
FeaturePosition,
Source,
Type
} from "@rcsb/rcsb-api-tools/build/RcsbGraphQL/Types/Borrego/GqlTypes";
import {PolymerEntityInstanceInterface} from "../../RcsbCollectTools/DataCollectors/PolymerEntityInstancesCollector";
import {range} from "lodash";
import {rcsbRequestCtxManager} from "../../RcsbRequest/RcsbRequestContextManager";
import {RcsbFvTooltipInterface} from "@rcsb/rcsb-saguaro/lib/RcsbFv/RcsbFvTooltip/RcsbFvTooltipInterface";
import {RcsbFvTooltip} from "../../RcsbFvWeb/RcsbFvTooltip/RcsbFvTooltip";
import {
RcsbFvTrackDataAnnotationInterface
} from "../../RcsbFvWeb/RcsbFvFactories/RcsbFvTrackFactory/RcsbFvTrackDataAnnotationInterface";
import {ExternalTrackBuilderInterface} from "../../RcsbCollectTools/FeatureTools/ExternalTrackBuilderInterface";

export function addUnmodeledTrackBuilder(
unmodeledTrackBuilder: UnmodeledTrackBuilder,
externalTrackBuilder?: ExternalTrackBuilderInterface
): ExternalTrackBuilderInterface {
const trackBuilder = async (data: {
annotations: Array<AnnotationFeatures>;
rcsbContext?: Partial<PolymerEntityInstanceInterface>
}): Promise<Array<AnnotationFeatures>> => {
return [
... await externalTrackBuilder?.filterFeatures?.(data) ?? [],
... await unmodeledTrackBuilder.filterFeatures(data)
];
}
return {
... externalTrackBuilder,
filterFeatures: async (data) => {
externalTrackBuilder?.filterFeatures?.(data);
return trackBuilder({
annotations: await externalTrackBuilder?.filterFeatures?.(data) ?? data.annotations,
rcsbContext: data.rcsbContext
});
}
}
}

export class UnmodeledTrackBuilder {

private readonly unmodeledDescription: Map<string,[number,number]> = new Map();
public async filterFeatures(data: {
annotations: Array<AnnotationFeatures>;
rcsbContext?: Partial<PolymerEntityInstanceInterface>;
}): Promise<Array<AnnotationFeatures>> {
if (!data.rcsbContext)
return data.annotations;
return [
...await this.buildUnobserved(data.annotations, data.rcsbContext),
...filterUnobserved(data.annotations)
];
}

public getTooltip(): RcsbFvTooltipInterface{
return new UnobservedToolTip(this.unmodeledDescription);
}

private async buildUnobserved(annotations: Array<AnnotationFeatures>, rcsbContext: Partial<PolymerEntityInstanceInterface>): Promise<Array<AnnotationFeatures>> {
const nInstances = (await rcsbRequestCtxManager.getEntityProperties(rcsbContext.entityId ?? "none"))[0].instances.length;
const featurePositions = Array.from(annotations.filter(
ann => ann.features?.filter(
f => f?.type == Type.UnobservedResidueXyz
)?.length ?? 0 > 0
).map(
ann => ann.features?.filter(
(f): f is Feature => f?.type == Type.UnobservedResidueXyz
) ?? []
).flat().map(
feature => feature.feature_positions?.filter(
(p): p is FeaturePosition => typeof p != "undefined"
) ?? []
).flat().map(
p => p.beg_seq_id && p.end_seq_id ? range(p.beg_seq_id,p.end_seq_id+1) : []
).flat().reduce(
(map,idx) => map.set(idx, (map.get(idx) ?? 0) + 1),
new Map<number,number>
).entries()).map(([idx,unobserved])=>{
this.unmodeledDescription.set(`${rcsbContext.entityId}:${idx}`, [unobserved, nInstances]);
return {
beg_seq_id: idx,
values: [Math.round(unobserved / nInstances * 100)/100]
}
});
return [{
source: Source.PdbEntity,
target_id: rcsbContext.entityId,
features: [{
type: UNMODELED as Type,
feature_positions: featurePositions
}]
}]
}

}

class UnobservedToolTip implements RcsbFvTooltipInterface {

private readonly regularTooltip: RcsbFvTooltip = new RcsbFvTooltip();
private readonly trackDescription: Map<string,[number,number]>;

constructor(trackDescription: Map<string,[number,number]>) {
this.trackDescription = trackDescription;
}

showTooltip(d: RcsbFvTrackDataAnnotationInterface): HTMLElement | undefined {
return this.regularTooltip.showTooltip(d);
}

showTooltipDescription(d: RcsbFvTrackDataAnnotationInterface): HTMLElement | undefined {
if(d.title == UNMODELED as Type)
return this.overloadTooltipDescription(d);
return this.regularTooltip.showTooltipDescription(d);
}

overloadTooltipDescription(d: RcsbFvTrackDataAnnotationInterface): HTMLElement | undefined {
if(!this.trackDescription.has(`${d.sourceId}:${d.begin}`))
return undefined;
const tooltipDescriptionDiv = document.createElement<"div">("div");
const [unobserved, total] = this.trackDescription.get(`${d.sourceId}:${d.begin}`) ?? [0,0];
tooltipDescriptionDiv.append(`Unmodeled in ${unobserved} of ${total} chains`);
return tooltipDescriptionDiv;
}

}

function filterUnobserved(annotations: Array<AnnotationFeatures>): Array<AnnotationFeatures> {
return annotations.map(ann=>{
return {
...ann,
features: ann.features?.filter(
f=> f?.type != Type.UnobservedResidueXyz && f?.type != Type.UnobservedAtomXyz
)
}
});
}

const UNMODELED = "UNMODELED";
3 changes: 2 additions & 1 deletion webpack.server.dev.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ const server = {
},
devServer: {
compress: true,
port: 9000,
allowedHosts: "all",
port: 9000
},
plugins: Object.keys(entries).map(key=>new HtmlWebpackPlugin({
filename:`${key}.html`,
Expand Down

0 comments on commit f669790

Please sign in to comment.