Gridify Images like Pinterest Layout on Canvas - javascript

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

Related

JS randomly distribute child elements in their parent without overlap

I am trying to make something where a bunch of circles (divs with border-radius) can be dynamically generated and laid out in their container without overlapping.
Here is my progress so far - https://jsbin.com/domogivuse/2/edit?html,css,js,output
var sizes = [200, 120, 500, 80, 145];
var max = sizes.reduce(function(a, b) {
return Math.max(a, b);
});
var min = sizes.reduce(function(a, b) {
return Math.min(a, b);
});
var percentages = sizes.map(function(x) {
return ((x - min) * 100) / (max - min);
});
percentages.sort(function(a, b) {
return b-a;
})
var container = document.getElementById('container');
var width = container.clientWidth;
var height = container.clientHeight;
var area = width * height;
var maxCircleArea = (area / sizes.length);
var pi = Math.PI;
var maxRadius = Math.sqrt(maxCircleArea / pi);
var minRadius = maxRadius * 0.50;
var range = maxRadius - minRadius;
var radii = percentages.map(function(x) {
return ((x / 100) * range) + minRadius;
});
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
var coords = [];
radii.forEach(function(e, i) {
var circle = document.createElement('div');
var randomTop = getRandomArbitrary(0, height);
var randomLeft = getRandomArbitrary(0, width);
var top = randomTop + (e * 2) < height ?
randomTop :
randomTop - (e * 2) >= 0 ?
randomTop - (e * 2) :
randomTop - e;
var left = randomLeft + (e * 2) < width ?
randomLeft :
randomLeft - (e * 2) >= 0 ?
randomLeft - (e * 2) :
randomLeft - e;
var x = left + e;
var y = top + e;
coords.push({x: x, y: y, radius: e});
circle.className = 'bubble';
circle.style.width = e * 2 + 'px';
circle.style.height = e * 2 + 'px';
circle.style.top = top + 'px';
circle.style.left = left + 'px';
circle.innerText = i
container.appendChild(circle);
});
I have got them being added to the parent container but as you can see they overlap and I don't really know how to solve this. I tried implementing a formula like (x1 - x2)^2 + (y1 - y2)^2 < (radius1 + radius2)^2 but I have no idea about this.
Any help appreciated.
What you're trying to do is called "Packing" and is actually a pretty hard problem. There are a couple potential approaches you can take here.
First, you can randomly distribute them (like you are currently doing), but including a "retry" test, in which if a circle overlaps another, you try a new location. Since it's possible to end up in an impossible situation, you would also want a retry limit at which point it gives up, goes back to the beginning, and tries randomly placing them again. This method is relatively easy, but has the down-side that you can't pack them very densely, because the chances of overlap become very very high. If maybe 1/3 of the total area is covered by circle, this could work.
Second, you can adjust the position of previously placed circles as you add more. This is more equivalent to how this would be accomplished physically -- as you add more you start having to shove the nearby ones out of the way in order to fit the new one. This will require not just finding the things that your current circle hits, but also the ones that would be hit if that one was to move. I would suggest something akin to a "springy" algorithm, where you randomly place all the circles (without thinking about if they fit), and then have a loop where you calculate overlap, and then exert a force on each circle based on that overlap (They push each other apart). This will push the circles away from each other until they stop overlapping. It will also support one circle pushing a second one into a third, and so on. This will be more complex to write, but will support much more dense configurations (since they can end up touching in the end). You still probably need a "this is impossible" check though, to keep it from getting stuck and looping forever.

Resize a group of variably sized images both horizontally & vertically - need help optimising

WARNING CODE CRASHES IN EVERYTHING EXCEPT GOOGLE CHROME
I'm trying to create a feature on our website that takes 8 random images and places them in two rows and dynamically resizes the images to take up the full width of the page.
I've created a jsbin for this to try and demonstrate the issue.
https://jsbin.com/yijemazovi/edit?html,css,js,output
The comments in the code should give you an good idea of what I'm doing. What seems to be happening for everything but Google Chrome is that the while condition is never satisfied so it goes on infinitely and crashes the browser.
Perhaps it is something as simple as I am doing the do/while loop incorrectly or I should just be using a while loop???
Any help is appreciated!
/*****
* Get the overall width of the container that we want to match
**********/
var ContainerWidth = $('.feature-inim-collage .col.span_1_of_1').width();
/*****
* Increase the height of the images until the total sum of the width
* if the 4 images + the gutters is larger than ContainerWidth - then
* stop
**********/
/*****
* Increment in jumps of 10px until we get within 80% of the width of
* the ContainerWidth and then go to a more precise increment of 1px.
* We can increase the px from 10 to 20 or 30 so there are less loops
* but this can cause issues when we look at mobile and there is less
* overall width in the containers and jumping by 30px will be too much
**********/
var i = 0;
do {
$('.feature-inims-top-row .growable-container').css('height', i);
var RowWidth1 = CalculateTotalWidth(1);
if(RowWidth1 < (ContainerWidth*0.8)){
i = i+10;
}else{
i++;
}
}
while (RowWidth1 < (ContainerWidth - 3));
/*****
* Repeat above for the 2nd row
**********/
var i = 0;
do {
$('.feature-inims-bottom-row .growable-container').css('height', i);
var RowWidth2 = CalculateTotalWidth(2);
if(RowWidth2 < (ContainerWidth*0.8)){
i = i+10;
}else{
i++;
}
}
while (RowWidth2 < (ContainerWidth - 3));
/*********
* Calculate the combined width of the images + the gutters
****/
function CalculateTotalWidth(Row) {
var Image1Width = $('.growable-container-1').width();
var Image2Width = $('.growable-container-2').width();
var Image3Width = $('.growable-container-3').width();
var Image4Width = $('.growable-container-4').width();
var Image5Width = $('.growable-container-5').width();
var Image6Width = $('.growable-container-6').width();
var Image7Width = $('.growable-container-7').width();
var Image8Width = $('.growable-container-8').width();
var GutterSize = 24; // (3 gutters # 8px each)
if(Row == 1){
var RowWidth = GutterSize + Image1Width + Image2Width + Image3Width + Image4Width;
}else{
var RowWidth = GutterSize + Image5Width + Image6Width + Image7Width + Image8Width;
}
return RowWidth
}
It turns out the issue with this was that in the CalculateTotalWidth() function I was checking the width of the container the image was in rather than the image itself. As soon as I changed this it worked perfectly.
var Image1Width = $('.growable-container-1 img').width();
instead of
var Image1Width = $('.growable-container-1').width();

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);

JavaScript Plugin Help - ContentFlow

I recently downloaded a JavaScript plugin (found here: http://www.jacksasylum.eu/ContentFlow/download.php) and I'm trying to change it up a bit. So far, all the edits that I've made to the code have been done by reading the walkthroughs under the documentation tab on that site. I've searched and searched and I can only find one piece of code that I THINK could be what I need to change. I just don't have enough JavaScript knowledge to do it.
If you have downloaded that plugin before, or you read some of the documentations tab, here's the picture of what's going on.
The icons are spread out way too far. (Ignore the icons, I just used the Facebook and Twitter because they were easy.) I need them to be closer. (They are spreading out over a 960px wide div!)
I found this code that may be what I'm looking for. It's called the StepWidth. I have no clue if that's what I need or not. Can anyone fill me in?
calcStepWidth: function(diff) {
var vI = this.conf.visibleItems;
var items = this.items.length;
items = items == 0 ? 1 : items;
if (Math.abs(diff) > vI) {
if (diff > 0) {
var stepwidth = diff - vI;
} else {
var stepwidth = diff + vI;
}
} else if (vI >= this.items.length) {
var stepwidth = diff / items;
} else {
var stepwidth = diff * ( vI / items);
//var stepwidth = diff/absDiff * Math.max(diff * diff,Math.min(absDiff,0.3)) * ( vI / this.items.length);
//var stepwidth = this.flowSpeedFactor * diff / this.visibleItems;
//var stepwidth = this.flowSpeedFactor * diff * ( this.visibleItems / this.items.length)
//var stepwidth = this.flowSpeedFactor * diff / this._millisecondsPerStep * 2; // const. speed
}
return stepwidth;
This might be easier if you have the plugin, but any knowledge will help.
First, this is not a JQuery plugin. Your issue is both with size of your images and the width of your contentFlow container. The small icons spread out to fill the 960px container. You need to increase the size of your images and/or reduce the width of your the containing div for the contentflow. You can use px or %. As an example, on the contentflow site, you can see that the gallery on the right on the main page has a div width:70%;.
First Install ContentFlowAddOn_DEFAULT.js, then search on " calcCoordinates: function (item) " you will find:
calcCoordinates: function (item) {
var rP = item.relativePosition;
//var rPN = item.relativePositionNormed;
var vI = this.conf.visibleItems;
var f = 1 - 1/Math.exp( Math.abs(rP)*0.75);
var x = item.side * vI/(vI+1)* f;
var y = 1;
return {x: x, y: y};
},
try to minimize the value 0.75 (i.e 0.3) or less till you reach the proper distance you are looking for

Scale Raphael / SVG container to fit all contents

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.

Categories