Bug with React PDF and Highcharts SVG data Labels
Hi, I am using Highcharts to get an SVG of my chart with getSVG() method and I've got a bunch of logic I found online which creates me a SVG that React-PDF accepts. I have it rendering all good React-PDF, only issue I'm having is, I want to resize the dataLabels. I can resize the Pie Chart with width and height fine, heres some code below.
let chartSVG = chart.getSVG({
chart: {
width: 400,
height: 162,
}, .....
This code is doing the work to let React-PDF accept the Highcharts SVG
import { useMemo, createElement } from 'react';
import { parse, TextNode, ElementNode } from 'svg-parser';
import { styles } from './results-pdf.styles';
import { View } from '@react-pdf/renderer';
const supportedStyleProps = [
'color',
'dominantBaseline',
'fill',
'fillOpacity',
'fillRule',
'opacity',
'stroke',
'strokeWidth',
'strokeOpacity',
'strokeLinecap',
'strokeDasharray',
'transform',
'textAnchor',
'visibility',
];
const isElementNode = (node: TextNode | ElementNode): boolean => node.type === 'element';
const removeLineBreaks = (text?: string | number | boolean) => {
if (typeof text === 'string') {
return text.replace(/(\r\n|\n|\r)/gm, '');
}
return text;
};
// https://dev.to/qausim/convert-html-inline-styles-to-a-style-object-for-react-components-2cbi
const formatStringToCamelCase = (str: string) => {
const splitted = str.split('-');
if (splitted.length === 1) return splitted[0];
return (
splitted[0] +
splitted
.slice(1)
.map(word => word[0].toUpperCase() + word.slice(1))
.join('')
);
};
const getStyleObjectFromString = (str: string | null) => {
const style: any = {};
if (!str) return {};
str.split(';').forEach(el => {
let [property, value] = el.split(':');
if (!property) return;
if (property === 'cursor') return;
// calling formatStringToCamelCase function ====
const formattedProperty = formatStringToCamelCase(property.trim());
if (supportedStyleProps.includes(formattedProperty)) {
if (formattedProperty === 'strokeDasharray') {
console.log('value', value);
value = value.replace(/pt/g, ''); //dasharray has now px
}
style[formattedProperty] = value.trim();
}
});
return style;
};
const handleRelativePositioning = (node: ElementNode, parentX?: number, parentY?: number) => {
return {
x: Number(node.properties?.x ?? parentX ?? 0) + Number(node.properties?.dx ?? 0),
y: Number(node.properties?.y ?? parentY ?? 0) + Number(node.properties?.dy ?? 0),
};
};
const getParentPosition = (pos: number | string | undefined) => {
if (!pos) return 0;
if (typeof pos === 'string') return Number(pos);
return pos;
};
const svgToJSXWithRelPositioning = (
node: TextNode | ElementNode | string | any,
key?: string,
parentX?: number,
parentY?: number
): any => {
if (typeof node === 'string') {
return removeLineBreaks(node);
}
if (!isElementNode(node)) {
return removeLineBreaks(node.value);
}
const elementName = node.tagName;
if (!elementName) {
// console.log('NO TAG NAME: ', node);
return null;
}
let componentProps;
if (node.tagName === 'desc' || node.tagName === 'defs') return null;
if (node.properties !== undefined) {
if (node.tagName === 'text' || node.tagName === 'tspan' || node.tagName === 'rect') {
// calling handleRelativePositioning function ===
componentProps = handleRelativePositioning(node, parentX, parentY);
if (node.tagName !== 'rect') {
componentProps = {
...componentProps,
textAnchor: node.properties['text-anchor'],
};
} else {
componentProps = {
...node.properties,
...componentProps,
};
}
} else {
componentProps = node.properties;
}
// console.log(node, componentProps);
if (node.properties.style) {
console.log(node.properties.style);
componentProps = {
...componentProps,
// Calling getStyleObjectFromString function ----
style: getStyleObjectFromString(node.properties.style as string),
};
}
}
let children = [];
if (node.children && node.children.length > 0) {
children = node.children.map((childNode: TextNode | ElementNode | string, i: number) =>
svgToJSXWithRelPositioning(
childNode,
key + '-' + i,
// calling getParentPosition function ====
getParentPosition(node.properties.x),
getParentPosition(node.properties.y)
)
);
} else {
children = [''];
}
componentProps = { ...componentProps, key: key ?? 'root' };
return createElement(elementName.toUpperCase(), componentProps, children);
};
export const SvgComponent = ({ svgXml }: { svgXml: string }) => {
const MyView: any = View;
const svgElement = useMemo(() => {
if (!svgXml || svgXml === '') return <></>;
// const svg = svgXml.replace(/px/g, 'pt'); //replace all px with pt
const parsed: TextNode | ElementNode | string | any = parse(svgXml);
// calling svgToJSXWithRelPositioning function ===
return svgToJSXWithRelPositioning(parsed.children[0]);
}, [svgXml]);
return <MyView style={styles.resultsChart}>{svgElement}</MyView>;
};
the SvgComponent above is called in the my PDF component I've built
<SvgComponent svgXml={chartSVG} />
Where I am getting the chartSVG and passes it to SvgComponent as a prop, which it receives as svgXML as seen above, it does all the functions up above it and gives me a SVG I can use in my PDF component, all works fine. just when I go to resize the dataLabels as they are too big, they will not Resize in highcharts you can style the datalabels with
plotOptions: {
variablepie: {
borderWidth: 3,
dataLabels: {
style: {
fontSize: '5px',
textOutline: 'none',
fontFamily: 'PhoenixSansBoldWeb',
},
},
},
series: {
dataLabels: {
// connectorWidth: 0,
style: {
fontSize: '5px',
textOutline: 'none',
fontFamily: 'PhoenixSansBoldWeb',
},
},
},
},
But React PDF is ignoring it... so question is how can I resize these data labels is there something wrong with the logic in the functions above where it's creating an SVG for React PDF to accept?
I was using the logic from here - https://gist.github.com/dennemark/5f0f3d7452d9334f9349172db6c40f74
if someone knows the answer to this that would be great. Thanks
Maybe related to https://github.com/diegomura/react-pdf/issues/1271