Skip to content

Instantly share code, notes, and snippets.

@kidroca
Last active November 29, 2023 04:57
Show Gist options
  • Save kidroca/19e5fe2de8e24aa92a41e94f2d41eda4 to your computer and use it in GitHub Desktop.
Save kidroca/19e5fe2de8e24aa92a41e94f2d41eda4 to your computer and use it in GitHub Desktop.
Render recharts svg chart inside a PDF document created with react-pdf/renderer
import React from 'react';
import { Global } from 'recharts';
import { htmlSvgToPdfSvg } from '../imageFromSvg';
export const ChartSvg = ({ debug, style, children, width, height }) => {
return chartToPdfSvg(children, width, height, debug, style);
};
const chartToPdfSvg = (children, width, height, debug, style) => {
Global.set('isSsr', true);
const component = htmlSvgToPdfSvg(children);
Global.set('isSsr', false);
const result = React.cloneElement(component, { width, height, debug, style });
return result;
};
export default ChartSvg;
import { View } from '@react-pdf/renderer';
import { CartesianGrid, Label, Scatter, ScatterChart, XAxis, YAxis } from 'recharts';
import Chart from './Chart';
export const ExampleUsage = ({ data = getSampleData() }) => (
<View>
<Chart width={600} height={300}>
<MyRechartsChart data={data} />
</Chart>
</View>
);
const MyRechartsChart = ({ width, height, data }) => (
<ScatterChart width={width} height={height}>
<CartesianGrid />
<XAxis name="x" dataKey="x" />
<YAxis name="y" dataKey="y" />
<Scatter name="My Scatter" data={data} isAnimationActive={false} />
</ScatterChart>
)
function getSampleData() {
return [
{ x: 100, y: 100 },
{ x: 200, y: 100 },
{ x: 200, y: 150 },
{ x: 150, y: 200 },
{ x: 125, y: 150 }
]
}
export default ExampleUsage;
import React, { createElement } from 'react';
import ReactDom from 'react-dom/server';
import reactHtmlParser from 'react-html-parser';
export const htmlSvgToPdfSvg = (children) => {
const svgString = ReactDom
.renderToStaticMarkup(children)
.replaceAll('px', 'pt');
const [component] = reactHtmlParser(svgString, { transform: convertToPdfSvg });
return component;
};
function convertToPdfSvg(node, index) {
if (node.type == 'text') {
return node.data;
}
node.props = { key: index };
Object.entries(node.attribs).forEach(([key, value]) => {
const [first, ...rest] = key.split('-');
const newKey = [first, ...rest.map(word => `${word[0].toUpperCase()}${word.slice(1)}`)].join('');
node.props[newKey] = value;
});
node.name = node.name?.toUpperCase();
if (node.name == 'CLIPPATH') node.name = 'CLIP_PATH';
// we're removing nested <defs> because they don't work
if (node.name == 'DEFS' && node.parent.name != 'SVG') return null;
if (node.children) node.children = node.children.map(convertToPdfSvg);
return createElement(node.name, node.props, node.children);
}
export default htmlSvgToPdfSvg;
{
"dependencies": {
"@react-pdf/renderer": "^2.0.16",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-html-parser": "^2.0.2",
"recharts": "^2.0.9"
}
}
@kidroca
Copy link
Author

kidroca commented Nov 11, 2021

export const htmlSvgToPdfSvg = (children) => {
  const svgString = ReactDom
    .renderToStaticMarkup(children)
    .replaceAll('px', 'pt');

  const [component] = reactHtmlParser(svgString, { transform: convertToPdfSvg });
  return component;
};

Instead of rendering to string and then using reactHtmlParser we can probably iterate children and achieve a similar result

@griselmatosm
Copy link

Hi kidroca! Thanks for sharing to the community 🙌🏼
I'm following this steps, but I'm not able to render properly any recharts component, except the Line Chart one.
Here is the link to the code in a Github repository https://github.com/griselmatosm/recharts-react-pdf-test

Thanks a lot in advance!

@kidroca
Copy link
Author

kidroca commented Apr 20, 2022

Hi @griselmatosm,

The exact same code from this gist is still used in our project and there are no problems
The only difference is we pass a isAnimationActive={false} prop to the chart when we want to render it in PDF
The isAnimationActive={false} is then set on the <Scatter> component:

          <Scatter
            isAnimationActive={isAnimationActive}
            name={referenceDataLabel}
            data={referenceRecords}
            r={5}
            strokeWidth={2}
          />

So maybe that's all that you need to do

@griselmatosm
Copy link

Hi @griselmatosm,

The exact same code from this gist is still used in our project and there are no problems The only difference is we pass a isAnimationActive={false} prop to the chart when we want to render it in PDF The isAnimationActive={false} is then set on the <Scatter> component:

          <Scatter
            isAnimationActive={isAnimationActive}
            name={referenceDataLabel}
            data={referenceRecords}
            r={5}
            strokeWidth={2}
          />

So maybe that's all that you need to do

Thanks so much! That's it!

@gwvwl
Copy link

gwvwl commented Oct 24, 2022

Hello, tell me please. I get this error: TypeError: renderFn is not a function .

@dylnclrk
Copy link

dylnclrk commented Feb 9, 2023

Trying to figure this out myself. I also see: TypeError: renderFn is not a function

@gwvwl
Copy link

gwvwl commented Feb 9, 2023

Hello @dylnclrk, my reason was npm conflict. Try create new app, for testing. And I created GitHub tutorial, how I made this.

@khanirrfan
Copy link

i am using same configs in my tupescript project, but getting renderFn is not a function, and I don't think there are any conflicts.

@jnovak-SM2Dev
Copy link

I'm having the same issue with renderFn. Anyone figure this out?

@leq382121
Copy link

I have digged into the node modules and found out that recharts inner tags "TITLE" and "DEFS" are breaking render function. I've included this line of code to ditch those two values and it's sucessfuly rendered.

if (node.name == "TITLE" || node.name == "DESC") return null;

image

Result:
image

Hope it helps!

@EvHaus
Copy link

EvHaus commented Jun 26, 2023

I just created https://github.com/EvHaus/react-pdf-charts which allows for recharts to be rendered into react-pdf. It had only very basic support at the moment, but I'm hoping with some help from the community -- we can get to full support.

@leq382121
Copy link

leq382121 commented Jun 26, 2023

@EvHaus it does not work properly. I can see that Path is being converted to Line and it's not even found in parsed HTML string.

@leq382121
Copy link

probably Html parser is bringing this issue

@leq382121
Copy link

this is what I get after using an example

image

@EvHaus
Copy link

EvHaus commented Jun 26, 2023

@leq382121 Hmm. Could you open an issue in https://github.com/EvHaus/react-pdf-charts/issues with the code you're using and I'll take a look.

@Kartik0899
Copy link

Kartik0899 commented Aug 17, 2023

@EvHaus @leq382121 I am facing an issue while displaying the BarChart inside my pdf.... So when I am using " import ReactPDFChart from "react-pdf-charts" " this and trying to get my BarChart then Barchart is only showing the CartesianGrid, XAxis and YAxis but no values.
Below is the code with "ReactPDFChart" -

import React from "react";
import ReactPDFChart from "react-pdf-charts";
import { BarChart, Bar, XAxis, YAxis, CartesianGrid } from "recharts";

const data = [
{ name: 'A', uv: 4000, pv: 2400, amt: 2400 },
{ name: 'B', uv: 3000, pv: 1398, amt: 2210 },
{ name: 'C', uv: 2000, pv: 9800, amt: 2290 },
{ name: 'D', uv: 2780, pv: 3908, amt: 2000 },
{ name: 'E', uv: 1890, pv: 4800, amt: 2181 },
{ name: 'F', uv: 2390, pv: 3800, amt: 2500 },
{ name: 'G', uv: 3490, pv: 4300, amt: 2100 },
];

// Silence useLayoutEffect does nothing on the server warnings. These come
// from recharts but they're harmless and just clutter the console output.
const consoleError = console.error;
console.error = function (message) {
if (message?.startsWith('Warning: useLayoutEffect does nothing on the server')) return;
consoleError.apply(console, arguments);
};

const PdfBarChart = () => (
<ReactPDFChart> <BarChart data={data} width={500} height={300}> <XAxis dataKey="name" /> <YAxis /> <CartesianGrid stroke="#ccc" strokeDasharray="3 3" /> <Bar dataKey="uv" fill="#8884d8" /> <Bar dataKey="pv" fill="#82ca9d" /> </BarChart> </ReactPDFChart>
);

export default PdfBarChart;

And when I try the same above code without "ReactPDFChart" it is giving me error like below -

htmlLayer.getElementsByClassName is not a function
TypeError: htmlLayer.getElementsByClassName is not a function
at CartesianAxis.componentDidMount (http://localhost:3000/static/js/bundle.js:144491:28)

So can anyone please guide or help me to figure this out and render the Bar and another Charts in the pdf.

@EvHaus
Copy link

EvHaus commented Aug 17, 2023

@Kartik0899 Could you please open a new issue at https://github.com/EvHaus/react-pdf-charts/issues and we can take it from there.

@omarhsouna-logient
Copy link

BarChart doesn't work properly, ni the PieChart

@Kartik0899
Copy link

BarChart doesn't work properly, ni the PieChart

@omarhsounalogient

You need to add isAnimationActive={false} when rendering BarChart and PieChart component in the browser.
For example -
<Bar isAnimationActive={false} dataKey="uv" fill="#8884d8" />
<Pie isAnimationActive={false} dataKey="value" nameKey="name" cx="25%" cy="50%" outerRadius={50} fill="#000" label />

Hope it helps!

@khanirrfan
Copy link

I'm having the same issue with renderFn. Anyone figure this out?

I degraded the version react-pdf ,and it worked

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment