0

More Detailed background on this question is here, and I have attempted to look at the answer linked in that thread, which is where I am stuck.

A have created a PHP file that loads via ajax inside a div element in another PHP file via ajax calls shown in that other thread. The PHP file uses a database pull and html with chart.js to make a chart canvas, and I would like to save it as an image on the server.

Below is an example of code for what I'm trying to do.

// html stuff to make a html canvas is above here.
// PHP stuff to take post data, get data from database and insert it into barChartData and chartOptions vars is above here. (these two vars contain all the chart.js config stuff and the chart works.)

var ctx = document.getElementById("Cloudbar1").getContext("2d");
Chart.defaults.global.defaultFontColor = 'black';
Chart.defaults.global.defaultFontFamily = "Arial, sans-serif";
Chart.defaults.global.defaultFontSize = 16;
window.myBar = new Chart(ctx, {
type: "bar",
data: barChartData,
options: chartOptions
});

//convert canvas element to a url format.
var dataURL = ctx.toDataURL();

//now send the file to the server with yet another ajax call.
$.ajax({
type: "POST",
url: "savecanvasimgtoserver.php",
data: { 
imgBase64: dataURL
}
})

When trying to run this, I'm getting a console error saying

"Uncaught TypeError: ctx.toDataURL is not a function"

I don't think it matters as I've not got this far in the code for the above error yet, but the php file the above code points to just contains:

$img = $_POST['data'];
$img = str_replace('data:image/png;base64,', '', $img);
$img = str_replace(' ', '+', $img);
$fileData = base64_decode($img);
//saving
$fileName = 'chart.png';
file_put_contents($fileName, $fileData);

Taken from this question.

From what I know, when you get a 'x is not a function' when it is a function, that means that the thing your trying to put into the function is in some way invalid, but 'ctx' is defiantly the canvas element and works in this case.

Anseur
  • 53
  • 1
  • 9
  • what happens if you put ctx.toDataURL(); into the data imgBase64 attribute instead of the variable. I have the suspission that the chart isnt ready (loaded, created) yet. – cptnk Nov 26 '19 at 14:40
  • It should be the canvas rather than the context should it not? https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL – Professor Abronsius Nov 26 '19 at 15:23
  • @cptnk Going with that suspission, I tried both enclosing the ajax call to savecanvasimagetoserver.php in a 8 second timeout. (no change) and then doing your suggestion, but I've never done that before, so not sure of the syntax, I tried it as "data: { imgBase64: ctx.toDataURL()" } but this gave the exact same error. ( I did remember to comment out the convert line above!) – Anseur Nov 26 '19 at 15:28
  • Oh, I almost forgot, there was one thing I should thow in encase it matters. The output from all of this (I made the chart show inside a hidden div that pops up via jquery show() ) and at the time the code being discussed here is run, that div is still hidden with inline css of display:none;. Would that stop me using this canvas todataURL() feature? – Anseur Nov 26 '19 at 15:41

2 Answers2

0

Just use var ctx = document.getElementById("Cloudbar1"); as ctx It will work. You don't need to get 2D context to generate the dataurl from a canvas element and make sure the script is on the same domain with the request url of the image to avoid CORS errors.

steveola
  • 1
  • 1
  • 1
  • Assuming I understood your suggestion correctly, I tried `data: { imgBase64: document.getElementById("Cloudbar1").toDataURL() }` This results in the script running without error, but gives me a 0kb invalid png image. – Anseur Nov 26 '19 at 15:51
  • It depends on how what executes before and after data: {} try to call it outside the data object before adding it with a variable references or you can even slow the execution in milliseconds. – steveola Nov 26 '19 at 16:10
  • OK, this is getting me somewhere. Using the above method, It sort of works. If I keep this included in a timeout of several seconds, AND then trigger the 'show()' jquery by opening the hidden div that this chart shows up in before the timeout triggers, this code creates the image OK. However, if I don't open this div and let the 8 second timeout hit, the PHP file creates an invalid .png file on the server that cannot be opened. So it's certainly the fact the div in question is hidden that is causing the problem. I don't want to show the chart to the user for my use case. – Anseur Nov 26 '19 at 16:28
0

Full credit to both @cptnk and @steveola, who between them have led me to the correct answer.

In my case the problem was caused by the fact that toDataURL() does not seem to be able to parse elements that are set to display:none;. So even though you run the necessary Javascript to do the calculations, if it's not rendered in the browser, toDataURL() fails to capture the image data.

My solution was to change my show/hide method from switching the element from a css display:none and display: block to using a position outside the view-port, in my case I am now switching between a top value of 9999 and 0 on the element in question. Feels like a bit of a hack, but it works.

I would imagine that also using things like switching visibility from 0 to 100 would also work (because the image is still rendered, it' just then made transparent), but in my use case, I didn't want the element taking up room in the page and intercepting click events on elements behind it, which it still would with css visibility.

// This show/hide method stops toDataURL(); from capturing the canvas data if it's not visible at the time it's run.
$(document).on('click', '#showreport1link', function(){
                    $('#chartpreview1').show();
                });

// This show/hide method does not.
$(document).on('click', '#showreport1link', function(){
                    $('#chartpreview1').css('top', '0px'); //was top: 9999; until clicked.
                });
Anseur
  • 53
  • 1
  • 9