Search code examples
reactjsdocxrecharts

How to insert a dynamic chart image (recharts) into Word document (docx) in Reactjs?


I would like to generate a Word document after I click the button. The document should also contain graphs which are generated with Recharts. I can also use another library but I have already started to use it to generate charts. I have nearly 20 data sets which should be shown with graphs inside of the Word doc.

What is the most appropriate way to create images and then insert them into the generated Word doc?

import React from "react";
import * as FileSaver from "file-saver";
import {Document, Paragraph, TextRun, Packer, ImageRun} from "docx";

function WordReport(props) {

  const exportToWord = () => {
    const doc = new Document({
      sections: [
        {
          properties: {},
          children: [
            new Paragraph({
              children: [
                new TextRun("Hello World"),
                new TextRun({
                  text: "Foo Bar",
                  bold: true,
                }),
                new TextRun({
                  text: "\tGithub is the best",
                  bold: true,
                }),
              ],
            }),
          ],
        },
      ],
    });

    Packer.toBlob(doc).then((blob) => {
      FileSaver.saveAs(blob, 'report.docx');
    });
  }

  const handleExportClick = (e) => {
    e.preventDefault();
    exportToWord();
  }
  
  return (
    <>
      You can download the report which includes all graphs in
      Word document format.
      <br />
      <button onClick={(e) => {handleExportClick(e); }} >
        Export in Word
      </button>
    </>
  );
}

export default WordReport;

Solution

  • I found my way. Data could be fed through props. Graphs should use isAnimationActive={false}

    import React, { useEffect, useState, useContext } from "react";
    import * as FileSaver from "file-saver";
    import {Document, Paragraph, HeadingLevel, TextRun, Packer, ImageRun} from "docx";
    import { renderToString } from "react-dom/server";
    import { LineChart, BarChart, Bar, LabelList, Line, XAxis, YAxis, CartesianGrid } from "recharts";
    
    function WordReport(props) {
      const [blobImages, setBlobImages] = useState([]);
    
      const dataLine = [
        {
          name: 'Page A',
          uv: 4000,
          pv: 2400,
          amt: 2400,
        },
        {
          name: 'Page B',
          uv: 3000,
          pv: 1398,
          amt: 2210,
        },
        {
          name: 'Page C',
          uv: 2000,
          pv: 9800,
          amt: 2290,
        },
        {
          name: 'Page D',
          uv: 2780,
          pv: 3908,
          amt: 2000,
        },
        {
          name: 'Page E',
          uv: 1890,
          pv: 4800,
          amt: 2181,
        },
        {
          name: 'Page F',
          uv: 2390,
          pv: 3800,
          amt: 2500,
        },
        {
          name: 'Page G',
          uv: 3490,
          pv: 4300,
          amt: 2100,
        },
      ];
    
      const dataBar = [
        {
          name: 'Page A',
          uv: 4000,
          pv: 2400,
          amt: 2400,
        },
        {
          name: 'Page B',
          uv: 3000,
          pv: 1398,
          amt: 2210,
        },
        {
          name: 'Page C',
          uv: 2000,
          pv: 9800,
          amt: 2290,
        },
        {
          name: 'Page D',
          uv: 2780,
          pv: 3908,
          amt: 2000,
        },
        {
          name: 'Page E',
          uv: 1890,
          pv: 4800,
          amt: 2181,
        },
        {
          name: 'Page F',
          uv: 2390,
          pv: 3800,
          amt: 2500,
        },
        {
          name: 'Page G',
          uv: 3490,
          pv: 4300,
          amt: 2100,
        },
      ];
    
      const CHART_PROPS_LIST = [
        {
          type: 'line',
          width: 500,
          height: 250,
          margin: { top: 20, right: 20, left: 20, bottom: 20 },
          data: dataLine,
        },
        {
          type: 'bar',
          width: 500,
          height: 250,
          margin: { top: 20, right: 20, left: 20, bottom: 20 },
          data: dataBar,
        },
      ];
    
      useEffect(() => {
        const createImageBlob = async () => {
          const promises = CHART_PROPS_LIST.map((chartProps) => {
    
            let chartComponent;
    
            if (chartProps.type === 'line') {
              chartComponent = (
                  <svg width={chartProps.width} height={chartProps.height}>
                    <LineChart {...chartProps}>
                      <CartesianGrid strokeDasharray="3 3" />
                      <XAxis dataKey="name" />
                      <YAxis />
                      <Line isAnimationActive={false} type="monotone" dataKey="pv" stroke="#8884d8" />
                      <Line isAnimationActive={false} type="monotone" dataKey="uv" stroke="#82ca9d" />
                    </LineChart>
                  </svg>
              );
            } else if (chartProps.type === 'bar') {
              chartComponent = (
                  <svg width={chartProps.width} height={chartProps.height}>
                    <BarChart {...chartProps}>
                      <CartesianGrid strokeDasharray="5 5" />
                      <XAxis dataKey="name" scale="point" padding={{ left: 10, right: 10 }} />
                      <YAxis />
                      <CartesianGrid strokeDasharray="3 3" />
                      <Bar isAnimationActive={false} dataKey="pv" fill="#8884d8" background={{ fill: '#eee' }} />
                    </BarChart>
                  </svg>
              );
            };
    
            // Render the chart component to an SVG markup
            const svgMarkup = renderToString(chartComponent);
    
            // Convert the SVG markup to a Blob
            const blob = new Blob([svgMarkup], { type: 'image/svg+xml' });
    
            return blob;
          });
    
          // Wait for all the promises to resolve
          const blobs = await Promise.all(promises);
    
          setBlobImages(blobs);
        };
        createImageBlob();
      }, []);
    
      const exportToWord = () => {
        const doc = new Document({
          sections: [
            {
              properties: {},
              children: [
                new Paragraph({
                  text: "Line chart",
                  heading: HeadingLevel.HEADING_1,
                }),
                new Paragraph({
                  children: [
                    new ImageRun({
                      data: blobImages[0],
                      transformation: {
                        width: 500,
                        height: 250,
                      },
                    }),
                  ],
                }),
                new Paragraph({
                  text: "Bar chart",
                  heading: HeadingLevel.HEADING_1,
                }),
                new Paragraph({
                  children: [
                    new ImageRun({
                      data: blobImages[1],
                      transformation: {
                        width: 500,
                        height: 250,
                      },
                    }),
                  ],
                }),
              ],
            },
          ],
        });
    
        Packer.toBlob(doc).then((blob) => {
          FileSaver.saveAs(blob, 'report.docx');
        });
      };
    
      const handleExportClick = (e) => {
        e.preventDefault();
        exportToWord();
      };
    
      return (
          <button onClick={(e) => {handleExportClick(e); }} >
          Export in Word
        </button>
      );
    }
    
    export default WordReport;