Using jsDraw2DX, I am trying to use a series on concentric circles to display a position upon a floor plan. An array holds all drawn circles to permit clean removal. The original plan was to have them appear one-by-one, stay for 5 seconds, then to disappear one-by-one. I backed off of that due to troubles with setTimeout, and decided that just the circles image was enough, provided it could stay for a bit, then disappear, ready for the next locate operation. In the stripped down code below, The circles draw OK and remove OK, provided the one alert (marked in the code) is present, but not otherwise.
How might I make this work in the case that no alert was present?
How might I add a 5 second delay before removal?
How might it also work with an additional slight delay between drawing or removing each circle?
CTest
var oldCircle = new Array;
var iter = 0;
var rad = 4;
document.addEventListener("click", printMousePos);
function printMousePos(e) {
var cursorX = e.clientX-10;
var cursorY = e.clientY-10;
//alert("clicked: X: " + cursorX + " Y: " + cursorY);
rad = 4;
animateLocator(cursorX, cursorY);
}
function animateLocator(x, y) {
//alert("Entered animateLocator: rad: "+rad+" x: "+x+" y: "+y);
var start = new Date().getTime();
//alert("starting push");
pushall(rad, x, y);
iter = 0;
//for(i=0; i<10; i++) {
// pushFunc(rad+i*4, x, y);
//}
alert("starting pop"); // This alert box makes it work OK; otherwiudse nothing appears.
setTimeout(popall(), 2000);
}
function pushall(rad, x, y) {
iter = 0;
for(i=0; i<10; i++) {
pushFunc(rad+i*4, x, y);
}
}
function popall() {
iter = 0;
for(i=0; i<10; i++) {
popFunc();
}
}
function pushFunc(rad, x, y) { oldCircle.push(drawCircle(rad, x, y)); }
function popFunc() { oldCircle.pop().remove(); }
function drawCircle(rad, x, y) {
iter++;
//alert("drawCircle rad: "+rad+" x: "+x+" y: "+y);
//Create jxColor object
var col = new jxColor("red");
//Create jxPen object
var pen = new jxPen(col,'2px');
//Create jsGraphics object
var gr = new jxGraphics(document.getElementById("graphics"));
var ctr = new jxPoint(x,y);
var cir = new jxCircle(ctr, rad+iter*4, pen);
//alert("after jxcircle");
cir.draw(gr);
//alert("after draw");
return cir;
}
demo
Remove the alert and Change:
setTimeout(popall(), 2000);
To:
setTimeout(popall, 2000);
Related
is it possible to number a list of points in p5.js?
Right now I am using ml5.pj for face mesh detections, which outputs x and y coordinates for a set of 465 points.
I want to select a few. In order to do that, I need to know what are the corresponding indexes.
Any possible way to do this?
Not relevant, but on Grasshopper 3D, it is a component called "point list"
let facemesh;
let video;
let predictions = [];
function setup() {
createCanvas(640, 480);
video = createCapture(VIDEO);
video.size(width, height);
facemesh = ml5.facemesh(video, modelReady);
// This sets up an event that fills the global variable "predictions"
// with an array every time new predictions are made
facemesh.on("predict", results => {
predictions = results;
});
// Hide the video element, and just show the canvas
video.hide();
}
function modelReady() {
console.log("Model ready!");
}
function draw() {
// image(video, 0, 0, width, height);
background(255);
// We can call both functions to draw all keypoints
drawKeypoints();
}
// A function to draw ellipses over the detected keypoints
function drawKeypoints() {
for (let i = 0; i < predictions.length; i += 1) {
const keypoints = predictions[i].scaledMesh;
// Draw facial keypoints.
for (let j = 0; j < keypoints.length; j += 1) {
const [x, y] = keypoints[j];
fill(0, 255, 0);
ellipse(x, y, 3, 3);
}
}
}
Okay, if I understand you correctly you want to add labeling to each point. You could get sophisticated with this and have it on hover via tracking the cursor coordinates and using those as a key to access an object val. However, since you say you are not well rounded in programming -- I'm going to keep this super simple here...
We are just going to add text to where the point is and have it offset vertically by 5px. You can read more about text here in the p5.js documentation: https://p5js.org/reference/#/p5/text
Here's a link on template literals in js: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
// A function to draw ellipses over the detected keypoints
function drawKeypoints() {
for (let i = 0; i < predictions.length; i += 1) {
const keypoints = predictions[i].scaledMesh;
// Draw facial keypoints.
for (let j = 0; j < keypoints.length; j += 1) {
const [x, y] = keypoints[j];
fill(0, 255, 0);
ellipse(x, y, 3, 3);
text(`${i}-${j}`, x, y+5); // Draw Text with Index Labelling
}
}
}
Advanced: Showing the text on hover.
Create an Object to show the values based on x-y:i-j key:vals
Detect Mouse X, Y Coordinates
Display on Hover
const hoverCoords = {}
function draw() {
background(255);
drawKeypoints();
hoverCoords[`${mouseX}-${mouseY}`] && text(hoverCoords[`${mouseX}-${mouseY}`], x, y+5)
}
// A function to draw ellipses over the detected keypoints
function drawKeypoints() {
for (let i = 0; i < predictions.length; i += 1) {
const keypoints = predictions[i].scaledMesh;
// Draw facial keypoints.
for (let j = 0; j < keypoints.length; j += 1) {
const [x, y] = keypoints[j];
hoverCoords[`${x}-${y}`] = `${i}-${j}` // Create object key val
fill(0, 255, 0);
ellipse(x, y, 3, 3);
}
}
}
I haven't tested the above but you know should be the right approach using an object and setting coordinates as key vals and then being able to do a truthy match on that to display the i-j vals. Look into objects in javascript.
I'm trying to have a few circles drawn on the screen that do not move after initialization. Right now it is constantly drawing them to the screen instead of keeping them there. Here's the code:
for (let i = 0; i < 1; i++) {
//location
const r = random(100, 900);
const r2 = random(900, 100);
//size
const rS = random(50, 250);
const rS2 = random(250, 50);
//draw the ellipse with parameters
ellipse(r, r2, rS, rS2);
}
(This is with the p5.js library)
It sounds like your code is in the draw() function, which is called multiple times a second. Since you call random() every single time, it creates new parameters every single time. Instead, you should assign parameters to a variable somewhere else (like in the setup function) and then use those in the draw function. Something like:
var ellipses = [];
function setup() {
createCanvas(640, 480);
for (let i = 0; i < 1; i++) {
ellipses.push({
r: random(100, 300),
r2: random(300, 100),
rS: random(50, 250),
rS2: random(250, 50)
});
}
}
function draw() {
clear();
//location
//draw the ellipse with parameters
ellipses.forEach(function (e) {
ellipse(e.r, e.r2, e.rS, e.rS2);
})
}
<script src="https://unpkg.com/p5#1.1.9/lib/p5.min.js"></script>
I wrote a code to drawing polygons:
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', '100%');
svg.setAttribute('height', window.innerHeight);
document.querySelector('#bg').appendChild(svg);
for(var x = 0; x < polygons.length; x++){
var polygon = document.createElementNS(svg.namespaceURI, 'polygon');
polygon.setAttribute('points', polygons[0 + x]);
polygon.setAttribute('fill', polygons[0 + x][1]);
svg.appendChild(polygon);
}
My full code with polygon points:
http://codepen.io/anon/pen/WrqrbB
I would like to animate this polygons similar to this animation:
http://codepen.io/zessx/pen/ZGBMXZ
How to animate my polygons?
You can
call an animation function to manipulate your coordinate values as desired,
convert them to a string, e.g. using .join(),
send the resulting string back to the polygon as its points attribute value, redrawing the shape (as you were already doing when you initially created your shapes), and
have the animation function, when it is finished, call itself again at a reasonable built-in time-delay using requestAnimationFrame.
The following snippet gives a basic idea of what can be done.
(Note that I've redefined the array polygons in my example so that it is different from what you had, but that was done for the sake of simplicity in this example.)
var svg = document.getElementsByTagName("svg")[0];
var polygons = [], numSteps = 100, stepNum = 0;
var coords = [
[40, 20, 80, 20, 80, 60, 40, 60],
[140, 20, 180, 20, 160, 50]
];
for (var x = 0; x < coords.length; x++) {
polygons[x] = document.createElementNS(svg.namespaceURI, 'polygon');
polygons[x].setAttribute('points', coords[x].join());
svg.appendChild(polygons[x]);
}
function anim() {
for (var x = 0; x < coords.length; x++) {
coords[x] = coords[x].map(function(coord) {
return coord + 4 * (Math.random() - 0.5);
});
polygons[x].setAttribute('points', coords[x].join());
stepNum += 1;
}
if (stepNum < numSteps) requestAnimationFrame(anim);
}
anim();
<svg></svg>
UPDATE The above snippet shows generally how to animate a polygon. In your case, however, there is a further issue. On your codepen demo, it is clear that you have hard-coded the point coordinates for each polygon separately. Thus, if you want to move one point, you're going to have to update coordinates in at least 2 if not more places, for every polygon that touches that point.
A better approach would be to create a separate array of all points and then define each polygon with respect to that array. (This is similar to how things are sometimes done in 3D graphics, e.g. WebGL.) The following code snippet demonstrates this approach.
var svg = document.getElementsByTagName("svg")[0];
var polyElems = [], numSteps = 100, stepNum = 0;
var pts = [[120,20], [160,20], [200,20], [240,20], [100,50], [140,50], [180,50], [220,50], [260,50], [120,80], [160,80], [200,80], [240,80]];
var polyPts = [[0,1,5], [1,2,6], [2,3,7], [0,4,5], [1,5,6], [2,6,7], [3,7,8], [4,5,9], [5,6,10], [6,7,11], [7,8,12], [5,9,10], [6,10,11], [7,11,12]];
for (var x = 0; x < polyPts.length; x++) {
polyElems[x] = document.createElementNS(svg.namespaceURI, 'polygon');
polyElems[x].setAttribute('fill', '#'+Math.floor(Math.random()*16777215).toString(16));
// random hex color routine from http://www.paulirish.com/2009/random-hex-color-code-snippets/
drawPolygon(x);
}
function anim() {
pts = pts.map(function(pt) {
return pt.map(function(coord) {
return coord + 3 * (Math.random() - 0.5); // move each point
});
});
for (var x = 0; x < polyPts.length; x++) {drawPolygon(x);}
stepNum += 1;
if (stepNum < numSteps) requestAnimationFrame(anim); // redo anim'n until all anim'n steps done
}
anim(); // start the animation
function drawPolygon(x) {
var ptNums = polyPts[x];
var currCoords = [pts[ptNums[0]], pts[ptNums[1]], pts[ptNums[2]]].join();
// creates a string of coordinates; note that [[1,2],[3,4],[5,6]].join() yields "1,2,3,4,5,6"
polyElems[x].setAttribute('points', currCoords);
svg.appendChild(polyElems[x]);
}
<svg></svg>
I'm making a canvas game in JavaScript and have some trouble saving the data. I'm placing images on the canvas with a for-loop and I want to save information for each image in objects. For each image an object.
function CreateBlocks(){
for(var i = 0; i <= blocks; i++){
var img = new Image();
img.src = "/images/Block.png";
blockObject = {
x: x,
y: y,
points: 10
}
ctx.drawImage(img,x,y);
x += 100;
y += 100;
}
}
Now this obviously overwrites the blockObject everytime it loops. I tried adding to loop value to the name of the object like block[i]Object or blockObject[i] but that returns syntax errors.
I could just create a single dimension array for each value, but that seems rather messy to me. How can I create the objects in the loop?
Simply use an array and push the new object each time:
function CreateBlocks(){
var arr = [];
for(var i = 0; i <= blocks; i++){
var img = new Image();
img.src = "/images/Block.png";
arr.push({
x: x,
y: y,
points: 10
});
ctx.drawImage(img,x,y);
x += 100;
y += 100;
}
}
If you create a blockObjects array, your second idea, using the blockObject[i] syntax will work:
var blockObjects=[];
function CreateBlocks(){
for(var i = 0; i <= blocks; i++){
var img = new Image();
img.src = "/images/Block.png";
blockObjects[i] = {
x: x,
y: y,
points: 10
};
ctx.drawImage(img,x,y);
x += 100;
y += 100;
}
}
I've spent about 12 hours looking through this code, and fiddling with it, trying to find out where there's a recursion problem because I'm getting the, "maximum call stack size exceeded," error, and haven't found it. Someone smarter than me please help me!
so far, all I found was that when I make the object, spot, a circle, object, the problem disappears, but when I make it a, 'pip', I get this stack overflow error. I've gone over the pip class with a friggin' microscope, and still have no idea why this is happening!
var canvas = document.getElementById('myCanvas');
//-------------------------------------------------------------------------------------
// Classes
//-------------------------------------------------------------------------------------
//=====================================================================================
//CLASS - point
function point(x,y){
this.x = x;
this.y = y;
}
//=====================================================================================
// CLASS - drawableItem
function drawableItem() {
var size = 0;
this.center = new point(0,0);
this.lineWidth = 1;
this.dependentDrawableItems = new Array();
}
//returns the size
drawableItem.prototype.getSize = function getSize(){
return this.size;
}
// changes the size of this item and the relative size of all dependents
drawableItem.prototype.changeSize = function(newSize){
var relativeItemSizes = new Array;
relativeItemSizes.length = this.dependentDrawableItems.length;
// get the relative size of all dependent items
for (var i = 0; i < this.dependentDrawableItems.length; i++){
relativeItemSizes[i] = this.dependentDrawableItems[i].getSize() / this.size;
}
// change the size
this.size = newSize;
// apply the ratio of change back to all dependent items
for (var i = 0; i < relativeItemSizes.length; i++){
this.dependentDrawableItems[i].changeSize(relativeItemSizes[i] * newSize);
}
}
//moves all the vertices and every dependent to an absolute point based on center
drawableItem.prototype.moveTo = function(moveX,moveY){
//record relative coordinates
var relativeItems = new Array;
relativeItems.length = this.dependentDrawableItems.length;
for (var i = 0; i < relativeItems.length; i++){
relativeItems[i] = new point;
relativeItems[i].x = this.dependentDrawableItems[i].center.x - this.center.x;
relativeItems[i].y = this.dependentDrawableItems[i].center.y - this.center.y;
}
//move the center
this.center.x = moveX;
this.center.y = moveY;
//move all the items relative to the center
for (var i = 0; i < relativeItems.length; i++){
this.dependentDrawableItems[i].moveItemTo(this.center.x + relativeItems[i].x,
this.center.y + relativeItems[i].y);
}
}
// draws every object in dependentDrawableItems
drawableItem.prototype.draw = function(ctx){
for (var i = 0; i < this.dependentDrawableItems.length; i++) {
this.dependentDrawableItems[i].draw(ctx);
}
}
//=====================================================================================
//CLASS - circle
function circle(isFilledCircle){
drawableItem.call(this);
this.isFilled = isFilledCircle
}
circle.prototype = new drawableItem();
circle.prototype.parent = drawableItem.prototype;
circle.prototype.constructor = circle;
circle.prototype.draw = function(ctx){
ctx.moveTo(this.center.x,this.center.y);
ctx.beginPath();
ctx.arc(this.center.x, this.center.y, this.size, 0, 2*Math.PI);
ctx.closePath();
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.outlineColor;
if (this.isFilled === true){
ctx.fill();
}else {
ctx.stroke();
}
this.parent.draw.call(this,ctx);
}
//=====================================================================================
//CLASS - pip
function pip(size){
circle.call(this,true);
}
pip.prototype = new circle(false);
pip.prototype.parent = circle.prototype;
pip.prototype.constructor = pip;
//----------------------------------------------------------------------
// Objects/variables - top layer is last (except drawable area is first)
//----------------------------------------------------------------------
var drawableArea = new drawableItem();
var spot = new pip();
spot.changeSize(20);
drawableArea.dependentDrawableItems[drawableArea.dependentDrawableItems.length] = spot;
//------------------------------------------
// Draw loop
//------------------------------------------
function drawScreen() {
var context = canvas.getContext('2d');
context.canvas.width = window.innerWidth;
context.canvas.height = window.innerHeight;
spot.moveTo(context.canvas.width/2, context.canvas.height/2);
drawableArea.draw(context);
}
window.addEventListener('resize', drawScreen);
Here's the demo: http://jsfiddle.net/DSU8w/
this.parent.draw.call(this,ctx);
is your problem. On a pip object, the parent will be circle.prototype. So when you now call spot.draw(), it will call spot.parent.draw.call(spot), where this.parent is still the circle.prototype…
You will need to explicitly invoke drawableItem.prototype.draw.call(this) from circle.prototype.draw. Btw, you should not use new for the prototype chain.
Why would you write code like that? It's so difficult to understand and debug. When I'm creating lots of classes I usually use augment to structure my code. This is how I would rewrite your code:
var Point = Object.augment(function () {
this.constructor = function (x, y) {
this.x = x;
this.y = y;
};
});
Using augment you can create classes cleanly. For example your drawableItem class could be restructured as follows:
var DrawableItem = Object.augment(function () {
this.constructor = function () {
this.size = 0;
this.lineWidth = 1;
this.dependencies = [];
this.center = new Point(0, 0);
};
this.changeSize = function (toSize) {
var fromSize = this.size;
var ratio = toSize / fromSize;
this.size = toSize;
var dependencies = this.dependencies;
var length = dependencies.length;
var index = 0;
while (index < length) {
var dependency = dependencies[index++];
dependency.changeSize(dependency.size * ratio);
}
};
this.moveTo = function (x, y) {
var center = this.center;
var dx = x - center.x;
var dy = y - center.y;
center.x = x;
center.y = y;
var dependencies = this.dependencies;
var length = dependencies.length;
var index = 0;
while (index < length) {
var dependency = dependencies[index++];
var center = dependency.center;
dependency.moveTo(center.x + dx, center.y + dy);
}
};
this.draw = function (context) {
var dependencies = this.dependencies;
var length = dependencies.length;
var index = 0;
while (index < length) dependencies[index++].draw(context);
};
});
Inheritance is also very simple. For example you can restructure your circle and pip classes as follows:
var Circle = DrawableItem.augment(function (base) {
this.constructor = function (filled) {
base.constructor.call(this);
this.filled = filled;
};
this.draw = function (context) {
var center = this.center;
var x = center.x;
var y = center.y;
context.moveTo(x, y);
context.beginPath();
context.arc(x, y, this.size, 0, 2 * Math.PI);
context.closePath();
context.lineWidth = this.lineWidth;
context[this.filled ? "fill" : "stroke"]();
base.draw.call(this, context);
};
});
var Pip = Circle.augment(function (base) {
this.constructor = function () {
base.constructor.call(this, true);
};
});
Now that you've created all your classes you can finally get down to the drawing:
window.addEventListener("DOMContentLoaded", function () {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var drawableArea = new DrawableItem;
var spot = new Pip;
spot.changeSize(20);
drawableArea.dependencies.push(spot);
window.addEventListener("resize", drawScreen, false);
drawScreen();
function drawScreen() {
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
spot.moveTo(width / 2, height / 2);
drawableArea.draw(context);
}
}, false);
We're done. See the demo for yourself: http://jsfiddle.net/b5vNk/
Not only have we made your code more readable, understandable and maintainable but we have also solved your recursion problem.
As Bergi mentioned the problem was with the statement this.parent.draw.call(this,ctx) in the circle.prototype.draw function. Since spot.parent is circle.prototype the this.parent.draw.call(this,ctx) statement is equivalent to circle.prototype.draw.call(this,ctx). As you can see the circle.prototype.draw function now calls itself recursively until it exceeds the maximum recursion depth and throws an error.
The augment library solves this problem elegantly. Instead of having to create a parent property on every prototype when you augment a class augment provides you the prototype of that class as a argument (we call it base):
var DerivedClass = BaseClass.augment(function (base) {
console.log(base === BaseClass.prototype); // true
});
The base argument should be treated as a constant. Because it's a constant base.draw.call(this, context) in the Circle class above will always be equivalent to DrawableItem.prototype.draw.call(this, context). Hence you will never have unwanted recursion. Unlike this.parent the base argument will alway point to the correct prototype.
Bergi's answer is correct, if you don't want to hard code the parent name multiple times you could use a helper function to set up inheritance:
function inherits(Child,Parent){
Child.prototype=Object.create(Parent.prototype);
Child.parent=Parent.prototype;
Child.prototype.constructor=Child;
};
function DrawableItem() {
this.name="DrawableItem";
}
DrawableItem.prototype.changeSize = function(newSize){
console.log("changeSize from DrawableItem");
console.log("invoking object is:",this.name);
}
function Circle(isFilledCircle){
Circle.parent.constructor.call(this);
this.name="Circle";//override name
}
inherits(Circle,DrawableItem);
Circle.prototype.changeSize = function(newSize){
Circle.parent.changeSize.call(this);
console.log("and some more from circle");
};
function Pip(size){
Pip.parent.constructor.call(this,true);
this.name="Pip";
}
inherits(Pip,Circle);
var spot = new Pip();
spot.changeSize();
For a polyfill on Object.create look here.