/**
* Applet to generate a graph of my 'as paid' and my inflation-corrected income
* @author Robert J Morton <robmorton@clara.net>
* @version 18 July 2000
* @copyright July 2000 Robert J Morton (all rights reserved) */
/* I am a fully paid-up Fellow of the Ancient and Noble Order of the Long Term Unemployed
so if you know any employer who will employ me as a Java developer please email me.
This applet require a specialised data files called income.dat located in the same directory
as income.class and infln.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 income_txtdat.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 graphs 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 income for each year
int w = 0; //year counter used on horizontal axis of graph
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 = 45; //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 Yb = 0; //possible upward zero bias
int n = 40; //off-set from start mark of year to start of its name eg 1999
int FYB = 6; //number of year from start of graph which is first one for which there is data
int H = 5; //number of horizontal pixels per year
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; //reference for off-screen image buffers
Graphics ga; //graphics context for the off-screen image
CheckboxGroup CBG, CBH; //checkbox group for the following
Checkbox CB[]; //array in which to put check box reference
graphbut IL[]; //array in which to put item listener references
int NB = 10; //total number of buttons (max is 10)
String decade[] = {"1960s", "1970s", "1980s", "1990s"}; //decade labels
String income[][] = { //income £ scale labels
{" £0k", "£10k", "£20k", "£30k", "£40k", "£50k"}, //<param name="scale" value="0" />
{" £0k", " £5k", "£10k", "£15k", "£20k", "£25k"}, //<param name="scale" value="1" />
{" £0k", " £2k", " £4k", " £6k", " £8k", "£10k"}, //<param name="scale" value="2" />
{" £0k", " £1k", " £2k", " £3k", " £4k", " £5k"}, //<param name="scale" value="3" />
{" £0k", " £½k", " £1k", "£1½k", " £2k", "£2½k"}, //<param name="scale" value="4" />
{" £0k", "£200", "£400", "£600", "£800", " £1k"}, //<param name="scale" value="5" />
{"£10k", " £0k", "£10k", "£20k", "£30k", "£40k"}, //<param name="scale" value="6" />
{"£30k", "£20k", "£10k", " £0k", "£10k", "£20k"}, //<param name="scale" value="7" />
{" £0", " £50", "£100", "£150", "£200", "£250"}, //<param name="scale" value="8" />
{"£10", " £0", "£10", "£20", "£30", "£40"}, //<param name="scale" value="9" />
{"0M", "1M", "2M", "3M", "4M", "5M"}, //<param name="scale" value="10" />
{"00M", "10M", "20M", "30M", "40M", "50M"} //<param name="scale" value="11" />
};
int SD[] = {25000, 12500, 5000, 2500, 1250, 500, 25000, 25000, 125, 25, 2500, 25000}; //scaling divisors
int YB[] = {0, 0, 0, 0, 0, 0, 40, 120, 0, 40, 0, 0}; //Y-bias for when Y-scale starts at less than zero
int sd; //scaling divisor in use
int sf = 0; //scaling factor
String FN; //name of current data file
boolean showac = false, showic = true; //switches to show actual and inflation-corrected traces
boolean ready = false; //set true when initialization thread completed (re Navigator lock-ups)
String DF[] = {
"file0", "file1", "file2", "file3",
"file4", "file5", "file6"
};
String BL[] = {
"label0", "label1", "label2", "label3",
"label4", "label5", "label6",
"show original amount only",
"show inflation-corrected",
"show both together"
};
int nf = 6; //number of dat files used by this instance of this applet
public void init() { //INITIALISE THE APPLET
TH = new Thread(this); //create a thread for downloading data from server
TH.start(); //start the loading thread
TH.suspend(); //suspend it until underlying applet initialization completes
bg1 = getBackground(); //get background colour of host document
setBackground(bg1); //set the applet's background colour
setLayout(null); //allow the control panels to be laid out manually
img1 = createImage(260, 250); //Create image in which to build the graph
ga = img1.getGraphics(); //capture its graphics context
ac = getAppletContext(); //get details of HTML document this applet is running in
nf = Integer.valueOf(getParameter("files")) //get number of dat files from HTML parameter
.intValue();
if(nf > 7) nf = 7; //not room to accommodate more than 7 graph buttons
sf = Integer.valueOf(getParameter("scale")) //get scale indicator parameter
.intValue();
sd = SD[sf]; //set up the appropriate scaling divisor
Yb = YB[sf];
boolean DB[] = {
false, false, false, false, //default button states
false, false, false,
false, true, false //show inflation-corrected trace by default
};
boolean BR[] = { //which buttons are required
false, false, false, false,
false, false, false,
true, true, true //all 3 buttons in lower group always required
};
if(sf > 9) { //if a non-money scale were specified
BR[7] = false; BR[8] = false; BR[9] = false; //don't create the 3 inflation radio buttons
showic = false; showac = true; //show only the main (non-inflation-corrected) trace
} else {
showic = true; showac = false; //show only the inflation-corrected trace by default
}
for(int i = 0; i < nf; i++) {
DF[i] = getParameter(DF[i]); //get name of data file for each trace
BL[i] = getParameter(BL[i]); //substitute the required button labels
BR[i] = true; //set up which buttons are required in upper group
}
try {
int df = Integer.valueOf(getParameter("default")) //specified default radio button
.intValue();
DB[df] = true; //set specified default radio button
FN = DF[df]; //set the default data file name
} catch(Exception e) { //or if parameter not present
DB[nf - 1] = true; //set last radio button as default
FN = DF[nf - 1]; //set the default data file name
}
CBG = new CheckboxGroup(); //create checkbox group for radio buttons
CBH = new CheckboxGroup(); //create checkbox group for radio buttons
CheckboxGroup G = CBG;
CB = new Checkbox[NB]; //array in which to put check box reference
IL = new graphbut[NB]; //array in which to put item listener references
int y = 15; //vertical position of first button in group
for(int i = 0; i < NB; i++) {
if(i > 6) { //if now beyond max of 7 upper group buttons
y = 30; //vertical position of first button in lower group
G = CBH; //add rest of buttons to lower group
}
if(BR[i]) { //if this button is required
CB[i] = new Checkbox(BL[i], G, DB[i]); //create next button
add(CB[i]); //add it to the applet panel
CB[i].setBounds(255, y + i * 20, 165, 15); //set its size and position
IL[i] = new graphbut(i, this); //create an event listener for it
CB[i].addItemListener(IL[i]); //add listener to event handler list
}
}
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; //thread latch = initialization thread finished
}
public void paint(Graphics g) { update(g); } //paint/repaint the graph
public void update(Graphics g) { //DISPLAY GRATICULE, SCALES AND TRACES
ga.setColor(bg1); ga.fillRect(0, 0, 260, 250); //clear the graph area
ga.setColor(bg2); ga.fillRect(X, Y-200, 200, 200); //clear the graph area
w = 0; //set to first year
for(int i = 0; i < decade.length; i++) { //for each year in current decade
ga.setColor(Color.black); //set pen (foreground) colour
ga.drawString(decade[i], X + 8 + w, Y + 20); //decade label
ga.drawLine(X + w, Y + 5, X + w, Y + 10); //decade boundary mark
ga.setColor(bg3);
ga.drawLine(X + w, Y, X + w, Y - 200); //draw vertical graticule lines
w += 10 * H; //jump across 10 years
}
ga.drawLine(X + w, Y, X + w, Y - 200); //draw final vertical graticule line
ga.setColor(Color.black);
ga.drawLine(X + w, Y + 5, X + w, Y + 10); //draw final year boundary mark
ga.drawLine(X, Y + 5, X + w, Y + 5); //draw horizontal axis
int z = 0; //vertical graticule increment
for(int i = 0; i < 6; i++) { //for each £10,000 graduation
if(sf == 6 && i < 1 || sf == 7 && i < 4 || sf == 9 && i < 1)
ga.setColor(Color.red); //show negative money as red
else
ga.setColor(Color.black);
ga.drawString(income[sf][i], X - n, Y + 5 - z); //vertical annotation
ga.setColor(Color.black);
ga.drawLine(X - 10, Y - z, X - 5, Y - z); //vertical annotation mark
ga.setColor(bg3);
ga.drawLine(X, Y - z, X + w, Y - z); //final horizontal annotation mark
z += 40; //accumulated number of £s (scaled)
}
ga.setColor(Color.black);
ga.drawLine(X - 5, Y, X - 5, Y - 200); //vertical axis
if(lp == 3) { //provided data download completed
try { //try for IOException
D.reset(); //reset byte array pointer to start of data
int py = 0, piy = 0; //y-values of previous real and inflated plots
boolean pr = false, pi = false; //logic latch for start of valid plots
int p = X + FYB * H; //horizontal pixel position of start of graph
w = 0; //initial year number
try { //try for EOFException
while(true) { //infinite loop broken only by try failure
double x = (double)D.readInt(); //read next input integer into a floating-point double
int r = (int)(x / sd); //real current value scaled to pixels
int y = Y - Yb - r; //convert to reverse vertical pixel position
int ph = p - H; //previous value of p
if(w > 0 && pr && x != 0 && showac) { //miss out first value because we are drawing from it
ga.setColor(Color.blue); //set trace colour to blue
ga.drawLine(ph - H, py, ph, py); //draw horizontal part of previous year's value
ga.drawLine(ph, py, ph, y); //draw vertical join to this year's value
ga.drawLine(ph, y, p, y); //draw horizontal part of this year's value
}
py = y; //note income for next pass
if(r != 0) pr = true; //logic latch for start of valid plots
int i = (int)(infln.getValue(x, w) / sd);//find amount in £2K and scale to pixels
int iy = Y - Yb - i; //convert to reverse vertical pixel position
if(w > 0 && pi && x != 0 && showic) { //skip first pass
ga.setColor(Color.cyan); //set trace colour to cyan
ga.drawLine(ph - H, piy, ph, piy); //draw horizontal part of previous year's value
ga.drawLine(ph, piy, ph, iy); //draw vertical join to this year's value
ga.drawLine(ph, iy, p, iy); //draw horizontal part of this year's value
}
piy = iy; //note inflation-corrected income for next pass
if(i != 0) pi = true; //logic latch for start of valid plots
w++; //increment the year number
p += H; //advance to next year's horizontal pixel position
}
} catch(EOFException f) {} //terminate loop when attempt is made to read past end of byte array
} catch(Exception e) {} //readByte() can throw an IOException or ArrayOutOfBoundsException
}
g.drawImage(img1, 0, 5, null); //screen the completed graph
showStatus(E); //to confirm that data was loaded from server OK
}
public void start() {
TH.resume(); //resume the loading thread if suspended
}
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() {
TH.suspend(); //freeze execution of thread while away from host HTML page
}
public void destroy() { //called after stop() before applet is removed from memory
if(TH != null) { //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) { //provided we are absolutely sure initialization is complete
try {
url = new URL(cb + FN + ".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 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 getData(int i) {
if(i < nf) {
FN = DF[i]; //get the name of the required data file
lp = 1; //initiate the loading sequence
LP = 0; //prime the loading sequence latch
}
}
void setActual() {showac = true; showic = false; repaint();}
void setInflated() {showac = false; showic = true; repaint();}
void setBoth() {showac = true; showic = true; repaint();}
}
class graphbut implements ItemListener { //LISTENS FOR EVENTS FROM THE YEAR SELECTOR CHECKBOXES
int id; //one of the above events
graphs ap; //the application that called: always the above applet!
public graphbut(int id, graphs ap) { //constructor for a new checkbox event
this.id = id; //set the id number pertaining to this instance of 'graphbut'
this.ap = ap; //set the reference to the instance of the applet
} //from which it came. (there will only be one instance anyway).
public void itemStateChanged(ItemEvent e) { //a checkbox event has occurred from checkbox 'id'
switch(id) {
case 0: ap.getData(0); break;
case 1: ap.getData(1); break;
case 2: ap.getData(2); break; //checkbox on whose instance of 'graphbut' this method was invoked.
case 3: ap.getData(3); break; //Execute the method in the above applet which deals with the
case 4: ap.getData(4); break;
case 5: ap.getData(5); break;
case 6: ap.getData(6); break;
case 7: ap.setActual(); break;
case 8: ap.setInflated(); break;
case 9: ap.setBoth(); 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 */
}