import java.awt.*;

import javax.swing.*;
import java.awt.geom.Line2D;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.*;

/**
 * Demonstrates the use of a custom table model, and some other
 * configuration, for a JTable.  The program lets the user enter
 * (x,y) coordinates of some points, and it draws a simple
 * scatter plot of all the points in the table for which
 * both the x and the y coordinate is defined.  This class can
 * be run as a standalone application.
 */
public class ScatterPlotTableDemo extends JPanel {


    /**
     * The main routine simply opens a window that shows a ScatterPlotTableDemo panel.
     */
    public static void main(String[] args) {
        JFrame window = new JFrame("ScatterPlotTableDemo");
        ScatterPlotTableDemo content = new ScatterPlotTableDemo();
        window.setContentPane(content);
        window.pack();
        window.setResizable(false);
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        window.setLocation( (screenSize.width - window.getWidth())/2,
                (screenSize.height - window.getHeight())/2 );
        window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        window.setVisible(true);
    }


    /**
     * This class defines the TableModel that is used for the JTable in this
     * program.  The table has three columns.  Column 0 simply holds the
     * row number of each row.  Column 1 holds the x-coordinates of the
     * points for the scatter plot, and Column 2 holds the y-coordinates.
     * The table has 25 rows.  No support is provided for adding more rows.
     */
    private class CoordInputTableModel extends AbstractTableModel {

        private Double[] xCoord = new Double[25];  // Data for Column 1, initially all null.
        private Double[] yCoord = new Double[25];  // Data for Column 2, initially all null.

        public int getColumnCount() {  // Tells caller how many columns there are.
            return 3;
        }

        public int getRowCount() {  // Tells caller how many rows there are.
            return xCoord.length;
        }

        public Object getValueAt(int row, int col) {  // Get the data for one cell.
            if (col == 0)
                return (row+1);   // Column 0 holds the row number.
            else if (col == 1)
                return xCoord[row];    // Column 1 holds the x-coordinates.
            else
                return yCoord[row];    // column 2 holds the y-coordinates.
        }

        public Class<?> getColumnClass(int col) {  // Get data type of column.
            if (col == 0)
                return Integer.class;
            else
                return Double.class;
        }

        public String getColumnName(int col) {  // Returns a name for column header.
            if (col == 0)
                return "Num";
            else if (col == 1)
                return "X";
            else
                return "Y";
        }

        public boolean isCellEditable(int row, int col) { // Can user edit cell?
            return col > 0;
        }

        public void setValueAt(Object obj, int row, int col) { // Changes cell value.
                // (This method is called by the system if the value of the cell
                // needs to be changed because the user has edited the current value.
                // It can also be called to change the value programmatically.
                // In this case, only columns 1 and 2 can be modified, and the data
                // type for obj must be Double.  The method fireTableCellUpdated()
                // has to be called to send a TableModelEvent to registered listeners.)
            if (col == 1) 
                xCoord[row] = (Double)obj;
            else if (col == 2)
                yCoord[row] = (Double)obj;
            fireTableCellUpdated(row, col);
        }

    }  // end nested class CoordInputTableModel


    /**
     * Defines the display area where a scatter plot of the points
     * in the table is drawn.  The range of values shown in the plot
     * is adjusted to make sure that all the points are visible.
     * Note that only points for which both coordinates are
     * defined are drawn.
     */
    private class Display extends JPanel {
        public void paintComponent(Graphics g) {  
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D)g;

            double min = -0.5;  // Minimum of the range of values displayed.
            double max = 5;     // Maximum of the range of value displayed.
            int count = model.getRowCount();
            for (int i = 0; i < count; i++) {
                Double x = (Double)model.getValueAt(i, 1);  // (Return type of getValue() is Object.)
                Double y = (Double)model.getValueAt(i, 2);
                if (x != null && y != null) {
                        // Adjust max and min to include x and y.
                    if (x < min)
                        min = x - 0.5;
                    if (x > max)
                        max = x + 0.5;
                    if (y < min)
                        min = y - 0.5;
                    if (y > max)
                        max = y + 0.5;
                }
            }

            /* Apply a translation so that the drawing coordinates on the display
                   correspond to the range of values that I want to show. */

            g2.translate(getWidth()/2,getHeight()/2);
            g2.scale(getWidth()/(max-min), -getHeight()/(max-min));
            g2.translate(-(max+min)/2, -(max+min)/2);

            /* I want to be able to draw lines that are a certain number of pixels
                   long.  Unfortunately, the unit of length is no longer equal to the
                   size of a pixel, so I have to figure out how big a pixel is in the
                   new coordinates.  Also, horizontal and vertical size can be different. */

            double pixelWidth = (max-min)/getWidth();    // Horizontal size of a pixel in new coords.
            double pixelHeight = (max-min)/getHeight();  // Vertical size of a pixel in new coord.

            /* When the thickness of a BasicStroke is set to 0, the actual width of
                   the stroke will be as small as possible, that is, one pixel wide. */

            g2.setStroke(new BasicStroke(0));

            /* Draw x and y axes with tick marks to mark the integers (but don't draw
                   the tick marks if there would be more than 100 of them. */

            g2.setColor(Color.BLUE);
            g2.draw( new Line2D.Double(min,0,max,0));
            g2.draw( new Line2D.Double(0,min,0,max));
            if (max - min < 100) {
                int tick = (int)min;
                while (tick <= max) {
                    g2.draw(new Line2D.Double(tick,0,tick,3*pixelHeight));
                    g2.draw(new Line2D.Double(0,tick,3*pixelWidth,tick));
                    tick++;
                }
            }

            /* Draw a small crosshair at each point from the table. */

            g2.setColor(Color.RED);
            for (int i = 0; i < count; i++) {
                Double x = (Double)model.getValueAt(i, 1);
                Double y = (Double)model.getValueAt(i, 2);
                if (x != null && y != null) {
                    g2.draw(new Line2D.Double(x-3*pixelWidth,y,x+3*pixelWidth,y));
                    g2.draw(new Line2D.Double(x,y-3*pixelHeight,x,y+3*pixelHeight));
                }
            }

        }
    } // end nested class Display


    private JTable table;                // The JTable where the points are input.
    private CoordInputTableModel model;  // The TableModel for the table.
    private Display display;             // The panel where the scatter plot is drawn.


    /**
     * The constructor creates the model, table, and display.  The table and display
     * are added to this panel.  The table is configured to show grid lines between
     * cells, to disallow dragging of a column to a new position, to increase the
     * height of the cells, to set different preferred sizes for the columns, and
     * to set a preferred size for the viewport of the scrollpane that will display
     * the table.  A listener is set up on the model so that the display can be
     * changed whenever the data in the model changes.  (Also, some random data is
     * put in the table, as an example to help the user see what is going on -- this
     * is just done for demonstration purposes.)
     */
    public ScatterPlotTableDemo() {

        setLayout(new BorderLayout());
        setBorder(BorderFactory.createLineBorder(Color.BLACK,1));

        model = new CoordInputTableModel();
        table = new JTable(model);
        table.setRowHeight(25);
        table.setShowGrid(true);
        table.setGridColor(Color.BLACK);
        table.setPreferredScrollableViewportSize(new Dimension(250, 300));
        table.getColumnModel().getColumn(0).setPreferredWidth(50);
        table.getColumnModel().getColumn(1).setPreferredWidth(100);
        table.getColumnModel().getColumn(2).setPreferredWidth(100);
        table.getTableHeader().setReorderingAllowed(false);
        add(new JScrollPane(table), BorderLayout.WEST);

        for  (int i = 0; i < 6; i++) {  // Fill first 6 rows with random values.
            model.setValueAt( (int)(450*Math.random())/100.0, i, 1 );
            model.setValueAt( (int)(450*Math.random())/100.0, i, 2 );
        }

        display = new Display();
        display.setPreferredSize(new Dimension(300,300));
        display.setBackground(Color.WHITE);
        add(display, BorderLayout.CENTER);

        model.addTableModelListener(new TableModelListener() {
                // Install a TableModelListener that will respond to any
                // changes in the model's data by redrawing the display that
                // shows the scatter plot.
            public void tableChanged(TableModelEvent e) {
                display.repaint();
            }
        });
    }

}
