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);
}
}
Related
This part is responsible for processing the Data in the val array so i everything is an integer and saves it in the output array. (The val array sometimes contains floats and i cant work with those)
Lets say the next element that gets processed has a x and y pair that is already in the output array with any other color.
How do i replace the old object with the new.
val.forEach(element => {
output.push({
x: Math.round(element.x / element.width),
y: Math.round(element.y / element.height),
color: mapColorToBlock(element.color)
})
});
/* val array...
[
{"x":0,"y":0,"color":"blue","width":256,"height":256},
{"x":0,"y":256,"color":"blue","width":256,"height":256},
{"x":256,"y":256,"color":"blue","width":256,"height":256},
{"x":256,"y":0,"color":"blue","width":256,"height":256},
{"x":0,"y":256,"color":"lime","width":256,"height":256}
]
*/
/*output array after the processing(notice how there are 2 objects with x = 0 and y = 1 (the second and last entry in output))
[
{ x: 0, y: 0, color: 12 },
{ x: 0, y: 1, color: 12 },
{ x: 1, y: 1, color: 12 },
{ x: 1, y: 0, color: 12 },
{ x: 0, y: 1, color: 6 }
]
*/
It is important that the new one replaces the old object.(The new entry doesnt have to be in the same position in the array as the old one)
In this scenario the output array would look like this.
[
{ x: 0, y: 0, color: 12 },
{ x: 1, y: 1, color: 12 },
{ x: 1, y: 0, color: 12 },
{ x: 0, y: 1, color: 6 }
]
Note:
It is important that the new one replaces the old object
val.forEach(element => {
const x = Math.round(element.x / element.width);
const y = Math.round(element.y / element.height);
const isDuplicate = output.some(item => item.x === x && item.y === y);
if (!isDuplicate) {
output.push({
x: x,
y: y,
color: mapColorToBlock(element.color)
})
}
});
reduce over the array to create a new object with keys based on the x and y coordinates, and then use Object.values to create a new array of objects from that object's values.
const data=[{x:0,y:0,color:"blue",width:256,height:256},{x:0,y:256,color:"blue",width:256,height:256},{x:256,y:256,color:"blue",width:256,height:256},{x:256,y:0,color:"blue",width:256,height:256},{x:0,y:256,color:"lime",width:256,height:256}];
const out = data.reduce((acc, c) => {
// Extract the values and create a key
// from the x and y values
const { x, y, color, width, height } = c;
const key = `${x}|${y}`;
// Do your x/y calculations
const newX = Math.round(x / width);
const newY = Math.round(y / height);
// Add the current object to the property
// defined by the key *replacing the data
// if it already exists*
acc[key] = { x: newX, y: newY, color };
// Return the accumulator for the next iteration
return acc;
}, {});
// Now extract the values from the new object
console.log(Object.values(out));
A modified version of Shuvo's answer.
This one is only sensible if mapColorToBlock can return different results in successive calls for the same value passed, otherwise Shuvo's answer which skips duplicates is better.
"It is important that the new one replaces the old object"...
let val = [
{"x":0,"y":0,"color":"blue","width":256,"height":256},
{"x":0,"y":256,"color":"blue","width":256,"height":256},
{"x":256,"y":256,"color":"blue","width":256,"height":256},
{"x":256,"y":0,"color":"blue","width":256,"height":256},
{"x":0,"y":256,"color":"lime","width":256,"height":256}
];
let output = [];
val.forEach(element => {
const x = Math.round(element.x / element.width);
const y = Math.round(element.y / element.height);
const found = output.find(item => item.x === x && item.y === y);
if (found) {
found.color = mapColorToBlock(element.color);
} else {
output.push({
x: x,
y: y,
color: mapColorToBlock(element.color)
})
}
});
Here you have to replace the previous element with same x and y
val.forEach(element => {
output = [...output.filter(out => !( out.x === Math.round(element.x / element.width) && out.y === Math.round(element.y / element.height)), {
x: Math.round(element.x / element.width),
y: Math.round(element.y / element.height),
color: mapColorToBlock(element.color)
}]
});
I have some objects in array with coordinates.
var players = [
{x:100, y:100, pid:1},
{x:-100, y:200, pid:2},
{x:600, y:1200, pid:3}
]
var mousepos = {x:50, y:0}
var selected = null
And I want to calculate which of these objects have closest coordinates to mousepos to select it pid
selected = pid of closest coords to mousepos
if anyone could give me easy done code?
Mate here is the answer. Use the distance formula to calculate the distance between points. One with the smallest distance is the closest point.
Fomula :- Sqrt((x2-x1)2 + (y2-y1)2)
It seems you are trying to calculate Euclidean Distance. If so then you can use Math build in object. Once the distance is calculated then you can return the index. Using this index retrieve the he object from the players
var players = [{
x: 100,
y: 100,
pid: 1
},
{
x: -100,
y: 200,
pid: 2
},
{
x: 600,
y: 1200,
pid: 3
}
]
var mousepos = {
x: 50,
y: 0
}
function calculateEuclidean() {
let selectedPid = 0;
players.forEach(function(item, index) {
let distance = Math.sqrt(Math.pow((item.x - mousepos.x), 2) + Math.pow((item.y - mousepos.y), 2))
if (index === 0) {
selectedPid = index
} else if (distance < selectedPid) {
selectedPid = index;
}
})
return players[selectedPid];
}
console.log(calculateEuclidean())
Working example given by random from discord
let closestPlayer = undefined;
let closestDist = undefined;
for (let i = 0; i < players.length; i++) {
let player = players[i];
let distance = Math.hypot(player.x - mousepos.x, player.y - mousepos.y);
if (closestPlayer == undefined || distance <= closestDist) {
closestPlayer = player;
closestDist = distance;
}
}
console.log(closestPlayer)
I need to find a library that allows me to get interpolated values from irregular 2d data. Imagine having something like this:
var data = [{{x: 0, y: 0, value: 0},
{x: 0.5, y: 1, value: 1},
{x: 1, y: 0, value: 2}}, .... Many more elements]
var value = interpolate(data, 0.24, 0.3); // 0.24 = x, 0.3 = y
What the interpolate method does is that it finds the element, in this case a triangle, that the coordinate is inside. Then it interpolates the value between the corners of the element it is contained in.
I do realize that there are lots of aspects in it to optimize performance like building up a tree that allows fast narrowing of elements by having preprocessed bounding boxes. All of this would be great as well, but I am just trying to get started.
There must be some library out there that I can use for it instead of writing my own.
Since search results for barycentric interpolation in javascript were inconclusive, here's some code that might help you get started.
This code takes as input a data set of 2D points, each with a "value", and a "new point" with an unknown value. It first finds the smallest triangle in the data set that contains the "new point", then performs barycentric interpolation using that triangle to find a value for the "new point".
This runs reasonably quickly with a data set of a few hundred points. There are many opportunities for testing, error checking, and optimization - for example, don't look at every possible triangle in the data set. N choose 3 grows with the cube of N, so optimizing to look at triangles made with only points "close to" the "new point" could show significant performance gains.
// Calculate the area of a triangle
function triangle_area(vertexA, vertexB, vertexC) {
return Math.abs(((vertexA.x - vertexC.x) * (vertexB.y - vertexA.y) - (
vertexA.x - vertexB.x) * (vertexC.y - vertexA.y)) * 0.5)
}
// Given a number N, return a list of all possible triples from the list [1..N]
// credit: http://stackoverflow.com/a/5752056/1612562
function list_triples(N) {
var fn = function(n, src, got, all) {
if (n == 0) {
if (got.length > 0) {
all[all.length] = got;
}
return;
}
for (var j = 0; j < src.length; j++) {
fn(n - 1, src.slice(j + 1), got.concat([ src[j] ]), all);
}
return;
}
var triples = [];
// Generates the list [0, ..., N]
// credit: http://stackoverflow.com/a/20066663/1612562
var indices =
Array.apply(null, {length: N}).map(Number.call, Number);
fn(3, indices, [], triples);
return triples;
}
// Given three vertices of a triangle and a point, determine if
// the point falls in the triangle
// credit: https://koozdra.wordpress.com/2012/06/27/javascript-is-point-in-triangle/
// credit: http://www.blackpawn.com/texts/pointinpoly/default.html
function is_in_triangle(newPoint, vertexA, vertexB, vertexC) {
var v0 = [vertexC.x - vertexA.x, vertexC.y - vertexA.y];
var v1 = [vertexB.x - vertexA.x, vertexB.y - vertexA.y];
var v2 = [newPoint.x - vertexA.x, newPoint.y - vertexA.y];
var dot00 = (v0[0] * v0[0]) + (v0[1] * v0[1]);
var dot01 = (v0[0] * v1[0]) + (v0[1] * v1[1]);
var dot02 = (v0[0] * v2[0]) + (v0[1] * v2[1]);
var dot11 = (v1[0] * v1[0]) + (v1[1] * v1[1]);
var dot12 = (v1[0] * v2[0]) + (v1[1] * v2[1]);
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
var v = (dot00 * dot12 - dot01 * dot02) * invDenom;
return ((u >= 0) && (v >= 0) && (u + v < 1));
}
// Perform barycentric interpolation on a point in a triangle
function barycentric_interpolate(newPoint, vertexA, vertexB, vertexC) {
var area = triangle_area(vertexA, vertexB, vertexC);
var sub_area_1 = triangle_area(newPoint, vertexB, vertexC);
var sub_area_2 = triangle_area(vertexA, newPoint, vertexC);
var sub_area_3 = triangle_area(vertexA, vertexB, newPoint);
return ((sub_area_1 * vertexA.v) + (sub_area_2 * vertexB.v) + (sub_area_3 *
vertexC.v)) / area;
}
// Find the smallest triangle in the data set containing the new
// point, and perform barycentric interpolation using that triangle
function interpolate(newPoint, data) {
var triangles = list_triples(data.length);
var smallest_triangle_area = Number.MAX_VALUE;
var smallest_triangle;
for (t in triangles) {
var vertexA = data[triangles[t][0]];
var vertexB = data[triangles[t][1]];
var vertexC = data[triangles[t][2]];
var in_triangle = is_in_triangle(newPoint, vertexA, vertexB, vertexC);
if (in_triangle) {
if (triangle_area(vertexA, vertexB, vertexC) < smallest_triangle_area) {
smallest_triangle = [vertexA, vertexB, vertexC];
}
}
}
return smallest_triangle
? barycentric_interpolate(newPoint, smallest_triangle[0], smallest_triangle[1], smallest_triangle[2])
: "Interpolation failed: newPoint isn't in a triangle";
}
var newPoint = {'x': 0.24, 'y': 0.3};
var data = [
{'x': 0, 'y': 0, 'v': 0},
{'x': 0.5, 'y': 1, 'v': 1},
{'x': 1, 'y': 0, 'v': 2},
{'x': 1.5, 'y': 2.5, 'v': 1.5},
{'x': 2, 'y': 1, 'v': 0.5}
];
console.log(interpolate(newPoint, data));
There are also other kinds of spatial interpolation, e.g. kriging, which does have at least one ready-made .js library.
I have been trying to make highchart tooltip to show the nearest point incase the x-axis value aren't align in different series.
This is what I got so far
http://jsfiddle.net/Yw8hb/5/
Highcharts.wrap(Highcharts.Tooltip.prototype, 'refresh', function (proceed) {
var args = arguments,
points = args[1],
point = points[0],
chart = point.series.chart;
// Loop over all the series of the chart
Highcharts.each(chart.series, function(series) {
// This one already exist
if (series == point.series) return;
var current,
dist,
distance = Number.MAX_VALUE;
// Loop over all the points
Highcharts.each(series.points, function(p) {
// use the distance in X to determine the closest point
dist = Math.abs(p.x - point.x);
if (dist < distance) {
distance = dist;
current = p;
}
});
// Add the closest point to the array
points.push(current);
});
proceed.apply(this, [].slice.call(args, 1));
});
It seems to be working half way there however when you hover in some spot it shows duplicated series. I have spent hours trying to figure this out any help would be very appreciated.
Before insertion, check whether points array contains the current point in your refresh callback function.
// Add the closest point to the array
if(points.indexOf(current)==-1)
points.push(current);
Highcharts.wrap(Highcharts.Tooltip.prototype, 'refresh', function (proceed) {
var args = arguments,
points = args[1],
point = points[0],
chart = point.series.chart;
// Loop over all the series of the chart
Highcharts.each(chart.series, function(series) {
// This one already exist
if (series == point.series) return;
var current,
dist,
distance = Number.MAX_VALUE;
// Loop over all the points
Highcharts.each(series.points, function(p) {
// use the distance in X to determine the closest point
dist = Math.abs(p.x - point.x);
if (dist < distance) {
distance = dist;
current = p;
}
});
// Add the closest point to the array
if(points.indexOf(current)==-1)
points.push(current);
});
proceed.apply(this, [].slice.call(args, 1));
});
$('#container').highcharts({
tooltip: {
shared: true
},
xAxis: {
crosshair: {
color: '#F70000'
}
},
series: [{
data: [{
x: 0.0,
y: 1
}, {
x: 1.0,
y: 2
}, {
x: 2.0,
y: 3
}, {
x: 3.0,
y: 2
}, {
x: 4.0,
y: 1
}]
}, {
data: [{
x: 0.2,
y: 0
}, {
x: 1.2,
y: 1
}, {
x: 2.2,
y: 1
}, {
x: 3.2,
y: 1
}, {
x: 4.2,
y: 2
}]
}, {
data: [{
x: 0.2,
y: 5
}, {
x: 1.2,
y: 9
}, {
x: 2.2,
y: 4
}, {
x: 3.2,
y: 5
}, {
x: 4.2,
y: 3
}]
}]
});
#container {
min-width: 300px;
max-width: 800px;
height: 300px;
margin: 1em auto;
}
<script src="http://code.jquery.com/jquery-git.js"></script>
<script src="http://code.highcharts.com/highcharts.js"></script>
<script src="http://code.highcharts.com/modules/exporting.js"></script>
<div id="container"></div>
If you want to show visible series' in the tooltip only, change
// This one already exist
if (series == point.series) return;
to
// This one already exist
if (series == point.series || series.visible==false) return;
Thanks for you solution!!!
for constant order the tooltips
Highcharts.wrap(Highcharts.Tooltip.prototype, 'refresh', function (proceed) {
var args = arguments,
points = args[1],
point = points[0],
chart = point.series.chart;
// Loop over all the series of the chart
Highcharts.each(chart.series, function (series) {
// This one already exist
if (series == point.series || series.visible == false)
return;
var current,
dist,
distance = Number.MAX_VALUE;
// Loop over all the points
Highcharts.each(series.points, function (p) {
// use the distance in X to determine the closest point
dist = Math.abs(p.x - point.x);
if (dist < distance) {
distance = dist;
current = p;
return;
}
});
// Add the closest point to the array
if (points.indexOf(current) == -1)
points.push(current);
});
// for not changing the tooltip series order
var tt = [].slice.call(args, 1);
tt[0].sort(function (a, b) {
if (a.color < b.color)
return -1;
if (a.color > b.color)
return 1;
return 0;
});
proceed.apply(this, tt);
});
Don't forget tooltip option shared!
options = {
tooltip: {
shared: true,
....
I'm unsure if this is possible, given that the order rotations are applied can affect the form of the rotational matrix; but I'd like get the Euler angles from a CSS matrix3d Transform. I'm finding a dearth of documentation on the format of the matrix3d and how transformations are applied. Here's my code so far:
getRotation: function (el) {
var matrix = Esprit.getTransform(el);
// 2d matrix
if (matrix.length === 6) {
return {
x: 0,
y: 0,
z: Math.round(Math.atan2(matrix[1], matrix[0]) * (180 / Math.PI))
};
}
// 3d matrix
else {
// incorrect calculations
// only work for a single rotation
// return {
// x: Math.round(Math.atan2(matrix[6], matrix[5]) * (180/Math.PI)),
// y: Math.round(Math.atan2(-matrix[2], matrix[0]) * (180/Math.PI)),
// z: Math.round(Math.atan2(matrix[1], matrix[0]) * (180/Math.PI))
// };
// convert from string to number
// for (var i = 0, len = matrix.length; i < len; i++) {
// matrix[i] = Number(matrix[i]);
// }
// gimball lock for positive 90 degrees
if (matrix[2] === 1) {
return {
x: Esprit.toDegrees(Math.atan2(matrix[0], matrix[1])),
y: Esprit.toDegrees(Math.PI / 2),
z: 0
}
}
// gimball lock for negative 90 degrees
else if (matrix[2] === -1) {
return {
x: Esprit.toDegrees(-Math.atan2(matrix[0], matrix[1])),
y: Esprit.toDegrees(-Math.PI / 2),
z: 0
}
}
// no gimball lock
else {
return {
x: Esprit.toDegrees(Math.atan2(matrix[6], matrix[10])),
y: Esprit.toDegrees(Math.asin(matrix[2])),
z: Esprit.toDegrees(Math.atan2(-matrix[1], matrix[0]))
}
}
}
},
getTransform: function (el) {
var transform = getComputedStyle(el).webkitTransform;
return transform !== 'none' ? transform.split('(')[1].split(')')[0].split(',') : Esprit.create3dMatrix();
},
toDegrees: function (radians) {
return Math.round(radians * 180 / Math.PI);
}
Any help or ideas would be greatly appreciated. Thanks!
John Schulz (#jfsiii) posted this response:
https://gist.github.com/4119165