diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fb507e..d25930d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,5 @@ +## Version 1.0.1 ( 2016-01-12 ) +- Fix [#1](https://github.com/rm-code/Graphoon/issues/1) - Adjusted force calculation and hopefully made it more stable + ## Version 1.0.0 ( 2016-01-12 ) - Initial Release diff --git a/Graphoon/Graph.lua b/Graphoon/Graph.lua index 8cde783..c9788e7 100644 --- a/Graphoon/Graph.lua +++ b/Graphoon/Graph.lua @@ -36,14 +36,10 @@ function Graph.new() --- -- Updates the boundaries of the graph. -- This represents the rectangular area in which all nodes are contained. - -- @param minX - The current minimum x position. - -- @param maxX - The current maximum y position. - -- @param minY - The current minimum x position. - -- @param maxY - The current maximum y position. -- @param nx - The new x position to check. -- @param ny - The new y position to check. -- - local function updateBoundaries( minX, maxX, minY, maxY, nx, ny ) + local function updateBoundaries( nx, ny ) return math.min( minX or nx, nx ), math.max( maxX or nx, nx ), math.min( minY or ny, ny ), math.max( maxY or ny, ny ); end @@ -158,7 +154,7 @@ function Graph.new() nodeCallback( nodeA ); end - minX, maxX, minY, maxY = updateBoundaries( minX, maxX, minY, maxY, nodeA:getPosition() ); + minX, maxX, minY, maxY = updateBoundaries( nodeA:getPosition() ); end end diff --git a/Graphoon/Node.lua b/Graphoon/Node.lua index 7d2a409..6180ec7 100644 --- a/Graphoon/Node.lua +++ b/Graphoon/Node.lua @@ -2,14 +2,14 @@ local current = (...):gsub('%.[^%.]+$', ''); local Node = {}; -local FORCE_SPRING = -0.01; -local FORCE_CHARGE = 100000; +local FORCE_SPRING = 0.005; +local FORCE_CHARGE = 200; local FORCE_MAX = 4; -local NODE_SPEED = 8; +local NODE_SPEED = 128; local DAMPING_FACTOR = 0.95; -local DEFAULT_MASS = 0.05; +local DEFAULT_MASS = 3; --- -- @param id - A unique id which will be used to reference this node. @@ -17,7 +17,7 @@ local DEFAULT_MASS = 0.05; -- @param y - The y coordinate the Node should be spawned at (optional). -- @param anchor - Wether the node should be locked in place or not (optional). -- -function Node.new( id, x, y, anchor, ... ) +function Node.new( id, x, y, anchor ) local self = {}; local px, py = x or 0, y or 0; @@ -46,17 +46,37 @@ function Node.new( id, x, y, anchor, ... ) ay = clamp( -FORCE_MAX, ay + fy, FORCE_MAX ); end + --- + -- Calculates the manhattan distance from the node's coordinates to the + -- target coordinates. + -- @param tx - The target coordinate in x-direction. + -- @param ty - The target coordinate in y-direction. + -- + local function getManhattanDistance( tx, ty ) + return px - tx, py - ty; + end + + --- + -- Calculates the actual distance vector between the node's current + -- coordinates and the target coordinates based on the manhattan distance. + -- @param dx - The horizontal distance. + -- @param dy - The vertical distance. + -- + local function getRealDistance( dx, dy ) + return math.sqrt( dx * dx + dy * dy ) + 0.1; + end + --- -- Attract this node to another node. -- @param node - The node to use for force calculation. -- function self:attractTo( node ) - local dx, dy = px - node:getX(), py - node:getY(); - local distance = math.sqrt(dx * dx + dy * dy); + local dx, dy = getManhattanDistance( node:getPosition() ); + local distance = getRealDistance( dx, dy ); dx = dx / distance; dy = dy / distance; - local strength = FORCE_SPRING * distance; + local strength = -1 * FORCE_SPRING * distance * 0.5; applyForce( dx * strength, dy * strength ); end @@ -65,12 +85,12 @@ function Node.new( id, x, y, anchor, ... ) -- @param node - The node to use for force calculation. -- function self:repelFrom( node ) - local dx, dy = px - node:getX(), py - node:getY(); - local distance = math.sqrt(dx * dx + dy * dy); + local dx, dy = getManhattanDistance( node:getPosition() ); + local distance = getRealDistance( dx, dy ); dx = dx / distance; dy = dy / distance; - local strength = FORCE_CHARGE * ( mass / ( distance * distance )); + local strength = FORCE_CHARGE * (( mass * node:getMass() ) / ( distance * distance )); applyForce(dx * strength, dy * strength); end @@ -119,6 +139,10 @@ function Node.new( id, x, y, anchor, ... ) mass = nmass; end + function self:getMass() + return mass; + end + return self; end diff --git a/Graphoon/init.lua b/Graphoon/init.lua index 4363512..5b24261 100644 --- a/Graphoon/init.lua +++ b/Graphoon/init.lua @@ -1,5 +1,5 @@ return { - _VERSION = 'Graphoon v1.0.0', + _VERSION = 'Graphoon v1.0.1', _DESCRIPTION = 'A force directed graph algorithm written in Lua.', _URL = 'https://github.com/rm-code/Graphoon', _LICENSE = [[ diff --git a/README.md b/README.md index 03641bf..d956de3 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ A force directed graph algorithm written in Lua. +![example](https://cloud.githubusercontent.com/assets/11627131/12252902/a5190d90-b8db-11e5-9199-a9fdb61416ac.png) + ## Introduction _Graphoon_ emerged from the graph calculation code used in both [LoGiVi](https://github.com/rm-code/logivi) and [LoFiVi](https://github.com/rm-code/lofivi). @@ -60,3 +62,28 @@ Or by using the ```setAnchor``` function: -- Invert anchor status node:setAnchor( not node:isAnchor(), mouseX, mouseY ) ``` + +### Using custom classes for Nodes and Edges + +If you prefer to not touch the default classes, you can simply inherit from them and tell Graphoon to use your custom classes instead. + +```lua +local GraphLibraryNode = require('lib.libfdgraph.fd').Node + +local CustomNodeClass = {} + +-- You can pass additional arguments to your custom class. Just make sure the +-- default parameters ar in the right order. +function CustomNodeClass.new( id, x, y, anchor, ... ) + local self = GraphLibraryNode.new( id, x, y, anchor ) + + -- ... Custom code +end + +return CustomNodeClass +``` + +```lua +local GraphLibrary = require('Graphoon').Graph +GraphLibrary.setNodeClass( require('CustomNodeClass') ) +```