
// Venn.java version 1.1 4/9/96 John Malpas.
import java.util.*;
import java.awt.*;
import java.awt.image.*;
import java.applet.Applet;

class VNode {
	double x, y;

	int xDir, yDir;
	double yF, WHaspect;
	int w, h, LW;
	int xMod, yMod, yMF;

	String label;
	int clrI;
	Font fnt;
	Image memImage;
	int pix[];
	int CX, CY;
 boolean
Xinside(int x, int y)
{
	double xA, yA, xref;
	xA = x;
	yA = y;
	xref = CX * Math.sqrt(1.0 - ((yA*yA)/(CY*CY)));
	return (xA < xref);
}
 boolean
Yinside(int x, int y)
{
	double xA, yA, yref;
	xA = x;
	yA = y;
	yref = CY * Math.sqrt(1.0 - ((xA*xA)/(CX*CX)));
	return (yA < yref);
}

 void
init(double fuzzF, Panel panel) {
	int maxSpan = w * h;
	int i, Y;

	if (memImage == null) {
		WHaspect = (w*1.0)/h;
		pix = new int[w * h];
		memImage = panel.createImage(
		 new MemoryImageSource(w, h, pix, 0, w) );
		CX = w/2;
		CY = h/2;
//System.out.println(w +" mi width : " +memImage.getWidth(null));
	}

	for (i = 0; i < maxSpan; i++) pix[i] = 0;
	
	int xmt, ymt;
	xmt = xMod/2;
	double sum, rnd;
	double yA, xA;
	double xref, yref;
	double fuzzM = 1.0/(1.0 - fuzzF);

	for (Y = 0; Y < h; Y++) {
		yA = Math.abs(Y - CY);
/*
		if (fuzzF >= 2.0) xref = CX;
		else
*/
		 xref = CX * Math.sqrt(1.0 - ((yA*yA)/(CY*CY)));
		ymt = (Y%xMod) * yMF;
		for (i = 1; i < w; i++) {
		  if ((i+ymt)%xMod == xmt && Y%yMod == 0) {
			xA = Math.abs(i - CX);
			if (xA > xref) continue;

			rnd = Math.random();
			if (xA > xref * fuzzF) {
				sum = (xref - xA)/xref;
				sum *= fuzzM;
//if (xA > 1.0) System.out.println("xA: " +xA);
				if (rnd > sum) continue;
			}
/**
			if (fuzzF >= 2.0) yref = CY;
			else
**/
			 yref = CY * Math.sqrt(1.0 - ((xA*xA)/(CX*CX)));
			if (yA > yref * fuzzF) {
				sum = (yref - yA)/yref;
				sum *= fuzzM;
				if (rnd > sum) continue;
			}
			pix[(Y*w) + i] = clrI;
		  }
		}
	}
}
}

class Vpanel extends Panel implements Runnable {
	Font Bfnt, Tfnt;
	Venn graph;
	int nnodes;
	VNode nodes[] = new VNode[100];
	int beginY = 40;
	int beginX = 10;
	String title = "";
	double Fuzz = 1.0;
	VNode pick;

	Thread relaxer;
	boolean freezeON;

 // constructor
Vpanel(Venn graph) {
	this.graph = graph;
}

 int
addNode(String pv) {
	int i;
	VNode n0, n = new VNode();
	String str;
	n.fnt = Bfnt;

	n0 = nodes[0];
	switch(nnodes) {
	case 2:
		n.x = beginX + 15;
		n.y = n0.h * 0.6;
		break;
	case 3:
		n.x = n0.w * 0.6;
		n.y = beginY + 20;
		break;
	default:
		n.x = beginX + (nnodes*35) + 90*Math.random();
		n.y = beginY + 80*Math.random() - (nnodes*15);
		n.xDir = nnodes%3;
		n.yDir = nnodes%2;
	}

	n.yF = 0.25 + (2.0*Math.random());

	StringTokenizer t = new StringTokenizer(pv, "|");

	for (i = 0; t.hasMoreTokens() ; i++ ) {
		str = t.nextToken();
		switch(i) {
		case 0:
			n.xMod = Integer.valueOf(str).intValue();
			break;
		case 1:
			// eg. 2, 1, 9, -1 ...
			n.yMF = Integer.valueOf(str).intValue();
			break;
		case 2:
			n.yMod = Integer.valueOf(str).intValue();
			break;
		case 3:
			n.clrI = Integer.parseInt(str, 16) |
				0xff000000;
			break;
		case 4:
			n.w = Integer.valueOf(str).intValue();
			break;
		case 5:
			n.h = Integer.valueOf(str).intValue();
			break;
		case 6:
			n.label = str;
			break;
		}
	}
	n.init(Fuzz, this);
	n.memImage.flush();

	nodes[nnodes] = n;
	return nnodes++;
}

	int sleepTime = 100;
 public void
run() {
	while (true) {
	    if (!freezeON) relax();
	    repaint();
	    try {
		Thread.sleep(sleepTime);
	    } catch (InterruptedException e) {
		break;
	    }
	}
}
	int periodSet = 150;
	int upCount;

// new x,y for each node; repaint

 synchronized void
relax() {
	VNode n, n0 = nodes[0];
	double xR, yR;
	int ncx, ncy, ncW, ncH;
	boolean xCH, yCH;

	if (upCount%20 == 0) {
		n0.x += (Math.random() * 1.5) - 0.75;
	}
	if (upCount%30 == 0) {
		n0.y += (Math.random() * 1.5) - 0.75;
	}
	int slice = upCount % 4;
 for (int i = 1 ; i < nnodes ; i++) {
	n = nodes[i];
	xCH = yCH = false;

	ncx = (int) (n.x - n0.x);
	ncy = (int) (n.y - n0.y);
	ncW = ncx + n.w;
	ncH = ncy + n.h;

	ncx = (int) Math.abs(ncx - n0.CX);
	ncy = (int) Math.abs(ncy - n0.CY);
	ncW = (int) Math.abs(ncW - n0.CX);
	ncH = (int) Math.abs(ncH - n0.CY);
	if (Fuzz < 1.0) {
		double MX = 16.0;
		double MY = 7.0;
		ncx -= (int) (MX*n.WHaspect);
		ncy -= (int) (MY*n.WHaspect);
		ncW -= (int) (MX*n.WHaspect);
		ncH -= (int) (MY*n.WHaspect);
	}
	if (slice<2 && (!n0.Xinside(ncx, ncy) || !n0.Xinside(ncx, ncH))) {
		n.xDir = 1;
		xCH = true;
	}
	else if (slice>=2 && (!n0.Xinside(ncW, ncy) || !n0.Xinside(ncW, ncH))) {
		n.xDir = 0;
		xCH = true;
	}


	if (slice<2 && (!n0.Yinside(ncx, ncy) || !n0.Yinside(ncW, ncy))) {
		n.yDir = 1;
		yCH = true;
	}
	else if (slice>=2 && (!n0.Yinside(ncW, ncH) || !n0.Yinside(ncx, ncH))) {
		n.yDir = 0;
		yCH = true;
	}
	if (xCH) n.yF += (0.2 * Math.random()) - 0.12;
	if (yCH) {
		n.yF += Math.random() * 1.25;
		if (n.yF < 0.25) n.yF = 0.25;
		else if (n.yF > 2.25) n.yF -= 2.0;
//System.out.println(n.label +" yF: " + n.yF);
	}
	xR = 1 + (Math.random() * 2);
	yR = xR * n.yF;
	if (n.xDir == 1)	n.x += xR;
	else			n.x -= xR;

	if (n.yDir == 1)	n.y += yR;
	else			n.y -= yR;
 }
}

    Image offImg;
    Dimension offD;
    Graphics offG;
    
 public void
paintNode(Graphics g, VNode n) {
	int x = (int)n.x;
	int y = (int)n.y;

// draw the last values:
	if (Fuzz > 1.0 && Fuzz < 2.0) {
		g.setColor(Color.gray);
		g.drawOval(x, y, n.w, n.h);
	}
/*
	else
	g.fillOval(x, y, n.w, n.h);
	if (n == nodes[0])
*/
	g.drawImage(n.memImage, x, y, this);
}
	int LH; // label height
	int titleX;

 public synchronized void
getOffScreen(Dimension d) {
	int i, fontHeight;
	offImg = createImage(d.width, d.height);
	offD = d;
	offG = offImg.getGraphics();
	offG.setFont(Bfnt /*getFont()*/ );

	FontMetrics fm = offG.getFontMetrics();
	LH = fm.getHeight() + 4;
	for (i = 0 ; i < nnodes ; i++) {
		if (nodes[i].label != null)
		 nodes[i].LW = fm.stringWidth(nodes[i].label) + 1;
	}
	int titleLW = 0;
	if (title != null) titleLW = fm.stringWidth(title) + 1;
	titleX = (int) (beginX + ((nodes[0].w - titleLW)/2));
}

 public synchronized void 
update(Graphics g) {
	Dimension d = size();
	if ((offImg == null) ||
	 (d.width != offD.width) || (d.height != offD.height)) {
		getOffScreen(d);
	}
	upCount++;

// blank out the backgroud:
	offG.setColor(getBackground());
	offG.fillRect(0, 0, d.width, d.height);

	VNode n = nodes[0];

	if (title != null) {
		offG.setColor(Color.gray);
//System.out.println("title: " + title);
		offG.setFont(Tfnt);
		offG.drawString(title, titleX, beginY/2 );
	}

	int i;
	for (i = 0 ; i < nnodes ; i++) {
		n = nodes[i];
		paintNode(offG, n);
		//if (i > 0) show_min_max(n);
	}
	offG.setColor(Color.black);
	int Y;
	for (i = 0 ; i < nnodes ; i++) {
		n = nodes[i];
		offG.setFont(n.fnt);
		if (i == 0)	Y = (int) (n.y + (n.h/5));
		else		Y = (int) (n.y + (n.h/2));

		if (n.label != null)
		 offG.drawString(n.label,
	 		(int) (n.x + (n.w - n.LW)/2), Y);
	}
	g.drawImage(offImg, 0, 0, null);
}

	int pxOff, pyOff;

 public synchronized boolean
mouseDown(Event evt, int x, int y) {
	int i, dist, closest = 10000;
	double xD, yD;
	VNode n;
	pick = null;
	for (i = 1;  i < nnodes; i++) {
		n = nodes[i];
		if (	x > n.x && x < (n.x+n.w) &&
			y > n.y && y < (n.y+n.h) )
		{
			xD =	Math.abs(x - (n.x+n.CX));
			yD =	Math.abs(y - (n.y+n.CY));
			dist = (int)Math.sqrt( (xD*xD) + (yD*yD) );
			if (dist < closest) {
				closest = dist;
				pick = n;
			}
		}
		
	}
	if (pick == null) {
		freezeON = !freezeON;
	}
	else {
		pxOff = (int)(x - pick.x);
		pyOff = (int)(y - pick.y);
	}
	return true;
}

 public synchronized boolean
mouseDrag(Event evt, int x, int y) {
	if (pick != null) {
		//pick.x = x - pick.CX;
		pick.x = x - pxOff;
		pick.y = y - pyOff;
		repaint();
	}
	return true;
}

 public synchronized boolean
mouseUp(Event evt, int x, int y) {
	pick = null;
	repaint();
	return true;
}

 public void
start() {
	if (relaxer == null) relaxer = new Thread(this);
	relaxer.start();
}
 public void
stop() {
	if (relaxer != null) relaxer.stop();
}
}

public class Venn extends Applet {
    Vpanel panel;

 public void
init() {
	setLayout(new BorderLayout());

	panel = new Vpanel(this);
	add("Center", panel);
	Panel p = new Panel();
	add("South", p);

	String pv;
	if ((pv = getParameter("period")) != null) {
		panel.sleepTime =
		panel.periodSet = Integer.valueOf(pv).intValue();
	}
	if ((pv = getParameter("beginY")) != null) {
		panel.beginY = Integer.valueOf(pv).intValue();
	}
	if ((pv = getParameter("beginX")) != null) {
		panel.beginX = Integer.valueOf(pv).intValue();
	}
	panel.title = getParameter("title");
	if ((pv = getParameter("fuzz")) != null) {
		panel.Fuzz = Double.valueOf(pv).doubleValue();
	}

	panel.Bfnt = new Font("Helvetica", Font.PLAIN, 16);
	panel.Tfnt = new Font("Helvetica", Font.BOLD, 16);

	pv = getParameter("nodes");
	StringTokenizer t = new StringTokenizer(pv, ",");
	while (t.hasMoreTokens()) {
	    panel.addNode(t.nextToken());
	}
	VNode n0 = panel.nodes[0];
	n0.y = panel.beginY;
	n0.x = panel.beginX;
	n0.fnt = panel.Tfnt;
}

 public void
start() {
	panel.start();
}
 public void
stop() {
	panel.stop();
}
 public boolean
action(Event evt, Object arg) {
	return false;
}
}
