将图表从 vue-chartjs 转换为 PDF

Convert charts from vue-chartjs to a PDF

我正在尝试使用 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,它将图表切成两半。

这是我的一张图表:

PieChart.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, 0, 0, 450);
    this.gradient2 = this.$refs.canvas
      .getContext("2d")
      .createLinearGradient(0, 0, 0, 450);

    this.gradient.addColorStop(0, "rgba(255, 0,0, 0.5)");
    this.gradient.addColorStop(0.5, "rgba(255, 0, 0, 0.25)");
    this.gradient.addColorStop(1, "rgba(255, 0, 0, 0)");

    this.gradient2.addColorStop(0, "rgba(0, 231, 255, 0.9)");
    this.gradient2.addColorStop(0.5, "rgba(0, 231, 255, 0.25)");
    this.gradient2.addColorStop(1, "rgba(0, 231, 255, 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 中的图表。

这是我的代码 sandbox

此外,如果您能解释如何响应式更新图表,那就太好了

显然,(本地)从 chart.js 转换图表(到 PDF)实际上并不那么容易,从 vue-chartjs 转换图表更加困难。幸运的是,有(至少)两种方法可以做到这一点,尽管它们不是最佳选择。

1) 简单的方法

image-charts.com提供了一个API可以接受chart.js格式的图表数据并转成图片,但是好像不能转成PDF.

例如,您可以将此数据作为 URL 传递给 image-charts:

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,150,0.5)', 
                    borderColor:'rgb(255,150,150)',
                    data:[-23,64,21,53,-39,-30,28,-10],
                    label:'Dataset',fill:'origin'
                }
            ]
        }
       }

...你会得到这个:

免费使用,没有速率限制,但右上角有水印,如上图所示。免费计划包括(目前):

  • 无限图表生成
  • 水印
  • 亚秒级请求延迟
  • GooglePremium Network
  • 全球 CDN
  • WebP 和 Gif 支持

...但是您可以查看他们的 pricing for more info and other plans. Also check out their docs for further information and play around in their sandbox 看看它是否是您需要的。

另外,Quickchart.io 还提供了一个易于使用的 API 接受图表数据,并输出 PDF(或其他格式)供您使用。只需使用您的图表 data/formatting 选项向 /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] }
            ] 
        }
    } 

View PDF here

你可以在他们的 sandbox, and check out their docs here for more info, but note that the API is rate limit enforced, unless you pay for the premium plans. There's also a node.js/JS module you can import and use in your project here 中玩耍。

虽然没有水印,而且您确实获得了 PDF 输出,但如果您的应用服务于许多用户并且您不想为高级计划付费,那么图像图表仍然可能是更好的解决方案,因为它们 ( image-charts) 不要限制请求。

2) Free & Local & Way 太难了

对于无需外部 API 且可以 运行 离线和本地的方法,您可以使用 html2canvas 和 jsPDF 将图表呈现为 PDF。它要复杂得多,尤其是使用 vue-chartjs,因为你必须专门将图表放在 594px x 459px 的容器中(对于信纸大小的纸张),并确保图表的选项设置为

{ 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 的图表元素必须包含在 594px x 459px 的元素中,因为这是纸张的大小,并且是图表。但是你将无法在你的网站或类似网站上调整图表的大小,所以你可以做的是创建两个相同的图表,一个你可以做任何你想做的事情,另一个包含在 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 有两个 mixins 可以用来实现这一点,reactivePropreactiveData。如果你想将动态数据传递给你作为道具创建的图表组件,你可以使用 reactiveProp:

线chart.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