Better 2D canvas collision detection - javascript

I am creating a simple platformer. I am trying to create collisions with objects and be able to detect those. With the code I have below I am not able to detect collisions properly and stop the player from moving when they collide. What is supposed to happen is the code is supposed to check if there is a collision with any of the objects in the level.Objects array. The code I have now does not detect collisions and you fall infinity into the ground. How would I create a function that detects collisions properly and returns true on which side it collides with?
function runGame() {
var game = document.getElementById('game')
var ctx = game.getContext("2d")
var DonaldRest = document.getElementById('DonaldRest')
var GrassTile = document.getElementById('GrassTile')
var gravity = 0.5
var momentum = 0;
var momentumDown = 0;
var spacing = 64;
var speed = 2;
var maxSpeed = 2;
var jumpHeight = 3;
var levels = [{
Name: "Level 1",
Objects: [{
Type: "GrassFloor",
Location: {
x: 0,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 1,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 2,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 3,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 4,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 5,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 6,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 7,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 8,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 9,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 10,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 11,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 12,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 13,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 14,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 15,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 16,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 17,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 18,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 19,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 20,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 21,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 22,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 23,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 24,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 25,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 26,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 27,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 28,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 29,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, ]
}]
var player = {
position: {
x: 0,
y: 0
},
Time: 0
}
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
var game = setInterval(function() {
ctx.imageSmoothingEnabled = false
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
ctx.fillStyle = "#adfffa"
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height)
ctx.drawImage(DonaldRest, ctx.canvas.width / 2 - (96 / 2), ctx.canvas.height / 2 - (96 / 2), 96, 96)
var Level = levels[0]
var Objects = Level.Objects
var OnGround = checkCollisions().Bottom
if (OnGround == false) {
if (momentumDown <= maxSpeed) {
momentumDown -= gravity;
player.position.y += momentumDown;
} else {
player.position.y += momentumDown;
}
} else {
momentumDown = 0;
console.log("collided")
}
for (var j = 0; j < Objects.length; j++) {
if (Objects[j].Type == "GrassFloor") {
ctx.drawImage(GrassTile, Objects[j].Location.x - player.position.x, (ctx.canvas.height - spacing + player.position.y) - (spacing * Objects[j].Height), spacing, spacing)
for (var i = -5; i < Objects[j].Height; i++) {
ctx.drawImage(DirtTile, Objects[j].Location.x - player.position.x, (ctx.canvas.height - spacing) - (i * spacing) + player.position.y, spacing, spacing)
}
}
}
}, 17); //17
$(document).keydown(function(e) {
if (e.which == 32) {
if (checkCollisions().Bottom == true) {
console.log(momentumDown);
momentumDown -= jumpHeight
console.log(momentumDown);
}
}
})
function isTouchingFloor(e1, e2) {
return e1.x < (e2.x + e2.w) && (e1.x + e1.w) > e2.x && e1.y - momentumDown < (e2.y + e2.h) && (e1.y - momentumDown + e1.h) > e2.y;
}
function checkCollisions() {
var Objects = levels[0].Objects;
var Collision = {
Top: false,
Left: false,
Bottom: false,
Right: false
}
var GrassTileImg = new Image()
var o1 = {
y: player.position.y,
h: 96,
x: player.position.x,
w: 96
}
for (var i = 0; i < Objects.length; i++) {
var o2 = {
y: Objects[i].Location.y,
x: Objects[i].Location.x,
h: 64,
w: 64
}
if (isTouchingFloor(o1, o2) == true) {
Collision.Bottom == true;
}
console.log(Collision.Bottom)
}
return Collision
}
}

This is a snippet of code I used when I was doing a group project for checking collisions. Essentially we make a function to check the collision between two objects and the corresponding side.
Collision Function
/**
* Checks for a collision of two objects. Moves objectA if side is a string with the word move.
* #param objectA The object that needs to move.
* #param objectB The object that needs to block.
* #param side If true, makes return a string. If "move", moves objectA.
* #returns {*} String if side evaluates to true, otherwise boolean.
*/
function checkCollision(objectA, objectB, side) {
if (side) {
var vx = objectA.centerX() - objectB.centerX(),
vy = objectA.centerY() - objectB.centerY(),
combinedHalfWidths = objectA.halfWidth() + objectB.halfWidth(),
combinedHalfHeights = objectA.halfHeight() + objectB.halfHeight(),
collisionSide = "";
if (Math.abs(vx) < combinedHalfWidths && Math.abs(vy) < combinedHalfHeights) {
var overlapX = combinedHalfWidths - Math.abs(vx),
overlapY = combinedHalfHeights - Math.abs(vy);
if (overlapX > overlapY) {
if (vy > 0) {
if (side === "move") {
objectA.vy = objectB.vy;
objectA.y += overlapY;
}
collisionSide = "top";
} else {
if (side === "move") {
objectA.vy = objectB.vy;
objectA.y -= overlapY;
}
collisionSide = "bottom";
}
} else {
if (vx > 0) {
if (side === "move") {
objectA.vx = objectB.vx;
objectA.x += overlapX;
}
collisionSide = "left";
} else {
if (side === "move") {
objectA.vx = objectB.vx;
objectA.x -= overlapX;
}
collisionSide = "right";
}
}
}
return collisionSide;
} else {
return !(objectA.x + objectA.width < objectB.x ||
objectB.x + objectB.width < objectA.x ||
objectA.y + objectA.height < objectB.y ||
objectB.y + objectB.height < objectA.y);
}
}
This function does the checking for all objects we have loaded in our level.
Collision Checking
function doCollisionChecks() {
var checkPush = false;
player.isOnGround = false;
for (var i = 0; i < solids.length; i++) {
if (checkCollision(player, solids[i], "move") === "bottom") {
player.isOnGround = true;
player.state = player.STANDING;
if (solids[i].vx) {
if (solids[i].vx !== player.extraVX) {
player.extraVX = solids[i].vx
}
} else {
player.extraVX = 0;
}
}
}
for (i = 0; i < objects.length; i++) {
if (checkCollision(objects[i], player, true) === "right" || checkCollision(objects[i], player, true) === "left") {
player.speedLimit = scaleWidth(2);
objects[i].speedLimit = scaleWidth(2);
checkPush = true;
}
//Letting the player move boxes, while avoiding a "box hop" bug.
if (checkCollision(objects[i], player, true) === "top") {
player.y = objects[i].y - player.height;
} else if (checkCollision(objects[i], player, true) === "bottom") {
player.y = objects[i].y + objects[i].height;
} else if (player.centerY() > objects[i].y + objects[i].height * 0.1 && player.centerY() < objects[i].y + objects[i].height * 0.9) {
checkCollision(objects[i], player, "move");
}
for (var j = 0; j < solids.length; j++) {
if (checkCollision(objects[i], solids[j], "move") === "bottom" && solids[j].vx) {
objects[i].extraVX = solids[j].vx;
} else {
objects[i].extraVX = 0;
}
checkCollision(objects[i], solids[j], "move");
}
for (j = 0; j < objects.length; j++) {
if (j !== i) {
//Avoids boxes falling through one another.
if (checkCollision(objects[i], objects[j], true) === "top") {
checkCollision(objects[j], objects[i], "move");
} else {
checkCollision(objects[i], objects[j], "move");
}
}
}
if (checkCollision(player, objects[i], true) === "bottom") {
player.isOnGround = true;
player.state = player.STANDING;
player.extraVX = objects[i].extraVX;
} else if (checkCollision(player, objects[i], true) === "top") {
score -= 50;
gameState = OVER;
}
checkCollision(player, objects[i], "move");
if (objects[i].y > c.height) {
if (objects[i].correct) {
score += 100;
objects.splice(i, 1);
checkWin();
} else {
fRed = 0;
score -= 100;
objects.splice(i, 1);
}
}
}
for (i = 0; i < enemies.length; i++) {
if (checkCollision(enemies[i], player)) {
score -= 50;
gameState = OVER;
}
j = 0;
while (enemies[i] && j < objects.length) {
if (checkCollision(objects[j], enemies[i], true) === "bottom") {
score += 75;
objects[j].vy = -1 * scaleHeight(6);
enemies.splice(i, 1);
//score++
}
j++;
}
}
if (checkCollision(player, powerUp)) {
score += 25;
activatePowerUp();
}
if (!checkPush) {
player.speedLimit = scaleWidth(3);
for (i = 0; i < objects.length; i++) {
objects[i].speedLimit = scaleWidth(3);
}
}
}
Sorry but there is a lot of irrelevant attributes used such as speed limits etc. But it works correctly.
You can find the whole source here.

Related

Why are the edge lines misplaced in plotly.js network graph? [duplicate]

I am trying to plot a 3d network using plotly.js
<html>
<head>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<div id="myDiv"></div>
<script>
var nodes = [
{ x: 0, y: 0, z: 0 },
{ x: 1, y: 1, z: 1 },
{ x: 2, y: 0, z: 2 },
{ x: 3, y: 1, z: 3 },
{ x: 4, y: 0, z: 4 }
];
var edges = [
{ source: 0, target: 1 },
{ source: 1, target: 2 },
{ source: 2, target: 3 },
{ source: 3, target: 4 }
];
var x = [];
var y = [];
var z = [];
for (var i = 0; i < nodes.length; i++) {
x.push(nodes[i].x);
y.push(nodes[i].y);
z.push(nodes[i].z);
}
var xS = [];
var yS = [];
var zS = [];
var xT = [];
var yT = [];
var zT = [];
for (var i = 0; i < edges.length; i++) {
xS.push(nodes[edges[i].source].x);
yS.push(nodes[edges[i].source].y);
zS.push(nodes[edges[i].source].z);
xT.push(nodes[edges[i].target].x);
yT.push(nodes[edges[i].target].y);
zT.push(nodes[edges[i].target].z);
}
var traceNodes = {
x: x, y: y, z: z,
mode: 'markers',
marker: { size: 12, color: 'red' },
type: 'scatter3d'
};
var traceEdges = {
x: [xS, xT],
y: [yS, yT],
z: [zS, zT],
mode: 'lines',
line: { color: 'red', width: 2},
opacity: 0.8,
type: 'scatter3d'
};
var layout = {
margin: { l: 0, r: 0, b: 0, t: 0 }
};
Plotly.newPlot('myDiv', [traceNodes, traceEdges], layout);
</script>
</body>
</html>
The nodes of the network are visible but the edges are not visible. Suggestions on
how to fix this issue will be really helpful.
The problem is how traceEdges is built. On each axis, there should be a list of (source, target) coordinates separated by a null value, ie. :
x: [source_0.x, target_0.x, null, source_1.x, target_1.x, null, ...]
We use null values to properly isolate each edge from the others and prevent chaining them all together (we want to draw a line between source_0 and target_0, but not between target_0 and source_1).
So instead of having [xS, xT], [yS, yT], and [zS, zT] we will just have edge_x, edge_y, and edge_z, defined as follows :
const edge_x = [];
const edge_y = [];
const edge_z = [];
for (var i = 0; i < edges.length; i++) {
const a = nodes[edges[i].source];
const b = nodes[edges[i].target];
edge_x.push(a.x, b.x, null);
edge_y.push(a.y, b.y, null);
edge_z.push(a.z, b.z, null);
}
const traceEdges = {
x: edge_x,
y: edge_y,
z: edge_z,
type: 'scatter3d',
mode: 'lines',
line: { color: 'red', width: 2},
opacity: 0.8
};
// (rest of the code doesn't change)
Here is the output :

Plotting 3d network graphs in plotly

I am trying to plot a 3d network using plotly.js
<html>
<head>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<div id="myDiv"></div>
<script>
var nodes = [
{ x: 0, y: 0, z: 0 },
{ x: 1, y: 1, z: 1 },
{ x: 2, y: 0, z: 2 },
{ x: 3, y: 1, z: 3 },
{ x: 4, y: 0, z: 4 }
];
var edges = [
{ source: 0, target: 1 },
{ source: 1, target: 2 },
{ source: 2, target: 3 },
{ source: 3, target: 4 }
];
var x = [];
var y = [];
var z = [];
for (var i = 0; i < nodes.length; i++) {
x.push(nodes[i].x);
y.push(nodes[i].y);
z.push(nodes[i].z);
}
var xS = [];
var yS = [];
var zS = [];
var xT = [];
var yT = [];
var zT = [];
for (var i = 0; i < edges.length; i++) {
xS.push(nodes[edges[i].source].x);
yS.push(nodes[edges[i].source].y);
zS.push(nodes[edges[i].source].z);
xT.push(nodes[edges[i].target].x);
yT.push(nodes[edges[i].target].y);
zT.push(nodes[edges[i].target].z);
}
var traceNodes = {
x: x, y: y, z: z,
mode: 'markers',
marker: { size: 12, color: 'red' },
type: 'scatter3d'
};
var traceEdges = {
x: [xS, xT],
y: [yS, yT],
z: [zS, zT],
mode: 'lines',
line: { color: 'red', width: 2},
opacity: 0.8,
type: 'scatter3d'
};
var layout = {
margin: { l: 0, r: 0, b: 0, t: 0 }
};
Plotly.newPlot('myDiv', [traceNodes, traceEdges], layout);
</script>
</body>
</html>
The nodes of the network are visible but the edges are not visible. Suggestions on
how to fix this issue will be really helpful.
The problem is how traceEdges is built. On each axis, there should be a list of (source, target) coordinates separated by a null value, ie. :
x: [source_0.x, target_0.x, null, source_1.x, target_1.x, null, ...]
We use null values to properly isolate each edge from the others and prevent chaining them all together (we want to draw a line between source_0 and target_0, but not between target_0 and source_1).
So instead of having [xS, xT], [yS, yT], and [zS, zT] we will just have edge_x, edge_y, and edge_z, defined as follows :
const edge_x = [];
const edge_y = [];
const edge_z = [];
for (var i = 0; i < edges.length; i++) {
const a = nodes[edges[i].source];
const b = nodes[edges[i].target];
edge_x.push(a.x, b.x, null);
edge_y.push(a.y, b.y, null);
edge_z.push(a.z, b.z, null);
}
const traceEdges = {
x: edge_x,
y: edge_y,
z: edge_z,
type: 'scatter3d',
mode: 'lines',
line: { color: 'red', width: 2},
opacity: 0.8
};
// (rest of the code doesn't change)
Here is the output :

How do i find the click event on indicator in radar chart?

My project uses echart to create radar chart and i have to find click event for indicators around radar chart.
It is implemented like this.
createradarchart() {
this.theme.getJsTheme()
.pipe(
takeWhile(() => this.alive),
delay(1),
)
.subscribe(config => {
this.options = {
name: 'KPI Radar',
grid: {
left: '5%',
right: '5%',
top: 0,
bottom: 0
},
// label: {
// distance: 5
// },
type: 'radar',
color: ['red', 'green', 'blue'],
legend: {
bottom: 5,
itemGap: 20,
data: ['Baseline', 'Threshold', 'Actual'],
textStyle: {
color: 'black',
fontSize: 10
}
},
radar: {
indicator: this.indicator,
nameGap: 5,
shape: 'circle',
radius: '43%',
name: {
textStyle: {
color: 'black',
fontSize: 10
}
}
},
tooltip: {
show: true,
textStyle: {fontSize:10},
trigger: 'item',
formatter: (params => {
return params['name']+'-'+params['value'][1];
})
},
series: this.seriesdata,
};
this.ctx = this.echartsIntance.getRenderedCanvas();
this.chart = new Chart(this.ctx,{type:'radar', options: this.options})
// this.ctx = canvas.getContext("2d");
});
}
where data and options are in format, with data being fetched from server:
seriesdata: any = {type: 'radar', data:[{name:'Baseline',value:[1,2,3,4,5]},{name:'Threshold',value:[1,2,3,4,5]},{name:'Actual',value:[1,2,3,4,5]}]};
indicator = [
{ name: 'DL User Thpt_Kbps[CDBH]', max: 100 },
{ name: 'ERAB SSR[CDBH]', max: 100 },
{ name: 'PS DCR %[CDBH]', max: 100 },
{ name: 'VoLTE CSSR', max: 100 },
{ name: 'VoLTE DCR[CBBH]', max: 100 }
];
options: EChartOption = {
name: 'KPI Radar',
grid: {
left: '2%',
right: '2%',
top: 0,
bottom: 0
},
// label:{
// distance: 5
// },
type: 'radar',
color: ['red', 'green', 'blue'],
legend: {
orient: 'vertical',
align: 'left',
right: 20,
data: ['Baseline', 'Threshold', 'Actual'],
textStyle: {
color: 'black',
fontSize: 10
}
},
radar: {
indicator: this.indicator,
nameGap: 5,
shape: 'circle',
radius:'60%',
},
tooltip: {
show: false,
// trigger: 'item',
// formatter: (params => {
// return params['name']+'-'+params['value'][1];
// })
}
};
This is where i want click event to be triggered, having the name of label which is clicked.
Approach i found to do this is this, but it didnt work, i debugged and found that scale.pointLabels is empty.
labelClicked(e:any){
var self = this;
var helpers = Chart.helpers;
var scale = self.chart.scale;
var opts = scale.options;
var tickOpts = opts.ticks;
// Position of click relative to canvas.
var mouseX = e.offsetX;
var mouseY = e.offsetY;
var labelPadding = 5; // number pixels to expand label bounding box by
// get the label render position
// calcs taken from drawPointLabels() in scale.radialLinear.js
var tickBackdropHeight = (tickOpts.display && opts.display) ?
helpers.valueOrDefault(tickOpts.fontSize, Chart.defaults.global.defaultFontSize)
+ 5: 0;
var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
for (var i = 0; i < scale.pointLabels.length; i++) {
// Extra spacing for top value due to axis labels
var extra = (i === 0 ? tickBackdropHeight / 2 : 0);
var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5);
// get label size info.
// TODO fix width=0 calc in Brave?
// https://github.com/brave/brave-browser/issues/1738
var plSize = scale._pointLabelSizes[i];
// get label textAlign info
var angleRadians = scale.getIndexAngle(i);
var angle = helpers.toDegrees(angleRadians);
var textAlign = 'right';
if (angle == 0 || angle == 180) {
textAlign = 'center';
} else if (angle < 180) {
textAlign = 'left';
}
// get label vertical offset info
// also from drawPointLabels() calcs
var verticalTextOffset = 0;
if (angle === 90 || angle === 270) {
verticalTextOffset = plSize.h / 2;
} else if (angle > 270 || angle < 90) {
verticalTextOffset = plSize.h;
}
// Calculate bounding box based on textAlign
var labelTop = pointLabelPosition.y - verticalTextOffset - labelPadding;
var labelHeight = 2*labelPadding + plSize.h;
var labelBottom = labelTop + labelHeight;
var labelWidth = plSize.w + 2*labelPadding;
var labelLeft;
switch (textAlign) {
case 'center':
labelLeft = pointLabelPosition.x - labelWidth/2;
break;
case 'left':
labelLeft = pointLabelPosition.x - labelPadding;
break;
case 'right':
labelLeft = pointLabelPosition.x - labelWidth + labelPadding;
break;
default:
console.log('ERROR: unknown textAlign '+textAlign);
}
var labelRight = labelLeft + labelWidth;
// Render a rectangle for testing purposes
self.ctx.save();
self.ctx.strokeStyle = 'red';
self.ctx.lineWidth = 1;
self.ctx.strokeRect(labelLeft, labelTop, labelWidth, labelHeight);
self.ctx.restore();
// compare to the current click
if (mouseX >= labelLeft && mouseX <= labelRight && mouseY <= labelBottom && mouseY >= labelTop) {
alert(scale.pointLabels[i]+' clicked');
// Break loop to prevent multiple clicks, if they overlap we take the first one.
break;
}
}
}
Thanks in advance

Split Overlap Ranges

I have implemented an algorithm for dividing overlapping existing ranges into a list of date / number ranges.
The algorithm is working but I was wondering if you can avoid the last loop of the algorithm.
It's possible?
Input Data
0-100(red)
90-150(green)
90-150(blue)
140-300(yellow)
170-240(black)
350-530(orange)
50-500(silver)
50-60(pink)
Output Data
0-49(red)
50-60(red,silver,pink)
61-89(red,silver)
90-100(red,green,blue,silver)
101-139(green,blue,silver)
140-150(green,blue,yellow,silver)
151-169(yellow,silver)
170-240(yellow,black,silver)
241-300(yellow,silver)
301-349(silver)
350-500(orange,silver)
501-530(orange)
Javascript Code:
function splitRanges(original_intervals) {
for (var to = [], from = [], n, i = original_intervals.length; i--;) {
if (to.indexOf(n = original_intervals[i].to) < 0)
to.push(n);
if (from.indexOf(n = original_intervals[i].from) < 0)
from.push(n);
}
to.sort(function(a, b) {
return a - b;
});
from.sort(function(a, b) {
return a - b;
});
var intervals = [];
while (to.length) {
var sFrom = from.shift();
var sTo = 0;
if (from.length == 0) {
sTo = (from.push((n = to.shift()) + 1), n);
} else {
if (from[0] > to[0]) {
while (to[0] < from[0]) {
from.unshift(to[0] + 1);
to.shift();
}
sTo = from[0] - 1;
} else {
sTo = from[0] - 1;
}
}
intervals.push({
from: sFrom,
to: sTo,
colors: []
});
}
/***********************Loop that i want remove*/
intervals.forEach(function(item, index) {
original_intervals.forEach(function(item1, index1) {
if ((item.from >= item1.from && item.from <= item1.to) || (item.to >= item1.from && item.to <= item1.to))
item.colors.push(item1.color);
});
});
return intervals;
}
var r1 = [{
id: 1,
from: 0,
to: 100,
color:'red'
}, {
id: 2,
from: 90,
to: 150,
color:'green'
}, {
id: 3,
from: 90,
to: 150,
color:'blue'
}, {
id: 4,
from: 140,
to: 300,
color:'yellow'
}, {
id: 5,
from: 170,
to: 240,
color:'black'
}, {
id: 6,
from: 350,
to: 530,
color:'orange'
}, {
id: 7,
from: 50,
to: 500,
color:'silver'
}
, {
id: 8,
from: 50,
to: 60,
color:'pink'
}
];
console.log(splitRanges(r1));
You need some iterations, at least the one with getting all range points, and then generate an array out of it and take the subset of colors for each small interval.
var data = [{ from: 0, to: 100, color: 'red' }, { from: 90, to: 150, color: 'green' }, { from: 90, to: 150, color: 'blue' }, { from: 140, to: 300, color: 'yellow' }, { from: 170, to: 240, color: 'black' }, { from: 350, to: 530, color: 'orange' }, { from: 50, to: 500, color: 'silver' }, { from: 50, to: 60, color: 'pink' }],
ranges = new Set,
parts,
result;
data.forEach(({ from, to }) => (ranges.add(from), ranges.add(to)));
parts = [...ranges].sort((a, b) => a - b);
result = parts.slice(1).map(function (a, i, aa) {
var from = i ? aa[i - 1] : parts[0],
to = a,
colors = data.filter(d => d.from <= from && to <= d.to).map(({ color }) => color);
return { from, to, colors };
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to divide an array into equal parts in NodeJS

I am new to nodejs and i need to divide an array which contain dates on x-axis and its points on y axis and trying to draw a graph using an array to store data for x and y axis to do that i am doing this :
while(startDate <= endDate)
{
arr.push({x:startDate.toISOString().slice(0,10),y: 0});
startDate.setDate(startDate.getDate() + 1);
}
it will store all the dates from start date to ending date now i need to divide it into weeks so i am finding weeks by :
var Weeks = Math.round((endDate - startDate) / (7 * 24 * 60 * 60 * 1000));
now to get on which date there is a point so i do :
for (var i = doc.length - 1; i >= 0; i--) {
for (var j = arr.length - 1; j >= 0; j--) {
if (arr[j].x == doc[i].deal_end_date) {
arr[j].y ++;
}
}
}
}
now this will give me an output as below :
startDate: 2017-07-10, endDate: 2017-07-31
arr :
[ { x: '2017-07-10', y: 1 },
{ x: '2017-07-11', y: 0 },
{ x: '2017-07-12', y: 0 },
{ x: '2017-07-13', y: 0 },
{ x: '2017-07-14', y: 0 },
{ x: '2017-07-15', y: 1 },
{ x: '2017-07-16', y: 0 },
{ x: '2017-07-17', y: 0 },
{ x: '2017-07-18', y: 0 },
{ x: '2017-07-19', y: 0 },
{ x: '2017-07-20', y: 0 },
{ x: '2017-07-21', y: 0 },
{ x: '2017-07-22', y: 0 },
{ x: '2017-07-23', y: 0 },
{ x: '2017-07-24', y: 0 },
{ x: '2017-07-25', y: 0 },
{ x: '2017-07-26', y: 0 },
{ x: '2017-07-27', y: 0 },
{ x: '2017-07-28', y: 0 },
{ x: '2017-07-29', y: 0 },
{ x: '2017-07-30', y: 0 },
{ x: '2017-07-31', y: 0 } ]
now i need to divide this array i.e arr into weeks and
i tried
var i,j,temparray,chunk = Weeks;
for (i=0,j=arr.length; i<j; i+=chunk) {
temparray = arr.slice(i,i+chunk);
}
but it stores in temparray as :
temparray: [ { x: '2017-07-31', y: 0 } ]
and i need my temparray as below :
startDate: 2017-07-01 endDate: 2017-07-28
Weeks: 4
/*temparray[1] should be from arr[0] to arr[6]*/
temparray[1] :
[ { x: '2017-07-01', y: 0 },
{ x: '2017-07-02', y: 0 },
{ x: '2017-07-03', y: 0 },
{ x: '2017-07-04', y: 0 },
{ x: '2017-07-05', y: 1 },
{ x: '2017-07-06', y: 0 },
{ x: '2017-07-07', y: 0 }]
/*temparray[2] should be from arr[7] to arr[13]*/
temparray[2] :
[ { x: '2017-07-08', y: 0 },
{ x: '2017-07-09', y: 0 },
{ x: '2017-07-10', y: 0 },
{ x: '2017-07-11', y: 0 },
{ x: '2017-07-12', y: 0 },
{ x: '2017-07-13', y: 0 },
{ x: '2017-07-14', y: 0 }]
/*temparray[3] should be from arr[14] to arr[20]*/
temparray[3] :
[ { x: '2017-07-15', y: 0 },
{ x: '2017-07-16', y: 0 },
{ x: '2017-07-17', y: 0 },
{ x: '2017-07-18', y: 0 },
{ x: '2017-07-19', y: 0 },
{ x: '2017-07-20', y: 0 },
{ x: '2017-07-21', y: 0 }]
/*temparray[4] should be from arr[21] to arr[27]*/
temparray[4] :
[ { x: '2017-07-22', y: 0 },
{ x: '2017-07-23', y: 0 },
{ x: '2017-07-24', y: 0 },
{ x: '2017-07-25', y: 0 },
{ x: '2017-07-26', y: 0 },
{ x: '2017-07-27', y: 0 },
{ x: '2017-07-28', y: 0 }]
A solution using fill and map:
function splitArray(array, chunkSize) {
return Array(Math.ceil(array.length/chunkSize)).fill().map(function(_,i) {
return array.slice(i * chunkSize, i * chunkSize + chunkSize);
});
}
var results = splitArray([1,2,3,4,5,6,7,8], 3);
console.log(results);
You can adapt it to use dates
With one simple line of code, you may get the solution that may work up to 3 times faster than provided above:
const src = [...'abcdefghijklmnop'];
const chunkArr = (arr, size) => arr.reduceRight((res,_,__,self) => [...res, self.splice(0, size)],[]);
console.log(chunkArr(src, 3));
.as-console-wrapper {max-height: 100% !important; top 0}
As answered by #Alberto Trindade Tavares i just do it by :
/* arr is my original array */
var ressu = splitArray(arr, 7);
function splitArray(arr, chunkSize) {
return Array(Math.ceil(arr.length/chunkSize)).fill().map(function(_,i) {
return arr.slice(i * chunkSize, i * chunkSize + chunkSize);
});
}
You can also do this by using for loop. This is easy to understand.
function splitToChunks(array, parts) {
const result = [];
for (let i = parts; i > 0; i--) {
result.push(array.splice(0, Math.ceil(array.length / i)));
}
return result;
}
var results = splitToChunks([1,2,3,4,5,6,7,8], 3);
console.log(results);

Categories