Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug with React PDF and Highcharts SVG data Labels #2017

Open
aimeetacchi opened this issue Sep 9, 2022 · 1 comment
Open

Bug with React PDF and Highcharts SVG data Labels #2017

aimeetacchi opened this issue Sep 9, 2022 · 1 comment

Comments

@aimeetacchi
Copy link

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

@ghost
Copy link

ghost commented Sep 16, 2022

Maybe related to #1271

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant