From 48d02dd8b22ba6a2b80d23a08285180777753700 Mon Sep 17 00:00:00 2001 From: Remi PLANEL <rplanel@pasteur.fr> Date: Mon, 6 May 2024 16:44:19 +0200 Subject: [PATCH] global max operon size for genes scale --- components/OperonStructure.vue | 36 +++++++++++---- components/content/ArticleStructure.vue | 48 +++++++++++++++++++- components/content/SystemOperonStructure.vue | 25 +++++----- types/structure.ts | 3 ++ 4 files changed, 90 insertions(+), 22 deletions(-) diff --git a/components/OperonStructure.vue b/components/OperonStructure.vue index 688d6ad6..421a0f76 100644 --- a/components/OperonStructure.vue +++ b/components/OperonStructure.vue @@ -35,15 +35,14 @@ const margin = ref<PlotMargin>({ const gbContainer = ref(null) const geneToHighlight = ref<string | null>(null) const snackbar = ref(false) -const innerPaddingRatio = ref<number>(0.1) +const innerPaddingRatio = ref<number>(0.05) // const color = d3.scaleOrdinal(d3.schemeCategory10); const domain = computed(() => { const genes = toValue(computedGenes) return genes?.map(d => { return d.gene }) }) -const maxOperonSize = computed(() => { - return props.maxOperonSize || 0 -}) +const maxOperonSize = toRef(props, "maxOperonSize") + const operonLength = computed(() => { // return props.maxOperonSize || 0 const genes = toValue(computedGenes) @@ -54,7 +53,6 @@ const operonLength = computed(() => { const innerPadding = computed(() => { const totalGeneLengthVal = toValue(operonLength) // const totalGeneLengthVal = props.maxOperonSize || 0 - const genes = toValue(computedGenes) let innerPadding = innerPaddingRatio.value // if (g enes.length === 1) innerPadding = 0 return totalGeneLengthVal * innerPadding @@ -75,7 +73,7 @@ const totalGeneLengthWithPadding = computed(() => { const allOperonPadding = computed(() => { - const maxOperonSizeVal = toValue(maxOperonSize) + const maxOperonSizeVal = toValue(maxOperonSize) || 0 const innerPaddingRatioVal = toValue(innerPaddingRatio) return maxOperonSizeVal * innerPaddingRatioVal }) @@ -89,21 +87,42 @@ const paddingPerGene = computed(() => { }) const maxOperonSizeWithPadding = computed(() => { - return toValue(allOperonPadding) + toValue(maxOperonSize) + return toValue(allOperonPadding) + (toValue(maxOperonSize) || 0) }) const domainGenes = computed(() => { return [0, maxOperonSizeWithPadding.value] // return [0, totalGeneLength.value] }) + +const structureRange = computed(() => { + const genes = toValue(computedGenes) + const minStructureWidth = genes.length * 100 + + // get genes width + const genesWidth = xScaleGenes.value(totalGeneLengthWithPadding.value) + + if (genesWidth > minStructureWidth) { + return [0, genesWidth] + } + else { + if (minStructureWidth > toValue(computedPlotWidth)) return [0, toValue(computedPlotWidth)] + return [0, minStructureWidth] + } + + + +}) + const xScaleStructure = computed(() => { return d3.scaleBand() .paddingInner(0) .domain(toValue(domain)) - .range([0, xScaleGenes.value(totalGeneLengthWithPadding.value)]) + .range(toValue(structureRange)) }) + const xScaleGenes = computed(() => { return d3.scaleLinear() .domain(toValue(domainGenes)) @@ -556,7 +575,6 @@ watch(genesTextRotate, (genesTextRotateVal) => { </v-btn></v-card-title> <v-card-subtitle>defenseFinder model version : {{ structureVersion }}</v-card-subtitle> </v-card-item> - <svg ref="svgRef" :width="computedContainerWidth" :height="plotHeight"> <g class="x-axis" /> </svg> diff --git a/components/content/ArticleStructure.vue b/components/content/ArticleStructure.vue index a7c99ab2..1d9e2eae 100644 --- a/components/content/ArticleStructure.vue +++ b/components/content/ArticleStructure.vue @@ -3,7 +3,7 @@ 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 { OperonStructureIndexName, StructureItem, StructureOperonGene } from '~/types/structure'; import type { SearchParams, SearchResponse } from 'meilisearch'; import { useStructuresBasket } from '~/stores/structuresBasket'; @@ -13,8 +13,10 @@ const client = useMeiliSearchRef() const structureBasket = useStructuresBasket() // get the structures const structures = ref<SearchResponse<StructureItem, SearchParams> | undefined>() +const operonStructures = ref<SearchResponse<StructureOperonGene, SearchParams> | undefined>() const structureTitle = ref("Structure") const msIndexName = ref<string>("structure") +const operonSructIndexName = ref<OperonStructureIndexName>("systemoperonstruct") // const stuctureUrls = ref<string[] | undefined>(undefined) const headers = ref<Record<string, any>[]>([ { title: 'Structure', key: 'structure', sortable: false, removable: false, fixed: true, minWidth: "110px" }, @@ -43,6 +45,7 @@ const computedSystem = computed(() => { }) onMounted(() => { fetchStructures() + fetchAllOperonStructures() }) function extractGeneName(name: string) { @@ -98,6 +101,31 @@ const perSubsystemStructures = computed(() => { else { return [] } }) +const groupedOperonStructures = computed(() => { + const operonStructuresVal = toValue(operonStructures) + if (operonStructuresVal && operonStructuresVal.hits.length > 0) { + return d3.groups(operonStructuresVal.hits, d => d.system, d => d.subsystem) + } + else { + return [] + } +}) + + +const maxSizeOperonStructures = computed(() => { + const groupedOperonStructuresVal = toValue(groupedOperonStructures) + return groupedOperonStructuresVal.reduce<number>((systemMax, [_, systems]) => { + const valMax = d3.max([ + systems.reduce<number>((subsystemMax, [_, subsystems]) => { + const valMax = d3.max([subsystems.reduce<number>((totSize, subsystem) => { + return totSize + subsystem.size + }, 0), subsystemMax]) || subsystemMax + return valMax + }, 0), systemMax] + ) || systemMax + return valMax + }, 0) +}) const allSubsystems = computed(() => { const perSubsystemStructuresVal = toValue(perSubsystemStructures) @@ -114,6 +142,7 @@ async function fetchStructures() { const d = await client.index(toValue(msIndexName)).search<StructureItem>("", { facets: ["*"], filter: [`system='${toValue(computedSystem)}'`, "completed='true'"], + limit: 500000 }) structures.value = d } catch (error) { @@ -121,6 +150,20 @@ async function fetchStructures() { } } +async function fetchAllOperonStructures() { + try { + const d = await client.index(toValue(operonSructIndexName)).search<StructureOperonGene>("", { + facets: ["*"], + filter: undefined, + limit: 500000 + }) + operonStructures.value = d + } catch (error) { + throw createError(`Cannot get structure for system: ${toValue(computedSystem)}`) + } +} + + </script> <template> <v-card flat color="transparent"> @@ -128,7 +171,8 @@ async function fetchStructures() { <template v-for="[subsystem, structures] in perSubsystemStructures" :key="subsystem[0]"> <!-- <ProseH3>{{ subsystem }}</ProseH3> --> <v-card-text class="pa-1"> - <SystemOperonStructure :system="computedSystem" :subsystem="subsystem" :structures="structures" :subsystems="allSubsystems"/> + <SystemOperonStructure :system="computedSystem" :subsystem="subsystem" :structures="structures" + :subsystems="allSubsystems" :max-operon-size="maxSizeOperonStructures" /> </v-card-text> </template> <!-- <ProseH3>Summary</ProseH3> --> diff --git a/components/content/SystemOperonStructure.vue b/components/content/SystemOperonStructure.vue index 3ac058bb..741ba4be 100644 --- a/components/content/SystemOperonStructure.vue +++ b/components/content/SystemOperonStructure.vue @@ -11,17 +11,20 @@ interface Props { subsystem: string structures: StructureItem[] subsystems: string[] + maxOperonSize: number } const { page } = useContent(); -const { system, subsystem, structures, subsystems } = withDefaults(defineProps<Props>(), { +const props = withDefaults(defineProps<Props>(), { + }); +const { system, subsystem, structures, subsystems } = props const client = useMeiliSearchRef() const pending = ref<boolean>(false) const sizeGene = ref<number>(140) - +const maxOperonSize = toRef(props, "maxOperonSize") const msIndexName = ref<'systemoperonstruct'>("systemoperonstruct") const msResponse = ref<SearchResponse<StructureOperonGene> | undefined>(undefined) const allSubsystems = ref<SearchResponse<StructureOperonGene> | undefined>(undefined) @@ -114,15 +117,15 @@ onMounted(() => { fetchSubsytems() }) -const maxOperonSize = computed(() => { - const allSubsystemsVal = toValue(allSubsystems) - return d3.max(d3.groups(allSubsystemsVal?.hits ?? [], d => d.subsystem) - .map(([_, val]) => { - return val.reduce((acc, curr) => { - return acc + curr.size - }, 0) - })) -}) +// const maxOperonSize = computed(() => { +// const allSubsystemsVal = toValue(allSubsystems) +// return d3.max(d3.groups(allSubsystemsVal?.hits ?? [], d => d.subsystem) +// .map(([_, val]) => { +// return val.reduce((acc, curr) => { +// return acc + curr.size +// }, 0) +// })) +// }) const msFilters = computed(() => { diff --git a/types/structure.ts b/types/structure.ts index 67004bdd..5753b726 100644 --- a/types/structure.ts +++ b/types/structure.ts @@ -1,3 +1,6 @@ +export type OperonStructureIndexName = 'systemoperonstruct' + + export interface StructureOperonGene { gene: string id: number -- GitLab