Skip to content

Commit 961ae07

Browse files
committed
Merge: Resolved conflicts and cleaned up description-tab
2 parents 5dc9873 + d55aa69 commit 961ae07

12 files changed

Lines changed: 194 additions & 49 deletions

File tree

.dockerignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
node_modules
2+
.angular
3+
.git
4+
dist
5+
.vscode
6+
npm-debug.log
7+
# Add these for TinaCMS
8+
.tina/__generated__
9+
.tina/out

Dockerfile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
FROM node:22
2+
3+
WORKDIR /app
4+
5+
# 1. Install Global Tools
6+
RUN npm install -g @angular/cli@19 tsx
7+
8+
# 2. Copy Package files
9+
COPY package*.json ./
10+
11+
# 3. Inject the Host/Poll flags directly into the package.json scripts
12+
# This prevents the "Invalid Option: --host" error
13+
RUN sed -i 's/ng serve/ng serve --host 0.0.0.0 --poll 2000/g' package.json
14+
15+
# 4. Install Dependencies
16+
RUN npm install --legacy-peer-deps --ignore-scripts
17+
18+
# 5. Copy the rest of the code
19+
COPY . .
20+
21+
# Expose the ports
22+
EXPOSE 4200
23+
EXPOSE 4001
24+
25+
# We use "npm run dev:serve" but pass the flags through the -- separator
26+
CMD npx tsx projects/website-angular/src/scripts/generate-index.ts && \
27+
npx tinacms dev --rootPath projects/website-angular -c "ng serve --host 0.0.0.0 --poll 2000 --disable-host-check"

docker-compose.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
services:
2+
app:
3+
build: .
4+
ports:
5+
- "4200:4200"
6+
- "4001:4001"
7+
volumes:
8+
- .:/app
9+
- /app/node_modules
10+
# This ensures the nested tina folder is visible to the watcher
11+
- ./projects/website-angular/tina:/app/projects/website-angular/tina
12+
environment:
13+
- NG_CLI_ANALYTICS=false
14+
- NODE_ENV=development
15+
stdin_open: true
16+
tty: true

projects/pathway-browser/src/app/details/common/external-reference/external-reference.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export class ExternalReferenceComponent {
4444
return entity ? entity.moleculeType : null;
4545
})
4646

47-
hasStructure = computed(() => this.moleculeType() === MoleculeType.PROTEIN || this.moleculeType() === MoleculeType.CHEMICAL);
47+
hasStructure = computed(() => [MoleculeType.CHEMICAL, MoleculeType.CHEMICAL_DRUG, MoleculeType.PROTEIN].includes(this.moleculeType() as MoleculeType));
4848

4949

5050
constructor(private entity: EntityService,
@@ -61,4 +61,4 @@ export class ExternalReferenceComponent {
6161
}
6262

6363
protected readonly Labels = Labels;
64-
}
64+
}

projects/pathway-browser/src/app/details/tabs/description-tab/description-tab.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {CONTENT_DETAIL, environment} from "../../../../environments/environment"
5050
import {SpeciesService} from "../../../services/species.service";
5151
import {Summation} from "../../../model/graph/summation.model";
5252
import {FigureService} from "./figure/figure.service";
53+
type HasModifiedResidue = Relationship.HasModifiedResidue;
5354
import {KeyValuePipe, NgClass, NgTemplateOutlet} from "@angular/common";
5455
import {RouterLink} from "@angular/router";
5556
import {SortByTextPipe} from "../../../pipes/sort-by-text.pipe";
@@ -77,7 +78,6 @@ import {
7778
} from "../../../../../../website-angular/src/app/content/detail/locations-tree/locations-tree.component";
7879
import {ReactionDiagramComponent} from "../../common/reaction-diagram/reaction-diagram.component";
7980

80-
type HasModifiedResidue = Relationship.HasModifiedResidue;
8181

8282

8383
@Component({

projects/pathway-browser/src/app/details/tabs/molecule-tab/structure-viewer/structure-viewer.component.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export class StructureViewerComponent {
119119
readonly moleculeType = input.required<string | null>();
120120
viewer = viewChild<ElementRef<HTMLElement>>('viewer');
121121
isProtein = computed(() => this.moleculeType() === MoleculeType.PROTEIN);
122-
isChemical = computed(() => this.moleculeType() === MoleculeType.CHEMICAL);
122+
isChemical = computed(() => this.moleculeType() === MoleculeType.CHEMICAL || MoleculeType.CHEMICAL_DRUG);
123123
chebiIdentifier = signal<string | undefined>(undefined);
124124

125125
pdbIdentifiers = computed(() => this.getPDBIdentifiers(this.xRefs()));
@@ -215,10 +215,12 @@ export class StructureViewerComponent {
215215
private structure: StructureService) {
216216
effect(() => {
217217
const [isProtein, isChemical] = [this.isProtein(), this.isChemical()]
218+
218219
if (isProtein) {
219220
this.getProteinStructure();
220221
} else if (isChemical) {
221-
this.chebiIdentifier.set(this.obj().identifier);
222+
const identifier = this.obj().databaseName === 'ChEBI' ? this.obj().identifier : (this.obj().crossReference as DatabaseIdentifier[]).find(c => c.databaseName === 'ChEBI')?.identifier;
223+
if (identifier) this.chebiIdentifier.set(identifier);
222224
}
223225
});
224226

@@ -321,4 +323,4 @@ export class StructureViewerComponent {
321323
}
322324
}
323325

324-
}
326+
}

projects/pathway-browser/src/app/diagram/diagram.component.ts

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
} from '@angular/core';
1515
import {DiagramService} from "../services/diagram.service";
1616
import {extract, ReactomeEvent, ReactomeEventTypes, Style} from "reactome-cytoscape-style";
17-
import cytoscape, {BoundingBoxWH, ElementsDefinition} from "cytoscape";
17+
import cytoscape, {BoundingBox12, BoundingBoxWH, ElementsDefinition} from "cytoscape";
1818
import {InteractorService} from "../interactors/services/interactor.service";
1919
import {
2020
catchError,
@@ -134,7 +134,7 @@ export class DiagramComponent implements AfterViewInit, OnDestroy {
134134
this.updateStyle();
135135
})
136136

137-
effect(() => {
137+
effect(async() => {
138138
const request = this.download.downloadRequest();
139139
if (request) {
140140
this.export(request.format);
@@ -144,18 +144,72 @@ export class DiagramComponent implements AfterViewInit, OnDestroy {
144144

145145
}
146146

147-
export(format: string) {
148-
const options: cytoscape.ExportOptions = {
147+
async export(format: string) {
148+
const options: cytoscape.ExportJpgBlobPromiseOptions = {
149149
full: true,
150-
...(format === DownloadFormat.JPEG ? {quality: 0.9} : {})
150+
...(format === DownloadFormat.JPEG ? {quality: 0.9} : {}),
151+
bg: 'transparent',
152+
output: 'blob-promise'
153+
}
154+
155+
const blobs = this.cys.map(cy => format === DownloadFormat.PNG ? cy.png(options) : cy.jpg(options));
156+
let blob: Blob;
157+
if (blobs.length > 1) {
158+
const images = await Promise.all(blobs.map(blob => blob.then(createImageBitmap)));
159+
const bbs = this.cys.map(cy => cy.elements().boundingBox({includeLabels: false}));
160+
const bgColors = this.cys.map(cy => getComputedStyle(cy.container()!).backgroundColor);
161+
blob = await this.mergeImages(images, bbs, bgColors, format === DownloadFormat.JPEG ? 'image/jpeg' : 'image/png', options?.quality);
162+
} else {
163+
blob = await blobs[0];
151164
}
165+
152166
const a = document.createElement('a');
153-
a.href = format === DownloadFormat.PNG ? this.cy.png(options) : this.cy.jpg(options);
167+
a.href = URL.createObjectURL(blob);
154168
a.download = `${this.pathwayId()}.${format}`;
155169
a.click();
156170
a.remove();
157171
}
158172

173+
async mergeImages(images: ImageBitmap[], bbs: BoundingBox12[], bgColors: string[], format: 'image/jpeg' | 'image/png', quality?: number): Promise<Blob> {
174+
// Compute merged canvas size in model space
175+
const xMin = Math.min(...bbs.map(bb => bb.x1));
176+
const yMin = Math.min(...bbs.map(bb => bb.y1));
177+
const xMax = Math.max(...bbs.map(bb => bb.x2));
178+
const yMax = Math.max(...bbs.map(bb => bb.y2));
179+
180+
// Calculate scale ratio between pixel space (image) and model space (bounding box)
181+
// Assume all images have the same scale - use the first one
182+
const bbWidth = bbs[0].x2 - bbs[0].x1;
183+
const scale = images[0].width / bbWidth;
184+
185+
const mergedWidth = (xMax - xMin) * scale;
186+
const mergedHeight = (yMax - yMin) * scale;
187+
188+
const canvas = document.createElement('canvas');
189+
canvas.width = mergedWidth;
190+
canvas.height = mergedHeight;
191+
const ctx = canvas.getContext('2d')!;
192+
if (format === 'image/jpeg') {
193+
ctx.fillStyle = this.dark.isDark() ? '#000' : '#fff';
194+
ctx.fillRect(0, 0, mergedWidth, mergedHeight);
195+
}
196+
197+
images.forEach((image, i) => {
198+
const offsetX = (bbs[i].x1 - xMin) * scale; // shift relative to merged bbox, scaled to pixel space
199+
const offsetY = (bbs[i].y1 - yMin) * scale;
200+
201+
// Fill background color for this layer
202+
ctx.fillStyle = bgColors[i];
203+
ctx.fillRect(0, 0, mergedWidth, mergedHeight);
204+
205+
// Draw image on top
206+
ctx.drawImage(image, offsetX, offsetY);
207+
image.close();
208+
});
209+
210+
return new Promise(resolve => canvas.toBlob(blob => resolve(blob!), format, quality));
211+
}
212+
159213
zoomToCytoscapeTransform = (x: number) => this.minZoom() * Math.pow(this.maxZoom() / this.minZoom(), (x - this.controlMinZoom()) / this.controlRange());
160214
zoomToControlTransform = (zoomCy: number) => this.controlMinZoom() + this.controlRange() * (Math.log(zoomCy / this.minZoom()) / Math.log(this.maxZoom() / this.minZoom()));
161215
thumbnailImg = signal<string>('');

projects/pathway-browser/src/app/model/graph.model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ export namespace Graph {
2727
referenceType: string;
2828
leaves?: Node[];
2929
standardIdentifier?: string;
30+
chebiIdentifier?: string;
3031
}
3132

3233
export interface SubPathway extends Entity {
3334
events: number[]
3435
}
3536

3637
}
37-

projects/pathway-browser/src/app/services/diagram.service.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,8 @@ export class DiagramService {
241241
...mergedResponse,
242242
chebiMapping: this.getCHEBIStructure(
243243
new Set(mergedResponse.graph.nodes
244-
.filter(node => node.standardIdentifier?.startsWith('chebi:'))
245-
.map(node => node.identifier)
244+
.filter(node => node.standardIdentifier?.startsWith('chebi:') || node.chebiIdentifier !== undefined)
245+
.map(node => (node.chebiIdentifier || node.standardIdentifier)!.split(':')[1])
246246
)
247247
)
248248
})),
@@ -458,7 +458,8 @@ export class DiagramService {
458458
const graphData = idToGraphNodes.get(item.id);
459459
if (!graphData) console.error("Missing graph data for node: ", item.id, ". Potential reason could be a wrong normal pathway for a disease")
460460
let preferredId = unitId || graphData?.identifier;
461-
let chebiStructure = preferredId ? chebiMapping.get(preferredId) : undefined
461+
console.log(chebiMapping, graphData?.chebiIdentifier, graphData?.identifier, preferredId)
462+
let chebiStructure = chebiMapping.get(graphData!.chebiIdentifier?.substring(6) || '') || chebiMapping.get(graphData!.identifier!);
462463
if (classes.some(clazz => clazz === 'Protein')) {
463464
html = this.getStructureVideoHtml({...item, type: 'Protein'}, width, height, preferredId);
464465
}

projects/reactome-cytoscape-style/src/lib/drawer/image-builder.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import chroma from "chroma-js";
2222

2323
export const imageBuilder = (properties: Properties, style: Style) => memoize(
2424
(node: cytoscape.NodeSingular): Aggregated<Image> => {
25-
// console.time(`build-image-${node.id()}`)
2625
let layers: Image[] = [];
2726
const clazz = node.classes().find(clazz => classToDrawers.has(clazz as Node)) as Node
2827
if (!clazz) return aggregate(layers, defaultBg);
@@ -146,7 +145,7 @@ function _expToGradient(id: string, exps: (number | [number, number] | undefined
146145
// console.time('exp-to-gradient')
147146
const stops: { start: number, stop: number, color: string, exp: number | undefined, width: number }[] = [];
148147
const size = exps.reduce((l: number, e) => e !== undefined && isArray(e) ? l + e[1] : l + 1, 0);
149-
const delta = Array.isArray(size) ? (1 / size[0] + 1/size[1])/ 2 : 1 / size;
148+
const delta = 1 / size;
150149
exps.forEach((exp, i) => {
151150
const p = stops.length - 1;
152151
const realExp = isArray(exp) ? exp[0]! : exp;
@@ -240,7 +239,7 @@ function aggregate<T extends Object, K extends keyof T>(toAggregate: T[], defaul
240239
const aggregate: Aggregated<T> = {} as Aggregated<T>;
241240
//@ts-ignore
242241
const keys = new Set<K>(Object.keys(defaultValue));
243-
keys.forEach(key => aggregate[key] = toAggregate.map(t => t[key] || defaultValue[key]));
242+
keys.forEach(key => aggregate[key] = toAggregate.map(t => t[key] ?? defaultValue[key]));
244243
return aggregate;
245244
}
246245

@@ -253,10 +252,10 @@ const RX = (properties: Properties, {height}: DrawerParameters, clazz: Node): Im
253252

254253
return {
255254
"background-image": `
256-
<path style="transform: scale(2)" fill="${color}" stroke-width="0.4" stroke="${color}" d="M3.2 4C3.3 4 3.4 4 3.6 4L6.75 8.81L5.7 10.15C5.7 10.15 5.53985 10.3884 5.31824 10.6092C5.00434 10.922 4.6582 11.3 4.28711 11.3C4.19141 11.3 4.2 11.3 4.1 11.3V11.5H6.4V11.3C6.2 11.3 6 11.3 5.9 11.2C5.8 11.1 5.8 11 5.8 10.9C5.8 10.6301 5.9 10.5547 6.16055 10.226L7 9.2L7.65291 10.226C7.82889 10.5025 8 10.7344 8 10.9C8 11.0656 7.90095 11.3 7.65291 11.3C7.55291 11.3 7.6 11.3 7.4 11.3V11.5H10.2V11.3C9.9 11.3 9.7 11.2 9.5 11C9.24121 10.7412 9 10.5 8.6 10L7.6 8.5L8.48711 7.35309C8.55228 7.28792 8.61656 7.21558 8.68081 7.13924C9.09787 6.6437 9.64859 6 10.2 6.01309V5.81309H7.8V6.01309C8 6.01309 8.2 6.01309 8.3 6.01309C8.45586 6.01309 8.6 6.20329 8.6 6.31309C8.6 6.62136 8.43963 6.81922 8.2462 7.03337L7.3 8.1L4.5 3.9C5.1 3.8 5.4 3.61 5.7 3.31C6 3.01 6.2 2.6 6.2 2.2C6.2 1.8 6.08711 1.47 5.78711 1.17C5.52798 0.910875 5.3 0.8 5 0.7C4.6 0.6 4.1 0.5 3.4 0.5H1V0.7H1.2C1.82201 0.7 2 1.14292 2 1.7V6C2 6.59634 2 6.9 1.2 6.9H1V7.1H3.8V6.9H3.6C2.9041 6.9 2.9 6.61047 2.9 6V4H3H3.2ZM3 3.7C3 3.7 3 3.7 2.9 3.7L2.88711 1C3.18711 0.9 3.4 0.9 3.6 0.9C4.47782 0.9 5 1.42405 5 2.3C5 3.40743 4.15401 3.7 3.2 3.7H3Z"/>
255+
<path id="RX" style="transform: scale(2)" fill="${color}" stroke-width="0.4" stroke="${color}" d="M3.2 4C3.3 4 3.4 4 3.6 4L6.75 8.81L5.7 10.15C5.7 10.15 5.53985 10.3884 5.31824 10.6092C5.00434 10.922 4.6582 11.3 4.28711 11.3C4.19141 11.3 4.2 11.3 4.1 11.3V11.5H6.4V11.3C6.2 11.3 6 11.3 5.9 11.2C5.8 11.1 5.8 11 5.8 10.9C5.8 10.6301 5.9 10.5547 6.16055 10.226L7 9.2L7.65291 10.226C7.82889 10.5025 8 10.7344 8 10.9C8 11.0656 7.90095 11.3 7.65291 11.3C7.55291 11.3 7.6 11.3 7.4 11.3V11.5H10.2V11.3C9.9 11.3 9.7 11.2 9.5 11C9.24121 10.7412 9 10.5 8.6 10L7.6 8.5L8.48711 7.35309C8.55228 7.28792 8.61656 7.21558 8.68081 7.13924C9.09787 6.6437 9.64859 6 10.2 6.01309V5.81309H7.8V6.01309C8 6.01309 8.2 6.01309 8.3 6.01309C8.45586 6.01309 8.6 6.20329 8.6 6.31309C8.6 6.62136 8.43963 6.81922 8.2462 7.03337L7.3 8.1L4.5 3.9C5.1 3.8 5.4 3.61 5.7 3.31C6 3.01 6.2 2.6 6.2 2.2C6.2 1.8 6.08711 1.47 5.78711 1.17C5.52798 0.910875 5.3 0.8 5 0.7C4.6 0.6 4.1 0.5 3.4 0.5H1V0.7H1.2C1.82201 0.7 2 1.14292 2 1.7V6C2 6.59634 2 6.9 1.2 6.9H1V7.1H3.8V6.9H3.6C2.9041 6.9 2.9 6.61047 2.9 6V4H3H3.2ZM3 3.7C3 3.7 3 3.7 2.9 3.7L2.88711 1C3.18711 0.9 3.4 0.9 3.6 0.9C4.47782 0.9 5 1.42405 5 2.3C5 3.40743 4.15401 3.7 3.2 3.7H3Z"/>
257256
`,
258257
"background-position-x": x,
259-
"background-position-y": (height / 2 - 11) + 'px',
258+
"background-position-y": (height / 2 - 11),
260259
"background-width": 22,
261260
"background-height": 24,
262261
};
@@ -276,7 +275,7 @@ const Pathway = (properties: Properties, {height, disease}: DrawerParameters): I
276275
<path style="transform: scale(1.5)" fill="${color}" stroke-width="0.4" stroke="${color}" d="M19.6864 21.0381C19.0364 21.0381 18.4531 20.8508 17.9364 20.4761C17.4197 20.1008 17.0614 19.6214 16.8614 19.0381H11.6864C10.5864 19.0381 9.64473 18.6464 8.8614 17.8631C8.07807 17.0798 7.6864 16.1381 7.6864 15.0381C7.6864 13.9381 8.07807 12.9964 8.8614 12.2131C9.64473 11.4298 10.5864 11.0381 11.6864 11.0381H13.6864C14.2364 11.0381 14.7074 10.8421 15.0994 10.4501C15.4907 10.0588 15.6864 9.58809 15.6864 9.03809C15.6864 8.48809 15.4907 8.01709 15.0994 7.62509C14.7074 7.23375 14.2364 7.03809 13.6864 7.03809H8.5114C8.29473 7.62142 7.9324 8.10075 7.4244 8.47609C6.91573 8.85075 6.3364 9.03809 5.6864 9.03809C4.85307 9.03809 4.14473 8.74642 3.5614 8.16309C2.97807 7.57975 2.6864 6.87142 2.6864 6.03809C2.6864 5.20475 2.97807 4.49642 3.5614 3.91309C4.14473 3.32975 4.85307 3.03809 5.6864 3.03809C6.3364 3.03809 6.91573 3.22542 7.4244 3.60009C7.9324 3.97542 8.29473 4.45475 8.5114 5.03809H13.6864C14.7864 5.03809 15.7281 5.42975 16.5114 6.21309C17.2947 6.99642 17.6864 7.93809 17.6864 9.03809C17.6864 10.1381 17.2947 11.0798 16.5114 11.8631C15.7281 12.6464 14.7864 13.0381 13.6864 13.0381H11.6864C11.1364 13.0381 10.6657 13.2338 10.2744 13.6251C9.8824 14.0171 9.6864 14.4881 9.6864 15.0381C9.6864 15.5881 9.8824 16.0591 10.2744 16.4511C10.6657 16.8424 11.1364 17.0381 11.6864 17.0381H16.8614C17.0781 16.4548 17.4407 15.9754 17.9494 15.6001C18.4574 15.2254 19.0364 15.0381 19.6864 15.0381C20.5197 15.0381 21.2281 15.3298 21.8114 15.9131C22.3947 16.4964 22.6864 17.2048 22.6864 18.0381C22.6864 18.8714 22.3947 19.5798 21.8114 20.1631C21.2281 20.7464 20.5197 21.0381 19.6864 21.0381ZM5.6864 7.03809C5.96973 7.03809 6.2074 6.94242 6.3994 6.75109C6.59073 6.55909 6.6864 6.32142 6.6864 6.03809C6.6864 5.75475 6.59073 5.51709 6.3994 5.32509C6.2074 5.13375 5.96973 5.03809 5.6864 5.03809C5.40307 5.03809 5.1654 5.13375 4.9734 5.32509C4.78207 5.51709 4.6864 5.75475 4.6864 6.03809C4.6864 6.32142 4.78207 6.55909 4.9734 6.75109C5.1654 6.94242 5.40307 7.03809 5.6864 7.03809Z" />
277276
`,
278277
"background-position-x": x,
279-
"background-position-y": (height / 2 - 18) + 'px',
278+
"background-position-y": (height / 2 - 18),
280279
"background-width": 36,
281280
"background-height": 36,
282281
};
@@ -296,4 +295,3 @@ export const OMMITED_ICON = memoize((properties: Properties) => {
296295
const s = extract(properties.global.onSurface);
297296
return svgStr(`<line x1="2.5" y1="3" x2="4.5" y2="7" stroke-width="1.5" stroke-linecap="round" stroke="${s}"/><line x1="5.5" y1="3" x2="7.5" y2="7" stroke-width="1.5" stroke-linecap="round" stroke="${s}"/>`, 10, 10)
298297
}, (p) => '')
299-

0 commit comments

Comments
 (0)