A force-directed graph is a way of drawing an aesthetic graph using simple physics such as repealing and springs. We are going to make our graph interactive so that users can drag nodes around and see how graph reorganizes itself.
In this recipe we are going to use the code base from the Creating a particle system in 2D recipe in Chapter 5, Building Particle Systems. To get some details of how to draw nodes and connections between them, please refer to the Connecting particles recipe in Chapter 6, Rendering and Texturing Particle Systems.
We will create an interactive force-directed graph. Perform the following steps to do so:
vector< pair<Particle*, Particle*> > mLinks; float mLinkLength; Particle* mHandle; bool mIsHandle;
setup
method set default values and create a graph.void MainApp::setup(){ mLinkLength = 40.f; mIsHandle = false; float drag = 0.95f; Particle *particle = newParticle(getWindowCenter(), 10.f, 10.f, drag ); mParticleSystem.addParticle( particle ); Vec2f r = Vec2f::one()*mLinkLength; for (int i = 1; i<= 3; i++) { r.rotate( M_PI * (i/3.f) ); Particle *particle1 = newParticle( particle->position+r, 7.f,7.f, drag ); mParticleSystem.addParticle( particle1 ); mLinks.push_back(make_pair(mParticleSystem.particles[0], particle1)); Vec2f r2 = (particle1->position-particle->position); r2.normalize(); r2 *= mLinkLength; for (int ii = 1; ii <= 3; ii++) { r2.rotate( M_PI * (ii/3.f) ); Particle *particle2 = newParticle( particle1->position+r2, 5.f, 5.f, drag ); mParticleSystem.addParticle( particle2 ); mLinks.push_back(make_pair(particle1, particle2)); Vec2f r3 = (particle2->position-particle1->position); r3.normalize(); r3 *= mLinkLength; for (int iii = 1; iii <= 3; iii++) { r3.rotate( M_PI * (iii/3.f) ); Particle *particle3 = newParticle( particle2->position+r3, 3.f, 3.f, drag ); mParticleSystem.addParticle( particle3 ); mLinks.push_back(make_pair(particle2, particle3)); } } } }
void MainApp::mouseDown(MouseEvent event){ mIsHandle = false; float maxDist = 20.f; float minDist = maxDist; for( std::vector<Particle*>::iterator it = mParticleSystem.particles.begin(); it != mParticleSystem.particles.end(); ++it ){ float dist = (*it)->position.distance( event.getPos() ); if(dist<maxDist&&dist<minDist) { mHandle = (*it); mIsHandle = true; minDist = dist; } } } void MainApp::mouseUp(MouseEvent event){ mIsHandle = false; }
update
method, calculate all forces affecting particles.void MainApp::update() { for( std::vector<Particle*>::iterator it1 = mParticleSystem.particles.begin(); it1 != mParticleSystem.particles.end(); ++it1 ) { for( std::vector<Particle*>::iterator it2 = mParticleSystem.particles.begin(); it2 != mParticleSystem.particles.end(); ++it2 ){ Vec2f conVec = (*it2)->position - (*it1)->position; if(conVec.length() <0.1f)continue; float distance = conVec.length(); conVec.normalize(); float force = (mLinkLength*2.0f - distance)* -0.1f; force = math<float>::min(0.f, force); (*it1)->forces += conVec * force*0.5f; (*it2)->forces += -conVec * force*0.5f; } } for( vector<pair<Particle*, Particle*> > ::iterator it = mLinks.begin(); it != mLinks.end(); ++it ){ Vec2f conVec = it->second->position - it->first->position; float distance = conVec.length(); float diff = (distance-mLinkLength)/distance; it->first->forces += conVec * 0.5f*diff; it->second->forces -= conVec * 0.5f*diff; } if(mIsHandle) { mHandle->position = getMousePos(); mHandle->forces = Vec2f::zero(); } mParticleSystem.update(); }
draw
method implement drawing particles and links between them.void MainApp::draw() { gl::enableAlphaBlending(); gl::clear( Color::white() ); gl::setViewport(getWindowBounds()); gl::setMatricesWindow( getWindowWidth(), getWindowHeight() ); gl::color( ColorA(0.f,0.f,0.f, 0.8f) ); for( vector<pair<Particle*, Particle*> > ::iterator it = mLinks.begin(); it != mLinks.end(); ++it ) { Vec2f conVec = it->second->position - it->first->position; conVec.normalize(); gl::drawLine(it->first->position + conVec * ( it->first->radius+2.f ), it->second->position - conVec * ( it->second->radius+2.f ) ); } gl::color( ci::ColorA(0.f,0.f,0.f, 0.8f) ); mParticleSystem.draw(); }
Particle.cpp
source file, drawing of each particle should be implemented, as follows:void Particle::draw(){ ci::gl::drawSolidCircle( position, radius); ci::gl::drawStrokedCircle( position, radius+2.f); }
In step 2, in the setup
method, we are creating our particles for each level of the graph and adding links between them. In the update
method in step 4, we are calculating forces affecting all particles, which is repelling each particle from each other, and forces coming from the springs connecting the nodes. While repelling spreading particles, springs try to keep them at a fixed distance defined in mLinkLength
.
3.145.8.153