Tutorial: A Model-View-Controller in Processing

From dftwiki3
Revision as of 11:44, 28 November 2014 by Thiebaut (talk | contribs) (Requirements)
Jump to: navigation, search

--D. Thiebaut (talk) 10:30, 26 November 2014 (EST)



This tutorial presents a simple Model-View-Controller implemented in Java, using Processing. The viewer is the Applet, and separate classes define the model and the controller. The system displays a simple grid network of vertices connected via random edges to their four neighbors (up, down, left, right).




Requirements


  • You need to setup Eclipse to run Processing apps. This is simply accomplished by
  1. importing the core.jar file found in a new installed Processing 2 application on your system (Windows or Mac) into your Eclipse project.
  2. Control/Right clicking on core.jar in Eclipse, selecting Build Path, then Add to Build Path.
  • You can find more detailed instructions [1]].


Running the Applet


  • Create a new project in Eclipse, or open an existing project
  • Add the 3 classes listed below
  • Import the core.jar file found in the Processing application installed on your computer, into your project.
  • Right/Control Click on core.jar, then select Build Path, and Add to Build Path. Your MVC is now ready to go!
  • Select the MVC1_Viewer class as your main class, and start it. You should see an applet containing a graph similar to (but might not have the same edges as) the one shown below, or in the video above.


50x50Network.png


Source


Controller


import processing.core.PApplet;

/**
 * MVC1_controller.  
 * The controller of a simple Model-View-Controller (MVC) system.
 * The MVC system displays a 50x50 network of vertices with left,
 * right, up, and down edges to their neighbors.
 * The controller is started by the viewer, which extends PApplet,
 * and which is the first object to become alive.  
 * The controller starts the model which initializes the graph.
 * Every graphical interaction in the viewer is passed on to the
 * controller. 
 * 
 * @author D. Thiebaut
 *
 */
public class MVC1_controller {
	// links to the viewer and model.
	MVC1_viewer viewer     = null;
	MVC1_model	model  	   = null;

	/**
	 * constructor.  
	 */
	public MVC1_controller() {
	}

	/**
	 * mutator
	 * @param v reference to the viewer
	 */
	public void setViewer( MVC1_viewer v ) {
		viewer = v;
	}
	
	/**
	 * mutator
	 * @param m reference to the model
	 */
	public void setModel( MVC1_model m ) {
		model = m;
	}
	
	/**
	 * launched by the viewer, which passes a reference to itself in the
	 * process.  Creates a set of references between the model, the view, 
	 * and the controller.
	 * Initializes the graph held by the model (vertices and edges)
	 * @param v reference to the viewer.
	 */
	public void initAll( MVC1_viewer v ) {
		// init viewer
		viewer = v;
		
		// create the model 
		model  = new MVC1_model();
		
		// create reference system between MVC components
		model.setController( this );
		viewer.setModel( model );
		model.setViewer( viewer );
		
		// create the graph of vertices and edges
		model.initGraph();
	}

	/**
	 * called by viewer when the mouse is over a given vertex.
	 * decides what viewer should do, depending on different 
	 * modes of operation.
	 * @param vertex
	 */
	public void setVertexUnderMouse( int vertex ) {
		// display vertex name
		viewer.displayVertexName( vertex );
		// display adjacency list
		viewer.displayAdjacencyList( vertex );
	}

}


Viewer


import java.util.ArrayList;

import processing.core.PApplet;

/**
 * The viewer class of a simple Model-View-Controller (MVC) system.
 * The MVC system displays a 50x50 network of vertices with left,
 * right, up, and down edges to their neighbors.
 * This class is the main class, as it extends PApplet, which will 
 * automatically call setup() on start-up, and will repeatedly call
 * draw(), 30 times a second.  
 * Constantly redraws the network of vertices and edges held by the 
 * model, 30 times a second.  
 * Passes special conditions to the controller for decision.  For
 * example, when the mouse is over a vertex, or when the mouse
 * is clicked somewhere on the canvas of the applet.
 * 
 * @author D. Thiebaut
 *
 */
public class MVC1_viewer extends PApplet {
	MVC1_controller 	controller = null;
	MVC1_model  		model  	   = null;
	public	int     	WIDTH	   = 800; // best generic width
	public  int			HEIGHT	   = 800; // best generic height
	private final int 	DELTAX	   = 10;  // no pixels between vertices
	private final int	DELTAY	   = 10;  // no pixels between vertices
	private int 		MINDIST	   = 5;   // min dist for vertex detected 
	private int 		vertexUnderMouse = -1;
									  // as under mouse pointer
	/**
	 * mutator: sets a reference to the model.
	 * @param m reference to the model
	 */
	public void setModel( MVC1_model m ) {
		model = m;
	}

	/**
	 * helper function.  Given a vertex number, returns its position
	 * (x, y) on applet canvas. Vertex 0 will be at (DELTAX, DELTAY).
	 * @param vertex the vertex to position
	 * @param noRows the number of rows in the network
	 * @param noCols the number of columns in the network
	 * @return an int array.  xy[0] is x, xy[1] is y.  In pixels.
	 */
	private int[] getXY( int vertex, int noRows, int noCols ) {
		int[] xy = new int[2];
		xy[0] = DELTAX + (int) ( vertex / noRows ) * DELTAX;
		xy[1] = DELTAY + ( vertex % noRows ) * DELTAY;
		return xy;
	}

	/**
	 * computes the number of vertex located at location (row, col).
	 * @param row
	 * @param col
	 * @return the number (int) of the vertex.
	 */
	private int vertexAtRowCol( int row, int col ) {
		int v = row + col * model.getNoRows();
		if ( v < 0 || v > model.getNoVertices() )
			return -1;
		return v;
	}

	/**
	 * the entry point for the MVC system.  The PApplet will automatically
	 * call setup() first.  This is the place that initializes everything
	 * up, including controller and model.  Gets the controller to link
	 * the 3 systems together.  The controller will initialize the model with
	 * the graph.
	 */
	public void setup() {
		//--- create model and controller, create reference --- 
		//--- system between all three.					    ---
		controller = new MVC1_controller( );
		controller.initAll( this );
		
		//--- make window fit model graph exactly        ---
		//--- add blank area of 30 pixels high at bottom ---
		//DELTAX = round( WIDTH / (model.getNoCols() + 1 ) );
		//DELTAY = round( HEIGHT / (model.getNoRows() + 1 ) );
		WIDTH = DELTAX * (model.getNoCols() + 1 );
		HEIGHT = 30 + DELTAY * (model.getNoRows() + 1 );
		
		//--- set Applet geometry ---
		size( WIDTH, HEIGHT );
		smooth();
	}
	
	/**
	 * return the vertex that is within MINDIST pixels of the current mouse location.
	 * @return the vertex number, or -1 if none found.
	 */
	public int closestVertexToMouse() {
		int row = round( (mouseY-DELTAY) / DELTAY );
		int col = round( (mouseX-DELTAX) / DELTAX );
		int v = vertexAtRowCol( row, col );
		if ( v == -1 ) 
			return -1;
		int[] xy = getXY( v, model.getNoRows(), model.getNoCols() );
		if ( dist( xy[0], xy[1], mouseX, mouseY ) < MINDIST ) 
			return v;
		return -1;
	}

	/**
	 * draw() is called automatically 30 times a second by mechanism inside
	 * the PApplet that is hidden from the programmer.  The role of draw() is
	 * to erase the screen (too fast for the user to notice) and redraw the 
	 * graph with vertices and edges, and whatever text needs to be displayed
	 * on the canvas.
	 * Detects if the mouse pointer is over a vertex and if so, calls the 
	 * controller to decide what should be done in this case.
	 */
	public void draw() {
		// erase window, make background white
		background( 255, 255, 255 );
		
		// get geometry of grid
		int V = model.getNoVertices();
		int noRows = model.getNoRows();
		int noCols = model.getNoCols();
		
		// if mouse just over a vertex, tell controller
		// so that specific action can be taken.
		vertexUnderMouse = closestVertexToMouse();
		if ( vertexUnderMouse != -1 )
			controller.setVertexUnderMouse( vertexUnderMouse );

		// draw the vertices and edges 
		fill( 0, 0, 0 ); // make vertices black
		strokeWeight( 2 );	 // make line width 2 pixels
		
		for ( int v = 0; v < V; v++ ) {
			// get coordinates of Vertex v
			int[] xy = getXY( v, noRows, noCols );
			
			// draw black disk for Vertex v
			ellipse( xy[0], xy[1], 4, 4 );
			
			// get adjacency list for each vertex and display edges
			for ( int u:  model.adjList( v ) ) { 
				int[] xy2 = getXY( u, noRows, noCols );
				line( xy[0], xy[1], xy2[0], xy2[1] );
			}				
		}
	}

	/**
	 * displays the vertex number next to the circle representing it.
	 * This is typically done whenever the mouse pointer is over a vertex.
	 * @param vertexNo
	 */
	public void displayVertexName(int vertexNo ) {
		// get position of vertex
		int[] xy = getXY( vertexNo, model.getNoRows(), model.getNoCols() );
		// set text color to red 
		fill( 255, 0, 0 );  
		// display text
		text( " "+vertexNo, xy[0], xy[1] );
	}
	
	public void displayAdjacencyList( int vertexNo ) {
		String s = "";
		for ( int u:  model.adjList( vertexNo ) ) 
			s += u + ", ";
		// remove last ", " part of the string
		s = s.trim();
		if ( s.length() > 0 )
			s = s.substring( 0, s.length()-1 );
		text( s, 100, HEIGHT-25 );
	}
	
}


Model


import java.util.ArrayList;
import java.util.Random;

import processing.core.PApplet;

/**
 * Model component of Model-View-Controller system.
 * Implements the graph, holds the vertices (numbered 0 to maxNoVertices)
 * and keeps the edges in a boolean adjacency matrix, that is 
 * maxNoVertices x maxNoVertices;
 * @author D. Thiebaut
 *
 */
public class MVC1_model {
	private final int maxNoVerticesRows = 40;       // geometry
	private final int maxNoVerticesCols = 30;       // 50 x 50 grid = 2500 vertices total
	private int maxNoVertices     		= maxNoVerticesRows * maxNoVerticesCols;
	private boolean[][] adjMat    		= null;     // adjacency matrix
	private int probability100    		= 35;       // probability (as percentage) for an edge (35 means 35%)
	
	private MVC1_viewer viewer         = null;// reference to the viewer of the MVC system
	private MVC1_controller controller = null;// reference to the controller of the MVC system
	
	/**
	 * constructor. Nothing to do.  The building of the network is done
	 * by initGraph().
	 */
	public MVC1_model( ) {
	}

	/**
	 * mutator. Sets the reference to the controller.
	 * @param c
	 */
	public void setController(MVC1_controller c) {
		controller = c;		
	}

	/**
	 * mutator. Sets the reference to the viewer.
	 * @param v
	 */
	public void setViewer( MVC1_viewer v ) {
		viewer = v;		
	}

	/**
	 * accessor
	 * @return number of vertices in graph
	 */
	public int getNoVertices() {
		return maxNoVertices;
	}
	
	public int getNoCols() {
		return maxNoVerticesCols;
	}
	
	public int getNoRows() {
		return maxNoVerticesRows;
	}
	
	/**
	 * add an undirected edge between u and v.
	 * @param v
	 * @param u
	 */
	public void addEdge( int v, int u ) {
		adjMat[v][u] = true;
		adjMat[u][v] = true;
	}
	
	/**
	 * returns vertex at given row and col.  Assumes row & col will always be valid.
	 * @param row
	 * @param col
	 * @return the vertex at row, col
	 */
	private int vertexAtRowCol( int row, int col ) {
		return row + col * maxNoVerticesRows;
	}
	
	/**
	 * generate a random graph.  The seed of the random number generator
	 * is set to a fixed number, so that the graph generated is always the same.
	 */
	public void initGraph() {
		Random random = new Random();
		random.setSeed( 12345 );				// seed for random number generator
		adjMat = new boolean[maxNoVertices][maxNoVertices];
		for ( int r=1; r<maxNoVerticesRows; r++ ) {
			for ( int c=1; c<maxNoVerticesCols; c++ ) {
				int currentVertex = vertexAtRowCol(r, c);
				int upVertex 	  = vertexAtRowCol(r-1, c);
				int leftVertex 	  = vertexAtRowCol(r, c-1);				
				
				// should we create an up connection?
				if ( random.nextInt(100) <= probability100  ) 
					addEdge( currentVertex, upVertex );
				
				// should we create a left connection?
				if ( random.nextInt(100) <= probability100 ) 
					addEdge( currentVertex, leftVertex );
			}
		}
	}
	
	/**
	 * returns the adjacency list of Vertex v in an arrayList.
	 * @param v for which we want the adjacent vertices.
	 * @return an arrayList of vertices.  The vertices numbers are
	 * listed in increasing order.
	 */
	public ArrayList<Integer> adjList( int v ) {
		ArrayList<Integer> adj = new ArrayList<Integer>();
		for ( int u=0; u<maxNoVertices; u++ ) 
			if ( adjMat[v][u] )
				adj.add( u );
		return adj;
	}

}


Application


An application of the MVC shown above where Union-Find and Dijkstra's shortest path are added to the model can be seen on the video below.

Some explainations
  • When the graph is built, a Union-Find algorithm is applied, and an Id array is created to represent the Id of all the vertices in the network.
  • Whenever the mouse pointer is over a vertex, all the vertices with the same Id are shown with an orange disk around them. This way all the vertices in the same connected component are highlighted.
  • Whenever a vertex is clicked on, this vertex is shown in red, and the shortest path to the vertex currently under the mouse pointer, if one such path exists, is shown in pink. Clicking on a new vertex moves the red dot to a new vertex.
  • Every time a new vertex is clicked on, Dijkstra is applied to that vertex, and the shortest paths to all the vertices reachable from that vertex are computed.