From 59e3f5a56d03e404431dd53e259ce564c92850da Mon Sep 17 00:00:00 2001
From: Remi  PLANEL <rplanel@pasteur.fr>
Date: Fri, 12 Apr 2024 14:04:36 +0200
Subject: [PATCH] WIP

---
 components/OperonStructure.vue                | 53 ++++++++++-----
 components/content/ArticleStructure.vue       | 19 +++---
 components/content/PdbeMolstarPlugin.vue      | 20 ++++--
 components/content/PdockqMatrix.vue           |  2 +-
 components/content/SystemOperonStructure.vue  | 66 +++++++++++++++++--
 content/3.defense-systems/avs.md              |  1 +
 .../df-wiki-cli/df_wiki_cli/content/main.py   |  4 +-
 stores/structuresBasket.ts                    | 32 +++++++++
 types/plot.ts                                 |  6 --
 types/structure.ts                            | 35 ++++++++++
 10 files changed, 188 insertions(+), 50 deletions(-)
 create mode 100644 stores/structuresBasket.ts
 create mode 100644 types/structure.ts

diff --git a/components/OperonStructure.vue b/components/OperonStructure.vue
index ca56049b..9df846ba 100644
--- a/components/OperonStructure.vue
+++ b/components/OperonStructure.vue
@@ -1,27 +1,29 @@
 <script setup lang="ts">
 import { useElementSize } from '@vueuse/core'
 import * as d3 from "d3";
-import type { PlotMargin, StructureOperonGene } from "../types/plot";
-
+import type { PlotMargin } from "../types/plot";
+import type { StructureOperonGene } from "../types/structure"
+import { useStructuresBasket } from '~/stores/structuresBasket';
 
 
 interface Props {
     genes: StructureOperonGene[] | null
 }
+const structureBasket = useStructuresBasket()
 
 const props = withDefaults(defineProps<Props>(), {
     genes: null
 });
-const height = ref<number>(75)
+const height = ref<number>(150)
 const svgRef = ref(null)
 
 
 
 const margin = ref<PlotMargin>({
     marginTop: 10,
-    marginRight: 0,
-    marginBottom: 20,
-    marginLeft: 0,
+    marginRight: 5,
+    marginBottom: 30,
+    marginLeft: 5,
 })
 
 const plotHeight = computed(() => {
@@ -38,11 +40,14 @@ const yScale = ref(d3.scaleBand()
 const gbContainer = ref(null)
 const gbContainerWidth = ref(useElementSize(gbContainer, { width: 500, height: 0 }, { box: 'border-box' }).width)
 
-const computedWidth = computed(() => {
+const computedContainerWidth = computed(() => {
     return gbContainerWidth.value
 })
 
-
+const computedPlotWidth = computed(() => {
+    const { marginLeft, marginRight } = toValue(margin)
+    return computedContainerWidth.value - marginLeft - marginRight
+})
 
 onMounted(() => {
     draw()
@@ -61,11 +66,9 @@ const computedGenes = computed(() => {
 })
 watch(computedGenes, () => {
     const genes = toValue(computedGenes)
-    console.log(genes)
     const domain = genes?.map(d => { return d.gene })
-    console.log(domain)
     xScale.value.domain(domain)
-        .range([0, computedWidth.value])
+        .range([0, computedPlotWidth.value])
 })
 
 
@@ -102,20 +105,24 @@ function createOrSelect(container, tag, selectionClass) {
 
 function draw() {
     const svg = d3.select(svgRef.value);
-    const { marginLeft, marginRight } = toValue(margin)
+    const { marginLeft } = toValue(margin)
     const xAxis = d3.axisBottom(xScale.value)
     const gx = createOrSelect(svg, 'g', 'x-axis')
 
     gx
         .attr("transform", `translate(${marginLeft},${toValue(height)})`)
         .call(xAxis)
+        .selectAll("text")
+        .attr("transform", 'rotate(45)')
+        .attr("text-anchor", "start")
 
     const gxTitle = createOrSelect(gx, "text", "x-axis-title")
-    gxTitle.attr("text-anchor", "end")
+    gxTitle
+        .attr("text-anchor", "end")
         .attr("fill", "currentColor")
 
     gxTitle
-        .attr("x", computedWidth.value - marginLeft - marginRight)
+        .attr("x", toValue(computedPlotWidth))
         .attr("y", - 10)
 
 
@@ -133,6 +140,7 @@ function drawGenes(genesGroup) {
                 const g = enter.append("g")
                     .classed("gene", true);
                 g.append("path").classed("gene", true)
+                g.append("image")
                 g.append("text")
                     // .attr("fill", "white")
                     .classed("gene-label", true)
@@ -145,14 +153,22 @@ function drawGenes(genesGroup) {
             exit => exit.remove()
         )
     genesSelection.attr("transform", d => `translate(${d.x},${d.y})`)
+    genesGroup.select("image")
+        .attr("href", d => d?.structImg)
+        .attr("width", d => d.width)
+        .attr("height", d => d.height)
+        .on("click", function (event) {
+            console.log(event)
+            const data = d3.select(this).data()
+            console.log(data)
+            structureBasket.set(data.map(s => s.structPath))
+
+        })
     genesSelection.select("path.gene")
         .attr("d", d => drawGene(d).toString())
 }
 
 function drawGene({ width, height }) {
-    console.log("dans le draw gene")
-    console.log(width)
-    console.log(height)
     const context = d3.path()
     context.moveTo(0, 0)
     context.lineTo(width, 0)
@@ -167,7 +183,8 @@ function drawGene({ width, height }) {
 <template>
     <div ref="gbContainer">
         <v-card flat color="transparent">
-            <svg ref="svgRef" :width="computedWidth" :height="plotHeight">
+            <!-- <v-img :href=""></v-img> -->
+            <svg ref="svgRef" :width="computedContainerWidth" :height="plotHeight">
                 <g class="x-axis" />
             </svg>
         </v-card>
diff --git a/components/content/ArticleStructure.vue b/components/content/ArticleStructure.vue
index 8fb4be2f..c4615559 100644
--- a/components/content/ArticleStructure.vue
+++ b/components/content/ArticleStructure.vue
@@ -3,13 +3,15 @@ import { toValue } from '@vueuse/core';
 // import MolstarPdbePlugin from './MolstarPdbePlugin.vue';
 import * as d3 from "d3";
 import SystemOperonStructure from './SystemOperonStructure.vue';
+import type { StructureItem } from '~/types/structure';
+import type { SearchParams, SearchResponse } from 'meilisearch';
 
 
 const { page } = useContent();
 const client = useMeiliSearchRef()
 
 // get the structures
-const structures = ref()
+const structures = ref<SearchResponse<StructureItem, SearchParams> | undefined>()
 const structureTitle = ref("Structure")
 const msIndexName = ref<string>("structure")
 const stuctureUrls = ref<string[] | undefined>(undefined)
@@ -62,11 +64,12 @@ function displayStructure(item) {
 
 const sanitizedStructures = computed(() => {
     const toValStructures = toValue(structures)
-    if (toValStructures?.hits?.length > 0) {
+    if (toValStructures?.hits && toValStructures?.hits?.length > 0) {
 
         return toValStructures.hits.map(item => {
+            console.log(item)
             return {
-                ...item, structuresUrls: [`/${item.system}/${pdbNameToCif(item.pdb)}`, `/${item.system}/${item.pdb}`]
+                ...item, structuresUrls: [`/${item.system.toLowerCase()}/${pdbNameToCif(item.pdb)}`, `/${item.system.toLowerCase()}/${item.pdb}`]
                     .map(url => {
                         return toValue(useRefinedUrl(url).refinedUrl)
                     })
@@ -77,6 +80,7 @@ const sanitizedStructures = computed(() => {
 })
 
 
+
 const perSubsystemStructures = computed(() => {
     const toValStructures = toValue(sanitizedStructures)
     if (toValStructures?.length > 0) {
@@ -89,11 +93,8 @@ const perSubsystemStructures = computed(() => {
 // ASYNC PART 
 
 async function fetchStructures() {
-
-
-
     try {
-        const d = await client.index(toValue(msIndexName)).search("", {
+        const d = await client.index(toValue(msIndexName)).search<StructureItem>("", {
             facets: ["*"],
             filter: [`system='${toValue(computedSystem)}'`, "completed='true'"],
         })
@@ -109,8 +110,8 @@ async function fetchStructures() {
         <template v-for="[subsystem, structures] in perSubsystemStructures" :key="subsystem[0]">
             <ProseH3>{{ computedSystem }} - {{ subsystem }}</ProseH3>
             <v-row align="end">
-                <v-col cols="6">
-                    <SystemOperonStructure :system="computedSystem" :subsystem="subsystem" />
+                <v-col cols="6" class="ml-0 ">
+                    <SystemOperonStructure :system="computedSystem" :subsystem="subsystem" :structures="structures" />
                 </v-col>
                 <v-col cols="6">
                     <PdockqMatrix :subsystem="subsystem" />
diff --git a/components/content/PdbeMolstarPlugin.vue b/components/content/PdbeMolstarPlugin.vue
index 67641746..498039a8 100644
--- a/components/content/PdbeMolstarPlugin.vue
+++ b/components/content/PdbeMolstarPlugin.vue
@@ -1,11 +1,12 @@
 <script setup lang="ts">
 import { computed, toValue } from '#imports'
 import { useDisplay } from "vuetify";
+import { useStructuresBasket } from '~/stores/structuresBasket';
 
 
 const { mobile, width, height } = useDisplay()
 
-
+const structureBasket = useStructuresBasket()
 useHead({
     link: [
         {
@@ -50,9 +51,12 @@ const moleculeFormat = ref<string>("pdb")
 const selectedPdb = ref<string | null>(null)
 const structureToDownload: Ref<string | null> = ref(null)
 
+const selectedPaePath = computed(() => {
+    console.log(selectedPdb)
+    return selectedPdb.value ? `${selectedPdb.value.split(".").slice(0, -1).join('.')}.pae.png` : undefined
+})
 
 const computedWidth = computed(() => {
-    // if (toValue(width) > toValue(maxWidth)) return toValue(maxWidth) / 1.5
     return toValue(width) / 1.5
 })
 
@@ -78,7 +82,8 @@ function viewPdb(pdbPath: string | null) {
 
 function closeStructure() {
     selectedPdb.value = null
-    dataUrls.value = undefined
+    // dataUrls.value = undefined
+    structureBasket.set(undefined)
     dialog.value = false
 }
 watch(selectedPdb, (newSelectedPdb, prevSelectPdb) => {
@@ -89,10 +94,10 @@ watch(selectedPdb, (newSelectedPdb, prevSelectPdb) => {
 })
 
 watchEffect(() => {
-    const toValUrl = toValue(dataUrls)
-    if (toValUrl?.length > 0) {
+    const toValUrl = toValue(structureBasket).structures
+    if (toValUrl && toValUrl?.length > 0) {
         dialog.value = true
-        selectedPdb.value = dataUrls.value[0]
+        selectedPdb.value = toValUrl[0]
     }
     else {
         dialog.value = false
@@ -105,7 +110,8 @@ watchEffect(() => {
         <v-card flat :rounded="false">
             <v-toolbar>
                 <v-toolbar-title>{{ title }}</v-toolbar-title>
-                <v-select v-model="selectedPdb" label="Select PDB" :items="dataUrls" hide-details="auto"></v-select>
+                <v-select v-model="selectedPdb" label="Select PDB" :items="structureBasket.structures"
+                    hide-details="auto"></v-select>
                 <v-spacer></v-spacer>
 
                 <v-btn :disabled="!selectedPdb" icon="md:download" :href="structureToDownload"></v-btn>
diff --git a/components/content/PdockqMatrix.vue b/components/content/PdockqMatrix.vue
index c03dce96..b47a1c70 100644
--- a/components/content/PdockqMatrix.vue
+++ b/components/content/PdockqMatrix.vue
@@ -179,7 +179,7 @@ function pdbNameToCif(pdbPath: string) {
     return `${cifPath}.cif`
 }
 function buildStructureUrl(item) {
-    return [`/${item.system}/${pdbNameToCif(item.pdb)}`, `/${item.system}/${item.pdb}`]
+    return [`/${item.system.toLowerCase()}/${pdbNameToCif(item.pdb)}`, `/${item.system}/${item.pdb}`]
 }
 
 function displayStructure(item) {
diff --git a/components/content/SystemOperonStructure.vue b/components/content/SystemOperonStructure.vue
index 2a5004a4..c1a69d2f 100644
--- a/components/content/SystemOperonStructure.vue
+++ b/components/content/SystemOperonStructure.vue
@@ -1,15 +1,18 @@
 <script setup lang="ts">
-import type { StructureOperonGene } from "../../types/plot";
+import type { StructureItem, StructureOperonGene } from "../../types/structure";
 import type { SearchResponse } from "meilisearch";
+import { withTrailingSlash, withLeadingSlash, joinURL } from 'ufo'
 
 
 interface Props {
     system: string
     subsystem: string
+    structures: StructureItem[]
 }
 
+const { page } = useContent();
 
-const { system, subsystem } = withDefaults(defineProps<Props>(), {
+const { system, subsystem, structures } = withDefaults(defineProps<Props>(), {
 });
 const client = useMeiliSearchRef()
 
@@ -17,35 +20,84 @@ const pending = ref<boolean>(false)
 const msIndexName = ref<'systemoperonstruct'>("systemoperonstruct")
 const msResponse = ref<SearchResponse<StructureOperonGene> | undefined>(undefined)
 
+const monomerStructures = computed(() => {
+    const structuresVal = toValue(structures)
+    if (structuresVal) {
+        return structuresVal.filter(struct => {
+            return struct.prediction_type === "monomer"
+        }).map((struct) => {
+            return {
+                ...struct,
+                structImg: `${struct.pdb.split(".pdb")[0]}.png`,
+                structPath: struct.pdb,
+                proteins_in_the_prediction: struct.proteins_in_the_prediction.map(prot => {
+                    return prot.split("__")[1]
+                })
+            }
+        })
+    }
+})
 
 const sanitizedHits = computed(() => {
     const toValMsResponse = toValue(msResponse)
     if (toValMsResponse && toValMsResponse?.hits?.length > 0) {
         return toValMsResponse.hits.map(hit => {
-            return { ...hit, gene: hit.gene.split("__")[1] }
+            // get structure information for this prot
+            const monomerStructuresVal = toValue(monomerStructures)
+            const sanitizedGene = hit.gene.split("__")[1]
+            if (monomerStructuresVal) {
+                const struct = monomerStructuresVal.find((struct) => {
+                    return struct.proteins_in_the_prediction[0] === sanitizedGene
+                })
+                const rawImgUrl = joinURL(`/${system.toLowerCase()}`, struct?.structImg ?? '')
+                const rawStructUrl = joinURL(`/${system.toLowerCase()}`, struct?.structPath ?? '')
+                const { refinedUrl: structImgHref } = useRefinedUrl(rawImgUrl)
+                return { ...hit, gene: sanitizedGene, structImg: toValue(structImgHref), structPath: rawStructUrl}
+            }
+            else {
+                return { ...hit, gene: sanitizedGene }
+            }
         })
     } else {
         return []
     }
+})
+
 
+const sanitizedSystem = computed(() => {
+    const pageVal = toValue(page)
+    return pageVal?.systemModel || pageVal.title
+})
+
+const sanitizedSubsystem = computed(() => {
+    const pageVal = toValue(page)
+    const systemModel = pageVal?.systemModel
+    if (systemModel) {
+        return subsystem.replace(pageVal.title, systemModel)
+    }
+    else {
+        return subsystem
+    }
 })
 
 onMounted(() => {
     fetchOperonStructure()
 })
 
+
+
 async function fetchOperonStructure() {
     try {
         pending.value = true
         const data = await client.index(toValue(msIndexName)).search<StructureOperonGene>("", {
             facets: ["*"],
-            filter: [`system='${system}' AND subsystem='${subsystem}'`],
+            filter: [`system='${toValue(sanitizedSystem)}' AND subsystem='${toValue(sanitizedSubsystem)}'`],
             limit: 500000,
         })
         msResponse.value = data
 
     } catch (error) {
-        throw createError(`Cannot get hits on refseq for system: ${system} `)
+        throw createError(`Cannot get hits on refseq for system: ${toValue(sanitizedSystem)} `)
     } finally {
         pending.value = false
     }
@@ -54,8 +106,8 @@ async function fetchOperonStructure() {
 
 <template>
 
-    <v-card flat>
-        <OperonStructure :genes="sanitizedHits" />
+    <v-card variant="flat">
+        <OperonStructure :genes="sanitizedHits" :system :subsystem />
     </v-card>
 
 </template>
\ No newline at end of file
diff --git a/content/3.defense-systems/avs.md b/content/3.defense-systems/avs.md
index f18121d5..dfc62f59 100644
--- a/content/3.defense-systems/avs.md
+++ b/content/3.defense-systems/avs.md
@@ -1,5 +1,6 @@
 ---
 title: Avs
+systemModel: AVAST
 layout: article
 tableColumns:
     article:
diff --git a/packages/df-wiki-cli/df_wiki_cli/content/main.py b/packages/df-wiki-cli/df_wiki_cli/content/main.py
index bc669941..14b32364 100644
--- a/packages/df-wiki-cli/df_wiki_cli/content/main.py
+++ b/packages/df-wiki-cli/df_wiki_cli/content/main.py
@@ -111,11 +111,11 @@ def structure(
     ],
 ):
     with open(stat, "r") as stat_f:
-        reader = csv.DictReader(stat_f)
+        reader = csv.DictReader(stat_f, delimiter="\t")
         count_row = 0
         for row in reader:
             count_row += 1
-            system_dir_name = row["system"]
+            system_dir_name = row["system"].lower()
             pdb_path_file = Path(row["pdb"])
             foldseek_html_file = Path(str(pdb_path_file).split(".pdb")[0] + ".html")
             png_structure = Path(str(pdb_path_file).split(".pdb")[0] + ".png")
diff --git a/stores/structuresBasket.ts b/stores/structuresBasket.ts
new file mode 100644
index 00000000..c64d6ee5
--- /dev/null
+++ b/stores/structuresBasket.ts
@@ -0,0 +1,32 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+
+export const useStructuresBasket = defineStore('structuresBasket', () => {
+    const rawStructures = ref<string[] | undefined>(undefined)
+
+    function pdbToCif(pdbPath: string) {
+        const cifPath = pdbPath.split(".").slice(0, -1).join(".")
+        return `${cifPath}.cif`
+    }
+    const structures = computed(() => {
+        const rawStructuresVal = toValue(rawStructures)
+        if (rawStructuresVal !== undefined) {
+            return rawStructuresVal
+                // add cif
+                .flatMap((struct) => {
+                    return [pdbToCif(struct), struct]
+                })
+                // refinedUrl
+                .map((struct) => {
+                    const { refinedUrl } = useRefinedUrl(struct)
+                    return toValue(refinedUrl)
+                })
+        }
+        return rawStructuresVal
+    })
+
+    function set(structuresToSet: MaybeRef<string[] | undefined>) {
+        rawStructures.value = toValue(structuresToSet)
+    }
+    return { structures, set }
+})
\ No newline at end of file
diff --git a/types/plot.ts b/types/plot.ts
index 483cd6bf..f3e16e34 100644
--- a/types/plot.ts
+++ b/types/plot.ts
@@ -7,9 +7,3 @@ export interface PlotMargin {
 
 
 
-export interface StructureOperonGene {
-    gene: string
-    id: number
-    subsystem: string
-    system: string
-}
\ No newline at end of file
diff --git a/types/structure.ts b/types/structure.ts
new file mode 100644
index 00000000..51fa0067
--- /dev/null
+++ b/types/structure.ts
@@ -0,0 +1,35 @@
+export interface StructureOperonGene {
+    gene: string
+    id: number
+    subsystem: string
+    system: string
+}
+
+export type StructurePredictionType = "monomer" | "multimer" | "multimer(dimer)" | "multimer(homodimer)"
+export type StructureType = "Validated" | "DF" | "na"
+
+export interface StructureItem {
+    id: number
+    proteins_in_the_prediction: string[]
+    prediction_type: StructurePredictionType
+    batch: number
+    nb_sys: number
+    type: StructureType
+    system_number_of_genes: number
+    system_genes: string[]
+    pdb: string
+    pae_table: string
+    plddt_table: string
+    fasta_file: string
+    completed: boolean
+    'iptm+ptm': number | null
+    pDockQ: number | null
+    plddts: number | null
+    structure_system: string
+    system: string
+    subsystem: string
+    Abi2: string
+    OrbA: string
+    Anti_BREX: string
+    // "structuresUrls": ["/wiki/Avs/AVAST_I.AVAST_I__Avs1A.0.V.cif", "/wiki/Avs/AVAST_I.AVAST_I__Avs1A.0.V.pdb"]
+}
\ No newline at end of file
-- 
GitLab