leeoniya / uPlot

📈 A small, fast chart for time series, lines, areas, ohlc & bars
MIT License
8.48k stars 370 forks source link

Title and Legend onto Canvas ? #907

Closed josefwilfinger closed 4 months ago

josefwilfinger commented 4 months ago

Hi,

do you think it would be possible to bring the title and the legend onto the canvas? In this way when in export the canvas as png. This information is also on the png.

Best Regards and thx 4 your work Josef

leeoniya commented 4 months ago

all the interactive stuff that is clickable or moves is in the dom for perf reasons.

you can render it all into an image using an svg foreignObject. demo is here: https://leeoniya.github.io/uPlot/demos/svg-image.html

josefwilfinger commented 4 months ago

oh thx :) i am 2 stupid. have not seen this example. searched for PNG and save export etc :)

leeoniya commented 4 months ago

i am 2 stupid

no, definitely not.

i want to improve the demo index by adding a search/filter and add relevant tags to each demo since many of them show off multiple things.

josefwilfinger commented 4 months ago

a remark regarding export function possibility. As an additional demo the following function triggers a save as png. It is as you will see nearly the same as your svg plot demo.

function saveUPlot(u, fileName)
{
    let pxRatio = devicePixelRatio;

    let rect = u.root.getBoundingClientRect();
    // rect of uPlot's canvas to get y shift due to title above it (if any)
    let rect2 = u.ctx.canvas.getBoundingClientRect();

    let htmlContent = u.root.outerHTML;

    let uPlotCssRules  = "";
    //take the uplotcss
    for (let i = 0; i < document.styleSheets.length; i++) {
        let styleSheet = document.styleSheets[i];

        if(styleSheet.href!== null && styleSheet.href.search('uPlot')>=0)
        {
            uPlotCssRules = styleSheet.cssRules;     
            break;
        }     
    }

    let cssContent = "";

    for (let { cssText } of uPlotCssRules)
            cssContent += `${cssText} `;

    let width = Math.ceil(rect.width * pxRatio);
    let height = Math.ceil(rect.height * pxRatio);

    let viewBox = `0 0 ${Math.ceil(rect.width)} ${Math.ceil(rect.height)}`;

    let svgText = `
            <svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="${viewBox}">
                    <style>
                            body { margin: 0; padding: 0; }
                            ${cssContent}
                    </style>
                    <foreignObject width="100%" height="100%">
                            <body xmlns="http://www.w3.org/1999/xhtml">${htmlContent}</body>
                    </foreignObject>
            </svg>
    `;

    let can = document.createElement('canvas');
    let ctx = can.getContext('2d');
    can.width = width;
    can.height = height;
    can.style.width = Math.ceil(rect.width) + "px";
    can.style.height = Math.ceil(rect.height) + "px";    

    let DOMURL = window.URL || window.webkitURL || window;

    let img = new Image();
    //let blob = new Blob([svgText], {type: 'image/svg+xml;charset=utf-8'});
    //let url = DOMURL.createObjectURL(blob);
    //using the following instead above mitigates crossOrigin problems with tainted canvas
    let url = "data:image/svg+xml;charset=utf-8," + svgText; 

    ctx.drawImage(u.ctx.canvas, 0, (rect2.top - rect.top) * pxRatio);

    img.onload = () => {
            ctx.drawImage(img, 0, 0);

            let link = document.createElement('a');
            link.download = fileName+'.png';            
            link.href = can.toDataURL("image/png");
            link.click();

            DOMURL.revokeObjectURL(url);
    };

    img.src = url;

}