/* * Copyright (c) 2009 The Jackson Laboratory * * This software was developed by Gary Churchill's Lab at The Jackson * Laboratory (see http://research.jax.org/faculty/churchill). * * This 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 3 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software. If not, see <http://www.gnu.org/licenses/>. */ package org.jax.qtl.scan.gui; import java.awt.Dimension; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; import javax.swing.JOptionPane; import javax.swing.JTable; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import org.jax.qtl.cross.Cross; import org.jax.qtl.cross.CrossChromosome; import org.jax.qtl.cross.GeneticMarker; import org.jax.qtl.cross.QtlBasket; import org.jax.qtl.cross.QtlBasketItem; import org.jax.qtl.cross.SingleMarkerQtlBasketItem; import org.jax.qtl.scan.ScanCommandBuilder; import org.jax.util.TextWrapper; import org.jax.util.gui.CanDisableContentsTableCellRenderer; import org.jax.util.gui.CheckableListTableModel; /** * A small panel for editing covariates. * @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A> */ public class ChromosomesAndCovariatesPanel extends ScanCommandEditorPanel { /** * every {@link java.io.Serializable} is supposed to have one of these */ private static final long serialVersionUID = 2159225430604268002L; /** * our logger */ private static final Logger LOG = Logger.getLogger( ChromosomesAndCovariatesPanel.class.getName()); private final ScanCommandBuilder scanCommand; private volatile Cross cross; private CheckableListTableModel covariatesTableModel; private static final int CHROMOSOME_NAME_COLUMN = 1; private static final int COVARIATE_NAME_COLUMN = 2; private static final int ADDITIVE_COLUMN = 0; private static final int INTERACTIVE_COLUMN = 1; private final TableModelListener covariatesTableListener = new TableModelListener() { public void tableChanged(TableModelEvent e) { ChromosomesAndCovariatesPanel.this.covariatesTableUpdated(e); } }; private volatile CheckableListTableModel chromosomeTableModel; /** * Constructor * @param scanCommand * the scan command whose covariate parameters this dialog edits */ public ChromosomesAndCovariatesPanel( ScanCommandBuilder scanCommand) { this.scanCommand = scanCommand; this.initComponents(); this.postGuiInit(); } /** * take care of the initialization that the GUI builder doesn't do */ private void postGuiInit() { this.chromosomeTableModel = ChromosomesAndCovariatesPanel.initializeChromoTable(this.chromosomesTable); this.chromosomeTableModel.addTableModelListener( new TableModelListener() { public void tableChanged(TableModelEvent e) { if(e.getType() == TableModelEvent.UPDATE) { ChromosomesAndCovariatesPanel.this.chromosomesToScanWasUpdated(); } } }); this.initializeCovariatesTable(); } /** * Validate the data contained in this panel. The user is alerted if * there is something wrong with the data format * @return * true iff the validation succeeds */ public boolean validateData() { String message = null; String[] chromosomeNames = this.scanCommand.getChromosomeNames(); if(chromosomeNames == null || chromosomeNames.length == 0) { message = "Please select at least one chromosome to " + "scan, or cancel the scan operation"; } if(message != null) { JOptionPane.showMessageDialog( this, TextWrapper.wrapText( message, TextWrapper.DEFAULT_DIALOG_COLUMN_COUNT), "Validation Failed", JOptionPane.WARNING_MESSAGE); return false; } else { return true; } } /** * Method for responding to a covariates update event * @param event * the event */ private void covariatesTableUpdated(TableModelEvent event) { // enforce additive selection when interactive selection happens if(event.getType() == TableModelEvent.UPDATE) { if(event.getFirstRow() == event.getLastRow() && event.getFirstRow() != TableModelEvent.HEADER_ROW) { if(event.getColumn() == INTERACTIVE_COLUMN) { Boolean interactiveValue = (Boolean)this.covariatesTableModel.getValueAt( event.getFirstRow(), event.getColumn()); if(interactiveValue.booleanValue()) { this.covariatesTableModel.setValueAt( Boolean.TRUE, event.getFirstRow(), ADDITIVE_COLUMN); } } else if(event.getColumn() == ADDITIVE_COLUMN) { Boolean additiveValue = (Boolean)this.covariatesTableModel.getValueAt( event.getFirstRow(), event.getColumn()); if(!additiveValue.booleanValue()) { this.covariatesTableModel.setValueAt( Boolean.FALSE, event.getFirstRow(), INTERACTIVE_COLUMN); } } } } int rowCount = this.covariatesTableModel.getRowCount(); List<String> additivePhenoCovariatesList = new ArrayList<String>(); List<String> interactivePhenoCovariatesList = new ArrayList<String>(); List<GeneticMarker> additiveGenoCovariatesList = new ArrayList<GeneticMarker>(); List<GeneticMarker> interactiveGenoCovariatesList = new ArrayList<GeneticMarker>(); for(int currRow = 0; currRow < rowCount; currRow++) { Boolean interactive = (Boolean)this.covariatesTableModel.getValueAt(currRow, INTERACTIVE_COLUMN); Boolean additive = (Boolean)this.covariatesTableModel.getValueAt(currRow, ADDITIVE_COLUMN); Object covariate = this.covariatesTableModel.getValueAt(currRow, COVARIATE_NAME_COLUMN); if(covariate instanceof String) { String phenotypeName = (String)covariate; if(interactive.booleanValue()) { interactivePhenoCovariatesList.add(phenotypeName); additivePhenoCovariatesList.add(phenotypeName); } else if(additive.booleanValue()) { additivePhenoCovariatesList.add(phenotypeName); } } else { GeneticMarker markerCovariate = (GeneticMarker)covariate; if(interactive.booleanValue()) { interactiveGenoCovariatesList.add(markerCovariate); additiveGenoCovariatesList.add(markerCovariate); } else if(additive.booleanValue()) { additiveGenoCovariatesList.add(markerCovariate); } } } String[] additivePhenoCovariates = additivePhenoCovariatesList.toArray(new String[additivePhenoCovariatesList.size()]); this.scanCommand.setAdditivePhenotypeCovariates(additivePhenoCovariates); String[] interactivePhenoCovariates = interactivePhenoCovariatesList.toArray(new String[interactivePhenoCovariatesList.size()]); this.scanCommand.setInteractivePhenotypeCovariates(interactivePhenoCovariates); this.scanCommand.setAdditiveGenotypeCovariates(additiveGenoCovariatesList); this.scanCommand.setInteractiveGenotypeCovariates(interactiveGenoCovariatesList); this.fireCommandModified(); } private void initializeCovariatesTable() { this.covariatesTableModel = new CheckableListTableModel(2); this.covariatesTableModel.addTableModelListener( this.covariatesTableListener); this.covariatesTableModel.setColumnIdentifiers( new String[] {"Additive", "Interactive", "Covariate"}); this.selectCovariatesTabel.setModel(this.covariatesTableModel); TableColumnModel chromoOrPhenoColumnModel = this.selectCovariatesTabel.getColumnModel(); TableColumn addColumn = chromoOrPhenoColumnModel.getColumn(ADDITIVE_COLUMN); addColumn.setCellRenderer(new CanDisableContentsTableCellRenderer( addColumn.getCellRenderer())); TableColumn interactColumn = chromoOrPhenoColumnModel.getColumn(INTERACTIVE_COLUMN); interactColumn.setCellRenderer(new CanDisableContentsTableCellRenderer( interactColumn.getCellRenderer())); TableColumn nameColumn = chromoOrPhenoColumnModel.getColumn(COVARIATE_NAME_COLUMN); nameColumn.setCellRenderer(new CanDisableContentsTableCellRenderer( nameColumn.getCellRenderer())); } /** * Build the table that holds the chromosomes we can scan * @param crossToBuildFrom * the cross for us to scan */ private void buildChromosomeTable(Cross crossToBuildFrom) { // update the chromosome stuff List<CrossChromosome> chromosomes = crossToBuildFrom.getGenotypeData(); if(chromosomes == null) { LOG.severe("unexpected condition: chromosomes are null"); return; } else if(chromosomes.size() == 0) { LOG.severe("unexpected condition: chromosomes are empty"); return; } CheckableListTableModel chromoTableModel = (CheckableListTableModel)this.chromosomesTable.getModel(); chromoTableModel.setRowCount(0); for(CrossChromosome currChromosome: chromosomes) { chromoTableModel.addRow(new Object[] { Boolean.FALSE, currChromosome.getChromosomeName()}); } this.chromosomesToScanWasUpdated(); } /** * Respond to a change in chromosome-to-scan selection */ private void chromosomesToScanWasUpdated() { // throw in selected chromosome names int chromosomeRows = this.chromosomesTable.getRowCount(); List<String> chromosomeNameList = new ArrayList<String>( chromosomeRows); for(int currRow = 0; currRow < chromosomeRows; currRow++) { Boolean selected = (Boolean)this.chromosomesTable.getValueAt(currRow, 0); if(selected.booleanValue()) { String chromosomeName = (String)this.chromosomeTableModel.getValueAt( currRow, CHROMOSOME_NAME_COLUMN); chromosomeNameList.add(chromosomeName); } } this.scanCommand.setChromosomeIndices( chromosomeNameList.toArray(new String[chromosomeNameList.size()])); this.fireCommandModified(); } /** * A little utility method that is used to initialize the chromosome * table model * @param chromoTable * the table that we're initializing * @return * the table model */ private static CheckableListTableModel initializeChromoTable(JTable chromoTable) { CheckableListTableModel chromoTableModel = new CheckableListTableModel(1); chromoTable.setModel(chromoTableModel); chromoTable.getTableHeader().setVisible(false); chromoTable.getTableHeader().setPreferredSize( new Dimension(0, 0)); TableColumnModel chromoColumnModel = chromoTable.getColumnModel(); TableColumn checkColumn = chromoColumnModel.getColumn(0); checkColumn.setPreferredWidth(0); checkColumn.setCellRenderer(new CanDisableContentsTableCellRenderer( checkColumn.getCellRenderer())); TableColumn labelColumn = chromoColumnModel.getColumn(1); labelColumn.setCellRenderer(new CanDisableContentsTableCellRenderer( labelColumn.getCellRenderer())); labelColumn.setPreferredWidth(1000); return chromoTableModel; } /** * Refresh the GUI by adding or removing covariates from the table * based on which ones are valid or not. */ public void refreshGui() { if(this.cross != this.scanCommand.getCross()) { // start over this.cross = this.scanCommand.getCross(); // update chromosome table this.buildChromosomeTable(this.cross); this.chromosomeTableModel.setAllCheckBoxValuesTo(true); // now update covariates this.covariatesTableModel.setRowCount(0); this.qtlBasketComboBox.removeAllItems(); if(this.cross != null) { QtlBasket[] baskets = this.cross.getQtlBaskets(); if(baskets.length == 0) { this.qtlBasketComboBox.addItem( "No QTL Baskets for " + this.cross); } else { for(QtlBasket qtlBasket: baskets) { this.qtlBasketComboBox.addItem(qtlBasket); } } } } if(this.cross != null) { // update the table String[] allPhenotypeNames = this.cross.getPhenotypeData().getDataNames(); int[] phenoIndicesWeAreAlreadyScanning = this.scanCommand.getPhenotypeIndices(); List<String> phenotypeNamesToAdd = new ArrayList<String>( Arrays.asList(allPhenotypeNames)); // cull out the phenotype names that are in the scan phenos already // we have to count in reverse here or things will get messed up for(int i = phenoIndicesWeAreAlreadyScanning.length - 1; i >= 0; i--) { phenotypeNamesToAdd.remove(phenoIndicesWeAreAlreadyScanning[i]); } // subtract the rows that don't belong (again, go in reverse) int rowCount = this.covariatesTableModel.getRowCount(); for(int currRow = rowCount - 1; currRow >= 0; currRow--) { Object tableCovariate = this.covariatesTableModel.getValueAt( currRow, COVARIATE_NAME_COLUMN); if(tableCovariate instanceof String) { String tableCovariateString = (String)this.covariatesTableModel.getValueAt( currRow, COVARIATE_NAME_COLUMN); boolean removeSuccessfull = phenotypeNamesToAdd.remove( tableCovariateString); if(!removeSuccessfull) { this.covariatesTableModel.removeRow(currRow); } } } // add the rows that were not there for(String currPhenoName: phenotypeNamesToAdd) { this.covariatesTableModel.addRow(new Object[] { Boolean.FALSE, Boolean.FALSE, currPhenoName}); } } } /** * Respond to a change in the selected qtl basket */ private void selectedQtlBasketChanged() { // clear out all the existing markers int numTableRows = this.covariatesTableModel.getRowCount(); for(int rowIndex = numTableRows - 1; rowIndex >= 0; rowIndex--) { Object rowValue = this.covariatesTableModel.getValueAt( rowIndex, COVARIATE_NAME_COLUMN); if(rowValue instanceof GeneticMarker) { this.covariatesTableModel.removeRow(rowIndex); } } QtlBasket selectedQtlBasket = this.getSelectedQtlBasket(); if(selectedQtlBasket != null && this.cross != null) { // create a set of all non-imputed markers Set<String> nonImputedMarkerNames = new HashSet<String>(); List<CrossChromosome> allChromosomes = this.cross.getGenotypeData(); for(CrossChromosome crossChromosome: allChromosomes) { for(GeneticMarker currMarker: crossChromosome.getAnyGeneticMap().getMarkerPositions()) { nonImputedMarkerNames.add(currMarker.getMarkerName()); } } for(QtlBasketItem currItem: selectedQtlBasket.getContents()) { if(currItem instanceof SingleMarkerQtlBasketItem) { SingleMarkerQtlBasketItem currSingleMarkerItem = (SingleMarkerQtlBasketItem)currItem; GeneticMarker currMarker = currSingleMarkerItem.getMarker(); if(nonImputedMarkerNames.contains(currMarker.getMarkerName())) { // since it's a non-imputed single marker it's OK // to act as a covariate LOG.fine("adding marker covariate: " + currMarker); this.covariatesTableModel.addRow(new Object[] { Boolean.FALSE, Boolean.FALSE, currMarker}); } else { LOG.fine( "skipping marker covariate because it looks " + "like it's imputed: " + currMarker); } } else { LOG.fine( "skipping QTL basket item as covariate because " + "the type isn't right: " + currItem); } } } } private QtlBasket getSelectedQtlBasket() { Object selectedQtlItem = this.qtlBasketComboBox.getSelectedItem(); if(selectedQtlItem instanceof QtlBasket) { return (QtlBasket)selectedQtlItem; } else { return null; } } /** 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. */ @SuppressWarnings("all") // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { qtlBasketLabel = new javax.swing.JLabel(); qtlBasketComboBox = new javax.swing.JComboBox(); selectCovariatesLabel = new javax.swing.JLabel(); selectCovariatesScrollPanel = new javax.swing.JScrollPane(); selectCovariatesTabel = new javax.swing.JTable(); chromosomesToScanLabel = new javax.swing.JLabel(); toggleSelectAllChromosomesButton = new javax.swing.JButton(); chromosomesScrollPanel = new javax.swing.JScrollPane(); chromosomesTable = new javax.swing.JTable(); qtlBasketLabel.setText("Include QTL's From:"); qtlBasketComboBox.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { qtlBasketComboBoxItemStateChanged(evt); } }); selectCovariatesLabel.setText("Select Additive and Interactive Covariates:"); selectCovariatesScrollPanel.setMinimumSize(new java.awt.Dimension(23, 100)); selectCovariatesScrollPanel.setViewportView(selectCovariatesTabel); chromosomesToScanLabel.setText("Chromosomes to Scan:"); toggleSelectAllChromosomesButton.setText("Toggle Select All"); toggleSelectAllChromosomesButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { toggleSelectAllChromosomesButtonActionPerformed(evt); } }); chromosomesScrollPanel.setMinimumSize(new java.awt.Dimension(50, 23)); chromosomesScrollPanel.setViewportView(chromosomesTable); org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(layout.createSequentialGroup() .addContainerGap() .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(layout.createSequentialGroup() .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(selectCovariatesScrollPanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 403, Short.MAX_VALUE) .add(layout.createSequentialGroup() .add(qtlBasketLabel) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(qtlBasketComboBox, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) .add(selectCovariatesLabel)) .addContainerGap()) .add(layout.createSequentialGroup() .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(chromosomesScrollPanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE) .add(layout.createSequentialGroup() .add(chromosomesToScanLabel) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(toggleSelectAllChromosomesButton))) .add(23, 23, 23)))) ); layout.setVerticalGroup( layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup() .add(25, 25, 25) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(chromosomesToScanLabel) .add(toggleSelectAllChromosomesButton)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(chromosomesScrollPanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 128, Short.MAX_VALUE) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(selectCovariatesLabel) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(qtlBasketLabel) .add(qtlBasketComboBox, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(selectCovariatesScrollPanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 121, Short.MAX_VALUE) .addContainerGap()) ); }// </editor-fold>//GEN-END:initComponents private void qtlBasketComboBoxItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_qtlBasketComboBoxItemStateChanged this.selectedQtlBasketChanged(); }//GEN-LAST:event_qtlBasketComboBoxItemStateChanged private void toggleSelectAllChromosomesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_toggleSelectAllChromosomesButtonActionPerformed this.chromosomeTableModel.toggleSelectAllCheckBoxes(); }//GEN-LAST:event_toggleSelectAllChromosomesButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JScrollPane chromosomesScrollPanel; private javax.swing.JTable chromosomesTable; private javax.swing.JLabel chromosomesToScanLabel; private javax.swing.JComboBox qtlBasketComboBox; private javax.swing.JLabel qtlBasketLabel; private javax.swing.JLabel selectCovariatesLabel; private javax.swing.JScrollPane selectCovariatesScrollPanel; private javax.swing.JTable selectCovariatesTabel; private javax.swing.JButton toggleSelectAllChromosomesButton; // End of variables declaration//GEN-END:variables /** * Determines how many covariates are available for the user to select * @return * the count */ public int getCandidateCovariateCount() { return this.covariatesTableModel.getRowCount(); } /** * {@inheritDoc} */ @Override protected ScanCommandBuilder getScanCommand() { return this.scanCommand; } }