Skip to content
Snippets Groups Projects
Select Git revision
  • c3c41b916bb6dede03f0449dbbc599a64b13aa70
  • main default protected
  • dev
  • sidecar-to-clean-uploaded-files
  • supabase
  • serve-django-static
  • FastAPI
  • v0.1.0
8 results

SubmitAnalysis.vue

Blame
  • RefseqDb.vue 24.64 KiB
    <script setup lang="ts">
    import * as d3 from "d3";
    
    import * as Plot from "@observablehq/plot";
    import PlotFigure from "~/components/PlotFigure";
    import { useDisplay } from "vuetify";
    
    import { ServerDbTable } from "#components"
    import { useSerialize } from "@/composables/useSerialize";
    import { useRasterize } from "@/composables/useRasterize";
    import { useDownloadBlob } from '@/composables/useDownloadBlob';
    
    import type { SortItem, AutocompleteMeiliFacetProps } from "@/components/ServerDbTable.vue"
    import type { ComponentPublicInstance } from 'vue'
    
    const sortBy: Ref<SortItem[]> = ref([{ key: 'type', order: "asc" }])
    const itemValue = ref("id");
    const { width } = useDisplay();
    const dbName = ref("refseq")
    const taxonomyFacet = ref<Record<string, any> | undefined>(undefined)
    const cellPlotMargin = ref({
        marginLeft: 150,
        marginBottom: 200,
        marginTop: 0,
        marginRight: 50
    })
    
    const client = useMeiliSearchRef()
    
    onMounted(async () => {
        try {
            const data = await client.index(toValue(dbName)).search("", {
                facets: ["*"],
                filter: [],
                page: 1,
                hitsPerPage: 25,
            })
            autocompleteMeiliFacetsProps.value.facetDistribution = data?.facetDistribution
        } catch (error) {
        }
        await getAllHits({
            index: toValue(dbName), query: "", params: {
                facets: ["*"],
                filter: [],
                limit: 500000,
                sort: ["type:asc"]
    
            }
        })
        try {
            const taxo = await client.index("refseqtaxo").search("", {
                facets: ["*"],
                filter: [],
                page: 1,
                hitsPerPage: 25,
            })
            taxonomyFacet.value = taxo?.facetDistribution
        } catch (error) {
    
        }
    })
    
    const { serialize } = useSerialize()
    const { rasterize } = useRasterize()
    const { download } = useDownloadBlob()
    
    const autocompleteMeiliFacetsProps = ref<AutocompleteMeiliFacetProps>({
        db: toValue(dbName),
        facets: [
            { title: "Defense System", type: "subheader" },
            { title: "System", value: "type", type: "facet", icon: "i-tabler:virus-off", },
            { title: "Subsystem", value: "subtype", type: "facet", icon: "i-tabler:virus-off" },
            { type: "divider" },
            { title: "Taxonomy", type: "subheader" },
            { title: "Superkingdom", value: "Superkingdom", type: "facet", icon: "i-tabler:binary-tree" },
            { title: "Phylum", value: "phylum", type: "facet", icon: "i-tabler:binary-tree" },
            { title: "Order", value: "order", type: "facet", icon: "i-tabler:binary-tree" },
            { title: "Family", value: "family", type: "facet", icon: "i-tabler:binary-tree" },
            { title: "Genus", value: "genus", type: "facet", icon: "i-tabler:binary-tree" },
            { title: "Species", value: "species", type: "facet", icon: "i-tabler:binary-tree" },
            { type: "divider" },
            { title: "Replicon", value: "replicon", type: "facet", icon: "mdi-dna", },
    
        ],
        facetDistribution: undefined
    })
    const computedAutocompleteMeiliFacetsProps = computed(() => {
        const toValFacetDistribution = toValue(autocompleteMeiliFacetsProps).facetDistribution
        const toValFacets = toValue(autocompleteMeiliFacetsProps).facets
        if (toValFacetDistribution !== undefined && toValFacets !== undefined) {
            return {
                ...toValue(autocompleteMeiliFacetsProps), facets: toValFacets.map(facet => {
                    if (facet.type === "facet") {
                        const count = toValFacetDistribution?.[facet.value] ? Object.keys(toValFacetDistribution[facet.value]).length : undefined
    
                        return count ? { ...facet, count } : { ...facet }
                    }
                    else {
                        return { ...facet }
                    }
                })
            }
        }
        else {
            return toValue(autocompleteMeiliFacetsProps)
        }
    
    
    })
    
    const availableTaxo: Ref<string[]> = ref([
        "species",
        "genus",
        "family",
        "order",
        "class",
        "phylum",
        "Superkingdom"
    ]);
    
    const scaleTypes = ref<string[]>(['linear', 'sqrt', 'symlog'])
    const selectedTaxoRank = ref<"species" | "genus" | "family" | "order" | "class" | "phylum" | "Superkingdom">("Superkingdom");
    
    const headers = ref([
        {
            title: "System",
            key: "type",
            fixed: true
        },
        { title: "Assembly", key: "Assembly" },
        { title: "Replicon", key: "replicon" },
        {
            title: "Subsystem",
            key: "subtype",
        },
        {
            title: "Accessions",
            key: "accession_in_sys",
            sortable: false
        }
    ])
    
    const fullWidth = computed(() => {
        return layoutPlot.value === 'fullwidth'
    })
    const computedHeaders = computed(() => {
        return [...headers.value, ...availableTaxo.value.map(taxo => {
            return {
                title: capitalize(taxo),
                key: taxo
            }
        })]
    })
    const { result: msResult } = useMeiliSearch(toValue(dbName))
    
    const computedWidth = computed(() => {
        const currentWidth = fullWidth.value ? width.value : width.value / 2
        return Math.max(currentWidth, 550);
    });
    
    const cellPlotComputedDimension = computed(() => {
        const { marginLeft, marginBottom, marginRight, marginTop } = toValue(cellPlotMargin)
        const toValWidth = toValue(width)
        const widthFixCell = countSystem.value * 50 + marginLeft + marginRight
        const heigthFix = countClade.value * 50 + marginTop + marginBottom
        if (widthFixCell > toValWidth) {
            return { width: toValWidth - marginLeft - marginRight, height: undefined }
        } else {
            return { width: widthFixCell, height: heigthFix }
        }
    
    })
    
    const allHits: Ref<Record<string, any> | undefined> = ref(undefined)
    
    
    const pendingAllHits = ref(false)
    async function getAllHits(params: { index: string, params: Record<string, any>, query: string }) {
        if (!params?.params?.filter || params?.params?.filter?.length === 0) {
            selectedTaxoRank.value = "Superkingdom"
    
        }
        // if (params.index === toValue(dbName)) {
    
        pendingAllHits.value = true
        try {
            const data = await client
                .index("refseqsanitized")
                .search("", {
                    ...params.params,
                    'attributesToRetrieve': ['type', 'Assembly', ...toValue(availableTaxo)]
                })
    
            allHits.value = data
        } finally {
            pendingAllHits.value = false
    
        }
    
        // }
    }
    
    
    
    const defaultDataTableServerProps = ref({
        showExpand: false
    })
    
    const dataTableServerProps = computed(() => {
        return {
            ...defaultDataTableServerProps.value,
            headers: computedHeaders.value,
            itemValue: itemValue.value
        }
    })
    const defaultBarPlotOptions = computed(() => {
    
        return {
            x: { label: null, tickRotate: 45 },
            y: { grid: true, clamp: true },
    
            // height: plotHeight.value + 100,
        }
    })
    
    
    // system distri
    
    const computedSystemDistribution = computed(() => {
        if (toValue(msResult)?.facetDistribution?.type) {
            return Object.entries(toValue(msResult).facetDistribution.type)
                .map(([key, value]) => {
                    return {
                        type: key,
                        count: value
                    }
                }).sort()
        } else { return [] }
    
    })
    const computedDistriSystemOptions = computed(() => {
        // const toValNormalizePerAssembly = toValue(normalizePerAssembly)
    
        return {
            ...defaultBarPlotOptions.value,
            title: "System distribution",
            marginBottom: 100,
            y: { ...defaultBarPlotOptions.value.y, type: toValue(scaleType), label: "Count" },
            x: { ...defaultBarPlotOptions.value.x, label: "Systems" },
            width: computedWidth.value,
            marks: [
                Plot.barY(
                    toValue(computedSystemDistribution),
                    {
                        y: "count", x: 'type', tip: true,
                        sort: { x: "-y" },
                    },
                ),
            ],
        };
    });
    
    
    // Taxo distri
    const computedTaxonomyDistribution = computed(() => {
        const toValSelectedTaxoRank = toValue(selectedTaxoRank)
        const toValFacetsPerRank = toValue(msResult)?.facetDistribution?.[toValSelectedTaxoRank]
        if (toValFacetsPerRank) {
            return Object.entries(toValFacetsPerRank).map(([key, value]) => {
                return {
                    [toValSelectedTaxoRank]: key,
                    count: value
                }
            }).sort()
        } else { return [] }
    
    })
    
    const computedDistriTaxoOptions = computed(() => {
        return {
            ...defaultBarPlotOptions.value,
            title: "Taxonomic distribution",
            marginBottom: 100,
            x: { ...defaultBarPlotOptions.value.x, label: selectedTaxoRank.value },
            y: { ...defaultBarPlotOptions.value.y, type: toValue(scaleType), label: "Count" },
            width: computedWidth.value,
            marks: [
                Plot.barY(
                    toValue(computedTaxonomyDistribution),
                    {
                        y: "count",
                        x: selectedTaxoRank.value,
                        tip: true,
                        // fill: "#6750a4",
                        sort: { x: "-y" },
                    }
                ),
            ],
        };
    });
    
    
    function capitalize(name: string) {
        const [first, ...rest] = name
        return first.toUpperCase() + rest.join('').toLowerCase();
    }
    
    function namesToCollapsibleChips(names: string[]) {
        return names.filter((it) => it !== "").map(it => ({ title: it }))
    }
    
    function namesToAccessionChips(names: string[]) {
        return namesToCollapsibleChips(names).map(it => {
            return {
                ...it,
                href: new URL(it.title, "https://www.ncbi.nlm.nih.gov/protein/").toString()
            }
        })
    }
    const systemPanel: Ref<string[]> = ref([])
    const layoutPlot: Ref<string> = ref("grid")
    function compareBySystem(a, b) {
        if (a === "No system found") {
            return -1
        }
        if (b === "No system found") {
            return 1
        }
        return a - b
    
    }
    
    const sortedCellDomain = computed(() => {
        const toValueAllHits = toValue(allHits)
        if (toValueAllHits && toValueAllHits?.hits?.length > 0) {
            return toValueAllHits.hits.map(d => d.type).sort(compareBySystem)
        }
    })
    const binPlotOptions = computed(() => {
        const { marginLeft, marginBottom } = toValue(cellPlotMargin)
        const { height } = toValue(cellPlotComputedDimension)
    
        return {
            marginLeft,
            marginBottom,
            padding: 0,
            grid: true,
            aspectRatio: height ? undefined : 1,
            x: { tickRotate: 90, label: "Systems", domain: toValue(sortedCellDomain) },
            color: { scheme: "plasma", legend: true, label: `Proportion per ${selectedTaxoRank.value}`, domain: [0, 100] },
        }
    })
    
    const countSystem = computed(() => {
        const toValueAllHits = toValue(allHits)
        const data = toValueAllHits?.hits ?? []
        const setSystem = new Set(data.map(d => d.type))
        return setSystem.size
    })
    
    const countClade = computed(() => {
        const toValueAllHits = toValue(allHits)
        const data = toValueAllHits?.hits ?? []
        const setSystem = new Set(data.map(d => d[selectedTaxoRank.value]))
        return setSystem.size
    })
    
    const binPlotGroup = computed(() => {
        return Plot.group(
            {
                label: (d) => d.fill,
                fill: {
                    /**
                     * 
                     * @param I is the list of element index that are par of the same group (cell)
                     * @param X is the list of all elements 
                     */
                    reduceIndex: function (I, X) {
                        const toValTaxonomyFacet = toValue(taxonomyFacet)
                        if (toValTaxonomyFacet !== undefined) {
                            const clade = X[I[0]][selectedTaxoRank.value]
                            const system = X[I[0]].type
                            // Get the list of all the items for this group (same cell)
                            // and group them per type and assembly
                            const itemsPerGroup = d3.rollup(I.map(index => {
                                return X[index]
                            }), D => D.length, d => d.type, d => d.Assembly)
                            const countForClade = toValTaxonomyFacet[selectedTaxoRank.value][clade]
                            const frequency = (itemsPerGroup.get(system).size / countForClade) * 100
                            return frequency
                        }
                        return I.length
                    }
                },
            },
            {
                x: "type",
                y: selectedTaxoRank.value,
                tip: true,
                inset: 0.5,
                sort: { y: "fill" }
            }
    
        )
    })
    
    
    
    
    const binPlotDataOptions = computed(() => {
        const toValueAllHits = toValue(allHits)
        const toValBinPlotGroup = toValue(binPlotGroup)
        const data = toValueAllHits?.hits ?? []
        const plotCellMark = Plot.cell(data, toValBinPlotGroup)
        const { width, height } = toValue(cellPlotComputedDimension)
    
        const dim = height ? { width, height } : { width }
    
        return toValueAllHits?.hits?.length > 0 ? {
            ...binPlotOptions.value,
            ...dim,
            title: `Proportion of genomes with defense system X per ${selectedTaxoRank.value} taxonomic rank`,
            color: {
                ...binPlotOptions.value.color,
                type: scaleType.value,
                tickFormat: '~s',
                ticks: scaleType.value === 'symlog' ? 3 : 5,
            },
            marks: [plotCellMark],
        } : null
    })
    
    
    const scaleType = ref("linear")
    const systemsDistributionPlot = ref<ComponentPublicInstance | null>(null)
    const taxonomicDistributionPlot = ref<ComponentPublicInstance | null>(null)
    const heatmapPlot = ref<ComponentPublicInstance | null>(null)
    function downloadSvg(component: ComponentPublicInstance | null, filename: string) {
        const blob = toValue(serialize(toValue(component)))
        if (blob !== undefined) {
            download(blob, filename)
        }
    }
    
    async function downloadPng(component: ComponentPublicInstance | null, filename: string) {
        const blob = await rasterize(toValue(component), filename)?.then((blob) => {
            download(blob, filename)
        })
    
    }
    
    </script>
    
    <template>
        <v-card flat class="mb-2" color="transparent">
            <v-card color="transparent" flat>
                <v-expansion-panels v-model="systemPanel" class="my-2" density="compact" multiple>
                    <v-expansion-panel elevation="3" value="barplot">
                        <v-expansion-panel-title color="grey-lighten-4"><v-icon color="primary"
                                class="mr-2">mdi-chart-bar</v-icon>Systems - Taxonomic</v-expansion-panel-title>
                        <v-expansion-panel-text>
                            <v-card flat color="transparent">
                                <v-toolbar flat color="transparent" density="compact">
                                    <v-btn-toggle v-model="layoutPlot" density="compact" rounded="false" variant="text"
                                        color="primary" mandatory class="mx-2">
                                        <v-btn icon="md:grid_view" value="grid"></v-btn>
                                        <v-btn icon="md:view_agenda" value="fullwidth"></v-btn>
                                    </v-btn-toggle>
                                    <v-select v-model="scaleType" class="mx-2" density="compact" :items="scaleTypes"
                                        label="Scale Type" hide-details="auto"></v-select>
                                    <v-select v-model="selectedTaxoRank" :items="availableTaxo" density="compact"
                                        label="Select taxonomic rank" hide-details="auto" class="mx-2"></v-select>
    
                                </v-toolbar>
                                <v-row align="start">
                                    <v-col :cols="fullWidth ? 12 : 6">
                                        <v-card variant="flat">
                                            <v-toolbar density="compact" color="transparent">
                                                <v-spacer></v-spacer>
    
                                                <v-menu>
                                                    <template v-slot:activator="{ props }">
                                                        <v-btn color="primary" prepend-icon="md:download" v-bind="props">
                                                            export
                                                        </v-btn>
                                                    </template>
                                                    <v-list>
                                                        <v-list-item value="svg">
                                                            <v-list-item-title
                                                                @click="downloadSvg(systemsDistributionPlot, 'df-systems-distribution.svg')">to
                                                                svg</v-list-item-title>
    
                                                        </v-list-item>
                                                        <v-list-item value="png">
                                                            <v-list-item-title
                                                                @click="downloadPng(systemsDistributionPlot, 'df-systems-distribution.png')">to
                                                                png</v-list-item-title>
                                                        </v-list-item>
                                                    </v-list>
                                                </v-menu>
                                            </v-toolbar>
    
                                            <PlotFigure ref="systemsDistributionPlot"
                                                :options="unref(computedDistriSystemOptions)" defer></PlotFigure>
                                        </v-card>
                                    </v-col>
                                    <v-col :cols="fullWidth ? 12 : 6">
                                        <v-card variant="flat">
                                            <v-toolbar density="compact" color="transparent">
                                                <v-spacer></v-spacer>
                                                <v-menu>
                                                    <template v-slot:activator="{ props }">
                                                        <v-btn color="primary" prepend-icon="md:download" v-bind="props">
                                                            export
                                                        </v-btn>
                                                    </template>
                                                    <v-list>
                                                        <v-list-item value="svg">
                                                            <v-list-item-title
                                                                @click="downloadSvg(taxonomicDistributionPlot, 'df-taxonomic-distribution.svg')">to
                                                                svg</v-list-item-title>
    
                                                        </v-list-item>
                                                        <v-list-item value="png">
                                                            <v-list-item-title
                                                                @click="downloadPng(taxonomicDistributionPlot, 'df-taxonomic-distribution.png')">to
                                                                png</v-list-item-title>
                                                        </v-list-item>
                                                    </v-list>
                                                </v-menu>
                                            </v-toolbar>
                                            <PlotFigure ref="taxonomicDistributionPlot" defer
                                                :options="unref(computedDistriTaxoOptions)"></PlotFigure>
                                        </v-card>
                                    </v-col>
                                </v-row>
                            </v-card>
    
                        </v-expansion-panel-text>
                    </v-expansion-panel>
                    <v-expansion-panel elevation="3" value="matrix">
                        <v-expansion-panel-title color="grey-lighten-4">
    
                            <v-icon v-if="pendingAllHits === false" color="primary" class="mr-2">mdi-data-matrix
                            </v-icon>
                            <v-progress-circular v-else indeterminate color="primary" :size="22" class="mr-2"
                                :width="3"></v-progress-circular>
                            Heatmap </v-expansion-panel-title>
                        <v-expansion-panel-text>
                            <v-card v-if="pendingAllHits === false" flat color="transparent">
                                <v-toolbar flat color="transparent" density="compact">
                                    <v-select v-model="scaleType" class="mx-2" density="compact" :items="scaleTypes"
                                        label="Scale Type" hide-details="auto"></v-select>
                                    <v-select v-model="selectedTaxoRank" :items="availableTaxo" density="compact"
                                        label="Select taxonomic rank" hide-details="auto" class="mx-2"></v-select>
    
                                </v-toolbar>
                                <v-card v-if="toValue(binPlotDataOptions) !== null" variant="flat">
                                    <v-toolbar density="compact" color="transparent">
                                        <v-spacer></v-spacer>
                                        <v-menu>
                                            <template v-slot:activator="{ props }">
                                                <v-btn color="primary" prepend-icon="md:download" v-bind="props">
                                                    export
                                                </v-btn>
                                            </template>
                                            <v-list>
                                                <v-list-item value="svg">
                                                    <v-list-item-title
                                                        @click="downloadSvg(heatmapPlot, 'df-heatmap-systems-taxonomy.svg')">to
                                                        svg</v-list-item-title>
    
                                                </v-list-item>
                                                <v-list-item value="png">
                                                    <v-list-item-title
                                                        @click="downloadPng(heatmapPlot, 'df-heatmap-systems-taxonomy.png')">to
                                                        png</v-list-item-title>
                                                </v-list-item>
                                            </v-list>
                                        </v-menu>
                                    </v-toolbar>
                                    <PlotFigure ref="heatmapPlot" :options="unref(binPlotDataOptions)" defer>
                                    </PlotFigure>
                                </v-card>
                            </v-card>
                            <v-card v-else flat color="transparent">
                                <v-skeleton-loader type="card" :loading="pendingAllHits" height="400"></v-skeleton-loader>
                            </v-card>
                        </v-expansion-panel-text>
                    </v-expansion-panel>
                </v-expansion-panels>
                <ServerDbTable title="RefSeq" :sortBy="sortBy"
                    :autocomplete-meili-facets-props="computedAutocompleteMeiliFacetsProps"
                    :data-table-server-props="dataTableServerProps" @refresh:search="(params) => getAllHits(params)">
    
                    <template #[`item.accession_in_sys`]="{ item }">
                        <CollapsibleChips :items="namesToAccessionChips(item.accession_in_sys)">
                        </CollapsibleChips>
                    </template>
                    <template #[`item.type`]="{ item }">
                        <v-chip color="info" link size="small" :to="`/defense-systems/${item.type.toLowerCase()}`"
                            target="_blank"> {{
                    item.type }}
                        </v-chip>
                    </template>
                    <template #[`item.Assembly`]="{ item }">
                        <v-chip color="info" link size="small"
                            :href="`https://www.ncbi.nlm.nih.gov/datasets/genome/${item.Assembly}`" target="_blank"> {{
                    item.Assembly }}
                        </v-chip>
                    </template>
                    <template #[`item.replicon`]="{ item }">
                        <v-chip color="info" link size="small"
                            :href="`https://www.ncbi.nlm.nih.gov/nuccore/${item.replicon}`" target="_blank"> {{
                    item.replicon }}
                        </v-chip>
                    </template>
    
                    <!--  -->
                    <template #[`item.subtype`]="{ item }">
                        <v-chip color="info" link size="small" :to="`/defense-systems/${item.type.toLowerCase()}`"
                            target="_blank"> {{
                            item.subtype }}
                        </v-chip>
                    </template>
                </ServerDbTable>
    
    
            </v-card>
    
        </v-card>
    </template>