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

Resolve "Being able to download the distributions in refseqDB as a svg/png/csv"

parent 6225734c
No related branches found
No related tags found
1 merge request!169Resolve "Being able to download the distributions in refseqDB as a svg/png/csv"
...@@ -4,14 +4,22 @@ import PlotFigure from "~/components/PlotFigure"; ...@@ -4,14 +4,22 @@ import PlotFigure from "~/components/PlotFigure";
import { useDisplay } from "vuetify"; import { useDisplay } from "vuetify";
import type { SortItem } from "@/components/ServerDbTable.vue" import type { SortItem } from "@/components/ServerDbTable.vue"
import { ServerDbTable } from "#components" 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 sortBy: Ref<SortItem[]> = ref([{ key: 'type', order: "asc" }])
const itemValue = ref("id"); const itemValue = ref("id");
const { width } = useDisplay(); const { width } = useDisplay();
const scaleTransform: Ref<string[]> = ref([]) const scaleTransform: Ref<string[]> = ref([])
const { serialize } = useSerialize()
const { rasterize } = useRasterize()
const { download } = useDownloadBlob()
const facets = ref([ const facets = ref([
"replicon", "replicon",
...@@ -32,6 +40,8 @@ const availableTaxo: Ref<string[]> = ref([ ...@@ -32,6 +40,8 @@ const availableTaxo: Ref<string[]> = ref([
"phylum", "phylum",
"Superkingdom" "Superkingdom"
]); ]);
const scaleTypes = ref<string[]>(['linear', 'sqrt', 'log', 'symlog'])
const selectedTaxoRank = ref("phylum"); const selectedTaxoRank = ref("phylum");
const headers = ref([ const headers = ref([
...@@ -121,22 +131,25 @@ const dataTableServerProps = computed(() => { ...@@ -121,22 +131,25 @@ const dataTableServerProps = computed(() => {
const defaultBarPlotOptions = computed(() => { const defaultBarPlotOptions = computed(() => {
return { return {
x: { label: null, tickRotate: 45, ticks: 10 }, x: { label: null, tickRotate: 45 },
y: { grid: true, type: scaleType.value }, y: { grid: true, clamp: true },
color: { legend: true },
width: computedWidth.value,
// height: plotHeight.value + 100, // height: plotHeight.value + 100,
} }
}) })
// system distri
const computedSystemDistribution = computed(() => { const computedSystemDistribution = computed(() => {
if (toValue(msResult)?.facetDistribution?.type) { if (toValue(msResult)?.facetDistribution?.type) {
return Object.entries(toValue(msResult).facetDistribution.type).map(([key, value]) => { return Object.entries(toValue(msResult).facetDistribution.type)
return { .map(([key, value]) => {
type: key, return {
count: value type: key,
} count: value
}).sort() }
}).sort()
} else { return [] } } else { return [] }
}) })
...@@ -144,13 +157,15 @@ const computedDistriSystemOptions = computed(() => { ...@@ -144,13 +157,15 @@ const computedDistriSystemOptions = computed(() => {
return { return {
...defaultBarPlotOptions.value, ...defaultBarPlotOptions.value,
marginBottom: 100, marginBottom: 100,
y: { ...defaultBarPlotOptions.value.y, type: toValue(scaleType), label: "Count" },
x: { ...defaultBarPlotOptions.value.x, label: "Systems" },
width: computedWidth.value,
marks: [ marks: [
// Plot.frame(), // Plot.frame(),
Plot.barY( Plot.barY(
toValue(computedSystemDistribution), toValue(computedSystemDistribution),
{ {
y: "count", x: 'type', tip: true, y: "count", x: 'type', tip: true,
// fill: "#6750a4",
sort: { x: "-y" }, sort: { x: "-y" },
}, },
...@@ -158,6 +173,9 @@ const computedDistriSystemOptions = computed(() => { ...@@ -158,6 +173,9 @@ const computedDistriSystemOptions = computed(() => {
], ],
}; };
}); });
// Taxo distri
const computedTaxonomyDistribution = computed(() => { const computedTaxonomyDistribution = computed(() => {
if (toValue(msResult)?.facetDistribution?.[selectedTaxoRank.value]) { if (toValue(msResult)?.facetDistribution?.[selectedTaxoRank.value]) {
return Object.entries(toValue(msResult).facetDistribution[selectedTaxoRank.value]).map(([key, value]) => { return Object.entries(toValue(msResult).facetDistribution[selectedTaxoRank.value]).map(([key, value]) => {
...@@ -174,6 +192,9 @@ const computedDistriTaxoOptions = computed(() => { ...@@ -174,6 +192,9 @@ const computedDistriTaxoOptions = computed(() => {
return { return {
...defaultBarPlotOptions.value, ...defaultBarPlotOptions.value,
marginBottom: 100, marginBottom: 100,
x: { ...defaultBarPlotOptions.value.x, label: selectedTaxoRank.value },
y: { ...defaultBarPlotOptions.value.y, type: toValue(scaleType), label: "Count" },
width: computedWidth.value,
marks: [ marks: [
Plot.barY( Plot.barY(
toValue(computedTaxonomyDistribution), toValue(computedTaxonomyDistribution),
...@@ -212,8 +233,8 @@ const binPlotOptions = ref({ ...@@ -212,8 +233,8 @@ const binPlotOptions = ref({
marginBottom: 200, marginBottom: 200,
padding: 0, padding: 0,
grid: true, grid: true,
x: { tickRotate: 90, tip: true, }, x: { tickRotate: 90, tip: true, label: "Systems" },
// y: { tickFormat: 's' },
color: { scheme: "turbo", legend: true }, color: { scheme: "turbo", legend: true },
}) })
...@@ -225,7 +246,11 @@ const binPlotDataOptions = computed(() => { ...@@ -225,7 +246,11 @@ const binPlotDataOptions = computed(() => {
color: { color: {
...binPlotOptions.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 }, // fy: { domain: groupSortDomain.value },
marks: [ marks: [
...@@ -236,6 +261,23 @@ const binPlotDataOptions = computed(() => { ...@@ -236,6 +261,23 @@ const binPlotDataOptions = computed(() => {
}) })
const scaleType = ref("linear") 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> </script>
<template> <template>
...@@ -253,21 +295,72 @@ const scaleType = ref("linear") ...@@ -253,21 +295,72 @@ const scaleType = ref("linear")
<v-btn icon="md:grid_view" value="grid"></v-btn> <v-btn icon="md:grid_view" value="grid"></v-btn>
<v-btn icon="md:view_agenda" value="fullwidth"></v-btn> <v-btn icon="md:view_agenda" value="fullwidth"></v-btn>
</v-btn-toggle> </v-btn-toggle>
<v-select v-model="scaleType" class="mx-2" density="compact" <v-select v-model="scaleType" class="mx-2" density="compact" :items="scaleTypes"
:items="['linear', 'sqrt', 'symlog']" label="Scale Type" hide-details="auto"></v-select> label="Scale Type" hide-details="auto"></v-select>
<v-select v-model="selectedTaxoRank" :items="availableTaxo" density="compact" <v-select v-model="selectedTaxoRank" :items="availableTaxo" density="compact"
label="Select taxonomic rank" hide-details="auto" class="mx-2"></v-select> label="Select taxonomic rank" hide-details="auto" class="mx-2"></v-select>
</v-toolbar> </v-toolbar>
<v-row align="start"> <v-row align="start">
<v-col :cols="fullWidth ? 12 : 6"> <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>
<v-col :cols="fullWidth ? 12 : 6"> <v-col :cols="fullWidth ? 12 : 6">
<v-card variant="flat">
<PlotFigure defer :options="unref(computedDistriTaxoOptions)"></PlotFigure> <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-col>
</v-row> </v-row>
</v-card> </v-card>
...@@ -285,19 +378,42 @@ const scaleType = ref("linear") ...@@ -285,19 +378,42 @@ const scaleType = ref("linear")
<v-expansion-panel-text> <v-expansion-panel-text>
<v-card v-if="pendingAllHits === false" flat color="transparent"> <v-card v-if="pendingAllHits === false" flat color="transparent">
<v-toolbar flat color="transparent" density="compact"> <v-toolbar flat color="transparent" density="compact">
<v-select v-model="scaleType" class="mx-2" density="compact" <v-select v-model="scaleType" class="mx-2" density="compact" :items="scaleTypes"
:items="['linear', 'sqrt', 'symlog']" label="Scale Type" hide-details="auto"></v-select> label="Scale Type" hide-details="auto"></v-select>
<v-select v-model="selectedTaxoRank" :items="availableTaxo" density="compact" <v-select v-model="selectedTaxoRank" :items="availableTaxo" density="compact"
label="Select taxonomic rank" hide-details="auto" class="mx-2"></v-select> label="Select taxonomic rank" hide-details="auto" class="mx-2"></v-select>
</v-toolbar> </v-toolbar>
<PlotFigure v-if="toValue(binPlotDataOptions) !== null" :options="unref(binPlotDataOptions)" <v-card v-if="toValue(binPlotDataOptions) !== null" variant="flat">
defer> <v-toolbar density="compact" color="transparent">
</PlotFigure> <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-card v-else flat color="transparent"> <v-card v-else flat color="transparent">
<v-skeleton-loader type="card" :loading="pendingAllHits" <v-skeleton-loader type="card" :loading="pendingAllHits" height="400"></v-skeleton-loader>
height="400"></v-skeleton-loader>
</v-card> </v-card>
</v-expansion-panel-text> </v-expansion-panel-text>
</v-expansion-panel> </v-expansion-panel>
......
import Papa from 'papaparse'; import Papa from 'papaparse';
// import { saveAs } from "file-saver"; // import { saveAs } from "file-saver";
import { useDownloadBlob } from './useDownloadBlob';
const { download } = useDownloadBlob()
export function useCsvDownload( export function useCsvDownload(
rawData: MaybeRef<Record<string, any>>, rawData: MaybeRef<Record<string, any>>,
...@@ -29,12 +31,7 @@ export function useCsvDownload( ...@@ -29,12 +31,7 @@ export function useCsvDownload(
}) })
const csvContent = Papa.unparse(toValue(data), { columns: toValue(columns) }); const csvContent = Papa.unparse(toValue(data), { columns: toValue(columns) });
blob.value = new Blob([csvContent], { type: "text/csv" }); blob.value = new Blob([csvContent], { type: "text/csv" });
var a = document.createElement("a"); download(blob, filename)
a.href = URL.createObjectURL(blob.value);
a.download = filename.value;
a.click();
URL.revokeObjectURL(a.href);
} }
return { data, filename } 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
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
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment