I have plotted some words on a canvas. They have random positions. I want them to slowly gravitate towards one another according to a set of rules.
The rules are that some words have to be above, to the left of, to the right of other words.
var rules = {
"id": {
leftOf: ["title"],
},
"title": {
above: ["userId"],
},
"completed": {
"rightOf": ["title"]
}
}
How do I add independent gravitational attractions between multiple objects mathematically?
In my codepen, I have just rigged the x and y to the positions rule.
Full code:
<canvas id="canvas" width="1000" height="300"></canvas>
<script type="text/javascript">
// vector code from https://gist.github.com/winduptoy/a1aa09c3499e09edbd33
function Vector(x, y) {
this.x = x || 0;
this.y = y || 0;
}
/* INSTANCE METHODS */
Vector.prototype = {
negative: function() {
this.x = -this.x;
this.y = -this.y;
return this;
},
add: function(v) {
if (v instanceof Vector) {
this.x += v.x;
this.y += v.y;
} else {
this.x += v;
this.y += v;
}
return this;
},
subtract: function(v) {
if (v instanceof Vector) {
this.x -= v.x;
this.y -= v.y;
} else {
this.x -= v;
this.y -= v;
}
return this;
},
multiply: function(v) {
if (v instanceof Vector) {
this.x *= v.x;
this.y *= v.y;
} else {
this.x *= v;
this.y *= v;
}
return this;
},
divide: function(v) {
if (v instanceof Vector) {
if(v.x != 0) this.x /= v.x;
if(v.y != 0) this.y /= v.y;
} else {
if(v != 0) {
this.x /= v;
this.y /= v;
}
}
return this;
},
equals: function(v) {
return this.x == v.x && this.y == v.y;
},
dot: function(v) {
return this.x * v.x + this.y * v.y;
},
cross: function(v) {
return this.x * v.y - this.y * v.x
},
length: function() {
return Math.sqrt(this.dot(this));
},
normalize: function() {
return this.divide(this.length());
},
min: function() {
return Math.min(this.x, this.y);
},
max: function() {
return Math.max(this.x, this.y);
},
toAngles: function() {
return -Math.atan2(-this.y, this.x);
},
angleTo: function(a) {
return Math.acos(this.dot(a) / (this.length() * a.length()));
},
toArray: function(n) {
return [this.x, this.y].slice(0, n || 2);
},
clone: function() {
return new Vector(this.x, this.y);
},
set: function(x, y) {
this.x = x; this.y = y;
return this;
}
};
/* STATIC METHODS */
Vector.negative = function(v) {
return new Vector(-v.x, -v.y);
};
Vector.add = function(a, b) {
if (b instanceof Vector) return new Vector(a.x + b.x, a.y + b.y);
else return new Vector(a.x + b, a.y + b);
};
Vector.subtract = function(a, b) {
if (b instanceof Vector) return new Vector(a.x - b.x, a.y - b.y);
else return new Vector(a.x - b, a.y - b);
};
Vector.multiply = function(a, b) {
if (b instanceof Vector) return new Vector(a.x * b.x, a.y * b.y);
else return new Vector(a.x * b, a.y * b);
};
Vector.divide = function(a, b) {
if (b instanceof Vector) return new Vector(a.x / b.x, a.y / b.y);
else return new Vector(a.x / b, a.y / b);
};
Vector.equals = function(a, b) {
return a.x == b.x && a.y == b.y;
};
Vector.dot = function(a, b) {
return a.x * b.x + a.y * b.y;
};
Vector.cross = function(a, b) {
return a.x * b.y - a.y * b.x;
};
var texts = ["userId", "id", "title", "completed"];
var rules = {
"id": {
leftOf: ["title"],
},
"title": {
above: ["userId"],
},
"completed": {
"rightOf": ["title"]
}
}
var vectors = [];
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
function Thing(text, x, y) {
this.text = text;
this.pos = new Vector(x, y);
this.velocity = new Vector(getRandomArbitrary(-1, 1), getRandomArbitrary(-1, 1));
this.thrust = 0.1;
}
var things = texts.map(function (item) {
return new Thing(item, getRandomArbitrary(0, 900), getRandomArbitrary(0, 300));
}
);
var lookup = {};
for (var i=0; i<texts.length; i++) {
lookup[texts[i]] = things[i];
}
var ctx = document.getElementById('canvas').getContext('2d');
ctx.font = '18px serif';
function tick() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i=0; i < things.length; i ++) {
var thing = things[i];
thing.velocity.add(thing.thrust);
for (var j=0; j < things.length; j++) {
if (things[j].text == thing.text) { continue }
if (!rules.hasOwnProperty(thing.text)) { continue }
var rule = rules[thing.text];
if (rule.hasOwnProperty( "leftOf")) {
console.log(thing.text, "must be left of", rule["leftOf"]);
for (var k=0; k < rule["leftOf"].length; k++ ) {
if (rule["leftOf"] == things[j].text) {
console.log("Changing velocity of", thing.text, "to cause it to be left of", things[j].text);
thing.pos.x = things[j].pos.x - 20;
thing.pos.y = things[j].pos.y;
}
}
}
if (rule.hasOwnProperty( "above")) {
console.log(thing.text, "must be above", rule["above"]);
for (var k=0; k < rule["above"].length; k++ ) {
if (rule["above"] == things[j].text) {
console.log("Changing velocity of", thing.text, "to cause it to be above", things[j].text);
thing.pos.x = things[j].pos.x;
thing.pos.y = things[j].pos.y - 30;
}
}
}
if (rule.hasOwnProperty( "rightOf")) {
console.log(thing.text, "must be right of", rule["rightOf"]);
for (var k=0; k < rule["rightOf"].length; k++ ) {
if (rule["rightOf"] == things[j].text) {
console.log("Changing velocity of", thing.text, "to cause it to be right of", things[j].text);
thing.pos.x = things[j].pos.x + 60;
thing.pos.y = things[j].pos.y;
}
}
}
}
thing.pos.add(thing.velocity);
ctx.fillText(thing.text, thing.pos.x, thing.pos.y);
}
}
setInterval(tick, 1000);
</script>