20
Views
5
Comments
Solved
[OutSystems Charts] Retrieving Binary Data from Outsystems Charts
outsystems-charts
Reactive icon
Forge asset by OutSystems
Application Type
Reactive
Service Studio Version
11.55.23 (Build 64171)

Hi everyone, 

I have a parent screen containing a child block in it. In the child block, I have a column chart, and in the parent screen, I have a button. I am trying to pass the binary data of the column chart to my parent screen through a click of a button, located on the parent screen, but I am having trouble on how do I retrieve the binary data of the chart in the first place.

The reason for doing this is because I need to pass that binary data to place the chart in an Excel sheet.

 Any help would be appreciated!

2016-04-22 00-29-45
Nuno Reis
 
MVP
Solution

I did some tests. Points to consider:

  • You can save a canvas or a svg into base64.
  • By default charts are inside ifs. If so, careful to only fetch after element exists on screen.
  • Having the id of chart container will return you the HTML code of the image. Not the image.

This code will work if you have a single SVG: just pass the container id and it will return a base64 with the image.


const container = document.getElementById($parameters.ChartId);
const svgElement = container.querySelector("svg");

if (!svgElement) {
    console.error($parameters.ChartId +" not found");
} else {
    const observer = new MutationObserver((mutationsList, observer) => {
        // You can add more sophisticated checks here
        if (svgElement.innerHTML.length > 0) {
            observer.disconnect(); // stop observing

            // Now the SVG is formed — proceed
            const serializer = new XMLSerializer();
            const svgString = serializer.serializeToString(svgElement);
            const blob = new Blob([svgString], { type: "image/svg+xml" });

            const reader = new FileReader();
            reader.onloadend = () => {
                const base64data = reader.result;
                console.log("Base64 SVG:", base64data);
                $parameters.Base64 = base64data;
            };

            reader.readAsDataURL(blob);
        }
    });

    observer.observe(svgElement, { childList: true, subtree: true });
}



But...

On my first test, the chart had multiple SVG so it only loaded the legend! I found a quick solution with AI, but it required a resolve() and promises aren't the easiest concept to explain.

So went with plan B. Created a function to:

  • Count elements until they are in the expected number.
  • Load them into a canvas.
  • Save the canvas as image.
  • Added an extra timeout as i don't think you need the images right away.

function captureChartToBase64(containerId, callback) {
    const container = document.getElementById(containerId);
    if (!container) {
        console.error(`Container with ID "${containerId}" not found.`);
        return;
    }
 
    const svgElements = container.querySelectorAll("svg");
    if (svgElements.length === 0) {
        console.error("No SVG elements found inside container.");
        return;
    }
 
    // Wait a bit to let rendering complete
    setTimeout(() => {
        const containerBox = container.getBoundingClientRect();
 
        let totalWidth = Math.ceil(containerBox.width);
        let totalHeight = Math.ceil(containerBox.height);
 
        const canvas = document.createElement("canvas");
        canvas.width = totalWidth;
        canvas.height = totalHeight;
        const ctx = canvas.getContext("2d");
 
        let loadedCount = 0;
 
        svgElements.forEach((svg) => {
            const serializer = new XMLSerializer();
            const svgClone = svg.cloneNode(true);
            const svgString = serializer.serializeToString(svgClone);
 
            const blob = new Blob([svgString], { type: "image/svg+xml" });
            const url = URL.createObjectURL(blob);
 
            const img = new Image();
            const svgBox = svg.getBoundingClientRect();
            const offsetX = svgBox.left - containerBox.left;
            const offsetY = svgBox.top - containerBox.top;
 
            img.onload = () => {
                ctx.drawImage(img, offsetX, offsetY);
                URL.revokeObjectURL(url);
                loadedCount++;
 
                if (loadedCount === svgElements.length) {
                    const base64 = canvas.toDataURL("image/png");
                    console.log("Chart image as Base64:", base64);
                    if (callback) callback(base64);
                }
            };
 
            img.onerror = () => {
                console.error("Failed to load an SVG image.");
                loadedCount++;
            };
 
            img.src = url;
        });
    }, 2000); // Delay to ensure chart finishes rendering


To call it, just do on every chart block This JS. One input parameter for elementId. One output parameter for base64.
captureChartToBase64($parameters.ChartId, (base64Image) => {
$parameters.Base64 = base64Image;
  });


It works on my computer.

UserImage.jpg
Awangku Aniq

I can see the base64 of the chart in the console log when calling the function, but it seems the base64 isn't getting passed to my output parameter.

UserImage.jpg
Awangku Aniq

Hi Nuno,

I fixed the issue I was having after asking ChatGPT, apparently it had something to do with the execution context ending already before the parameter is assigned. I fixed this triggering the event to send the base64 to the parent in a separate action from the JavaScript node.

captureChartToBase64($parameters.ChartId, (base64) => {    
$parameters.Base64 = base64;    
$actions.SendChartImage($parameters.Base64);
});

I don't really get how it works as I am still sending the same output parameter to the SendChartImage Client but hey it works haha.

Thanks !

2016-04-22 00-29-45
Nuno Reis
 
MVP

Hello.

OutSystems Charts is not ideal to export. But I've done similar before so you can give it a try.

My solution was to use a JS library to create charts on a canvas. Then you just need to get the id of the parent canvas and follow the standard JavaScript instructions to save it as a blob or base64.

Can you control the html around the chart?



UserImage.jpg
Awangku Aniq

Hi,

After searching around most of the day, it seems most solution points to the way you just described, but what do you mean by controlling the html around the chart ?

Thanks.

2016-04-22 00-29-45
Nuno Reis
 
MVP
Solution

I did some tests. Points to consider:

  • You can save a canvas or a svg into base64.
  • By default charts are inside ifs. If so, careful to only fetch after element exists on screen.
  • Having the id of chart container will return you the HTML code of the image. Not the image.

This code will work if you have a single SVG: just pass the container id and it will return a base64 with the image.


const container = document.getElementById($parameters.ChartId);
const svgElement = container.querySelector("svg");

if (!svgElement) {
    console.error($parameters.ChartId +" not found");
} else {
    const observer = new MutationObserver((mutationsList, observer) => {
        // You can add more sophisticated checks here
        if (svgElement.innerHTML.length > 0) {
            observer.disconnect(); // stop observing

            // Now the SVG is formed — proceed
            const serializer = new XMLSerializer();
            const svgString = serializer.serializeToString(svgElement);
            const blob = new Blob([svgString], { type: "image/svg+xml" });

            const reader = new FileReader();
            reader.onloadend = () => {
                const base64data = reader.result;
                console.log("Base64 SVG:", base64data);
                $parameters.Base64 = base64data;
            };

            reader.readAsDataURL(blob);
        }
    });

    observer.observe(svgElement, { childList: true, subtree: true });
}



But...

On my first test, the chart had multiple SVG so it only loaded the legend! I found a quick solution with AI, but it required a resolve() and promises aren't the easiest concept to explain.

So went with plan B. Created a function to:

  • Count elements until they are in the expected number.
  • Load them into a canvas.
  • Save the canvas as image.
  • Added an extra timeout as i don't think you need the images right away.

function captureChartToBase64(containerId, callback) {
    const container = document.getElementById(containerId);
    if (!container) {
        console.error(`Container with ID "${containerId}" not found.`);
        return;
    }
 
    const svgElements = container.querySelectorAll("svg");
    if (svgElements.length === 0) {
        console.error("No SVG elements found inside container.");
        return;
    }
 
    // Wait a bit to let rendering complete
    setTimeout(() => {
        const containerBox = container.getBoundingClientRect();
 
        let totalWidth = Math.ceil(containerBox.width);
        let totalHeight = Math.ceil(containerBox.height);
 
        const canvas = document.createElement("canvas");
        canvas.width = totalWidth;
        canvas.height = totalHeight;
        const ctx = canvas.getContext("2d");
 
        let loadedCount = 0;
 
        svgElements.forEach((svg) => {
            const serializer = new XMLSerializer();
            const svgClone = svg.cloneNode(true);
            const svgString = serializer.serializeToString(svgClone);
 
            const blob = new Blob([svgString], { type: "image/svg+xml" });
            const url = URL.createObjectURL(blob);
 
            const img = new Image();
            const svgBox = svg.getBoundingClientRect();
            const offsetX = svgBox.left - containerBox.left;
            const offsetY = svgBox.top - containerBox.top;
 
            img.onload = () => {
                ctx.drawImage(img, offsetX, offsetY);
                URL.revokeObjectURL(url);
                loadedCount++;
 
                if (loadedCount === svgElements.length) {
                    const base64 = canvas.toDataURL("image/png");
                    console.log("Chart image as Base64:", base64);
                    if (callback) callback(base64);
                }
            };
 
            img.onerror = () => {
                console.error("Failed to load an SVG image.");
                loadedCount++;
            };
 
            img.src = url;
        });
    }, 2000); // Delay to ensure chart finishes rendering


To call it, just do on every chart block This JS. One input parameter for elementId. One output parameter for base64.
captureChartToBase64($parameters.ChartId, (base64Image) => {
$parameters.Base64 = base64Image;
  });


It works on my computer.

UserImage.jpg
Awangku Aniq

I can see the base64 of the chart in the console log when calling the function, but it seems the base64 isn't getting passed to my output parameter.

UserImage.jpg
Awangku Aniq

Hi Nuno,

I fixed the issue I was having after asking ChatGPT, apparently it had something to do with the execution context ending already before the parameter is assigned. I fixed this triggering the event to send the base64 to the parent in a separate action from the JavaScript node.

captureChartToBase64($parameters.ChartId, (base64) => {    
$parameters.Base64 = base64;    
$actions.SendChartImage($parameters.Base64);
});

I don't really get how it works as I am still sending the same output parameter to the SendChartImage Client but hey it works haha.

Thanks !

Community GuidelinesBe kind and respectful, give credit to the original source of content, and search for duplicates before posting.