import javax.swing.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.util.*; import java.io.*; import javax.swing.colorchooser.*; public class DartsApplet extends JApplet implements MouseMotionListener, ChangeListener { private JTextArea textArea; private JButton createHeatMapButton; private JLabel legendLabel; private JButton changeColorsButton; private DartsHeatMap heatMap; private JFrame changeColorsFrame; private JTextField regionField, expScoreField; private JCheckBox showBoardCheckBox, showArgmaxCheckBox; private JPanel gridPanel; private JTextField maxExpScoreField, aimForField; private JLabel sigmaXLabel, sigmaYLabel, rhoLabel; private JTextField sigmaXField, sigmaYField, rhoField; private JLabel numScoresLabel; private JRadioButton simpleButton, generalButton; private JProgressBar progressBar; private JLabel progressLabel; private boolean showBoard = true, showOptimum = true; private boolean simpleModel = true; public void init() { ///////////////////////////////// // the west panel ///////////////////////////////// // the instructions label JPanel instructionsPanel = new JPanel(); instructionsPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder("Instructions"), BorderFactory.createEmptyBorder(5,5,0,5))); JLabel instructionsLabel = new JLabel("" + "1. Throw 50 or so darts, aim-
    " + "ing at the double bullseye.

" + "2. Enter the scores of each
   throw below, " + "separated
   by commas.

" + "3. Click \"Create heat map!\"
   to see a " + "personalized
   heat map." + ""); instructionsPanel.add(instructionsLabel); // text area to enter the scores textArea = new JTextArea(); textArea.setLineWrap(true); JScrollPane scrollPane = new JScrollPane(textArea); scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); scrollPane.setPreferredSize(new Dimension(200,300)); scrollPane.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder("Scores"), BorderFactory.createEmptyBorder(5,5,5,5))); // create heat map button createHeatMapButton = new JButton("Create heat map!"); createHeatMapButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int r = createHeatMap(); if (r > 0) { String message = failMessages[r]; if (r == INVALID_NUMBERS_FAIL) { message += invalidNumbers; } JOptionPane.showMessageDialog(DartsApplet.this, message, "Error", JOptionPane.ERROR_MESSAGE); } } }); createHeatMapButton.setAlignmentX(JComponent.CENTER_ALIGNMENT); // put it together JPanel westPanel = new JPanel(); westPanel.setPreferredSize(new Dimension(200,500)); westPanel.setLayout(new BoxLayout(westPanel, BoxLayout.Y_AXIS)); westPanel.add(instructionsPanel); westPanel.add(scrollPane); westPanel.add(Box.createVerticalStrut(5)); westPanel.add(createHeatMapButton); westPanel.add(Box.createVerticalStrut(10)); add(westPanel, BorderLayout.WEST); ///////////////////////////////// // the main panel ///////////////////////////////// // legend JPanel legendPanel = new JPanel(); legendPanel.setLayout(new BoxLayout(legendPanel, BoxLayout.Y_AXIS)); legendLabel = new JLabel(); legendLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK)); legendLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT); legendPanel.add(legendLabel); JLabel lowHighLabel = new JLabel("Low High"); lowHighLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT); legendPanel.add(lowHighLabel); // change colors changeColorsButton = new JButton("Change"); changeColorsButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changeColorsFrame.setVisible(true); } }); buildChangeColorsFrame(); JPanel changeColorsPanel = new JPanel(); changeColorsPanel.setLayout(new BoxLayout(changeColorsPanel, BoxLayout.Y_AXIS)); changeColorsPanel.add(changeColorsButton); changeColorsPanel.add(new JLabel(" ")); // heat map heatMap = new DartsHeatMap(); heatMap.setPreferredSize(new Dimension((int)(2*Stats.R),(int)(2*Stats.R))); heatMap.setBorder(BorderFactory.createLineBorder(Color.BLACK)); // pay attention to the mouse heatMap.addMouseMotionListener(this); // a bit of a hack to make sure that the heat map size doesn't change JPanel heatMapPanel = new JPanel(); heatMapPanel.add(heatMap); // set the color scheme setColorScheme(CLASSIC); JPanel colorSchemePanel = new JPanel(); colorSchemePanel.add(new JLabel("Color legend:
 ")); colorSchemePanel.add(legendPanel); colorSchemePanel.add(Box.createHorizontalStrut(1)); colorSchemePanel.add(changeColorsPanel); // mouse info panel JPanel mouseInfoPanel = new JPanel(); mouseInfoPanel.add(new JLabel("Region:")); regionField = new JTextField(3); regionField.setEditable(false); mouseInfoPanel.add(regionField); mouseInfoPanel.add(Box.createHorizontalStrut(10)); mouseInfoPanel.add(new JLabel("Expected score:")); expScoreField = new JTextField(4); expScoreField.setEditable(false); mouseInfoPanel.add(expScoreField); // options panel JPanel optionsPanel = new JPanel(); showBoardCheckBox = new JCheckBox(); showBoardCheckBox.setSelected(showBoard); showBoardCheckBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { showBoard = !showBoard; heatMap.repaint(); } }); optionsPanel.add(showBoardCheckBox); optionsPanel.add(new JLabel("Show dartboard")); optionsPanel.add(Box.createHorizontalStrut(10)); showArgmaxCheckBox = new JCheckBox(); showArgmaxCheckBox.setSelected(showOptimum); showArgmaxCheckBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { showOptimum = !showOptimum; if (E != null) { // smart repaint (for speed!) // assume 1 mm per pixel heatMap.repaint(0,imax-3,(int)(2*Stats.R)-1-jmax-3,6,6); } } }); optionsPanel.add(showArgmaxCheckBox); optionsPanel.add(new JLabel("Show optimum place to aim")); // put it all together JPanel mainPanel = new JPanel(); mainPanel.setPreferredSize(new Dimension(400,500)); mainPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder("Heat map"), BorderFactory.createEmptyBorder(5,5,5,5))); mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); mainPanel.add(colorSchemePanel); mainPanel.add(heatMapPanel); mainPanel.add(mouseInfoPanel); mainPanel.add(Box.createVerticalGlue()); mainPanel.add(optionsPanel); add(mainPanel, BorderLayout.CENTER); ///////////////////////////////// // the east panel ///////////////////////////////// // the help panel JPanel helpPanel = new JPanel(); helpPanel.setPreferredSize(new Dimension(200,180)); helpPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder("Help"), BorderFactory.createEmptyBorder(5,5,0,5))); helpPanel.add(new JLabel("" + "First look at the color legend
to see which colors " + "corres-
pond to high expected scores.
Then look at the heat " + "map
to see which aiming locations
are assigned these " + "colors.
" + "(Note: the \u03c3 parameters are
measured " + "in mm; for refer-
ence, the dartboard's " + "radius
is 170 mm.)" + "")); // the stats panel JPanel statsPanel = new JPanel(); statsPanel.setPreferredSize(new Dimension(200,240)); statsPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder("Stats"), BorderFactory.createEmptyBorder(5,5,0,5))); // the stats maxExpScoreField = new JTextField(4); maxExpScoreField.setEditable(false); maxExpScoreField.setForeground(Color.RED); aimForField = new JTextField(4); aimForField.setEditable(false); aimForField.setForeground(Color.RED); sigmaXLabel = new JLabel("\u03c3 = "); sigmaXField = new JTextField(4); sigmaXField.setEditable(false); sigmaXField.setForeground(Color.RED); sigmaYLabel = new JLabel("\u03c3_y = "); sigmaYField = new JTextField(4); sigmaYField.setEditable(false); sigmaYField.setForeground(Color.RED); rhoLabel = new JLabel("\u03c1 = "); rhoField = new JTextField(4); rhoField.setEditable(false); rhoField.setForeground(Color.RED); gridPanel = new JPanel(); gridPanel.setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.LINE_START; c.gridx = 0; c.gridy = 0; gridPanel.add(new JLabel("Max expected score: "),c); c.gridx = 1; c.gridy = 0; gridPanel.add(maxExpScoreField,c); c.gridx = 0; c.gridy = 1; gridPanel.add(new JLabel("Aim for: "),c); c.gridx = 1; c.gridy = 1; gridPanel.add(aimForField,c); c.gridx = 0; c.gridy = 2; gridPanel.add(sigmaXLabel,c); c.gridx = 1; c.gridy = 2; gridPanel.add(sigmaXField,c); // number of scores label numScoresLabel = new JLabel(" 
 "); c.insets = new Insets(7,0,0,0); c.gridx = 0; c.gridy = 7; c.gridwidth=2; gridPanel.add(numScoresLabel,c); // simple or general button simpleButton = new JRadioButton("Simple model"); simpleButton.setSelected(true); simpleButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (!simpleModel) { simpleModel = true; sigmaXLabel.setText("\u03c3 = "); // clear all the fields maxExpScoreField.setText(""); aimForField.setText(""); sigmaXField.setText(""); sigmaYField.setText(""); rhoField.setText(""); // rearrange the grid panel gridPanel.remove(sigmaYLabel); gridPanel.remove(sigmaYField); gridPanel.remove(rhoLabel); gridPanel.remove(rhoField); // clear the num scores label numScoresLabel.setText(""); } } }); generalButton = new JRadioButton("General model"); generalButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (simpleModel) { simpleModel = false; sigmaXLabel.setText("\u03c3_x = "); // clear all the fields maxExpScoreField.setText(""); aimForField.setText(""); sigmaXField.setText(""); sigmaYField.setText(""); rhoField.setText(""); // rearrange the grid panel GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.LINE_START; c.gridx = 0; c.gridy = 3; gridPanel.add(sigmaYLabel,c); c.gridx = 1; c.gridy = 3; gridPanel.add(sigmaYField,c); c.gridx = 0; c.gridy = 4; gridPanel.add(rhoLabel,c); c.gridx = 1; c.gridy = 4; gridPanel.add(rhoField,c); // clear the num scores label numScoresLabel.setText(""); } } }); ButtonGroup buttonGroup = new ButtonGroup(); buttonGroup.add(simpleButton); buttonGroup.add(generalButton); c.insets = new Insets(7,0,0,0); c.gridx = 0; c.gridy = 8; c.gridwidth=2; gridPanel.add(simpleButton,c); c.insets = new Insets(0,0,0,0); c.gridx = 0; c.gridy = 9; c.gridwidth=2; gridPanel.add(generalButton,c); statsPanel.add(gridPanel); // the progress panel JPanel progressPanel = new JPanel(); progressPanel.setPreferredSize(new Dimension(200,70)); progressPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder("Progress"), BorderFactory.createEmptyBorder(5,5,5,5))); progressPanel.setLayout(new BoxLayout(progressPanel, BoxLayout.Y_AXIS)); // the progress label and bar progressLabel = new JLabel(" 
 "); progressPanel.add(progressLabel); progressBar = new JProgressBar(0,112); progressBar.setValue(0); progressBar.setStringPainted(true); JPanel progressBarPanel = new JPanel(); progressBarPanel.add(progressBar); progressPanel.add(progressBarPanel); // put it together JPanel eastPanel = new JPanel(); eastPanel.setPreferredSize(new Dimension(200,500)); eastPanel.setLayout(new BoxLayout(eastPanel, BoxLayout.Y_AXIS)); eastPanel.add(helpPanel); eastPanel.add(statsPanel); eastPanel.add(progressPanel); add(eastPanel, BorderLayout.EAST); } // pay attention to the mouse over the heat map // (we have to implement MouseMotionListener and add this as a separate function, // instead of the usual way, because otherwise this won't run on a JVM <= 1.5) public void mouseMoved(MouseEvent e) { if (E != null) { // assume 1 mm per pixel int i = e.getX(); int j = (int)(2*Stats.R)-1-e.getY(); int n = E.length; regionField.setText(Stats.getRegion(i,j,n,n)); if (0 <= i && i < n && 0 <= j && j < n) { expScoreField.setText(Double.toString(roundTo2(E[i][j]))); } else { expScoreField.setText("-----"); } } } public void mouseDragged(MouseEvent e) {} private static double roundTo2(double d) { return Math.round(100*d)/100.0; } // the frame to change the color scheme JRadioButton classicButton, grayscaleButton, rainbowButton, customButton; JLabel[] customLabels; int numCustomColors; JColorChooser chooser; private void buildChangeColorsFrame() { changeColorsFrame = new JFrame("Change colors"); JPanel choosePanel = new JPanel(); choosePanel.add(new JLabel("Choose a color scheme:")); // classic classicButton = new JRadioButton("Classic"); classicButton.setSelected(true); JPanel classicPreview = new JPanel(); JLabel[] classicLabels = new JLabel[CLASSIC.length]; for (int i=0; i max) max = E[i][j]; } } // draw the heat map for (int i=0; i 0) return INVALID_NUMBERS_FAIL; // everything passed, so make a new task task = new StatsTask(scores); task.start(); return 0; } private static void checkNumbers(int[] scores) { Set invalids = new TreeSet(); boolean atCapacity = false; for (int i=0; i it = invalids.iterator(); StringBuffer sb = new StringBuffer(); while (it.hasNext()) { sb.append(Integer.toString(it.next())+", "); } if (sb.length() > 0) { // remove the last ", " sb.delete(sb.length()-2,sb.length()); if (atCapacity) sb.append(" ..."); else sb.append("."); } invalidNumbers = sb.toString(); } private double[][] E; private double s1, s2, s3; private int imax, jmax; private class StatsTask extends Thread { private int[] scores; public StatsTask(int[] scores) { this.scores = new int[scores.length]; for (int i=0; iRunning EM
algorithm..."); s1 = Stats.simpleEM(scores,DartsApplet.this); // compute the heat map updateProgress("Computing expected
scores..."); E = Stats.computeExpScores(s1,nn,DartsApplet.this); } else { // we're going to use the general model // run the EM algorithm updateProgress("Running EM
algorithm..."); double[] a = Stats.generalEM(scores,DartsApplet.this); s1=a[0]; s2=a[1]; s3=a[2]; // compute the heat map updateProgress("Computing expected
scores..."); E = Stats.computeExpScores(s1,s2,s3,nn,DartsApplet.this); } // tell the heat map to draw itself updateProgress("Drawing the
heat map..."); heatMap.clear(); heatMap.repaint(); // update all the labels double[] b = Stats.getMaxAndArgmax(E); double max = b[0]; imax = (int)b[1]; jmax = (int)b[2]; String aimStr = Stats.getRegion(imax,jmax,nn,nn); int n = scores.length; maxExpScoreField.setText(Double.toString(roundTo2(max))); aimForField.setText(aimStr); if (simpleModel) { sigmaXField.setText(Double.toString(roundTo2(Math.sqrt(s1)))); sigmaYField.setText(" "); rhoField.setText(" "); } else { sigmaXField.setText(Double.toString(roundTo2(Math.sqrt(s1)))); sigmaYField.setText(Double.toString(roundTo2(Math.sqrt(s2)))); rhoField.setText(Double.toString(roundTo2(s3/Math.sqrt(s1*s2)))); } numScoresLabel.setText("Computed based on
" + n + " score" + ((n>1)?"s":"") + "."); updateProgress(112); updateProgress(" 
Done."); enableAll(true); } } private void enableAll(boolean b) { createHeatMapButton.setEnabled(b); changeColorsButton.setEnabled(b); showBoardCheckBox.setEnabled(b); showArgmaxCheckBox.setEnabled(b); simpleButton.setEnabled(b); generalButton.setEnabled(b); if (b) heatMap.addMouseMotionListener(this); if (!b) heatMap.removeMouseMotionListener(this); } public void updateProgress(int progress) { progressBar.setValue(progress); } public void updateProgress(String progressString) { progressLabel.setText(progressString); } public void start() { } public void stop() { } public void destroy() { } }