force_directed_graph.rb
require 'jruby_art' require 'toxiclibs' require_relative 'cluster' require_relative 'node' # # Copyright (c) 2010 Daniel Shiffman # # This demo & library is free software you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation either # version 2.1 of the License, or (at your option) any later version. # # http://creativecommons.org/licenses/LGPL/2.1/ # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA class ForceDirectedGraph < Processing::App attr_reader :physics, :clusters, :show_physics, :show_particles, :f def setup size(640, 360) @f = create_font('Georgia', 12, true) @show_physics = true @show_particles = true # Initialize the physics @physics = Physics::VerletPhysics2D.new physics.set_world_bounds(Toxi::Rect.new(10, 10, width - 20, height - 20)) # Spawn a new random graph new_graph end # Spawn a new random graph def new_graph # Clear physics physics.clear center = TVec2D.new(width / 2, height / 2) @clusters = (0..8).map { Cluster.new(physics, rand(3..8), rand(20..100), center) } # All clusters connect to all clusters clusters.each_with_index do |ci, i| clusters[i + 1..clusters.size - 1].each do |cj| ci.connect(cj) end end end def draw # Update the physics world physics.update background(255) # Display all points clusters.each(&:display) if show_particles # If we want to see the physics if show_physics clusters.each_with_index do |ci, i| ci.internal_connections self # Cluster connections to other clusters clusters[1 + i..clusters.size - 1].each do |cj| ci.show_connections(self, cj) end end end # Instructions fill(0) text_font(f) text("'p' to display or hide particles\n'c' to display or hide connections\n'n' for new graph", 10, 20) end # Key press commands def key_pressed case key when 'c' @show_physics = !show_physics @show_particles = true unless show_physics when 'p' @show_particles = !show_particles @show_physics = true unless show_particles when 'n' new_graph end end end ForceDirectedGraph.new(title: 'Force directed graphs')cluster.rb
# # Copyright (c) 2010 Daniel Shiffman # # This demo & library is free software you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation either # version 2.1 of the License, or (at your option) any later version. # # http://creativecommons.org/licenses/LGPL/2.1/ # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA class Cluster attr_reader :nodes, :diameter, :physics # We initialize a with a number of nodes, a diameter, and centerpoint def initialize(physics, n, d, center) @physics = physics @diameter = d @nodes = (0..n).map { Node.new(center.add(TVec2D.random_vector)) } # Connect all the nodes with a Spring nodes[1..nodes.size - 1].each_with_index do |pi, i| nodes[0..i].each do |pj| physics.add_spring(Physics::VerletSpring2D.new(pi, pj, diameter, 0.01)) end end end def display nodes.each(&:display) end # This functons connects one cluster to another # Each point of one cluster connects to each point of the other cluster # The connection is a "VerletMinDistanceSpring" # A VerletMinDistanceSpring is a spring which only enforces its rest length if the # current distance is less than its rest length. This is handy if you just want to # ensure objects are at least a certain distance from each other, but don't # care if it's bigger than the enforced minimum. def connect(other) other_nodes = other.nodes nodes.each do |pi| other_nodes.each do |pj| physics.add_spring(Physics::VerletMinDistanceSpring2D.new(pi, pj, (diameter + other.diameter) * 0.5, 0.05)) end end end # Draw all the internal connections def internal_connections(app) app.stroke(200, 0, 0, 80) nodes[0..nodes.size - 1].each_with_index do |pi, i| nodes[i + 1..nodes.size - 1].each do |pj| app.line(pi.x, pi.y, pj.x, pj.y) end end end # Draw all the connections between this and another Cluster def show_connections(app, other) app.stroke(200, 200, 0, 20) app.stroke_weight(2) other_nodes = other.nodes nodes.each do |pi| other_nodes[0..other_nodes.size - 1].each do |pj| app.line(pi.x, pi.y, pj.x, pj.y) end end end end
node.rb
require 'forwardable' # The Nature of Code # Daniel Shiffman # http://natureofcode.com # Force directed graph # Heavily based on: http://code.google.com/p/fidgen/ # Notice how we are using inheritance here! # We could have just stored a reference to a VerletParticle object # inside the Node class, but inheritance is a nice alternative class Node < Physics::VerletParticle2D extend Forwardable def_delegators(:@app, :fill, :stroke, :stroke_weight, :ellipse) def initialize(pos) super(pos) @app = $app end # All we're doing really is adding a display function to a VerletParticle def display fill(50, 200, 200, 150) stroke(50, 200, 200) stroke_weight(2) ellipse(x, y, 16, 16) end end
No comments:
Post a Comment