Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • mdm-lab/wiki
  • hvaysset/wiki
  • jsousa/wiki
  • tclabby/wiki
4 results
Show changes
Commits on Source (86)
Showing
with 828 additions and 525 deletions
......@@ -12,7 +12,7 @@ workflow:
# Functions that should be executed before the build script is run
variables:
HELM_VERSION: "3.9.3"
HELM_VERSION: "3.13.3"
IMAGE_NAME: "df-wiki"
# dev
HOST_DEV: 'defense-finder.dev.pasteur.cloud'
......@@ -31,6 +31,7 @@ cache:
stages:
- delete-release
- build-df-cli
- lint
......@@ -39,9 +40,9 @@ stages:
- update-meilisearch-indexes
- get-meili-key
- build
- build-wiki
# - build-wiki
- deploy
# - load-website
.docker-login: &docker-login
- i=0; while [ "$i" -lt 12 ]; do docker info && break; sleep 5; i=$(( i + 1 )) ; done
......@@ -210,7 +211,7 @@ lint:
--host ${MEILI_HOST}
--key ${MEILI_MASTER_KEY}
update
--file data/all_predictions_statistics.csv
--file data/all_predictions_statistics_clean.csv
--document structure
- >
df-wiki-cli
......@@ -353,25 +354,94 @@ build:prod:wiki:
- if: $CI_COMMIT_BRANCH == "main"
# build-wiki:dev:
# stage: build-wiki
# needs:
# - set-meili-env:dev
# rules:
# - if: $CI_COMMIT_BRANCH != "main"
# image: node:21.1-bookworm-slim
# variables:
# NODE_OPTIONS: --max_old_space_size=12288
# NUXT_APP_BASE_URL: /wiki/
# NUXT_PUBLIC_MEILISEARCH_CLIENT_HOST_URL: ${MEILI_HOST}
# NUXT_PUBLIC_MEILISEARCH_CLIENT_SEARCH_API_KEY: ${MEILI_API_KEY}
# NUXT_PUBLIC_MEILI_HOST: ${MEILI_HOST}
# NUXT_PUBLIC_MEILI_API_KEY: ${MEILI_API_KEY}
# before_script:
# - npm install
# script:
# - npm run generate
# artifacts:
# paths:
# - .output/public
# build-wiki:prod:
# stage: build-wiki
# rules:
# - if: $CI_COMMIT_BRANCH == "main"
# needs:
# - set-meili-env:prod
# - get-zotero
# image: node:21.1-bookworm-slim
# variables:
# NODE_OPTIONS: --max_old_space_size=12288
# NUXT_APP_BASE_URL: /wiki/
# NUXT_PUBLIC_MEILISEARCH_CLIENT_HOST_URL: ${MEILI_HOST}
# NUXT_PUBLIC_MEILISEARCH_CLIENT_SEARCH_API_KEY: ${MEILI_API_KEY}
# NUXT_PUBLIC_MEILI_HOST: ${MEILI_HOST}
# NUXT_PUBLIC_MEILI_API_KEY: ${MEILI_API_KEY}
# before_script:
# - npm install
# script:
# - npm run generate
# artifacts:
# paths:
# - .output/public
# load-website:dev:
# image: harbor.pasteur.fr/kube-system/helm-kubectl:$HELM_VERSION
# stage: load-website
# needs:
# - build-wiki:dev
# - deploy:dev
# variables:
# NAMESPACE: "defense-finder-dev"
# environment:
# name: "k8sdev-01"
# rules:
# - if: $CI_COMMIT_BRANCH != "main"
# script:
# - kubectl --namespace ${NAMESPACE} wait pod -l "app.kubernetes.io/name=df-wiki" --for condition=Ready --timeout=600s
# - echo "Le pod est ready"
# - WIKI_POD=$(kubectl --namespace ${NAMESPACE} get pods -l "app.kubernetes.io/name=df-wiki" --output jsonpath='{.items[0].metadata.name}')
# - kubectl --namespace ${NAMESPACE} cp .output/public/ ${WIKI_POD}:/website
# - |
# kubectl --namespace ${NAMESPACE}
# exec ${WIKI_POD} -- bash -c 'cd /structure-data/sanitized-dump && find * -type d -exec sh -c "for d in "$@"; do (cd "/usr/share/nginx/html/$d"; cp --archive --recursive --symbolic-link /structure-data/sanitized-dump/$d/* .) done" argv0 {} +'
# load-website:prod:
# image: harbor.pasteur.fr/kube-system/helm-kubectl:$HELM_VERSION
# stage: load-website
# needs:
# - build-wiki:prod
# - deploy:prod
# environment:
# name: "k8sprod-02"
# variables:
# NAMESPACE: "defense-finder-prod"
# rules:
# - if: $CI_COMMIT_BRANCH == "main"
# script:
# - kubectl --namespace ${NAMESPACE} wait pod -l "app.kubernetes.io/name=df-wiki" --for condition=Ready --timeout=600s
# - echo "Le pod est ready"
# - WIKI_POD=$(kubectl --namespace ${NAMESPACE} get pods -l "app.kubernetes.io/name=df-wiki" --output jsonpath='{.items[0].metadata.name}')
build-wiki:
stage: build-wiki
image: node:21.1-bookworm-slim
variables:
NODE_OPTIONS: --max_old_space_size=12288
NUXT_APP_BASE_URL: /wiki/
NUXT_PUBLIC_MEILISEARCH_CLIENT_HOST_URL: ${MEILI_HOST}
NUXT_PUBLIC_MEILISEARCH_CLIENT_SEARCH_API_KEY: ${MEILI_API_KEY}
NUXT_PUBLIC_MEILI_HOST: ${MEILI_HOST}
NUXT_PUBLIC_MEILI_API_KEY: ${MEILI_API_KEY}
before_script:
- npm install
script:
- npm run generate
artifacts:
paths:
- .output/public
# - kubectl --namespace ${NAMESPACE} cp .output/public/ ${WIKI_POD}:/website
# - kubectl --namespace ${NAMESPACE} cp scripts/copy-structure-data.sh ${WIKI_POD}:/structure-data/sanitized-dump
# - kubectl --namespace ${NAMESPACE} exec ${WIKI_POD} -- bash -c 'cd /structure-data/sanitized-dump && bash copy-structure-data.sh'
......@@ -397,6 +467,15 @@ build-wiki:
--set env="${ENV:-development}"
--values deploy/df-wiki/values.yaml
--values deploy/df-wiki/values.${ENV:-development}.yaml
after_script:
- kubectl --namespace ${KUBE_NAMESPACE} wait pod -l "app.kubernetes.io/name=df-wiki" --for condition=Ready --timeout=600s
- echo "Wiki pod is ready"
- WIKI_POD=$(kubectl --namespace ${KUBE_NAMESPACE} get pods -l "app.kubernetes.io/name=df-wiki" --output jsonpath='{.items[0].metadata.name}')
- echo ${WIKI_POD}
- kubectl --namespace ${KUBE_NAMESPACE} cp scripts/copy-structure-data.sh ${WIKI_POD}:/structure-data/sanitized-dump
- kubectl --namespace ${KUBE_NAMESPACE} exec ${WIKI_POD} -- bash -c 'cd /structure-data/sanitized-dump && bash copy-structure-data.sh'
deploy:dev:
extends: .deploy
......@@ -464,3 +543,5 @@ delete-helm-release:prod:
script:
- echo "Removing $CI_PROJECT_NAME-$CI_ENVIRONMENT_NAME"
- helm delete -n ${NAMESPACE} $CI_PROJECT_NAME-$CI_ENVIRONMENT_NAME
......@@ -148,7 +148,7 @@ export default {
const options = {
...(method === "plot" && {
marks: this.mark == null ? [] : [this.mark],
width: 688 // better default for VitePress
// width: 688 // better default for VitePress
}),
...this.options,
className: "plot"
......
<script setup lang="ts">
// import type { FacetDistribution } from "meilisearch";
import { useCsvDownload } from "@/composables/useCsvDownload"
import { useSlots } from 'vue'
import { useDisplay } from "vuetify";
import * as Plot from "@observablehq/plot";
import PlotFigure from "~/components/PlotFigure";
import * as d3 from "d3";
import { useThrottleFn } from '@vueuse/core'
import { useMeiliSearch } from "#imports"
// import { saveAs } from "file-saver";
import { useFileSystemAccess } from '@vueuse/core'
export interface SortItem {
key: string,
order: boolean | 'asc' | 'desc'
}
export interface NumericalFilter {
id: string
name: string
range: [number, number]
}
export interface NumericalFilterModel extends NumericalFilter {
model: [number, number]
}
export interface Props {
title?: string
db?: string
sortBy?: SortItem[]
facets: MaybeRef<string[]>
numericalFilters?: MaybeRef<string | undefined>
dataTableServerProps: Record<string, any>
columnsToDownload?: MaybeRef<string[] | undefined>
}
export interface FilterItem {
......@@ -32,7 +44,10 @@ export interface FilterItem {
const props = withDefaults(defineProps<Props>(), {
title: '',
db: 'refseq',
columnsToDownload: undefined,
sortBy: () => [{ key: "type", order: "asc" }],
numericalFilters: undefined
});
......@@ -51,18 +66,16 @@ const msFilter: Ref<string | undefined> = ref(undefined)
const page = ref(1)
let loading = ref(false)
const expanded = ref([])
const { height } = useDisplay();
const { height, mobile } = useDisplay();
const minTableHeight = ref(400)
const computedTableHeight = computed(() => {
const computedHeight = height.value - 350
return computedHeight > minTableHeight.value ? computedHeight : minTableHeight.value
})
const plddtRange = ref([0, 100])
// const { pending: pendingDownloadData, downloadCsv } = useCsvDownload(props.db, `df-${props.db}`)
const pendingDownloadData = ref(false)
const filterInputValues = computed(() => {
// console.log("recompouted FILTER value")
if (filterOrSearch.value != null) {
return filterOrSearch.value.filter(({ props }) => props.type !== 'text')
} else {
......@@ -70,24 +83,6 @@ const filterInputValues = computed(() => {
}
})
// const queryInputValue = computed(() => {
// if (filterOrSearch.value !== null) {
// const phrase = filterOrSearch.value
// .filter((f) => {
// return f.props.type === 'text'
// })
// .map((f) => {
// return f.value
// })
// if (phrase.length > 1) {
// return `${phrase.join(" ")}`
// }
// else { return phrase[0] }
// } else {
// return null
// }
// })
const isFilter = computed(() => {
return Array.isArray(filterOrSearch.value)
})
......@@ -125,41 +120,22 @@ watch([paginationParams, msSortBy, page], ([newParams, newSort, newPage]) => {
onMounted(async () => {
searchOrFilter()
emitRefreshRes()
})
const hasPlddt = computed(() => props.db === 'structure')
// Fetch results
const plddtFilter = computed(() => {
const plddtRangeValue = plddtRange.value
if (hasPlddt.value && plddtRangeValue?.length === 2 && (plddtRangeValue[0] !== 0 || plddtRangeValue[1] !== 100)) {
return `plddts ${plddtRangeValue[0]} TO ${plddtRangeValue[1]}`
} else {
return undefined
}
})
const computedFilter = computed(() => {
if (toValue(msFilter)) {
if (toValue(plddtFilter)) {
return `${toValue(msFilter)} AND ${toValue(plddtFilter)}`
}
else {
return toValue(msFilter)
}
} else {
if (toValue(plddtFilter)) {
return `${toValue(plddtFilter)}`
}
else {
return undefined
}
}
return [toValue(msFilter), props.numericalFilters].filter(f => f !== undefined).join(" AND ")
})
watch(computedFilter, () => {
searchOrFilter()
emitRefreshRes()
})
const msError = computed(() => {
if (filterError.value?.type && filterError.value?.message) {
......@@ -167,28 +143,18 @@ const msError = computed(() => {
} else { return false }
})
const throttleSearch = useThrottleFn(async () => { searchOrFilter() }, 300)
async function searchOrFilter(pagination = true) {
const throttleSearch = useThrottleFn(async () => {
searchOrFilter()
emitRefreshRes()
}, 300)
async function searchOrFilter() {
// do something, it will be called at most 1 time per second
try {
loading.value = true
// const q = queryInputValue.value === null ? "" : queryInputValue.value
const q = search.value
emit("refresh:search", {
index: props.db,
query: q,
params: { ...notPaginatedParams.value, filter: toValue(computedFilter), sort: msSortBy.value }
})
if (pagination) {
await msSearch(q, { ...paginationParams.value, filter: toValue(computedFilter), sort: msSortBy.value })
}
else {
await msSearch(q, { ...notPaginatedParams.value, filter: toValue(computedFilter), sort: msSortBy.value })
}
await msSearch(q, { ...paginationParams.value, filter: toValue(computedFilter), sort: msSortBy.value })
} catch (error: any) {
filterError.value = error
console.log(error)
......@@ -199,18 +165,31 @@ async function searchOrFilter(pagination = true) {
}
function emitRefreshRes() {
console.log("emit refresh:search")
const q = search.value
emit("refresh:search", {
index: props.db,
query: q,
params: { ...notPaginatedParams.value, filter: toValue(computedFilter), sort: msSortBy.value }
})
}
function clearFilterOrSearch() {
filterOrSearch.value = null
searchOrFilter()
// searchOrFilter()
// emitRefreshRes()
}
watch(msFilter, async (fos) => {
searchOrFilter()
search.value = ''
// watch(msFilter, async (fos) => {
// searchOrFilter()
// emitRefreshRes()
// search.value = ''
})
// })
const totalHits = computed(() => {
......@@ -234,8 +213,11 @@ watch(filterInputValues, (newSoF) => {
}
})
watch(search, () => { searchOrFilter() })
// watch(plddtRange, () => { searchOrFilter() })
watch(search, () => {
searchOrFilter()
emitRefreshRes()
})
const filterStep = computed(() => {
return filterInputValues.value !== null && filterInputValues.value.length > 0 ? filterInputValues.value?.length % 3 : null
})
......@@ -292,13 +274,7 @@ const autocompleteItems = computed(() => {
}
})
// const canAddTextSearch = computed(() => {
// if (filterOrSearch.value !== null && filterOrSearch.value.length > 0) {
// const lastItem = filterOrSearch.value.slice(-1)[0]
// return lastItem?.props.type === 'value' || lastItem?.props.type === "text"
// }
// return true
// })
function selectItem(item) {
filterOrSearch.value = Array.isArray(filterOrSearch.value) ? [...filterOrSearch.value, item] : [item]
......@@ -314,68 +290,100 @@ function clearSearch() {
search.value = ""
}
async function downloadData() {
pendingDownloadData.value = true
try {
const { data } = await useAsyncMeiliSearch({
index: props.db,
params: { ...toValue(notPaginatedParams), filter: toValue(computedFilter), sort: toValue(msSortBy) },
query: toValue(search),
})
// const groupSortDomain = computed(() => {
// console.log(msResult.value)
// return msResult.value ? d3.groupSort(msResult.value?.hits?.filter((d) => d.phylum), (g) => d3.median(g, (d) => d.phylum), (d) => d.type) : []
// })
useCsvDownload(data, props.columnsToDownload, props.title)
} finally {
pendingDownloadData.value = false
}
}
</script>
<template>
<v-card flat color="transparent">
<v-card-text>
</v-card-text>
<v-card-text>
<v-row>
<v-col cols="5">
<v-text-field v-model="search" label="Search..." hide-details prepend-inner-icon="mdi-magnify"
single-line clearable></v-text-field>
</v-col>
<v-col>
<v-autocomplete ref="autocompleteInput" hide-details v-model:model-value="filterOrSearch"
auto-select-first chips clearable label="Filter results..." :items="autocompleteItems" single-line
item-value="value" item-title="title" multiple return-object prepend-inner-icon="md:search"
@click:appendInner="searchOrFilter" @click:clear="clearFilterOrSearch"
@update:modelValue="() => clearSearch()">
<template #chip="{ props, item, index }">
<v-chip v-bind="props" :text="item.raw.title" :closable="item.props.deletable"
@click:close="item.props.type === deleteOneFilter(index)"></v-chip>
</template>
<template #item="{ props, item }">
<v-list-item v-bind="{ ...props, active: false, onClick: () => selectItem(item) }"
:title="item.title" :subtitle="item.raw?.count ? item.raw.count : ''" :value="props.value">
</v-list-item>
</template>
</v-autocomplete>
</v-col>
</v-row>
<v-row v-if="props.db === 'structure'">
<v-col>
<v-range-slider v-model="plddtRange" density="compact" hide-details="auto" label="pLDDT" step="0.1"
@update:modelValue="throttleSearch()">
<template v-slot:prepend>
<span hide-details single-line type="number" variant="outlined" density="compact"
style="width: 70px">{{ plddtRange[0] }}</span>
</template>
<template v-slot:append>
<span hide-details single-line type="number" variant="outlined" style="width: 70px"
density="compact">{{ plddtRange[1] }}</span>
</template>
</v-range-slider>
</v-col>
</v-row>
<slot name="numerical-filters" :search="throttleSearch"></slot>
</v-card-text>
<v-data-table-server v-if="!msError" v-model:page="page" color="primary" v-bind="dataTableServerProps"
v-model:items-per-page="hitsPerPage" v-model:sortBy="sortByRef" v-model:expanded="expanded" fixed-header
:loading="loading" :items="msResult?.hits ?? []" :items-length="totalHits" density="compact"
:items-per-page-options="itemsPerPage" :height="computedTableHeight" class="elevation-1 mt-2">
<template #top>
<template v-if="mobile">
<v-toolbar> <v-badge :content="totalHits" color="primary" class="mx-2">
<v-btn prepend-icon="md:download" :loading="pendingDownloadData" variant="text" color="primary"
@click="downloadData()">{{
props.title }}
</v-btn>
</v-badge></v-toolbar>
<v-toolbar><v-text-field v-model="search" label="Search..." hide-details="auto"
prepend-inner-icon="mdi-magnify" single-line clearable class="mx-2"></v-text-field></v-toolbar>
<v-toolbar><v-autocomplete ref="autocompleteInput" hide-details v-model:model-value="filterOrSearch"
auto-select-first chips clearable label="Filter results..." :items="autocompleteItems"
single-line item-value="value" item-title="title" multiple return-object
prepend-inner-icon="md:search" @click:appendInner="searchOrFilter" class="mx-2"
@click:clear="clearFilterOrSearch" @update:modelValue="() => clearSearch()">
<template #chip="{ props, item, index }">
<v-chip v-bind="props" :text="item.raw.title" :closable="item.props.deletable"
@click:close="item.props.type === deleteOneFilter(index)"></v-chip>
</template>
<template #item="{ props, item }">
<v-list-item v-bind="{ ...props, active: false, onClick: () => selectItem(item) }"
:title="item.title" :subtitle="item.raw?.count ? item.raw.count : ''"
:value="props.value">
</v-list-item>
</template>
</v-autocomplete></v-toolbar>
</template>
<template v-else>
<v-toolbar>
<v-badge :content="totalHits" color="primary" class="mr-3">
<v-btn prepend-icon="md:download" :loading="pendingDownloadData" variant="text" color="primary"
@click="downloadData()">{{
props.title }}
</v-btn>
</v-badge>
<v-spacer></v-spacer>
<v-card variant="flat" color="transparent" :min-width="400" class="mx-2" :rounded="false">
<v-text-field v-model="search" label="Search..." hide-details="auto"
prepend-inner-icon="mdi-magnify" single-line clearable></v-text-field>
</v-card>
<v-card variant="flat" color="transparent" :min-width="500" class="mx-2" :rounded="false">
<v-autocomplete ref="autocompleteInput" hide-details v-model:model-value="filterOrSearch"
auto-select-first chips clearable label="Filter results..." :items="autocompleteItems"
single-line item-value="value" item-title="title" multiple return-object
prepend-inner-icon="md:search" @click:appendInner="searchOrFilter"
@click:clear="clearFilterOrSearch" @update:modelValue="() => clearSearch()">
<template #chip="{ props, item, index }">
<v-chip v-bind="props" :text="item.raw.title" :closable="item.props.deletable"
@click:close="item.props.type === deleteOneFilter(index)"></v-chip>
</template>
<template #item="{ props, item }">
<v-list-item v-bind="{ ...props, active: false, onClick: () => selectItem(item) }"
:title="item.title" :subtitle="item.raw?.count ? item.raw.count : ''"
:value="props.value">
</v-list-item>
</template>
</v-autocomplete>
</v-card>
</v-toolbar>
</template>
</template>
<template v-for="(slot, index) of Object.keys(slots)" :key="index" v-slot:[slot]="data">
<slot :name="slot" v-bind="data"></slot>
</template>
......
<script setup lang="ts">
import { usePfamStore } from "@/stores/pfam";
export interface Props {
headers: Array<Object>;
systems: Array<Object>;
height?: number;
}
const props = withDefaults(defineProps<Props>(), {
height: 800,
});
const itemsPerParge = ref(25);
const search = ref("");
const sortBy = ref([{ key: "system", order: "asc" }]);
const expanded = ref([]);
function filterOnlyCapsText(value, query, item) {
if (value != null && query != null) {
if (typeof value === "string") {
return value.toString().toLowerCase().indexOf(query.toLowerCase()) !== -1;
}
if (typeof value == "object") {
if (value?.name) {
return (
value.name.toString().toLowerCase().indexOf(query.toLowerCase()) !==
-1
);
}
}
}
return false;
}
const { initPfam } = usePfamStore();
onMounted(() => {
initPfam();
})
</script>
<template>
<v-card>
<v-toolbar density="compact">
<v-toolbar-title>Defense Systems</v-toolbar-title>
<v-text-field v-model="search" density="compact" variant="underlined" prepend-inner-icon="mdi-magnify"
label="Search for defense systems" single-line hide-details class="mx-2" clearable></v-text-field>
</v-toolbar>
<v-data-table-virtual fixed-header :height="height" :items-per-page="itemsPerParge" v-model:sort-by="sortBy"
:headers="props.headers" density="compact" :custom-filter="filterOnlyCapsText" :items="props.systems"
:search="search" item-value="system.name">
<template #[`item.system`]="{ item }">
<v-chip variant="text" link :to="item?.system?.path ? `${item?.system?.path}` : null">{{
item?.system?.name ?? 'None'
}}</v-chip>
</template>
<template #[`item.article`]="{ item }">
<ArticleDoi v-if="item?.article" :doi="item.article.doi" :title="item.article?.title"
:abstract="item.article?.abstract" :divider="false" :enumerate="false" />
</template>
<template #[`item.PFAM`]="{ item }">
<pfam-chips v-if="item?.PFAM" :pfam-string="item.PFAM"></pfam-chips>
</template>
</v-data-table-virtual>
</v-card>
</template>
\ No newline at end of file
......@@ -10,7 +10,7 @@
<script setup lang="ts">
const slot = useSlots()
const el = ref(null)
const el: Ref<HTMLElement | null> = ref(null)
const rendered = ref(false)
async function render() {
......
......@@ -83,7 +83,7 @@ useHead({
link: [
{
rel: 'stylesheet',
href: 'https://www.ebi.ac.uk/pdbe/pdb-component-library/css/pdbe-molstar-3.1.2.css'
href: 'https://www.ebi.ac.uk/pdbe/pdb-component-library/css/pdbe-molstar-3.1.3.css'
},
],
script: [
......@@ -96,7 +96,7 @@ useHead({
},
{
type: "text/javascript",
src: "https://www.ebi.ac.uk/pdbe/pdb-component-library/js/pdbe-molstar-component-3.1.2.js",
src: "https://www.ebi.ac.uk/pdbe/pdb-component-library/js/pdbe-molstar-component-3.1.3.js",
// tagPosition: 'bodyClose'
}
]
......@@ -105,86 +105,43 @@ useHead({
const pdbeMolstarComponent = ref(null)
// const selectedPdb = ref("/wiki/avs/AVAST_I,AVAST_I__Avs1A,0,V-plddts_85.07081.pdb")
const selectedPdb: Ref<string | null> = ref(null)
const structureToDownload: Ref<string | null> = ref(null)
const selectedPaePath = computed(() => {
return selectedPdb.value ? `${selectedPdb.value.split(".").slice(0, -1).join('.')}.png` : null
})
// const paeData = ref([])
// watch(selectedPaePath, async (newPaePath) => {
// if (newPaePath !== null) {
// try {
// const data = await d3.tsv(newPaePath);
// if (data.length > 500) {
// paeError.value = `The PAE is too large to be displayed (${data.length} residus)`
// paeData.value = []
// }
// else if (data?.[0]?.[0] === undefined) {
// paeError.value = "The PAE cannot be downloaded"
// paeData.value = []
// }
// else {
// paeData.value = data
// paeError.value = null
// }
// }
// catch (error) {
// console.log(error)
// }
// } else {
// paeData.value = []
// }
// })
// const sanitizedPaeData = computed(() => {
// return paeData.value.reduce((acc, curr, index) => {
// const scoredResidue = index
// // let newAcc = [...acc]
// for (const [alignedResidue, value] of Object.entries(curr)) {
// // console.log(value)
// acc.push({ alignedResidue: parseInt(alignedResidue), scoredResidue: parseInt(scoredResidue), value: parseFloat(value) })
// // newAcc = [...newAcc, ...[{ alignedResidue: parseInt(alignedResidue), scoredResidue: parseInt(scoredResidue), value: parseFloat(value) }]]
// }
// return acc
// }, [])
// })
// const plotPaeOptions = computed(() => {
// return {
// width: 640,
// height: 640,
// color: { scheme: "Greens", legend: true, reverse: true, label: "Expected position error (Ångströms)" },
// y: { reverse: true },
// marks: [
// Plot.dot(sanitizedPaeData.value, { x: "scoredResidue", y: "alignedResidue", stroke: "value" })
// ]
// }
// })
watch(selectedPdb, (newSelectedPdb, prevSelectPdb) => {
viewPdb(newSelectedPdb)
structureToDownload.value = newSelectedPdb
})
onMounted(() => {
const urls = toValue(refinedDataUrls)
if (props.uniq && urls.length >= 1) {
structureToDownload.value = urls[0]
}
})
function viewPdb(pdbPath: string | null) {
if (pdbPath !== null) {
dialog.value = true
const format = toValue(pdbPath)?.split(".").slice(-1)[0]?.toLowerCase() ?? "pdb"
moleculeFormat.value = format
if (pdbeMolstarComponent.value?.viewerInstance) {
console.log(pdbeMolstarComponent.value)
const viewerInstance = pdbeMolstarComponent.value.viewerInstance
const customData = { url: pdbPath, format: format, binary: false }
viewerInstance.visual.update({ customData })
}
}
}
function setSelectedPdbToFirst() {
const urls = toValue(refinedDataUrls)
if (urls.length >= 1) {
......@@ -200,105 +157,115 @@ const moleculeFormat: Ref<string> = ref("pdb")
<template>
<span class="d-flex flex-wrap align-center justify-center">
<template v-if="uniq">
<v-row>
<v-btn size="x-small" variant="text" icon="md:visibility" @click="setSelectedPdbToFirst()"></v-btn>
<v-btn :disabled="!structureToDownload" size="x-small" variant="text" icon="md:download" class="ml-1"
:href="structureToDownload"></v-btn>
</v-row>
</template>
<v-row v-else>
<v-col>
<v-btn v-if="uniq" size="x-small" variant="tonal" icon="md:visibility" @click="setSelectedPdbToFirst()"></v-btn>
<v-select v-else v-model="selectedPdb" label="Select PDB" :items="refinedDataUrls" hide-details="auto">
</v-select>
<span class="d-flex flex-wrap align-center justify-center">
<v-select v-model="selectedPdb" label="Select PDB" :items="refinedDataUrls" hide-details="auto">
</v-select>
</span>
</v-col>
<v-dialog v-model="dialog" transition="dialog-bottom-transition" fullscreen :scrim="false">
<v-card flat :rounded="false">
<v-toolbar>
<v-toolbar-title>Structures</v-toolbar-title>
<v-select v-model="selectedPdb" label="Select PDB" :items="refinedDataUrls"
hide-details="auto"></v-select>
<v-spacer></v-spacer>
<v-btn :disabled="!selectedPdb" icon="md:download" :href="selectedPdb"></v-btn>
<v-btn icon @click="closeStructure">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-card-text>
<v-row>
<v-col :cols="mobile ? 12 : 'auto'">
<v-sheet v-if="selectedPdb"
class="d-flex align-center justify-center flex-wrap text-center mx-auto px-4 my-3"
:height="computedHeight" :width="computedWidth" position="relative">
<pdbe-molstar ref="pdbeMolstarComponent" hide-controls="true" landscape="true"
:custom-data-url="selectedPdb" alphafold-view="true"
:custom-data-format="moleculeFormat"></pdbe-molstar>
</v-sheet>
</v-col>
<v-col :cols="mobile ? 12 : undefined">
<v-img :src="selectedPaePath"></v-img>
<!-- <PlotFigure v-if="sanitizedPaeData?.length > 0 && paeError === null" defer
</v-row>
<v-dialog v-model="dialog" transition="dialog-bottom-transition" fullscreen :scrim="false">
<v-card flat :rounded="false">
<v-toolbar>
<v-toolbar-title>Structures</v-toolbar-title>
<v-select v-model="selectedPdb" label="Select PDB" :items="refinedDataUrls" hide-details="auto"></v-select>
<v-spacer></v-spacer>
<v-btn :disabled="!selectedPdb" icon="md:download" :href="structureToDownload"></v-btn>
<v-btn icon @click="closeStructure">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-card-text>
<v-row>
<v-col :cols="mobile ? 12 : 'auto'">
<v-sheet v-if="selectedPdb"
class="d-flex align-center justify-center flex-wrap text-center mx-auto px-4 my-3"
:height="computedHeight" :width="computedWidth" position="relative">
<pdbe-molstar ref="pdbeMolstarComponent" landscape="true" hide-controls="true"
:custom-data-url="selectedPdb" alphafold-view="true"
:custom-data-format="moleculeFormat"></pdbe-molstar>
</v-sheet>
</v-col>
<v-col :cols="mobile ? 12 : undefined">
<v-img :src="selectedPaePath"></v-img>
<!-- <PlotFigure v-if="sanitizedPaeData?.length > 0 && paeError === null" defer
:options="plotPaeOptions"></PlotFigure>
<v-alert v-else type="warning" variant="tonal">{{ paeError }}</v-alert> -->
<v-card flat color="transparent">
<v-card-title>Model Confidence</v-card-title>
<v-card-text>
AlphaFold produces a per-residue model
confidence score (pLDDT) between 0 and 100. Some regions below 50 pLDDT may be
unstructured
in isolation.
</v-card-text>
<v-list>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(0, 83, 214);">
&nbsp;</div>
</template>
<v-list-item-title>
Very high (pLDDT > 90)
</v-list-item-title>
</v-list-item>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(101, 203, 243);">
&nbsp;</div>
</template>
<v-list-item-title>
High (90 > pLDDT > 70)
</v-list-item-title>
</v-list-item>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(255, 219, 19);">
&nbsp;</div>
</template>
<v-list-item-title>
Low (70 > pLDDT > 50) </v-list-item-title>
</v-list-item>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(255, 125, 69);">
&nbsp;</div>
</template>
<v-list-item-title>
Very low (pLDDT &lt; 50) </v-list-item-title>
</v-list-item>
</v-list>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-dialog>
</span>
<v-card flat color="transparent">
<v-card-title>Model Confidence</v-card-title>
<v-card-text>
AlphaFold produces a per-residue model
confidence score (pLDDT) between 0 and 100. Some regions below 50 pLDDT may be
unstructured
in isolation.
</v-card-text>
<v-list>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(0, 83, 214);">
&nbsp;</div>
</template>
<v-list-item-title>
Very high (pLDDT > 90)
</v-list-item-title>
</v-list-item>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(101, 203, 243);">
&nbsp;</div>
</template>
<v-list-item-title>
High (90 > pLDDT > 70)
</v-list-item-title>
</v-list-item>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(255, 219, 19);">
&nbsp;</div>
</template>
<v-list-item-title>
Low (70 > pLDDT > 50) </v-list-item-title>
</v-list-item>
<v-list-item>
<template #prepend>
<div class="legendColor mr-2" style="background-color: rgb(255, 125, 69);">
&nbsp;</div>
</template>
<v-list-item-title>
Very low (pLDDT &lt; 50) </v-list-item-title>
</v-list-item>
</v-list>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-dialog>
</template>
<style scoped>
.msp-plugin .msp-plugin-content {
<style>
/* .msp-plugin .msp-plugin-content {
color: black !important;
} */
div.msp-plugin-content.msp-layout-expanded {
z-index: 5 !important
}
.legendColor {
......
......@@ -4,13 +4,23 @@ import PlotFigure from "~/components/PlotFigure";
import { useDisplay } from "vuetify";
import type { SortItem } from "@/components/ServerDbTable.vue"
import { ServerDbTable } from "#components"
import { useSerialize } from "@/composables/useSerialize";
import { useRasterize } from "@/composables/useRasterize";
import { useDownloadBlob } from '@/composables/useDownloadBlob';
import type { ComponentPublicInstance } from 'vue'
const sortBy: Ref<SortItem[]> = ref([{ key: 'type', order: "asc" }])
const itemValue = ref("id");
const { width } = useDisplay();
const scaleTransform: Ref<string[]> = ref([])
const { serialize } = useSerialize()
const { rasterize } = useRasterize()
const { download } = useDownloadBlob()
const facets = ref([
"replicon",
"type",
......@@ -30,6 +40,8 @@ const availableTaxo: Ref<string[]> = ref([
"phylum",
"Superkingdom"
]);
const scaleTypes = ref<string[]>(['linear', 'sqrt', 'log', 'symlog'])
const selectedTaxoRank = ref("phylum");
const headers = ref([
......@@ -67,24 +79,35 @@ const computedWidth = computed(() => {
return Math.max(currentWidth, 550);
});
const allHits = ref([])
onMounted(async () => {
const params = {
facets: ["*"],
filter: [],
sort: ["type:asc"],
limit: 500000
}
await getAllHits({ index: "refseq", params, query: "" })
})
async function getAllHits(params) {
const allHits: Ref<Record<string, any> | undefined> = ref(undefined)
// onMounted(async () => {
// console.log("on mounted get all hits")
// const params = {
// facets: ["*"],
// filter: undefined,
// sort: ["type:asc"],
// limit: 500000
// }
// getAllHits({ index: "refseq", params, query: "" })
// })
const pendingAllHits = ref(false)
async function getAllHits(params: { index: string, params: Record<string, any>, query: string }) {
console.log(params.index)
if (params.index === 'refseq') {
console.log(params.index)
const { data, error } = await useAsyncMeiliSearch(params)
console.log(error.value)
console.log(data.value)
allHits.value = data.value
console.log("get all hits in function ")
console.log(params)
pendingAllHits.value = true
try {
const { data, error } = await useAsyncMeiliSearch(params)
allHits.value = data.value
console.log(error.value)
} finally {
pendingAllHits.value = false
}
}
}
......@@ -108,22 +131,25 @@ const dataTableServerProps = computed(() => {
const defaultBarPlotOptions = computed(() => {
return {
x: { label: null, tickRotate: 45, ticks: 10 },
y: { grid: true, type: scaleType.value },
color: { legend: true },
width: computedWidth.value,
height: plotHeight.value + 100,
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()
return Object.entries(toValue(msResult).facetDistribution.type)
.map(([key, value]) => {
return {
type: key,
count: value
}
}).sort()
} else { return [] }
})
......@@ -131,13 +157,15 @@ const computedDistriSystemOptions = computed(() => {
return {
...defaultBarPlotOptions.value,
marginBottom: 100,
y: { ...defaultBarPlotOptions.value.y, type: toValue(scaleType), label: "Count" },
x: { ...defaultBarPlotOptions.value.x, label: "Systems" },
width: computedWidth.value,
marks: [
// Plot.frame(),
Plot.barY(
toValue(computedSystemDistribution),
{
y: "count", x: 'type', tip: true,
fill: "#6750a4",
sort: { x: "-y" },
},
......@@ -145,6 +173,9 @@ const computedDistriSystemOptions = computed(() => {
],
};
});
// Taxo distri
const computedTaxonomyDistribution = computed(() => {
if (toValue(msResult)?.facetDistribution?.[selectedTaxoRank.value]) {
return Object.entries(toValue(msResult).facetDistribution[selectedTaxoRank.value]).map(([key, value]) => {
......@@ -161,6 +192,9 @@ const computedDistriTaxoOptions = computed(() => {
return {
...defaultBarPlotOptions.value,
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),
......@@ -168,7 +202,7 @@ const computedDistriTaxoOptions = computed(() => {
y: "count",
x: selectedTaxoRank.value,
tip: true,
fill: "#6750a4",
// fill: "#6750a4",
sort: { x: "-y" },
}
),
......@@ -192,36 +226,58 @@ function namesToAccessionChips(names: string[]) {
}
})
}
const systemPanel: Ref<number> = ref(["table"])
const layoutPlot: Ref<string[]> = ref("grid")
const systemPanel: Ref<string[]> = ref([])
const layoutPlot: Ref<string> = ref("grid")
const binPlotOptions = ref({
marginLeft: 150,
marginBottom: 200,
padding: 0,
width: 1920,
grid: true,
x: { tickRotate: 90, tip: true, },
x: { tickRotate: 90, tip: true, label: "Systems" },
// y: { tickFormat: 's' },
color: { scheme: "turbo", legend: true },
})
const binPlotDataOptions = computed(() => {
return allHits.value?.hits?.length > 0 ? {
const toValueAllHits = toValue(allHits)
return toValueAllHits?.hits?.length > 0 ? {
...binPlotOptions.value,
width: width.value,
color: {
...binPlotOptions.value.color,
type: scaleType.value
type: scaleType.value,
tickFormat: '~s',
ticks: scaleType.value === 'symlog' ? 3 : 5,
// width: 350
},
// fy: { domain: groupSortDomain.value },
marks: [
Plot.cell(allHits.value?.hits ?? [], Plot.group({ fill: "count" }, { x: "type", y: selectedTaxoRank.value, tip: true, inset: 0.5, sort: { y: "fill" } })),
Plot.cell(toValueAllHits?.hits ?? [], Plot.group({ fill: "count" }, { x: "type", y: selectedTaxoRank.value, tip: true, inset: 0.5, sort: { y: "fill" } })),
]
} : 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>
......@@ -239,21 +295,72 @@ const scaleType = ref("linear")
<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="['linear', 'sqrt', 'symlog']" label="Scale Type" hide-details="auto"></v-select>
<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">
<PlotFigure :options="unref(computedDistriSystemOptions)" defer></PlotFigure>
<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">
<PlotFigure defer :options="unref(computedDistriTaxoOptions)"></PlotFigure>
<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>
......@@ -261,34 +368,60 @@ const scaleType = ref("linear")
</v-expansion-panel-text>
</v-expansion-panel>
<v-expansion-panel elevation="3" value="matrix">
<v-expansion-panel-title color="grey-lighten-4"><v-icon color="primary"
class="mr-2">mdi-data-matrix</v-icon>Heatmap</v-expansion-panel-title>
<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 flat color="transparent">
<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="['linear', 'sqrt', 'symlog']" label="Scale Type" hide-details="auto"></v-select>
<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>
<PlotFigure v-if="toValue(binPlotDataOptions) !== null" :options="unref(binPlotDataOptions)"
defer>
</PlotFigure>
<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" db="refseq" :sortBy="sortBy" :facets="facets"
:data-table-server-props="dataTableServerProps" @refresh:search="getAllHits">
<template #top>
<v-toolbar><v-toolbar-title class="text-capitalize">
RefSeq
</v-toolbar-title><v-spacer></v-spacer>
:data-table-server-props="dataTableServerProps" @refresh:search="(params) => getAllHits(params)">
</v-toolbar>
</template>
<template #[`item.accession_in_sys`]="{ item }">
<CollapsibleChips :items="namesToAccessionChips(item.accession_in_sys)">
</CollapsibleChips>
......
......@@ -2,13 +2,18 @@
import * as Plot from "@observablehq/plot";
import PlotFigure from "~/components/PlotFigure";
import type { SortItem } from "@/components/ServerDbTable.vue"
import { useNumericalFilter } from "@/composables/useNumericalfilter"
import { ServerDbTable } from "#components"
const sortBy: Ref<SortItem[]> = ref([{ key: 'system', order: "asc" }])
const sortBy: Ref<SortItem[]> = ref([{ key: 'System', order: "asc" }])
const itemValue = ref("id");
const facets: Ref<string[]> = ref(["system", "completed", "prediction_type", ])
const facets: Ref<string[]> = ref(["System", "subtype", "gene_name", "completed", "prediction_type",])
const headers: Ref<Object[]> = ref([
{ title: 'Structure', key: 'structure', sortable: false, removable: false },
{ title: "Type", key: "system", removable: false },
{ title: "System", key: "System", removable: false },
{ title: "Gene name", key: "gene_name", removable: false },
{ title: "Subtype", key: "subtype", removable: false },
// { title: "pdb file", key: "pdb" },
// { title: "fasta", key: "fasta_file" },
{ title: "Proteins in structure", key: 'proteins_in_the_prediction', sortable: false, removable: true },
......@@ -24,7 +29,16 @@ const headers: Ref<Object[]> = ref([
// { title: "Type", key: "type", removable: true },
])
const { search: msSearch, result: msResult } = useMeiliSearch('structure')
const { range: plddtsRange, stringifyFilter: plddtsFilter, reset: plddtsReset } = useNumericalFilter("plddts", 0, 100)
const { range: iptmRange, stringifyFilter: iptmFilter, reset: iptmReset } = useNumericalFilter("iptm+ptm", 0, 1)
const { range: pdockqRange, stringifyFilter: pdockqFilter, reset: pdockqReset } = useNumericalFilter("pDockQ", 0, 1)
const numericalFilters = computed(() => {
const listFilters = [plddtsFilter, iptmFilter, pdockqFilter].map(f => toValue(f)).filter(f => f !== undefined)
return listFilters.length > 0 ? listFilters.join(" AND ") : undefined
})
const defaultDataTableServerProps = ref({
showExpand: false
......@@ -41,11 +55,12 @@ const dataTableServerProps = computed(() => {
function namesToCollapsibleChips(names: string[], file: string | null = null) {
function namesToCollapsibleChips(names: string[], systemDir: string, file: string | null = null) {
if (file === null) {
return names.filter((it) => it !== "").map(it => ({ title: it.split("__").pop() }))
return names.filter((it) => it !== "").map(it => ({ title: it.split("__")[1] }))
} else {
return names.filter((it) => it !== "").map(it => ({ title: it.split("__").pop(), href: `/wiki/${toSystemName(file)}/${file}` }))
return names.filter((it) => it !== "").map(it => ({ title: it.split("__")[1], href: `/wiki/${systemDir}/${file}` }))
}
}
......@@ -54,50 +69,78 @@ function pdbNameToCif(pdbPath: string) {
return `${cifPath}.cif`
}
function toSystemName(rawName: string) {
return rawName.split(/_|-0/)[0].toLocaleLowerCase()
// Does it work if it's a list of system genes ?
// split on __ for systeme_vgenes
return rawName.split("__")[0].toLocaleLowerCase()
}
const plddtDistribution = computed(() => {
if (toValue(msResult)?.facetDistribution?.plddts) {
return Object.entries(toValue(msResult).facetDistribution.plddts).map(([key, value]) => { })
}
})
function remove(key) {
headers.value = headers.value.filter(header => header.key !== key)
}
</script>
<template>
<ServerDbTable title="Predicted Structures" db="structure" :sortBy="sortBy" :facets="facets"
:data-table-server-props="dataTableServerProps">
<template #top>
<v-toolbar><v-toolbar-title class="text-capitalize">
Predicted Structures
</v-toolbar-title><v-spacer></v-spacer>
</v-toolbar>
:data-table-server-props="dataTableServerProps" :numerical-filters="numericalFilters">
<template #numerical-filters="{ search }">
<v-row>
<v-col cols="12" md="12" lg="4">
<v-range-slider v-model="plddtsRange" strict density="compact" hide-details="auto" label="pLDDT"
step="0.5" :min="0" :max="100" thumb-label="always" @update:modelValue="search()">
<template #append>
<v-btn variant="text" icon="md:restart_alt" @click="plddtsReset()"></v-btn>
</template>
</v-range-slider>
</v-col>
<v-col cols="12" md="12" lg="4">
<v-range-slider v-model="iptmRange" strict density="compact" hide-details="auto" label="iptm+ptm"
step="0.1" :min="0" :max="1" thumb-label="always" @update:modelValue="search()">
<template #append>
<v-btn variant="text" icon="md:restart_alt" @click="iptmReset()"></v-btn>
</template>
</v-range-slider>
</v-col>
<!-- pdockqReset -->
<v-col cols="12" md="12" lg="4">
<v-range-slider v-model="pdockqRange" strict density="compact" hide-details="auto" label="pDockQ"
step="0.1" :min="0" :max="1" thumb-label="always" @update:modelValue="search()">
<template #append>
<v-btn variant="text" icon="md:restart_alt" @click="pdockqReset()"></v-btn>
</template>
</v-range-slider>
</v-col>
</v-row>
</template>
<template #[`item.proteins_in_the_prediction`]="{ item }">
<CollapsibleChips :items="namesToCollapsibleChips(item.proteins_in_the_prediction, item.fasta_file)">
<CollapsibleChips
:items="namesToCollapsibleChips(item.proteins_in_the_prediction, item.System_name_ok, item.fasta_file)">
</CollapsibleChips>
</template>
<template #[`item.system_genes`]="{ item }">
<CollapsibleChips :items="namesToCollapsibleChips(item.system_genes)"></CollapsibleChips>
<CollapsibleChips :items="namesToCollapsibleChips(item.system_genes, item.System_name_ok)"></CollapsibleChips>
</template>
<template #[`item.structure`]="{ item }">
<MolstarPdbePlugin v-if="item?.pdb && item.pdb !== 'na'"
:data-urls="[`/${toSystemName(item.system)}/${pdbNameToCif(item.pdb)}`]" uniq>
:data-urls="[`/${item.System_name_ok}/${pdbNameToCif(item.pdb)}`]" uniq>
</MolstarPdbePlugin>
<span v-else class="d-flex flex-wrap align-center justify-center">
<v-icon color="warning" icon="md:dangerous"></v-icon>
</span>
<!-- <v-icon v-else color="warning" icon="md:dangerous"></v-icon> -->
</template>
<template #[`item.completed`]="{ item }">
<v-icon v-if="item.completed" color="success" icon="md:check"></v-icon>
......
......@@ -3,7 +3,7 @@ import type { SortItem } from "@/components/ServerDbTable.vue"
import { ServerDbTable } from "#components"
const sortBy: Ref<SortItem[]> = ref([{ key: 'title', order: "asc" }])
const itemValue = ref("title");
const facets: Ref<string[]> = ref(["title", "Sensor", "Effector", "Activator", "PFAM.AC"])
const facets: Ref<string[]> = ref(["title", "Sensor", "Effector", "Activator", "PFAM.AC", "PFAM.DE"])
const headers: Ref<Object[]> = ref([
{ title: "System", key: "title", removable: false },
{ title: "Article", key: "doi", removable: false },
......@@ -30,16 +30,13 @@ const dataTableServerProps = computed(() => {
}
})
const columnsToDownload = ref(['title', 'doi', 'Sensor', 'Activator', 'Effector', 'PFAM', 'contributors',])
</script>
<template>
<ServerDbTable title="systems" db="systems" :sortBy="sortBy" :facets="facets"
:data-table-server-props="dataTableServerProps">
<ServerDbTable title="List Systems" db="systems" :sortBy="sortBy" :facets="facets"
:data-table-server-props="dataTableServerProps" :columns-to-download="columnsToDownload">
<template #top>
<v-toolbar><v-toolbar-title class="text-capitalize">
List Systems </v-toolbar-title><v-spacer></v-spacer>
</v-toolbar>
</template>
<template #[`item.title`]="{ item }">
<v-chip color="info" link :to="`/defense-systems/${item.title.toLowerCase()}`">{{
......
import { ref } from 'vue';
import Papa from 'papaparse';
import { saveAs } from "file-saver";
export function useCsvDownload(index: MaybeRef<string>, baseName: MaybeRef<string> = "df"
// import { saveAs } from "file-saver";
import { useDownloadBlob } from './useDownloadBlob';
const { download } = useDownloadBlob()
export function useCsvDownload(
rawData: MaybeRef<Record<string, any>>,
columns: MaybeRef<string[] | undefined> = undefined,
baseName: MaybeRef<string> = 'data'
) {
const pending = ref(false)
const { search: msSearch, result: msResult } = useMeiliSearch(toValue(index))
const filename = ref(`${toValue(baseName)}-data.csv`)
const downloadCsv = async (
query: MaybeRef<string>,
filter: MaybeRef<string>,
sortBy: MaybeRef<string[]>,
params: MaybeRef<Record<string, any>>,
) => {
filename.value = `${toValue(baseName)}-${toValue(filter)}.csv`
pending.value = true
try {
await msSearch(
toValue(query),
{
...toValue(params),
filter: toValue(filter),
sort: toValue(sortBy)
})
const csvContent = Papa.unparse(toValue(msResult).hits);
// console.log(csvContent)
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
saveAs(blob, `${toValue(filename)}`);
} finally {
pending.value = false
}
const filename = ref(`df-${toValue(baseName)}.csv`)
const data = ref()
const blob = ref()
if (toValue(rawData)?.hits?.length > 0) {
data.value = toValue(rawData).hits.map(row => {
let sanitizedRow = { ...row }
if (sanitizedRow?.PFAM?.length > 0) {
sanitizedRow = {
...sanitizedRow,
PFAM: sanitizedRow.PFAM.map(({ AC }) => AC).join(", ")
}
}
if (sanitizedRow?.contributors?.length > 0) {
sanitizedRow = {
...sanitizedRow,
contributors: sanitizedRow.contributors.join(", ")
}
}
return sanitizedRow
})
const csvContent = Papa.unparse(toValue(data), { columns: toValue(columns) });
blob.value = new Blob([csvContent], { type: "text/csv" });
download(blob, filename)
}
// watch(msResult, (newRes) => {
// console.log("save file !!!!!!")
// })
return {
pending, downloadCsv,
};
return { data, filename }
}
export function useDownloadBlob() {
function download(blob: MaybeRef<Blob>, filename: MaybeRef<string>) {
const toValueBlob = toValue(blob)
const toValueFilename = toValue(filename)
var a = document.createElement("a");
a.href = URL.createObjectURL(toValueBlob);
a.download = toValueFilename;
a.click();
URL.revokeObjectURL(a.href);
}
return { download }
}
\ No newline at end of file
export function useNumericalFilter(
id: MaybeRef<string>,
min: MaybeRef<number>,
max: MaybeRef<number>,
) {
const range: Ref<[number, number]> = ref([toValue(min), toValue(max)])
const stringifyFilter: Ref<string | undefined> = ref(`${toValue(id)} ${toValue(min)} TO ${toValue(max)}`)
watchEffect(() => {
console.log("watch reange")
console.log(range.value)
if (range.value[0] === toValue(min) && range.value[1] === toValue(max)) {
stringifyFilter.value = undefined
} else {
stringifyFilter.value = `'${toValue(id)}' ${range.value[0]} TO ${range.value[1]}`
}
})
function reset() {
range.value = [toValue(min), toValue(max)]
}
// watch(() => range, () => {
// console.log("watch reange")
// console.log(range)
// if (range.value[0] === toValue(min) && range.value[1] === toValue(max)) {
// stringifyFilter.value = undefined
// } else {
// stringifyFilter.value = `${toValue(id)} ${toValue(min)} TO ${toValue(max)}`
// }
// }, { deep: true })
return { range, stringifyFilter, reset }
}
\ No newline at end of file
import { useDownloadBlob } from './useDownloadBlob';
import { useSerialize } from './useSerialize';
import { useSvgPlot } from './useSvgPlot';
const { serialize } = useSerialize()
export function useRasterize() {
function rasterize(component: MaybeRef<ComponentPublicInstance | null>, filename: MaybeRef<string>) {
const toValueCompo = toValue(component)
if (toValueCompo !== null) {
const { svg } = useSvgPlot(toValueCompo)
const toValueSvg = toValue(svg)
if (toValueSvg !== null) {
let resolve, reject;
const promise: Promise<Blob> = new Promise((y, n) => (resolve = y, reject = n));
const image = new Image;
image.onerror = reject;
console.log(toValueSvg)
image.onload = () => {
console.log("try to get boundingclientRect")
const rect = toValueSvg.getBoundingClientRect();
console.log(rect)
const canvas = document.createElement("canvas");
canvas.width = rect.width
canvas.height = rect.height
const ctx = canvas.getContext("2d")
if (ctx !== null) {
ctx.drawImage(image, 0, 0, rect.width, rect.height);
ctx.canvas.toBlob(resolve);
}
}
const blob = toValue(serialize(component))
if (blob !== undefined) {
image.src = URL.createObjectURL(blob);
}
return promise;
}
}
}
return { rasterize }
}
\ No newline at end of file
import { useSvgPlot } from './useSvgPlot';
export function useSerialize() {
const xmlns = ref("http://www.w3.org/2000/xmlns/");
const xlinkns = ref("http://www.w3.org/1999/xlink");
const svgns = ref("http://www.w3.org/2000/svg");
const blob = ref<Blob>()
function serialize(compo: MaybeRef<ComponentPublicInstance | null>) {
const toValueCompo = toValue(compo)
if (toValueCompo !== null) {
const { svg } = useSvgPlot(toValueCompo)
const toValueSvg = toValue(svg)
if (toValueSvg !== null) {
const clonedSvg = toValueSvg.cloneNode(true);
const fragment = window.location.href + "#";
const walker = document.createTreeWalker(toValueSvg, NodeFilter.SHOW_ELEMENT);
while (walker.nextNode()) {
for (const attr of walker.currentNode.attributes) {
if (attr.value.includes(fragment)) {
attr.value = attr.value.replace(fragment, "#");
}
}
}
clonedSvg.setAttributeNS(xmlns.value, "xmlns", svgns.value);
clonedSvg.setAttributeNS(xmlns.value, "xmlns:xlink", xlinkns.value);
const serializer = new window.XMLSerializer;
const string = serializer.serializeToString(clonedSvg);
blob.value = new Blob([string], { type: "image/svg+xml" });
return blob
}
else { return undefined }
}
}
return { serialize }
}
\ No newline at end of file
export function useSvgPlot(component: MaybeRef<ComponentPublicInstance>) {
const svg = ref<SVGElement | null>(null)
const toValueCompo = toValue(component)
const rootElem = toValueCompo.$el
svg.value = rootElem.querySelector("svg.plot")
return { svg }
}
\ No newline at end of file
......@@ -10,10 +10,10 @@ In the following pages are presented different general concepts that are useful
You'll find information on :
1. [Abortive infection](/general-concepts/abortive-infection)
2. [Defense Islands](/general-concepts/defense-islands)
3. [Triggers of defense systems](/general-concepts/defense-systems_trigger)
4. [Effectors of defense systems](/general-concepts/defense-systems_effector)
5. [How defense systems were and are discovered](/general-concepts/defense-systems-discovery)
2. [Triggers of defense systems](/general-concepts/defense-systems_trigger)
3. [Effectors of defense systems](/general-concepts/defense-systems-effectors)
4. [How defense systems were and are discovered](/general-concepts/defense-systems-discovery)
5. [Defense Islands](/general-concepts/defense-islands)
6. [Defensive domains](/general-concepts/defensive-domains)
7. [MGE and defense systems](/general-concepts/mge-defense-systems)
8. [Anti defense systems](/general-concepts/anti-defense-systems)
\ No newline at end of file
---
title: Discovery of defense systems
layout: article
toc: true
contributors:
- Helena Shomar
---
# Discovery of defense systems
Anti-phage defense systems have been discovered through various research methodologies and scientific investigations.
The first defense systems that were discovered and characterized were restriction modifications (RM) and CRISPR-cas systems, in the 1960s and early 2000s respectively. These systems are the most abundantly encoded in prokaryotic genomes and were discovered by researchers that observed heritable bacterial resistance of certain strains to bacteriophages. A combination of functional studies, bacterial genetics, and biochemical assays enabled to elucidate their mechanisms of action, leading to the development of tools that revolutionized molecular biology and genetic engineering.
In recent years, the discovery and characterization of dozens of novel anti-phage defense systems involve a combination of bioinformatics, genomics analysis and experimental approaches. The computational pipeline that has allowed to identify and validate numerous systems in the past years is based on the observation that anti-phage defense systems tend to co-localize on prokaryotic chromosomes in regions denoted as defense islands. Using this principle, recent studies have discovered more than 150 novel systems, by identifying and testing single or multi protein uncharacterized systems that are enriched within such defense islands. Candidate systems are typically cloned into heterologous expression hosts, to validate their anti-phage function. The mechanisms of many these newly discovered systems remain unknown.
---
title: Abortive Infection
layout: article
toc: true
---
This section is empty. You can help by adding to it.
## test article