Преобразование диаграмм из vue-chartjs в 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.

Вот песочница с моим кодом.

Кроме того, если бы вы могли объяснить, как реактивно обновлять диаграммы, это было бы здорово.

Не могли бы вы создать репозиторий или codeandbox, который демонстрирует вашу проблему, чтобы мы могли помочь вам лучше? Предоставленный вами код сложно использовать для воспроизведения проблемы, так как он имеет зависимости, которые вы не включили.

marsnebulasoup 21.12.2020 22:18

Вот, я выпустил свой код в песочницу - (codeandbox.io/s/quizzical-pond-v15jz?file=/src/App.vue) пожалуйста, помогите мне

Tesfaye alemayehu 22.12.2020 14:42

Наконец-то я нашел решение, см. мой ответ ниже. Извините, что так долго, но на самом деле это было значительно сложнее, чем я думал.

marsnebulasoup 23.12.2020 18:55
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
2 926
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

По-видимому, (локально) преобразование диаграмм (в PDF-файлы) из chart.js на самом деле не так просто, а преобразование диаграмм из 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'
                }
            ]
        }
       }

... и вы получите это обратно:

Его можно использовать бесплатно, без ограничения скорости, но в правом верхнем углу есть водяной знак, как вы можете видеть на изображении выше. Бесплатный план включает (в настоящее время):

  • Неограниченное создание диаграмм
  • Водяной знак
  • Задержка второго запроса
  • Google Премиум Сеть
  • CDN по всему миру
  • Поддержка WebP и GIF

... но вы можете проверить их цены для получения дополнительной информации и других планов. Также ознакомьтесь с их документацией для получения дополнительной информации и поиграйте в их песочнице, чтобы убедиться, что это то, что вам нужно.

В качестве альтернативы, 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] }
            ] 
        }
    } 

Посмотреть PDF здесь

Вы можете поиграть в их песочнице и проверить их документы здесь для получения дополнительной информации, но обратите внимание, что для API применяется ограничение скорости, если вы не платите за премиальные планы . Также есть модуль node.js/JS, который вы можете импортировать и использовать в своем проекте здесь.

Несмотря на то, что водяного знака нет, и вы получаете вывод в формате PDF, диаграммы изображений, вероятно, по-прежнему являются лучшим решением, если ваше приложение обслуживает много пользователей, и вы не хотите платить за премиальные планы, поскольку они (диаграммы изображений) не не ограничивайте запросы.

2) Бесплатный, местный и слишком сложный путь

Для подхода без внешнего API, который может работать автономно и локально, вы можете использовать html2canvas и jsPDF для преобразования диаграмм в PDF-файлы. Это значительно сложнее, особенно с vue-chartjs, потому что вам нужно специально поместить диаграмму в контейнер размером 594 на 459 пикселей (для бумаги формата Letter) и убедиться, что параметры диаграммы установлены на

{ responsive: true, maintainAspectRatio: false }

...или изображение будет либо растянуто, либо сжато, либо слишком маленькое, либо слишком большое, например:

Но, если вы сделаете все это, вы можете получить несколько довольно красивых PDF-файлов с диаграммами:

Я думаю, что самый простой способ экспортировать диаграммы в PDF-файлы — это создать модуль, который можно импортировать в любое место, где вам нужно их экспортировать:

экспортер.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 на 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

Я создал модуль node.js для exporter.js, который вы можете установить и использовать в качестве альтернативы. См. здесь инструкции по установке и дополнительную информацию.

Как обновить диаграммы с помощью vue-chartjs

На самом деле это довольно просто. vue-chartjs имеет два миксина, которые вы можете использовать для достижения этой цели: 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.

Примеры кода и много более подробной информации об этом доступны в документации.

Посмотрите демонстрацию обновления диаграмм и преобразования их в PDF-файлы здесь.

Чтобы вы знали, такие компании, как Dropbox, Microsoft, Spotify и Grab, предпочитают использовать image-charts.com (более быстрый по всему миру и более надежный) вместо quickchart.io :) // Отказ от ответственности: я основатель IC!

FGRibreau 24.12.2020 11:29

@FGRibreau - я не вижу способа для диаграмм изображений вывести PDF-файл. Возможно ли это сделать или поддерживаются только изображения?

marsnebulasoup 28.12.2020 03:45

в настоящее время мы не поддерживаем вывод PDF, но как только клиент попросит об этом, мы реализуем его на следующий день!

FGRibreau 28.12.2020 10:30

Другие вопросы по теме