/**
* Current Account Balances: Graphical Display Applet
* @author Robert J Morton <robmorton@clara.net>
* @version 12 July 2000
* @copyright July 2000 Robert J Morton (all rights reserved) */
/* This applet requires specialised data files called savyyyy.dat
(where yyyy is the year number) located in the same directory
as savlim1.class and savlim2.class on the web server. It requires
no server-side executables or scripts. The data files are generated
from input text files by an off-line command-line java program
called savlim0.class. This applet conforms to API 1.1 */
import java.applet.*; //all the gubbins to make the applet work
import java.awt.*; //for graphics operations (GUI)
import java.awt.event.*; //for the new-fangled 1.1 event handling
import java.net.*; //for downloading data from the remote server
import java.io.*; //for stream handling for the above
public class savlim1 extends Applet implements Runnable {
AppletContext ac; //get details of HTML document this applet is running in
String cb; //code base URL - where this applet's class file came from
URL url; //url of current HTML file
InputStream I; //input stream for downloading data files
int L = 0; //length of the remote item being loaded
int l = 0; //number of bytes of the above successfully downloaded so far
byte B[]; //gigantic byte array to hold the downloaded data
DataInputStream D; //to get input data on the account balances for each day
int w = 0; //days counter used on horizontal axis of graph
int m = 0; //today's balance in integral pence
int year = 2001; //year for inflation calculation
Thread TH; //reference for a separate Internet Data Transfer thread
int lp = 1, LP = 0; //indicates current and previous download phases
String E; //for Exception during downloading + method where it occurred
int X = 25; //horizontal bias from left edge of graph image to start of x-axis
int Y = 205; //vertical bias from top edge of applet to start of y-axis
int n = 25; //off-set from start mark of year to start of its name eg 1999
int H = 0; //savings penalty thresold
boolean inflate = false; //show normal or inflated balances
String yearFile = "2001"; //name of year data file
boolean leap = true; //indicates a leap year
boolean ready = false; //to make sure init thread finished before starting data laod
Color bg1 = new Color(210, 210, 210); //main background colour
Color bg2 = new Color(208, 176, 176); //graph background colour ideally with a 24-bit system
Color bg3 = new Color(128, 128, 128); //graticule colour
Image img1, img2; //references for off-screen image buffers
Graphics ga, gb; //graphics context for the off-screen images
String month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; //weeks in year
String hits[] = {"0k", "1k", "2k", "3k", "4k", "5k"}; //balance £ scale
Checkbox //checkboxes for selecting year to display
CB00, //1991
CB01, //1992
CB02, //1993
CB03, //1994
CB04, //1995
CB05, //1996
CB06, //1997
CB07, //1998
CB08, //1999
CB09, //2000
CB10, //2001
CB11, //display actual balances
CB12; //display inflation-corrected balances
CheckboxGroup CBG, CBI; //checkbox group for the above
public void init() {
TH = new Thread(this); //create a thread for downloading data from server
TH.start(); //start the loading thread
try {
TH.suspend(); //suspend it until underlying applet initialization completes
} catch(SecurityException e) {} //fuck browser security
bg1 = getBackground(); //get background of host document
setBackground(bg1); //set the applet's background colour
setLayout(null); //allow the control panels to be laid out manually
ac = getAppletContext(); //get details of HTML document this applet is running in
CBG = new CheckboxGroup(); //create checkbox group for radio buttons
CB00 = new Checkbox("1991", CBG, false);
CB01 = new Checkbox("1992", CBG, false);
CB02 = new Checkbox("1993", CBG, false);
CB03 = new Checkbox("1994", CBG, false);
CB04 = new Checkbox("1995", CBG, false);
CB05 = new Checkbox("1996", CBG, false);
CB06 = new Checkbox("1997", CBG, false);
CB07 = new Checkbox("1998", CBG, false);
CB08 = new Checkbox("1999", CBG, false);
CB09 = new Checkbox("2000", CBG, false);
CB10 = new Checkbox("2001", CBG, true);
int k = 20, j = 35; //set the shape of the check box and its label
add(CB00); CB00.setBounds(410, j, 50, 15); j += k;
add(CB01); CB01.setBounds(410, j, 50, 15); j += k;
add(CB02); CB02.setBounds(410, j, 50, 15); j += k;
add(CB03); CB03.setBounds(410, j, 50, 15); j += k;
add(CB04); CB04.setBounds(410, j, 50, 15); j += k;
add(CB05); CB05.setBounds(410, j, 50, 15); j += k;
add(CB06); CB06.setBounds(410, j, 50, 15); j += k;
add(CB07); CB07.setBounds(410, j, 50, 15); j += k;
add(CB08); CB08.setBounds(410, j, 50, 15); j += k;
add(CB09); CB09.setBounds(410, j, 50, 15); j += k;
add(CB10); CB10.setBounds(410, j, 50, 15); j += k;
CBI = new CheckboxGroup(); //create checkbox group for radio buttons
CB11 = new Checkbox("Display actual balances.", CBI, true);
CB12 = new Checkbox("Display balances inflation-corrected to £Y2K.", CBI, false);
add(CB11); CB11.setBounds( 5, 265, 170, 15);
add(CB12); CB12.setBounds(180, 265, 270, 15);
//CREATE A LISTENER FOR EACH YEAR RADIO BUTTON
savlim2 y00 = new savlim2(savlim2.Y1991, this); CB00.addItemListener(y00);
savlim2 y01 = new savlim2(savlim2.Y1992, this); CB01.addItemListener(y01);
savlim2 y02 = new savlim2(savlim2.Y1993, this); CB02.addItemListener(y02);
savlim2 y03 = new savlim2(savlim2.Y1994, this); CB03.addItemListener(y03);
savlim2 y04 = new savlim2(savlim2.Y1995, this); CB04.addItemListener(y04);
savlim2 y05 = new savlim2(savlim2.Y1996, this); CB05.addItemListener(y05);
savlim2 y06 = new savlim2(savlim2.Y1997, this); CB06.addItemListener(y06);
savlim2 y07 = new savlim2(savlim2.Y1998, this); CB07.addItemListener(y07);
savlim2 y08 = new savlim2(savlim2.Y1999, this); CB08.addItemListener(y08);
savlim2 y09 = new savlim2(savlim2.Y2000, this); CB09.addItemListener(y09);
savlim2 y10 = new savlim2(savlim2.Y2001, this); CB10.addItemListener(y10);
//CREATE A LISTENER OF EACH INFATION BUTTON
savlim2 y11 = new savlim2(savlim2.no, this); CB11.addItemListener(y11);
savlim2 y12 = new savlim2(savlim2.yes, this); CB12.addItemListener(y12);
img1 = createImage(392, 230); //Create image in which to build the graph
ga = img1.getGraphics(); //capture its graphics context
img2 = createImage(392, 20); //Create image in which to build the average balance message
gb = img2.getGraphics(); //capture its graphics context
cb = getCodeBase().toString(); //URL (less file name) from where this applet came
if(!cb.endsWith("/")) { //workaround re Hotjava re Microsoft intranet machine names
int x = cb.lastIndexOf('/'); //where getCodeBase() wrongly seems to return the
if(x != -1) cb = cb.substring(0, x + 1); //document base (cira Nov 1999)
}
lp = 1; LP = 0; //indicates current and previous download phases
ready = true; //signal thread finished (for Navigator's benefit)
}
public void paint(Graphics g) { update(g); } //paint/repaint the graph
public void update(Graphics g) {
ga.setColor(bg1); ga.fillRect(0, 0, 392, 230); //clear the image buffer area
ga.setColor(bg2); ga.fillRect(X, Y-200, 366, 200); //clear the image buffer area
w = 0; //set to first day of the year
for(int i = 0; i < month.length; i++) { //for each month shown
ga.setColor(Color.black); //set pen (foreground) colour
ga.drawString(month[i], X + 6 + w, Y + 20); //month name
ga.drawLine(X + w, Y + 5, X + w, Y + 10); //month boundary mark
ga.setColor(bg3);
ga.drawLine(X + w, Y, X + w, Y - 200); //vertical graticule lines
w += days[i]; if(i == 1 && leap) w++; //accumulated number of days
}
ga.drawLine(X + w, Y, X + w, Y - 200); //final vertical graticule line
ga.setColor(Color.black);
ga.drawLine(X + w, Y + 5, X + w, Y + 10); //final year boundary mark
ga.drawLine(X, Y + 5, X + w, Y + 5); //horizontal axis
int z = 0; //vertical graticule increment
for(int i = 0; i < hits.length; i++) { //for each £1000 graduation
ga.setColor(Color.black);
ga.drawString(hits[i], X - n, Y + 5 - z); //vertical annotation
ga.drawLine(X - 10, Y - z, X - 5, Y - z); //vertical annotation mark
ga.setColor(bg3); //make all others light grey
ga.drawLine(X, Y - z, X + w, Y - z); //final horizontal annotation mark
z += 40; //accumulated number of £s (scaled)
}
H = 300000; //set penalty threshold
if(inflate) H = infln.getInt(H, year); //compute inflated penalty threshold
H /= 2500;
ga.setColor(Color.red); //set colour for penalty threshold
ga.drawLine(X, Y - H, X + w, Y - H); //draw savings penalty threshold
ga.setColor(Color.black);
ga.drawLine(X - 5, Y, X - 5, Y - 200); //vertical axis
if(lp == 3) { //provided appropriate data download completed
try { //try for IOException
D.reset(); //reset byte array pointer to start of data
w = X; //initial x-bias for day number
int prevy1 = 0, prevy2 = 0; //previous day's balance for each account
int a = 0, max = 0, min = 2000000; //prime total, maximum and minimum account balances (integral pence)
try { //try for EOFException
while(true) { //infinite loop broken only by try failure
m = 0; //today's balances
prevy1 = plot(prevy1, Color.blue); //plot my account only
prevy2 = plot(prevy2, Color.cyan); //plot mine + ruby's balance
a += H; //accumulate total pence
if(max < H) max = H; //trap maximum balance
if(min > H) min = H; //trap minimum balance
w++; //increment the day number
}
} catch(EOFException f) { //terminate loop when attempt is made to read past end of byte array
} finally {
gb.setColor(bg1); gb.fillRect(0,0,392,20); //clear the average balance panel
gb.setColor(Color.black); //display average balance for the year in black
gb.drawString(
"Average £" + money((int)((double)a / (double)(w - X))) +
" Max £" + money(max) +
" Min £" + money(min) +
" Swing £" + money(max - min),
0, 17);
}
} catch(IOException e) {} //readByte() can throw an IOException also
}
g.drawImage(img1, 5, 30, null); //screen the completed graph
g.drawImage(img2, 5, 0, null); //screen the completed average banalce message
g.drawString("YEAR", 420, 18);
showStatus(E);
}
private int plot(int prev, Color c) throws IOException {
int x = (int)D.readInt(); //read account's next day's balance from the input array
if(x == 0) return x; //don't plot zero balances
m += x;
if(inflate) //if inflation radio button checked
H = infln.getInt(m, year); //compute inflated balance
else //otherwise, normal radio button is checked
H = m; //so use normal balance
int h = H / 2500; //scale to pixels
if(h < 0) h = -1; //don't plot below the zero line
if(h > 200) h = 201; //don't plot above top of graph
int y = Y - h; //off-set it from Y-axis zero
if(w > X && (y != prev || (y == prev && h > -1 && h < 201))) { //skip for first value
ga.setColor(c); //set trace colour to blue
ga.drawLine(w - 1, prev, w, y); //join previous plot to this day's balance
}
return y; //return balance for next pass
}
String money(int x) { //MONEY DISPLAY FORMATTER (takes integral pence)
double X = (double)x; X /= 100; //form into decimal pounds
String s = "" + X; //form into a string
int c = '.'; //decimal point character
int i = s.lastIndexOf(c); //find position of decimal point in string
if(i == -1) s += ".00"; //if no decimal point found
else {
int e = s.length() - i; //how far decimal point is from end of string
if(e == 1) s += "00"; //add two zeros after decimal point
else if(e == 2) s += "0"; //add only one zero after decimal point
}
return s; //return the formatted string
}
public void start() {
try {
TH.resume(); //resume the loading thread if suspended
} catch(SecurityException e) {} //fuck browser security
}
public void run() { //MANAGE INTERNET DATA TRANSFERS ON A SEPARATE THREAD
while(TH.isAlive()) { //while this thread exists
switch(lp) { //THE 2 DOWNLOADING PHASES
case 1: fileConnect(); break; //connect to index resource on server
case 2: fileLoad(); break; //manage the downloading of its content
case 3:
if(lp != LP) { //if loading of data has finished
LP = lp; //latch the current load state
repaint(); //display the graph
}
}
try {
Thread.currentThread().sleep(250); //sleep to allow other things to take place
} catch (InterruptedException e) {} //catch interrupt from GUI or browser
}
}
public void stop() {
try {
TH.suspend(); //freeze execution of thread while away from host HTML page
} catch(SecurityException e) {} //fuck browser security
}
public void destroy() { //called after stop() before applet is removed from memory
if(TH.isAlive()) { //if the Internet data transfer thread object still exists
TH.stop(); //stop it running
TH = null; //and jettison its object into limbo
}
}
void fileConnect() { //CONNECT TO THE HITS DATA FILE ON THE SERVER
if(ready) { //if we're sure init thread finished (re Navigator lockups)
try {
url = new URL(cb + "sav" + yearFile + ".dat"); //form the url of the account balance data
URLConnection u = url.openConnection(); //open a connection to the remote file
L = u.getContentLength(); //length of the remote file (bytes)
l = 0; //number of bytes so far successfully downloaded
I = u.getInputStream(); //create an input stream object to access the file
B = new byte[L]; //create the gigantic buffer for the data
D = new DataInputStream(new ByteArrayInputStream(B));
lp = 2; //advance to the file loading phase
} catch(Exception e) {
lp = 0; //set unrecoverable error status
E = "fileConnect() " + e; //note the exception and where it occurred
}
}
}
void fileLoad() { //DOWNLOAD THE HITS DATA
int k; //current byte being read()
try { //if read() hasn't hit the current end of input stream
while(l < L && (k = I.read()) != -1) //and the entire file has not yet been downloaded
B[l++] = (byte)k; //add its new byte to the array big byte array
if(l >= L) { //if the whole of the index has now been downloaded
I.close(); //close the URL Connection's input stream
lp = 3; //indicate file loading completed successfully
E = "Graph data loaded.";
}
} catch(Exception e) {
lp = 0; //set error condition
E = "fileLoad() " + e + L + " " + l; //note the exception and where it occurred
}
}
void get1991() {yearFile = "1991"; lp = 1; LP = 0; leap = false; year = 1991;}
void get1992() {yearFile = "1992"; lp = 1; LP = 0; leap = true; year = 1992;}
void get1993() {yearFile = "1993"; lp = 1; LP = 0; leap = false; year = 1993;}
void get1994() {yearFile = "1994"; lp = 1; LP = 0; leap = false; year = 1994;}
void get1995() {yearFile = "1995"; lp = 1; LP = 0; leap = false; year = 1995;}
void get1996() {yearFile = "1996"; lp = 1; LP = 0; leap = true; year = 1996;}
void get1997() {yearFile = "1997"; lp = 1; LP = 0; leap = false; year = 1997;}
void get1998() {yearFile = "1998"; lp = 1; LP = 0; leap = false; year = 1998;}
void get1999() {yearFile = "1999"; lp = 1; LP = 0; leap = false; year = 1999;}
void get2000() {yearFile = "2000"; lp = 1; LP = 0; leap = true; year = 2000;}
void get2001() {yearFile = "2001"; lp = 1; LP = 0; leap = false; year = 2001;}
void normal() {inflate = false; repaint();}
void inflate() {inflate = true; repaint();}
}
class savlim2 implements ItemListener { //LISTENS FOR EVENTS FROM THE YEAR SELECTOR CHECKBOXES
static final int Y1991 = 0; //an event from the '1991' checkbox
static final int Y1992 = 1; //an event from the '1992' checkbox
static final int Y1993 = 2; //an event from the '1993' checkbox
static final int Y1994 = 3; //an event from the '1994' checkbox
static final int Y1995 = 4; //an event from the '1995' checkbox
static final int Y1996 = 5; //an event from the '1996' checkbox
static final int Y1997 = 6; //an event from the '1997' checkbox
static final int Y1998 = 7; //an event from the '1998' checkbox
static final int Y1999 = 8; //an event from the '1999' checkbox
static final int Y2000 = 9; //an event from the '2000' checkbox
static final int Y2001 =10; //an event from the '2001' checkbox
static final int no = 11; //an event from the 'no' checkbox
static final int yes = 12; //an event from the 'yes' checkbox
int id; //one of the above events
savlim1 ap; //the application that called: always the above applet!
public savlim2(int id, savlim1 ap) { //constructor for a new checkbox event
this.id = id; //set id number pertaining to this instance of 'savlim2'
this.ap = ap; //set reference to the instance of the applet from
} //which it came. (there'll only be one instance anyway).
public void itemStateChanged(ItemEvent e) { //entered when a checkbox event has occurred
switch(id) {
case Y1991: ap.get1991(); break; //Execute the method in the above
case Y1992: ap.get1992(); break; //applet which deals with the
case Y1993: ap.get1993(); break; //checkbox on whose instance of
case Y1994: ap.get1994(); break; //savlim2 this method was invoked.
case Y1995: ap.get1995(); break;
case Y1996: ap.get1996(); break;
case Y1997: ap.get1997(); break;
case Y1998: ap.get1998(); break;
case Y1999: ap.get1999(); break;
case Y2000: ap.get2000(); break;
case Y2001: ap.get2001(); break;
case no: ap.normal(); break;
case yes: ap.inflate(); break;
}
}
}
/* Robert J Morton, the author of this program,
is a poor but Right Honourable Member of the
Ancient and Noble Order of the Long-term Unemployed.
Offers of work please to: robmorton@clara.net */