2

I want to download a chart created with recharts as a png image. I made it work if I write on the DOM. I would like to download the image instead. This is what I have so far thanks to this post:

Recharts component to PNG

I have a clickable element to download the png and a chart in a component:

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

    )

Whenever I click, I generate a canvas. Once I have an image thanks to the canvas, I could just write it in the DOM:

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;
    });
  };

  handleDownload = async () => {
    const chart = this.currentChart
    let chartSVG = ReactDOM.findDOMNode(chart).children[0]

    const pngData = await this.svgToPng(chartSVG, 400, 500)
    document.write('<img src="'+pngData+'"/>') // <------
  }

I would like to download the image instead of appending it to the DOM.

I tried this approach:

to download the image you can place it in an <a> tag using download attribute (html5) : <a href="javascript:canvas.toDataURL('image/jpeg');" download="download" >Download as jpeg</a>

I placed an the a tag in the DOM:

<a href={aTag} download="download" >Download as jpeg</a>

where aTag is:

const aTag = `'javascript:${this.state.imageData}.toDataURL('image/jpeg');'`

Basically, in my component I set a state with the canvas object:

class DownloadChart extends React.Component {
  state = {
    imageData: ''
  }
  async componentDidMount() {
    await this.handleDownload()
  }

I got rid of the document.write instruction, so I just generate the canvas object, and append it to the href attribute, whenever the component is mounted

<a href={aTag} download="download" >Download as png</a>

Any suggestions on how to achieve the download of a png image? I couldn't make it work with none of the two approaches.

AlbertMunichMar
  • 1,680
  • 5
  • 25
  • 49

2 Answers2

0

Try getting rid of the extra quotes in aTag.

i.e.

const aTag = `javascript:${this.state.imageData}.toDataURL('image/jpeg');`
ldtcoop
  • 680
  • 4
  • 14
0

I've been trying to find out how to accomplish this and this question had 95% of the solution I needed to finally make it work.

You can use FileSaver to download the PNG.

In handleDownload, replace document.write('<img src="'+pngData+'"/>') with FileSaver.saveAs(pngData, "test.png").

The download doesn't work inside the CodeSandbox, so you'll have to open the sandbox first, click "Open in New Window" in the upper right corner, and then try the download:

Edit focused-haze-vmhzg