10

I currently have a Recharts component that I would like to export as a PNG file.

<LineChart
  id="currentChart"
  ref={(chart) => (this.currentChart = chart)}
  width={this.state.width}
  height={this.state.height}
  data={this.testData}
  margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
>
  <XAxis dataKey="name" />
  <YAxis />
  <CartesianGrid strokeDasharray="3 3" />
  <Tooltip />
  <Legend />
  <Line type="monotone" dataKey="pv" stroke="#8884d8" activeDot={{ r: 8 }} />
  <Line type="monotone" dataKey="uv" stroke="#82ca9d" />
</LineChart>;

but I'm unsure if this is directly supported by the library.

I have an idea that involves using a canvas and a 2D rendering context to get me close to a solution, as outlined on MDN

However, I'm not sure of a generic way to render an HTML element (or React Component) as a canvas to implement this solution.

I might be going about this all wrong, and I would appreciate the correction!

Jonathan Irwin
  • 5,009
  • 2
  • 29
  • 48
Cole
  • 2,641
  • 1
  • 16
  • 34

6 Answers6

11

I was able to solve my problem by delving into the Recharts component. Recharts renders as an SVG under a wrapper so all I had to do was convert properly to save as both HTML or SVG

// Exports the graph as embedded JS or PNG
exportChart(asSVG) {

    // A Recharts component is rendered as a div that contains namely an SVG
    // which holds the chart. We can access this SVG by calling upon the first child/
    let chartSVG = ReactDOM.findDOMNode(this.currentChart).children[0];

    if (asSVG) {
        let svgURL = new XMLSerializer().serializeToString(chartSVG);
        let svgBlob = new Blob([svgURL], {type: "image/svg+xml;charset=utf-8"});
        FileSaver.saveAs(svgBlob, this.state.uuid + ".svg");
    } else {
        let svgBlob = new Blob([chartSVG.outerHTML], {type: "text/html;charset=utf-8"});
        FileSaver.saveAs(svgBlob, this.state.uuid + ".html");
    }
}

I am using FileSaver.js for the save prompt.

Cole
  • 2,641
  • 1
  • 16
  • 34
11

This function takes SVG element on input and transforms to image/png data:

export const svgToPng = (svg, width, height) => {

    return new Promise((resolve, reject) => {

        let canvas = document.createElement('canvas');
        canvas.width = width;
        canvas.height = height;
        let ctx = canvas.getContext('2d');

        // Set background to white
        ctx.fillStyle = '#ffffff';
        ctx.fillRect(0, 0, width, height);

        let xml = new XMLSerializer().serializeToString(svg);
        let dataUrl = 'data:image/svg+xml;utf8,' + encodeURIComponent(xml);
        let img = new Image(width, height);

        img.onload = () => {
            ctx.drawImage(img, 0, 0);
            let imageData = canvas.toDataURL('image/png', 1.0);
            resolve(imageData)
        }

        img.onerror = () => reject();

        img.src = dataUrl;
    });
};

And how to access the Recharts SVG element? This code snippet allows you to render any Chart outside of your current visible DOM and use it's SVG:

const exportChart = () => {

    // Output image size
    const WIDTH = 900;
    const HEIGHT = 250;

    const convertChart = async (ref) => {

        if (ref && ref.container) {
            let svg = ref.container.children[0];
            let pngData = await svgToPng(svg, WIDTH, HEIGHT);
            console.log('Do what you need with PNG', pngData);
        }
    };

    const chart = <LineChart data={...} width={WIDTH} height={HEIGHT}
        ref={ref => convertChart(ref)} />;

    // Render chart component into helper div
    const helperDiv = document.createElement('tmp');
    ReactDOM.render(chart, helperDiv);
}
peter.bartos
  • 11,855
  • 3
  • 51
  • 62
  • I can't create a blob using the pngData, any help on that? I need the blob so that I can download the png image – AlbertMunichMar Jul 25 '19 at 11:30
  • @AlbertMunichMar I believe, you can try to modify the solution by downloading the pdf image from canvas. See here: https://stackoverflow.com/questions/8126623/downloading-canvas-element-to-an-image – peter.bartos Jul 25 '19 at 14:02
3

@brammitch created a package for this (inspired by answers here):

https://github.com/brammitch/recharts-to-png

Fabio Espinosa
  • 880
  • 6
  • 14
3

The written answer helped me a lot. Many thanks for that. Nevertheless, I was missing an "out of the box" solution for the download as png, which I would like to make up for here. Even if it's too late, maybe it will help someone else.

handleExportChart = () => {

        let chartSVG = ReactDOM.findDOMNode(this.currentChart).children[0];
        const width = chartSVG.clientWidth;
        const height = chartSVG.clientHeight;
        let svgURL = new XMLSerializer().serializeToString(chartSVG);
        let svgBlob = new Blob([svgURL], { type: "image/svg+xml;charset=utf-8" });
        let URL = window.URL || window.webkitURL || window;
        let blobURL = URL.createObjectURL(svgBlob);

        let image = new Image();
        image.onload = () => {
            let canvas = document.createElement('canvas');
            canvas.width = width;
            canvas.height = height;
            let context = canvas.getContext('2d');
            context.drawImage(image, 0, 0, context.canvas.width, context.canvas.height);
            let png = canvas.toDataURL('image/png', 1.0);
            FileSaver.saveAs(png, "Test.png");
        };

        image.src = blobURL;
    };

Abdullah
  • 31
  • 2
0

This is an old post but this might help someone

let pngData = await getPngData(this.ref);
FileSaver.saveAs(pngData, filename);

using recharts-to-png and file-saver npm modules

Maha BENJEBARA
  • 232
  • 4
  • 12
0
        <PolarRadiusAxis axisLine={true} tick={true} tickCount={6} />
        <Radar
          name="Mike"
          dataKey="weight"
          stroke="#8884d8"
          fill="#8884d8"
          fillOpacity={0.6}
        />

      </RadarChart>
    </ResponsiveContainer>
 <button onClick={handleDownload}>Save Chart</button>

The Chart component receives the data prop, which represents the data to be displayed in the Recharts chart.

The handleDownload function is triggered when the "Download Chart" button is clicked.

Inside the handleDownload function:

The chart container element is obtained using document.getElementsByClassName("recharts-wrapper")[0]. The SVG content of the chart is extracted by selecting the first SVG element within the chart container. The SVG content is serialized into a string using XMLSerializer().serializeToString(chartSvg). An image element is created, and a blob is created from the SVG string using new Blob([chartXml], { type: "image/svg+xml;charset=utf-8" }). A URL for the blob is created using URL.createObjectURL(svgBlob). An onload event handler is attached to the image element, which ensures that the image is fully loaded before performing further actions. Inside the onload event handler: A canvas element is created with the same dimensions as the chart container. The image is drawn onto the canvas using context.drawImage(image, 0, 0). The canvas is converted to a data URL using canvas.toDataURL("image/png"). A temporary link element is created with the data URL as its href and "chart.png" as the download attribute value. The link element is clicked programmatically using link.click(), triggering the download of the binary file. The Chart component renders a responsive container with a Recharts LineChart inside it. The chart is configured with the provided data, and various components such as Line, CartesianGrid, XAxis, YAxis, Tooltip, and Legend are added as needed.

The rendered chart is enclosed within a element with a fixed height of 300px.

A "Download Chart" button is rendered below the chart, and the handleDownload function is attached to its onClick event.