content: /*
   *    This program 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 2 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 General Public License for more details.
   *
   *    You should have received a copy of the GNU General Public License
   *    along with this program; if not, write to the Free Software
   *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   */
  
  /*
   *    WekaOffscreenChartRenderer.java
   *    Copyright (C) 2011 University of Waikato, Hamilton, New Zealand
   *
   */
  
  package weka.gui.beans;
  
  import java.awt.image.BufferedImage;
  import java.util.List;
  
  import weka.core.Instance;
  import weka.core.Instances;
  import weka.gui.AttributeVisualizationPanel;
  import weka.gui.visualize.Plot2D;
  import weka.gui.visualize.PlotData2D;
  
  /**
   * Default OffscreenChartRenderer that uses Weka's built-in chart and graph
   * classes.
   * 
   * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
   * @version $Revision: 7662 $
   */
  public class WekaOffscreenChartRenderer extends AbstractOffscreenChartRenderer {
    
    /**
     * The name of this off screen renderer
     * 
     * @return the name of this off screen renderer
     */
    public String rendererName() {
      return "Weka Chart Renderer";
    }
    
    /**
     * Gets a short list of additional options (if any),
     * suitable for displaying in a tip text, in HTML form
     * 
     * @return additional options description in simple HTML form
     */
    public String optionsTipTextHTML() {
      return "<html><ul><li>-title=[chart title]</li>" +
      		"<li>-color=[coloring/class attribute name]</li></html>";
    }
  
    /**
     * Render an XY line chart
     * 
     * @param width the width of the resulting chart in pixels
     * @param height the height of the resulting chart in pixels
     * @param series a list of Instances - one for each series to be plotted
     * @param xAxis the name of the attribute for the x-axis (all series Instances 
     * are expected to have an attribute of the same type with this name)
     * @param yAxis the name of the attribute for the y-axis (all series Instances 
     * are expected to have an attribute of the same type with this name)
     * @param optionalArgs optional arguments to the renderer (may be null)
     * 
     * @return a BufferedImage containing the chart
     * @throws Exception if there is a problem rendering the chart
     */
    public BufferedImage renderXYLineChart(int width, int height,
        List<Instances> series, String xAxis, String yAxis,
        List<String> optionalArgs) throws Exception {
      BufferedImage osi = new BufferedImage(width, height, 
          BufferedImage.TYPE_INT_RGB);
      
      String plotTitle = "Line chart";
      String userTitle = getOption(optionalArgs, "-title");
      plotTitle = (userTitle != null) ? userTitle : plotTitle;
      
      Plot2D offScreenPlot = new Plot2D();
      offScreenPlot.setSize(width, height);
      
      // master plot
      PlotData2D master = new PlotData2D(series.get(0));
      master.setPlotName(plotTitle);
      boolean[] connectPoints = new boolean[series.get(0).numInstances()];
      for (int i = 0; i < connectPoints.length; i++) {
        connectPoints[i] = true;
      }
      master.setConnectPoints(connectPoints);
      offScreenPlot.setMasterPlot(master);
      // find x and y axis
      Instances masterInstances = series.get(0);
      int xAx = getIndexOfAttribute(masterInstances, xAxis);
      int yAx = getIndexOfAttribute(masterInstances, yAxis);
      if (xAx < 0) {
        xAx = 0;
      }
      if (yAx < 0) {
        yAx = 0;
      }
      
      // plotting axes and color
      offScreenPlot.setXindex(xAx);
      offScreenPlot.setYindex(yAx);
      offScreenPlot.setCindex(masterInstances.numAttributes() - 1);
      String colorAtt = getOption(optionalArgs, "-color");
      int tempC = getIndexOfAttribute(masterInstances, colorAtt);
      if (tempC >= 0) {
        offScreenPlot.setCindex(tempC);
      }
      
      // additional plots
      if (series.size() > 1) {
        for (Instances plotI : series) {
          PlotData2D plotD = new PlotData2D(plotI);
          connectPoints = new boolean[plotI.numInstances()];
          for (int i = 0; i < connectPoints.length; i++) {
            connectPoints[i] = true;
          }
          plotD.setConnectPoints(connectPoints);
          offScreenPlot.addPlot(plotD);
        }
      }
      
      // render
      java.awt.Graphics g = osi.getGraphics();
      offScreenPlot.paintComponent(g);
      
      return osi;
    }
  
    /**
     * Render an XY scatter plot
     * 
     * @param width the width of the resulting chart in pixels
     * @param height the height of the resulting chart in pixels
     * @param series a list of Instances - one for each series to be plotted
     * @param xAxis the name of the attribute for the x-axis (all series Instances 
     * are expected to have an attribute of the same type with this name)
     * @param yAxis the name of the attribute for the y-axis (all series Instances 
     * are expected to have an attribute of the same type with this name)
     * @param optionalArgs optional arguments to the renderer (may be null)
     * 
     * @return a BufferedImage containing the chart
     * @throws Exception if there is a problem rendering the chart
     */
    public BufferedImage renderXYScatterPlot(int width, int height,
        List<Instances> series, String xAxis, String yAxis,
        List<String> optionalArgs) throws Exception {
      
      BufferedImage osi = new BufferedImage(width, height, 
          BufferedImage.TYPE_INT_RGB);
      
      String plotTitle = "Scatter plot";
      String userTitle = getOption(optionalArgs, "-title");
      plotTitle = (userTitle != null) ? userTitle : plotTitle;
      
      Plot2D offScreenPlot = new Plot2D();
      offScreenPlot.setSize(width, height);
      
      // master plot
      PlotData2D master = new PlotData2D(series.get(0));
      master.setPlotName(plotTitle);
      master.m_displayAllPoints = true;
      
      offScreenPlot.setMasterPlot(master);
      
      Instances masterInstances = series.get(0);
      int xAx = getIndexOfAttribute(masterInstances, xAxis);
      int yAx = getIndexOfAttribute(masterInstances, yAxis);
      if (xAx < 0) {
        xAx = 0;
      }
      if (yAx < 0) {
        yAx = 0;
      }
      
      // plotting axes and color
      offScreenPlot.setXindex(xAx);
      offScreenPlot.setYindex(yAx);
      offScreenPlot.setCindex(masterInstances.numAttributes() - 1);
      String colorAtt = getOption(optionalArgs, "-color");
      int tempC = getIndexOfAttribute(masterInstances, colorAtt);
      if (tempC >= 0) {
        offScreenPlot.setCindex(tempC);
      }
      
      String hasErrors = getOption(optionalArgs, "-hasErrors");
      // master plot is the error cases
      if (hasErrors != null) {
        int[] plotShapes = new int[masterInstances.numInstances()];
        for (int i = 0; i < plotShapes.length; i++) {
          plotShapes[i] = Plot2D.ERROR_SHAPE;
        }
        master.setShapeType(plotShapes);
      }
      
      // look for  an additional attribute that stores the
      // shape sizes
      String shapeSize = getOption(optionalArgs, "-shapeSize");
      if (shapeSize != null && shapeSize.length() > 0) {
        int shapeSizeI = getIndexOfAttribute(masterInstances, shapeSize);
        
        if (shapeSizeI >= 0) {
          int[] plotSizes = new int[masterInstances.numInstances()];
          for (int i = 0; i < masterInstances.numInstances(); i++) {
            plotSizes[i] = (int)masterInstances.instance(i).value(shapeSizeI);
          }
          master.setShapeSize(plotSizes);
        }
      }        
      
      // additional plots
      if (series.size() > 1) {
        for (Instances plotI : series) {
          PlotData2D plotD = new PlotData2D(plotI);
          plotD.m_displayAllPoints = true;
  
          offScreenPlot.addPlot(plotD);
  
          if (shapeSize != null && shapeSize.length() > 0) {
            int shapeSizeI = getIndexOfAttribute(plotI, shapeSize);
            if (shapeSizeI >= 0) {
              int[] plotSizes = new int[plotI.numInstances()];
              for (int i = 0; i < plotI.numInstances(); i++) {
                plotSizes[i] = (int)plotI.instance(i).value(shapeSizeI);
              }
              plotD.setShapeSize(plotSizes);
            }
          }
          
          // all other plots will have x shape if master plot are errors
          if (hasErrors != null) {
            int[] plotShapes = new int[plotI.numInstances()];
            for (int i = 0; i < plotShapes.length; i++) {
              plotShapes[i] = Plot2D.X_SHAPE;
            }
            plotD.setShapeType(plotShapes);
          }
        }
      }
      
      // render
      java.awt.Graphics g = osi.getGraphics();
      offScreenPlot.paintComponent(g);
      
      return osi;
    }
  
    /**
     * Render histogram(s) (numeric attribute) or pie chart (nominal attribute).
     * Some implementations may not be able to render more than one histogram/pie
     * on the same chart - the implementation can either throw an exception or
     * just process the first series in this case.
     * 
     * This Default implementation uses Weka's built in VisualizeAttributePanel
     * to render with and, as such, can only render histograms. It produces
     * histograms for both numeric and nominal attributes.
     * 
     * @param width the width of the resulting chart in pixels
     * @param height the height of the resulting chart in pixels
     * @param series a list of Instances - one for each series to be plotted
     * @param attsToPlot the attribute to plot
     * corresponding to the Instances in the series list
     * @param optionalArgs optional arguments to the renderer (may be null)
     * 
     * @return a BufferedImage containing the chart
     * @throws Exception if there is a problem rendering the chart
     */
    public BufferedImage renderHistogram(int width, int height,
        List<Instances> series, String attToPlot,
        List<String> optionalArgs) throws Exception {
      
      BufferedImage osi = new BufferedImage(width, height, 
          BufferedImage.TYPE_INT_RGB);
      
      // we can only handle one series per plot so merge all series together
      Instances toPlot = new Instances(series.get(0));
      for (int i = 1; i < series.size(); i++) {
        Instances additional = series.get(i);
        for (Instance temp : additional) {
          toPlot.add(temp);
        }
      }
      
      int attIndex = getIndexOfAttribute(toPlot, attToPlot);
      if (attIndex < 0) {
        attIndex = 0;
      }
      
      String colorAtt = getOption(optionalArgs, "-color");
      int tempC = getIndexOfAttribute(toPlot, colorAtt);
      
      AttributeVisualizationPanel offScreenPlot = 
        new AttributeVisualizationPanel();
      offScreenPlot.setSize(width, height);
      offScreenPlot.setInstances(toPlot);
      offScreenPlot.setAttribute(attIndex);
      if (tempC >= 0) {
        offScreenPlot.setColoringIndex(tempC);
      }        
      
      // render
      java.awt.Graphics g = osi.getGraphics();
      offScreenPlot.paintComponent(g);
      // wait a little while so that the calculation thread can complete
      Thread.sleep(2000);
      offScreenPlot.paintComponent(g);
      
  //    offScreenPlot.setAttribute(attIndex);
      
      return osi;
    }
  }
  