/* 
 * CurveComponent3.java
 *
 * (C) Copyright 2007, Morten Rhiger
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * March 14, 2007, Morten Rhiger (mir@ruc.dk)
 */

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

import java.awt.geom.Line2D;
import java.awt.geom.Point2D;

import javax.swing.*;

/**
    A larger class to illustrate how to draw on JComponents and how to
    reach to events.  Objects of this class are components onto which
    a curve is drawn.  The user can move a crosshair over the curve.
 
    @author    Morten Rhiger (mir@ruc.dk)
    @version   March 20, 2007
*/
public class CurveComponent3
    extends JComponent 
    implements MouseMotionListener, MouseListener {

    /**
        Computes the function to plot.
	@param  x the x cordinate 
	@return   the corresponding y coordinate
    */
    private double f(double x) {
	// compute f(x) here
	return Math.log(1 + x * x) * Math.sin(x);
    }       

    /**
        The stroke to use for drawing the crosshair that follows the
	mouse.
    */
    private Stroke crossHairStroke;

    /**
        The object that formats double numbers nicely
    */
    private java.text.DecimalFormat formatter;

    /**
        These fields determine where to put the origo of the
	coordinate system.
    */

    private double center_x;
    private double center_y;

    /** 
	These fields determine how many pixels (on the screen)
	corresponds to une in the (user) coordinate system.
    */

    private double resolution_x;
    private double resolution_y;

    /**
        These parameters hold the x and y coordinate of the mouse
        pointer.  They are set in the method mouseMoved().
    */
    private int mouse_x = -1; // current x coordinate of mouse, or -1
    private int mouse_y = -1; // current y coordinate of mouse, or -1

    /**
        These parameters hold the x and y coordinate of the mouse
        while dragging.
    */
    private int drag_start_x;
    private int drag_start_y;

    private int drag_mode;

    /**
        Constructs a new CurveComponent.
    */
    public CurveComponent3() {

	center_x = 100;
	center_y = 200;

	resolution_x = 50.0;
	resolution_y = 50.0;

	crossHairStroke =
	    new BasicStroke(0.0F, 
			    BasicStroke.CAP_BUTT,
			    BasicStroke.JOIN_MITER,
			    1.0F,
			    new float[] { 1.0F },
			    0.0F);

	formatter = new java.text.DecimalFormat("####.00");

	this.addMouseMotionListener(this);
	this.addMouseListener(this);
    }

    /**
        Invoked automatically by the system when the mouse is
	dragged. (That is, when the mouse moves and one of the mouse
	keys is held down.) This method doesn't do anything. 

	@param e the event
    */
    public void mouseDragged(MouseEvent e) { 
	int dx = e.getX() - drag_start_x;
	int dy = e.getY() - drag_start_y;

	if (drag_mode == MouseEvent.BUTTON1) {

	    center_x += dx;
	    center_y += dy;
	    
	    mouse_x = e.getX();
	    mouse_y = e.getY();

	} else if (drag_mode == MouseEvent.BUTTON3) {
	    
	    if (dy > 0) {
		resolution_y++;
	    } else if (dy < 0 && resolution_y > 11) {
		resolution_y--;
	    }

	    if (dx > 0) {
		resolution_x++;
	    } else if (dx < 0 && resolution_x > 11) {
		resolution_x--;
	    }

	}
    
	drag_start_x = e.getX();
	drag_start_y = e.getY();

	repaint();
    }

    /**
        Invoked automatically by the system when the mouse is
	moved. (That is, when the mouse moves and none of the mouse
	keys are held down.)  This method simply saves the current x
	and y coordinate of the mouse and asks the component to
	repaint itself.

	@param e the event
    */
    public void mouseMoved(MouseEvent e) {
	mouse_x = e.getX();
	mouse_y = e.getY();
	repaint();
    }

    /** 
	Handles mouse events.
    */
    public void mousePressed(MouseEvent e) {
	drag_mode = e.getButton();
	drag_start_x = e.getX();
	drag_start_y = e.getY();
	repaint();
    }

    /** 
	Handles mouse events.
    */
    public void mouseEntered(MouseEvent e) { }
    public void mouseExited(MouseEvent e) { }
    public void mouseReleased(MouseEvent e) { }
    public void mouseClicked(MouseEvent e) { }


    /**
        Draws the coordinate system (axes and ticks).  Private method
        invoked from paintComponent().

	@param g2     the graphics object to use when drawing
	@param width  the width of the component
	@param height the width of the component
    */
    private void paintCoordinateSystem(Graphics2D g2, int width, int height) {
	
	// draw center of coordinate system

	g2.setColor(Color.LIGHT_GRAY);

	g2.draw(new Line2D.Double(0, center_y, width, center_y));
	g2.draw(new Line2D.Double(center_x, 0, center_x, height));

	// draw ticks

	g2.setColor(Color.DARK_GRAY);

	int mark_x = 0;
	for (double x = center_x; x >= 0; x -= resolution_x) {
	    if (mark_x != 0) {
		g2.draw(new Line2D.Double(x, 0, x, 5));
		g2.draw(new Line2D.Double(x, height - 5, x, height));
		g2.drawString("" + mark_x, (float) x, (float) height - 10);
	    }
	    mark_x--;
	}

	mark_x = 0;
	for (double x = center_x; x < width; x += resolution_x) {
	    if (mark_x != 0) {
		g2.draw(new Line2D.Double(x, 0, x, 5));
		g2.draw(new Line2D.Double(x, height - 5, x, height));
		g2.drawString("" + mark_x, (float) x, (float) height - 10);
	    }
	    mark_x++;
	}

	int mark_y = 0;
	for (double y = center_y; y >= 0; y -= resolution_y) {
	    if (mark_y != 0) {
		g2.draw(new Line2D.Double(0, y, 5, y));
		g2.draw(new Line2D.Double(width - 5, y, width, y));
		g2.drawString("" + mark_y, (float) 10, (float) y + 4.0F);
	    }
	    mark_y++;
	}

	mark_y = 0;
	for (double y = center_y; y < width; y += resolution_y) {
	    if (mark_y != 0) {
		g2.draw(new Line2D.Double(0, y, 5, y));
		g2.draw(new Line2D.Double(width, y, width - 5, y));
		g2.drawString("" + mark_y, (float) 10, (float) y + 4.0F);
	    }
	    mark_y--;
	}	    
    }

    /**
        Draws the curve. Private method invoked from paintComponent().

	@param g2     the graphics object to use when drawing
	@param width  the width of the component
	@param height the width of the component
    */
    private void paintCurve(Graphics2D g2, int width, int height) {
	
	g2.setColor(Color.BLACK);

	Point2D.Double left = null;

	for (double sx = 0; sx < width; sx += 1) {

	    // Convert screen x coordinate sx (in pixels) to the
	    // coordinate system of the curve:

	    double x = (sx - center_x) / resolution_x;

	    // Compute y coordinate

	    double y = f(x);

	    // Convert y in the coordinate system of the curve to
	    // screen coordinates (remember to negate the y value):
	    
	    double sy = center_y - y * resolution_y;

	    // Construct right-most point. Then draw from left-most
	    // point to right-most point:

	    Point2D.Double right = new Point2D.Double(sx, sy);

	    if (left != null) 
		g2.draw(new Line2D.Double(left, right));

	    left = right;
	}
    }

    /** 
	Draws the crosshair. Private method invoked from
        paintComponent().

	@param g2     the graphics object to use when drawing
	@param width  the width of the component
	@param height the width of the component
    */
    private void paintCrossHair(Graphics2D g2, int width, int height) {
	if (mouse_x != -1 && mouse_y != -1) {
	    double x = (mouse_x - center_x) / resolution_x;
	    double y = f(x);

	    double sy = center_y - y * resolution_y;

	    g2.setStroke(crossHairStroke);
	    g2.setColor(Color.RED);

	    g2.draw(new Line2D.Double(mouse_x, 0, mouse_x, getWidth()));

	    // p1 on the curve, p2 on the y-axis
 	    Point2D.Double p1 = new Point2D.Double(mouse_x,  sy);
 	    Point2D.Double p2 = new Point2D.Double(center_x, sy);
	    g2.draw(new Line2D.Double(p1, p2));

	    g2.drawString("(" + formatter.format(x) 
			  + ", " + formatter.format(y) + ")",
			  (float) mouse_x + 5, (float) sy - 5);

	    p1 = new Point2D.Double(mouse_x,  mouse_y);
	    p2 = new Point2D.Double(center_x, mouse_y);
	    g2.draw(new Line2D.Double(p1, p2));

	    y = (mouse_y - center_y) / resolution_y;
	    g2.drawString("(" + formatter.format(x) 
			  + ", " + formatter.format(-y) + ")",
			  (float) mouse_x + 5, (float) mouse_y - 5);
	}
    }

    /**
        Paints the component.
    */
    public void paintComponent(Graphics g) {
	Graphics2D g2 = (Graphics2D) g;

	int width  = getWidth();
	int height = getHeight();

	// Clear window

	g2.setBackground(Color.WHITE);
	g2.clearRect(0, 0, width, height);

	paintCoordinateSystem(g2, width, height);
	paintCurve(g2, width, height);
	paintCrossHair(g2, width, height);	    
    }

    public static void main(String[] args) {
	JFrame frame = new JFrame();

	frame.setSize(400, 400);
	frame.setTitle("Curve");
	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	frame.add(new CurveComponent3());
	frame.setVisible(true);
    }
}

