/*
 * Copyright (c) 1996, 1997 by Jonathan R. Senning
 *
 * The original version of this applet was written by J. Senning during the
 * summer of 1996 in preparation for a planned sabbatical project.  The idea
 * for it came from the MATLAB m-file eigenshow.m written by ATLAST workshop
 * participants.
 * 
 * $Id: EigenExplorer.java,v 1.10 1997/05/26 14:46:01 senning Exp $
 *
 */

import java.awt.*;
import java.applet.Applet;

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---- class EigenExplorer --------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------

public class EigenExplorer extends Applet
{

    // ----------------------------------------------------------------

    GridBagLayout gridbag = new GridBagLayout();
    public double A[][] = { { 2, 1 }, { 1, 2 } };	// default matrix
    TextField tA[][];		// matrix entry
    Label lA[][];		// matrix display
    Button updateButton;	// update the matrix from the textfields
    Checkbox traceXButton;	// toggle circle trace of x vector
    Checkbox traceAxButton;	// toggle ellipse trace of Ax vector
    Slate slate;		// the double-buffered drawing area

    // ----------------------------------------------------------------

    public EigenExplorer()
    {
	super();
    }

    // ----------------------------------------------------------------

    public void init()
    {
	// ---- Override default matrix values from passed parameters ----

	String params[] = { "A11", "A12", "A21", "A22" };
	Double temp;
	String sTemp;

	for ( int i = 0; i < 2; i++ )
	{
	    for ( int j = 0; j < 2; j++ )
	    {
		sTemp = getParameter( params[ 2 * i + j ] );
		if ( sTemp != null )
		{
		    try {
			temp = Double.valueOf( sTemp );
			A[i][j] = temp.doubleValue();
		    } catch ( NumberFormatException e ) {
			// use default value
		    }
		}
	    }
	}

	// ---- Construct the text block for the matrix ----

	tA = new TextField [2][2];
	Panel matrixTextPanel = new Panel();
	matrixTextPanel.setLayout( new GridLayout( 2, 2, 0, 0 ) );
	for ( int i = 0; i < 2; i++ )
	{
	    for ( int j = 0; j < 2; j++ )
	    {
		tA[i][j] = new TextField( 3 );
		tA[i][j].setText( String.valueOf( A[i][j] ) );
		matrixTextPanel.add( tA[i][j] );
	    }
	}

	// ---- Construct the matrix panel ----

	lA = new Label [2][2];
	Panel matrixLabelPanel = new Panel();
	matrixLabelPanel.setLayout( new GridLayout( 2, 2, 0, 0 ) );
	for ( int i = 0; i < 2; i++ )
	{
	    for ( int j = 0; j < 2; j++ )
	    {
		lA[i][j] = new Label( String.valueOf( A[i][j] ) ); 
		matrixLabelPanel.add( lA[i][j] );
	    }
	}

	// ---- Construct update button and trace checkboxes----

	updateButton = new Button( "Update Matrix" );

	traceXButton = new Checkbox( "Trace x vector" );
	traceXButton.setState( true );
	traceAxButton = new Checkbox( "Trace Ax vector" );
	traceAxButton.setState( false );

	// ---- Construct the button panel ----
       
	Panel buttonPanel = new Panel();
	buttonPanel.setLayout( new GridLayout( 3, 1, 5, 5 ) );
	buttonPanel.add( updateButton );
	buttonPanel.add( traceXButton );
	buttonPanel.add( traceAxButton );
	
	// ---- Construct the status panel ----

	Panel statusPanel = new Panel();
	statusPanel.setLayout( new FlowLayout( FlowLayout.CENTER, 10, 10 ) );

	statusPanel.add( matrixTextPanel );
	statusPanel.add( new Label( "    A =" ) );
	statusPanel.add( matrixLabelPanel );
	statusPanel.add( buttonPanel );

	// ---- Construct the slate ----

	double v = norm( A );
	if ( v < 1.0 )
	{
	    v = 1.0;
	}
	slate = new Slate( this, -v, v, -v, v );

	// ---- Put the status panel on the toplevel window ----

	this.setLayout( gridbag );

	constrain( this, statusPanel, 0, 0, GridBagConstraints.REMAINDER, 1,
		GridBagConstraints.HORIZONTAL, GridBagConstraints.NORTHWEST,
		0.0, 0.0, 0, 0, 0, 0 );
	constrain( this, slate, 0, 1, GridBagConstraints.REMAINDER, 1,
		GridBagConstraints.BOTH, GridBagConstraints.CENTER,
		0.0, 1.0, 0, 0, 0, 0 );

	// ---- Try and set the correct background color ----
      
	this.setBackground( Color.lightGray );
	
	validate();
    }

    // ----------------------------------------------------------------

    public void constrain( Container container, Component component,
			   int x, int y, int width, int height,
			   int fill, int anchor, double wx, double wy,
			   int top, int left, int bottom, int right )
    {
	GridBagConstraints c = new GridBagConstraints();
	c.gridx = x;
	c.gridy = y;
	c.gridwidth = width;
	c.gridheight = height;
	c.fill = fill;
	c.anchor = anchor;
	c.weightx = wx;
	c.weighty = wy;
	if ( top + left + bottom + right > 0 )
	{
	    c.insets = new Insets( top, left, bottom, right );
	}
	((GridBagLayout)container.getLayout()).setConstraints( component, c );
	container.add( component );
    }

    // ----------------------------------------------------------------

    public boolean action( Event event, Object arg )
    {
	// ---- if the update button was pressed or "Enter" was
	//      pressed while the focus was any of the textfields
	//      then we need to update the matrix ----
	
	if ( event.target == updateButton || 
	     event.target == tA[0][0] || event.target == tA[0][1] ||
	     event.target == tA[1][0] || event.target == tA[1][1] )
	{
	    for ( int i = 0; i < 2; i++ )
	    {
		for ( int j = 0; j < 2; j++ )
		{
		    Double d;
		    try { d = Double.valueOf( tA[i][j].getText() ); }
		    catch ( NumberFormatException e ) { return false; }
		    A[i][j] = d.doubleValue();
		    lA[i][j].setText( String.valueOf( A[i][j] ) ); 
		}
	    }
	}

	// ---- compute the size of the slate ----

	double v = norm( A );
	if ( v < 1.0 )
	{
	    v = 1.0;
	}
	slate.setWindow( -v, v, -v, v );
	slate.repaint();

	return true;
    }

    // ----------------------------------------------------------------

    public double norm( double A[][] )
      // matrix norm (is this norm of transpose(A) * A?, I forget...)
    {
	double a = A[0][0] * A[0][0] + A[1][0] * A[1][0];
	double b = A[0][0] * A[0][1] + A[1][0] * A[1][1];
	double c = A[0][1] * A[0][1] + A[1][1] * A[1][1];
	double s1 = ( a + c ) / 2.0;
	double s2 = ( a - c ) / 2.0;
	s2 = Math.sqrt( s2 * s2 + b * b );
	return Math.sqrt( s1 + s2 );
    }

    // ----------------------------------------------------------------

    public String getAppletInfo()
    {
	return "$Id: EigenExplorer.java,v 1.10 1997/05/26 14:46:01 senning Exp $";
    }

    // ----------------------------------------------------------------

} // ---- Ends class EigenExplorer ----


// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---- class Slate ----------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------

class Slate extends Canvas
{

    // ----------------------------------------------------------------

    Container container;
    private double x_min;
    private double x_max;
    private double y_min;
    private double y_max;
    private double x_factor;
    private double y_factor;
    private double x_offset;
    private double y_offset;
    private double x = 1.0;
    private double y = 0.0;
    private int width  = 0;
    private int height = 0;

    private FontMetrics fontMetrics;
    private int fontDescent;
    private int fontHeight;

    private Image backImage = null;
    private Graphics backGraphics = null;

    private Point xText;
    private Point AxText;
    private Point eValueText;
  
    // ----------------------------------------------------------------

    public Slate( Container c, double x0, double x1, double y0, double y1 )
    {
	container = c;
	this.setWindow( x0, x1, y0, y1 );
    }

    // ----------------------------------------------------------------

    public Slate( Container c )
    {
	this( c, -1.0, 1.0, -1.0, 1.0 );
    }

    // ----------------------------------------------------------------

    public void setWindow( double x0, double x1, double y0, double y1 )
    {
	x_min = x0;
	x_max = x1;
	y_min = y0;
	y_max = y1;
	backImage = null;
	backGraphics = null;
    }

    // ----------------------------------------------------------------

    public void addNotify()
    {
	super.addNotify();
    }

    // ----------------------------------------------------------------

    public void initializeSlate()
    {
	if ( backImage == null )
	{
	    Dimension d = size();
	    width  = d.width;
	    height = d.height;

	    int dist = width - 1;
	    if ( height < width )
	    {
		dist = height - 1;
	    }

	    x_factor = (double) dist / ( x_max - x_min );
	    y_factor = (double) dist / ( y_min - y_max );

	    x_offset = - x_factor * x_min;
	    y_offset = (double) dist - y_factor * y_min;

	    fontMetrics = this.getFontMetrics( this.getFont() );
	    fontDescent = fontMetrics.getDescent();
	    fontHeight = fontMetrics.getHeight();

	    backImage = createImage( width, height );
	    backGraphics = backImage.getGraphics();
	}
    }

    // ----------------------------------------------------------------

    public boolean mouseDown( Event e, int ix, int iy )
    {
	x = ( (double) ix - x_offset ) / x_factor;
	y = ( (double) iy - y_offset ) / y_factor;
	double s = magnitude( x, y );
	x /= s;
	y /= s;
	repaint();
	return true;
    }

    // ----------------------------------------------------------------

    public boolean mouseDrag( Event e, int ix, int iy )
    {
	x = ( (double) ix - x_offset ) / x_factor;
	y = ( (double) iy - y_offset ) / y_factor;
	double s = magnitude( x, y );
	x /= s;
	y /= s;
	repaint();
	return true;
    }

    // ----------------------------------------------------------------

    public void drawLine( Graphics g,
			  double x0, double y0, double x1, double y1 )
    {
	int ix0 = (int) ( x_factor * x0 + x_offset );
	int iy0 = (int) ( y_factor * y0 + y_offset );
	int ix1 = (int) ( x_factor * x1 + x_offset );
	int iy1 = (int) ( y_factor * y1 + y_offset );
	g.drawLine( ix0, iy0, ix1, iy1 );
    }

    // ----------------------------------------------------------------

    public void drawCircle( Graphics g, double x, double y, double r )
    {
	int ix = (int) ( x_factor * ( x - r ) + x_offset );
	int iy = (int) ( y_factor * ( y + r ) + y_offset );
	int irx = (int) Math.abs( x_factor * 2.0 * r );
	int iry = (int) Math.abs( y_factor * 2.0 * r );
	g.drawOval( ix, iy, irx, iry );
    }

    // ----------------------------------------------------------------

    public void drawEllipse( Graphics g, double A[][] )
    {
	int nseg = 64;
	double ox = A[0][0];
	double oy = A[1][0];
	int iox = (int) ( x_factor *  ox + x_offset );
	int ioy = (int) ( y_factor *  oy + y_offset );	
	for ( int i = 1; i <= nseg; i++ )
	{
	    double a = i * 2.0 * 3.141529654 / (double) nseg;
	    double C = Math.cos( a );
	    double S = Math.sin( a );
	    int ix = (int) ( x_factor * ( A[0][0] * C + A[0][1] * S ) + x_offset );
	    int iy = (int) ( y_factor * ( A[1][0] * C + A[1][1] * S ) + y_offset );
	    g.drawLine( iox, ioy, ix, iy );
	    iox = ix;
	    ioy = iy;
	}
    }

    // ----------------------------------------------------------------

    public Insets insets()
    {
	return new Insets( 4, 4, 5, 5 );
    }

    // ----------------------------------------------------------------

    public void paint( Graphics g )
    {
	update( g );
    }

    // ----------------------------------------------------------------

    public void update( Graphics g )
    {
	if ( backImage == null )
	{
	    initializeSlate();
	    xText = new Point( fontHeight, fontHeight );
	    AxText = new Point( fontHeight, height - 1 - fontDescent );
	    int strWidth = fontMetrics.stringWidth( "||Ax|| = 9.999999" );
	    eValueText = new Point( width - 1 - fontHeight - strWidth,
				    height - 1 - fontDescent );
	}

	if ( x < x_min )
	{
	    x = x_min;
	}
	else if ( x > x_max )
	{
	    x = x_max;
	}
	if ( y < y_min )
	{
	    y = y_min;
	}
	else if ( y > y_max )
	{
	    y = y_max;
	}

	backGraphics.setColor( getBackground() );
	backGraphics.fillRect( 0, 0, width, height );

	if ( ((EigenExplorer)container).traceXButton.getState() )
	{
	    backGraphics.setColor( Color.darkGray );
	    this.drawCircle( backGraphics, 0.0, 0.0, 1.0 );
	}
	
	if ( ((EigenExplorer)container).traceAxButton.getState() )
	{
	    backGraphics.setColor( Color.gray );	
	    this.drawEllipse( backGraphics, ((EigenExplorer)container).A );
	}

	backGraphics.setColor( Color.blue );
	this.drawLine( backGraphics, 0.0, 0.0, x, y );
	backGraphics.drawString( "x = ( " + x + ", " + y + " )",
				 xText.x, xText.y );

	double ax = ((EigenExplorer)container).A[0][0] * x +
		    ((EigenExplorer)container).A[0][1] * y;
	double ay = ((EigenExplorer)container).A[1][0] * x +
		    ((EigenExplorer)container).A[1][1] * y;

	backGraphics.setColor( Color.red );
	this.drawLine( backGraphics, 0.0, 0.0, ax, ay );
	backGraphics.drawString( "Ax = ( " + ax + ", " + ay + " )",
				 AxText.x, AxText.y );

	backGraphics.setColor( Color.black );
	backGraphics.drawString( "||Ax|| = " + magnitude( ax, ay ),
				 eValueText.x, eValueText.y );

	g.drawImage( backImage, 0, 0, this );
    }

    // ----------------------------------------------------------------

    double magnitude( double x, double y )
    {
	return Math.sqrt( x * x + y * y );
    }

    // ----------------------------------------------------------------

} // ---- Ends class Slate ----
