/* * Copyright (c) 2003-2012 Fred Hutchinson Cancer Research Center * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.fhcrc.cpl.toolbox.gui.chart; import org.fhcrc.cpl.toolbox.statistics.BasicStatistics; import org.fhcrc.cpl.toolbox.statistics.RInterface; import org.fhcrc.cpl.toolbox.filehandler.TempFileManager; import org.apache.log4j.Logger; import java.io.File; import java.io.IOException; import java.util.Map; import java.util.HashMap; import java.util.ArrayList; import java.util.List; /** * This class knows how to ask R to plot perspective 3D charts to a file * and then display them in a panel */ public class PanelWithRPerspectivePlot extends PanelWithBlindImageChart { protected static Logger _log = Logger.getLogger(PanelWithRPerspectivePlot.class); public static final int DEFAULT_CHART_WIDTH = 700; public static final int DEFAULT_CHART_HEIGHT = 700; //because the dialog boxes need to be bigger than the image public static final int DEFAULT_CHART_DIALOG_WIDTH = DEFAULT_CHART_WIDTH + 10; public static final int DEFAULT_CHART_DIALOG_HEIGHT = DEFAULT_CHART_HEIGHT + 25; public static final int DEFAULT_ROTATION_ANGLE = 0; public static final int DEFAULT_TILT_ANGLE = 30; public static final double DEFAULT_SHADE = .5; protected boolean useGradientForColor = false; protected boolean showBorders = true; protected boolean showBox = true; protected int chartWidth = DEFAULT_CHART_WIDTH; protected int chartHeight = DEFAULT_CHART_HEIGHT; protected List<Integer> rotationAngles; protected List<Integer> tiltAngles; protected double shade = DEFAULT_SHADE; //Line style definitions, from par public static final int LINE_STYLE_SOLID = 1; public static final int LINE_STYLE_DASHED = 2; public static final int LINE_STYLE_DOTTED = 3; public static final int DEFAULT_LINE_STYLE = LINE_STYLE_SOLID; public static final String DEFAULT_BACKGROUND_COLOR_STRING = "white"; public static final String DEFAULT_FOREGROUND_COLOR_STRING = "lightgreen"; protected String xAxisName = "x"; protected String yAxisName = "y"; protected String zAxisName = "z"; protected boolean showAxisDetails = true; protected String backgroundColorString = DEFAULT_BACKGROUND_COLOR_STRING; protected String foregroundColorString = DEFAULT_FOREGROUND_COLOR_STRING; protected java.util.List<LineVariables> lineVariableList = new ArrayList<LineVariables>(); protected java.util.List<Integer> lineStyleList = new ArrayList<Integer>(); protected int millisToWait = 300000; public PanelWithRPerspectivePlot() { rotationAngles = new ArrayList<Integer>(); rotationAngles.add(DEFAULT_ROTATION_ANGLE); tiltAngles = new ArrayList<Integer>(); tiltAngles.add(DEFAULT_TILT_ANGLE); } public void plotPointsSummary(java.util.List<Float> xValuesList, java.util.List<Float> yValuesList, double xBinSize, double yBinSize) { double[] xValues = new double[xValuesList.size()]; double[] yValues = new double[yValuesList.size()]; for (int i=0; i<xValues.length; i++) { xValues[i] = xValuesList.get(i); yValues[i] = yValuesList.get(i); } } public void plotPointsSummary(double[] xValues, double[] yValues, double xBinSize, double yBinSize) { _log.debug("plotPointsSummary: xBinSize = " + xBinSize + ", yBinSize = " + yBinSize + ", values: " + xValues.length); double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE; double maxX = Double.MIN_VALUE, maxY = Double.MIN_VALUE; for (int i=0; i<xValues.length; i++) { if (xValues[i] < minX) minX = xValues[i]; if (xValues[i] > maxX) maxX = xValues[i]; if (yValues[i] < minY) minY = yValues[i]; if (yValues[i] > maxY) maxY = yValues[i]; } _log.debug(" minX=" + minX + ", maxX=" + maxX + ", minY=" + minY + ", maxY=" + maxY); int numXBins = countBins(minX, maxX, xBinSize); int numYBins = countBins(minY, maxY, yBinSize); double[] xBins = enumerateBins(minX, xBinSize, numXBins); double[] yBins = enumerateBins(minY, yBinSize, numYBins); double[][] zMatrix = new double[numXBins][numYBins]; for (int i=0; i<xValues.length; i++) { int xBin = calcBin(xValues[i], minX, xBinSize); int yBin = calcBin(yValues[i], minY, yBinSize); zMatrix[xBin][yBin]++; } plot(xBins, yBins, zMatrix); } protected int calcBin(double val, double globalMin, double binSize) { return (int)((val-globalMin) / binSize); } protected int countBins(double minValue, double maxValue, double binSize) { return (int)((maxValue - minValue) / binSize) + 1; } protected double[] enumerateBins(double minValue, double binSize, int numBins) { double[] result = new double[numBins]; _log.debug("enumerateBins, min=" + minValue + ", binSize=" + binSize + ", numBins=" + numBins); for (int i=0; i<numBins; i++) { result[i] = minValue + (i * binSize); } return result; } public void plot(float[] xArray, float[] yArray, float[][] zMatrix) { double[][] zMatrixDouble = new double[zMatrix.length][zMatrix[0].length]; for (int i=0; i<zMatrix.length; i++) { for (int j=0; j<zMatrix[0].length; j++) zMatrixDouble[i][j] = zMatrix[i][j]; } plot(xArray, yArray, zMatrixDouble); } public void plot(float[] xArray, float[]yArray, double[][] zMatrix) { double[]xArrayDouble = new double[xArray.length]; double[]yArrayDouble = new double[yArray.length]; for (int i=0; i<xArray.length; i++) xArrayDouble[i] = xArray[i]; for (int i=0; i<yArray.length; i++) yArrayDouble[i] = yArray[i]; plot(xArrayDouble, yArrayDouble, zMatrix); } /** * Draw a solid line * @param xValues * @param yValues * @param zValues * @param color */ public void addLine(double[] xValues, double[] yValues, double[] zValues, String color) { addLine(xValues, yValues, zValues, color, DEFAULT_LINE_STYLE); } /** * Draw a line in the specified style * @param xValues * @param yValues * @param zValues * @param color * @param style must be an accepted value of the 'lty' variable to R's 'par' */ public void addLine(double[] xValues, double[] yValues, double[] zValues, String color, int style) { if (lineVariableList == null) { lineVariableList = new ArrayList<LineVariables>(); lineStyleList = new ArrayList<Integer>(); } lineVariableList.add(new LineVariables(xValues, yValues, zValues, color)); lineStyleList.add(style); } public void plot(double[] xArray, double[]yArray, double[][] zMatrix) { Map<String, double[]> vectorVarMap = new HashMap<String, double[]>(); vectorVarMap.put(xAxisName, xArray); vectorVarMap.put(yAxisName, yArray); Map<String, double[][]> matrixVarMap = new HashMap<String, double[][]>(); matrixVarMap.put(zAxisName, zMatrix); String tickType = showAxisDetails? "detailed" : "simple"; String perspColor = "\"" + foregroundColorString + "\""; String colorBuilderString = ""; if (useGradientForColor) { colorBuilderString = "z<-" + zAxisName + "; " + "zi <- (z[-1,-1] + z[ -1,-ncol(z)] + z[-nrow(z),-1] + z[-nrow(z),-ncol(z)]) / 4; " + "fcol<-terrain.colors(101)[round(100 * (zi-min(zi)) * (1 / (max(zi)-min(zi)))) + 1]; "; perspColor = "fcol"; } String bordersString = ""; if (!showBorders) bordersString = ", border=NA"; String boxString = ""; if (!showBox) boxString = ", box=FALSE"; if (lineVariableList != null) { int lineNum = 0; for (LineVariables lineVariables : lineVariableList) { _log.debug("Adding line " + lineNum + " with " + lineVariables.xValues.length + " points"); vectorVarMap.put("linex" + lineNum,lineVariables.xValues); vectorVarMap.put("liney" + lineNum,lineVariables.yValues); vectorVarMap.put("linez" + lineNum,lineVariables.zValues); lineNum++; } } StringBuffer rExpressionBuf = new StringBuffer(colorBuilderString); List<File> outputFiles = new ArrayList<File>(); for (int i=0; i<rotationAngles.size(); i++) { //need to try to create a unique filename String outputFileName = "persp" + xArray.length + "" + yArray.length + "" + BasicStatistics.mean(zMatrix[0]) + "_angle" + i + ".png"; File pngFile = TempFileManager.createTempFile(outputFileName, this); outputFiles.add(pngFile); //can't use TempFileManager, since this object may be long gone when the //user closes the window pngFile.deleteOnExit(); rExpressionBuf.append("png(\"" + outputFileName +"\"," + chartWidth + "," + chartHeight + "); par(bg = \"" + backgroundColorString + "\",mar=rep(.5,4)); persp(" + xAxisName + "," + yAxisName + "," + zAxisName + ",theta=" + rotationAngles.get(i) + ", phi=" + tiltAngles.get(i) + ", shade=" + shade + ", col=" + perspColor + ", ticktype=\"" + tickType +"\"" + bordersString + boxString + ") -> res;" + "round(res,3); "); //rExpression = rExpression + " x<-c(-.05, .05, .1);"; //rExpression = rExpression + "lines (trans3d(x, y=10, z= 6 + sin(x), pmat = res), col = 3);"; if (lineVariableList != null) { int lineNum = 0; for (int j=0; j< lineVariableList.size(); j++) { LineVariables lineVariables = lineVariableList.get(j); int lineStyle = lineStyleList.get(j); rExpressionBuf.append("lines(trans3d(linex" + lineNum + ", liney" + lineNum + ", linez" + lineNum + ", pmat = res), col = \"" + lineVariables.color + "\", lty=" + lineStyle + ");"); lineNum++; } } rExpressionBuf.append(" dev.off();"); } _log.debug(rExpressionBuf.toString()); RInterface.evaluateRExpression(rExpressionBuf.toString(), vectorVarMap, matrixVarMap, null, millisToWait); try { setImageFiles(outputFiles); } catch (IOException e) { throw new RuntimeException("Failed to load image from file " + outputFiles.get(0).getAbsolutePath(),e); } TempFileManager.deleteTempFiles(this); } //be careful here. These actually have to be valid R variable names public void setAxisRVariableNames(String xName, String yName, String zName) { xAxisName = xName; yAxisName = yName; zAxisName = zName; } public List<Integer> getRotationAngles() { return rotationAngles; } public void setRotationAngles(List<Integer> rotationAngles) { this.rotationAngles = rotationAngles; } public List<Integer> getTiltAngles() { return tiltAngles; } public void setTiltAngles(List<Integer> tiltAngles) { this.tiltAngles = tiltAngles; } //these are a bit hacky, since this is actually a list. Blow out the list, re-add one item public void setRotationAngle(int rotationAngle) { rotationAngles = new ArrayList<Integer>(); rotationAngles.add(rotationAngle); } public void setTiltAngle(int tiltAngle) { tiltAngles = new ArrayList<Integer>(); tiltAngles.add(tiltAngle); } public double getShade() { return shade; } public void setShade(double shade) { this.shade = shade; } public int getChartHeight() { return chartHeight; } public void setChartHeight(int chartHeight) { this.chartHeight = chartHeight; } public int getChartWidth() { return chartWidth; } public void setChartWidth(int chartWidth) { this.chartWidth = chartWidth; } public String getBackgroundColorString() { return backgroundColorString; } public void setBackgroundColorString(String backgroundColorString) { this.backgroundColorString = backgroundColorString; } public String getForegroundColorString() { return foregroundColorString; } public void setForegroundColorString(String foregroundColorString) { this.foregroundColorString = foregroundColorString; } public boolean getShowAxisDetails() { return showAxisDetails; } public void setShowAxisDetails(boolean showAxisDetails) { this.showAxisDetails = showAxisDetails; } public int getMillisToWait() { return millisToWait; } public void setMillisToWait(int millisToWait) { this.millisToWait = millisToWait; } public static class LineVariables { public double[] xValues; public double[] yValues; public double[] zValues; public String color = "black"; public LineVariables(double[] x, double[] y, double[] z, String color) { this.xValues = x; this.yValues = y; this.zValues = z; this.color = color; } } public boolean isUseGradientForColor() { return useGradientForColor; } public void setUseGradientForColor(boolean useGradientForColor) { this.useGradientForColor = useGradientForColor; } public boolean isShowBorders() { return showBorders; } public void setShowBorders(boolean showBorders) { this.showBorders = showBorders; } public boolean isShowBox() { return showBox; } public void setShowBox(boolean showBox) { this.showBox = showBox; } public void finalize() throws Throwable { TempFileManager.deleteTempFiles(this); super.finalize(); } }