/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program 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
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.gui.plotter.mathplot;
import java.awt.Color;
import java.awt.Font;
import java.awt.geom.AffineTransform;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import org.math.plot.Plot3DPanel;
import com.rapidminer.datatable.DataTable;
import com.rapidminer.datatable.DataTableRow;
import com.rapidminer.gui.plotter.PlotterConfigurationModel;
import com.rapidminer.gui.plotter.conditions.PlotterCondition;
import com.rapidminer.gui.plotter.conditions.RowsPlotterCondition;
import com.rapidminer.gui.tools.ProgressThread;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.LogService;
/**
* This plotter can be used to create 3D surface plots of equidistant data.
*
* @author Sebastian Land, Ingo Mierswa, Thilo Kamradt
*/
public class SurfacePlot3D extends JMathPlotter3D {
private static final long serialVersionUID = -8086776011628491876L;
private static final int MAX_NUMBER_OF_ROWS = 100;
private int xAxis = -1;
private int yAxis = -1;
private boolean[] zAxis = null;
private boolean runningCalculation = false;
public SurfacePlot3D(PlotterConfigurationModel settings) {
super(settings);
}
public SurfacePlot3D(PlotterConfigurationModel settings, DataTable dataTable) {
super(settings, dataTable);
}
// private final
private void addGridPlot(String columnName, Color color, double[] yArray, double[] xArray, double[][] zArray) {
((Plot3DPanel) getPlotPanel()).addGridPlot(columnName, color, yArray, xArray, zArray);
}
@Override
public void update() {
if (getAxis(0) != -1 && getAxis(1) != -1) {
// check for changes at z Axis
boolean zChanged = false;
if (zAxis == null || zAxis.length != countColumns()) {
zChanged = true;
zAxis = new boolean[countColumns()];
for (int counter = 0; counter < zAxis.length; counter++) {
zAxis[counter] = getPlotColumn(counter);
}
} else {
for (int counter = 0; counter < zAxis.length; counter++) {
if (zAxis[counter] != getPlotColumn(counter)) {
zChanged = true;
zAxis[counter] = getPlotColumn(counter);
}
}
}
// only recalculate the plot if anything changed
if ((xAxis != getAxis(1) || yAxis != getAxis(0) || zChanged) && !runningCalculation) {
runningCalculation = true;
xAxis = getAxis(1);
yAxis = getAxis(0);
// recalculate the plot
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
getPlotPanel().removeAllPlots();
}
});
ProgressThread plotCalculationThread = new ProgressThread("surface3DPlot") {
@Override
public void run() {
this.getProgressListener().setTotal(zAxis.length);
for (int currentVariable = 0; currentVariable < zAxis.length; currentVariable++) {
if (zAxis[currentVariable]) {
List<Double> xSet = new LinkedList<>();
List<Double> ySet = new LinkedList<>();
Map<String, Double> zMap = new HashMap<>();
DataTable table = getDataTable();
synchronized (table) {
Iterator<DataTableRow> iterator = table.iterator();
while (iterator.hasNext()) {
DataTableRow row = iterator.next();
double x = row.getValue(xAxis);
double y = row.getValue(yAxis);
double z = row.getValue(currentVariable);
if (!Double.isNaN(x) && !Double.isNaN(y) && !Double.isNaN(z)) {
xSet.add(x);
ySet.add(y);
zMap.put(x + "+" + y, z);
}
}
// because all sets have the same size
int size = xSet.size();
final double[] xArray = new double[size];
final double[] yArray = new double[size];
final double[][] zArray = new double[size][size];
int xCounter = 0;
Iterator<Double> x = xSet.iterator();
double last = 0.0d;
while (x.hasNext()) {// n^2
xArray[xCounter] = x.next();
Iterator<Double> y = ySet.iterator();
int yCounter = 0;
while (y.hasNext()) {
yArray[yCounter] = y.next();
Double value = zMap.get(xArray[xCounter] + "+" + yArray[yCounter]);
if (value != null) {
zArray[xCounter][yCounter] = value;
last = value;
} else {
zArray[xCounter][yCounter] = last;
}
yCounter++;
}
xCounter++;
}
// PlotPanel construction
if (xArray.length > 0 && yArray.length > 0 && zArray.length > 0) {
final Color color = getColorProvider().getPointColor(
(currentVariable + 1) / (double) zAxis.length);
final String columnName = getDataTable().getColumnName(currentVariable);
// add the GridPlot inside the GUI-Thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
addGridPlot(columnName, color, yArray, xArray, zArray);
}
});
}
}// end Synchronize
this.getProgressListener().setCompleted(currentVariable + 1);
}
}
runningCalculation = false;
}
};
plotCalculationThread.start();
}
} else if (!runningCalculation) {
// no valid axis are selected
getPlotPanel().removeAllPlots();
}
}
@Override
public PlotterCondition getPlotterCondition() {
return new RowsPlotterCondition(MAX_NUMBER_OF_ROWS);
}
@Override
public int getValuePlotSelectionType() {
return MULTIPLE_SELECTION;
}
@Override
public String getPlotName() {
return "z-Axis";
}
@Override
public String getPlotterName() {
return PlotterConfigurationModel.SURFACE_PLOT_3D;
}
@Override
public JComponent getPlotter() {
DataTable table = getDataTable();
if (table != null && table.getNumberOfRows() > MAX_NUMBER_OF_ROWS) {
LogService.getRoot().log(Level.INFO, "com.rapidminer.gui.plotter.mathplot.SurfacePlot2D.too_many_examples",
new Object[] { table.getNumberOfRows(), MAX_NUMBER_OF_ROWS });
// Display Label with error message because Plot3DPanel can not handle such a lot of
// data points
JLabel label = new JLabel(I18N.getGUILabel("surface3DPlot.too_many_examples", MAX_NUMBER_OF_ROWS));
label.setHorizontalAlignment(SwingConstants.CENTER);
Font originalFont = label.getFont();
label.setFont(originalFont.deriveFont((float) (originalFont.getSize() * 1.25)));
originalFont.deriveFont(new AffineTransform());
return label;
} else {
return super.getPlotter();
}
}
}