Skip to content

Commit

Permalink
Merge pull request #83 from i-VRESSE/contactmap2
Browse files Browse the repository at this point in the history
Render contact map plots by parsing their plotly data from html files.
  • Loading branch information
sverhoeven authored Mar 7, 2024
2 parents ced087b + 2e9a13c commit 80db144
Show file tree
Hide file tree
Showing 29 changed files with 654 additions and 429 deletions.
3 changes: 3 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ module.exports = {
"plugin:import/recommended",
"plugin:import/typescript",
],
rules: {
"max-params": ["error", 5],
},
},

// Node
Expand Down
6 changes: 3 additions & 3 deletions app/browse/OutputReport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,17 @@ export const OutputReport = ({
)}
{module.name === "contactmap" && (
<Link
title="Recluster"
title="Contact map report"
to={`/jobs/${jobid}/analysis/contactmap/${module.id}`}
className="dark:invert"
>
&#128202;
&#128208;
</Link>
)}
{module.report && (
<>
<a
title="Analysis report"
title="Caprieval analysis report"
target="_blank"
rel="noreferrer"
href={`/jobs/${jobid}/files/${module.report.path}`}
Expand Down
53 changes: 1 addition & 52 deletions app/caprieval/caprieval.server.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, test, expect } from "vitest";

import { getLastCaprievalModule, getPlotFromHtml } from "./caprieval.server";
import { getLastCaprievalModule } from "./caprieval.server";
import { outputFileWithoutInteractiveVersions } from "../models/test.fixtures";

describe("getLastCaprievalModule", () => {
Expand All @@ -11,54 +11,3 @@ describe("getLastCaprievalModule", () => {
expect(result).toEqual(expected);
});
});
describe("getPlotFromHtml", () => {
test("should return the plotly data and layout", () => {
const expected = {
data: [
{
x: [1999, 2000, 2001, 2002],
y: [10, 15, 13, 17],
type: "scatter",
},
],
layout: {
title: "Sales Growth",
xaxis: {
title: "Year",
showgrid: false,
zeroline: false,
},
yaxis: {
title: "Percent",
showline: false,
},
},
};
const input = `
<div>
<script type="text/javascript">window.PlotlyConfig = { MathJaxConfig: 'local' };</script>
<script src="https://cdn.plot.ly/plotly-2.16.1.min.js"></script>
<div id="6ce37419-297c-401e-860d-61704c0795b8" class="plotly-graph-div" style="height:800px; width:1000px;">
</div>
<script id="data1" type="application/json">
${JSON.stringify(expected)}
</script>
<script type="text/javascript">
const { data, layout } = JSON.parse(document.getElementById("data1").text)
window.PLOTLYENV = window.PLOTLYENV || {};
if (document.getElementById("6ce37419-297c-401e-860d-61704c0795b8")) {
Plotly.newPlot(
"6ce37419-297c-401e-860d-61704c0795b8",
data,
layout,
{ responsive: true },
);
}
</script>
</div>
`;

const p = getPlotFromHtml(input);
expect(p).toEqual(expected);
});
});
86 changes: 11 additions & 75 deletions app/caprieval/caprieval.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
listOutputFiles,
getModuleIndexPadding,
buildAnalyisPath,
fetchHtml,
} from "~/models/job.server";
import type { PlotlyProps } from "~/components/PlotlyPlot";
import type { DirectoryItem } from "~/bartender-client/types";
Expand All @@ -19,6 +20,7 @@ import type {
StructureTable,
} from "@i-vresse/haddock3-analysis-components";
import { BartenderError } from "~/models/errors";
import { getDataFromHtml, getPlotFromHtml } from "~/lib/html";

// Package does not expose types, so extract them from the components
export type Table =
Expand Down Expand Up @@ -80,27 +82,6 @@ export async function getCaprievalModuleInfo(
return [moduleIndex, interactivness, pad];
}

export async function getReportHtml(
jobid: number,
module: number,
bartenderToken: string,
moduleIndexPadding: number,
isInteractive = false,
moduleName = "caprieval"
) {
const shtml = await fetchHtml(
jobid,
module,
isInteractive,
bartenderToken,
moduleIndexPadding,
moduleName,
`report.html`
);
const table = getTableFromHtml(shtml);
return table;
}

export interface CaprievalData {
scatters: PlotlyProps;
boxes: PlotlyProps;
Expand Down Expand Up @@ -145,30 +126,30 @@ export async function getCaprievalData({
moduleName?: string;
structurePrefix?: string;
}): Promise<CaprievalData> {
const shtml = await fetchHtml(
const shtml = await fetchHtml({
jobid,
module,
isInteractive,
bartenderToken,
moduleIndexPadding,
moduleName,
`${scatterSelection}.html`
);
htmlFilename: `${scatterSelection}.html`,
});
const scatters = getPlotFromHtml(shtml, 1);
let bhtml: string;
if (scatterSelection === "report" && boxSelection === "report") {
// if both are report, we can reuse the html
bhtml = shtml;
} else {
bhtml = await fetchHtml(
bhtml = await fetchHtml({
jobid,
module,
isInteractive,
bartenderToken,
moduleIndexPadding,
moduleName,
`${boxSelection}.html`
);
htmlFilename: `${boxSelection}.html`,
});
}
// plot id is always 1 for non-report plot as html file contains just one plot
const bplotId = boxSelection === "report" ? 2 : 1;
Expand All @@ -180,70 +161,25 @@ export async function getCaprievalData({
} else if (boxSelection === "report") {
thtml = bhtml;
} else {
thtml = await fetchHtml(
thtml = await fetchHtml({
jobid,
module,
isInteractive,
bartenderToken,
moduleIndexPadding,
moduleName,
`report.html`
);
htmlFilename: `report.html`,
});
}
const table = prefixTable(getTableFromHtml(thtml), structurePrefix);

return { scatters, boxes, table };
}

async function fetchHtml(
jobid: number,
module: number,
isInteractive: boolean,
bartenderToken: string,
moduleIndexPadding: number,
moduleName = "caprieval",
htmlFilename = "report.html"
) {
const prefix = buildAnalyisPath({
moduleIndex: module,
moduleName,
isInteractive,
moduleIndexPadding,
});
const response = await getJobfile(
jobid,
`${prefix}${htmlFilename}`,
bartenderToken
);
if (!response.ok) {
throw new Error(`could not get ${htmlFilename}`);
}
return await response.text();
}

export function getPlotFromHtml(html: string, plotId = 1) {
return getDataFromHtml<PlotlyProps>(html, `data${plotId}`);
}

export function getTableFromHtml(html: string, tableId = 2) {
return getDataFromHtml<Table>(html, `datatable${tableId}`);
}

export function getDataFromHtml<T>(html: string, id: string) {
// this is very fragile, but much faster then using a HTML parser
// as order of attributes is not guaranteed
// see commit meessage of this line for benchmark
const re = new RegExp(
`<script id="${id}" type="application\\/json">([\\s\\S]*?)<\\/script>`
);
const a = html.match(re);
if (!a) {
throw new Error(`could not find script with id ${id}`);
}
const dataAsString = a[1].trim();
return JSON.parse(dataAsString) as T;
}

export function getLastCaprievalModule(files: DirectoryItem): [number, number] {
if (!files.children) {
throw new Error("No modules found");
Expand Down
22 changes: 11 additions & 11 deletions app/catalogs/haddock3.easy.json
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@
{
"id": "alascan",
"category": "analysis",
"label": "HADDOCK3 module for alanine scan.",
"label": "",
"description": "HADDOCK3 module for alanine scan.",
"schema": {
"type": "object",
Expand Down Expand Up @@ -290,7 +290,7 @@
{
"id": "caprieval",
"category": "analysis",
"label": "Calculate CAPRI metrics.",
"label": "",
"description": "HADDOCK3 module to calculate the CAPRI metrics.",
"schema": {
"type": "object",
Expand Down Expand Up @@ -479,7 +479,7 @@
{
"id": "clustfcc",
"category": "analysis",
"label": "Cluster modules with FCC.",
"label": "",
"description": "HADDOCK3 module for clustering with FCC.",
"schema": {
"type": "object",
Expand Down Expand Up @@ -662,18 +662,18 @@
"type": "boolean"
},
"cluster_heatmap_datatype": {
"default": "shortest-cont-ratio",
"default": "shortest-cont-probability",
"title": "Type of data to be used in the heatmap.",
"description": "Type of data to be used in the heatmap.",
"$comment": "ca-ca-dist and shortest-dist are averages distances observed in cluster. While ca-ca-cont-ratio and shortest-cont-ratio are ratio of the distances to be observed under their respective thresholds.",
"$comment": "ca-ca-dist & shortest-dist are averages distances observed in cluster. ca-ca-cont-probability & shortest-cont-probability are probability of the distances to be observed under their respective thresholds.",
"type": "string",
"minLength": 5,
"maxLength": 30,
"enum": [
"ca-ca-dist",
"ca-ca-cont-ratio",
"ca-ca-cont-probability",
"shortest-dist",
"shortest-cont-ratio"
"shortest-cont-probability"
]
},
"chordchart_datatype": {
Expand Down Expand Up @@ -867,7 +867,7 @@
{
"id": "emscoring",
"category": "scoring",
"label": "EM scoring module.",
"label": "EM scoring module.\n\nThis module performs energy minimization and scoring of the models generated\nin the previous step of the workflow. No restraints are applied during this step.\n",
"description": "HADDOCK3 module to perform energy minimization scoring.",
"schema": {
"type": "object",
Expand Down Expand Up @@ -1363,7 +1363,7 @@
{
"id": "mdscoring",
"category": "scoring",
"label": "MD scoring module.",
"label": "MD scoring module.\n\nThis module will perform a short MD simulation on the input models and\nscore them. No restraints are applied during this step.",
"description": "HADDOCK3 module to perform energy minimization scoring.",
"schema": {
"type": "object",
Expand Down Expand Up @@ -1582,7 +1582,7 @@
{
"id": "seletop",
"category": "analysis",
"label": "Select a top models.",
"label": "",
"description": "HADDOCK3 module to select top cluster/model.",
"schema": {
"type": "object",
Expand Down Expand Up @@ -1610,7 +1610,7 @@
{
"id": "seletopclusts",
"category": "analysis",
"label": "Select a top cluster module.",
"label": "",
"description": "Haddock Module for 'seletopclusts'.",
"schema": {
"type": "object",
Expand Down
Loading

0 comments on commit 80db144

Please sign in to comment.