-
Notifications
You must be signed in to change notification settings - Fork 63
Add force-directed graph #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| window.demoDescription = 'Force-directed Graph generation'; | ||
|
|
||
| var space = new CanvasSpace('pt').setup({ | ||
| bgcolor: '#e6e6e6' | ||
| }); | ||
| var form = new Form(space); | ||
| var graph = new ForceDirectedGraph(space.size.x, space.size.y); | ||
|
|
||
| for (var i = 0; i < 50; i++) { | ||
| // Could be new Vector as well (or anything that inherits from Vector) | ||
| graph.addVertex(new Vertex()); | ||
| } | ||
| for (var i = 1; i < 50; i++) { | ||
| graph.addEdge(new Edge(graph.vertices[i - 1], graph.vertices[i])); | ||
| } | ||
| graph.addEdge(new Edge(graph.vertices[0], graph.vertices[graph.vertices.length - 1])); | ||
| graph.randomize(); | ||
| graph.generate(); | ||
|
|
||
| space.add({ | ||
| animate: function(time, dt) { | ||
| form.stroke(false); | ||
| form.points(graph.vertices); | ||
| form.stroke('#000'); | ||
| form.lines(graph.edges); | ||
| } | ||
| }); | ||
|
|
||
| space.play(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| Function::property = (prop, desc) -> | ||
| Object.defineProperty @prototype, prop, desc | ||
|
|
||
|
|
||
| # Hacky Edge implementation | ||
| class Edge | ||
| constructor: (source, target) -> | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of a new class with eg:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Two solutions I can currently think of:
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. Then I think either |
||
| @source = source | ||
| @target = target | ||
|
|
||
| @property 'p1', | ||
| get: -> | ||
| return { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can be simplified as @target.clone() |
||
| x: @target.x, | ||
| y: @target.y, | ||
| z: @target.z | ||
| } | ||
|
|
||
| @property 'x', | ||
| get: -> | ||
| return @source.x | ||
|
|
||
| @property 'y', | ||
| get: -> | ||
| return @source.y | ||
|
|
||
| @property 'z', | ||
| get: -> | ||
| return @source.z | ||
|
|
||
| clone: () -> | ||
| return new Edge(@source.clone(), @target.clone()) | ||
|
|
||
|
|
||
| class Vertex extends Vector | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be a |
||
| constructor: () -> | ||
| super | ||
| @displacement = new Vector() | ||
|
|
||
| class Graph | ||
| constructor: () -> | ||
| @vertices = [] | ||
| @edges = [] | ||
|
|
||
| addVertex: (vertex) -> | ||
| @vertices.push(vertex) | ||
| return @ | ||
|
|
||
| addEdge: (edge) -> | ||
| @edges.push(edge) | ||
| return @ | ||
|
|
||
|
|
||
| class ForceDirectedGraph extends Graph | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think if we simply make this as |
||
| # Fruchterman & Reingold algorithm: http://citeseer.ist.psu.edu/viewdoc/download?doi=10.1.1.13.8444&rep=rep1&type=pdf | ||
| # Walshaw's algorithm: http://jgaa.info/accepted/2003/Walshaw2003.7.3.pdf | ||
|
|
||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for reading all the papers! 👍 |
||
| # ## Create a new force-directed graph. Vertices and Edges are stored in `this.vertices` and `this.edges`, respectively. | ||
| # @return new ForceDirectedGraph object | ||
| constructor: (width, height) -> | ||
| super() | ||
| @width = width | ||
| @height = height | ||
| @frames = [] | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| _fa: (x, k) -> | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. May want to move |
||
| return x * x / k; | ||
|
|
||
| _fr: (x, k) -> | ||
| return k * k / x; | ||
|
|
||
| _cool: (lambda, temperature) -> | ||
| return lambda * temperature | ||
|
|
||
| # ## Calculate new vertex/edge positions using a force-directed graph algorithm | ||
| # @return this ForceDirectedGraph object | ||
| generate: () -> | ||
| lambda = 0.9 # From Walshaw paper | ||
| iterations = 50 # From F&R paper | ||
| temperature = 0.1 * Math.sqrt(@width * @height) # From F&R paper | ||
| C = 0.2 # From Walshaw paper | ||
| k = C * Math.sqrt(@width * @height / @vertices.length) | ||
|
|
||
| for i in [0...iterations] by 1 | ||
|
|
||
| # Caluclate repulsive forces | ||
| for v in @vertices | ||
|
|
||
| # Each vertex has a position and a displacement vector | ||
| v.displacement = new Vector() | ||
|
|
||
| for u in @vertices when u isnt v | ||
| delta = v.$subtract(u) | ||
| deltaMag = delta.magnitude() | ||
|
|
||
| if (deltaMag == 0) | ||
| deltaMag = 0.01; # If two different vertices are in the same position | ||
|
|
||
| v.displacement.add( | ||
| delta.$divide(deltaMag) | ||
| .multiply(@_fr(deltaMag, k)) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would prefer moving this to a single line: |
||
| ) | ||
|
|
||
| # Calculate attractive forces | ||
| for e in @edges | ||
| delta = e.source.$subtract(e.target) | ||
| deltaMag = delta.magnitude() | ||
|
|
||
| if (deltaMag == 0) | ||
| deltaMag = 0.01; | ||
|
|
||
| e.source.displacement.subtract( | ||
| delta.$divide(deltaMag) | ||
| .multiply(@_fa(deltaMag, k)) | ||
| ) | ||
| e.target.displacement.add( | ||
| delta.$divide(deltaMag) | ||
| .multiply(@_fa(deltaMag, k)) | ||
| ) | ||
|
|
||
| # Limit the maximum displacement to the temperature | ||
| # and then make sure the vertices don't fall ourside the frame | ||
| for v in @vertices | ||
| disp = v.displacement | ||
| dispMag = v.displacement.magnitude() | ||
|
|
||
| # Move vertex | ||
| v.add(disp.divide(dispMag).multiply(Math.min(dispMag, temperature))) | ||
|
|
||
| if (v.x < 0) | ||
| v.x = 0 | ||
| if (v.x > @width) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use Alternatively, use the Point's max and min functions: |
||
| v.x = @width | ||
| if (v.y < 0) | ||
| v.y = 0 | ||
| if (v.y > @height) | ||
| v.y = @height | ||
|
|
||
| # Cooling | ||
| temperature = @_cool(lambda, temperature) | ||
|
|
||
| # ## Move every vertex in the graph to a random position (within the frame) | ||
| # @return this ForceDirectedGraph object | ||
| randomize: () -> | ||
| for vertex in @vertices | ||
| x = Math.random() * @width | ||
| y = Math.random() * @height | ||
| vertex.moveTo(x, y); | ||
| return @ | ||
|
|
||
|
|
||
| this.Edge = Edge; | ||
| this.Vertex = Vertex; | ||
| this.ForceDirectedGraph = ForceDirectedGraph; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if we should add
VertexandEdgeas separate classes right now, unless they are useful enough on their own.