highcharts / node-export-server

Highcharts Node.js export server
Other
348 stars 252 forks source link

Inject css variables using nodejs module #522

Open sebherrerabe opened 3 weeks ago

sebherrerabe commented 3 weeks ago

Hello,

My team and I are attempting to implement the export module to enable the downloading of our charts. However, we’re encountering an issue due to our extensive use of CSS variables, which your server doesn’t seem to accept as it doesn’t allow external resources.

I’ve begun setting up the export server Node.js module on our Express app. The image generation appears to be functioning well, but we’re still facing the same issue. We’re unable to inject the CSS variables. Here’s how we’ve implemented it:

module.exports = {
    downloadChart: async (req, res) => {
        const exportSettings = {
            export: {
                type: req.body.type,
                // this line is just for test purposes
                outfile: "./samples/module/" + req.body.filename + "." + "png",
                scale: req.body.scale
            },
            pool: {
                minWorkers: 1,
                maxWorkers: 1
            },
            payload: {
                svg: req.body.svg
            },
            customLogic: {
                allowCodeExecution: true,
                resources: {
                    css: "here I'd like to inject our variables"
                }
            }
        };
        const options = exporter.setOptions(exportSettings);

        // Initialize a pool of workers
        await exporter.initPool(options);

        // Perform an export
        exporter.startExport(exportSettings, function(res, err) {
            console.log(res);
            // The export result is now in res.
            // It will be base64 encoded (res.data).
            // use fs to write the file
            fs.writeFile(exportSettings.export.outfile, res.data, "base64", function(err) {
                if (err) {
                    console.error(err);
                }
            });

            // Kill the pool when we're done with it.
            exporter.killPool();
        });
        // send file
        res.status(200);
    }
};
jszuminski commented 2 weeks ago

Thanks for reporting @sebherrerabe!

As a workaround, try exporting from a JSON config and not from an SVG payload. Then, the resources.css option should most likely be taken into account properly. If you try it out and it does not work, please share your chart's config as well as the CSS variables that you're setting so I could investigate this further.

sebherrerabe commented 2 weeks ago

@jszuminski Thanks for replying! In fact, I found the reason why the variables weren’t working. I think the documentation is wrong, at least for the export settings of the Node.js module. The property is not "customLogic", instead it’s "customCode". I was able to inject our CSS variables successfully.

Unfortunately, that wasn’t our only problem… Sadly, our HTML elements do not render as desired when exporting a chart (we use React). As you might’ve guessed, what we are trying to do is to use the Exporting module provided by you in the front and change the URL to the one pointing at our custom Express endpoint (where we import the export server module).

I believe the Exporting module in the front converts the whole chart into an SVG element but this does not support some features natively. This is a problem to be honest, because we use HTML almost everywhere (we have lots of Highcharts charts). For now, we are even questioning if we should use a library to just take screenshots in the browser.

Config in NodeJs now:

const exportSettings = {
    export: {
        type: req.body.type,
        // TODO: Define the actual directory where the files will be saved
        outfile: "./samples/module/" + randomName + "." + req.body.type.split("/")[1],
        scale: req.body.scale
    },
    pool: {
        minWorkers: 1,
        maxWorkers: 1
    },
    payload: {
        svg: req.body.svg
    },
    customCode: {
        allowCodeExecution: true,
        allowFileResources: true,
        resources: {
            // TODO: Find a way to import the css directly from the file system
            css: "@import 'http://localhost:3001/compiled/site.css';"
        }
    }
};

Config in the front:

/// ...imports
// we init the exporting module
Exporting(Highcharts);

const ChartComponent = (props: Props): JSX.Element => {
    // some logic
    const chartOptions = {
        exporting: {
            url: "/charts/download",
            allowHtml: true
        },
        plotOptions: {
            column: {
                borderRadius: 4,
                borderColor: "white",
                stacking: "normal",
                dataLabels: {
                    enabled: true,
                    align: "center",
                    useHTML: true,
                    style: {
                        color: "white",
                        textOutline: "none"
                    },
                    formatter: JSXFormatter<PointLabelObject>(({ point }) => {
                        if (checkIfSimpleColumn(point)) {
                            return null;
                        }
                        // THE FOLLOWING COMPONENT DOES NOT RENDER CORRECTLY
                        return (
                            <Badge variant={dataCollectionStatusData[point.series.userOptions.id].variant}>
                                {point.y !== point.total && <strong style={{ marginRight: 2 }}>{point.y}</strong>}
                                {point.series.name}
                            </Badge>
                        );
                    })
                }
                // ... other properties
            }
        }
    };

    return (
        <HighchartsReact
            highcharts={Highcharts}
            options={chartOptions}
        />
    );
};

Expected result: image

Actual result: image