forEach problem: lineTo is not defined at CanvasRenderingContext2D - javascript

I tried using a forEach loop on an array of points and a canvas drawing context.
This is a simplified sample:
<html>
<head>
<<script>
// assume this is going to be some code independent of the calling web page
function showPoints(context,data) {
context.beginPath();
context.moveTo(0,0);
context.lineTo(0, data[0]); // OK
data.forEach(function(pt,i){
lineTo(10*i, pt); // Error
lineTo(10*(i+1), pt);
},context); // to be made available to the forEach callback
context.stroke();
}
</script>
</head>
<body>
<canvas width="500" height="500" id="myCanvas">
<script>
const data = [10,20,25,30,25];
const c = document.getElementById('myCanvas');
const ctx = c.getContext("2d");
showPoints(ctx, data);
</script>
</body>
<html>
This fails with ReferenceError: lineTo is not defined at CanvasRenderingContext2D
Sure, ctx.lineTo is defined, used before and still visible directly above the forEach line.
Passing ctx as a this parameter seems ok as well, in my understanding of the error message.
A regular named function did not make a difference.
Any hints what I'm doing/understanding wrong, please? (javascript is not my native language)
Sure I could replace the forEach construct, or use the global context constant, but I want to learn its usage.

As #Titus mentioned in his comment already, to use a method context (this) you can pass it to the forEach callback, but you have to use it there by explicitly mentioning this.
function showPoints(context,data) {
context.beginPath();
context.moveTo(0,0);
data.forEach(function(pt,i){
this.lineTo(10*i, pt);
this.lineTo(10*(i+1), pt);
},context); // to be made available to the forEach callback
context.stroke();
}

As many of the comments under your post have mentioned, there is no lineTo() available to be called. However, ctx.lineTo() does exist, and would not cause an error.

Related

Hook canvas's toDataURL

[EDIT] I should have been more specific (thanks for the response Below the Radar!). I'd like the trigger to console.log to capture the returned URI when the object calls toDataURL. In the linked jsfiddle, the object calling toDataURL is canvas. This may be a sticking point, because I may need to capture the calling object as well? I could accomplish what I want by simply encapsulating canvas.toDataURL() in a console.log statement, but I'd like this to be more dynamic than that---which led me to think of adding a hook on toDataURL
jsFiddle, update: https://jsfiddle.net/spe4q1d8/170/
I'm trying to create a hook that I could place at the top or bottom of a JavaScript file which would trigger console.log every time a specific function is called. The particular function I want to hook is toDataURL() HTMLCanvasElement.toDataURL().
I've been able to set up a hook on something like alert or console.log itself, but can't figure out how to hook toDataURL.
jsFiddle, hook on alert and hook on document.createElement()
https://jsfiddle.net/spe4q1d8/142/
References
Hooking document.createElement using function prototype
Need to hook into a javascript function call, any way to do this?
https://blog.pengoworks.com/index.cfm/2012/1/12/Adding-custom-callbacks-to-existing-JavaScript-functions
you can extend the prototyped function for instance:
var toDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function(type, encoderOptions) {
var uri = toDataURL.call(this, type, encoderOptions);
console.log(uri);
return uri;
}
var canvas = document.createElement('canvas');
canvas.toDataURL()

pjs sketches and html canvas

I'm trying to understand what I'm doing when I stick a pjs sketch to an html canvas in the following way.
<body>
<canvas id="mycanvas" style="border: 1px solid black;"></canvas>
</body>
<script src="https://cdn.jsdelivr.net/processing.js/1.4.8/processing.min.js">
</script>
<script>
var sketchProc = function(processingInstance) {
with (processingInstance) {
size(800, 400);
frameRate(30);
draw = function() {
background(235, 245, 255);
};
}};
var canvas = document.getElementById("mycanvas");
var processingInstance = new Processing(canvas, sketchProc);
</script>
When I began learning this stuff, I was eager to add my animations to an html doc. I learned the above way pretty much how I imagine a hamster can learn to push a button and receive a pellet; that is, I don't understand the mechanism well, but the result is positive.
Here is the above example in action:
https://jsfiddle.net/h1Lb91ux/
Here is my attempt at making sense of this.
The following code is defining a function called sketchProc.
var sketchProc = function(processingInstance) {
with (processingInstance) {
size(800, 400);
frameRate(30);
draw = function() {
background(235, 245, 255);
};
}};
This doesn't return anything because we're just defining sketchProc, not calling it. It's argument is processingInstance, which is being defined as a local variable just like if I did:
var functionX = function(x) {
return x;
};
But I do not understand the with statement. I know of with statements from excel-vba, which can be used to attach multiple methods to an object without having to repeatedly type out the object name. Is this the same?
The following statement makes perfect sense.
var canvas = document.getElementById("mycanvas");
But this next one is a source of confusion:
var processingInstance = new Processing(canvas, sketchProc);
To me, this looks like we're making a single instance or object from an object constructor function (in javascript) or a class (in processing), which takes 2 arguments: canvas and sketchproc. We're calling this specific instance processingInstance, which confusingly is the same name as the local variable used in the function sketchProc.
I hope this is not too rambling.
The following code is defining a function called sketchProc.
var sketchProc = function(processingInstance) {
with (processingInstance) {
size(800, 400);
frameRate(30);
draw = function() {
background(235, 245, 255);
};
}};
It's defining a function object that itself contains functions like draw(). You can read more about function objects here, but basically this is just an instance that contains functions that Processing can call.
But I do not understand the with statement.
Try googling something like "javascript with" for a ton of results, including this one. But my understanding is that it's a shortcut to avoid having to call processingInstance. before everything. You can try removing the with statement and putting processingInstance. before any Processing function to see the alternative. The with is optional, but makes you code slightly shorter. I personally don't like the use of with here, but that's just me.
But this next one is a source of confusion:
var processingInstance = new Processing(canvas, sketchProc);
To me, this looks like we're making a single instance or object from
an object constructor function (in javascript) or a class (in
processing), which takes 2 arguments: canvas and sketchproc. We're
calling this specific instance processingInstance, which confusingly
is the same name as the local variable used in the function
sketchProc.
Your understanding sounds about right. Creating an instance of Processing is what kicks off all of the Processing magic, calling the setup() function and the draw() function for you automatically. You can also rename the variable if it confuses you.
Taking a step back, this code uses Processing.js's instance mode, which is why you have all this extra code that you don't really understand. I would recommend you start with the more basic global mode, which would allow your code to look like this:
<script type="application/processing">
function setup(){
size(800, 400);
frameRate(30);
}
function draw() {
background(235, 245, 255);
};
</script>
<canvas> </canvas>
I'm also curious why you're using Processing.js. If you're coming from a JavaScript background, you might have more luck with P5.js instead.

Use p5.js functions inside global variables

I want to have a file with global variables, for example:
function Globals() {
}
Globals.gravity = createVector(0, -9.81);
Unfortunately p5.js functions can only be used when they are declared inside setup() or draw() or are called from one of these functions.
My question is what would be the best approach to make globals easy to use?
My only idea is to make them functions, but that is not very pretty (you have to call function to get a value) and it is probably slow, because each access to a global variable requires making a call.
Globals.gravity = function() { return createVector(0, -9.81); }
Well, fortunately, you could use p5.js functions outside setup() and draw() function.
In order to use those functions, you need to call new p5() beforehand, like so ...
new p5(); //<-- call this
function Globals() {}
Globals.gravity = createVector(0, -9.81);
For more info, refer here

Running JS code in limited context

I'm trying to run trusted JS code in an "isolated" context.
Basically came up with this method:
function limitedEval(src, context) {
return (function() {
with(this) {
return eval(src)
}
}).call(context)
}
This works great, however when the script is using the var keyword it is stored in the execution context as opposed to the provided context in the with statement (which I understand is by design). So for example, the following code doesn't work:
var ctx = {};
limitedEval('var foo = "hello"', ctx);
limitedEval('alert(foo)', ctx); // error: foo is undefined
I'd like to be able to call limitedEval() multiple times and reuse the context. Is that possible?
This seems like a very interesting problem. The problem with your code is that you generate new function every time you execute limitedEval. This means that whatever variables you create using var keyword, will only exist within the context of that function. What you really need is to have 1 function per context and reuse that function's context. The most obvious way to do that is by using generators. Generators are essentially functions that can be paused and then restarted.
// IIFE to store gen_name
var limitedEval = function() {
// Symbol for generator property so we don't pollute `ctx` namespace
var gen_name = Symbol();
return function limitedEval(src, context) {
if(!(gen_name in context)) {
// Generator that will run eval and pause til getting the next source code
var generator = function * () {
with(this) {
while(true) {
yield eval( yield );
}
}
};
context[gen_name] = generator.call(context);
// Initially, we need to execute code until reaching the first `yield`
context[gen_name].next();
}
// First, send the source to the `eval`
context[gen_name].next( src );
// Second, get the `eval` result
return context[gen_name].next().value;
};
}();
And now you can call
var ctx = {};
limitedEval('var foo = "hello"', ctx);
limitedEval('alert(foo);', ctx);
Every limitedEval call will now reuse whatever generator function it will find on the provided ctx object. If the function doesn't exist, it will create it and put it on the ctx object. Because you now have only one function per ctx, it will reuse the function's context when creating variables with var keyword. Note: you will not be able to look up those variables via the ctx object, because they will only exist within the function's context.
I'm not entirely sure if you can achieve the same result without using generators.
Edit: others made great suggestions so I replaced randomly generated property with Symbol and did with(this) instead of with(context)

why can't these variables be referenced globally in my p5 functions?

I'm having trouble understanding how specially defined p5 functions can reference globally defined variables. Functions like this one -- where a constant supplies the argument for a p5 function locally -- work just fine.
function setup() {
}
function draw() {
ellipse(50, 50, 80, 80);
}
But for some reason, I can't get something like this to draw an ellipse:
var CANVAS_HEIGHT = 1024;
var CANVAS_WIDTH = 768;
var RADIUS = 150;
var circleColor = 150;
var bgColor = 50;
function setup() {
backgroundColor = color(bgColor);
createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
}
function draw() {
fill(circleColor);
ellipse(CANVAS_WIDTH/2, CANVAS_HEIGHT/2, RADIUS*2, RADIUS*2);
}
Apparently the setup function does create a canvas referencing the global variables. The draw function, however, doesn't seem to reference them. What am I missing here? Thanks for any help.
You can't use variables like that in the setup() function.
You have to use the values directly, and you have to assign the variables inside the setup() function:
var CANVAS_HEIGHT;
var CANVAS_WIDTH;
var RADIUS;
var circleColor;
var bgColor;
function setup() {
backgroundColor = color(50);
createCanvas(1024, 768);
CANVAS_HEIGHT = 1024;
CANVAS_WIDTH = 768;
RADIUS = 150;
circleColor = 150;
bgColor = 50;
}
From the p5.js faq:
Why can't I assign variables using p5 functions and variables before setup()?
In global mode, p5 variable and function names are not available
outside setup(), draw(), mousePressed(), etc. (Except in the case
where they are placed inside functions that are called by one of these
methods.) What this means is that when declaring variables before
setup(), you will need to assign them values inside setup() if you
wish to use p5 functions. For example:
var n;
function setup() {
createCanvas(100, 100);
n = random(100);
}
The explanation for this is a little complicated, but it has to do
with the way the library is setup in order to support both global and
instance mode. To understand what's happening, let's first look at the
order things happen when a page with p5 is loaded (in global mode).
Scripts in are loaded. of HTML page loads (when this is complete, the
onload event fires, which then triggers step 3). p5 is started, all
functions are added to the global namespace. So the issue is that the
scripts are loaded and evaluated before p5 is started, when it's not
yet aware of the p5 variables. If we try to call them here, they will
cause an error. However, when we use p5 function calls inside setup()
and draw() this is ok, because the browser doesn't look inside
functions when the scripts are first loaded. This is because the
setup() and draw() functions are not called in the user code, they are
only defined, so the stuff inside of them isn't run or evaluated yet.
It's not until p5 is started up that the setup() function is actually
run (p5 calls it for you), and at this point, the p5 functions exist
in the global namespace.

Categories

Resources