gt: 	protected String getId(int series, int index) {
context: /**
   * Copyright (C) 2001-2015 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.charts;
  
  import java.awt.Color;
  import java.awt.Font;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.LinkedHashMap;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Random;
  
  import javax.swing.JComponent;
  
  import org.jfree.chart.ChartFactory;
  import org.jfree.chart.ChartMouseEvent;
  import org.jfree.chart.ChartMouseListener;
  import org.jfree.chart.JFreeChart;
  import org.jfree.chart.axis.DateAxis;
  import org.jfree.chart.axis.LogAxis;
  import org.jfree.chart.axis.NumberAxis;
  import org.jfree.chart.axis.SymbolAxis;
  import org.jfree.chart.block.BlockBorder;
  import org.jfree.chart.block.BlockContainer;
  import org.jfree.chart.block.BlockResult;
  import org.jfree.chart.block.BorderArrangement;
  import org.jfree.chart.block.LabelBlock;
  import org.jfree.chart.entity.XYItemEntity;
  import org.jfree.chart.labels.XYToolTipGenerator;
  import org.jfree.chart.plot.PlotOrientation;
  import org.jfree.chart.plot.XYPlot;
  import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
  import org.jfree.chart.title.LegendTitle;
  import org.jfree.data.Range;
  import org.jfree.data.xy.DefaultXYDataset;
  import org.jfree.data.xy.DefaultXYZDataset;
  import org.jfree.data.xy.XYDataset;
  import org.jfree.ui.HorizontalAlignment;
  import org.jfree.ui.RectangleEdge;
  
  import com.rapidminer.ObjectVisualizer;
  import com.rapidminer.datatable.DataTable;
  import com.rapidminer.datatable.DataTableRow;
  import com.rapidminer.gui.plotter.PlotterConfigurationModel;
  import com.rapidminer.gui.plotter.RangeablePlotterAdapter;
  import com.rapidminer.tools.ObjectVisualizerService;
  import com.rapidminer.tools.Tools;
  import com.rapidminer.tools.math.MathFunctions;
  
  
  /**
   * This is the abstract superclass for scatter plotter based on JFreeChart.
   *
   * @author Ingo Mierswa
   */
  public abstract class Abstract2DChartPlotter extends RangeablePlotterAdapter {
  
  	private static final long serialVersionUID = 4568273282283350833L;
  
  	public static class SeriesAndItem {
  
  		private int series;
  
  		private int item;
  
  		public SeriesAndItem(int series, int item) {
  			this.series = series;
  			this.item = item;
  		}
  
  		@Override
  		public int hashCode() {
  			return Integer.valueOf(series).hashCode() ^ Integer.valueOf(item).hashCode();
  		}
  
  		@Override
  		public boolean equals(Object o) {
  			if (!(o instanceof SeriesAndItem)) {
  				return false;
  			}
  			SeriesAndItem s = (SeriesAndItem) o;
  			return this.series == s.series && this.item == s.item;
  		}
  	}
  
  	/** The axis names. */
  	private static final String[] axisNames = { "x-Axis", "y-Axis" };
  
  	private static final int X_AXIS = 0;
  	private static final int Y_AXIS = 1;
  	private static final int COLOR_AXIS = 2;
  
  	/** The currently used data table object. */
  	protected transient DataTable dataTable;
  
  	private XYDataset dataSet = new DefaultXYDataset();
  
  	/** The columns which are used for the axes. */
  	private int[] axis = new int[] { -1, -1 };
  
  	/** The column which is used for the color. */
  	private int colorColumn = -1;
  
  	private int jitterAmount = 0;
  
  	private boolean[] logScales = new boolean[] { false, false };
  
  	private boolean plotColumnsLogScale = false;
  
  	private double minColor;
  
  	private double maxColor;
  
  	private boolean nominal = true;
  
  	private Map<SeriesAndItem, String> idMap = new HashMap<SeriesAndItem, String>();
  
  	public Abstract2DChartPlotter(final PlotterConfigurationModel settings) {
  		super(settings);
  		setBackground(Color.white);
  
  	}
  
  	public Abstract2DChartPlotter(PlotterConfigurationModel settings, DataTable dataTable) {
  		this(settings);
  		setDataTable(dataTable);
  	}
  
  	/** Subclasses have to implement this method. */
  	public abstract AbstractXYItemRenderer getItemRenderer(boolean nominal, int size, double minColor, double maxColor);
  
  	/** Returns true. */
  	@Override
  	public boolean canHandleJitter() {
  		return true;
  	}
  
  	/** Sets the level of jitter and initiates a repaint. */
  	@Override
  	public void setJitter(int jitter) {
  		this.jitterAmount = jitter;
  		updatePlotter();
  	}
  
  	/**
  	 * Returns true if a log scale for this column is supported. Returns true for the x- and y-axis.
  	 */
  	@Override
  	public boolean isSupportingLogScale(int axis) {
  		if (axis == X_AXIS || axis == Y_AXIS) {
  			return true;
  		} else {
  			return false;
  		}
  	}
  
  	/** Returns true. */
  	@Override
  	public boolean isSupportingLogScaleForPlotColumns() {
  		return true;
  	}
  
  	/** Sets if the given axis should be plotted with log scale. */
  	@Override
  	public void setLogScale(int axis, boolean logScale) {
  		logScales[axis] = logScale;
  		updatePlotter();
  	}
  
  	/** Sets if the given axis should be plotted with log scale. */
  	@Override
  	public void setLogScaleForPlotColumns(boolean logScale) {
  		plotColumnsLogScale = logScale;
  		updatePlotter();
  	}
  
  	@Override
  	public void dataTableSet() {
  		this.dataTable = getDataTable();
  		updatePlotter();
  	}
  
  	@Override
  	public void setPlotColumn(int index, boolean plot) {
  		if (plot) {
  			this.colorColumn = index;
  		} else {
  			this.colorColumn = -1;
  		}
  		updatePlotter();
  	}
  
  	@Override
  	public boolean getPlotColumn(int index) {
  		return colorColumn == index;
  	}
  
  	@Override
  	public String getPlotName() {
  		return "Color Column";
  	}
  
  	@Override
  	public int getNumberOfAxes() {
  		return axisNames.length;
  	}
  
  	@Override
  	public void setAxis(int index, int dimension) {
  		axis[index] = dimension;
  		updatePlotter();
  	}
  
  	@Override
  	public int getAxis(int index) {
  		return axis[index];
  	}
  
  	@Override
  	public String getAxisName(int index) {
  		return axisNames[index];
  	}
  
  	private void prepareData() {
  		idMap.clear();
  
  		if (colorColumn < 0 || dataTable.isNominal(colorColumn)) {
  			prepareNominalData();
  		} else {
  			prepareNumericalData();
  		}
  	}
  
  	private void prepareNumericalData() {
  		this.nominal = false;
  		dataSet = new DefaultXYZDataset();
  
  		if (axis[X_AXIS] >= 0 && axis[Y_AXIS] >= 0) {
  			this.minColor = Double.POSITIVE_INFINITY;
  			this.maxColor = Double.NEGATIVE_INFINITY;
  
  			List<double[]> dataList = new LinkedList<double[]>();
  			List<String> idList = new LinkedList<String>();
  			synchronized (dataTable) {
  				Iterator<DataTableRow> i = this.dataTable.iterator();
  				while (i.hasNext()) {
  					DataTableRow row = i.next();
  
  					double xValue = Double.NaN;
  					if (axis[X_AXIS] >= 0) {
  						xValue = row.getValue(axis[X_AXIS]);
  					}
  
  					double yValue = Double.NaN;
  					if (axis[Y_AXIS] >= 0) {
  						yValue = row.getValue(axis[Y_AXIS]);
  					}
  
  					double colorValue = Double.NaN;
  					if (colorColumn >= 0) {
  						colorValue = row.getValue(colorColumn);
  					}
  
  					if (plotColumnsLogScale) {
  						if (Tools.isLessEqual(colorValue, 0.0d)) {
  							colorValue = 0;
  						} else {
  							colorValue = Math.log10(colorValue);
  						}
  					}
  
  					// TM: removed check
  					// if (!Double.isNaN(xValue) && !Double.isNaN(yValue)) {
  					double[] data = new double[3];
  					data[X_AXIS] = xValue;
  					data[Y_AXIS] = yValue;
  					data[COLOR_AXIS] = colorValue;
  
  					if (!Double.isNaN(colorValue)) {
  						this.minColor = Math.min(this.minColor, colorValue);
  						this.maxColor = Math.max(this.maxColor, colorValue);
  					}
  
  					dataList.add(data);
  					idList.add(row.getId());
  					// }
  				}
  			}
  
  			double[][] data = new double[3][dataList.size()];
  
  			double minX = Double.POSITIVE_INFINITY;
  			double maxX = Double.NEGATIVE_INFINITY;
  			double minY = Double.POSITIVE_INFINITY;
  			double maxY = Double.NEGATIVE_INFINITY;
  
  			int index = 0;
  			for (double[] d : dataList) {
  				data[X_AXIS][index] = d[X_AXIS];
  				data[Y_AXIS][index] = d[Y_AXIS];
  				data[COLOR_AXIS][index] = d[COLOR_AXIS];
  
  				minX = MathFunctions.robustMin(minX, d[X_AXIS]);
  				maxX = MathFunctions.robustMax(maxX, d[X_AXIS]);
  				minY = MathFunctions.robustMin(minY, d[Y_AXIS]);
  				maxY = MathFunctions.robustMax(maxY, d[Y_AXIS]);
  
  				index++;
  			}
  
  			// jittering
  			if (this.jitterAmount > 0) {
  				Random jitterRandom = new Random(2001);
  				double oldXRange = maxX - minX;
  				double oldYRange = maxY - minY;
  				for (int i = 0; i < dataList.size(); i++) {
  					if (Double.isInfinite(oldXRange) || Double.isNaN(oldXRange)) {
  						oldXRange = 0;
  					}
  					if (Double.isInfinite(oldYRange) || Double.isNaN(oldYRange)) {
  						oldYRange = 0;
  					}
  					double pertX = oldXRange * (jitterAmount / 200.0d) * jitterRandom.nextGaussian();
  					double pertY = oldYRange * (jitterAmount / 200.0d) * jitterRandom.nextGaussian();
  					data[X_AXIS][i] += pertX;
  					data[Y_AXIS][i] += pertY;
  				}
  			}
  
  			// add data
  			((DefaultXYZDataset) dataSet).addSeries("All", data);
  
  			// id handling
  			int idCounter = 0;
  			for (String id : idList) {
  				idMap.put(new SeriesAndItem(0, idCounter++), id);
  			}
  		}
  	}
  
  