问题描述
我正在尝试制作一个由 vue-chartjs 绘制的图表的报告系统,我想将它们下载为 PDF 或任何文件。
这是 App.vue 的样子
<template>
<div>
<div id="printSection" ref="document">
<AreaChart />
<PieChart />
...
</div>
<button @click="printSection">Print Shit</button>
</div>
</template>
<script>
import AreaChart from "./SuperAdmin/AreaChart";
import PieChart from "./SuperAdmin/PieChart";
...
//import { Printd } from "printd"; //tried with this one
//import html2pdf from 'html2pdf.js'; // and this one
export default {
name: "App",components: {
AreaChart,PieChart,},data() {
return { };
},mounted() {
var d = new Printd();
},methods: {
printSomething () { // this was for printd
this.d.print( this.$el,this.csstext)
},exportToPDF () { // this was for html2pdf
html2pdf()
html2pdf(this.$refs.document,{
margin: 1,filename: 'document.pdf',image: { type: 'jpeg',quality: 0.98 },html2canvas: { dpi: 192,letterRendering: true },jsPDF: { unit: 'in',orientation: 'landscape' }
})
},printSection() {
this.$htmlToPaper("printSection"); // this was for htmltopaper
}
}
};
</script>
<style>
...
</style>
所以,如您所见,我尝试了 3 个包将图表转换为 PDF,但它们总是打印空纸,除了 html2pdf,它将图表切成两半。
这是我的图表之一:
饼图.vue
<script>
import { Pie } from "vue-chartjs";
import displayshit from '../../service/Hello'
import labels from 'chartjs-plugin-labels'
//import { plugins } from 'chart.js';
export default {
extends: Pie,async mounted() {
var data = [];
var new_data = [];
var Type = ['Mobile','Card','Both']
var sum = 0 ;
data = (await displayshit.displayanothershit()).data.message;
var Mobile_number = data.find(element=> JSON.stringify(element._id) == JSON.stringify([Type[0]])) || 0
var Card_number = data.find(element=> JSON.stringify(element._id) == JSON.stringify([Type[1]])) || 0
var Both_number = data.find(element=> element._id.length == 2) || 0
new_data.push(Mobile_number.count || 0)
new_data.push(Card_number.count || 0)
new_data.push(Both_number.count || 0)
this.gradient = this.$refs.canvas
.getContext("2d")
.createLinearGradient(0,450);
this.gradient2 = this.$refs.canvas
.getContext("2d")
.createLinearGradient(0,450);
this.gradient.addColorStop(0,"rgba(255,0.5)");
this.gradient.addColorStop(0.5,0.25)");
this.gradient.addColorStop(1,0)");
this.gradient2.addColorStop(0,"rgba(0,231,255,0.9)");
this.gradient2.addColorStop(0.5,0.25)");
this.gradient2.addColorStop(1,0)");
this.renderChart(
{
labels: Type,datasets: [
{
backgroundColor: [this.gradient,this.gradient2,"#00D8FF"],data: new_data
},],{ responsive: true,maintainAspectRatio: false,plugins: {
labels: {render: 'label',}
}}
);
},};
</script>
我想将图表转换为 PDF 文件,但我也希望能够选择将哪些图表包含在 PDF 中。
另外,如果你能解释如何被动地更新图表,那就太好了
解决方法
显然,从chart.js(本地)转换图表(到PDF)实际上并不那么容易,而从vue-chartjs转换图表则更加困难。幸运的是,有(至少)两种方法可以做到这一点,尽管它们不是很理想。
1) 简单的方法
image-charts.com 提供的 API 可以接受 chart.js 格式的图表数据并将其转换为图像,但似乎无法将其转换为 PDF。
例如,您可以将此数据作为 URL 传递给图像图表:
https://image-charts.com/chart.js/2.8.0?
bkg=white
&c= {
type: 'line',data: { labels: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug'],datasets: [
{
backgroundColor: 'rgba(255,150,0.5)',borderColor:'rgb(255,150)',data:[-23,64,21,53,-39,-30,28,-10],label:'Dataset',fill:'origin'
}
]
}
}
...然后你得到这个:
它可以免费使用,没有速率限制,但右上角有一个水印,如上图所示。免费计划包括(目前):
- 无限图表生成
- 水印
- 亚秒请求延迟
- 谷歌Premium Network
- 全球 CDN
- WebP 和 Gif 支持
...但是您可以查看他们的 pricing 以了解更多信息和其他计划。还可以查看他们的 docs 以获取更多信息,并在他们的 sandbox 中试用,看看它是否是您所需要的。
或者,Quickchart.io 还提供了一个易于使用的 API,它接受图表数据,并将输出 PDF(或其他格式)供您使用。只需使用您的图表数据/格式选项向 /chart
端点发出请求,您就会获得 PDF 输出:
https://quickchart.io/chart?
format=PDF
&bkg=white
&c= {
type: 'bar',data: {
labels: ['Q1','Q2','Q3','Q4'],datasets: [
{ label: 'Users',data: [50,60,70,180] },{ label: 'Revenue',data: [100,200,300,400] }
]
}
}
您可以在他们的 sandbox 中玩耍,并查看他们的文档 here 以了解更多信息,但请注意,除非您 pay for the premium plans,否则 API 是强制执行的速率限制。还有一个 node.js/JS 模块,您可以在您的项目 here 中导入和使用。
虽然没有水印,并且您确实获得了 PDF 输出,但如果您的应用为许多用户提供服务并且您不想为高级计划付费,图像图表仍然可能是更好的解决方案,因为它们(图像-图表)不要对请求进行速率限制。
2) 免费的、本地的和太难的方法
对于没有外部 API、可以离线和本地运行的方法,您可以使用 html2canvas 和 jsPDF 将图表呈现为 PDF。它要复杂得多,尤其是使用 vue-chartjs,因为您必须专门将图表放置在 594 像素 x 459 像素的容器中(对于信纸大小的纸张),并确保图表的选项设置为
{ responsive: true,maintainAspectRatio: false }
...否则图像将被拉伸或压扁或太小或太大,如下所示:
但是,如果你做所有这些,你最终可以得到一些相当漂亮的图表 PDF:
我认为将图表导出为 PDF 的最简单方法是创建一个模块,该模块可以在您需要导出它们的任何位置导入:
exporter.js
import html2canvas from "html2canvas";
import jsPDF from "jspdf";
export default class Exporter {
constructor(charts) {
this.charts = charts;
this.doc = new jsPDF("landscape","px","letter");
}
export_pdf() {
return new Promise((resolve,reject) => {
try {
this.charts.forEach(async (chart,index,charts) => {
this.doc = await this.create_page(chart,this.doc);
if (index === charts.length - 1) resolve(this.doc); // this.doc.save("charts.pdf"); // <-- at the end of the loop; display PDF
this.doc.addPage("letter");
});
} catch (error) {
reject(error);
}
});
}
async create_page(chart,doc) {
const canvas = await html2canvas(chart,{
scrollY: -window.scrollY,scale: 5 // <-- this is to increase the quality. don't raise this past 5 as it doesn't get much better and just takes longer to process
});
const image = canvas.toDataURL("image/jpeg",1.0),pageWidth = doc.internal.pageSize.getWidth(),pageHeight = doc.internal.pageSize.getHeight(),ratio = (pageWidth - 50) / canvas.width,canvasWidth = canvas.width * ratio,canvasHeight = canvas.height * ratio,marginX = (pageWidth - canvasWidth) / 2,marginY = (pageHeight - canvasHeight) / 2;
doc.addImage(image,"JPEG",marginX,marginY,canvasWidth,canvasHeight);
return doc;
}
}
您可以在 App.vue 或任何拥有图表的地方导入它,然后创建一个新的 Exporter
实例,其中包含要导出的图表元素数组,然后调用 export_pdf()
方法它并使用 PDF 做任何你想做的事情:
let area = document.getElementById("area");
let line = document.getElementById("line");
const exp = new Exporter([line,area]);
exp.export_pdf().then((pdf) => pdf.save("charts.pdf"));
通过这种方式,您可以根据需要渲染任意数量的图表。
App.vue 示例:
import Exporter from "@/assets/exporter.js";
...
export default {
methods: {
exportToPDF() {
let bar = document.getElementById("bar");
let radar = document.getElementById("radar");
let pie = document.getElementById("pie");
let area = document.getElementById("area");
let line = document.getElementById("line");
const exp = new Exporter([line,bar,radar,pie,area]);
exp.export_pdf().then((pdf) => pdf.save("charts.pdf"));
}
...
}
现在,请注意,如前所述,您传入 Exporter
的图表元素必须包含在 594 像素 x 459 像素的元素中,因为这是纸张的尺寸,也是图表的最佳尺寸.但是随后您将无法在您的网站等上调整图表的大小,因此您可以做的是创建两个相同的图表,一个您可以随心所欲,另一个包含在右侧的 div 中尺寸,但放置在屏幕外,因此用户看不到它。
像这样:
<template>
<BarChart />
<div class="hidden">
<BarChart id="bar" />
</div>
</template>
<script>
export default { STUFF GOES HERE}
</script>
<style>
.hidden {
width: 594px !important;
height: 459px !important;
position: absolute !important;
left: -600px !important;
}
</style>
另一个图表隐藏在左边,你看不到它。
两个图表都将填充相同的数据,但是当您导出为 PDF 时,屏幕外图表将转换为 PDF,而不是用户会看到的那个。这太复杂了,但这是我能找到的唯一方法。如果您尝试使用 v-if 或 v-show 隐藏图表,当您显示隐藏的图表时,由于某种原因,另一个将被删除。也许您可以创建一个单独的组件来一起创建两个图表,使事情变得更容易,但这需要进一步的工作。
另请注意,如果隐藏图表仍在屏幕上显示,您可能需要增加它向左移动的距离。
确保安装依赖项:
npm i html2canavs jspdf
我为 exporter.js 创建了一个 node.js 模块,您可以安装和使用它。有关安装说明和更多信息,请参阅 here
如何使用 vue-chartjs 刷新图表
其实这很简单。 vue-chartjs 有两个可用于实现此目的的 mixin,reactiveProp
和 reactiveData
。如果您想将动态数据传递到您作为道具创建的图表组件,您可以使用 reactiveProp
:
Linechart.js
import { Line,mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
extends: Line,mixins: [reactiveProp],props: ['options'],mounted () {
// this.chartData is created in the mixin.
// If you want to pass options please create a local options object
this.renderChart(this.chartData,this.options)
}
}
// from https://vue-chartjs.org/guide/#updating-charts
无论你想在何处显示图表,只需导入 Linechart.js,并将其注册为组件:
import LineChart from './LineChart.js'
export default {
components: {
LineChart
},...
}
...然后将图表放在您的模板中,并通过 chart-data
道具传递数据:
<line-chart :chart-data="datacollection"></line-chart>
它会在 datacollection
更改时自动更新。
提供代码示例和大量更深入的信息on the docs.
查看图表更新和将它们转换为 PDF 的演示here。