Graphical representation of speed on canvas - javascript

I have the following code:
https://jsfiddle.net/8o3sn9mh/21/
var
canvas = document.getElementById('c'),
ctx = canvas.getContext('2d');
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width;
canvas.height = height;
thrust = height * 0.000001;
maxVelocity = height * 0.00067;
velocity = height * 0.00019;
velocityInterval = setInterval(function(){
velocity+= thrust;
if(velocity > maxVelocity) velocity = maxVelocity;
ctx.fillRect(1, height / 2, width * (velocity / maxVelocity), height / 30);
}, 1);
explanation: I have a canvas which adapts to the user's window. I am trying to represent speed progress upon the canvas with the following starting parameters:
velocity, Max velocity(speed limit) and thrust which accelerates the velocity every millisecond.
the bar starts accelerating from a certain velocity(0.00019) and when you reach full speed the bar's width is exactly the canvas' width. it works fine but as you can see the graph bar starts at a certain x point which is not 0x, because i decided that the starting velocity shall be quite fast.
how can I start the bar at 0x and still be accurate with the speed progress?
here is how it should look like(of course the logic i was talking about is not included here):
https://jsfiddle.net/8o3sn9mh/33/

You can re-map the value between 0 and max.
Javascript has no native build in function for that.
But you can take one from p5 library for example which is:
Math.map = function (value, istart, istop, ostart, ostop) {
return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
}
Or follow this discussion on SO.
This function re-maps a value to a new range.
So in your case you'd define
var mapStart = width* ((height * 0.00019)/maxVelocity);
and map the (velocity,width) calculation to be between 0 and max. width
var mappedValue = Math.map((width * (velocity / maxVelocity)), mapStart, width, 0, width);
ctx.fillRect(1, height / 2, mappedValue, height / 30);
The fiddle.

Related

Scaling mouse drawings elements on a html canvas

Is there any way to scale drawing elements on a canvas? I'm creating an application where then user can place points on a canvas, but when I try to resize the browser window all the elements disappear.
My initial tentative was to calculate the screen difference before and after the resizing. After I get the percentages, I just sat the scale on the canvas and place the coordinates that was saved from the first time I drew on the canvas, but still doesn't work, if the points appear on the canvas, it is on the same place without scaling. Can someone give me little line of thought?
private calculateCanvasScreenDifference(previousSize, guestScreen) {
return ((controlScreen - currentSize) * 100) / controlScreen;
}
let difWidthPercent = Math.abs(this.calculateCanvasScreenDifference(canvasPreviousWidth, canvasWidth) * 0.01);
let difHeightPercent = Math.abs(this.calculateCanvasScreenDifference(canvasPreviousHeight, canvasHeight) * 0.01);
let scaleX = ((Math.abs(difWidthPercent) <= 1) ? 1.00 - difWidthPercent : difWidthPercent - 1.00);
let scaleY = ((Math.abs(difHeightPercent) <= 1) ? 1.00 - difHeightPercent : difHeightPercent - 1.00);
this.cx.scale(Number(scaleX), Number(scaleY));
...
...
// then start recreating the drawing that was previous saved on an array of object(x, y values)
this.cx.beginPath();
this.cx.arc(coord.x, coord.y, 7, 0, Math.PI * 2, true);
this.cx.stroke();
Keep track of your canvas width and start with a scale factor of 1.
let originalWidth = canvas.width;
let scale = 1;
On resize calculate the new scale factor. And update tracked canvas size.
let scale = newWidth / originalWidth;
originalWidth = newWidth;
Use the scale factor for all drawing at all times. e.g.
context.arc(coord.x * scale, coord.y * scale, radius, 0, Math.PI*2, false);
Note: This approach assumes the original and new canvas sizes are proportional. If not then you will need to track width and height, and calculate separate x and y scale factors.

CSS & Fabric js: Scale canvas and objects on small resolutions

I am working with fabricjs for an application. On mobile I need to scale the whole canvas down so it can fit the screen.
The issue is that on the canvas there are 10 objects in total, between images, texts and fabric js background objects.
I tried scaling the whole canvas using CSS transform and it seems to work, but the whole area gets smaller, as in this picture
The original canvas is the entire picture, but when I apply the transform, the canvas becomes the red area, and when moving objects with fabricjs, they get clipped when outside the red area.
Is there any way to fix this in CSS ?
edit
Basically I need to scale everything inside the canvas, but the canvas itself needs to always be 100% in height and with of the current window.
Or is there any way to resize the whole canvas with all the objects ? I also tried fabric js setZoom() method, but it gets even more messed up.
Thank you
The best way is find a scale ratio that fit your screen (is unclear how you plan to fit a square canvas on a long screen).
Then leave the css untrasformed. ratio is the scale ratio that fit the canvas in screen;
resize the canvas to actual screen dimensions:
canvas.setDimensions({ width: originalWidth * ratio, height: originalHeight * ratio });
canvas.setZoom(ratio)
This should give you a smaller canvas with everything in place.
Possible Duplicate: Scale fabric.js canvas objects
It seems Fabric.js doesn't support this natively... at least, I haven't found the options to do it.
Until Fabric.js provides this, the best way may be to iterate through the objects and scale them manually to match the new screen size based on the ratio of the previous screen size. For example...
const canvas = new fabric.Canvas({});
let prevScreenWidth = window.innerWidth;
let prevScreenHeight = window.innerHeight;
window.addEventListener('resize', handleResize());
/*********************************************************
* Get the new screen size ratio in relation to the previous screen size
* to determine the scale factor.
*********************************************************/
function handleResize() {
// Error Handling: Make sure the scale ratio denominator is defined.
prevScreenWidth = prevScreenWidth || window.innerWidth;
prevScreenHeight = prevScreenHeight || window.innerHeight;
// Get the current/new screen width and height (scale ratio numerator).
const currScreenWidth = window.innerWidth;
const currScreenHeight = window.innerHeight;
// Determine width and height scale ratios
const widthRatio = currScreenWidth / prevScreenWidth;
const heightRatio = currScreenHeight / prevScreenHidth;
// Get the average of the width and height scale ratios
const scaleFactor = (widthRatio + heightRatio) / 2;
// Scale the objects with the screen size adjustment ratio average as the scale factor
scaleObjects(scaleFactor);
// Record the current screen sizes to be used for the next resize.
prevScreenWidth = window.innerWidth;
prevScreenHeight = window.innerHeight;
}
/*********************************************************
* Iterate through each object and apply the same scale factor to each one.
*********************************************************/
function scaleObjects(factor) {
// Set canvas dimensions (this could be skipped if canvas resizes based on CSS).
canvas.setDimensions({
width: canvas.getWidth() * factor,
height: canvas.getHeight() * factor
});
if (canvas.backgroundImage) {
// Need to scale background images as well
const bi = canvas.backgroundImage;
bi.width = bi.width * factor; bi.height = bi.height * factor;
}
const objects = canvas.getObjects();
for (let i in objects) {
const scaleX = objects[i].scaleX;
const scaleY = objects[i].scaleY;
const left = objects[i].left;
const top = objects[i].top;
const tempScaleX = scaleX * factor;
const tempScaleY = scaleY * factor;
const tempLeft = left * factor;
const tempTop = top * factor;
objects[i].scaleX = tempScaleX;
objects[i].scaleY = tempScaleY;
objects[i].left = tempLeft;
objects[i].top = tempTop;
objects[i].setCoords();
}
canvas.renderAll();
canvas.calcOffset();
}
See this previous answer for more information on the scaleObjects() method... Scale fabric.js canvas objects

JavaScript canvas, scale between two points on chart/graph?

So I've built a small graph application with JavaScript to help me practice using the canvas. I've spent the last 10 hours trying to scale between two points on the X-Axis and can't for the life of me figure it out. I've learned that to scale you need to translate > scale > translate. This works fine when I scale to the far left/right using the following type code.
let x = 0;
let y = this.getCanvasHeight() / 2;
this.getCanvasContext().clearRect(0, 0, this.getCanvas().width, this.getCanvas().height);
this.setCanvas();
ctx.translate(x, y);
ctx.scale(scale, 1);
ctx.translate(-x, -y);
this.resetCanvasLines();
this.renderGraph(this.state.points, scale);
This piece of code simply allows me to zoom into the far left of the graph. So now I'm trying to pick two points on this graph and zoom in on top of them, so that they fit evenly on the screen. The Y-Axis will always be the same.
My thinking was to get the midpoint between the two points and zoom in on that location, which I feel should work but I just can't get it working. My graph width is 3010px and split into 5 segments of 602px. I want to zoom let's say from x1 = 602 and x2 = 1806, which has the midpoint of 1204. Is there a technique to properly calculating the scale amount?
rangeFinder(from, to) {
let points = this.state.points;
if (points.length === 0) {
return;
}
let ctx = this.getCanvasContext();
let canvasWidth = this.getCanvasWidth();
let canvasHeight = this.getCanvasHeight() / 2;
let seconds = this.state.seconds;
let second = canvasWidth / seconds;
let scale = 1;
// My graph starts from zero, goes up to 5 and the values are to represent seconds.
// This gets the pixel value for the fromX value.
let fromX = from * second;
to = isNaN(to) ? 5 : to;
// Get the pixel value for the to location.
let toX = parseInt(to) * second;
let y = canvasHeight / 2;
// get the midpoint between the two points.
let midpoint = fromX + ((toX - fromX) / 2);
// This is where I really go wrong. I'm trying to calculate the scale amount
let zoom = canvasWidth - (toX - fromX);
let zoomPixel = (zoom / 10) / 1000;
let scaleAmount = scale + ((zoom / (canvasWidth / 100)) / 100) + zoomPixel;
ctx.clearRect(0, 0, this.getCanvas().width, this.getCanvas().height);
this.setCanvas();
// translate and scale.
ctx.translate(midpoint, y);
ctx.scale(scaleAmount, 1);
ctx.translate(-midpoint, -y);
this.resetCanvasLines();
this.renderGraph(points);
}
Any help would be great, thanks.
Scale = 5/3 = total width / part width.
After scale, x = 602 should have moved to 602 * 5/3 ~ 1000. Translate the new image by -1000. There is no need to find mid-point.

Image jumping at the beginning of scrolling

I wrote some code to zoom in my image, but when I scroll at the very beginning this picture jumps a little. How to fix the problem?
Full page view.
Editor view.
HTML
<canvas id="canvas"></canvas>
JS
function draw(scroll) {
scroll = (window.scrollY || window.pageYOffset) / (document.body.clientHeight - window.innerHeight) * 3000;
canvas.setAttribute('width', window.innerWidth);
canvas.setAttribute('height', window.innerHeight);
//The main formula that draws and zooms the picture
drawImageProp(ctx, forest, 0, (-scroll * 3.9) / 4, canvas.width, canvas.height + (scroll * 3.9) / 2);
}
Not a bug fix
I had a look at the Codepen example and it does jump at the top (sometimes). I have a fix for you but I did not have the time to locate the source of your code problem. I did notice that the jump involved a aspect change so it must be in the scaling that your error is. (look out for negatives)
GPU is a better clipper
Also your code is actually doing unnecessary work, because you are calculating the image clipping region. Canvas context does the clipping for you and is especially good at clipping images. Even though you provide the clip area the image will still go through clip as that is part of the render pipeline. The only time you should be concerned about the clipped display of an image is whether or not any part of the image is visible so that you don't send a draw call, and it only really matters if you are pushing the image render count (ie game sprite counts 500+)
Code example
Anyway I digress. Below is my code. You can add the checks and balances. (argument vetting, scaling max min, etc).
Calling function.
// get a normalised scale 0-1 from the scroll postion
var scale = (window.scrollY || window.pageYOffset) / (document.body.clientHeight - window.innerHeight);
// call the draw function
// scale 0-1 where 0 is min scale and 1 is max scale (min max determined in function
// X and y offset are clamped but are ranged
// 0 - image.width and 0 - image.height
// where 0,0 shows top left and width,height show bottom right
drawImage(ctx, forest, scale, xOffset, yOffset);
The function.
The comments should cover what you need to know. You will notice that all I am concerned with is how big the image should be and where the top left corner will be. The GPU will do the clipping for you, and will not cost you processing time (even for unaccelerated displays). I personally like to work with normalised values 0-1, it is a little extra work but my brain likes the simplicity, it also reduces the need for magic numbers (magics number are a sign that code is not adaptable) . Function will work for any size display and any size image. Oh and I like divide rather than multiply, (a bad coding habit that comes from a good math habit) replacing the / 2 and needed brackets with * 0.5 will make it more readable.
function drawImage(ctx, img, scale, x, y){
const MAX_SCALE = 4;
const MIN_SCALE = 1;
var w = canvas.width; // set vars just for source clarity
var h = canvas.height;
var iw = img.width;
var ih = img.height;
var fit = Math.max(w / iw, h / ih); // get the scale to fill the avalible display area
// Scale is a normalised value from 0-1 as input arg Convert to range
scale = (MAX_SCALE - MIN_SCALE) * scale + MIN_SCALE;
var idw = iw * fit * scale; // get image total display size;
var idh = ih * fit * scale;
x /= iw; // normalise offsets
y /= ih; //
x = - (idw - w) * x; // transform offsets to display coords
y = - (idh - h) * y;
x = Math.min( 0, Math.max( - (idw - w), x) ); // clamp image to display area
y = Math.min( 0, Math.max( - (idh - h), y) );
// use set transform to scale and translate
ctx.setTransform(scale, 0, 0, scale, idw / 2 + x, idh / 2 + y);
// display the image to fit;
ctx.drawImage(img, ( - iw / 2 ) * fit, (- ih / 2 ) * fit);
// restore transform.
ctx.setTransform(1, 0, 0, 1, 0, 0)
}
Sorry I did not solve the problem directly, but hopefully this will help you redesign your approch.
I recently added a similar answer involving zooming and panning (and rotation) with the mouse which you may be interested in How to pan the canvas? Its a bit messy still "note to self (my clean it up)" and has no bounds clamping. But shows how to set a zoom origin, and convert from screen space to world space. (find where a screen pixel is on a pan/scale/rotated display).
Good luck with your project.

Calculate camera zoom required for object to fit in screen height

In Three.js I'm using this formulas to calculate visible width & height
var vFOV = camera.fov * Math.PI / 180; // convert vertical fov to radians
var height = 2 * Math.tan( vFOV / 2 ) * dist; // visible height
var aspect = window.width / window.height;
var width = height * aspect; // visible width
And with that I calculate the camera zoom required for object to fit exactly into render area by WIDTH
var zoom = (ObjectHeight/aspect) / (2*Math.tan(vFOV/2)) + ObjectDepth;
How do I calculate the camera zoom required for object to fit exactly into render area by HEIGHT?
Thanks to GuyGood, I found the solution:
var zoom = (ObjectHeight/2) / Math.tan(vFOV/2) - ObjectDepth;
I am doing the following which is based on the boundingSphere radius:
geometry.computeBoundingSphere();
radius = geometry.boundingSphere.radius;
distanceFactor = Math.abs( aspect * radius / Math.sin( fov/2 );
This is based on this stuff right here and I hope i interpreted it the right way:
http://www.flipcode.com/forums/thread/4172
This distanceFactor is the factor you need to move the camera along its viewing direction to fit it correctly. At the moment i am not sure if it is by height or width but maybe it helps you figure it out. :)

Categories