Scale Raphael / SVG container to fit all contents - javascript

I have a number of Raphael / SVG items that could possibly go outside the boundary. What I want is to be able to auto zoom and center the SVG to show all contents.
I have some partially working code that centers it appropriately, I just cannot figure out how to get the scaling working
Edit
This now works... but doesnt center align, and requires padding...
var maxValues = { x: 0, y: 0 };
var minValues = { x: 0, y: 0 };
//Find max and min points
paper.forEach(function (el) {
var bbox = el.getBBox();
if (bbox.y < minValues.y) minValues.y = bbox.y;
if (bbox.y2 < minValues.y) minValues.y = bbox.y2;
if (bbox.y > maxValues.y) maxValues.y = bbox.y;
if (bbox.y2 > maxValues.y) maxValues.y = bbox.y2;
if (bbox.x < minValues.x) minValues.x = bbox.x;
if (bbox.x2 < minValues.x) minValues.x = bbox.x2;
if (bbox.x > maxValues.x) maxValues.x = bbox.x;
if (bbox.x2 > maxValues.x) maxValues.x = bbox.x2;
});
var w = maxValues.x - minValues.x;
var h = maxValues.y - minValues.y;
console.log(minValues,maxValues,w,h)
paper.setViewBox(minValues.x, minValues.y, w, h, false);

I'm using this snippet of code to center the viewbox that contains all of the SVG that I want to show in almost Full Screen.
var elmnt = $(viewport);
var wdif = screen.width - width;
var hdif = screen.height - height;
if (wdif < hdif){
var scale = (screen.width - 50) / width;
var ty = (screen.height - (height * scale)) / 2
var tx = 20;
}else{
var scale = (screen.height - 50) / height;
var tx = (screen.width - (width * scale)) / 2
var ty = 20;
}
elmnt.setAttribute("transform", "scale("+scale+") translate("+tx+","+ty+")");
What it does is to use the difference between the viewport size and the screen size, and use it as the scale. Off course yo need to have all the elements wrapped in a viewbox. I hope this works for you to. Bye!
EDIT
I´ve made this fiddle.... http://jsfiddle.net/MakKZ/
While in the original snippet I´ve used the screen size, on the fiddle, you are going to see I´m using the size of the HTML element that jsfiddle uses. No matter how many circles (or the size of the circles) you draw on the screen you always see them all.

Related

How can I convert a Darknet label file to regular x,y,w,h?

I'm sorry to ask this here but I have been at it for days and I can't figure out what formula to use. I'm hoping someone knows what i can do, thanks!
So I am trying to make a GUI tool for darknet but the little squares shown in the front are in the wrong position.
Image Dimensions : 1024 x 683
Annotation Line in Label File : 0 0.6681250000000001 0.510788 0.09750000000000003 0.15103200000000006
I am trying to get x,y,w,h like this
var convertDarknetImageLabelsToMatrices = function(loadedImage){
// loadedImage = {
// annotation: "0 0.6681250000000001 0.510788 0.09750000000000003 0.15103200000000006",
// dimensions: {
// height: 683,
// width: 1024
// }
// }
var width = loadedImage.dimensions.width
var height = loadedImage.dimensions.height
var matrices = []
var labels = loadedImage.annotation.split('\n')
labels.forEach(function(label){
if(!label)return
var lineParts = label.split(' ')
var classNumber = lineParts[0]
var x = lineParts[1] * width / 2
var y = lineParts[2] * height / 2
var w = lineParts[3] * width
var h = lineParts[4] * height
matrices.push({
classNumber: classNumber,
tag: loadedImage.className,
x: x,
y: y,
w: w,
h: h,
imageWidth: width,
imageHeight: height
})
})
return matrices
}
the parsing part is
var x = lineParts[1] * width / 2
var y = lineParts[2] * height / 2
var w = lineParts[3] * width
var h = lineParts[4] * height
I added the /2 because it seems to make it closer. On some images it will be almost exact on a single axis, like X will line up but Y will be way off. I've tried to figure a static formula but its just always off in some other direction whenever opening another image.
Training itself is fine, I just can't edit/view the boxes later in the UI :(
thank you! if you need more info please let me know!
thanks to all who viewed and considered answering. I just figured it out! here is what I need to use for parsing.
var w = lineParts[3] * width
var h = lineParts[4] * height
var x = (lineParts[1] * width) - w / 2
var y = (lineParts[2] * height) - h / 2

Gridify Images like Pinterest Layout on Canvas

I am using pixi.js to create an interactive app. I need to place images in different containers on the screen in the form of pinterest like layout or gridify them so that they look organize and nice. As Im working on canvas so plugins like gridify.js do not suport canvas and Im unable to find any plugin or code to get this working on canvas. Also, the images need to be clickable so I cannot use plugins like html to canvas conversion as they only generate the image.
I have found a solution for this by examining the gridify.js logic. Here is my code:
var items = options.items;
var width = options.containerWidth,
item_margin = parseInt(options.margin || 0),
item_width = parseInt(options.max_width || options.width || 220),
column_count = Math.max(Math.floor(width / (item_width + item_margin)), 1),
left = column_count == 1 ? item_margin / 2 : (width % (item_width + item_margin)) / 2,
lastHeight = 0;
if (options.max_width) {
column_count = Math.ceil(width / (item_width + item_margin));
item_width = (width - column_count * item_margin - item_margin) / column_count;
left = item_margin / 2;
}
for (var i = 0, length = items.length; i < length; i++) {
var ratio = item_width / items[i].width;
items[i].width = item_width;
items[i].height = items[i].height * ratio;
items[i].position.y = lastHeight + item_margin / 2;
items[i].position.x = 0;
lastHeight += items[i].height + (item_margin * 2);
}
Iterate all the containers and pass the parameters to above function. It will gridify the images/contents in a container. I have used single column per container but you can use multiple columns by using the original code from the gridify.js

Javascript elements only snapping to grid from right side

I am trying to resize elements so that they snap to a grid and so far it is working if I resize from the right side and bottom but for some reason it doesn't work from the left side. I am sure it's just my math but here is the code:
var dimension = win.getDimension();
var newW = Math.round(dimension[0] / 10) * 10;
var newH = Math.round(dimension[1] / 10) * 10;
win.setDimension(newW, newH);
Any ideas?
So what I ended up doing was having an event called onBeforeResizeStart which saved the current x position for the window and in the onResizeFinish event I checked to see if the new x position matched the saved x position. If it did not then I just updated the x position and did the same snap calculation.
onBeforeResizeStart:
var position = getCoords(win);
window.CurrentX = position.left;
onResizeFinish
var position = getCoords(win);
if(position.left != window.currentX)
{
var newX = Math.round(position.left / 10) * 10;
win.setPosition(newX, position.top);
}
var dimension = win.getDimension();
var newW = Math.round(dimension[0] / 10) * 10;
var newH = Math.round(dimension[1] / 10) * 10;
win.setDimension(newW, newH);

bounce check function for HTML5 canvas fails at corners

I have a HTML5 canvas that generates a bouncing box every time you click on it. The box array stores the x-value, y-value, x-velocity, and y-velocity of each box created. The box will travel in a random direction at first and will bounce of the sides of the canvas but if it hits a corner the box dissappears instead of bouncing back. EDIT: I answered my own question noticing that the soundY and soundX functions were causing the problem.
var box = new Array();
var width = window.innerWidth;
var height = window.innerHeight;
var field = document.getElementById('canvas');
field.width = width;
field.height = height;
field.ctx = field.getContext('2d');
field.ctx.strokeStyle = 'rgba(255,255,255,1)';
setInterval('redraw()', 200);
addEventListener('click', createBox, false);
function createBox(e) { // this box will always fail collision detection at the upper-left corner
box.push(100); // x-value is normally mouse position
box.push(100); // y-value is normally mouse position
box.push(-5); // x-speed is normally random
box.push(-5); // y-speed is normally random
}
function redraw() {
field.ctx.clearRect(0,0,width,height);
for(var i = 0; i < box.length; i+=4) {
if(box[i] < 0) { box[i+2] *= -1; soundY(box[i+1]); } // parameter of soundY is less than 0
else if(box[i] > width) { box[i+2] *= -1; soundY(box[i+1]); } // which is invalid and causes this to break
if(box[i+1] < 0) { box[i+3] *= -1; soundX(box[i]); }
else if(box[i+1] > height) { box[i+3] *= -1; soundX(box[i]); }
box[i] += box[i+2];
box[i+1] += box[i+3];
field.ctx.strokeRect(box[i], box[i+1], 4, 4);
}
}
function soundX(num) {
// play a sound file based on a number between 0 and width
}
function soundY(num) {
// play a sound file based on a number between 0 and height
}
The only way I could recreate the problem was by generating the box in one of the corners so that with the right x and y velocity the box was initially created outside the bounds of the canvas. When that happens, the inversion of the velocity isn't enough to bring the item back in bounds and so on the next frame the velocity is inverted again (and so on).
I think this might solve your problem:
var boxes = [];
var boxSize = 4;
var width = window.innerWidth;
var height = window.innerHeight;
var field = document.getElementById('canvas');
function redraw() {
field.ctx.clearRect(0, 0, width, height);
var box;
for (var i = 0; i < boxes.length; i++) {
box = boxes[i];
field.ctx.strokeRect(box.x, box.y, boxSize, boxSize);
if (box.x < 0) {
box.x = 0;
box.dx *= -1;
} else if (box.x > width - boxSize) {
box.x = width - boxSize;
box.dx *= -1;
}
if (box.y < 0) {
box.y = 0;
box.dy *= -1;
} else if (box.y > height - boxSize) {
box.y = height - boxSize;
box.dy *= -1;
}
box.x += box.dx;
box.y += box.dy;
}
}
field.width = width;
field.height = height;
field.ctx = field.getContext('2d');
field.ctx.strokeStyle = 'rgba(0,0,0,1)';
setInterval(redraw, 200);
addEventListener('click', createBox, false);
function createBox(e) {
boxes.push({
x: e.clientX - 10,
y: e.clientY - 10, // arbitrary offset to place the new box under the mouse
dx: Math.floor(Math.random() * 8 - boxSize),
dy: Math.floor(Math.random() * 8 - boxSize)
});
}
I fixed a few errors in your code and made some changes to make it a bit more readable (I hope). Most importantly, I extended your collision detection so that it resets the coordinates of the box to the bounds of your canvas should the velocity take it outside.
Created a jsfiddle which might be handy if further discussion is needed.
It was additional code (see edit) that I left out assuming it was unrelated to the issue, but removing the code solved the problem as it appears this use-case would cause an invalid input in this part of the code.

Zoom Canvas to Mouse Cursor

I'm programming a HTML5 < canvas > project that involves zooming in and out of images using the scroll wheel.
I want to zoom towards the cursor like google maps does but I'm completely lost on how to calculate the movements.
What I have: image x and y (top-left corner); image width and height; cursor x and y relative to the center of the canvas.
In short, you want to translate() the canvas context by your offset, scale() it to zoom in or out, and then translate() back by the opposite of the mouse offset. Note that you need to transform the cursor position from screen space into the transformed canvas context.
ctx.translate(pt.x,pt.y);
ctx.scale(factor,factor);
ctx.translate(-pt.x,-pt.y);
Demo: http://phrogz.net/tmp/canvas_zoom_to_cursor.html
I've put up a full working example on my website for you to examine, supporting dragging, click to zoom in, shift-click to out, or scroll wheel up/down.
The only (current) issue is that Safari zooms too fast compared to Chrome or Firefox.
I hope, these JS libraries will help you:
(HTML5, JS)
Loupe
http://www.netzgesta.de/loupe/
CanvasZoom
https://github.com/akademy/CanvasZoom
Scroller
https://github.com/zynga/scroller
As for me, I'm using loupe. It's awesome!
For you the best case - scroller.
I recently needed to archive same results as Phrogz had already done but instead of using context.scale(), I calculated each object size based on ratio.
This is what I came up with. Logic behind it is very simple. Before scaling, I calculate point distance from edge in percentages and later adjust viewport to correct place.
It took me quite a while to come up with it, hope it saves someones time.
$(function () {
var canvas = $('canvas.main').get(0)
var canvasContext = canvas.getContext('2d')
var ratio = 1
var vpx = 0
var vpy = 0
var vpw = window.innerWidth
var vph = window.innerHeight
var orig_width = 4000
var orig_height = 4000
var width = 4000
var height = 4000
$(window).on('resize', function () {
$(canvas).prop({
width: window.innerWidth,
height: window.innerHeight,
})
}).trigger('resize')
$(canvas).on('wheel', function (ev) {
ev.preventDefault() // for stackoverflow
var step
if (ev.originalEvent.wheelDelta) {
step = (ev.originalEvent.wheelDelta > 0) ? 0.05 : -0.05
}
if (ev.originalEvent.deltaY) {
step = (ev.originalEvent.deltaY > 0) ? 0.05 : -0.05
}
if (!step) return false // yea..
var new_ratio = ratio + step
var min_ratio = Math.max(vpw / orig_width, vph / orig_height)
var max_ratio = 3.0
if (new_ratio < min_ratio) {
new_ratio = min_ratio
}
if (new_ratio > max_ratio) {
new_ratio = max_ratio
}
// zoom center point
var targetX = ev.originalEvent.clientX || (vpw / 2)
var targetY = ev.originalEvent.clientY || (vph / 2)
// percentages from side
var pX = ((vpx * -1) + targetX) * 100 / width
var pY = ((vpy * -1) + targetY) * 100 / height
// update ratio and dimentsions
ratio = new_ratio
width = orig_width * new_ratio
height = orig_height * new_ratio
// translate view back to center point
var x = ((width * pX / 100) - targetX)
var y = ((height * pY / 100) - targetY)
// don't let viewport go over edges
if (x < 0) {
x = 0
}
if (x + vpw > width) {
x = width - vpw
}
if (y < 0) {
y = 0
}
if (y + vph > height) {
y = height - vph
}
vpx = x * -1
vpy = y * -1
})
var is_down, is_drag, last_drag
$(canvas).on('mousedown', function (ev) {
is_down = true
is_drag = false
last_drag = { x: ev.clientX, y: ev.clientY }
})
$(canvas).on('mousemove', function (ev) {
is_drag = true
if (is_down) {
var x = vpx - (last_drag.x - ev.clientX)
var y = vpy - (last_drag.y - ev.clientY)
if (x <= 0 && vpw < x + width) {
vpx = x
}
if (y <= 0 && vph < y + height) {
vpy = y
}
last_drag = { x: ev.clientX, y: ev.clientY }
}
})
$(canvas).on('mouseup', function (ev) {
is_down = false
last_drag = null
var was_click = !is_drag
is_drag = false
if (was_click) {
}
})
$(canvas).css({ position: 'absolute', top: 0, left: 0 }).appendTo(document.body)
function animate () {
window.requestAnimationFrame(animate)
canvasContext.clearRect(0, 0, canvas.width, canvas.height)
canvasContext.lineWidth = 1
canvasContext.strokeStyle = '#ccc'
var step = 100 * ratio
for (var x = vpx; x < width + vpx; x += step) {
canvasContext.beginPath()
canvasContext.moveTo(x, vpy)
canvasContext.lineTo(x, vpy + height)
canvasContext.stroke()
}
for (var y = vpy; y < height + vpy; y += step) {
canvasContext.beginPath()
canvasContext.moveTo(vpx, y)
canvasContext.lineTo(vpx + width, y)
canvasContext.stroke()
}
canvasContext.strokeRect(vpx, vpy, width, height)
canvasContext.beginPath()
canvasContext.moveTo(vpx, vpy)
canvasContext.lineTo(vpx + width, vpy + height)
canvasContext.stroke()
canvasContext.beginPath()
canvasContext.moveTo(vpx + width, vpy)
canvasContext.lineTo(vpx, vpy + height)
canvasContext.stroke()
canvasContext.restore()
}
animate()
})
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body>
<canvas class="main"></canvas>
</body>
</html>
I took #Phrogz's answer as a basis and made a small library that enables canvas with dragging, zooming and rotating.
Here is the example.
var canvas = document.getElementById('canvas')
//assuming that #param draw is a function where you do your main drawing.
var control = new CanvasManipulation(canvas, draw)
control.init()
control.layout()
//now you can drag, zoom and rotate in canvas
You can find more detailed examples and documentation on the project's page
Faster
Using ctx.setTransform gives you more performance than multiple matrix calls ctx.translate, ctx.scale, ctx.translate.
No need for complex transformation inversions as and expensive DOM matrix calls tp converts point between zoomed and screen coordinate systems.
Flexible
Flexibility as you don't need to use ctx.save and ctx.restore if you are rendering content at using different transforms. Returning to the transform with ctx.setTransform rather than the potentially frame rate wreaking ctx.restorecall
Easy to invert the transform and get the world coordinates of a (screen) pixel position and the other way round.
Examples
Using mouse and mouse wheel to zoom in and out at mouse position
An example using this method to scale page content at a point (mouse) via CSS transform CSS Demo at bottom of answer also has a copy of the demo from the next example.
And an example of this method used to scale canvas content at a point using setTransform
How
Given a scale and pixel position you can get the new scale as follow...
const origin = {x:0, y:0}; // canvas origin
var scale = 1; // current scale
function scaleAt(x, y, scaleBy) { // at pixel coords x, y scale by scaleBy
scale *= scaleBy;
origin.x = x - (x - origin.x) * scaleBy;
origin.y = y - (y - origin.y) * scaleBy;
}
To position the canvas and draw content
ctx.setTransform(scale, 0, 0, scale, origin.x, origin.y);
ctx.drawImage(img, 0, 0);
To use if you have the mouse coordinates
const zoomBy = 1.1; // zoom in amount
scaleAt(mouse.x, mouse.y, zoomBy); // will zoom in at mouse x, y
scaleAt(mouse.x, mouse.y, 1 / zoomBy); // will zoom out by same amount at mouse x,y
To restore the default transform
ctx.setTransform(1,0,0,1,0,0);
The inversions
To get the coordinates of a point in the zoomed coordinate system and the screen position of a point in the zoomed coordinate system
Screen to world
function toWorld(x, y) { // convert to world coordinates
x = (x - origin.x) / scale;
y = (y - origin.y) / scale;
return {x, y};
}
World to screen
function toScreen(x, y) {
x = x * scale + origin.x;
y = y * scale + origin.y;
return {x, y};
}

Categories