diff --git a/components/OperonStructure.vue b/components/OperonStructure.vue index a64d775cdd69070c5db4c311111ff8314d5e51ad..688d6ad6f9457a00153a788a8801dd7c5af4abd6 100644 --- a/components/OperonStructure.vue +++ b/components/OperonStructure.vue @@ -9,6 +9,8 @@ import { useTextRotate, type TextRotateOuput } from '~/composables/useTextRotate interface Props { genes: StructureOperonGeneWithImg[] | null + maxOperonSize: number | undefined + subsystems: string[] } const structureBasket = useStructuresBasket() @@ -39,46 +41,66 @@ const domain = computed(() => { const genes = toValue(computedGenes) return genes?.map(d => { return d.gene }) }) - -const totalGeneLength = computed(() => { +const maxOperonSize = computed(() => { + return props.maxOperonSize || 0 +}) +const operonLength = computed(() => { + // return props.maxOperonSize || 0 const genes = toValue(computedGenes) return genes.reduce((acc, curr) => { return acc + (curr?.size ?? 10) }, 0) }) const innerPadding = computed(() => { - const totalGeneLengthVal = toValue(totalGeneLength) + const totalGeneLengthVal = toValue(operonLength) + // const totalGeneLengthVal = props.maxOperonSize || 0 const genes = toValue(computedGenes) let innerPadding = innerPaddingRatio.value - if (genes.length === 1) innerPadding = 0 + // if (g enes.length === 1) innerPadding = 0 return totalGeneLengthVal * innerPadding }) const innerPaddingpPerGene = computed(() => { const genes = toValue(computedGenes) const innerPaddingVal = toValue(innerPadding) - return innerPaddingVal / genes.length + return genes.length === 1 ? innerPaddingVal / 2 : innerPaddingVal / genes.length }) const totalGeneLengthWithPadding = computed(() => { - const totalGeneLengthVal = toValue(totalGeneLength) + const totalGeneLengthVal = toValue(operonLength) + // const totalGeneLengthVal = props.maxOperonSize || 0 return totalGeneLengthVal + innerPadding.value }) +const allOperonPadding = computed(() => { + const maxOperonSizeVal = toValue(maxOperonSize) + const innerPaddingRatioVal = toValue(innerPaddingRatio) + return maxOperonSizeVal * innerPaddingRatioVal +}) + +const paddingPerGene = computed(() => { + const genes = toValue(computedGenes) + const innerPaddingVal = toValue(allOperonPadding) + if (genes.length === 0) return 0 + return innerPaddingVal / genes.length +}) +const maxOperonSizeWithPadding = computed(() => { + return toValue(allOperonPadding) + toValue(maxOperonSize) +}) const domainGenes = computed(() => { - return [0, totalGeneLengthWithPadding.value] + return [0, maxOperonSizeWithPadding.value] // return [0, totalGeneLength.value] }) -const xScale = computed(() => { +const xScaleStructure = computed(() => { return d3.scaleBand() .paddingInner(0) .domain(toValue(domain)) - .range([0, computedPlotWidth.value]) + .range([0, xScaleGenes.value(totalGeneLengthWithPadding.value)]) }) @@ -98,16 +120,11 @@ const computedPlotWidth = computed(() => { const computedGenes = computed<StructureOperonGene[]>(() => { const genes = toValue(genesProps) if (genes !== null && genes?.length > 0) { - let currentSumSize = 0 - return genes.map((d, i) => { + return genes.map((d) => { const size = d?.size ?? 10 - // const position = currentSumSize - // currentSumSize = position + size - // // + innerPaddingpPerGene.value return { ...d, size, - // position, highlight: geneToHighlight.value === d.gene } }) @@ -129,11 +146,10 @@ const structureVersion = computed(() => { const geneNodes = computed<StructureOperonGeneWithCoordinate[]>(() => { const genes = toValue(computedGenes) const xScaleVal = toValue(xScaleGenes) - const innerPaddingpPerGeneVal = toValue(innerPaddingpPerGene) - // const yScaleVal = toValue(yScale) + const innerPaddingpPerGeneVal = toValue(paddingPerGene) if (genes !== null) { - let currentSumSize = 0 - return genes.map(d => { + let currentSumSize = props.subsystems.length > 1 ? 0 : innerPaddingpPerGeneVal / 2 + return genes.map((d) => { const width = xScaleVal(d.size) const position = currentSumSize const x = xScaleVal(position) @@ -148,6 +164,7 @@ const geneNodes = computed<StructureOperonGeneWithCoordinate[]>(() => { height: 0 } }) + } else { return [] } }) @@ -182,7 +199,7 @@ const geneNodesWithY = computed(() => { }) const structureNodes = computed<StructureOperonGeneWithCoordinate[]>(() => { const genes = toValue(geneNodesWithY) - const xScaleVal = toValue(xScale) + const xScaleVal = toValue(xScaleStructure) const yScaleVal = toValue(yScale) if (genes !== null) { return genes.map(d => { @@ -192,7 +209,7 @@ const structureNodes = computed<StructureOperonGeneWithCoordinate[]>(() => { ...d, x: x === undefined ? 0 : x, y: y === undefined ? 0 : y, - width: toValue(xScale).bandwidth(), + width: toValue(xScaleStructure).bandwidth(), height: structureHeight.value } }) diff --git a/components/content/ArticleStructure.vue b/components/content/ArticleStructure.vue index 85e97f86f8d091fcbfa93e4e9b0974fa098ee90b..a7c99ab2fa0677afd69e506a1df8d5f9cc3ba5c8 100644 --- a/components/content/ArticleStructure.vue +++ b/components/content/ArticleStructure.vue @@ -98,6 +98,14 @@ const perSubsystemStructures = computed(() => { else { return [] } }) + +const allSubsystems = computed(() => { + const perSubsystemStructuresVal = toValue(perSubsystemStructures) + return perSubsystemStructuresVal.map(([subsystem, _]) => { + return subsystem + }) +}) + // ================================================================================== // ASYNC PART @@ -120,7 +128,7 @@ 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" /> + <SystemOperonStructure :system="computedSystem" :subsystem="subsystem" :structures="structures" :subsystems="allSubsystems"/> </v-card-text> </template> <!-- <ProseH3>Summary</ProseH3> --> diff --git a/components/content/SystemOperonStructure.vue b/components/content/SystemOperonStructure.vue index d42bf36990c83db93fe8ced14a4b58e9fa970c4c..3ac058bbc22760c5cbd0caeebefe0576e1195683 100644 --- a/components/content/SystemOperonStructure.vue +++ b/components/content/SystemOperonStructure.vue @@ -1,4 +1,5 @@ <script setup lang="ts"> +import * as d3 from "d3"; import type { StructureItem, StructureOperonGene, StructureOperonGeneWithImg } from "../../types/structure"; import type { SearchResponse } from "meilisearch"; import { joinURL } from 'ufo' @@ -9,11 +10,12 @@ interface Props { system: string subsystem: string structures: StructureItem[] + subsystems: string[] } const { page } = useContent(); -const { system, subsystem, structures } = withDefaults(defineProps<Props>(), { +const { system, subsystem, structures, subsystems } = withDefaults(defineProps<Props>(), { }); const client = useMeiliSearchRef() @@ -22,7 +24,7 @@ const sizeGene = ref<number>(140) const msIndexName = ref<'systemoperonstruct'>("systemoperonstruct") const msResponse = ref<SearchResponse<StructureOperonGene> | undefined>(undefined) - +const allSubsystems = ref<SearchResponse<StructureOperonGene> | undefined>(undefined) const monomerStructures = computed(() => { const structuresVal = toValue(structures) if (structuresVal) { @@ -97,14 +99,6 @@ const sanitizedSystem = computed(() => { }) const sanitizedSubsystem = computed(() => { - // const pageVal = toValue(page) - // const systemModel = pageVal?.systemModel - // if (systemModel) { - // return subsystem.replace(pageVal.title, systemModel) - // } - // else { - // return subsystem - // } return subsystem }) @@ -117,10 +111,34 @@ const { wholeRow } = useColumnLayout({ contentWidth, widthThreshold: 600 }) onMounted(() => { fetchOperonStructure() + 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 msFilters = computed(() => { + const subsystemFilters = subsystems.reduce((acc, curr, i) => { + let filterToAdd = "" + if (i === 0) { + filterToAdd = `subsystem='${curr}'` + } + else { + filterToAdd = `OR subsystem='${curr}'` + } + return `${acc} ${filterToAdd}` + }, '') + return `system='${toValue(sanitizedSystem)}' AND (${subsystemFilters})` +}) + async function fetchOperonStructure() { try { pending.value = true @@ -137,6 +155,24 @@ async function fetchOperonStructure() { pending.value = false } } + + +async function fetchSubsytems() { + try { + pending.value = true + const data = await client.index(toValue(msIndexName)).search<StructureOperonGene>("", { + facets: ["*"], + filter: [toValue(msFilters)], + limit: 500000, + }) + allSubsystems.value = data + + } catch (error) { + throw createError(`Cannot get hits on refseq for system: ${toValue(sanitizedSystem)} `) + } finally { + pending.value = false + } +} </script> <template> @@ -147,7 +183,8 @@ async function fetchOperonStructure() { <v-card-text> <v-row align="top"> <v-col :cols="wholeRow ? 12 : 6" align-self="stretch" style="background-color: transparent;"> - <OperonStructure :genes="sanitizedHits" :system :subsystem /> + <OperonStructure :genes="sanitizedHits" :system :subsystem :max-operon-size="maxOperonSize" + :subsystems /> </v-col> <v-col :cols="wholeRow ? 12 : 6">