Custom Graph UI in Java


----------------
Written by:
Stephen Andersen
Group 8
Winter 2016
----------------

Introduction

This tutorial or 'app note' will demonstrate how to create
a custom User Interface with a graph that will update based
on an updated file.


This tutorial also expects you to understand basic Java.
This tutorial was written in Java 1.7.

JFrame Basics


Make a new JFrame:


			JFrame frame = new JFrame("Example Title");

/*
* Add any class that implements JComponents Here
* frame.add(new JComponent());
*/

JTabbedPane tabs = new JTabbedPane();

/*
* Add any class that implements JComponents Here
* tabs.add(new JComponent(), "Tab Name");
* tabs.add(new Graph(), "Graph Name");
*/
//I add the JTabbedPane
frame.setContentPane(pane);

// I set a method "endTasks()" to be activated if the JFrame is closed
frame.addWindowListener(new WindowAdapter(){public void windowClosing(WindowEvent e){endTasks();}});

// Compress and consolidate all JComponents (They won't be visible otherwise)
frame.pack();
// Set the Current Size of the frame (X_SIZE and Y_SIZE must be integers)
frame.setSize(X_SIZE, Y_SIZE);
// Should be obvious
frame.setVisible(true);
// Sets the frame to the center of the screen
frame.setLocationRelativeTo(null);


Graph Basics


To make my graph class:

The arguments are entered in this order:
Graph Title
Y Label
WIDTH
HEIGHT
ArrayList of Points

			final Graph graph = new Graph("Average Score", "Average Score", X_SIZE, (Y_SIZE/2)-28, 	points);


The final modifier is required because it is used in the ActionListener anonymous inner class.

Timer(Swing) Basics

To make a Timer


			int periodBetweenTimerCalls = 1000;//milliseconds

ActionListener a = null; // If you don't already have an ActionListener, give it null

Timer t = new Timer(periodBetweenTimerCalls, a);

// You can add more than one ActionListeners
t.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
//Do Stuff Here Every 1000 milliseconds
}
});

//Start Timer
t.start();

//Stop Timer, as used in endTasks()
t.stop();

//One-liner for making and starting
new Timer(1000, a).start();
//Not recommended due to loss of Timer (you can't stop)


Making an ActionListener:


			/* ActionListener is an abstract class and therefore must be either
* anonymous or implemented by a different class.
*/
// This is an anonymous inner class
ActionListener a = new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
//Do Stuff Here Every Time The Timer Running This Calls
}
};


Examples

The following EG.java code reads one file and outputs one graph.

fileToRead = "ave_fitness_graph";

Can be changed to a file name you prefer.

In the "ave_fitness_graph" file, your input should be placed in the format:
			x1 y1
x2 y2
x3 y3

This will create a linear graph.

Example Code

EG.java

			import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class EG {
private ArrayList<Point> points;
private Double X_SIZE = new Double(1100), Y_SIZE = new Double(600);

private Timer t;
private ActionListener graph_t;

public static void main(final String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new EG();
}
});
}

String filePath, fileToRead = "ave_fitness_graph";
Integer iterations = 0;
EG() {
initFiles();

final JFrame frame = new JFrame("Evolution of Music");

// Make several different types of Graphs
final Graph graph = new Graph("Average Score", "Average Score", X_SIZE, (Y_SIZE/2)-28, points);

JTabbedPane tabs = new JTabbedPane();
tabs.add(graph, graph.getTitle());
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(2, 2));

panel.add(tabs);
frame.setContentPane(panel);
frame.addWindowListener(new WindowAdapter(){public void windowClosing(WindowEvent e){endTasks();}});

frame.pack();
frame.setSize(X_SIZE.intValue(), Y_SIZE.intValue());
frame.setVisible(true);
frame.setLocationRelativeTo(null);

graph_t = new ActionListener(){
ArrayList<Point> points;
@Override
public void actionPerformed(ActionEvent arg0) {
try {
ArrayList<Point> fitness = readFitness();
if (points == null || fitness.size() != points.size()) {
graph.update(fitness);
points = fitness;

frame.repaint();
}
} catch (IOException e) {
System.err.println("LONG LIVE FITNESS...\nExcept that it died...");
System.exit(0);
}
}
};

// Will update every 1000 milliseconds
t = new Timer(1000, graph_t);
t.start();
}

public String initFiles() {
String text = "";
try {
try {
filePath = this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
String arg[] = filePath.split("/");
filePath = "";
for (int i = 0;i < arg.length-1;i++)
filePath += arg[i] + "/";

} catch (URISyntaxException e) {
e.printStackTrace();
System.err.println("Couldn't get Class info/pathing");
System.exit(0);
}
// Make a new file if one doesn't exist
File file = new File(filePath + fileToRead);

if (!file.createNewFile()) {
System.err.println("Failed to create file '" + fileToRead + "' \n\t-\tin " +
filePath + "' \n\t-\t" + "Maybe it already exists?");
}
//Empty File
new PrintWriter(filePath + fileToRead).close();
// Get Empty Graph
points = readFitness();
} catch (IOException e1) {
e1.printStackTrace();
System.err.println("Can't Read '"+fileToRead+"'");
System.exit(0);
}
return text;
}

public void endTasks() {
// Should end tasks
t.stop();
System.exit(0);
}

public ArrayList<Point> readFitness() throws IOException {
ArrayList<Point> points = new ArrayList();
FileInputStream stream = new FileInputStream(filePath+fileToRead);
InputStreamReader inputStream = new InputStreamReader(stream);
BufferedReader reader = new BufferedReader(inputStream);

String columns[];
String line;

while (reader.ready() && !(line = reader.readLine()).equals("")) {
columns = line.split(" ");
if (columns[0].isEmpty()) break;//I expect "iteration num" in that order
points.add(
new Point(
Integer.parseInt(columns[0]),
Integer.parseInt(columns[1])));
}
reader.close();
iterations = points.size();
if (iterations == 0)
points.add(new Point(0, 0));
return points;
}
}

The Graph class is seen below.


Graph.java

	import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;

import javax.swing.JPanel;

public class Graph extends JPanel {
private String title, yTitle;
private Double X_SIZE, Y_SIZE;
private ArrayList<Point> points;
private Color barColour = null;
private Boolean isNegative = false;

Graph(String title, String yTitle, Double x, Double y, ArrayList<Point> points) {
super(null);
this.title = title;
this.yTitle = yTitle;
X_SIZE = x-50;
Y_SIZE = y-10;
this.points = points;
}

Graph(String title, String yTitle, Double x, Double y, ArrayList<Point> points, boolean negatives) {
super(null);
this.title = title;
this.yTitle = yTitle;
X_SIZE = x-50;
Y_SIZE = y-10;
this.points = points;

isNegative = negatives;
}

Graph(String title, String yTitle, Double x, Double y, ArrayList<Point> points, Color c) {
super(null);
this.title = title;
this.yTitle = yTitle;
X_SIZE = x-50;
Y_SIZE = y-10;
this.points = points;

barColour = c;
}

public void update(ArrayList<Point> points) {
this.points = points;
repaint();
}

public String getTitle() {
return title;
}

@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
String text;
Graphics2D g2D = (Graphics2D)g;

g.setColor(Color.WHITE);
g.fillRect(0, 0, X_SIZE.intValue()+40, Y_SIZE.intValue());

if (points.isEmpty()) return;
Double Y_TEMP = (isNegative) ? ((Y_SIZE - 10)/2):(Y_SIZE - 10);

int max = points.get(0).y;
for (Point p:points) if (Math.abs(p.y) > max) max = Math.abs(p.y);

Double xfraction = (points.get(points.size()-1).x == 0) ? 0:(X_SIZE - 40) / points.get(points.size()-1).x;
Double yfraction = (Y_TEMP - 40) / max;

g2D.setColor(Color.BLUE);
g2D.setStroke(new BasicStroke(0.01f));
double NUM_DIVISIONS = 5.0;
if (isNegative) {
for (double i = 0;i < NUM_DIVISIONS+1;i++){
g2D.drawString(new Double(max*(NUM_DIVISIONS-i)/NUM_DIVISIONS).intValue()+"",
13,
new Double(((Y_TEMP-40)*i)/NUM_DIVISIONS).intValue()+20);
g2D.drawLine(50,
new Double(((Y_TEMP-40)*i)/NUM_DIVISIONS).intValue()+15,
X_SIZE.intValue()+20,
new Double(((Y_TEMP-40)*i)/NUM_DIVISIONS).intValue()+15);
}
for (double i = 0;i < NUM_DIVISIONS+1;i++){
g2D.drawString(-new Double((max)-(max*(NUM_DIVISIONS-i)/NUM_DIVISIONS)).intValue()+"",
13,
new Double((Y_TEMP-40)+((Y_TEMP-40)*i)/NUM_DIVISIONS).intValue()+20);
g2D.drawLine(50,
new Double((Y_TEMP-40)+((Y_TEMP-40)*i)/NUM_DIVISIONS).intValue()+15,
X_SIZE.intValue()+20,
new Double((Y_TEMP-40)+((Y_TEMP-40)*i)/NUM_DIVISIONS).intValue()+15);
}
} else {
for (double i = 0;i < NUM_DIVISIONS+1;i++){
g2D.drawString(new Double(max*(NUM_DIVISIONS-i)/NUM_DIVISIONS).intValue()+"",
13,
new Double(((Y_TEMP-40)*i)/NUM_DIVISIONS).intValue()+20);
g2D.drawLine(50,
new Double(((Y_TEMP-40)*i)/NUM_DIVISIONS).intValue()+15,
X_SIZE.intValue()+20,
new Double(((Y_TEMP-40)*i)/NUM_DIVISIONS).intValue()+15);
}
}

g2D.setColor(Color.BLACK);

//X_AXIS
g2D.drawLine(50, new Double(Y_TEMP-40).intValue()+15,
new Double(X_SIZE-40).intValue()+50, new Double(Y_TEMP-40).intValue()+15);
//Y_AXIS
g2D.drawLine(50, 15,
50, new Double((isNegative) ? ((Y_SIZE-90)):(Y_SIZE-50)).intValue()+15);

text = title.toUpperCase()+" PER ITERATION";
g.drawString(text, (X_SIZE.intValue() - g.getFontMetrics().stringWidth(text))/2, 10);
text = "NUMBER OF ITERATIONS";
g.drawString(text, (X_SIZE.intValue() - g.getFontMetrics().stringWidth(text))/2, new Double(Y_SIZE-10).intValue());

AffineTransform original = g2D.getTransform();
g2D.rotate(-Math.PI/2);
text = yTitle.toUpperCase();
g2D.drawString(text, -new Double((((isNegative) ? (Y_SIZE-10):Y_TEMP) + g.getFontMetrics().stringWidth(text))/2).intValue()-5, 10);//X_SIZE.intValue()/2);
g2D.setTransform(original);

g2D.setStroke(new BasicStroke(2.0f));

if (barColour == null) {
g2D.setColor(Color.BLACK);
for (int i = 0;i < points.size()-1;i++)
//if (points.get(i+1).y - points.get(i).y)
g2D.drawLine(
new Double(points.get(i).x*xfraction).intValue()+50,
new Double((Y_TEMP-40) - points.get(i).y*yfraction).intValue()+15,
new Double(points.get(i+1).x*xfraction).intValue()+50,
new Double((Y_TEMP-40) - points.get(i+1).y*yfraction).intValue()+15);
} else {
g2D.setColor(barColour);
for (int i = 0;i < points.size()-1;i++)
g.fillRect(
new Double(points.get(i).x*xfraction).intValue()+50,
new Double((Y_TEMP-40) - points.get(i).y*yfraction).intValue()+15,
new Double(points.get(i+1).x*xfraction).intValue()-new Double(points.get(i).x*xfraction).intValue(),
new Double(points.get(i).y*yfraction).intValue());
g2D.setColor(Color.BLACK);
for (int i = 0;i < points.size()-1;i++){
// Horz Line
g.drawLine(
new Double(points.get(i).x*xfraction).intValue()+50,
new Double((Y_TEMP-40) - points.get(i).y*yfraction).intValue()+15,
new Double(points.get(i+1).x*xfraction).intValue()+50,
new Double((Y_TEMP-40) - points.get(i).y*yfraction).intValue()+15);
//Left
g.drawLine(
new Double(points.get(i).x*xfraction).intValue()+50,
new Double((Y_TEMP-40) - points.get(i).y*yfraction).intValue()+15,
new Double(points.get(i).x*xfraction).intValue()+50,
new Double(Y_TEMP-40).intValue()+15);
//Right
g.drawLine(
new Double(points.get(i+1).x*xfraction).intValue()+50,
new Double((Y_TEMP-40) - points.get(i).y*yfraction).intValue()+15,
new Double(points.get(i+1).x*xfraction).intValue()+50,
new Double(Y_TEMP-40).intValue()+15);
}
}

g.setColor(Color.BLACK);
g2D.setStroke(new BasicStroke(1.2f));
Point p;
int division = points.size()/10;
for (int i = 0;i < points.size();i++) {
p = points.get(i);
//X_AXIS
if (division == 0 || p.x % division == 0) {// || i == points.size()-1) {
g2D.drawString(p.x+"",
new Double(p.x*xfraction).intValue()+50-(g.getFontMetrics().stringWidth(p.x+"")/2),
new Double(Y_TEMP-40).intValue()+30);
g2D.drawLine(new Double(p.x*xfraction).intValue()+50, new Double(Y_TEMP-40).intValue()+11,
new Double(p.x*xfraction).intValue()+50, new Double(Y_TEMP-40).intValue()+19);
}
}

for (double i = 0;i < NUM_DIVISIONS+1;i++)
//Y_AXIS
g2D.drawLine(46, new Double(((Y_TEMP-40)*i)/NUM_DIVISIONS).intValue()+15,
54, new Double(((Y_TEMP-40)*i)/NUM_DIVISIONS).intValue()+15);
}
}