coming up with another question.
I've been facing issues in finding a solution to how to give the squash and stretch effect to the spheres when they were touching the wall, especially when they're moving in different directions, and I have to mention that I also used a function to make the whole animation more fluid.
Here are 2 ways I have tried so far:
Add more keyframes to each ball to give them the squash effect at the time they were touching the wall, the outcome was not what I was expecting, as it kept being stretched during the whole animation
Trying to create a new timeline inside the bounceModify function for the min value (the min value is responsible for the spheres' moment of touching the wall), and the outcome was invalid property, missing scale? gsap.registerPlugin(). => it is uncommented in the code
Down below, you will find the code I've been working, on and a side note that I'm not that familiar with GSAP.
JS code and GSAP timelines
gsap.fromTo("#Nuub", {opacity: 0, y: 200}, {y: 0, opacity: 1, duration: 1.5, delay: 10});
gsap.fromTo(".buttons", {opacity: 0, y: 200}, {y: 0, opacity: 1, duration: 1.5, delay: 10.5}); // delay: 1.2
let ball_1 = document.querySelector("#Circle_1");
let ball_2 = document.querySelector("#Circle_2");
let ball_3 = document.querySelector("#Circle_3");
let ball_4 = document.querySelector("#Circle_4");
let container = document.querySelector("#nuub");
let ball_1_bounds = ball_1.getBoundingClientRect();
let ball_2_bounds = ball_2.getBoundingClientRect();
let ball_3_bounds = ball_2.getBoundingClientRect();
let ball_4_bounds = ball_2.getBoundingClientRect();
let container_bounds = container.getBoundingClientRect();
let max_X = container_bounds.right - ball_1_bounds.right; // right is the max for X, left is the min for X
let max_Y = container_bounds.bottom - ball_1_bounds.bottom; // bottom is the max for Y, top is the min for Y
let min_X = container_bounds.left - ball_1_bounds.left;
let min_Y = container_bounds.top - ball_1_bounds.top;
let max_X_2 = container_bounds.right - ball_2_bounds.right; // right is the max for X, left is the min for X
let max_Y_2 = container_bounds.bottom - ball_2_bounds.bottom; // bottom is the max for Y, top is the min for Y
let min_X_2 = container_bounds.left - ball_2_bounds.left;
let min_Y_2 = container_bounds.top - ball_2_bounds.top;
let max_X_3 = container_bounds.right - ball_3_bounds.right; // right is the max for X, left is the min for X
let max_Y_3 = container_bounds.bottom - ball_3_bounds.bottom; // bottom is the max for Y, top is the min for Y
let min_X_3= container_bounds.left - ball_3_bounds.left;
let min_Y_3 = container_bounds.top - ball_3_bounds.top;
let max_X_4 = container_bounds.right - ball_4_bounds.right; // right is the max for X, left is the min for X
let max_Y_4 = container_bounds.bottom - ball_4_bounds.bottom; // bottom is the max for Y, top is the min for Y
let min_X_4 = container_bounds.left - ball_4_bounds.left;
let min_Y_4 = container_bounds.top - ball_4_bounds.top;
let tl = new TimelineMax()
tl.to("#Circle_1", {
transformOrigin: "50% 100%",
x: 4000,
y: 3000,
duration: 10,
ease: "power2",
modifiers: {
x: bounceModify(min_X, max_X),
y: bounceModify(min_Y, max_Y)
}
})
.to("#Circle_1", {
transformOrigin: "50% 50%",
x: 0,
y: 0,
ease: "elastic",
duration: 3,
modifiers: {
x: bounceModify(min_X, max_X),
y: bounceModify(min_Y, max_Y)
}
})
//ball 2
let tl2 = new TimelineMax();
tl2.to("#Circle_2", {
x: -3800,
y: -2600,
duration: 10,
ease: "power2",
modifiers: {
x: bounceModify(min_X_2, max_X_2),
y: bounceModify(min_Y_2, max_Y_2)
}
})
.to("#Circle_2", {
x: 0,
y: 0,
duration: 1.5,
ease: "power1",
modifiers: {
x: bounceModify(min_X_2, max_X_2),
y: bounceModify(min_Y_2, max_Y_2)
}
})
//ball 3
let tl3 = new TimelineMax();
tl3.to("#Circle_3", {
x: -3200,
y: -2200,
duration: 10,
ease: "power2",
modifiers: {
x: bounceModify(min_X_3, max_X_3),
y: bounceModify(min_Y_3, max_Y_3)
}
})
.to("#Circle_3", {
x: 0,
y: 0,
duration: 1.5,
ease: "power1",
modifiers: {
x: bounceModify(min_X_3, max_X_3),
y: bounceModify(min_Y_3, max_Y_3)
}
})
// ball 4
let tl4 = new TimelineMax();
tl4.to("#Circle_4", {
x: 3700,
y: 2700,
duration: 10,
ease: "power2",
modifiers: {
x: bounceModify(min_X_4, max_X_4),
y: bounceModify(min_Y_4, max_Y_4)
}
})
.to("#Circle_4", {
x: 0,
y: 0,
duration: 1.5,
ease: "power1",
modifiers: {
x: bounceModify(min_X_4, max_X_4),
y: bounceModify(min_Y_4, max_Y_4)
}
})
function bounceModify(min, max){
let range = max - min;
return function(value){
value = parseFloat(value);
let cycle, clipValue;
if (value > max) {
// let tlbounce = new TimelineMax()
// tlbounce.to(min, .20, {
// scaleX: 1.15,
// scaleY: 0.9,
// ease: "sine.in"
// })
cycle = (value - max) / range;
clipValue = (cycle % 1) * range;
value = ((cycle | 0) & 1 !== 0)
// min is when it hits the wall
? min + clipValue
: max - clipValue;
// max is when it floats
} else if (value < min) {
cycle = (min - value) / range;
clipValue = (cycle % 1) * range;
value = ((cycle | 0) & 1 !== 0)
? max - clipValue
: min + clipValue;
}
return value + "px";
}
}
HTML code
<div id="nuub">
<!-- the logo container -->
<div class="nuub_animation">
<svg class="nuub_cropped" width="447" height="149" viewBox="0 0 447 149" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gradient_1" gradientTransform="rotate(-85 0.4 0.6)">
<stop offset="0%" stop-color="#020024" />
<stop offset="20%" stop-color="#0c0c41"/>
<stop offset="100%" stop-color="#a0c0c6" />
</linearGradient>
</defs>
<g>
<g id="Group">
<path id="Circle_1" d="M38.6446 2.84853C13.3219 9.64117 -3.19286 34.8397 1.10098 60.3669C3.85344 76.6912 14.4229 90.9338 29.066 98.0551C54.719 110.545 85.5466 100.246 98.4281 74.8287C100.96 69.8985 103.493 58.5044 103.493 52.0404C103.603 19.2823 70.5732 -5.69706 38.6446 2.84853Z" fill="url(#gradient_1)"/>
<path id="Circle_2" d="M132.338 39.8794C118.796 44.2618 110.209 54.9985 109.328 69.0221C108.337 82.3882 114.833 93.6728 126.833 99.589C147.642 109.997 171.864 96.6309 173.735 73.9522C174.836 60.1478 168.01 48.0963 155.789 42.1801C148.633 38.6743 139.054 37.6882 132.338 39.8794Z" fill="url(#gradient_1)" />
<path id="Circle_4" d="M86.2071 102.657C82.4638 103.314 77.2892 109.121 76.5185 113.284C74.0963 125.883 90.1707 134.319 99.3089 125.226C108.998 115.475 99.9695 99.9176 86.2071 102.657Z" fill="url(#gradient_1)"/>
<path id="Circle_3" d="M122.98 110.107C102.612 117.338 107.786 147.904 129.366 147.904C147.202 147.904 155.789 127.417 143.238 114.927C139.605 111.312 133.109 108.354 128.815 108.463C127.934 108.573 125.292 109.23 122.98 110.107Z" fill="url(#gradient_1)"/>
<g id="Nuub">
<path id="Vector" d="M388.648 54.6699V83.0456L391.731 88.8522C393.933 93.125 396.245 95.6449 400.098 97.9456C415.842 107.696 435.55 97.5074 436.871 79.1015C438.082 61.6816 419.586 48.7537 403.511 55.7654C400.649 56.9706 398.116 58.0662 397.896 58.0662C397.676 58.0662 397.456 50.9449 397.456 42.1801V26.2941H393.052H388.648V54.6699ZM422.888 67.8169C426.742 71.6515 427.182 72.6375 427.182 77.7868C427.182 82.936 426.742 83.9221 422.888 87.7566C419.035 91.5912 418.044 92.0294 412.87 92.0294C407.695 92.0294 406.704 91.5912 402.851 87.7566C398.997 83.9221 398.557 82.936 398.557 77.7868C398.557 72.6375 398.997 71.6515 402.851 67.8169C406.704 63.9824 407.695 63.5441 412.87 63.5441C418.044 63.5441 419.035 63.9824 422.888 67.8169Z" fill="black"/>
<path id="Vector_2" d="M217.995 56.6419C213.591 58.9426 211.059 61.2434 208.637 65.1875C205.444 70.3368 205.334 70.8846 205.003 86.2228L204.673 101.89H209.077H213.591V90.6052C213.701 75.8147 214.802 71.5419 219.536 67.05C222.839 64.0919 224.271 63.5441 229.005 63.5441C233.739 63.5441 235.17 64.0919 238.473 67.05C243.208 71.5419 244.309 75.8147 244.419 90.6052V101.89H248.823H253.227V87.2088C253.227 73.2949 253.117 72.3088 250.144 66.7213C246.07 59.0522 239.134 54.5603 230.326 53.9029C225.041 53.5743 223.06 54.0125 217.995 56.6419Z" fill="black"/>
<path id="Vector_3" d="M265.337 68.3647C265.337 82.2787 265.448 83.2647 268.42 88.8522C272.824 97.2882 279.65 101.342 289.559 101.342C299.468 101.342 306.294 97.2882 310.698 88.8522C313.671 83.2647 313.781 82.2787 313.781 68.3647V53.6838H309.377H304.973V64.8588C304.863 79.7588 303.762 84.0316 299.028 88.414C295.725 91.4816 294.293 92.0294 289.559 92.0294C284.825 92.0294 283.394 91.4816 280.091 88.414C275.356 84.0316 274.255 79.7588 274.145 64.8588V53.6838H269.741H265.337V68.3647Z" fill="black"/>
<path id="Vector_4" d="M326.993 68.3647C326.993 82.2787 327.103 83.2647 330.075 88.8522C334.479 97.2882 341.305 101.342 351.214 101.342C361.123 101.342 367.949 97.2882 372.353 88.8522C375.326 83.2647 375.436 82.2787 375.436 68.3647V53.6838H371.032H366.628V64.8588C366.518 79.7588 365.417 84.0316 360.683 88.414C357.38 91.4816 355.949 92.0294 351.214 92.0294C346.48 92.0294 345.049 91.4816 341.746 88.414C337.012 84.0316 335.911 79.7588 335.8 64.8588V53.6838H331.397H326.993V68.3647Z" fill="black"/>
</g>
</g>
</g>
</svg>
</div>
<div class="buttons">
<button>Services</button>
<button>Calculate Price</button>
<button>About Us</button>
</div>
</div>
Any help is more than welcome, thank you so much! :)
Related
I currently have two variables defining svg elements.
I am having trouble morphing between the two, I tried react-svg-morph but the circle would disappear on each render.
What is the best way to render a morph?
This is my setup:
let hexagonShape = <svg width="100%" height="100%" viewBox="0 0 15 15" fill="green" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 4.5V10.5L7.5 14L13.5 10.5V4.5L7.5 1L1.5 4.5Z" stroke="green" strokeWidth=".6" />
</svg>
let circleShape = <svg height="100%" width="100%" viewBox="0 0 15 15" fill='blue' xmlns="http://www.w3.org/2000/svg">
<circle cy="50%" cx="50%" r="49%" stroke="blue" strokeWidth=".3" />
</svg>
const [updatedSvg, setUpdatedSvg] = useState(0)
const [updatedCircleSvg, setUpdatedCircleSvg] = useState()
const [updatedHexagonSvg, setUpdatedHexagonSvg] = useState()
function changeSvg() {
if (updatedSvg === 0) {
setUpdatedCircleSvg(true)
setUpdatedHexagonSvg(false)
}
if (updatedSvg === 1) {
setUpdatedCircleSvg(false)
setUpdatedHexagonSvg(true)
}
}
useEffect(() => {
changeSvg()
}, [updatedSvg])
And I have two simple buttons that toggle between the two svg renders:
<div className={classes.container}>
<div className={classes.shape}>
{/* this is where the svgs are being rendered */}
{updatedCircleSvg && circleShape }
{updatedHexagonSvg && hexagonShape}
</div>
<button onClick={() => setUpdatedSvg(0)}>circle</button>
<button onClick={() => setUpdatedSvg(1)}>hexagon</button>
</div>
A bit of direction would greatly help. Thank you
Here is a simple example using Quadratic Bezier curves:
const getPoints = radius => {
const factor = Math.sqrt(3);
return [
{x: 0, y: -radius},
{x: radius / factor, y: -radius},
{x: radius * factor / 2, y: -radius / 2},
{x: radius * 2 / factor, y: 0},
{x: radius * factor / 2, y: radius / 2},
{x: radius / factor, y: radius},
{x: 0, y: radius},
{x: -radius / factor, y: radius},
{x: -radius * factor / 2, y: radius / 2},
{x: -radius * 2 / factor, y: 0},
{x: -radius * factor / 2, y: -radius / 2},
{x: -radius / factor, y: -radius}
];
}
const getRoundPath = radius => {
const p = getPoints(radius);
return `M ${p[0].x},${p[0].y}
Q ${p[1].x},${p[1].y} ${p[2].x},${p[2].y}
Q ${p[3].x},${p[3].y} ${p[4].x},${p[4].y}
Q ${p[5].x},${p[5].y} ${p[6].x},${p[6].y}
Q ${p[7].x},${p[7].y} ${p[8].x},${p[8].y}
Q ${p[9].x},${p[9].y} ${p[10].x},${p[10].y}
Q ${p[11].x},${p[11].y} ${p[0].x},${p[0].y}`
};
const getHexagonPath = radius => {
const p = getPoints(radius);
for (let index = 1; index < 12; index += 2) {
const prev = p[index - 1];
const next = p[(index + 1) % 12];
p[index].x = (prev.x + next.x) / 2;
p[index].y = (prev.y + next.y) / 2;
}
return `M ${p[0].x},${p[0].y}
Q ${p[1].x},${p[1].y} ${p[2].x},${p[2].y}
Q ${p[3].x},${p[3].y} ${p[4].x},${p[4].y}
Q ${p[5].x},${p[5].y} ${p[6].x},${p[6].y}
Q ${p[7].x},${p[7].y} ${p[8].x},${p[8].y}
Q ${p[9].x},${p[9].y} ${p[10].x},${p[10].y}
Q ${p[11].x},${p[11].y} ${p[0].x},${p[0].y}`
}
const morphPath = (toHex) => {
const path = toHex ? getHexagonPath(80) : getRoundPath(80);
d3.select('path')
.transition()
.duration(750)
.attr('d', path);
setTimeout(() => morphPath(!toHex), 1000);
};
d3.select('path').attr('d', getRoundPath(80))
morphPath(true);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="200" height="200">
<g transform="translate(100,100)">
<path />
</g>
</svg>
I have a simple setup where use clicks location and creates a label with text.
What i would like to be able to do is resize on zoom, so that label is relative size always. Currently label is huge when i zoom in covering key areas.
code:
createTag() {
return new Konva.Tag({
// omitted for brevity
})
}
createLabel(x: number, y: number) {
return new Konva.Label({
x: x,
y: y,
opacity: 0.75
});
}
createText() {
return new Konva.Text({
// omitted for brevity
});
}
createMarker(point: any) {
var label = createLabel(point.x, point.y);
var tag = createTag();
var text = createText();
label.add(tag);
label.add(text);
this.layer.add(label);
this.stage.add(this.layer);
}
Mouse click event listener
this.stage.on('click', function (e) {
// omitted for brevity
var pointer = this.getRelativePointerPosition();
createMarker(pointer);
// omitted for brevity
}
this.stage.on('wheel', function (e) {
// zoom code
});
How can i resize the labels to be relative size of the canvas?
There are many possible solutions. If you are changing scale of the whole stage, then you can just apply reversed scale to the label, so its absolute scale will be always 1.
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
var layer = new Konva.Layer();
stage.add(layer);
var circle = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 50,
fill: 'green',
});
layer.add(circle);
// tooltip
var tooltip = new Konva.Label({
x: circle.x(),
y: circle.y(),
opacity: 0.75,
});
layer.add(tooltip);
tooltip.add(
new Konva.Tag({
fill: 'black',
pointerDirection: 'down',
pointerWidth: 10,
pointerHeight: 10,
lineJoin: 'round',
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 10,
shadowOffsetY: 10,
shadowOpacity: 0.5,
})
);
tooltip.add(
new Konva.Text({
text: 'Try to zoom with wheel',
fontFamily: 'Calibri',
fontSize: 18,
padding: 5,
fill: 'white',
})
);
var scaleBy = 1.01;
stage.on('wheel', (e) => {
e.evt.preventDefault();
var oldScale = stage.scaleX();
var pointer = stage.getPointerPosition();
var mousePointTo = {
x: (pointer.x - stage.x()) / oldScale,
y: (pointer.y - stage.y()) / oldScale,
};
var newScale =
e.evt.deltaY > 0 ? oldScale * scaleBy : oldScale / scaleBy;
stage.scale({ x: newScale, y: newScale });
// apply reversed scale to label
// so its absolte scale is still 1
tooltip.scale({ x: 1 / newScale, y: 1 / newScale })
var newPos = {
x: pointer.x - mousePointTo.x * newScale,
y: pointer.y - mousePointTo.y * newScale,
};
stage.position(newPos);
});
<script src="https://unpkg.com/konva#8.3.0/konva.min.js"></script>
<div id="container"></div>
How do you apply path rotation to graphic elements such as a rectangle or a path?
For example, applying rotation to a path:
<svg width="200px" height="200px" viewbox="0 0 200 200">
<rect x="100" y="0" width="20" height="20" fill="red" transform="rotate(45)"/>
</svg>
IMPORTANT NOTE:
I'm not running an browser. I remember seeing solutions that use browser or canvas to do the calculations.
I only have the markup. For a path I have the path data, for a rectangle the position and width and height, and the line the x1 y1 and x2 y2 data.
UPDATE:
It's important to know the transform origin. That would be rotated from the element center.
I would use an array of points to draw the path. For the rotation I would rotate the points and draw the rotated shape.Please read the comments in my code. I hope this is what you were asking.
const SVG_NS = svg.namespaceURI;
// the rotation
let angle = Math.PI/4;
// the points used to rotate the initial rect
let theRect = [
{ x: 100, y: 0 },
{ x: 100 + 20, y: 0 },
{ x: 100 + 20, y: 0 + 20 },
{ x: 100, y: 0 + 20 }
];
// calculating the rotated points
let rotatedRect = [];
theRect.forEach(p => {
rotatedRect.push(rotatePoint(p, angle));
});
drawRect(theRect);
drawRect(rotatedRect);
// a function to draw the rect. For this I'm using the points array
function drawRect(ry) {
let d = `M${ry[0].x},${ry[0].y}L${ry[1].x},${ry[1].y} ${ry[2].x},${ry[2].y} ${
ry[3].x
},${ry[3].y}`;
drawSVGelmt({ d: d }, "path", svg);
}
// a function used to rotate a point around the origin {0,0}
function rotatePoint(p, rot) {
let cos = Math.cos(rot);
let sin = Math.sin(rot);
return {
x: p.x * cos - p.y * sin,
y: p.x * sin + p.y * cos
};
}
// a function to draw an svg element
function drawSVGelmt(o, tag, parent) {
let elmt = document.createElementNS(SVG_NS, tag);
for (let name in o) {
if (o.hasOwnProperty(name)) {
elmt.setAttributeNS(null, name, o[name]);
}
}
parent.appendChild(elmt);
return elmt;
}
svg{border:1px solid; max-width:90vh;}
<svg id="svg" viewbox="0 0 200 200">
<rect x="100" y="0" width="20" height="20" fill="red" transform="rotate(45)"/>
</svg>
I have a slider that has the following raw snap points:
[-100, -200, -300, -400, -500, -600]
And I would like to convert the sliding value to match the following snap points:
[0, 5, 10, 25, 50, 100]
A raw value in [-100, -200) should be mapped to a value in [0, 5)
A raw value in [-200, -300) should be mapped to a value in [5, 10)
A raw value in [-300, -400) should be mapped to a value in [10, 25)
And so on ..
How can I achieve that?
Edit: added my attempt (different raw values though)
// sliderValue is an integer obtained from the slider
const base = -70
const offset = -80
const limits = [
base + offset * 0, // -70
base + offset * 1, // -150
base + offset * 2, // -230
base + offset * 3, // -310
base + offset * 4, // -390
base + offset * 5, // -470
]
const points = [0, 5, 10, 25, 50, 100]
// I can't even begin to make sense of this
// don't know I came up with it, but it works ¯\_(ツ)_/¯
if (sliderValue <= limits[4]) {
percentage = scaleValue(sliderValue, limits[4], limits[5], 50, 100)
} else if (sliderValue <= limits[3]) {
percentage = scaleValue(sliderValue, limits[3], limits[4], 25, 50)
} else if (sliderValue <= limits[2]) {
percentage = scaleValue(sliderValue, limits[2], limits[3], 10, 25)
} else if (sliderValue <= limits[1]) {
percentage = scaleValue(sliderValue, limits[1], limits[2], 5, 10)
} else if (sliderValue <= limits[0]) {
percentage = scaleValue(sliderValue, limits[0], limits[1], 0, 5)
}
console.log(percentage)
// ..
function scaleValue(num, in_min, in_max, out_min, out_max) {
return ((num - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
}
You could take a function with a look up for the section. Then build the new value, based on the four values as a linear function.
function getValue(x) {
var a = [-100, -200, -300, -400, -500, -600],
b = [0, 5, 10, 25, 50, 100],
i = a.findIndex((v, i, a) => v >= x && x >= a[i + 1]);
return [x, (x -a[i])* (b[i + 1] - b[i]) / (a[i + 1] - a[i]) +b[i]].join(' ');
}
console.log([-100, -150, -200, -250, -300, -350, -400, -450, -500, -550, -600].map(getValue));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Simple linear equation: add 100, divide by 20, then negate.
UPDATE: Due to early-morning eye bleariness, I misread the question. (Sorry!) The general method for mapping linear relations to each other is to figure out the offset of the two sets and the scale factor.
I can't find a smooth relationship between the example points you gave, so I'm not sure how to find a single equation that would neatly and continuously map the points to each other. It looks like your solution (you said it works) might be the best: figure out which range each value maps to, and scale correspondingly.
You can just map the values:
var mapping = {
"-100": 0,
"-200": 5,
"-300": 10,
"-400": 25,
"-500": 50,
"-600": 100
}
function map_values(array){
return [mapping[array[0]], mapping[array[1]]];
}
var input = [-200,-300];
console.log(map_values(input));
I have a grid with items inside of it with x and y co-orditantes. I am trying to write a function (with lodash) to determine where is the first empty spot where the top most left most spot is the first position.
I am trying to do this by iterating over each spot until I find the first empty spot. It is only a 2 column layout so I work through them in a pattern like so - x: 0, y:0 -> x:1, y:0 -> x:0, y:1 -> x:1, y:1 ... and then checking all the items along the way to see if there is not a match, so I then know if there is an opening. My attempt looks like so :
function fillEmptySpace(isFilled, startX, startY) {
if (!isFilled) {
_.forEach(items, function(item, i) {
if (!_.isMatch(item, {
'x': startX
}) && !_.isMatch(item, {
'y': startY
})
) {
console.log("empty spot at", startX, startY);
isFilled = true;
} else if (!_.isMatch(item, {
'x': startX + 1
}) && !_.isMatch(item, {
'y': startY
})) {
console.log("empty spot at", startX + 1, startY);
isFilled = true;
}
});
startY += 1;
fillEmptySpace(isFilled, startX, startY);
}
}
fillEmptySpace(false, 0, 0);
The data looks like so :
var items = [{
i: 'a',
x: 0,
y: 0,
w: 1,
h: 1,
maxW: 2
}, {
i: 'b',
x: 1,
y: 4,
w: 1,
h: 1,
maxW: 2
}, {
i: 'c',
x: 0,
y: 1,
w: 1,
h: 1,
maxW: 2
}, {
i: 'd',
x: 0,
y: 2,
w: 1,
h: 1,
maxW: 2
}];
And here is the fiddle I have been fooling around in : https://jsfiddle.net/alexjm/ugpy13xd/38/
I can't seem to get this logic quite right, I am not sure a this point where I am getting it wrong. Any input would be greatly appreciated!
Just as a note : with the provided data it should identify the first empty space as x:1, y:0, however right now it is saying empty spot at 0 0, which cannot be correct. Thanks!
When it comes to 2D arrays, the 1D index can be calculated with x + y * width. If we then sort the 1D indexes, we can create an O(nlogn) solution:
function findEmptySpace(grid, width) {
var index = _(grid)
.map(function(p) { return p.x + p.y * width })
.sortBy()
.findIndex(_.negate(_.eq));
if (index < 0) index = grid.length;
return {
x: index % width,
y: index / width >> 0 // ">> 0" has the same result as "Math.floor"
};
}
var items = [{x:0,y:0},{x:0,y:4},{x:0,y:1},{x:0,y:2}];
function findEmptySpace(grid, width) {
var index = _(grid)
.map(function(p) { return p.x + p.y * width; })
.sortBy()
.findIndex(_.negate(_.eq));
if (index < 0) index = grid.length;
return {
x: index % width,
y: index / width >> 0 // ">> 0" has the same result as "Math.floor"
};
}
document.getElementById('btn').onclick = function() {
var space = findEmptySpace(items, 2);
items.push(space);
console.log(space);
};
#btn { font-size: 14pt }
<script src="https://cdn.jsdelivr.net/lodash/4.13.1/lodash.min.js"></script>
<button id="btn">Fill the Empty Space</button>
If you pre-sort the array, the solution would be worst-case O(n).
May I suggest checking to see if the point exists vs checking to see if it doesn't. Iterate over each item in the list to see if it exists if it does set a flag, then increment positions through your grid. Keep in mind this will not account for coords less than your intial value of "startY". Consider the following code:
function findEmptySpace(startX, startY) {
var isFilled = false;
_.forEach(items, function(item, i) {
if (_.isMatch(item, { 'x': startX }) && _.isMatch(item, { 'y': startY }) {
// this spot is filled check next x
isFilled = true;
continue;
}
}
if (isFilled == true) {
// we need to recursively call our function but I don't know the value of x
(startX == 0) ? findEmptySpace(1, startY): findEmptySpace(0, startY + 1);
} else {
console.log("Congrats, we found a spot", startX, startY);
}
}
It looks like you're always going to find a match at 0,0 since your logic is finding if there is any item in the list that is not on 0,0, instead of if there is an item in the list on 0,0
What you really want to do is stop checking once you've found an item in the current x,y (and, additionally, check both x and y in your isMatch). You can use your existing routine and your existing isFilled check:
function fillEmptySpace(isFilled, startX, startY) {
_.forEach(items, function(item, i) {
if (!isFilled) {
if (!_.isMatch(item, {'x': startX, 'y': startY})) {
console.log("empty spot at", startX, startY);
isFilled = true;
} else if (!_.isMatch(item, {'x': startX + 1, 'y': startY})) {
console.log("empty spot at", startX + 1, startY);
isFilled = true;
}
}
});
if (!isFilled) {
startY += 1;
fillEmptySpace(isFilled, startX, startY);
}
}