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!
I did some tests. Points to consider:
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:
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; });
captureChartToBase64($parameters.ChartId, (base64Image) => { $parameters.Base64 = base64Image; });
It works on my computer.
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.
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 !
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?
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.