Skip to content
Snippets Groups Projects
Commit 51b7289a authored by Remi  PLANEL's avatar Remi PLANEL
Browse files

Resolve "export genomic context to svg or png"

parent c6cd3aad
No related branches found
No related tags found
1 merge request!22Resolve "export genomic context to svg or png"
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
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)
console.log(toValueSvg)
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;
image.onload = () => {
const rect = toValueSvg.getBoundingClientRect();
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);
console.log(image.src)
}
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")
console.log(svg.value)
return { svg }
}
\ No newline at end of file
...@@ -40,11 +40,11 @@ export default defineNuxtConfig({ ...@@ -40,11 +40,11 @@ export default defineNuxtConfig({
defaultLocale: 'en', // not needed if you have @nuxtjs/i18n installed defaultLocale: 'en', // not needed if you have @nuxtjs/i18n installed
}, },
security: { security: {
// csrf: { headers: {
// // https: false, contentSecurityPolicy: {
// addCsrfTokenToEventCtx: true, "img-src": ["'self'", "data:", "blob:"]
// cookieKey: 'csrftoken', }
// } }
}, },
vuetify: { vuetify: {
vuetifyOptions: { vuetifyOptions: {
......
...@@ -7,12 +7,18 @@ import { useRoute, computed } from "#imports"; ...@@ -7,12 +7,18 @@ import { useRoute, computed } from "#imports";
import * as d3 from "d3"; import * as d3 from "d3";
import { useElementSize } from '@vueuse/core' import { useElementSize } from '@vueuse/core'
import { joinURL } from "ufo"; import { joinURL } from "ufo";
import type { ComponentPublicInstance } from 'vue'
import { useSerialize } from '~/composables/useSerialize';
import { useRasterize } from '~/composables/useRasterize';
import { useDownloadBlob } from '~/composables/useDownloadBlob';
const route = useRoute(); const route = useRoute();
const { selectedProtein } = useSelectedProtein() const { selectedProtein } = useSelectedProtein()
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
const { serialize } = useSerialize()
const { rasterize } = useRasterize()
const { download } = useDownloadBlob()
const analysisId = computed(() => { const analysisId = computed(() => {
if (Array.isArray(route.params.analysisId)) return null if (Array.isArray(route.params.analysisId)) return null
...@@ -164,7 +170,18 @@ const computedWidth = computed(() => { ...@@ -164,7 +170,18 @@ const computedWidth = computed(() => {
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)
})
}
watch(selectedProtein, () => { watch(selectedProtein, () => {
const toValHitId = toValue(selectedProtein) const toValHitId = toValue(selectedProtein)
...@@ -191,7 +208,8 @@ const minRange = ref(0) ...@@ -191,7 +208,8 @@ const minRange = ref(0)
const maxRange = ref(innerWidth.value) const maxRange = ref(innerWidth.value)
const svgRef = ref(null) const svgRef = ref<ComponentPublicInstance | null>(null)
const figureRef = ref<ComponentPublicInstance | null>(null)
const domain = ref([0, 10000]) const domain = ref([0, 10000])
// const range = ref() // const range = ref()
...@@ -443,8 +461,31 @@ useHead({ ...@@ -443,8 +461,31 @@ useHead({
<v-card> <v-card>
<v-toolbar density="compact" class="pr-2"> <v-toolbar density="compact" class="pr-2">
<v-toolbar-title>{{ analysis.name }}</v-toolbar-title> <v-toolbar-title>{{ analysis.name }}</v-toolbar-title>
<v-btn color="primary" prepend-icon="mdi-download" :href="getResultArchiveUrl(analysis.id)">Download <v-menu>
all results</v-btn> <template v-slot:activator="{ props }">
<v-btn color="primary" prepend-icon="md:download" class="mr-2" v-bind="props">
Export
</v-btn>
</template>
<v-list>
<v-list-item v-if="analysis !== null" prepend-icon="mdi-archive" value="archive">
<v-list-item-title @click="getResultArchiveUrl(analysis.id)">Download all results
</v-list-item-title>
</v-list-item>
<v-divider></v-divider>
<v-list-subheader>Images</v-list-subheader>
<v-list-item prepend-icon="mdi-svg" value="svg">
<v-list-item-title @click="downloadSvg(figureRef, `df-results-${analysis.name}.svg`)">to
svg</v-list-item-title>
</v-list-item>
<v-list-item prepend-icon="mdi-image" value="png">
<v-list-item-title @click="downloadPng(figureRef, `df-results-${analysis.name}.png`)">to
png</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-chip color="primary" rounded>{{ new Date(analysis.create_time).toLocaleString() }}</v-chip> <v-chip color="primary" rounded>{{ new Date(analysis.create_time).toLocaleString() }}</v-chip>
<template v-if="analysis.percentage_done !== 100 && analysis.stderr === ''" #extension> <template v-if="analysis.percentage_done !== 100 && analysis.stderr === ''" #extension>
<v-row> <v-row>
...@@ -471,7 +512,7 @@ useHead({ ...@@ -471,7 +512,7 @@ useHead({
<template v-else> <template v-else>
<v-card-text> <v-card-text>
<div ref="gbContainer"> <div ref="gbContainer">
<v-card flat color="transparent"> <v-card ref="figureRef" flat color="transparent">
<v-toolbar variant="flat" density="compact" color="transparent"> <v-toolbar variant="flat" density="compact" color="transparent">
<v-spacer></v-spacer><v-toolbar-items> <v-spacer></v-spacer><v-toolbar-items>
<v-switch v-model="displayHmmerHits" color="primary" label="Display HMM-only hits <v-switch v-model="displayHmmerHits" color="primary" label="Display HMM-only hits
......
...@@ -8,9 +8,6 @@ const { data: systems, error } = await useAPI<SystemsOut>( ...@@ -8,9 +8,6 @@ const { data: systems, error } = await useAPI<SystemsOut>(
`/analysis/${route.params.analysisId}/systems` `/analysis/${route.params.analysisId}/systems`
); );
if (error.value) { if (error.value) {
throw createError({ message: `Error while getting the list systems for analysis ${route.params.analysisId}` }) throw createError({ message: `Error while getting the list systems for analysis ${route.params.analysisId}` })
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment