Can I access this of an object in JavaScript? - javascript

var b = {
state: 'initial',
initial: {
onClick: function() {
console.log(this);
},
x: 0,
},
}
Hi, I want to know if its possible to access b object inside onClick function in the code above?
the output is {x: 0, onClick: ƒ} instead of {state: 'initial', ... }
Changing it to arrow func will output window object instead.
Im making an escape room game and kind of have a chicken & egg situation.
var spriteObject =
{
img: null,
INITIAL: {
sourceX: 0,
sourceY: 0,
sourceWidth: 64,
sourceHeight: 64,
x: 0,
y: 0,
width: 64,
height: 64,
isWithinBounds: function(x, y) {
return x > this.x && x < this.x + this.width && y > this.y && y < this.y + this.height;
},
},
state: 'INITIAL',
};
const inventoryItems = [];
let currentRoom = "kitchen";
const knife = {
...spriteObject,
INITIAL: {
...spriteObject.INITIAL,
x: 200,
y: 162,
sourceX: 1330,
sourceY: 8,
sourceWidth: 803,
sourceHeight: 514,
width: 803,
height: 514,
onClick: () => {
inventoryItems.push(knife);
layers.kitchen.sprites = layers.kitchen.sprites.filter(sprite => sprite !== knife);
},
},
img: kitchenLayout,
};
const layers = {
kitchen: {
sprites: [knife],
},
};
window.addEventListener("click", function(e) {
const x = e.pageX - canvas.offsetLeft;
const y = e.pageY - canvas.offsetTop;
layers[currentRoom].sprites.forEach(sprite => {
const currSprite = sprite[sprite.state];
if (currSprite.onClick && currSprite.isWithinBounds(x, y)) {
currSprite.onClick();
render();
}
})
})
Let's say I have a kitchen room and a knife on the counter. I can pick the knife and put it in my inventory. The knife will have 3 different conditions: on the counter (initial), in inventory, and dull (after used). I am not sure if I want to model in inventory as a state, but I have trouble figuring out how to remove the knife from list of sprites in kitchen. It is doable in the code above but it seems to rely on the fact that I declare knife as a variable. I don't want to do this, in case I want to declare my items directly on the sprites array. I appreciate any hints, thanks

You should be able to use javascript classes for this. That way you can reference your current class as this.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

Related

What are flexible approaches for creating different kinds or types of shape objects like rect- or triangles?

I have this code snippet that creates an array of boxes, I want to make it generic so it can also, for example, store a triangle. I'm not quite sure what parameters I need to use or how I need to modify this so it will allow a triangle. It almost seems like it would be better to create an array of triangles then position them to form a box if I want triangles and boxes, but then I'd loose the flexibility of creating easy rectangles. Context: this is a snippet of a program that implements a z-buffer.
class Box {
/** #member {Object} position of the box storing x,y,z coordinates */
position;
/** #member {Object} size of the box storing width and height */
size;
/** #member {Object} color of the box given in RGB */
color;
constructor (props) {
this.position = props.position;
this.size = props.size;
this.color = props.color;
}
/**
* Check if given point is in box
* #param {Number} px coordinate of the point
* #param {Number} py coordinate of the point
* #return {Boolean} point in box
*/
pointInBox (px,py) {
return this.position.x < px && this.position.x + this.size.width > px
&& this.position.y < py && this.position.y + this.size.height > py;
}
}
const boxes = [
new Box({
position: { x: 50, y: 50, z: 10 },
size: { width: 150, height: 50 },
color: { r: 255, g: 0, b:0 }
}),
new Box({
position: { x: 80, y: 30, z: 5 },
size: { width: 10, height: 150 },
color: { r: 0, g: 255, b:0 }
}),
new Box({
position: { x: 70, y: 70, z: 8 },
size: { width: 50, height: 40 },
color: { r: 0, g: 0, b: 255 }
})
];
console.log({ boxes });
.as-console-wrapper { min-height: 100%!important; top: 0; }
With vanilla JS you would have to use inheritance such as below. I would recommend using Typescript, a super set of Javascript, that makes using types a lot easier.
class Shape {
constructor({ color, position}){
this.color = color;
this.positon = position;
}
}
class Cube extends Shape {
constructor({color, position, height, width, length}){
super({ color, position });
this.height = height;
this.width = width;
this.length = length;
}
}
const myCube = new Cube({
color: "#555555",
position: {x: 12, y: 5, z: 9},
height: 12,
width: 12,
length: 12
});
console.log(myCube)
In Typescript, it would look something like this instead:
interface Coordinate {
x: number;
y: number;
z: number;
}
interface Shape {
color: string;
position: Coordinate;
}
interface Box extends Shape {
height: number;
width: number;
length: number;
}
And now, If I want a function that will work for both boxes and shapes, you can do it like this:
function getPosition(shape:Shape){
return shape.position;
}
const myShape: Box = {
color: "red",
position: {
x: 1,
y: 4,
z: 7,
},
height: 12,
length: 12,
width: 12,
};
getPosition(myShape);
Because Box extends shape, the function works for both of them, and any other interface that extends shape.
That's just scratching the surface of what you can do with Typescript.
With Vanilla-JS one of cause is not limited to just one option like choosing an inheritance based approach. Composition, based on tailored mixins, does provide/support the flexibility the OP is looking for.
One also is free of where (and when and even how) to compose, for instance at plain object level within e.g. a factory function or at instantiation time within a class constructor.
// function based "position" mixin.
function withPosition({ x = 0, y = 0, z = 0 }) {
// "position" specifc (default) assignment.
Object.assign(this, { position: { x, y, z } });
}
// function based "shape" mixin.
function asShape({ color='#000', ...position }) {
// "shape" specifc (default) assignment.
Object.assign(this, { color });
// delegate "position" specific assignement
// and default handling to the mixin.
withPosition.call(this, (position || {}));
}
// factory function.
function createRectangle({ width=10, height=10, ...options }) {
const type = {};
// delegate "shape" specific assignements
// and default handling to the mixin.
asShape.call(type, options);
// "rectangle" specifc assignments including defaults.
return Object.assign(type, { width, height });
}
// factory function.
function createCube({ length=10, ...options }) {
// composition via ...
// ... "rectangle" specifc forwarding ...
// ............. and "cube" specific `length` enrichment.
return { ...createRectangle(options), length }
}
class Cube {
// `Cube` type instantiation.
constructor({ width=10, height=10, length=10, ...options }) {;
// delegate "shape" specific assignements
// and default handling to the mixin.
asShape.call(this, options);
// "cube" specifc assignments including defaults.
Object.assign(this, { width, height, length });
}/*
constructor({ length=10, ...options }) {
Object.assign(this, { ...createRectangle(options), length });
}*/
}
const my1stRectangle = createRectangle({
x: 50,
y: 50,
z: 10,
width: 150,
height: 50,
color: '#fc0',
});
const my2ndRectangle = createRectangle({});
const myCubeComposite = createCube({
x: 50,
y: 50,
z: 50,
color: '#c0f',
});
const myComposedCubeType = new Cube({});
console.log({
my1stRectangle,
my2ndRectangle,
myCubeComposite,
myComposedCubeType,
});
console.log(
'(myCubeComposite instanceof Cube) ?',
(myCubeComposite instanceof Cube)
);
console.log(
'(myComposedCubeType instanceof Cube) ?',
(myComposedCubeType instanceof Cube)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

Composition in JS

I am learning the concepts of Composition in JS. Below is my demo code.
The moveBy function assigns the values correctly to x and y.
However, the setFillColor function does not assign the passed value to fillColor.
What exactly is happening when the setFillColor function is called?
const withMoveBy = (shape) => ({
moveBy: (diffX, diffY) => {
shape.dimensions.x += diffX;
shape.dimensions.y += diffY;
},
});
const withSetFillColor = (shape) => ({
setFillColor: (color) => {
console.log(shape.fillColor); // 1
shape.fillColor = color;
shape.dimensions.fillColor = color;
console.log(shape.fillColor); // 2
},
});
const shapeRectangle = (dimensions) => ({
type: 'rectangle',
fillColor: 'white',
dimensions,
});
const shapeCircle = (dimensions) => ({
type: 'circle',
fillColor: 'white',
dimensions,
});
const createShape = (type, dimensions) => {
let shape = null;
switch (type) {
case 'rectangle': {
shape = shapeRectangle(dimensions);
break;
}
case 'circle': {
shape = shapeCircle(dimensions);
break;
}
}
if (shape) {
shape = {
...shape,
...withSetFillColor(shape),
...withMoveBy(shape),
};
}
return shape;
};
let r = createShape('rectangle', {
x: 1,
y: 1,
width: 10,
height: 10,
});
let c = createShape('circle', { x: 10, y: 10, diameter: 10 });
r.moveBy(2, 3);
c.moveBy(1, 2);
r.setFillColor('red');
c.setFillColor('blue');
console.log(r);
console.log(c);
OUTPUT:
Line marked as // 1 prints white in case of rectangle as well as circle.
Line marked as // 2 prints red for rectangle and blue for circle.
The final output is:
{
"type": "rectangle",
"fillColor": "white",
"dimensions": {
"x": 3,
"y": 4,
"width": 10,
"height": 10,
"fillColor": "red"
}
}
{
"type": "circle",
"fillColor": "white",
"dimensions": {
"x": 11,
"y": 12,
"diameter": 10,
"fillColor": "blue"
}
}
The fillColor as the property of object is still white.
However, the one inside of dimensions has taken the correct value.
The problem stems from this assignment in createShape - annotations by me:
// creating the "new object"
shape = {
...shape, // shallow copying of the "old object"
...withSetFillColor(shape),
...withMoveBy(shape),
};
Here, you create a new object that is composed of:
shallow-copied properties of the existing ...shape (type, fillcolor, dimensions which is an object)
setFillColor, a closure that is bound to shape (the old object)
moveBy, a closure that is bound to shape (the old object)
After this statement is executed, you have created two shapes:
The "old object", which the methods operate on
The "new object", which you return
Out of the properties copied from the old object, only dimensions is a non-primitive value, so it is shared between the instances.
Then, when you call:
r.moveBy(2, 3);
it changes oldShape.dimensions, but it's the same object as newShape.dimensions, so it's visible in the output.
However, this call:
r.setFillColor('red');
modifies the fillColor property of the oldShape, which you are not seeing. It also writes to oldShape.dimensions.fillColor, which, again, is shared between the objects, so that change is visible in both.
Let me illustrate the problem by re-writing your code. I have removed some of the details to focus on the issue only. Added annotations and logging to the code to show more clearly what happens:
const withSetFillColor = (shape) => ({
setFillColor: (color) => {
console.log(`now changing shape with id [${shape.id}]`);
shape.fillColor = color;
shape.dimensions.fillColor = color;
},
});
const shapeRectangle = (dimensions) => ({
id: 1, //add an ID of the created object for illustrative purpose
type: 'rectangle',
fillColor: 'white',
dimensions,
});
const createShape = (type, dimensions) => {
//variable is now named 1 to showcase what happens
let shape1 = null;
switch (type) {
case 'rectangle': {
shape1 = shapeRectangle(dimensions);
break;
}
}
//this is effectively what happens when you clone and reassign an object:
//a *second one* is created but the first one persists
let shape2 = null;
if (shape1) {
shape2 = {
...shape1,
...withSetFillColor(shape1),
id: 2, //make it a different ID for illustrative purpose
};
}
console.log(`Created shape1 and shape2 and they are the same: ${shape1 === shape2}`);
console.log(`The dimensions object is the same: ${shape1.dimensions === shape2.dimensions}`);
return shape2;
};
let r = createShape('rectangle', {
x: 1,
y: 1,
width: 10,
height: 10,
});
r.setFillColor('red');
console.log(r);
You create and manipulate two different objects. This is the reason why the code assigns a property to the object but it appears as if it is not changed.
There are several way to deal with this.
Only create one object and assign to it
If you use Object.assign() you can directly change one object instead of having two competing ones. Thus, passing the object to the withX() functions will work as intended.
const withMoveBy = (shape) => ({
moveBy: (diffX, diffY) => {
shape.dimensions.x += diffX;
shape.dimensions.y += diffY;
},
});
const withSetFillColor = (shape) => ({
setFillColor: (color) => {
shape.fillColor = color;
shape.dimensions.fillColor = color;
},
});
const shapeRectangle = (dimensions) => ({
type: 'rectangle',
fillColor: 'white',
dimensions,
});
const shapeCircle = (dimensions) => ({
type: 'circle',
fillColor: 'white',
dimensions,
});
const createShape = (type, dimensions) => {
let shape = null;
switch (type) {
case 'rectangle': {
shape = shapeRectangle(dimensions);
break;
}
case 'circle': {
shape = shapeCircle(dimensions);
break;
}
}
if (shape) {
//use Object assign to only manipulate one `shape` object
Object.assign(
shape,
withSetFillColor(shape),
withMoveBy(shape)
);
}
return shape;
};
let r = createShape('rectangle', {
x: 1,
y: 1,
width: 10,
height: 10,
});
let c = createShape('circle', { x: 10, y: 10, diameter: 10 });
r.moveBy(2, 3);
c.moveBy(1, 2);
r.setFillColor('red');
c.setFillColor('blue');
console.log(r);
console.log(c);
Don't use arrow functions, use this instead
Alternatively, use regular functions or the shorthand method definition syntax which lets you use this. You can then add these methods to your object and use this to refer to the object, instead of having to pass it in.
const withMoveBy = { //no need for a function to produce the object
moveBy(diffX, diffY) { //shorthand method syntax
this.dimensions.x += diffX;
this.dimensions.y += diffY;
},
};
const withSetFillColor = { //no need for a function to produce the object
setFillColor(color) { //shorthand method syntax
this.fillColor = color;
this.dimensions.fillColor = color;
},
};
const shapeRectangle = (dimensions) => ({
type: 'rectangle',
fillColor: 'white',
dimensions,
});
const shapeCircle = (dimensions) => ({
type: 'circle',
fillColor: 'white',
dimensions,
});
const createShape = (type, dimensions) => {
let shape = null;
switch (type) {
case 'rectangle': {
shape = shapeRectangle(dimensions);
break;
}
case 'circle': {
shape = shapeCircle(dimensions);
break;
}
}
if (shape) {
shape = {
...shape,
...withSetFillColor,
...withMoveBy,
};
}
return shape;
};
let r = createShape('rectangle', {
x: 1,
y: 1,
width: 10,
height: 10,
});
let c = createShape('circle', { x: 10, y: 10, diameter: 10 });
r.moveBy(2, 3);
c.moveBy(1, 2);
r.setFillColor('red');
c.setFillColor('blue');
console.log(r);
console.log(c);
A mixed approach
This is more of an explanation of what's happening than an actual new approach.
Both of the above both work but show two sides of the same coin. Combining objects together is called mixin*. Mixins are similar to object composition because you build up more complex objects from simpler ones but also a separate category of its own since you do it via concatenation.
Traditionally, you would use Object.assign(obj, mixinA, mixinB) for adding things to obj. Which makes it similar to the first approach. However, mixinA and mixinB would be actual objects like in the second approach.
Using class syntax, there is an interesting alternative to add mixins to a class. I'm adding it here just to show it - it's totally OK to not use classes and use regular objects instead.
const withMoveBy = Base => class extends Base { //mixin
moveBy(diffX, diffY) {
this.dimensions.x += diffX;
this.dimensions.y += diffY;
}
};
const withSetFillColor = Base => class extends Base { //mixin
setFillColor(color) {
this.fillColor = color;
this.dimensions.fillColor = color;
}
};
class Shape {
constructor({type, fillColor, dimensions}) {
this.type = type;
this.fillColor = fillColor;
this.dimensions = dimensions;
}
}
const shapeRectangle = (dimensions) => ({
type: 'rectangle',
fillColor: 'white',
dimensions,
});
const shapeCircle = (dimensions) => ({
type: 'circle',
fillColor: 'white',
dimensions,
});
const createShape = (type, dimensions) => {
let shapeArgs = null;
switch (type) {
case 'rectangle': {
shapeArgs = shapeRectangle(dimensions);
break;
}
case 'circle': {
shapeArgs = shapeCircle(dimensions);
break;
}
}
let shape = null;
if (shapeArgs) {
//add mixins to the Shape class
const mixedInConstructor = withMoveBy(withSetFillColor(Shape));
//create the enhanced class
shape = new mixedInConstructor(shapeArgs);
}
return shape;
};
let r = createShape('rectangle', {
x: 1,
y: 1,
width: 10,
height: 10,
});
let c = createShape('circle', { x: 10, y: 10, diameter: 10 });
r.moveBy(2, 3);
c.moveBy(1, 2);
r.setFillColor('red');
c.setFillColor('blue');
console.log(r);
console.log(c);
* Yes, the title was a pun. You can laugh now.

Running ForceAtlas2 from a set position in Sigma.js

I am trying to render a complex network using the React-Sigma wrapper. I know the base structure of this network to be many nodes of degree one and some of a very high degree. To this end, I have preformatted my graph data with x and y coordinates that represent an approximate layout, with degree one nodes clustered around the nodes they are connected to. I then want to run the ForceAtlas2 simulation from this point. However, when I try to do this, the simulation appears to just randomise and change the initial positions of the nodes to be in the centre, as seen in this gif:
Is there any way to stop this from happening and run the simulation from the initial positions? My code is below:
const App = () => {
const myGraph = getExampleGraph();
return (
<Sigma
graph={myGraph}
renderer="webgl"
settings={{
clone: false,
batchEdgesDrawing: true,
}}
style={{
height: "100vh"
}}
>
<ForceAtlas2
iterationsPerRender={1}
barnesHutOptimize
barnesHutTheta={1}
timeout={50000}
worker
/>
</Sigma>
)
}
Code to get a random example graph like the one I described:
export const getRandomInt = (max: number) => Math.floor(Math.random() * Math.floor(max));
export const getRandomPosition = () => ({
x: Math.random(),
y: Math.random()
});
export const randomisePosition = (position) => ({
x: position.x + (Math.random() - .5)/10,
y: position.y + (Math.random() - .5)/10
});
export const getExampleGraph = () => {
const positions: any = {
"1": getRandomPosition(),
"2": getRandomPosition(),
"3": getRandomPosition()
};
const highDegNodes = [
{ id: "1", size: 20, label: "1", ...positions["1"]},
{ id: "2", size: 20, label: "2", ...positions["2"] },
{ id: "3", size: 20, label: "3", ...positions["3"] }
];
const nodes = Object.assign([], highDegNodes);
const edges = [];
for(let i = 0; i < 50; i += 1) {
const id = (i + 4) + '';
const node = { id, size: 1, label: id, x: 0, y: 0 };
const target = (getRandomInt(3) + 1) + '';
edges.push({
id: `${id}:${target}:${i}`,
source: id,
target,
});
const nodePos = randomisePosition(positions[target]);
node.x = nodePos.x;
node.y = nodePos.y;
nodes.push(node);
if (Math.random() < .1) {
const target2 = (getRandomInt(3) + 1) + '';
edges.push({
id: `${id}:${target2}:${i}:2`,
source: id,
target: target2,
});
}
}
return {
nodes,
edges
}
};
The problem there is with implementation of ForceAtlas2 used in Sigma.js. It expects a higher scale of positions and is highly unstable at scale under 1.
The easiest to stabilize is you can multiply your positions by 100:
const getRandomPosition = () => ({
x: Math.random()*100,
y: Math.random()*100
});
const randomisePosition = (position) => ({
x: position.x + (Math.random() - .5)*10,
y: position.y + (Math.random() - .5)*10
});
you could also apply slowDown={10} to forceAtlas to make it softer and remove batchEdgesDrawing if your graph is not too big:
<Sigma
graph={myGraph}
renderer="webgl"
settings={{
clone: false,
}}
style={{
height: "100vh"
}}
>
<ForceAtlas2
iterationsPerRender={1}
barnesHutOptimize
barnesHutTheta={1}
slowDown={10}
timeout={2000}
worker
/>
</Sigma>

How to merge similar HSL colors together by a specific amount [duplicate]

This question already has answers here:
Find regions of similar color in image
(5 answers)
Closed 4 years ago.
I am looking for a way to merge similar hsl colors together by a specific amount. Let's say I have the following three colors: 150, 50, 100, 149, 49, 100, 151, 51, 99. The result I am looking for should be a single value: 150, 50, 100.
Let me try to explain it better:
Example Data:
const colors = {
red: [
{h:151 , s:57 , l:100},
{h:150 , s:5 , l:100},
{h:155 , s:50 , l:100},
{h:125 , s:100 , l:100},
{h:132 , s:0 , l:100},
],
green: [...],
blue: [...]
}
Now let's say each array contains a lot of items, and I want to reduce the number of items by merging similar items together. The threshold must be a variable. The merging process is simply taking the average of all similar items.
I hope that my explanation is clear, it is quite hard to explain.
Here is something you can use as a start. What it does :
Ignore a value, if there are already a value that is matching (using threshold system);
What you want to do is very specific, so start with understanding this, then adapt it to your need. In case of coding trouble, we will help you.
Actually in here, we shouldn't create your code, but help you with yours. I feel in a good mood
const colors = {
red: [{
h: 151,
s: 57,
l: 100
},
{
h: 150,
s: 5,
l: 100,
},
{
h: 155,
s: 50,
l: 100,
},
{
h: 125,
s: 100,
l: 100,
},
{
h: 132,
s: 0,
l: 100,
},
],
};
const tresholdH = 10;
const tresholdS = 100;
const tresholdL = 100;
function reduceArrayByTreshold(array) {
return array.reduce((tmp, x) => {
if (tmp.some(({
h,
s,
l,
}) => (x.h >= h - tresholdH && x.h <= h + tresholdH) &&
(x.s >= s - tresholdS && x.s <= s + tresholdS) &&
(x.l >= l - tresholdL && x.l <= l + tresholdL))) {
return tmp;
}
return [
...tmp,
x,
];
}, []);
}
const colorsReduced = Object.keys(colors).reduce((tmp, x) => {
tmp[x] = reduceArrayByTreshold(colors[x]);
return tmp;
}, {});
console.log(colorsReduced);

JavaScript array cloning

I have this way to make an array
var playerList = [];
exports.player = function(socket, name)
{
this.id = socket.id;
this.name = name;
this.x = 20;
this.y = 40;
return this
}
exports.addPlayer = function(data)
{
playerList.push(data)
}
And I'm adding items to playerList array like this
var client = new player(socket, data);
exports.addPlayer(client);
But I also got a function that makes the following
exports.getSafeList = function(id)
{
var player_array = playerList.slice();
for(var i = 0; i < player_array.length; i++)
{
if(player_array[i].id != id)
{
player_array[i].id = 'unknown';
}
}
return player_array;
}
And now I do the following
exports.getPlayerList = function()
{
return playerList;
}
console.log(players.getPlayerList())
console.log(players.getSafeList(id))
So far the code is working fine but when I log the 2 functions it seems that getPlayerList variable merges with player_list one, this is the output
When theres just ONE player on the array
[ { id: 'tjvh8XdMtX-o6QYDAAAB', name: 'Raggaer', x: 20, y: 40 } ]
[ { id: 'tjvh8XdMtX-o6QYDAAAB', name: 'Raggaer', x: 20, y: 40 } ]
But when there are more:
[ { id: 'unknown', name: 'Raggaer', x: 20, y: 40 },
{ id: '2-K5At07wLV4BDiAAAAC', name: 'Alvaro', x: 20, y: 40 } ]
[ { id: 'unknown', name: 'Alvaro', x: 20, y: 40 },
{ id: '2-K5At07wLV4BDiAAAAC', name: 'Alvaro', x: 20, y: 40 } ]
As you can see on both arrays id appears as "unknown" when it shouldn't, since I'm not modyfing the playerList array...
The problem is that while Array.prototype.slice() will create a separate copy of the original array, its items will still be references to the same object instances. So modifying an item in one array ends up modifying the corresponding item in the cloned array.
If your items are simple data objects (no functions), this workaround might do the trick for you:
// instead of "var player_array = playerList.slice();"
var player_array = JSON.parse(JSON.stringify(playerList));

Categories