/*
 * -------------------------------------------------------------------------
 *      $Id: Portfolio.java,v 1.9 2005/12/30 16:38:31 estewart Exp $
 * -------------------------------------------------------------------------
 *      Copyright (c) 2004 Visual Numerics Inc. All Rights Reserved.
 *
 *      This software is confidential information which is proprietary to
 *      and a trade secret of Visual Numerics, Inc.  Use, duplication or
 *      disclosure is subject to the terms of an appropriate license
 *      agreement.
 *
 *      VISUAL NUMERICS MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE
 *      SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING
 *      BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY,
 *      FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. VISUAL
 *      NUMERICS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 *      AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR
 *      ITS DERIVATIVES.
 *--------------------------------------------------------------------------
 */

package com.imsl.demo.portfolio;
import com.imsl.chart.*;
import com.imsl.demo.gallery.Describe;
import java.awt.Dimension;

/**
 *
 * @author  ed
 * @created February 26, 2004
 */
public class Portfolio extends com.imsl.chart.JFrameChart implements java.awt.event.MouseMotionListener, java.awt.event.MouseListener {

    private double[][] solution;
    private double riskFreeRate = 4.0;
    private Integer old1min, old2min, old3min, old4min;
    private Integer old1max, old2max, old3max, old4max;
    private int optimalIndex, oldIndex;
    private Chart chart;
    private AxisXY axis;
    private Pie pie, miniPie;
    private Data mousePoint;
    private ToolTip pointTip;
    private String[] names = {"A1","B2","C3","D4"};
    private final java.awt.Color darkGreen = new java.awt.Color(47,129,47);

    /** Creates new form Portfolio */
    public Portfolio(boolean exitOnClose) {
        if (!exitOnClose) {
            // remove the WindowListener,  installed by JFrameChart, that
            // exits the application when the window is closed.
            Object l[] = getListeners(java.awt.event.WindowListener.class);
            for (int k = 0;  k < l.length;  k++) {
                removeWindowListener((java.awt.event.WindowListener)l[k]);
            }
        }
        Describe des = new Describe(this, "/com/imsl/demo/portfolio/Portfolio.html");
        des.show();
        Dimension ds = des.getSize();

        Dimension ss = getToolkit().getScreenSize();
        int h = Math.min(ss.width/2, ss.height-ds.height-32);
        int w = (int)(h/0.8);
        setSize(w, h);
        setLocation(ss.width-ds.width, ds.height);
        setTitle("Portfolio Optimization");

        initComponents();
        setupChart();
        doAll();
    }

    // do everything - find the efficient frontier, find the portfolio,
    // clear and replot the chart, and update the solution text
    private void doAll() {
        if (!checkSpinners()) return;
        runEfficient();
        optimalIndex = findOptimalIndex();
        clearChart();
        plotEfficientFrontier(solution[0],solution[1]);
        solutionText.setText(buildHTMLText());
    }

    // configure the axes on the chart; add a MouseMotionListener
    private void setupChart() {
        chart = getChart();
        axis = new AxisXY(chart);
        axis.getAxisX().getAxisTitle().setTitle("Risk (Return Volatility), %");
        axis.getAxisY().getAxisTitle().setTitle("Fractional Return, %");
        axis.getAxisX().getAxisTitle().setFontStyle(java.awt.Font.BOLD);
        axis.getAxisY().getAxisTitle().setFontStyle(java.awt.Font.BOLD);
        axis.getAxisX().setTextFormat(new java.text.DecimalFormat("##"));
        axis.getAxisY().setTextFormat(new java.text.DecimalFormat("##"));
        axis.getAxisX().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
        axis.getAxisY().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
        axis.getAxisX().setWindow(0.0,20.0);
        axis.getAxisY().setWindow(0.0,30.0);

        getPanel().addMouseMotionListener(this);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    private void initComponents() {//GEN-BEGIN:initComponents
        jLabel2 = new javax.swing.JLabel();
        jLabel3 = new javax.swing.JLabel();
        jPanelNorth = new javax.swing.JPanel();
        jPanelSpinners = new javax.swing.JPanel();
    jPanelLab = new javax.swing.JPanel();
        jLabel1 = new javax.swing.JLabel();
        jPanel2 = new javax.swing.JPanel();
        jCheckBox1 = new javax.swing.JCheckBox();
        minSpinner1 = new javax.swing.JSpinner(new javax.swing.SpinnerNumberModel(0, 0, 100, 1));
        maxSpinner1 = new javax.swing.JSpinner(new javax.swing.SpinnerNumberModel(100, 0, 100, 1));
        jPanel3 = new javax.swing.JPanel();
        jCheckBox2 = new javax.swing.JCheckBox();
        minSpinner2 = new javax.swing.JSpinner(new javax.swing.SpinnerNumberModel(0, 0, 100, 1));
        maxSpinner2 = new javax.swing.JSpinner(new javax.swing.SpinnerNumberModel(100, 0, 100, 1));
        jPanel4 = new javax.swing.JPanel();
        jCheckBox3 = new javax.swing.JCheckBox();
        minSpinner3 = new javax.swing.JSpinner(new javax.swing.SpinnerNumberModel(0, 0, 100, 1));
        maxSpinner3 = new javax.swing.JSpinner(new javax.swing.SpinnerNumberModel(100, 0, 100, 1));
        jPanel5 = new javax.swing.JPanel();
        jCheckBox4 = new javax.swing.JCheckBox();
        minSpinner4 = new javax.swing.JSpinner(new javax.swing.SpinnerNumberModel(0, 0, 100, 1));
        maxSpinner4 = new javax.swing.JSpinner(new javax.swing.SpinnerNumberModel(100, 0, 100, 1));
        jPanel8 = new javax.swing.JPanel();
        jPanel6 = new javax.swing.JPanel();
        jLabel4 = new javax.swing.JLabel();
        rateSlider = new javax.swing.JSlider();
        solutionText = new javax.swing.JEditorPane();
        jButton = new javax.swing.JButton();

        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                exitForm(evt);
            }
        });

        jPanelNorth.setLayout(new java.awt.BorderLayout());

        jPanelSpinners.setLayout(new javax.swing.BoxLayout(jPanelSpinners, javax.swing.BoxLayout.Y_AXIS));

        jLabel1.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        jLabel1.setText("Assets and weight constraints");
    jPanelLab.add(jLabel1);
    jPanelSpinners.add(jPanelLab);

        jCheckBox1.setText(names[0]);
        jCheckBox1.setSelected(true);
        jCheckBox1.setForeground(darkGreen);
        jCheckBox1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCheckBox1ActionPerformed(evt);
            }
        });

        jPanel2.add(jCheckBox1);
        jPanel2.add(minSpinner1);
        jPanel2.add(maxSpinner1);
        jPanelSpinners.add(jPanel2);

        jCheckBox2.setText(names[1]);
        jCheckBox2.setSelected(true);
        jCheckBox2.setForeground(java.awt.Color.red);
        jCheckBox2.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCheckBox2ActionPerformed(evt);
            }
        });

        jPanel3.add(jCheckBox2);
        jPanel3.add(minSpinner2);
        jPanel3.add(maxSpinner2);
        jPanelSpinners.add(jPanel3);

        jCheckBox3.setText(names[2]);
        jCheckBox3.setSelected(true);
        jCheckBox3.setForeground(java.awt.Color.blue);
        jCheckBox3.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCheckBox3ActionPerformed(evt);
            }
        });

        jPanel4.add(jCheckBox3);
        jPanel4.add(minSpinner3);
        jPanel4.add(maxSpinner3);
        jPanelSpinners.add(jPanel4);

        jCheckBox4.setText(names[3]);
        jCheckBox4.setSelected(true);
        jCheckBox4.setForeground(java.awt.Color.yellow);
        jCheckBox4.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCheckBox4ActionPerformed(evt);
            }
        });

        jPanel5.add(jCheckBox4);
        jPanel5.add(minSpinner4);
        jPanel5.add(maxSpinner4);
        jPanelSpinners.add(jPanel5);

        jPanelNorth.add(jPanelSpinners, java.awt.BorderLayout.CENTER);

        jPanel8.setLayout(new java.awt.BorderLayout());

        jLabel4.setText("Risk Free Rate of Return: ");
        jPanel6.add(jLabel4);

        rateSlider.setPaintLabels(true);
        rateSlider.setPaintTicks(true);
        rateSlider.setMajorTickSpacing(1);
        rateSlider.setValue((int)riskFreeRate);
        rateSlider.setMinimum(1);
        rateSlider.setMaximum(10);
        rateSlider.setSnapToTicks(true);
        rateSlider.setPreferredSize(new java.awt.Dimension(125, 43));
        rateSlider.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                rateSliderStateChanged(evt);
            }
        });

        jPanel6.add(rateSlider);

        jPanel8.add(jPanel6, java.awt.BorderLayout.NORTH);

        /** Block of code is for solutionText as a JTextField
         * But want to enable color, so should be a JEditorPane with text/html
        solutionText.setEditable(false);
        solutionText.setColumns(40);
        solutionText.setFont(new java.awt.Font("Monospaced", 0, 12));
        solutionText.setRows(6);
        solutionText.setText("Solution");
        solutionText.setBackground(new java.awt.Color(204, 204, 204));
        solutionText.setBorder(new javax.swing.border.SoftBevelBorder(javax.swing.border.BevelBorder.RAISED));
         *
         */
        // This chunk of code is for solutionText as JEditorPane
        solutionText.setEditable(false);
        solutionText.setContentType("text/html");
        solutionText.setFont(new java.awt.Font("Monospaced", java.awt.Font.PLAIN, 12));
        solutionText.setBackground(jPanel8.getBackground());
        solutionText.setBorder(new javax.swing.border.SoftBevelBorder(javax.swing.border.BevelBorder.RAISED));
        jPanel8.add(solutionText, java.awt.BorderLayout.CENTER);

        jButton.setText("Apply Constraints");
        jButton.setToolTipText("Compute solution and plot");
        jButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonActionPerformed(evt);
            }
        });

        jPanel8.add(jButton, java.awt.BorderLayout.SOUTH);

        jPanelNorth.add(jPanel8, java.awt.BorderLayout.EAST);

        getContentPane().add(jPanelNorth, java.awt.BorderLayout.NORTH);

        pack();
    }//GEN-END:initComponents

    private void jCheckBox4ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBox4ActionPerformed
        if (jCheckBox4.isSelected()) {
            minSpinner4.setEnabled(true);
            maxSpinner4.setEnabled(true);
            minSpinner4.setValue(old4min);
            maxSpinner4.setValue(old4max);
        } else {
            old4min = (Integer)minSpinner4.getValue();
            old4max = (Integer)maxSpinner4.getValue();
            minSpinner4.setValue(new Integer(0));
            maxSpinner4.setValue(new Integer(0));
            minSpinner4.setEnabled(false);
            maxSpinner4.setEnabled(false);
        }
    }//GEN-LAST:event_jCheckBox4ActionPerformed

    private void jCheckBox3ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBox3ActionPerformed
        if (jCheckBox3.isSelected()) {
            minSpinner3.setEnabled(true);
            maxSpinner3.setEnabled(true);
            minSpinner3.setValue(old3min);
            maxSpinner3.setValue(old3max);
        } else {
            old3min = (Integer)minSpinner3.getValue();
            old3max = (Integer)maxSpinner3.getValue();
            minSpinner3.setValue(new Integer(0));
            maxSpinner3.setValue(new Integer(0));
            minSpinner3.setEnabled(false);
            maxSpinner3.setEnabled(false);
        }
    }//GEN-LAST:event_jCheckBox3ActionPerformed

    private void jCheckBox2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBox2ActionPerformed
        if (jCheckBox2.isSelected()) {
            minSpinner2.setEnabled(true);
            maxSpinner2.setEnabled(true);
            minSpinner2.setValue(old2min);
            maxSpinner2.setValue(old2max);
        } else {
            old2min = (Integer)minSpinner2.getValue();
            old2max = (Integer)maxSpinner2.getValue();
            minSpinner2.setValue(new Integer(0));
            maxSpinner2.setValue(new Integer(0));
            minSpinner2.setEnabled(false);
            maxSpinner2.setEnabled(false);
        }
    }//GEN-LAST:event_jCheckBox2ActionPerformed

    private void jCheckBox1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBox1ActionPerformed
        if (jCheckBox1.isSelected()) {
            minSpinner1.setEnabled(true);
            maxSpinner1.setEnabled(true);
            minSpinner1.setValue(old1min);
            maxSpinner1.setValue(old1max);
        } else {
            old1min = (Integer)minSpinner1.getValue();
            old1max = (Integer)maxSpinner1.getValue();
            minSpinner1.setValue(new Integer(0));
            maxSpinner1.setValue(new Integer(0));
            minSpinner1.setEnabled(false);
            maxSpinner1.setEnabled(false);
        }
    }//GEN-LAST:event_jCheckBox1ActionPerformed

    private void jButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonActionPerformed
        doAll();
    }//GEN-LAST:event_jButtonActionPerformed

    private void rateSliderStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_rateSliderStateChanged
        riskFreeRate = (double)rateSlider.getValue();
    }//GEN-LAST:event_rateSliderStateChanged

    // find the efficient frontier
    private void runEfficient() {
        final int num=4;
        EfficientFrontier ef = new EfficientFrontier(num);

        // hard coded var-covar matrix and expected returns
        double[][] cov = new double[][] {{  0.002226421,   0.0000093276,   0.0000099678,   0.0000299572},
                                         {  0.0000093276,   0.003398130,   0.0000107892,   0.0000169680},
                                         {  0.0000099678,   0.0000107892,   0.001703816,   0.0000143299},
                                         {  0.0000299572,   0.0000169680,   0.0000143299,   0.004418127}};
        double[] x = new double[] {0.0009369806, 0.0016386612, 0.0003375174,  0.0005914344};
        for (int i=0; i<x.length; i++) {
           x[i] /= 2.0;
           cov[i][i] *= 20.0;
        }

        ef.setCovarianceMatrix(cov);
        ef.setExpectedReturns(x);

        double[][] b = new double[num][2];
        b[0][0] = ((Integer)minSpinner1.getValue()).intValue()/100.0;
        b[0][1] = ((Integer)maxSpinner1.getValue()).intValue()/100.0;
        b[1][0] = ((Integer)minSpinner2.getValue()).intValue()/100.0;
        b[1][1] = ((Integer)maxSpinner2.getValue()).intValue()/100.0;
        b[2][0] = ((Integer)minSpinner3.getValue()).intValue()/100.0;
        b[2][1] = ((Integer)maxSpinner3.getValue()).intValue()/100.0;
        b[3][0] = ((Integer)minSpinner4.getValue()).intValue()/100.0;
        b[3][1] = ((Integer)maxSpinner4.getValue()).intValue()/100.0;
        ef.setBounds(b);

        try {
            ef.compute();
        } catch (com.imsl.IMSLException ie) {
            System.out.println(ie.getMessage());
            ie.printStackTrace();
        }

        solution = ef.getSolution();
        // convert from daily fractional return to yearly percentage
        for (int i=0; i<solution[1].length; i++) {
            solution[0][i] *= 100.0;   // percentages
            solution[1][i] *= (365.0*100.0);  // daily data
            //solution[1][i] *= (12.0*100.0); //monthly data
        }
    }

    // with the efficient frontier and risk-free rate, find the index
    // of the optimal portfolio in the solution
    private int findOptimalIndex() {
        double slope, intercept, difference;
        double leastDifference = 1e6;
        int bestLocation = solution[0].length-1;

        for (int i=solution[0].length-2; i>1; i--) {
            slope = (solution[1][i+1]-solution[1][i-1])/(solution[0][i+1]-solution[0][i-1]);
            if (slope < 0.0) i=0;
            intercept = solution[1][i]-slope*solution[0][i];
            difference = Math.abs(intercept-riskFreeRate);
            if (difference < leastDifference) {
                leastDifference = difference;
                bestLocation = i;
            }
        }
        return bestLocation;
    }

    // draw the efficient frontier, risk-free rate, tangent line, and pie chart
    private void plotEfficientFrontier(double[] risk, double[] ret) {

        // draw the efficient frontier
        Data efData = new Data(axis,risk,ret);
        efData.setTitle("Efficient Frontier");
        efData.setDataType(Data.DATA_TYPE_LINE);
        efData.setLineColor(java.awt.Color.magenta);
        efData.setLineWidth(1.5);

        // plot the risk free rate
        Data rfData = new Data(axis, new double[] {0.0}, new double[] {riskFreeRate});
        rfData.setTitle("Risk Free Rate");
        rfData.setDataType(Data.DATA_TYPE_MARKER);
        rfData.setMarkerType(Data.MARKER_TYPE_FILLED_SQUARE);
        rfData.setMarkerColor(java.awt.Color.darkGray);

        // plot the line to the tangent
        double x = solution[0][optimalIndex];
        double y = solution[1][optimalIndex];
        final double slope = (y-riskFreeRate)/x;
        final double intercept = y-slope*x;
        ChartFunction tanFunc = new ChartFunction() {
            public double f(double x) {
                return slope*x + intercept;
            }
        };

        Data tanData = new Data(axis, tanFunc, 0, 20);//solution[0][solution[0].length-1]);
        tanData.setLineColor(java.awt.Color.cyan);

        tanData.setLineWidth(1.5);
        tanData.setLineDashPattern(new double[] {2,10});

        addPieChart();

        repaint();
    }

    // add the pie chart of solution weights
    private void addPieChart() {
        double y[] = {100*solution[2][optimalIndex],
                      100*solution[3][optimalIndex],
                      100*solution[4][optimalIndex],
                      100*solution[5][optimalIndex]};
        if (pie != null) pie.remove();
        pie = new Pie(chart, y);
        pie.setLabelType(Pie.LABEL_TYPE_TITLE);
        pie.setViewport(0.25, 0.55, 0.05, 0.35);

        PieSlice[] slice = pie.getPieSlice();
        slice[0].setTitle(names[0]);
        slice[0].setFillColor(darkGreen);
        slice[1].setTitle(names[1]);
        slice[1].setFillColor(java.awt.Color.red);
        slice[2].setTitle(names[2]);
        slice[2].setFillColor(java.awt.Color.blue);
        slice[3].setTitle(names[3]);
        slice[3].setFillColor(java.awt.Color.yellow);
    }

    // add the pie chart of solution weights
    private void addMiniPieChart(int pointIndex) {
        double y[] = {100*solution[2][pointIndex],
                      100*solution[3][pointIndex],
                      100*solution[4][pointIndex],
                      100*solution[5][pointIndex]};
        if (miniPie != null) miniPie.remove();
        miniPie = new Pie(chart, y);
        miniPie.setViewport(0.7, 0.85, 0.35, 0.5);

        PieSlice[] slice = miniPie.getPieSlice();
        slice[0].setFillColor(darkGreen);
        slice[1].setFillColor(java.awt.Color.red);
        slice[2].setFillColor(java.awt.Color.blue);
        slice[3].setFillColor(java.awt.Color.yellow);
    }

    // construct the summary text as plain text
    // no longer in use since solutionText became a JEditorPane
    private String buildPlainText() {
        java.text.DecimalFormat df = new java.text.DecimalFormat("###.##");
        StringBuffer sb = new StringBuffer();
        sb.append("   Parameters\t\tSolution\n");
        sb.append("   ----------\t\t--------\n");
        sb.append("   Risk Free Rate = "+rateSlider.getValue()+"\n");
        sb.append("   "+((Integer)minSpinner1.getValue()).intValue()+" < "+names[0]+" < "+((Integer)maxSpinner1.getValue()).intValue()+"\t");
        sb.append(names[0]+" = " + df.format(100*solution[2][optimalIndex]) + "%\n");

        sb.append("   "+((Integer)minSpinner2.getValue()).intValue()+" < "+names[1]+" < "+((Integer)maxSpinner2.getValue()).intValue()+"\t");
        sb.append(names[1]+" = " + df.format(100*solution[3][optimalIndex]) + "%\n");

        sb.append("   "+((Integer)minSpinner3.getValue()).intValue()+" < "+names[2]+"  < "+((Integer)maxSpinner3.getValue()).intValue()+"\t");
        sb.append(names[2]+" = " + df.format(100*solution[4][optimalIndex]) + "%\n");

        sb.append("   "+((Integer)minSpinner4.getValue()).intValue()+" < "+names[3]+"  < "+((Integer)maxSpinner4.getValue()).intValue()+"\t");
        sb.append(names[3]+" = " + df.format(100*solution[5][optimalIndex]) + "%\n");

        return sb.toString();
    }

    // construct the summary text as html
    private String buildHTMLText() {
        // note that solutionText.setFont should have worked above
        // but it didn't. To work around, use face="Courier New" in line
        java.text.DecimalFormat df = new java.text.DecimalFormat("###.##");
        StringBuffer sb = new StringBuffer();
        sb.append("&nbsp &nbsp &nbsp<font face=\"Courier New\"><U>Parameters</U>&#09<U>Solution</U></font><br>");
        sb.append("&nbsp &nbsp<font color=green face=\"Courier New\">"+((Integer)minSpinner1.getValue()).intValue()+" &lt "+names[0]+" &lt "+((Integer)maxSpinner1.getValue()).intValue()+"&#09");
        sb.append(names[0]+" = " + df.format(100*solution[2][optimalIndex]) + "%</font><br>");

        sb.append("&nbsp &nbsp<font color=red face=\"Courier New\">"+((Integer)minSpinner2.getValue()).intValue()+" &lt "+names[1]+" &lt "+((Integer)maxSpinner2.getValue()).intValue()+"&#09");
        sb.append(names[1]+" = " + df.format(100*solution[3][optimalIndex]) + "%</font><br>");

        sb.append("&nbsp &nbsp<font color=blue face=\"Courier New\">"+((Integer)minSpinner3.getValue()).intValue()+" &lt "+names[2]+" &lt "+((Integer)maxSpinner3.getValue()).intValue()+"&#09");
        sb.append(names[2]+" = " + df.format(100*solution[4][optimalIndex]) + "%</font><br>");

        sb.append("&nbsp &nbsp<font color=yellow face=\"Courier New\">"+((Integer)minSpinner4.getValue()).intValue()+" &lt "+names[3]+" &lt "+((Integer)maxSpinner4.getValue()).intValue()+"&#09");
        sb.append(names[3]+" = " + df.format(100*solution[5][optimalIndex]) + "%</font><br>");

        sb.append("&nbsp &nbsp<font face=\"Courier New\">Risk Free Rate = "+rateSlider.getValue()+"</font><br>");
        return sb.toString();
    }


    // make sure the spinners define a consistent problem
    private boolean checkSpinners() {
        if (((Integer)maxSpinner1.getValue()).intValue() < ((Integer)minSpinner1.getValue()).intValue()) {
            maxSpinner1.setValue((Integer)minSpinner1.getValue());
        }
        if (((Integer)maxSpinner2.getValue()).intValue() < ((Integer)minSpinner2.getValue()).intValue()) {
            maxSpinner2.setValue((Integer)minSpinner2.getValue());
        }
        if (((Integer)maxSpinner3.getValue()).intValue() < ((Integer)minSpinner3.getValue()).intValue()) {
            maxSpinner3.setValue((Integer)minSpinner3.getValue());
        }
        if (((Integer)maxSpinner4.getValue()).intValue() < ((Integer)minSpinner4.getValue()).intValue()) {
            maxSpinner4.setValue((Integer)minSpinner4.getValue());
        }
        int totalMin = ((Integer)minSpinner1.getValue()).intValue() + ((Integer)minSpinner2.getValue()).intValue() +
                       ((Integer)minSpinner3.getValue()).intValue() + ((Integer)minSpinner4.getValue()).intValue();
        int totalMax = ((Integer)maxSpinner1.getValue()).intValue() + ((Integer)maxSpinner2.getValue()).intValue() +
                       ((Integer)maxSpinner3.getValue()).intValue() + ((Integer)maxSpinner4.getValue()).intValue();
        if (totalMin > 100) {
            popWarning("This problem is infeasible because the\n"+
                       "minimum constraints total more than 100%");
            return false;
        } else if (totalMin == 100) {
            popWarning("This problem has a trivial solution because\n"+
                       "the sum of the minimum constraints equals 100%");
            return true;
        } else if (totalMax < 100) {
            popWarning("This problem is infeasible because the\n"+
                       "maximum constraints total less than 100%");
            return false;
        } else return true;
    }

    // pop up a warning box, called by checkSpinners()
    private void popWarning(String msg) {
        javax.swing.JOptionPane.showMessageDialog(this, msg,
            "Warning", javax.swing.JOptionPane.WARNING_MESSAGE);
    }

    // erase the existing data from the chart
    private void clearChart() {
        if (axis == null) return;
        ChartNode[] children = axis.getChildren();
        for (int i=0; i<children.length; i++) {
            if (children[i] instanceof Data) {
                children[i].remove();
            }
        }
    }

    /** The mouseDragged and mouseMoved methods are to
     * implement MouseMotionListener
     */
    public void mouseDragged(java.awt.event.MouseEvent e) {
    }

    // when user moves mouse over efficient frontier, display the portfolio
    public void mouseMoved(java.awt.event.MouseEvent e) {

          int currentIndex = -1;
        final int p = solution[0].length;
        final double[] eps = {1,1};
        final java.text.DecimalFormat df = new java.text.DecimalFormat("##");
        double[] user = new double[2];
        axis.mapDeviceToUser(e.getX(), e.getY(), user);
        double xRange[] = axis.getAxisX().getWindow();
        double yRange[] = {solution[1][0]-eps[1], solution[1][p-1]+eps[1]};

        // Return if outside axis
        if ((user[0] < xRange[0]) || (user[0] > xRange[1]) ||
            (user[1] < yRange[0]) || (user[1] > yRange[1])) {
            return;
        }

        // find the closest data point in Y
        double newDist;
        double dist = 10;
        int index = -1;
        for (int i=0; i<p; i++) {
            newDist = Math.abs(solution[1][i] - user[1]);
            if (newDist < dist) {
                dist = newDist;
                index = i;
            }
        }

        // now see if x is close enough
        if (Math.abs(solution[0][index] - user[0]) < eps[0]) {
               // only redraw if moved enough to be a different point
            if (index != oldIndex) {
                if (mousePoint != null) mousePoint.remove();
                if (miniPie != null) miniPie.remove();
                if (pointTip != null) pointTip.remove();
                mousePoint = new Data(axis, new double[] {solution[0][index]}, new double[] {solution[1][index]});
                mousePoint.setDataType(Data.DATA_TYPE_MARKER);
                mousePoint.setMarkerType(Data.MARKER_TYPE_CIRCLE_PLUS);
                mousePoint.setMarkerColor(java.awt.Color.magenta);
                mousePoint.setMarkerSize(2.0);
                String title = df.format(100*solution[2][index]) + ", " +
                               df.format(100*solution[3][index]) + ", " +
                               df.format(100*solution[4][index]) + ", " +
                               df.format(100*solution[5][index]);
                mousePoint.setTitle(title);
                pointTip = new ToolTip(mousePoint);
                addMiniPieChart(index);
                repaint();
            }
        } else {
            if (mousePoint != null) mousePoint.remove();
            if (miniPie != null) miniPie.remove();
            if (pointTip != null) pointTip.remove();
            repaint();
        }
        oldIndex = index;
    }

    /** mouseClicked, Enetered, Exited, Pressed, Released implement MouseListener
     */
    public void mouseClicked(java.awt.event.MouseEvent e) {
    }

    public void mouseEntered(java.awt.event.MouseEvent e) {
    }

    public void mouseExited(java.awt.event.MouseEvent e) {
          // clean up when cursor leaves the chart area
        if (mousePoint != null) mousePoint.remove();
        if (miniPie != null) miniPie.remove();
        if (pointTip != null) pointTip.remove();
        repaint();
    }

    public void mousePressed(java.awt.event.MouseEvent e) {
    }

    public void mouseReleased(java.awt.event.MouseEvent e) {
    }

    /** Exit the Application */
    private void exitForm(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_exitForm
        setVisible(false);
        dispose();
    }//GEN-LAST:event_exitForm

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        boolean exitOnClose = true;
        if (args.length > 0  && args[0].equals("-noexit"))  exitOnClose = false;
        new Portfolio(exitOnClose).show();
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton jButton;
    private javax.swing.JCheckBox jCheckBox1;
    private javax.swing.JCheckBox jCheckBox2;
    private javax.swing.JCheckBox jCheckBox3;
    private javax.swing.JCheckBox jCheckBox4;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel4;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JPanel jPanel3;
    private javax.swing.JPanel jPanel4;
    private javax.swing.JPanel jPanel5;
    private javax.swing.JPanel jPanel6;
    private javax.swing.JPanel jPanel8;
    private javax.swing.JPanel jPanelNorth;
    private javax.swing.JPanel jPanelSpinners;
    private javax.swing.JPanel jPanelLab;
    private javax.swing.JSpinner maxSpinner1;
    private javax.swing.JSpinner maxSpinner2;
    private javax.swing.JSpinner maxSpinner3;
    private javax.swing.JSpinner maxSpinner4;
    private javax.swing.JSpinner minSpinner1;
    private javax.swing.JSpinner minSpinner2;
    private javax.swing.JSpinner minSpinner3;
    private javax.swing.JSpinner minSpinner4;
    private javax.swing.JSlider rateSlider;
    private javax.swing.JEditorPane solutionText;
    // End of variables declaration//GEN-END:variables
}