Difference between revisions of "Tutorial: A Model-View-Controller in Processing"
(→Application) |
(→Model) |
||
(4 intermediate revisions by the same user not shown) | |||
Line 17: | Line 17: | ||
::# importing the core.jar file found in a new installed Processing 2 application on your system (Windows or Mac) into your Eclipse project. | ::# importing the core.jar file found in a new installed Processing 2 application on your system (Windows or Mac) into your Eclipse project. | ||
::# Control/Right clicking on core.jar in Eclipse, selecting Build Path, then Add to Build Path. | ::# Control/Right clicking on core.jar in Eclipse, selecting Build Path, then Add to Build Path. | ||
− | * You can find more detailed instructions [ | + | * You can find more detailed instructions [https://processing.org/tutorials/eclipse/ here]. |
<br /> | <br /> | ||
+ | <tanbox> | ||
+ | A programming laboratory based on this tutorial can be found [[CSC212_Lab_15_2014|here]]. | ||
+ | </tanbox> | ||
+ | <br /> | ||
+ | |||
=Running the Applet= | =Running the Applet= | ||
<br /> | <br /> | ||
Line 318: | Line 323: | ||
*/ | */ | ||
public class MVC1_model { | public class MVC1_model { | ||
− | private final int maxNoVerticesRows = | + | private final int maxNoVerticesRows = 50; // geometry |
− | private final int maxNoVerticesCols = | + | private final int maxNoVerticesCols = 50; // 50 x 50 grid = 2500 vertices total |
private int maxNoVertices = maxNoVerticesRows * maxNoVerticesCols; | private int maxNoVertices = maxNoVerticesRows * maxNoVerticesCols; | ||
private boolean[][] adjMat = null; // adjacency matrix | private boolean[][] adjMat = null; // adjacency matrix | ||
Line 446: | Line 451: | ||
<br /><br /><br /><br /><br /><br /><br /> | <br /><br /><br /><br /><br /><br /><br /> | ||
<br /><br /><br /><br /><br /><br /><br /> | <br /><br /><br /><br /><br /><br /><br /> | ||
− | [[Category:Java]][[Category:Tutorials]][[Category:Processing]] | + | [[Category:Java]][[Category:Tutorials]][[Category:Processing]][[Category:MVC]] |
Latest revision as of 11:29, 2 December 2014
--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).
Contents
Requirements
- You need to setup Eclipse to run Processing apps. This is simply accomplished by
- importing the core.jar file found in a new installed Processing 2 application on your system (Windows or Mac) into your Eclipse project.
- Control/Right clicking on core.jar in Eclipse, selecting Build Path, then Add to Build Path.
- You can find more detailed instructions here.
A programming laboratory based on this tutorial can be found here.
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.
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 = 50; // geometry
private final int maxNoVerticesCols = 50; // 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.