/* * 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.cross.gui; import java.awt.event.ActionEvent; import java.util.Arrays; import java.util.List; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.AbstractAction; import org.jax.analyticgraph.framework.Graph2DComponent; import org.jax.analyticgraph.framework.SimpleGraphCoordinateConverter; import org.jax.analyticgraph.graph.AxisRenderingGraph; import org.jax.qtl.QTL; import org.jax.qtl.cross.Cross; import org.jax.qtl.cross.EffectPlotCommandBuilder; import org.jax.qtl.cross.GeneticMarker; import org.jax.qtl.cross.gui.EffectPlot.EffectPlotData; import org.jax.qtl.cross.gui.EffectPlot.EffectPlotDataPoint; import org.jax.qtl.gui.SimpleGraphContainerPanel; import org.jax.r.RAssignmentCommand; import org.jax.r.RCommand; import org.jax.r.jriutilities.JRIUtilityFunctions; import org.jax.r.jriutilities.RInterface; import org.jax.r.jriutilities.RInterfaceFactory; import org.jax.r.jriutilities.RObject; import org.jax.r.jriutilities.SilentRCommand; import org.jax.util.gui.desktoporganization.Desktop; import org.rosuda.JRI.REXP; /** * Action class for showing effect plots * @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A> */ public class ShowEffectPlotAction extends AbstractAction { /** * every {@link java.io.Serializable} is supposed to have one of these */ private static final long serialVersionUID = 1231114942692497182L; /** * our logger */ private static final Logger LOG = Logger.getLogger( ShowEffectPlotAction.class.getName()); /** * Anything followed by a final dot separator followed by anything */ private static final Pattern EFFECT_DATA_NAME_PATTERN = Pattern.compile("(.*)\\.(.*)"); /** * We need a variable name that we can use as a temporary place holder * for effect plot data */ private static final String TEMP_EFFECT_PLOT_DATA_ACCESSOR = "temp.effect.plot.data"; private static final String EFFECT_MEANS_SUFFIX = "$Means"; private static final String EFFECT_STD_ERROR_SSUFFIX = "$SEs"; private final EffectPlotCommandBuilder effectPlotCommandBuilder; private final String phenotypeName; /** * Constructor * @param cross * the cross * @param phenotypeName * the index of the phenotype we're showing an effect for * @param marker * the genetic marker that we're showing an effect for */ public ShowEffectPlotAction( Cross cross, String phenotypeName, GeneticMarker marker) { super("Show Effect Plot for " + marker.getMarkerName() + " ..."); this.phenotypeName = phenotypeName; this.effectPlotCommandBuilder = new EffectPlotCommandBuilder(); this.effectPlotCommandBuilder.setCross(cross); this.effectPlotCommandBuilder.setFirstMarker(marker); List<String> allPhenotypeNames = Arrays.asList(cross.getPhenotypeData().getDataNames()); this.effectPlotCommandBuilder.setPhenotypeIndex( allPhenotypeNames.indexOf(phenotypeName)); } /** * Constructor * @param cross * the cross * @param phenotypeName * the index of the phenotype we're showing an effect for * @param marker1 * the 1st genetic marker that we're showing an effect for * @param marker2 * the 2nd genetic marker that we're showing an effect for */ public ShowEffectPlotAction( Cross cross, String phenotypeName, GeneticMarker marker1, GeneticMarker marker2) { super("Show Effect Plot for " + marker1.getMarkerName() + " and " + marker2.getMarkerName() + " ..."); this.phenotypeName = phenotypeName; this.effectPlotCommandBuilder = new EffectPlotCommandBuilder(); this.effectPlotCommandBuilder.setCross(cross); this.effectPlotCommandBuilder.setFirstMarker(marker1); this.effectPlotCommandBuilder.setSecondMarker(marker2); List<String> allPhenotypeNames = Arrays.asList(cross.getPhenotypeData().getDataNames()); this.effectPlotCommandBuilder.setPhenotypeIndex( allPhenotypeNames.indexOf(phenotypeName)); } /** * {@inheritDoc} */ public void actionPerformed(ActionEvent e) { EffectPlotData effectsPlotData = this.extractEffectsPlotData(); EffectPlot effectPlot = new EffectPlot(effectsPlotData); AxisRenderingGraph axisGraph = new AxisRenderingGraph( new SimpleGraphCoordinateConverter()); axisGraph.setInteriorGraph(effectPlot); Graph2DComponent graphComponent = new Graph2DComponent(); graphComponent.addGraph2D(axisGraph); SimpleGraphContainerPanel simpleGraphContainerPanel = new SimpleGraphContainerPanel(graphComponent, effectPlot); Desktop desktop = QTL.getInstance().getDesktop(); desktop.createInternalFrame( simpleGraphContainerPanel, this.phenotypeName + " Effect Plot", null, this.effectPlotCommandBuilder.getCommand().getCommandText()); } /** * Pick the plot data out of the effect plot R object. * @return * the effect plot data suitable for our graphs */ private EffectPlotData extractEffectsPlotData() { RInterface rInterface = RInterfaceFactory.getRInterfaceInstance(); synchronized(rInterface) { // create a temporary variable with the effect data in it RCommand effectPlotCommand = this.effectPlotCommandBuilder.getCommand(); RAssignmentCommand tempAssignment = new RAssignmentCommand( TEMP_EFFECT_PLOT_DATA_ACCESSOR, effectPlotCommand.getCommandText()); rInterface.evaluateCommandNoReturn( new SilentRCommand(tempAssignment)); // poke at the data... see if it's 1D or 2D (ie: a matrix) RObject meansAccessor = new RObject( rInterface, TEMP_EFFECT_PLOT_DATA_ACCESSOR + EFFECT_MEANS_SUFFIX); RObject stdErrorsAccessor = new RObject( rInterface, TEMP_EFFECT_PLOT_DATA_ACCESSOR + EFFECT_STD_ERROR_SSUFFIX); final EffectPlotData returnData; if(JRIUtilityFunctions.inheritsRClass(meansAccessor, "matrix")) { returnData = this.extractDataForTwoEffects( meansAccessor, stdErrorsAccessor); } else { returnData = this.extractDataForOneEffect( meansAccessor, stdErrorsAccessor); } // clean up the temp data before we return rInterface.evaluateCommand(new SilentRCommand( "rm(" + TEMP_EFFECT_PLOT_DATA_ACCESSOR + ")")); return returnData; } } /** * Extract the effect plot data assuming that it is a 2D effect * @param meansAccessor * the means R object * @param stdErrorsAccessor * the standard errors R object * @return * the plot data */ private EffectPlotData extractDataForTwoEffects( RObject meansAccessor, RObject stdErrorsAccessor) { String[] meansColNames = JRIUtilityFunctions.getColumnNames(meansAccessor); String[] stdErrorsColNames = JRIUtilityFunctions.getColumnNames(stdErrorsAccessor); // make sure they're both equal if(!Arrays.equals(meansColNames, stdErrorsColNames)) { LOG.severe( meansAccessor.getAccessorExpressionString() + " column names dont match up with " + stdErrorsAccessor.getAccessorExpressionString() + " column names"); return null; } String[] meansRowNames = JRIUtilityFunctions.getRowNames(meansAccessor); String[] stdErrorRowNames = JRIUtilityFunctions.getRowNames(stdErrorsAccessor); // make sure they're both equal if(!Arrays.equals(meansRowNames, stdErrorRowNames)) { LOG.severe( meansAccessor.getAccessorExpressionString() + " row names dont match up with " + stdErrorsAccessor.getAccessorExpressionString() + " row names"); return null; } EffectCategoryAndPointNames columnCategoryAndPoints = ShowEffectPlotAction.extractCategoryAndPointNames( meansColNames); EffectCategoryAndPointNames rowCategoryAndPoints = ShowEffectPlotAction.extractCategoryAndPointNames( meansRowNames); // strangely enough, the rows will become the main grouping and the // columns will be the line values EffectPlotDataPoint[][] dataLines = new EffectPlotDataPoint[meansColNames.length][]; for(int lineIndex = 0; lineIndex < dataLines.length; lineIndex++) { EffectPlotDataPoint[] currLine = new EffectPlotDataPoint[meansRowNames.length]; dataLines[lineIndex] = currLine; // read in the current columns REXP meansColumnRExpression = meansAccessor.getRInterface().evaluateCommand( new SilentRCommand( meansAccessor.getAccessorExpressionString() + "[," + (lineIndex + 1) + "]")); double[] meansLineValues = meansColumnRExpression.asDoubleArray(); REXP stdErrorsColumnRExpression = stdErrorsAccessor.getRInterface().evaluateCommand( new SilentRCommand( stdErrorsAccessor.getAccessorExpressionString() + "[," + (lineIndex + 1) + "]")); double[] stdErrorsValues = stdErrorsColumnRExpression.asDoubleArray(); for(int pointIndex = 0; pointIndex < stdErrorsValues.length; pointIndex++) { EffectPlotDataPoint currPoint = new EffectPlotDataPoint( meansLineValues[pointIndex], stdErrorsValues[pointIndex]); currLine[pointIndex] = currPoint; } } return new EffectPlotData( rowCategoryAndPoints.getCategory(), this.phenotypeName, columnCategoryAndPoints.getCategory(), rowCategoryAndPoints.getPointNames(), dataLines, columnCategoryAndPoints.getPointNames()); } /** * Extract the effect plot data assuming that it is a 1D effect * @param meansAccessor * the means R object * @param stdErrorsAccessor * the standard errors R object * @return * the plot data */ private EffectPlotData extractDataForOneEffect( RObject meansAccessor, RObject stdErrorsAccessor) { String[] meansNames = JRIUtilityFunctions.getNames(meansAccessor); String[] stdErrorsNames = JRIUtilityFunctions.getNames(stdErrorsAccessor); // make sure they're equal if(!Arrays.equals(meansNames, stdErrorsNames)) { LOG.severe( meansAccessor.getAccessorExpressionString() + " names dont match up with " + stdErrorsAccessor.getAccessorExpressionString() + " names"); return null; } EffectCategoryAndPointNames categoryAndPoints = ShowEffectPlotAction.extractCategoryAndPointNames(meansNames); // extract the effect line data REXP meansRExpression = meansAccessor.getRInterface().evaluateCommand( new SilentRCommand(meansAccessor.getAccessorExpressionString())); double[] meansValues = meansRExpression.asDoubleArray(); REXP stdErrorsRExpression = stdErrorsAccessor.getRInterface().evaluateCommand( new SilentRCommand(stdErrorsAccessor.getAccessorExpressionString())); double[] stdErrorValues = stdErrorsRExpression.asDoubleArray(); EffectPlotDataPoint[] line = new EffectPlotDataPoint[meansValues.length]; for(int pointIndex = 0; pointIndex < line.length; pointIndex++) { line[pointIndex] = new EffectPlotDataPoint( meansValues[pointIndex], stdErrorValues[pointIndex]); } return new EffectPlotData( categoryAndPoints.getCategory(), this.phenotypeName, categoryAndPoints.getPointNames(), line); } /** * Parse the raw column or row names to figure out what the effect point * and effect category names are * @param dataNames * the names to parse * @return * the parsed names */ private static EffectCategoryAndPointNames extractCategoryAndPointNames( String[] dataNames) { String category = null; String[] pointNames = new String[dataNames.length]; for(int nameIndex = 0; nameIndex < pointNames.length; nameIndex++) { Matcher currMatcher = EFFECT_DATA_NAME_PATTERN.matcher(dataNames[nameIndex]); if(currMatcher.matches()) { String categoryGroup = currMatcher.group(1); String pointNameGroup = currMatcher.group(2); if(category != null) { // the category should be equal to the category group if(!category.equals(categoryGroup)) { LOG.severe( "failed to parse effect plot category. " + "expected to see " + category + " but saw " + categoryGroup + " instead"); return null; } } else { category = categoryGroup; } pointNames[nameIndex] = pointNameGroup; } else { LOG.severe( "cannot extract effect plot data because the string " + dataNames[nameIndex] + " doesn't match the pattern " + "that we were expecting"); return null; } } return new EffectCategoryAndPointNames(category, pointNames); } /** * a simple holder class for the effect category an point names */ private static class EffectCategoryAndPointNames { private final String category; private final String[] pointNames; /** * @param category * @param pointNames */ public EffectCategoryAndPointNames(String category, String[] pointNames) { this.category = category; this.pointNames = pointNames; } /** * @return the category */ public String getCategory() { return this.category; } /** * @return the pointNames */ public String[] getPointNames() { return this.pointNames; } /** * {@inheritDoc} */ @Override public boolean equals(Object otherObject) { if(otherObject instanceof EffectCategoryAndPointNames) { EffectCategoryAndPointNames otherEffectCategoryAndPointNames = (EffectCategoryAndPointNames)otherObject; return this.category.equals(otherEffectCategoryAndPointNames.category) && Arrays.equals( this.pointNames, otherEffectCategoryAndPointNames.pointNames); } else { return false; } } } }