Can anyone suggest possible approaches to generate a PDF image from a VictoryChart in react native?
react-native-html-to-pdf is a cross-platform solution to convert HTML including images to a PDF using React Native.
Are there any methods to save a VictoryChart view to an image?
Thanks in advance,
@esutton I wonder if you could use a generic solution like https://github.com/gre/react-native-view-shot - it currently supports jpg/png output.
Thanks @jevakallio
When user sends report, using the screenshot method I would need to:
I fear the resulting user experience would not be good. I had hoped there were alternatives to react-native-view-shot.
Does not VictoryChart use D3 underneath?
Could something like this An example of creating a PNG from an SVG in D3. be possible?
Export VictoryChart to SVG, then render as image?
I expect this is not easy to accomplish in React Native. Especially for someone who is no D3 expert.
Or this perhaps?
@esutton we use d3 internally, but the example you posted also uses the browser canvas element to render the graphic into an image. This API is not available on React Native, so that mechanism won't work.
Internally, we also use react-native-svg, which could be extended to support saving the drawing into an image (in fact, the Android implementation already draws the SVG to a bitmap and renders that bitmap on-screen). That's outside the scope of Victory Native, though.
I wonder if you would be able to achieve this by rendering a temporary dummy view as you suggest, but rendering it off-screen using relative positioning so the user would not see the view? I have not tried this, just an idea.
@jevakallio Thanks for the ideas.
I think reporting / printing of charts is a very important feature to users.
It sounds like the options are to:
svgRef.toDataURL(base64 => {
console.log(base64);
});
It looks like toDataURL method is supported by both Android and iOS. Is there a way to get the SVG reference of the Victory Native chart?
https://github.com/react-native-community/react-native-svg/search?utf8=%E2%9C%93&q=toDataURL
Oh, I wasn't aware of RNSVGSvgViewManager.toDataURL!
VictoryChart has access to the SVG already, and it's technically accessible as chart.containerRef.svgRef.
<VictoryChart
ref={(chart) => {
if (chart && chart.containerRef && chart.containerRef.svgRef) {
setTimeout(() => {
chart.containerRef.svgRef.toDataURL((base64) => {
console.log(base64);
});
}, 0);
}
}}
>
<VictoryLine/>
</VictoryChart>
This seems to work, but has a couple of problems
toDataURL on the SVG ref directly in the chart's ref callback - thus the setTimeout hack above. Calling the method on the same call stack results in a native exception, at least on iOS. This won't be a problem if the chart is already mounted by the time the snapshot is taken, of course.@esutton does this solution work for your use case? If it does, I think we can expose a method to grab the SVG reference.
@jevakallio I think exposing a method to grab the SVG reference would be very helpful in printing and report generation.
@jevakallio Will you have time to expose method to grab SVG reference?
I tried using your example and the console log is dumping base64 data. To avoid crashes I had to increase the timeout from 0 to 500.
ExceptionsManager.js:71 Exception thrown while executing UI block: *** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0]
I need to save to file using RNFS and see what the image result looks like and learn how to scale image to fill a full landscape page in a PDF. For rendering an image for PDF use, I hope I can understand how to render off-screen using relative positioning so the user would not see the view, as you mentioned.
Thanks in advance,
Can any react native experts share suggestions on how to invisibly render a VictoryChart for image generation purpose?
I have VictorChart writing image to a file using RNFS, then adding image to HTML content, then rendering HTML to PDF.
The hard part, is understanding how to create "dummy view" or a technique to render image offscreen ( with width set to match desired PDF page width ).
A second issue is I have not figured out how to make chart X:Y scales match so that distance meters Y matches distance meters Y scaling.
See example below. The X:Y scale ratio is * NOT * 1:1. How can I do 1:1 ?

Example: Save VictoryChart as base64 SVG data as file image using RNFS
onWriteImageToFile(fileAttachment) {
RNFS.writeFile(fileAttachment.path, fileAttachment.fileData, fileAttachment.encoding)
.then(() => {
console.log('RNFS.writeFile ', fileAttachment.path);
// this.onWriteFileComplete(fileAttachment, sendCallback);
})
.catch((err) => {
console.log(err);
Alert.alert(`Failed to write file: ${fileAttachment.name}`, err.message);
});
},
In render method, call SVG to DataURL((base64) method.
<VictoryChart
ref={(chart) => {
if (chart && chart.containerRef && chart.containerRef.svgRef) {
setTimeout(() => {
chart.containerRef.svgRef.toDataURL((base64) => {
const name = 'profile.png';
const path = `${RNFS.DocumentDirectoryPath}/${name}`;
const fileAttachment = {
path,
name,
fileData: base64,
mimeType: 'image/png',
encoding: 'base64',
};
this.onWriteImageToFile(fileAttachment);
});
}, 500);
}
}}
>
... < removed code >
Thank in advance,
Render offscreen ( works on iOS and hopefully Android )
Now if I can figure out how to do this once:
Or perhaps make a new transitional view that says "Creating PDF" onscreen,
and renders chart offscreen,
and creates image file by calling svgRef.toDataURL,
Ugly but I think it can work.
render()
const { height, width } = Dimensions.get('window');
return (
<View
style={[styles.containerOffscreen]}
width={2 * width}
>
{victoryChart}
</View>
);
},
});
const styles = StyleSheet.create({
containerOffscreen: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'flex-end',
alignItems: 'center',
},
See [email protected] exposes a containerRef:
https://github.com/FormidableLabs/victory/issues/781#event-1281057513
released in [email protected]. Look for this in the next victory native release as well. ~ next week
I have not tried but hope this will work with the new containerRef exposed in [email protected]:
class App extends React.Component {
constructor(props) {
super(props);
this.logContainerRef = this.logContainerRef.bind(this);
}
componentDidMount() {
this.logContainerRef();
}
logContainerRef() {
console.log(this.containerRef);
}
render() {
return (
<VictoryBar
containerComponent={
<VictoryContainer containerRef={(ref) => {
this.containerRef = ref;
setTimeout(() => {
this.containerRef.svgRef.toDataURL((base64) => {
console.log(base64);
});
}, 500);
}}
/>
}
/>
);
}
}
@esutton Your approach sounds great, did you manage to make it work? I'm in the same situation now, need to create a pdf with chart inside
@dzuncoi I have it working for victory-native 0.7.0
````
ref={(chart) => {
if (chart && chart.containerRef && chart.containerRef.svgRef) {
setTimeout(() => {
chart.containerRef.svgRef.toDataURL((base64) => {
const name = 'profile.png';
// iOS Cache Problem: New profile.png images is created but PDF still contains old, even if delete png and pdf first?
// ToDo: Create a "tmp-<uuid>" folder each time?
// const path = `${Utility.getFilePath()}/tmp-pdf/${name}`;
const path = `${Utility.getFilePath()}/${name}`;
const fileAttachment = {
path,
name,
fileData: base64,
mimeType: 'image/png',
encoding: 'base64',
};
// console.log(base64);
// console.log('fileAttachment:', fileAttachment);
this.onSvgCreated(fileAttachment);
});
}, 500);
}
}}
>
````
However the container ref is exposure has changed and the above can no longer work with the latest version of victory-native.
See: Add a containerRef prop for those who need a reference to the rendered chart #781
I will need to get it working with the latest victory-native but have not had time to try yet.
@esutton Thanks for your response. I didn't make it work in latest version so I'm trying the approach using react-native-view-shot, convert it to image and insert to HTML, performance with 2,3 small images (400x200) is quite good
@dzuncoi Thanks!.
I will give https://github.com/gre/react-native-view-shot a shot!
It sounds like less effort to get working.
@dzuncoi Do you know if react-native-view-shot works offscreen?
I need to make a landscape image of the victor-native chart that gets included in the PDF even if the device is being help in portrait mode. For this reason I need to draw offscreen.
I guess react-native-view-shot should be the same concept.
Yes, I tested this case, it works. Because I think react-native-view-shot will capture base on component's ref, no matter where it is.
My example:
// ...
capture = () => {
captureRef(this.mRef, {
format: "jpg",
quality: 1,
})
}
render() {
return (
<Button onPress={this.capture} />
<View ref={ref => this.mRef = ref} style={{ position: absolute, left: 300, top: 200, background: 'red' }}>
// Some element inside
</View>
)
}
The element above will be half onscreen, half offscreen, and the image is like a charm :)
@esutton
@dzuncoi Thank you for the code example.
The element above will be half onscreen, half offscreen, and the image is like a charm :)
That's what my victory-native image capture looks like as well.
It's rather ugly with left corner of the line chart displaying. I added a circular progress bar on top to make it better. It would be nice to hide it completely while being rendered and saved to storage.
Hello, I also have similar problem but with react js. Does anyone know how to generate an image for PDF in react js? Thanks in advance!
I'm closing this issue as a wont fix
Most helpful comment
Oh, I wasn't aware of
RNSVGSvgViewManager.toDataURL!VictoryCharthas access to the SVG already, and it's technically accessible aschart.containerRef.svgRef.This seems to work, but has a couple of problems
toDataURLon the SVG ref directly in the chart's ref callback - thus thesetTimeouthack above. Calling the method on the same call stack results in a native exception, at least on iOS. This won't be a problem if the chart is already mounted by the time the snapshot is taken, of course.@esutton does this solution work for your use case? If it does, I think we can expose a method to grab the SVG reference.