package com.ibm.ulc.examples.pieExtension;

/*
 */
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.util.*;

/**
 * An example implementation of a piechart component.
 * The purpose of this widget is purely to demonstrate how a java bean
 * can be wrapped as a ULC component and installed as an extension
 * to the UI Engine. 
 *
 * @see UIPieChart
 * @see com.ibm.ulc.examples.Pie.ULCPieChart
 */

public class PieChart extends JPanel implements MouseMotionListener, MouseListener {
	private Vector fListeners = null;
	private Rectangle fPieBounds; // the allocated bounds for the pie
	private int fPieHeight = -1; // desired height
	private int fPieWidth = -1; // desired height
	private int fPieStack = -1; // desired stack height
	private int fMaxIterations = 10; // maximum stack depth

	private Point fBorder = new Point(20, 20);
	private int fGap = 0;
	private int fDataColumns;
	private double fDataValues[];
	private double fMaximumDataValue = 0;
	private double fDataSum = 0;
	private double fAngles[];
	private Color fColors[];
	private String fLabels[];
	private Image fOffscreen = null; // image used for buffered drawing
	private Dimension fOffscreenSize;
	private int fMinRadius = 6; // the minimal radius of a segment

/**
 * Constructs the Pie and initializes it with a context.
 * @param conext the context where pie fetches its parameters from
 */
public PieChart(int width, int height) {
	setLayout(new BorderLayout());
	fPieStack = 10;
	setPreferredSize(new Dimension(width + fPieStack, height + fPieStack));
	fListeners = new Vector();
	fPieBounds = new Rectangle();
	fPieWidth = width;
	fPieHeight = height;
	addMouseListener(this);
	addMouseMotionListener(this);
}
/**
 * 
 * @param listener java.awt.event.ActionListener
 */
public synchronized void addActionListener(ActionListener listener) {
	fListeners.addElement(listener);
}
private void calcAngles() {
	double startAngle = 0.0;
	for (int i = 0; i < fDataColumns; i++) {
		double dr = Math.floor(fDataValues[i] / fDataSum * 360.0 + 0.5);
		if (i == fDataColumns - 1)
			dr = Math.max(0.0, 360.0 - startAngle);
		fAngles[i] = dr;
		startAngle = startAngle + fAngles[i];
	}
	// enforce a minimal size of each segment, i.e., fMinRadius degrees
	double faked = 0;
	int nFaked = 0;
	for (int i = 0; i < fDataColumns; i++) {
		if (fAngles[i] < fMinRadius && fDataValues[i] != 0.0) {
			faked = faked + (fMinRadius - fAngles[i]);
			nFaked++;
			fAngles[i] = fMinRadius;
		}
	}
	// distribute the faked amount evenly among the other segments
	double segmentFake = faked / (fDataColumns - nFaked);
	for (int i = 0; i < fDataColumns; i++) {
		if (fAngles[i] > fMinRadius + segmentFake)
			fAngles[i] -= segmentFake;
	}
}
private void doPaintChart(Graphics g, Rectangle bounds, int iteration) {
	double startAngle = 0.0;
	Rectangle r;
	int yOffset = 1 * iteration;
	for (int i = 0; i < fDataColumns; i++) {
		r = bounds;
		Color c = (Color) (fColors[i]);
		if (iteration != 0)
			c = c.darker();
		g.setColor(c);
		g.fillArc(r.x, r.y + yOffset, r.width, r.height, (int) startAngle, (int) fAngles[i]);
		startAngle += fAngles[i];
	}
}
private int findSegment(Rectangle r, int x, int y) {
	if (r.contains(x, y)) {
		int cx = r.x + r.width / 2;
		int cy = r.y + r.height / 2;
		int x1 = x - cx;
		int y1 = y - cy;
		int r1 = (int) Math.sqrt(Math.abs(x1) * Math.abs(x1) + Math.abs(y1) * Math.abs(y1));
		if (r1 < r.width / 2) {
			double angle = Math.atan2(y1, x1);
			angle = 360.0 / (2 * Math.PI) * angle;
			if (angle < 0)
				angle = -angle;
			else
				angle = 360.0 - angle;
			double startAngle = 0;
			for (int i = 0; i < fDataColumns; i++) {
				if (startAngle + fAngles[i] > angle)
					return i;
				startAngle += fAngles[i];
			}
		}
	}
	return -1;
}
public String getToolTipText(int segment) {
	return fLabels[segment] + ":" + Double.toString((double) fDataValues[segment]);
}
private void initialize() {
	fMaximumDataValue = 0.0;
	fDataSum = 0;
	for (int i = 0; i < fDataColumns; i++) {
		if (fDataValues[i] > fMaximumDataValue) {
			fMaximumDataValue = fDataValues[i];
		}
		fDataSum += fDataValues[i];
	}
	calcAngles();
}
private void layout(Dimension d) {
	int dia = Math.min(d.width - 2 * fBorder.x, d.height - 2 * fBorder.y);
	if (fPieWidth == -1)
		fPieBounds.width = dia;
	else
		fPieBounds.width = fPieWidth;
	if (fPieHeight == -1)
		fPieBounds.height = fPieBounds.width / 10 * 4;
	else
		fPieBounds.height = fPieHeight;
	if (fPieStack == -1)
		fMaxIterations = fPieBounds.width * fPieBounds.height / 500;
	else
		fMaxIterations = fPieStack;
	fMaxIterations = Math.max(Math.min(fMaxIterations, 50), 10);
	fPieBounds.x = fGap;
	fPieBounds.y = (d.height - fPieBounds.height - fMaxIterations) / 2;
}
private Color mapColorName(String rs) {
	Color c = null;
	if (rs.equals("red")) {
		c = Color.red;
	} else
		if (rs.equals("green")) {
			c = Color.green;
		} else
			if (rs.equals("blue")) {
				c = Color.blue;
			} else
				if (rs.equals("pink")) {
					c = Color.pink;
				} else
					if (rs.equals("orange")) {
						c = Color.orange;
					} else
						if (rs.equals("magenta")) {
							c = Color.magenta;
						} else
							if (rs.equals("cyan")) {
								c = Color.cyan;
							} else
								if (rs.equals("white")) {
									c = Color.white;
								} else
									if (rs.equals("yellow")) {
										c = Color.yellow;
									} else
										if (rs.equals("gray")) {
											c = Color.gray;
										} else
											if (rs.equals("darkGray")) {
												c = Color.darkGray;
											}
	return c;
}
public void mouseClicked(MouseEvent me) {
	int seg = findSegment(fPieBounds, me.getX(), me.getY());
	if (seg != -1) {
		Enumeration e = fListeners.elements();
		while (e.hasMoreElements()) {
			ActionListener al = (ActionListener) e.nextElement();
			al.actionPerformed(new ActionEvent(this, 0, fLabels[seg]));
		}
	}
}
public void mouseDragged(MouseEvent me) {
	int seg = findSegment(fPieBounds, me.getX(), me.getY());
	if (seg != -1)
		setToolTipText(getToolTipText(seg));
}
public void mouseEntered(MouseEvent me) {
	int seg = findSegment(fPieBounds, me.getX(), me.getY());
	if (seg != -1)
		setToolTipText(getToolTipText(seg));
}
public void mouseExited(MouseEvent e) {
}
public void mouseMoved(MouseEvent me) {
	int seg = findSegment(fPieBounds, me.getX(), me.getY());
	if (seg != -1)
		setToolTipText(getToolTipText(seg));
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent me) {
}
public synchronized void paint(Graphics g) {
	Dimension d = getSize();
	g.setColor(getBackground());
	g.fillRect(0, 0, d.width, d.height);
	layout(d);
	paintChart(g, fPieBounds);
}
private void paintChart(Graphics g, Rectangle bounds) {
	for (int i = fMaxIterations; i >= 0; i--)
		doPaintChart(g, bounds, i);
}
/**
 * This method was created by a SmartGuide.
 * @param listener java.awt.event.ActionListener
 */
public synchronized void removeActionListener(ActionListener listener) {
	fListeners.removeElement(listener);
}
/**
 * @param nValues int
 * @param labels java.lang.String[]
 * @param values double[]
 * @param colors java.lang.String[]
 */
public void setData(String[] labels, double[] values, String[] colors) {
	fDataColumns = values.length;
	fDataValues = new double[fDataColumns];
	fAngles = new double[fDataColumns];
	fColors = new Color[fDataColumns];
	fLabels = new String[fDataColumns];
	System.arraycopy(labels, 0, fLabels, 0, fDataColumns);
	System.arraycopy(values, 0, fDataValues, 0, fDataColumns);
	System.arraycopy(labels, 0, fLabels, 0, fDataColumns);
	for (int i = 0; i < fDataColumns; i++) {
		fColors[i] = mapColorName(colors[i]);
		if (fColors[i] == null) { // interpret the color as an RGB value
			if (fColors[i] == null) {
				fColors[i] = Color.gray;
			}
		}
	}
	initialize();
	repaint();
}
public synchronized void update(Graphics g) {
	Dimension d = getSize();
	Graphics offgraphics;
	if ((fOffscreen == null) || (d.width != fOffscreenSize.width) || (d.height != fOffscreenSize.height)) {
		layout(d);
		fOffscreen = createImage(d.width, d.height);
		fOffscreenSize = d;
	}
	offgraphics = fOffscreen.getGraphics();
	offgraphics.setFont(getFont());
	offgraphics.setColor(getBackground());
	offgraphics.fillRect(0, 0, d.width, d.height);
	paintChart(offgraphics, fPieBounds);
	g.drawImage(fOffscreen, 0, 0, null);
}
}
