gt: 		yTicSize = getNumericalTicSize(minY, maxY);
context: /*
   *  RapidMiner
   *
   *  Copyright (C) 2001-2008 by Rapid-I and the contributors
   *
   *  Complete list of developers available at our web site:
   *
   *       http://rapid-i.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;
  
  import java.awt.Color;
  import java.awt.Component;
  import java.awt.Font;
  import java.awt.Graphics;
  import java.awt.Graphics2D;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.geom.AffineTransform;
  import java.awt.geom.Line2D;
  import java.awt.geom.Rectangle2D;
  import java.text.DecimalFormat;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  
  import javax.swing.Icon;
  import javax.swing.JCheckBox;
  import javax.swing.JComponent;
  import javax.swing.JLabel;
  import javax.swing.JSlider;
  import javax.swing.event.ChangeEvent;
  import javax.swing.event.ChangeListener;
  
  import com.rapidminer.datatable.DataTable;
  import com.rapidminer.datatable.DataTableRow;
  import com.rapidminer.tools.Tools;
  import com.rapidminer.tools.math.MathFunctions;
  
  
  /** The distribution plotter can be used to plot distributions (histograms) of
   *  the selected dimension.
   * 
   * @author Ingo Mierswa
   * @version $Id: DistributionPlotter.java,v 2.12 2006/03/27 13:21:58 ingomierswa
   *          Exp $
   */
  public class HistogramPlotter extends PlotterAdapter {
  
  	private static final long serialVersionUID = 5447178172542465015L;
  
      public static final int MIN_BIN_NUMBER = 2;
      
      public static final int MAX_BIN_NUMBER = 100;
      
      public static final int DEFAULT_BIN_NUMBER = 40;
      
  	private static Icon[] RECTANGLE_STYLE_ICONS;
  	
  	static {
  		RECTANGLE_STYLE_ICONS = new RectangleStyleIcon[RectangleStyle.NUMBER_OF_STYLES];
  		for (int i = 0; i < RECTANGLE_STYLE_ICONS.length; i++) {
  			RECTANGLE_STYLE_ICONS[i] = new RectangleStyleIcon(i);
  		}
  	}
  
  	/**
  	 * Defines the icon which is plotted before the attribute in the selection
  	 * list (legend or key).
  	 */
  	private static class RectangleStyleIcon implements Icon {
  
  		private RectangleStyle style;
  
  		private RectangleStyleIcon(int index) {
  			this.style = new RectangleStyle(index);
  		}
  
  		public int getIconWidth() {
  			return 16;
  		}
  
  		public int getIconHeight() {
  			return 16;
  		}
  
  		public void paintIcon(Component c, Graphics g, int x, int y) {
  			style.set((Graphics2D) g);
  			g.fillRect(x, y, 16, 16);
  			g.setColor(Color.black);
  			g.drawRect(x, y, 16, 16);
  		}
  	}
  
  
  	private static final Font SCALED_LABEL_FONT = LABEL_FONT.deriveFont(AffineTransform.getScaleInstance(1, -1));
  
  	private static final int LABEL_MARGIN_X = 50;
  
  	private static final int LABEL_MARGIN_Y = 15;
  
  	protected transient DataTable dataTable;
  
  	protected double minX, maxX, minY, maxY, xTicSize, yTicSize;
  
  	/** Indicates which columns will be plotted. */
  	private boolean[] columns = new boolean[0];
  	
  	protected Map<Integer,Bins> allPlots = new HashMap<Integer,Bins>();
  
  	protected int binNumber = DEFAULT_BIN_NUMBER;
  
  	protected boolean drawLegend = true;
  	
  	private String key = null;
  	
  	protected int currentXPlotterColumn = -1;
  	
  	private int jitterAmount = 0;
  
  	private boolean logScale = false;
  	
  	private boolean absolute = false;
  	
  	public HistogramPlotter() {
  		super();
  		setBackground(Color.white);
  	}
  
  	public HistogramPlotter(DataTable dataTable) {
  		this();
  		setDataTable(dataTable);
  	}
  
  	public void setDataTable(DataTable dataTable) {
  		super.setDataTable(dataTable);
  		this.dataTable = dataTable;
  		this.columns = new boolean[this.dataTable.getNumberOfColumns()];
  		repaint();
  	}
  
  	public Bins getBins(int plotColumn) {
  		return allPlots.get(plotColumn);
  	}
  
  	public Icon getIcon(int index) {
  		return RECTANGLE_STYLE_ICONS[index % RECTANGLE_STYLE_ICONS.length];
  	}
  
      public JComponent getOptionsComponent(int index) {
          if (index == 0) {
          	JLabel label = new JLabel("Number of bins:");
              label.setToolTipText("Set the number of bins which should be displayed.");
              return label;
          } else if (index == 1) {
              final JSlider binNumberSlider = new JSlider(MIN_BIN_NUMBER, MAX_BIN_NUMBER, DEFAULT_BIN_NUMBER);
              binNumberSlider.setMajorTickSpacing(MAX_BIN_NUMBER - MIN_BIN_NUMBER);
              binNumberSlider.setMinorTickSpacing((MAX_BIN_NUMBER - MIN_BIN_NUMBER) / 10);
              binNumberSlider.setPaintTicks(true);
              binNumberSlider.setPaintLabels(true);
              binNumberSlider.setToolTipText("Set the number of bins which should be displayed.");
              binNumberSlider.addChangeListener(new ChangeListener() {
                  public void stateChanged(ChangeEvent e) {
                      if (!binNumberSlider.getValueIsAdjusting())
                          setBinNumber(binNumberSlider.getValue());
                  }
              });
              return binNumberSlider;
          } else if (index == 2) {
          	final JCheckBox logScaleBox = new JCheckBox("Log Scale");
          	logScaleBox.addActionListener(new ActionListener() {
  				public void actionPerformed(ActionEvent e) {
  					setLogScale(logScaleBox.isSelected());
  				}
          	});
          	return logScaleBox;
          } else {
              return null;
          }
      }
      
      public void setLogScale(boolean logScale) {
      	this.logScale = logScale;
      	repaint();
      }
      
      public boolean isLogScale() {
      	return this.logScale;
      }
      
      public void setAbsolute(boolean absolute) {
      	this.absolute = absolute;
      	repaint();
      }
      
      public boolean isSupportingAbsoluteValues() {
      	return true;
      }
      
  	/** Indicates how many bins should be used for the distribution plot. */
  	public void setBinNumber(int binNumber) {
  		this.binNumber = binNumber;
  		repaint();
  	}
  	
  	public void setPlotColumn(int index, boolean plot) {
  		columns[index] = plot;
  		repaint();
  	}
  
  	public boolean getPlotColumn(int index) {
  		return columns[index];
  	}
  	
  	public int getValuePlotSelectionType() {
  		return MULTIPLE_SELECTION;
  	}
  	
  	protected int getNumberOfPlots() {
  		int counter = 0;
  		for (int i = 0; i < columns.length; i++) {
  			if (columns[i])
  				counter++;
  		}
  		return counter;
  	}
  	
  	public void setDrawLegend(boolean drawLegend) {
  		this.drawLegend = drawLegend;
  	}
  	
  	public void setKey(String key) {
  		this.key = key;
  	}
  
  	/** Returns true. */
  	public boolean canHandleJitter() {
  		return true;
  	}
  	
  	/** Sets the level of jitter and initiates a repaint. */
  	public void setJitter(int jitter) {
  		this.jitterAmount = jitter;
  		repaint();
  	}
  	
  	public void prepareData() {		
  		this.minX = Double.POSITIVE_INFINITY;
  		this.maxX = Double.NEGATIVE_INFINITY;
  		this.minY = Double.POSITIVE_INFINITY;
  		this.maxY = Double.NEGATIVE_INFINITY;
  
  		allPlots.clear();
  		
  		if (getNumberOfPlots() == 0) {
  			return;
  		}
  
  		this.currentXPlotterColumn = -1;
  		synchronized (dataTable) {
  			// init
  			double[] minX = new double[dataTable.getNumberOfColumns()];
  			double[] maxX = new double[dataTable.getNumberOfColumns()];
  			for (int d = 0; d < minX.length; d++) {
  				minX[d] = Double.POSITIVE_INFINITY;
  				maxX[d] = Double.NEGATIVE_INFINITY;
  			}
  			// calculate min and max for all plots
  			Iterator<DataTableRow> i = dataTable.iterator();
  			while (i.hasNext()) {
  				DataTableRow row = i.next();
  				for (int d = 0; d < row.getNumberOfValues(); d++) {
  					if (getPlotColumn(d)) {
  						currentXPlotterColumn = d;
  						double value = row.getValue(d);
  						if (absolute)
  							value = Math.abs(value);
  						if (!Double.isNaN(value)) {
  							minX[d] = MathFunctions.robustMin(value, minX[d]);
  							this.minX = MathFunctions.robustMin(value, this.minX);
  							maxX[d] = MathFunctions.robustMax(value, maxX[d]);
  							this.maxX = MathFunctions.robustMax(value, this.maxX);
  						}
  					}
  				}
  			}
  			// create bins and add to bin map
  			for (int d = 0; d < minX.length; d++) {
  				if (getPlotColumn(d)) {
  					Bins bins = new Bins(d, minX[d], maxX[d], this.binNumber);
  					allPlots.put(d, bins);
  				}
  			}
  			// fill with data points
  			i = dataTable.iterator();
  			while (i.hasNext()) {
  				DataTableRow row = i.next();
  				for (int d = 0; d < row.getNumberOfValues(); d++) {
  					if (getPlotColumn(d)) {
  						Bins bins = allPlots.get(d); 
  						double value = row.getValue(d);
  						if (absolute)
  							value = Math.abs(value);
  						bins.addPoint(value);
  						this.maxY = Math.max(bins.getMaxCounter(), this.maxY);
  					}
  				}
  			}
  		}
  		
  		// rescale counters for logscale?
  		if (isLogScale()) {
  			this.maxY = 0.0d;
  			for (Bins bins : allPlots.values()) {
  				for (Bin bin : bins) {
  					double counter = bin.getCounter();
  					if (counter > 1.0d) {
  						double newValue = Math.log10(counter); 
  						bin.setCounter(newValue);
  						this.maxY = Math.max(newValue, this.maxY);
  					} else if (counter == 1) {
  						// hack in order to prevent 1 counters from vanishing since log(1) = 0
  						bin.setCounter(0.2d);
  						this.maxY = Math.max(0.2d, this.maxY);
  					}
  				}
  			}
  		}
  		
  		this.minY = 0;
  		
  		if (dataTable.getNumberOfRows() == 0) {
  			minX = 0;
  			maxX = 1;
  			minY = 0;
  			maxY = 1;
  		}
  
  		if (minX == maxX) {
  			minX -= 0.5;
  			maxX += 0.5;
  		}
  		if (minY == maxY) {
  			minY -= 0.5;
  			maxY += 0.5;
  		}
  		
  		xTicSize = getTicSize(dataTable, currentXPlotterColumn, minX, maxX);
  