//package STOPCL;

import java.lang.Math;

import java.awt.*;
import java.awt.event.*;

/** PieDial extends Component to draw a Pie Chart,
 * specified by arrays of colors and starting angles.
 * The last pie wedge ands at the start of the first,
 * so the entire circle is always filled.
 *
 * Java AWT (and geometry) measures angles in degrees,
 * zero degrees "East" and ninety degrees "North"
 * increasing counter-clockwise,
 * with sectors specified in counter-clockwise order,
 * although the first start can be any value.
 * We always use this ZERO_EAST_DEG_CCW system internally.
 *
 * Navigation and surveying measures angles in degrees,
 * zero degrees "North" and ninety degrees "East"
 * increasing clockwise,
 * with sectors specified in clockwise order,
 * although the first start can be any value.
 * If the caller specifies this ZERO_NORTH_DEG_CW system,
 * we convert their angles to the internal system,
 * and convert back when reporting.

 * @author Morris Hirsch
 * IPD Inc Newport RI
 * Oct 1 1998 */

public class PieDial extends Component
implements Mapping
{

    public static final int
      ZERO_EAST_DEG_CCW = 1,
      ZERO_NORTH_DEG_CW = 2;

    protected int measure = ZERO_EAST_DEG_CCW;
    protected Color [] colors = null;

/* Allow caller to use ints or floats,
 * but the awt arc method uses ints degrees */

    protected int [] starts = null;

    protected Color markColor = null;

    protected int markValue = 0;

    protected int xo = 0, yo = 0, side = 0;

    protected boolean active = false;

/** Construct a PieDial.
 * @param measure -- how angles are measured,
 * ZERO_EAST_DEG_CCW or ZERO_NORTH_DEG_CW.
 * @param colors -- colors to show for each wedge,
 * @param starts -- starting angle of each wedge,
 * order must be consistent with the chosen measuring system.
 * First starting angle also closes last wedge. */

    public PieDial (int measure, Color [] colors, int [] starts) {
        setPie (measure, colors, starts);
    }

    public PieDial (int measure, Color [] colors, float [] starts) {
        setPie (measure, colors, starts);
    }

/** Construct a PieDial.
 * @param colors -- colors to show for each wedge,
 * @param starts -- starting angle of each wedge,
 * order must be consistent with the default measuring system. 
 * First starting angle also closes last wedge. */

    public PieDial (Color [] colors, int [] starts) {
        this (ZERO_EAST_DEG_CCW, colors, starts);
    }

    public PieDial (Color [] colors, float [] starts) {
        this (ZERO_EAST_DEG_CCW, colors, starts);
    }

/** Construct a PieDial per defaults */

    public PieDial () {
        this ((Color [])null, (float [])null);
    }

    protected String [] pieLabels = null;

    protected String xyString = null;
    protected int stringX = 0, stringY = 0;

/** Set Pie segment labels,
 * to be accessed via public String XYToString (int x, int y) */

    public void setPieLabels (String [] pieLabels) {
	this.pieLabels = pieLabels;
    }

/** Find the segment containing (x, y) and return that label. */

    public String XYToString (int x, int y) {
	if ((null == starts) || (null == pieLabels))
	    return null;

	if (pieLabels.length != starts.length)
	    return null;

        int nn = starts.length;
        int degs = xy_to_degrees (x, y);

/* We always use ZERO_EAST_DEG_CCW system internally,
 * so reverse the test spans when ZERO_NORTH_DEG_CW */

	for (int ii = 0;
	     ii < starts.length;
	     ii++) {

            int nnafter = (ii +1) % nn;

/* Clockwise tests:
 * 0: (1, 0)
 * 1: (2, 1)
 * ...
 * n-1: (0, n-1)
 */

            if ((ZERO_NORTH_DEG_CW == measure)
	     && inside (degs, starts [nnafter], starts [ii]))
	        return pieLabels[ii];

/* Counter-Clockwise tests:
 * 0: (0, 1)
 * 1: (1, 2)
 * ...
 * n-1: (n-1, 0)
 */

            if ((ZERO_EAST_DEG_CCW == measure)
	     && inside (degs, starts [ii], starts [nnafter]))
	        return pieLabels[ii];
        }

        return null;
    }

/** Is a circular value between two others?
 * @param val -- value to test,
 * @param starting -- start of arc,
 * @param ending -- end of arc,
 */

    public static boolean inside (int val, int starting, int ending) {
	if (starting < ending)
	    return ((starting < val) && (val < ending));
	else
	    return ((starting < val) || (val < ending));
    }

/** Is a circular value between two others?
 * @param val -- value to test,
 * @param starting -- start of arc,
 * @param ending -- end of arc,
 */

    public static boolean inside (float val, float starting, float ending) {
	if (starting < ending)
	    return ((starting < val) && (val < ending));
	else
	    return ((starting < val) || (val < ending));
    }

/* Convert point to ZERO_EAST_DEG_CCW (internal system) radial.
 * Scale x and y according to actual width and height,
 * otherwise angles are wrong except at 0, 90, 270, 180.
 * May not be square even if square is requested.
 * Not a problem now that we center a proper circle,
 * but keep the fix as a comment. */

    public int xy_to_degrees (int x, int y) {

        Dimension d = getSize ();
      // double aspect = ((double)d.height) / ((double)d.width);
	double aspect = 1;

        double radians = Math.atan2
        ((double)(y - (d.height / 2)),
          aspect * (double)((d.width / 2) - x));

        return (int)(radians * 180 / Math.PI) + 180;
    }

/* First try for very close hit,
 * then accept further misses,
 * but finally give up. */

    protected int find_radial_target (int degs) {
        int target = -1;

	for (int delta = 1;
	((delta < 10)  && (target < 0));
	delta += 2)

	for (int ii = 0;
	    ((ii < starts.length) && (target < 0));
	    ii++)
	    if (near (degs, starts [ii], delta))
	        target = ii;

	return target;
    }

/** Are two circular values within some range of each other?
 * @param a -- one value,
 * @param b -- other value,
 * @param ww -- range for nearness. */

    public static boolean near (int a, int b, int ww) {

	if (a == b)
	    return true;

/* simpler with "smaller" and "larger" */

	int larger, smaller;

	if (a > b) {
	    larger = a;
	    smaller = b;
	}
	else {
	    larger = b;
	    smaller = a;
	}

/* Test "forward" direction. No modulo here! */

        if (smaller + ww > larger)
	    return true;

/* Test "backward" direction. Avoid modulo -- both go past it. */

	if ((larger + ww > 360)
	  && (larger + ww > smaller + 360))
	    return true;

	return false;
    }

    public static boolean near (float a, float b, float ww) {

	if (a == b)
	    return true;

/* simpler with "smaller" and "larger" */

	float larger, smaller;

	if (a > b) {
	    larger = a;
	    smaller = b;
	}
	else {
	    larger = b;
	    smaller = a;
	}

/* Test "forward" direction. No modulo here! */

        if (smaller + ww > larger)
	    return true;

/* Test "backward" direction. Avoid modulo -- both go past it. */

	if ((larger + ww > 360)
	  && (larger + ww > smaller + 360))
	    return true;

	return false;
    }

/** Update a PieDial.
 * @param measure -- how angles are measured,
 * ZERO_EAST_DEG_CCW or ZERO_NORTH_DEG_CW.
 * @param colors -- colors to show for each wedge,
 * @param starts -- starting angle of each wedge,
 * order consistent with the chosen measuring system.  */

    public void setPie (int measure, Color [] colors, int [] starts) {
        this.measure = measure;
        this.colors = colors;
        this.starts = starts;

        if (ZERO_NORTH_DEG_CW == measure)
            this.starts = north_to_east (this.starts);

	repaint (100);
    }

/* Convert from calling floats to internal ints degrees */

/** Update a PieDial.
 * @param measure -- how angles are measured,
 * ZERO_EAST_DEG_CCW or ZERO_NORTH_DEG_CW.
 * @param colors -- colors to show for each wedge,
 * @param starts -- starting angle of each wedge,
 * order consistent with the chosen measuring system.  */

    public void setPie (int measure, Color [] colors, float [] starts) {
        this.measure = measure;
        this.colors = colors;
        this.starts = floats_to_ints (starts);

        if (ZERO_NORTH_DEG_CW == measure)
            this.starts = north_to_east (this.starts);

	repaint (100);
    }

/** Update a PieDial.
 * @param colors -- colors to show for each wedge,
 * @param starts -- starting angle of each wedge,
 * order consistent with the current measuring system.  */

    public void setPie (Color [] colors, int [] starts) {
        setPie (this.measure, colors, starts);
    }

/* Convert from calling floats to internal ints degrees */

/** Update a PieDial.
 * @param colors -- colors to show for each wedge,
 * @param starts -- starting angle of each wedge,
 * order consistent with the current measuring system.  */

    public void setPie (Color [] colors, float [] starts) {
        setPie (this.measure, colors, starts);
    }

/* Mark some value on the scale */

    public void setMark (Color markColor, float markValue) {
	setMark (markColor, (int)markValue);
    }

    public void setMark (Color markColor, int markValue) {
	this.markColor = markColor;
	this.markValue = markValue;

        if (ZERO_NORTH_DEG_CW == measure)
            this.markValue = north_to_east (this.markValue);

	repaint (100);
    }

/** Report current starting angles of all segments,
 * @return degrees in user choice of measure directions. */

    public int [] getStarts () {
        if (ZERO_EAST_DEG_CCW == measure)
            return starts;
        else
            return east_to_north (starts);
    }

/** Report current starting angles of all segments,
 * @return degrees in user choice of measure directions. */

    public float [] getFloatStarts () {
        if (ZERO_EAST_DEG_CCW == measure)
            return ints_to_floats (starts);
        else
            return ints_to_floats (east_to_north (starts));
    }

/** Convert from ints [] to floats [] */

    public static float [] ints_to_floats (int [] ints) {
	float [] floats = new float [ints.length];
	for (int ii = 0; ii < ints.length; ii++)
	    floats [ii] = (float)ints [ii];
	return floats;
    }

/** Convert from floats [] to ints [] */

    public static int [] floats_to_ints (float [] floats) {
	int [] ints = new int [floats.length];
	for (int ii = 0; ii < floats.length; ii++)
	    ints [ii] = (int)floats [ii];
	return ints;
    }

/** MinimumSize is 4 times the current Font point size.
 * A square space is always requested,
 * but is not required. */

    public Dimension getMinimumSize () {

        int points = 12;
        Font oFont = getFont ();
        if (null != oFont)
	    points = oFont.getSize ();

	return new Dimension (4*points, 4*points);
    }

/** PreferredSize is 8 times the current Font point size.
 * A square space is always requested,
 * but is not required. */

    public Dimension getPreferredSize () {

        int points = 12;
        Font oFont = getFont ();
        if (null != oFont)
	    points = oFont.getSize ();

	return new Dimension (8*points, 8*points);
    }

/** Draw an edge to show we have mouse. */

    public void setActive (boolean bv) {
	active = bv;
	repaint (100);
    }

    public void setBounds (int x, int y, int w, int h) {
	super.setBounds (x, y, w, h);

        Dimension d = getSize ();

	if ((null == d)
	 || (0 == d.width)
	 || (0 == d.height))
	    return;

/* Paint the full allocated space,
 * but only draw the Pie in a centered square. */

       side = d.width;

       if (d.height > d.width) {
	   side = d.width;
	   yo = (d.height - side) / 2;
       }

       else if (d.width > d.height) {
	   side = d.height;
	   xo = (d.width - side) / 2;
       }

    }

    public void paint (Graphics g) {

        Dimension d = getSize ();

/* Initialize the background of the pie so that the user can
   tell there is something there at initialization (other
   than grey on grey) without giving focus (to see the outline). */

        g.setColor(Color.green);
        g.fillArc (xo, yo, side, side, 0, 360);

        for (int ii = 0;
             (null != starts) && (ii < starts.length);
	     ii++) {

/* Each section ends at start of next,
 * last ends at start of first.
 * They must be in proper order,
 * or some will be covered. */

                int nexta, alen;

                if (ii+1 < starts.length)
                    nexta = starts [ii+1];

                else
                    nexta = starts [0];

                alen = nexta - starts [ii];

/* Allow for crossing the zero */

                if ((ZERO_NORTH_DEG_CW == measure) && (0 < alen))
                    alen -= 360;

                if ((ZERO_EAST_DEG_CCW == measure) && (0 > alen))
                    alen += 360;

                g.setColor (colors [ii]);

                g.fillArc (xo, yo, side, side, starts [ii], alen);
        }

/* This works but does not always show up well enough.
 * Maybe provide contrasting outline.
 * Maybe better with thick line not wedge. */

       if (null != markColor) {
	   g.setColor (markColor);
	   g.fillArc (xo, yo, side, side, markValue, 2);
       }

/* Draw an edge to show we have mouse */

	if (active) {
	    g.setColor (Color.black);
	    g.drawArc (xo, yo, side, side, 0, 360);

/* And show a segment label if we have one */

            if (null != xyString) {

                FontMetrics fm = g.getFontMetrics ();
	        int sw = fm.stringWidth (xyString),
	            sh = fm.getHeight ();

                int xx = stringX - 2,
	            yy = stringY - sh - 2;

	        if (xx + sw + 4 > d.width)
	            xx = d.width - sw - 4;

                if (yy < 0)
	            yy = sh + 4;

	        g.setColor (Color.white);
	        g.fillRect (xx, yy, sw+4, sh+4);

	        g.setColor (Color.black);
                g.drawString (xyString,
		      xx + 2,
		      yy + fm.getAscent ()+2);
	    }
	}

    }

/* east_to_north --
 * E 000 -> 090
 * N 090 -> 000
 * W 180 -> 270
 * S 270 -> 180
 */

    static int east_to_north (int degs) {
        int ans = 90 - degs;
        if (ans < 0)
            ans += 360;
        return ans;
    }

    static int [] east_to_north (int [] degs) {
        if (null == degs)
            return null;

        int [] ans = new int [degs.length];
        for (int ii = 0; ii < degs.length; ii++)
            ans [ii] = east_to_north (degs [ii]);
        return ans;
    }

/* north_to_east --
 * N 000 -> 090
 * E 090 -> 000
 * S 180 -> 270
 * W 270 -> 180
 */

    static int north_to_east (int degs) {
        int ans = 90 - degs;
        if (ans < 0)
            ans += 360;
        return ans;
    }

    static int [] north_to_east (int [] degs) {
        if (null == degs)
            return null;

        int [] ans = new int [degs.length];
        for (int ii = 0; ii < degs.length; ii++)
            ans [ii] = north_to_east (degs [ii]);
        return ans;
    }
}
/* <IMG SRC="/cgi-bin/counter">*/
