Skip to content
Snippets Groups Projects
Select Git revision
  • 6c6f3f0ae40ec7e53a9a754ada052f94dc686e1d
  • main default protected
  • v0.5.1
  • v0.5
  • v0.4.3.1
  • v0.4.2
  • v0.4.1
  • v0.4
  • v0.3
9 results

__init__.py

Blame
  • [analysisId].vue 13.24 KiB
    <script setup lang="ts">
    import { useThrottleFn } from '@vueuse/core'
    import type { Analysis, Gene, GenesOut, Hmmer, HmmersOut, ProteinsOut } from '@/types'
    // import { useFetchAnalysis } from "../../composables/useFetchAnalysis";
    import { useSelectedProtein } from '@/composables/useSelectedProtein'
    import { useRoute, computed } from "#imports";
    import * as d3 from "d3";
    import { useElementSize } from '@vueuse/core'
    import { joinURL } from "ufo";
    
    const route = useRoute();
    const { selectedProtein } = useSelectedProtein()
    
    const runtimeConfig = useRuntimeConfig()
    
    
    const analysisId = computed(() => {
      if (Array.isArray(route.params.analysisId)) return null
      return parseInt(route.params.analysisId)
    })
    
    
    const breadcrumbItems = computed(() => {
      return [
        {
          title: 'Home',
          disabled: false,
          to: { name: 'index' }
        },
        {
          title: 'Analyses',
          disabled: false,
          to: { name: 'analyses' },
        },
        {
          title: analysis.value?.name,
          disabled: true,
          to: {
            name: route.name, params: { ...route.params },
          }
        }
      ]
    
    })
    
    
    const height = ref(300)
    
    const genesMap = computed(() => {
      const mapGenes = new Map<string, Gene>()
      const toValGenes = toValue(genes)
      if (toValGenes !== null) {
        for (const gene of toValGenes.genes) {
          mapGenes.set(gene.hit_id, { ...gene, hit_gene_ref: gene.hit_gene_ref.split('__')[0].toLowerCase() })
        }
      }
      return mapGenes
    })
    
    const hmmersMap = computed(() => {
      const hmmMap = new Map<string, Hmmer>()
      const toValHmmers = toValue(rawHmmer)
      if (toValHmmers !== null) {
        for (const hmmer of toValHmmers.hmmers) {
          if (hmmMap.has(hmmer.hit_id)) {
            const hmmerInMapScore = hmmMap.get(hmmer.hit_id)?.hit_score ?? -1
            if (hmmerInMapScore < hmmer.hit_score) {
              hmmMap.set(hmmer.hit_id, { ...hmmer })
            }
          } else {
            hmmMap.set(hmmer.hit_id, { ...hmmer })
          }
        }
      }
      return hmmMap
    })
    
    const { data: rawProteins } = await useAPI<ProteinsOut>(
      `/analysis/${route.params.analysisId}/proteins`
    );
    const { data: genes } = await useAPI<GenesOut>(
      `/analysis/${route.params.analysisId}/genes`
    );
    
    const { data: rawHmmer } = await useAPI<HmmersOut>(
      `/analysis/${route.params.analysisId}/hmmers`
    );
    const computedXUnit = computed(() => {
      if (rawProteins.value?.proteins) {
        if (rawProteins.value.proteins[0].start === null && rawProteins.value.proteins[0].end === null) {
          return "aa"
        }
        else { return "bp" }
      }
    })
    
    const sanitizedData = computed(() => {
      if (rawProteins.value?.proteins) {
        const toValGenesMap = toValue(genesMap)
        const toValHmmerMap = toValue(hmmersMap)
        const prots = rawProteins.value.proteins.map(prot => {
          const isHmmerHit = toValHmmerMap.has(prot.id)
          const isDefenseSystem = toValGenesMap.has(prot.id)
          return {
    
            ...prot,
            name: prot.id,
            gene_name: toValHmmerMap.get(prot.id)?.gene_name,
            strand: prot.strand,
            defenseSystem: toValGenesMap.get(prot.id)?.hit_gene_ref,
            isDefenseSystem,
            isHmmerHit
          }
        })
        const proteins = []
        let currentGenomeIndex = 1
    
        for (const prot of prots) {
          if (prot?.start !== null && prot?.end !== null) {
            proteins.push({ ...prot, length: prot.end - prot.start, protLength: prot.length })
          }
          else {
            proteins.push({ ...prot, start: currentGenomeIndex, end: currentGenomeIndex + prot.length - 1, protLength: prot.length })
            currentGenomeIndex = currentGenomeIndex + prot.length + 20
          }
        }
        return proteins
      }
      else { return [] }
    })
    
    const proteinIndex = computed(() => {
      return new Map(sanitizedData.value.map((d, i, arr) => {
        let previousStart = 0
        if (i > 0) {
          previousStart = arr[i - 1].start - 10
        }
        let nextEnd = previousStart + 400
        if (i < arr.length - 1) {
          nextEnd = arr[i + 1].end + 10
        }
        return [d.id, { ...d, previousStart, nextEnd }]
      }))
    
    })
    
    
    
    
    const displayHmmerHits = ref<boolean>(true)
    type ColorScale = (value: string | number) => string
    const color = computed<ColorScale>(() => {
    
      return d3.scaleOrdinal(["default", undefined, "df", "hmmer"], d3.schemeTableau10)
    })
    
    
    const gbContainer = ref(null)
    const gbContainerWidth = ref(useElementSize(gbContainer, { width: 500, height: 0 }, { box: 'border-box' }).width)
    
    const computedWidth = computed(() => {
      return gbContainerWidth.value
    })
    
    
    
    
    
    watch(selectedProtein, () => {
      const toValHitId = toValue(selectedProtein)
      const toValProtIndex = toValue(proteinIndex)
      if (toValHitId && toValProtIndex.size > 0 && toValProtIndex.has(toValHitId)) {
    
        const prot = toValProtIndex.get(toValHitId)
        domain.value = [prot?.previousStart ?? 0, prot?.nextEnd ?? 400]
      }
    })
    
    const marginLeftGb = ref(10)
    const marginRightGb = ref(10)
    const marginBottomGb = ref(10)
    
    const innerWidth = computed(() => {
      return computedWidth.value - marginLeftGb.value - marginRightGb.value
    })
    const innerHeigth = computed(() => {
      return height.value - marginBottomGb.value
    })
    
    const minRange = ref(0)
    const maxRange = ref(innerWidth.value)
    
    
    const svgRef = ref(null)
    
    const domain = ref([0, 10000])
    // const range = ref()
    const xScale = ref(d3.scaleLinear()
      .domain(domain.value)
      .range([minRange.value, maxRange.value])
    );
    const yScale = ref(d3.scaleLinear()
      .domain([-1, 1])
      .range([0, innerHeigth.value]));
    
    
    
    
    const computedData = computed(() => {
      const newData = sanitizedData.value.filter(gene => {
        const { start, end } = gene
        const [scaleStart, scaleEnd] = xScale.value.domain()
    
        return start <= scaleEnd && end >= scaleStart
    
      }).map(gene => {
        const width = xScale.value(gene.end) - xScale.value(gene.start)
        const height = yScale.value(-0.75)
        const x = xScale.value(gene.start) + marginLeftGb.value
        const y = yScale.value(-0.75) - marginBottomGb.value
    
        return {
          ...gene,
          width,
          height,
          x,
          y
        }
      })
      return newData
    })
    
    function drawGene({ width, height, strand }) {
      const context = d3.path()
      const halfHeight = height / 2
      const isWidthLonger = halfHeight < width
      if (strand < 0) {
        context.moveTo(0, halfHeight)
        if (isWidthLonger) context.lineTo(halfHeight, 0)
        context.lineTo(width, 0)
        context.lineTo(width, height)
        if (isWidthLonger) context.lineTo(halfHeight, height)
        context.closePath()
      } else if (strand > 0) {
        context.moveTo(0, 0)
        if (isWidthLonger) context.lineTo(width - halfHeight, 0)
        context.lineTo(width, halfHeight)
        if (isWidthLonger) context.lineTo(width - halfHeight, height)
        context.lineTo(0, height)
        context.closePath()
      }
      else {
        context.moveTo(0, 0)
        context.lineTo(width, 0)
        context.lineTo(width, height)
        context.lineTo(0, height)
        context.closePath()
      }
      return context
    }
    
    function positionText(selection) {
      selection.each(function (d) {
        const textWidth = this.clientWidth
        if (d.width < 10) {
          d3.select(this)
            .text('')
        }
        // else {
    
        const halfW = d.width / 2
        const halfTw = textWidth / 2
        const k = d.height / 8
        const x = d.strand > 0 ? halfW - halfTw - k : halfW - halfTw + k
        d3.select(this)
          .attr("transform", `translate(${halfW},45) rotate(45)`)
        // }
      })
    }
    
    function proteinText(item) {
      if (item.isHmmerHit && displayHmmerHits.value || item.isDefenseSystem) {
        return `${item.gene_name} / ${item.name}`
      }
    
    }
    
    function proteinTitle(item) {
      let title = `name=${item.name} | length=${item.length}`
    
      if (item.isHmmerHit && !item.isDefenseSystem) {
        title = `gene name=${item.gene_name} | ${title}`
      }
      if (item.isDefenseSystem) {
        title = `system=${item.defenseSystem} | ${title}`
      }
    
      return title
    }
    
    const maxLabelSize = ref(38)
    
    function proteinTextTrunc(item) {
      const text = proteinText(item)
      return (text && text.length > maxLabelSize.value) ? text.slice(0, maxLabelSize.value - 1) + '...' : text;
    }
    
    
    function drawGenes(genesSelection) {
      genesSelection
        .selectAll("g.gene") // get all "existing" lines in svg
        .data(computedData.value) // sync them with our data
        .join(
          enter => {
            const g = enter.append("g")
              .classed("gene", true);
            g.append("path").classed("gene", true)
            g.append("text")
              // .attr("fill", "white")
              .classed("gene-label", true)
              .attr("fill", "currentColor")
              .attr("dominant-baseline", "middle")
            g.append("title")
          },
          update => {
            update
              .attr("transform", d => `translate(${d.x},${d.y})`)
            update.select("path.gene")
              .attr("d", d => drawGene(d).toString())
              .attr("fill", d => {
    
                if (d.isDefenseSystem) return toValue(color)("df")
                if (displayHmmerHits.value && d.isHmmerHit) return toValue(color)("hmmer")
                return toValue(color)("default")
              })
            update.select("text.gene-label").text(proteinTextTrunc).call(positionText)
            update.select("title").text(proteinTitle)
          },
          exit => exit.remove()
        )
    
    }
    
    function draw() {
    
      const throttledZoomed = useThrottleFn((event) => {
        zoomed(event)
      }, 50)
    
    
      function zoomed(event) {
        const { transform } = event
        const zx = transform.rescaleX(xScale.value);
        domain.value = zx.domain()
        minRange.value = zx.range()[0]
        maxRange.value = zx.range()[1]
      }
    
    
      const svg = d3.select(svgRef.value);
      const zoom = d3.zoom()
        .scaleExtent([0.5, 32])
        .on("zoom", throttledZoomed);
      const xAxis = d3.axisBottom(xScale.value)
      let gx = svg.select("g.xaxis")
      if (gx.empty()) {
        gx = svg.append("g").classed("xaxis", true)
    
      }
      gx
        .attr("transform", `translate(${marginLeftGb.value},${height.value - 18})`)
        .call(xAxis)
    
      let gxTitle = gx.select("text.x-axis-title")
      if (gxTitle.empty()) {
        gxTitle = gx.append("text")
          .classed("x-axis-title", true)
          .attr("text-anchor", "end")
          .attr("fill", "currentColor")
          .html(() => `${computedXUnit.value} &rarr;`)
      }
      gxTitle
        .attr("x", innerWidth.value)
        .attr("y", - 10)
    
    
    
    
      let gGenes = svg.select("g.genes")
      if (gGenes.empty()) {
        gGenes = svg.append("g").classed("genes", true)
      }
      gGenes.call(drawGenes, xScale, yScale)
      svg.call(zoom).call(zoom.transform, d3.zoomIdentity);
    
    
    
    }
    function getResultArchiveUrl(analysisId: number) {
      return joinURL(runtimeConfig.public.dfApiPrefix, `/analysis/${analysisId}/results-archive`)
    }
    
    onMounted(() => {
    
      draw()
    })
    
    watchEffect(() => {
      xScale.value = d3.scaleLinear()
        .domain(domain.value) // input values...
        .range([minRange.value, innerWidth.value])
      draw()
    })
    
    
    const selectedResult = ref(null);
    
    
    
    const { data: analysis, refresh, pending, error } = await useAPI<Analysis>(`/analysis/${analysisId.value}`, {
      key: `analysis-${analysisId.value}`,
    });
    
    if (error.value) {
      throw createError({ statusCode: 404, statusMessage: 'No analysis found' })
    }
    
    
    useHead({
      title: analysis.value?.name ?? 'analysis',
    })
    
    
    </script>
    
    <template>
      <v-card v-if="analysis" flat color="transparent">
    
        <v-app-bar color="background">
          <v-breadcrumbs :items="breadcrumbItems"></v-breadcrumbs>
        </v-app-bar>
    
        <v-card>
          <v-toolbar density="compact" class="pr-2">
            <v-toolbar-title>{{ analysis.name }}</v-toolbar-title>
            <v-btn color="primary" prepend-icon="mdi-download" :href="getResultArchiveUrl(analysis.id)">Download
              all results</v-btn>
            <v-chip color="primary" rounded>{{ new Date(analysis.create_time).toLocaleString() }}</v-chip>
            <template v-if="analysis.percentage_done !== 100 && analysis.stderr === ''" #extension>
              <v-row>
                <v-col cols="12">
                  <v-card flat color="transparent">
                    <v-card-text>
                      <v-progress-linear indeterminate color="primary"></v-progress-linear>
                    </v-card-text>
                  </v-card>
                </v-col>
              </v-row>
            </template>
          </v-toolbar>
    
          <template v-if="analysis.stderr !== ''">
            <v-card color="error" variant="tonal" class="my-2">
              <v-card-title>Standard error</v-card-title>
              <v-card-text>
                <pre> {{ analysis.stderr }} </pre>
              </v-card-text>
            </v-card>
          </template>
    
          <template v-else>
            <v-card-text>
              <div ref="gbContainer">
                <v-card flat color="transparent">
                  <v-toolbar variant="flat" density="compact" color="transparent">
                    <v-spacer></v-spacer><v-toolbar-items>
                      <v-switch v-model="displayHmmerHits" color="primary" label="Display HMM-only hits
                    " class="mr-2"></v-switch>
                    </v-toolbar-items></v-toolbar>
    
                  <svg ref="svgRef" :width="computedWidth" :height="height">
                    <g class="x-axis" />
                  </svg>
                </v-card>
              </div>
            </v-card-text>
            <v-card-text>
              <v-btn-toggle v-model="selectedResult" rounded="0" color="primary" group>
                <v-btn value="systems" :to="`/analyses/${analysis.id}/systems`">
    
                  Systems
                </v-btn>
                <v-btn value="genes" exact :to="`/analyses/${analysis.id}/genes`">
                  Genes
                </v-btn>
                <v-btn value="hmmer" :to="`/analyses/${analysis.id}/hmmer`">
                  Hmmer
                </v-btn>
              </v-btn-toggle>
            </v-card-text>
          </template>
          <NuxtPage />
        </v-card>
      </v-card>
    </template>