I have two tabs with different graphs created using chart.js.
TabOne.vue
<template>
<div class="tab-content tab-one" :class="{ active: isActive }">
<h3>Tab One</h3>
<canvas ref="chartCanvas"></canvas>
</div>
</template>
<script>
import Chart from 'chart.js/auto';
export default {
props: ['isActive'],
mounted() {
this.renderChart();
console.log("tab one mounted")
},
methods: {
renderChart() {
this.$nextTick(() => {
const ctx = this.$refs.chartCanvas.getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
});
}
}
};
</script>
<style scoped>
canvas {
max-width: 100%;
}
</style>
TabTwo.vue
<template>
<div class="tab-content tab-two" :class="{ active: isActive }">
<h3>Tab Two</h3>
<canvas ref="chartCanvas"></canvas>
</div>
</template>
<script>
import Chart from 'chart.js/auto';
export default {
props: ['isActive'],
mounted() {
this.renderChart();
console.log("tab two mounted")
},
methods: {
renderChart() {
this.$nextTick(() => {
const ctx = this.$refs.chartCanvas.getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'My First Dataset',
data: [65, 59, 80, 81, 56, 55, 40],
fill: false,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
});
}
}
};
</script>
<style scoped>
canvas {
max-width: 100%;
}
</style>
I have a views/HomeView.vue file which import these component and in the download logic it get’s the tab’s data converts the canvas to image and save it to the pdf and downloads it
HomeView.vue
<template>
<v-container>
<v-tabs v-model="activeTab" background-color="cyan" dark>
<v-tab @click="activateTab('0')">Tab One</v-tab>
<v-tab @click="activateTab('1')">Tab Two</v-tab>
<v-spacer></v-spacer>
<v-btn @click="downloadPdf">Download PDF</v-btn>
</v-tabs>
<v-tabs-items v-model="activeTab">
<v-tab-item key="0">
<tab-one :is-active="activeTab === '0'" ref="tabOne"></tab-one>
</v-tab-item>
<v-tab-item key="1">
<tab-two :is-active="activeTab === '1'" ref="tabTwo"></tab-two>
</v-tab-item>
</v-tabs-items>
</v-container>
</template>
<script>
import TabOne from '@/components/TabOne.vue';
import TabTwo from '@/components/TabTwo.vue';
import jsPDF from 'jspdf';
import 'jspdf-autotable';
export default {
components: {
TabOne,
TabTwo
},
data() {
return {
activeTab: '0',
};
},
methods: {
activateTab(tabName) {
this.activeTab = tabName;
},
downloadPdf() {
const doc = new jsPDF();
const canvasToImage = (canvas) => {
try {
const imageData = canvas.toDataURL('image/png');
return imageData;
} catch (e) {
console.error('Error converting canvas to image data URL:', e);
return null;
}
};
const tabOneCanvas = this.$refs.tabOne?.$refs.offScreenCanvas;
const tabOneImageData = tabOneCanvas ? canvasToImage(tabOneCanvas) : null;
const tabTwoCanvas = this.$refs.tabTwo?.$refs.offScreenCanvas;
const tabTwoImageData = tabTwoCanvas ? canvasToImage(tabTwoCanvas) : null;
if (tabOneImageData) {
doc.text('Tab One', 10, 10);
doc.addImage(tabOneImageData, 'PNG', 10, 20, 180, 100);
}
if (tabTwoImageData) {
doc.addPage();
doc.text('Tab Two', 10, 10);
doc.addImage(tabTwoImageData, 'PNG', 10, 20, 180, 100);
}
if (tabOneImageData || tabTwoImageData) {
doc.save('tabs-data.pdf');
} else {
console.error('No data available to download.');
}
}
}
};
</script>
I tried to convert the canvas to image in the tab component and send it to the HomeView.vue, i am getting the first tab’s image data but the second tab’s data i am getting after it is rendered.
First after initial execution of code click download button it will give an corrupt png error if we select the tab two and click download button it downloads both the tabs data
HomeView.vue :
<template>
<v-container>
<v-tabs v-model="activeTab" background-color="cyan" dark>
<v-tab @click="activateTab('0')">Tab One</v-tab>
<v-tab @click="activateTab('1')">Tab Two</v-tab>
<v-spacer></v-spacer>
<v-btn @click="downloadPdf">Download PDF</v-btn>
</v-tabs>
<v-tabs-items>
<v-tab-item>
<tab-one :class="{ active: activeTab === '0' }" ref="tabOne"></tab-one>
</v-tab-item>
<v-tab-item>
<tab-two :class="{ active: activeTab === '1' }" ref="tabTwo"></tab-two>
</v-tab-item>
</v-tabs-items>
</v-container>
</template>
<script>
import TabOne from '@/components/TabOne.vue';
import TabTwo from '@/components/TabTwo.vue';
import jsPDF from 'jspdf';
import 'jspdf-autotable';
export default {
components: {
TabOne,
TabTwo
},
data() {
return {
activeTab: '0', // Initially display TabOne
};
},
methods: {
activateTab(tabName) {
this.activeTab = tabName;
},
downloadPdf() {
const doc = new jsPDF();
// Function to convert canvas to image data URL
const canvasToImage = (canvas) => {
console.log("canvas", canvas)
try {
const imageData = canvas.toDataURL('image/png');
console.log("imageData", imageData)
return imageData;
} catch (e) {
console.error('Error converting canvas to image data URL:', e);
return null;
}
};
// Wait for next tick to ensure components are fully mounted
this.$nextTick(() => {
// Fetch data from TabOne component if available
const tabOneCanvas = this.$refs.tabOne?.$refs.chartCanvas;
const tabOneImageData = tabOneCanvas ? canvasToImage(tabOneCanvas) : null;
// Fetch data from TabTwo component if available
const tabTwoCanvas = this.$refs.tabTwo?.$refs.chartCanvas;
const tabTwoImageData = tabTwoCanvas ? canvasToImage(tabTwoCanvas) : null;
// Add content to the PDF document for TabOne
if (tabOneImageData) {
doc.text('Tab One', 10, 10);
doc.addImage(tabOneImageData, 'PNG', 10, 20, 180, 100);
}
// Add content to the PDF document for TabTwo
if (tabTwoImageData) {
doc.addPage();
doc.text('Tab Two', 10, 10);
doc.addImage(tabTwoImageData, 'PNG', 10, 20, 180, 100);
}
// Save the PDF
if (tabOneImageData || tabTwoImageData) {
doc.save('tabs-data.pdf');
} else {
console.error('No data available to download.');
}
});
}
}
};
</script>
<style scoped>
.tab-one,
.tab-two {
display: none;
}
.tab-one.active,
.tab-two.active {
display: block;
}
</style>
TabOne.vue :
<template>
<div class="tab-content tab-one" :class="{ active: isActive }">
<h3>Tab One</h3>
<canvas ref="chartCanvas"></canvas>
</div>
</template>
<script>
import Chart from 'chart.js/auto';
export default {
props: ['isActive'],
mounted() {
this.renderChart();
console.log("tab one mounted")
},
methods: {
renderChart() {
this.$nextTick(() => {
const ctx = this.$refs.chartCanvas.getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
});
}
}
};
</script>
<style scoped>
canvas {
max-width: 100%;
}
</style>
TabTwo.vue :
<template>
<div class="tab-content tab-two" :class="{ active: isActive }">
<h3>Tab Two</h3>
<canvas ref="chartCanvas"></canvas>
</div>
</template>
<script>
import Chart from 'chart.js/auto';
export default {
props: ['isActive'],
mounted() {
this.renderChart();
console.log("tab two mounted")
},
methods: {
renderChart() {
this.$nextTick(() => {
const ctx = this.$refs.chartCanvas.getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'My First Dataset',
data: [65, 59, 80, 81, 56, 55, 40],
fill: false,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
});
}
}
};
</script>
<style scoped>
canvas {
max-width: 100%;
}
</style>
ThejasNS Shetty is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.