【问题标题】:Convert charts from vue-chartjs to a PDF将图表从 vue-chartjs 转换为 PDF
【发布时间】:2020-12-21 20:53:53
【问题描述】:

我正在尝试用 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, 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 和我的代码。

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

【问题讨论】:

  • 您能否创建一个 repo 或 codesandbox 来证明您的问题,以便我们更好地帮助您?您提供的代码很难用于重现问题,因为它具有您未包含的依赖项。
  • 给你,我已经在沙盒上发布了我的代码 - (codesandbox.io/s/quizzical-pond-v15jz?file=/src/App.vue) 请帮助我
  • 我终于找到了解决方案,请参阅下面的答案。抱歉花了这么长时间,但实际上比我想象的要困难得多。

标签: vue.js chart.js jspdf vue-chartjs jsreport


【解决方案1】:

显然,(本地)从 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,150,0.5)', 
                    borderColor:'rgb(255,150,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] }
            ] 
        }
    } 

View PDF here

您可以在他们的sandbox 中玩耍,并查看他们的文档here 了解更多信息,但请注意,API 是强制执行的速率限制,除非您pay for the premium plans。还有一个 node.js/JS 模块,您可以在项目here 中导入和使用。

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

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.on the docs.提供了代码示例和大量更深入的信息

查看图表更新和转换为 PDF 的演示 here。

【讨论】:

  • 请注意,Dropbox、Microsoft、Spotify 和 Grab 等公司更喜欢使用 image-charts.com(在全球范围内更快、可靠性更高)而不是 quickchart.io :) // 免责声明:我是IC创始人!
  • @FGRibreau - 我看不到图像图表输出 PDF 文件的方法。是否可以这样做或仅支持图像?
  • 我们目前不支持 PDF 输出,但一旦客户提出要求,我们将在第二天实施!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-11-11
  • 2011-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-28
相关资源
最近更新 更多