Add first working draft of the force directed graph

This commit is contained in:
Robert Machmer 2015-12-20 12:09:20 +01:00
parent ff55890510
commit 805278d67a
3 changed files with 191 additions and 0 deletions

13
Edge.lua Normal file
View File

@ -0,0 +1,13 @@
local Edge = {};
function Edge.new( id, origin, target )
local self = {};
self.id = id;
self.origin = origin;
self.target = target;
return self;
end
return Edge;

78
Graph.lua Normal file
View File

@ -0,0 +1,78 @@
local Node = require('Node');
local Edge = require('Edge');
local Graph = {};
-- TODO remove / replace LÖVE functions
function Graph.new()
local self = {};
-- Node Objects are stored in a table using their ID as an index.
local nodes = {};
local edges = {};
local edgeIDs = 0;
local center = Node.new( 'center ', love.graphics.getWidth() * 0.5, love.graphics.getHeight() * 0.5);
function self:init( table )
math.randomseed(120123091239581834213143141);
for _, id in pairs( table.nodes ) do
self:addNode( Node.new( id, math.random(100, 800), math.random(100, 400) ) );
end
for _, edge in pairs( table.edges ) do
self:addEdge( nodes[edge[1]], nodes[edge[2]] );
end
end
function self:addNode( node )
nodes[node:getID()] = node;
end
function self:addEdge( origin, target )
edges[edgeIDs] = Edge.new( edgeIDs, origin, target );
edgeIDs = edgeIDs + 1;
end
function self:update( dt, ... )
for _, edge in pairs( edges ) do
edge.origin:attractTo( edge.target );
edge.target:attractTo( edge.origin );
end
for _, nodeA in pairs( nodes ) do
nodeA:attractTo( center );
for _, nodeB in pairs( nodes ) do
if nodeA ~= nodeB then
nodeA:repelFrom( nodeB );
end
end
nodeA:move( dt );
end
end
function self:draw()
for _, edge in pairs( edges ) do
love.graphics.line( edge.origin:getX(), edge.origin:getY(), edge.target:getX(), edge.target:getY() );
end
for _, nodeA in pairs( nodes ) do
love.graphics.points( nodeA:getX(), nodeA:getY() );
love.graphics.print( nodeA:getID(), nodeA:getX(), nodeA:getY() );
end
end
function self:getNodeAt(x, y, range)
for _, node in pairs( nodes ) do
local nx, ny = node:getPosition();
if x < nx + range and x > nx - range and y < ny + range and y > ny - range then
return node;
end
end
end
return self;
end
return Graph;

100
Node.lua Normal file
View File

@ -0,0 +1,100 @@
local Node = {};
local FORCE_SPRING = -0.01;
local FORCE_CHARGE = 100000;
local FORCE_MAX = 4;
local NODE_SPEED = 8;
local DAMPING_FACTOR = 0.95;
---
-- @param id - A unique id which will be used to reference this node.
--
function Node.new( id, x, y )
local self = {};
local px, py = x or 0, y or 0;
local ax, ay = 0, 0;
local vx, vy = 0, 0;
---
-- Clamps a value to a certain range.
-- @param min
-- @param val
-- @param max
--
local function clamp( min, val, max )
return math.max( min, math.min( val, max ) );
end
---
-- Calculates the new xy-acceleration for this node.
-- The values are clamped to keep the graph from "exploding".
-- @param fx - The force to apply in x-direction.
-- @param fy - The force to apply in y-direction.
--
local function applyForce( fx, fy )
ax = clamp( -FORCE_MAX, ax + fx, FORCE_MAX );
ay = clamp( -FORCE_MAX, ay + fy, FORCE_MAX );
end
function self:attractTo( node )
local dx, dy = px - node:getX(), py - node:getY();
local distance = math.sqrt(dx * dx + dy * dy);
dx = dx / distance;
dy = dy / distance;
local strength = FORCE_SPRING * distance;
applyForce( dx * strength, dy * strength );
end
function self:repelFrom( node )
local dx, dy = px - node:getX(), py - node:getY();
local distance = math.sqrt(dx * dx + dy * dy);
dx = dx / distance;
dy = dy / distance;
local mass = 0.015 * (0 + math.log(math.max(24, 0)));
local strength = FORCE_CHARGE * ((mass) / (distance * distance));
applyForce(dx * strength, dy * strength);
end
---
-- Update the node's position based on the calculated velocity and
-- acceleration.
--
function self:move( dt )
vx = (vx + ax * dt * NODE_SPEED) * DAMPING_FACTOR;
vy = (vy + ay * dt * NODE_SPEED) * DAMPING_FACTOR;
px = px + vx;
py = py + vy;
ax, ay = 0, 0;
end
---
-- Returns the node's unique identifier.
--
function self:getID()
return id;
end
function self:getX()
return px;
end
function self:getY()
return py;
end
function self:getPosition()
return px, py;
end
function self:setPosition( nx, ny )
px, py = nx, ny;
end
return self;
end
return Node;