I have this jsfiddle which creates a pattern of 4 points.
What I want is for it to continuously draw the projected line until the user click for point B, then point C and D.
function draw(){
//
ctx.clearRect(0,0,cw,ch);
// draw connecting lines
for(var i=0;i<connectors.length;i++){
var c=connectors[i];
var s=anchors[c.start];
var e=anchors[c.end];
ctx.beginPath();
ctx.moveTo(s.x,s.y);
ctx.lineTo(e.x,e.y);
ctx.stroke();
}
// draw circles
for(var i=0;i<anchors.length;i++){
ctx.beginPath();
ctx.arc(anchors[i].x,anchors[i].y,radius,0,Math.PI*2);
ctx.fill();
ctx.fillText(anchors[i].label,anchors[i].x-5,anchors[i].y-15);
}
}
Ok so, basically you need your connector adding function to be slightly smarter, so we can make this work like in this fiddle
(You were adding way to many connectors, and it stopped at length over 7, this fixes both those)
if(draggingIndex==-1 && fullDrag == null){
addAnchor(startX,startY);
var al = anchors.length-1;
var almod4 = al%4;
if(almod4==1){
connectors.push({start:al-1,end:al});
}
if(almod4==2){
connectors.push({start:al-2,end:al});
connectors.push({start:al-1,end:al});
}
if(almod4==3){
connectors.push({start:al-2,end:al});
connectors.push({start:al-1,end:al});
}
draw();
}
As you can see, based on the value of anchors.length-1 modular 4, we know if we need to draw 1 or 2 lines. In our draw function we can then add:
if (anchors.length>0 && anchors.length%4>0){
ctx.strokeStyle='gray';
var al = anchors.length-1;
var almod4 = al%4;
if (almod4==1 || almod4==2){
//draw extra line
ctx.beginPath();
ctx.moveTo(anchors[al-1].x,anchors[al-1].y);
ctx.lineTo(mouseX,mouseY);
ctx.stroke();
}
ctx.beginPath();
ctx.moveTo(anchors[al].x,anchors[al].y);
ctx.lineTo(mouseX,mouseY);
ctx.stroke();
}
Note that instead of checking for almod4 being 2 or 3, we check for 1 and 2, because that means we're in the process of adding 2 or 3.
Now all you need to do is tell it to draw at every mouseover, and voila, preview lines.
Related
I'm using ChartJS to draw a line chart. The problem is that I'm trying to draw some small lines on Vertical axis to mark the axis in each 10% like this:
extendChartLine: function() {
window.Chart.plugins.register({
beforeDraw: function(chart) {
var ctx = chart.chart.ctx,
chartArea = chart.chartArea,
spacing = (chartArea.bottom - 40) /10;
for(var i = 1; i < 10; i++) {
ctx.restore();
ctx.beginPath();
ctx.moveTo(40, chartArea.bottom - i*spacing);
ctx.strokeStyle = '#333';
ctx.lineTo(47, chartArea.bottom - i*spacing);
ctx.lineWidth = 0.2;
ctx.stroke();
ctx.closePath();
ctx.save();
}
}
});
The problem is that is function is called many times causing those lines are drawing more than one time and collapsing on each others (I know that because some lines are darker than others)
I tried to use a flag variable in order to make use these codes are executing on 1 time only, but no lines are drawing on the chart.
I faced the same, if the charts have the animation option on, I think that is causing the redraw to be called multiple times
So, basically, I have created a canvas on which I draw points. The coordinates of those points are obtained via a JSON obtained from a url. Here's the function that draws the points:
function drawDATA() {
getJSON('http://theossrv2.epfl.ch/aiida_assignment2/api/points/',
function (err, data) {
if (err !== null) {
alert('Something went wrong: ' + err);
} else {
for (var i = 0; i < data.circles.length; i++) {
c.beginPath();
c.arc(data.circles[i].x, data.circles[i].y, 5, 0, 2 * Math.PI);
c.strokeStyle = 'red';
c.stroke();
c.fillStyle = 'red';
c.fill();
}
}
});
}
where c is the context of my canvas.
Now, in the HTML code, I have a button that calls this function when it is pressed:
<input id="Refresh" type="button" value="Refresh" onclick="drawDATA();" />
As you can probably imagine, each time I press the button, new points are added to the canvas (while the points that were previously drawn stay). For example, once I open my HTML file and press the button twice, I have 6 points on my canvas (each time I click on the button, three points appear).
My problem is that I want the previous point to fade out in the following manner:
The first time I press the button, the first 3 points (call them A,B,C) appear normally.
The second time I press the button, three new points appear (D,E,F) and the opacity of A,B,C is set to 75%
The third time I press the button, G,H,I appear, the opacity of A,B,C is set to 50% and the opacity of D,E,F is set to 75%
... and so on (so, after 5 clicks, the points A,B,C must completely disappear).
I tried to approach this problem by creating a CSS file that allows me to fadeOut some elements of my canvas, but it was far from the expected result (so I don't think it is necessary for me to show it).
I hope I was clear enough. Thank you for your help.
The simplest way would be to paint your whole canvas with an opaque layer before applying your new points:
c.beginPath();
c.fillStyle = 'rgba(255, 255, 255, 0.25)';
c.fillRect(0, 0, c.canvas.width, c.canvas.height);
c.closePath();
// draw new points here
Here's a live example
Queue Array
If instead of painting the entire canvas with an opaque layer
you could create a queue array of sets of points that also have each a alpha property.
function drawSET( set ) {
for (var i = 0; i < set.circles.length; i++) {
var circle = set.circles[i];
c.beginPath();
c.arc(circle.x, circle.y, 5, 0, 2 * Math.PI);
c.strokeStyle = 'rgba(255, 0, 0,'+ set.alpha +')';
c.stroke();
c.fillStyle = 'rgba(255, 0, 0,'+ set.alpha +')';
c.fill();
c.closePath();
}
}
var queue = [];
var alphaStep = 0.2;
function drawDATA() {
getJSON('http://theossrv2.epfl.ch/aiida_assignment2/api/points/', function (err, data) {
if (err) return console.log('Something went wrong: ' + err);
queue.push( {alpha: 1, circles: data.circles} ); // Append
if (queue.length > 1/alphaStep) queue.splice(0,1); // Remove first
c.clearRect(0, 0, c.canvas.width, c.canvas.height); // clear canvas
for (var i=0; i<queue.length; i++) {
var set = queue[i];
drawSET( set ); // Draw as is
set.alpha -= alphaStep; // And lower alpha for next iteration
}
});
}
Here's a live example - controlling each set opacity
Re-render scene using alpha to fade.
You need to create an array holding the points. Each point has an alpha value that you decrease each time you draw it. When that alpha value reaches zero you remove the point from the array.
When you get new points you add them to the array of points setting their alpha to one and then render all the points.
Each time you draw the circles you must clear the canvas and draw all the circles. If you have other content on the canvas you must either redraw that content or save it as a separate canvas.
const points = []; // array to hold points
// clear canvas and redraw all points
function drawPoints(ctx, points){
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
for (var i = 0; i < points.length; i ++) {
const point = points[i];
ctx.beginPath();
ctx.globalAlpha = point.alpha;
ctx.fillStyle = "red";
ctx.arc(point.x, point.y, 5, 0, 2 * Math.PI);
ctx.fill();
// decrease alpha. Will fade through 0.75, 0.50, 0.25
point.alpha -= 0.25;
if (point.alpha <= 0) { // remove point
points.splice(i--,1);
}
}
ctx.globalAlpha = 1; // restore the alpha
}
function drawDATA() {
getJSON('http://theossrv2.epfl.ch/aiida_assignment2/api/points/',
function (err, data) { // I am ignoring error
// add new points to points array
points.push(...data.circles.map(circle => {
return {x : circle.x, y : circle.y, alpha : 1};
}));
// Use animation frame to ensure its presented correctly.
requestAnimationFrame(()=> {
drawPoints(c, points); // draw points
});
}
});
}
You could memorize the dots you drew in one iteration and redraw them with a color closer to the background color in the next iteration. You cannot retrieve previously drawn shapes in your canvas so it should be the best way to do it.
use a fillStyle of rgba.
Keep the alpha values in your data.circles array, then decrease them as desired, and use it
c.fillStyle = "rgba(255,0,0,"+data.circles[i].alpha+")";
I have a canvas which is filled with the webcam stream.
On top of that, I want to have rectangles (just the borders of a rectangle) appear for 1 second at random areas. So every second a rectangle will pop up, and the next it will be somewhere else.
Currently, rectangles are appearing every second but the last doesn't disappear. Therefore, on the 2nd second there are 2 rectangle, 3rd second 3 rectangles, etc...
I need to find a way to either have the rectangle appear for 1 second, have it removed after 1 second, or have it moved after 1 second: results are the same for me.
let sx; // x axis
let sy; // y axis
let i = setInterval( axisChanger, 1000 ); // pops up every second
function axisChanger() {
sx = getRandomInt(0, 535); // gets a random num
sy = getRandomInt(0, 445); // gets a random num
}
requestAnimationFrame(animate);
function animate(t) {
requestAnimationFrame(animate);
randomRect();
}
function randomRect() {
ctx.rect(sx, sy, 50, 30); // these 4 lines make a hollow rectangle: border only.
ctx.lineWidth = 2;
ctx.strokeStyle = '#FF0000';
ctx.stroke();
}
If I use clearRect(), then the inside of the rectangle will also be gone... and so part of the webcam stream with it.
If you only need to draw a single rectangle, replace rect() and stroke() with strokeRect():
function randomRect() {
ctx.lineWidth = 2;
ctx.strokeStyle = '#FF0000';
ctx.strokeRect(sx, sy, 50, 50);
}
The reason for the current behavior is that rect() adds to the main path and accumulates all rect() calls. Because of that the path must be cleared using beginPath().
But since you are only using a single rectangle you can simply use strokeRect() which does not add anything to the path but renders directly.
The alternative however, would be:
function randomRect() {
ctx.beginPath(); // clear path and sub-paths
ctx.rect(sx, sy, 50, 30); // these 4 lines make a hollow rectangle: border only.
ctx.lineWidth = 2;
ctx.strokeStyle = '#FF0000';
ctx.stroke();
}
I'm currently making a website, and have recently discovered the wonders of HTML5 canvas.
I have made a triangle using HTML5 canvas and JavaScript (simple, I know).
I will be placing a triangle after each section of the webpage to indicate the user should continue to scroll down. I will be using multiple triangles on the webpage, each in a different colour (yes, I spell it that way).
So, I want to make a function that uses an array of the canvas elements on the page to change the colour of each of the triangles. For example the first will be blue, the second red, and the third yellow.
Here is the code so far.
Problem: it does not work! Where have I gone wrong? Do you have a better solution? I am new to Stack Overflow.
// declare all global variables
var allCanvasElements = document.getElementsByClassName('canvas');
var canvasArrayLength = allCanvasElements.length;
function canvasLoop() {
// create canvas
var c = allCanvasElements[i];
var ctx = c.getContext("2d");
for (i = 0; i < canvasArrayLength; i++) {
// canvas code
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(60,0);
ctx.lineTo(30,30);
// selects current canvas, gives colour
if (c == allCanvasElements[i]) {
ctx.strokeStyle = "red";
ctx.fillStyle = "red";
}
// fills and strokes the canvas
ctx.fill();
ctx.stroke();
}
}
window.addEventListener("load", canvasLoop(), false);
literally just read through the code again after posting, and found that I was using using 'i' to find the current element in the canvas array before I had even declared the loop that uses 'i'. Silly me! A quick change, now it works just fine. Now, on to the rest of the Canvas elements! Here is the corrected code if you're interested -
// declare all global variables
var allCanvasElements = document.getElementsByClassName('canvas');
var canvasArrayLength = allCanvasElements.length;
function canvasLoop() {
// create canvas
var c = allCanvasElements[i];
var ctx = c.getContext("2d");
for (i = 0; i < canvasArrayLength; i++) {
// canvas code
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(60,0);
ctx.lineTo(30,30);
// selects current canvas, gives colour
if (c == allCanvasElements[i]) {
ctx.strokeStyle = "red";
ctx.fillStyle = "red";
}
// fills and strokes the canvas
ctx.fill();
ctx.stroke();
}
}
window.addEventListener("load", canvasLoop(), false);
I want to be able to draw an array of balls, I can push the balls into the array (when clicking the canvas).
I can draw 1 of the balls on the canvas if I create it with ball = new Ball, but as soon as I try to draw from inside the array it breaks.
I draw inside a for loop with balls[i].draw
Here's a jsFiddle.
And here's the relevant code:
function init(){
defaultBall();
for(i=0;i<balls.length;i++){
balls[i].draw;
}
ball.draw();
}
function Ball(X, Y, Radius, Color){
this.X = X || 0;
this.Y = Y || 0;
this.radius = 5;
this.color = Color;
}
Ball.prototype.draw = function(){
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.X, this.Y, this.radius, 10, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
}
var balls = [];
function defaultBall(){
addBall(new Ball(50, 50, 5, '#6B7E00'));
addBall(new Ball(150, 50, 5, '#6B7E00'));
}
function addBall(ball){
balls.push(ball);
}
var ball = new Ball(50, 50, 5, '#6B7E00');
How should I formulate the loop to be able to draw the balls?
Help appriciated.
Maybe
balls[i].draw;
should be
balls[i].draw();
on line 4
Your init method isn't getting called again. If you change
function addBall(ball){
balls.push(ball);
ball.draw();
}
to
function addBall(ball){
balls.push(ball);
}
you'll see the new balls. You need to have some sort of mechanism that calls init (aka draw) again.
And as Frits pointed out you will need to use draw() instead of draw.
Your code runs just fine, it is just that there is a small error with your balls loop.
defaultBall();
for(i=0;i<balls.length;i++){
balls[i].draw;
}
ball.draw();
You forgot to use the parenthesis to call the draw method when looping through your balls. The fix is simple:
defaultBall();
for(i=0;i<balls.length;i++){
balls[i].draw();
}
ball.draw();
Note that now your balls will show up as intended. When the fiddle is edited with this new fix for looping through the balls you can see two green balls show up.