Java tutorial
/* * 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 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * Plot2D.java * Copyright (C) 2000-2012 University of Waikato, Hamilton, New Zealand * */ package weka.gui.visualize; import weka.core.Environment; import weka.core.Instance; import weka.core.Instances; import weka.core.Settings; import weka.core.Utils; import javax.swing.JFrame; import javax.swing.JPanel; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.event.InputEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.Random; import java.util.Vector; /** * This class plots datasets in two dimensions. It can also plot classifier * errors and clusterer predictions. * * @author Mark Hall (mhall@cs.waikato.ac.nz) * @version $Revision$ */ public class Plot2D extends JPanel { /** for serialization */ private static final long serialVersionUID = -1673162410856660442L; /* constants for shape types */ public static final int MAX_SHAPES = 5; public static final int ERROR_SHAPE = 1000; public static final int MISSING_SHAPE = 2000; public static final int CONST_AUTOMATIC_SHAPE = -1; public static final int X_SHAPE = 0; public static final int PLUS_SHAPE = 1; public static final int DIAMOND_SHAPE = 2; public static final int TRIANGLEUP_SHAPE = 3; public static final int TRIANGLEDOWN_SHAPE = 4; public static final int DEFAULT_SHAPE_SIZE = 2; /** Default colour for the axis */ protected Color m_axisColour = Color.green; /** Default colour for the plot background */ protected Color m_backgroundColour = Color.black; /** The plots to display */ protected ArrayList<PlotData2D> m_plots = new ArrayList<PlotData2D>(); /** The master plot */ protected PlotData2D m_masterPlot = null; /** The name of the master plot */ protected String m_masterName = "master plot"; /** The instances to be plotted */ protected Instances m_plotInstances = null; /** * An optional "compainion" of the panel. If specified, this class will get to * do its thing with our graphics context before we do any drawing. Eg. the * visualize panel may need to draw polygons etc. before we draw plot axis and * data points */ protected Plot2DCompanion m_plotCompanion = null; /** the class for displaying instance info. */ protected Class<?> m_InstanceInfoFrameClass = null; /** For popping up text info on data points */ protected JFrame m_InstanceInfo = null; /** The list of the colors used */ protected ArrayList<Color> m_colorList; /** default colours for colouring discrete class */ protected Color[] m_DefaultColors = { Color.blue, Color.red, Color.green, Color.cyan, Color.pink, new Color(255, 0, 255), Color.orange, new Color(255, 0, 0), new Color(0, 255, 0), Color.white }; /** * Indexes of the attributes to go on the x and y axis and the attribute to * use for colouring and the current shape for drawing */ protected int m_xIndex = 0; protected int m_yIndex = 0; protected int m_cIndex = 0; protected int m_sIndex = 0; /** * Holds the min and max values of the x, y and colouring attributes over all * plots */ protected double m_maxX; protected double m_minX; protected double m_maxY; protected double m_minY; protected double m_maxC; protected double m_minC; /** Axis padding */ protected final int m_axisPad = 5; /** Tick size */ protected final int m_tickSize = 5; /** the offsets of the axes once label metrics are calculated */ protected int m_XaxisStart = 0; protected int m_YaxisStart = 0; protected int m_XaxisEnd = 0; protected int m_YaxisEnd = 0; /** * if the user resizes the window, or the attributes selected for the * attributes change, then the lookup table for points needs to be * recalculated */ protected boolean m_plotResize = true; /** if the user changes attribute assigned to an axis */ protected boolean m_axisChanged = false; /** * An array used to show if a point is hidden or not. This is used for * speeding up the drawing of the plot panel although I am not sure how much * performance this grants over not having it. */ protected int[][] m_drawnPoints; /** Font for labels */ protected Font m_labelFont; protected FontMetrics m_labelMetrics = null; /** the level of jitter */ protected int m_JitterVal = 0; /** random values for perterbing the data points */ protected Random m_JRand = new Random(0); /** Constructor */ public Plot2D() { super(); setProperties(); this.setBackground(m_backgroundColour); m_drawnPoints = new int[this.getWidth()][this.getHeight()]; /** Set up some default colours */ m_colorList = new ArrayList<Color>(10); for (int noa = m_colorList.size(); noa < 10; noa++) { Color pc = m_DefaultColors[noa % 10]; int ija = noa / 10; ija *= 2; for (int j = 0; j < ija; j++) { pc = pc.darker(); } m_colorList.add(pc); } } /** * Set the properties for Plot2D */ private void setProperties() { if (VisualizeUtils.VISUALIZE_PROPERTIES != null) { String thisClass = this.getClass().getName(); String axisKey = thisClass + ".axisColour"; String backgroundKey = thisClass + ".backgroundColour"; String axisColour = VisualizeUtils.VISUALIZE_PROPERTIES.getProperty(axisKey); if (axisColour == null) { /* * System.err.println("Warning: no configuration property found in " * +VisualizeUtils.PROPERTY_FILE +" for "+axisKey); */ } else { // System.err.println("Setting axis colour to: "+axisColour); m_axisColour = VisualizeUtils.processColour(axisColour, m_axisColour); } String backgroundColour = VisualizeUtils.VISUALIZE_PROPERTIES.getProperty(backgroundKey); if (backgroundColour == null) { /* * System.err.println("Warning: no configuration property found in " * +VisualizeUtils.PROPERTY_FILE +" for "+backgroundKey); */ } else { // System.err.println("Setting background colour to: "+backgroundColour); m_backgroundColour = VisualizeUtils.processColour(backgroundColour, m_backgroundColour); } try { m_InstanceInfoFrameClass = Class.forName(VisualizeUtils.VISUALIZE_PROPERTIES .getProperty(thisClass + ".instanceInfoFrame", "weka.gui.visualize.InstanceInfoFrame")); } catch (Exception e) { e.printStackTrace(); m_InstanceInfoFrameClass = InstanceInfoFrame.class; } } } /** * Apply settings * * @param settings the settings to apply * @param ownerID the ID of the owner perspective, panel etc. to use when * looking up our settings */ public void applySettings(Settings settings, String ownerID) { m_axisColour = settings.getSetting(ownerID, VisualizeUtils.VisualizeDefaults.AXIS_COLOUR_KEY, VisualizeUtils.VisualizeDefaults.AXIS_COLOR, Environment.getSystemWide()); m_backgroundColour = settings.getSetting(ownerID, VisualizeUtils.VisualizeDefaults.BACKGROUND_COLOUR_KEY, VisualizeUtils.VisualizeDefaults.BACKGROUND_COLOR, Environment.getSystemWide()); this.setBackground(m_backgroundColour); repaint(); } /** * Set a companion class. This is a class that might want to render something * on the plot before we do our thing. Eg, Malcolm's shape drawing stuff needs * to happen before we plot axis and points * * @param p a companion class */ public void setPlotCompanion(Plot2DCompanion p) { m_plotCompanion = p; } /** * Set level of jitter and repaint the plot using the new jitter value * * @param j the level of jitter */ public void setJitter(int j) { if (m_plotInstances.numAttributes() > 0 && m_plotInstances.numInstances() > 0) { if (j >= 0) { m_JitterVal = j; m_JRand = new Random(m_JitterVal); // if (m_pointLookup != null) { m_drawnPoints = new int[m_XaxisEnd - m_XaxisStart + 1][m_YaxisEnd - m_YaxisStart + 1]; updatePturb(); // } this.repaint(); } } } /** * Set a list of colours to use when colouring points according to class * values or cluster numbers * * @param cols the list of colours to use */ public void setColours(ArrayList<Color> cols) { m_colorList = cols; } /** * Set the index of the attribute to go on the x axis * * @param x the index of the attribute to use on the x axis */ public void setXindex(int x) { m_xIndex = x; for (int i = 0; i < m_plots.size(); i++) { m_plots.get(i).setXindex(m_xIndex); } determineBounds(); if (m_JitterVal != 0) { updatePturb(); } m_axisChanged = true; this.repaint(); } /** * Set the index of the attribute to go on the y axis * * @param y the index of the attribute to use on the y axis */ public void setYindex(int y) { m_yIndex = y; for (int i = 0; i < m_plots.size(); i++) { m_plots.get(i).setYindex(m_yIndex); } determineBounds(); if (m_JitterVal != 0) { updatePturb(); } m_axisChanged = true; this.repaint(); } /** * Set the index of the attribute to use for colouring * * @param c the index of the attribute to use for colouring */ public void setCindex(int c) { m_cIndex = c; for (int i = 0; i < m_plots.size(); i++) { m_plots.get(i).setCindex(m_cIndex); } determineBounds(); m_axisChanged = true; this.repaint(); } /** * Return the list of plots * * @return the list of plots */ public ArrayList<PlotData2D> getPlots() { return m_plots; } /** * Get the master plot * * @return the master plot */ public PlotData2D getMasterPlot() { return m_masterPlot; } /** * Return the current max value of the attribute plotted on the x axis * * @return the max x value */ public double getMaxX() { return m_maxX; } /** * Return the current max value of the attribute plotted on the y axis * * @return the max y value */ public double getMaxY() { return m_maxY; } /** * Return the current min value of the attribute plotted on the x axis * * @return the min x value */ public double getMinX() { return m_minX; } /** * Return the current min value of the attribute plotted on the y axis * * @return the min y value */ public double getMinY() { return m_minY; } /** * Return the current max value of the colouring attribute * * @return the max colour value */ public double getMaxC() { return m_maxC; } /** * Return the current min value of the colouring attribute * * @return the min colour value */ public double getMinC() { return m_minC; } /** * Sets the master plot from a set of instances * * @param inst the instances * @exception Exception if instances could not be set */ public void setInstances(Instances inst) throws Exception { // System.err.println("Setting Instances"); PlotData2D tempPlot = new PlotData2D(inst); tempPlot.setPlotName("master plot"); setMasterPlot(tempPlot); } /** * Set the master plot. * * @param master the plot to make the master plot * @exception Exception if the plot could not be set. */ public void setMasterPlot(PlotData2D master) throws Exception { if (master.m_plotInstances == null) { throw new Exception("No instances in plot data!"); } removeAllPlots(); m_masterPlot = master; m_plots.add(m_masterPlot); m_plotInstances = m_masterPlot.m_plotInstances; m_xIndex = 0; m_yIndex = 0; m_cIndex = 0; determineBounds(); } /** * Clears all plots */ public void removeAllPlots() { m_masterPlot = null; m_plotInstances = null; m_plots = new ArrayList<PlotData2D>(); m_xIndex = 0; m_yIndex = 0; m_cIndex = 0; } /** * Add a plot to the list of plots to display * * @param newPlot the new plot to add * @exception Exception if the plot could not be added */ public void addPlot(PlotData2D newPlot) throws Exception { if (newPlot.m_plotInstances == null) { throw new Exception("No instances in plot data!"); } if (m_masterPlot != null) { if (m_masterPlot.m_plotInstances.equalHeaders(newPlot.m_plotInstances) == false) { throw new Exception("Plot2D :Plot data's instances are incompatable " + " with master plot"); } } else { m_masterPlot = newPlot; m_plotInstances = m_masterPlot.m_plotInstances; } m_plots.add(newPlot); setXindex(m_xIndex); setYindex(m_yIndex); setCindex(m_cIndex); } /** * Set up fonts and font metrics * * @param gx the graphics context */ private void setFonts(Graphics gx) { if (m_labelMetrics == null) { m_labelFont = new Font("Monospaced", Font.PLAIN, 12); m_labelMetrics = gx.getFontMetrics(m_labelFont); } gx.setFont(m_labelFont); } /** * Pops up a window displaying attribute information on any instances at a * point+-plotting_point_size (in panel coordinates) * * @param x the x value of the clicked point * @param y the y value of the clicked point * @param newFrame true if instance info is to be displayed in a new frame. */ public void searchPoints(int x, int y, final boolean newFrame) { if (m_masterPlot.m_plotInstances != null) { int longest = 0; for (int j = 0; j < m_masterPlot.m_plotInstances.numAttributes(); j++) { if (m_masterPlot.m_plotInstances.attribute(j).name().length() > longest) { longest = m_masterPlot.m_plotInstances.attribute(j).name().length(); } } StringBuffer insts = new StringBuffer(); Vector<Instances> data = new Vector<Instances>(); for (int jj = 0; jj < m_plots.size(); jj++) { PlotData2D temp_plot = (m_plots.get(jj)); data.add(new Instances(temp_plot.m_plotInstances, 0)); for (int i = 0; i < temp_plot.m_plotInstances.numInstances(); i++) { if (temp_plot.m_pointLookup[i][0] != Double.NEGATIVE_INFINITY) { double px = temp_plot.m_pointLookup[i][0] + temp_plot.m_pointLookup[i][2]; double py = temp_plot.m_pointLookup[i][1] + temp_plot.m_pointLookup[i][3]; // double size = temp_plot.m_pointLookup[i][2]; double size = temp_plot.m_shapeSize[i]; if ((x >= px - size) && (x <= px + size) && (y >= py - size) && (y <= py + size)) { { data.get(jj).add((Instance) temp_plot.m_plotInstances.instance(i).copy()); insts.append("\nPlot : " + temp_plot.m_plotName + "\nInstance: " + (i + 1) + "\n"); if (temp_plot.m_plotInstances.instance(i).weight() != 1.0) { insts.append( "Weight : " + temp_plot.m_plotInstances.instance(i).weight() + "\n"); } for (int j = 0; j < temp_plot.m_plotInstances.numAttributes(); j++) { for (int k = 0; k < (longest - temp_plot.m_plotInstances.attribute(j).name().length()); k++) { insts.append(" "); } insts.append(temp_plot.m_plotInstances.attribute(j).name()); insts.append(" : "); if (temp_plot.m_plotInstances.instance(i).isMissing(j)) { insts.append("Missing"); } else if (temp_plot.m_plotInstances.attribute(j).isNominal() || temp_plot.m_plotInstances.attribute(j).isString()) { insts.append(temp_plot.m_plotInstances.attribute(j) .value((int) temp_plot.m_plotInstances.instance(i).value(j))); } else { insts.append(temp_plot.m_plotInstances.instance(i).value(j)); } insts.append("\n"); } } } } } } // remove datasets that contain no instances int i = 0; while (data.size() > i) { if (data.get(i).numInstances() == 0) { data.remove(i); } else { i++; } } if (insts.length() > 0) { // Pop up a new frame if (newFrame || m_InstanceInfo == null) { try { final JFrame jf = (JFrame) m_InstanceInfoFrameClass.newInstance(); ((InstanceInfo) jf).setInfoText(insts.toString()); ((InstanceInfo) jf).setInfoData(data); final JFrame testf = m_InstanceInfo; jf.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { if (!newFrame || testf == null) { m_InstanceInfo = null; } jf.dispose(); } }); jf.setVisible(true); if (m_InstanceInfo == null) { m_InstanceInfo = jf; } } catch (Exception e) { e.printStackTrace(); } } else { // Overwrite info in existing frame ((InstanceInfo) m_InstanceInfo).setInfoText(insts.toString()); ((InstanceInfo) m_InstanceInfo).setInfoData(data); } } } } /** * Determine the min and max values for axis and colouring attributes */ public void determineBounds() { double value; // , min, max; NOT USED // find maximums minimums over all plots m_minX = m_plots.get(0).m_minX; m_maxX = m_plots.get(0).m_maxX; m_minY = m_plots.get(0).m_minY; m_maxY = m_plots.get(0).m_maxY; m_minC = m_plots.get(0).m_minC; m_maxC = m_plots.get(0).m_maxC; for (int i = 1; i < m_plots.size(); i++) { value = m_plots.get(i).m_minX; if (value < m_minX) { m_minX = value; } value = m_plots.get(i).m_maxX; if (value > m_maxX) { m_maxX = value; } value = m_plots.get(i).m_minY; if (value < m_minY) { m_minY = value; } value = m_plots.get(i).m_maxY; if (value > m_maxY) { m_maxY = value; } value = m_plots.get(i).m_minC; if (value < m_minC) { m_minC = value; } value = m_plots.get(i).m_maxC; if (value > m_maxC) { m_maxC = value; } } fillLookup(); this.repaint(); } // to convert screen coords to attrib values // note that I use a double to avoid accuracy // headaches with ints /** * convert a Panel x coordinate to a raw x value. * * @param scx The Panel x coordinate * @return A raw x value. */ public double convertToAttribX(double scx) { double temp = m_XaxisEnd - m_XaxisStart; double temp2 = ((scx - m_XaxisStart) * (m_maxX - m_minX)) / temp; temp2 = temp2 + m_minX; return temp2; } /** * convert a Panel y coordinate to a raw y value. * * @param scy The Panel y coordinate * @return A raw y value. */ public double convertToAttribY(double scy) { double temp = m_YaxisEnd - m_YaxisStart; double temp2 = ((scy - m_YaxisEnd) * (m_maxY - m_minY)) / temp; temp2 = -(temp2 - m_minY); return temp2; } // //// /** * returns a value by which an x value can be peturbed. Makes sure that the x * value+pturb stays within the plot bounds * * @param xvalP the x coordinate to be peturbed * @param xj a random number to use in calculating a peturb value * @return a peturb value */ int pturbX(double xvalP, double xj) { int xpturb = 0; if (m_JitterVal > 0) { xpturb = (int) (m_JitterVal * (xj / 2.0)); if (((xvalP + xpturb) < m_XaxisStart) || ((xvalP + xpturb) > m_XaxisEnd)) { xpturb *= -1; } } return xpturb; } /** * Convert an raw x value to Panel x coordinate. * * @param xval the raw x value * @return an x value for plotting in the panel. */ public double convertToPanelX(double xval) { double temp = (xval - m_minX) / (m_maxX - m_minX); double temp2 = temp * (m_XaxisEnd - m_XaxisStart); temp2 = temp2 + m_XaxisStart; return temp2; } /** * returns a value by which a y value can be peturbed. Makes sure that the y * value+pturb stays within the plot bounds * * @param yvalP the y coordinate to be peturbed * @param yj a random number to use in calculating a peturb value * @return a peturb value */ int pturbY(double yvalP, double yj) { int ypturb = 0; if (m_JitterVal > 0) { ypturb = (int) (m_JitterVal * (yj / 2.0)); if (((yvalP + ypturb) < m_YaxisStart) || ((yvalP + ypturb) > m_YaxisEnd)) { ypturb *= -1; } } return ypturb; } /** * Convert an raw y value to Panel y coordinate. * * @param yval the raw y value * @return an y value for plotting in the panel. */ public double convertToPanelY(double yval) { double temp = (yval - m_minY) / (m_maxY - m_minY); double temp2 = temp * (m_YaxisEnd - m_YaxisStart); temp2 = m_YaxisEnd - temp2; return temp2; } /** * Draws an X. * * @param gx the graphics context * @param x the x coord * @param y the y coord * @param size the size of the shape */ private static void drawX(Graphics gx, double x, double y, int size) { gx.drawLine((int) (x - size), (int) (y - size), (int) (x + size), (int) (y + size)); gx.drawLine((int) (x + size), (int) (y - size), (int) (x - size), (int) (y + size)); } /** * Draws a plus. * * @param gx the graphics context * @param x the x coord * @param y the y coord * @param size the size of the shape */ private static void drawPlus(Graphics gx, double x, double y, int size) { gx.drawLine((int) (x - size), (int) (y), (int) (x + size), (int) (y)); gx.drawLine((int) (x), (int) (y - size), (int) (x), (int) (y + size)); } /** * Draws a diamond. * * @param gx the graphics context * @param x the x coord * @param y the y coord * @param size the size of the shape */ private static void drawDiamond(Graphics gx, double x, double y, int size) { gx.drawLine((int) (x - size), (int) (y), (int) (x), (int) (y - size)); gx.drawLine((int) (x), (int) (y - size), (int) (x + size), (int) (y)); gx.drawLine((int) (x + size), (int) (y), (int) (x), (int) (y + size)); gx.drawLine((int) (x), (int) (y + size), (int) (x - size), (int) (y)); } /** * Draws an triangle (point at top). * * @param gx the graphics context * @param x the x coord * @param y the y coord * @param size the size of the shape */ private static void drawTriangleUp(Graphics gx, double x, double y, int size) { gx.drawLine((int) (x), (int) (y - size), (int) (x - size), (int) (y + size)); gx.drawLine((int) (x - size), (int) (y + size), (int) (x + size), (int) (y + size)); gx.drawLine((int) (x + size), (int) (y + size), (int) (x), (int) (y - size)); } /** * Draws an triangle (point at bottom). * * @param gx the graphics context * @param x the x coord * @param y the y coord * @param size the size of the shape */ private static void drawTriangleDown(Graphics gx, double x, double y, int size) { gx.drawLine((int) (x), (int) (y + size), (int) (x - size), (int) (y - size)); gx.drawLine((int) (x - size), (int) (y - size), (int) (x + size), (int) (y - size)); gx.drawLine((int) (x + size), (int) (y - size), (int) (x), (int) (y + size)); } /** * Draws a data point at a given set of panel coordinates at a given size and * connects a line to the previous point. * * @param x the x coord * @param y the y coord * @param xprev the x coord of the previous point * @param yprev the y coord of the previous point * @param size the size of the point * @param shape the shape of the data point (square is reserved for nominal * error data points). Shapes: 0=x, 1=plus, 2=diamond, * 3=triangle(up), 4 = triangle (down). * @param gx the graphics context */ protected static void drawDataPoint(double x, double y, double xprev, double yprev, int size, int shape, Graphics gx) { drawDataPoint(x, y, size, shape, gx); // connect a line to the previous point gx.drawLine((int) x, (int) y, (int) xprev, (int) yprev); } /** * Draws a data point at a given set of panel coordinates at a given size. * * @param x the x coord * @param y the y coord * @param size the size of the point * @param shape the shape of the data point (square is reserved for nominal * error data points). Shapes: 0=x, 1=plus, 2=diamond, * 3=triangle(up), 4 = triangle (down). * @param gx the graphics context */ protected static void drawDataPoint(double x, double y, int size, int shape, Graphics gx) { Font lf = new Font("Monospaced", Font.PLAIN, 12); FontMetrics fm = gx.getFontMetrics(lf); if (size == 0) { size = 1; } if (shape != ERROR_SHAPE && shape != MISSING_SHAPE) { shape = shape % 5; } switch (shape) { case X_SHAPE: drawX(gx, x, y, size); break; case PLUS_SHAPE: drawPlus(gx, x, y, size); break; case DIAMOND_SHAPE: drawDiamond(gx, x, y, size); break; case TRIANGLEUP_SHAPE: drawTriangleUp(gx, x, y, size); break; case TRIANGLEDOWN_SHAPE: drawTriangleDown(gx, x, y, size); break; case ERROR_SHAPE: // draws the nominal error shape gx.drawRect((int) (x - size), (int) (y - size), (size * 2), (size * 2)); break; case MISSING_SHAPE: int hf = fm.getAscent(); int width = fm.stringWidth("M"); gx.drawString("M", (int) (x - (width / 2)), (int) (y + (hf / 2))); break; } } /** * Updates the perturbed values for the plots when the jitter value is changed */ private void updatePturb() { double xj = 0; double yj = 0; for (int j = 0; j < m_plots.size(); j++) { PlotData2D temp_plot = (m_plots.get(j)); for (int i = 0; i < temp_plot.m_plotInstances.numInstances(); i++) { if (temp_plot.m_plotInstances.instance(i).isMissing(m_xIndex) || temp_plot.m_plotInstances.instance(i).isMissing(m_yIndex)) { } else { if (m_JitterVal > 0) { xj = m_JRand.nextGaussian(); yj = m_JRand.nextGaussian(); } temp_plot.m_pointLookup[i][2] = pturbX(temp_plot.m_pointLookup[i][0], xj); temp_plot.m_pointLookup[i][3] = pturbY(temp_plot.m_pointLookup[i][1], yj); } } } } /** * Fills the lookup caches for the plots. Also calculates errors for numeric * predictions (if any) in plots */ private void fillLookup() { for (int j = 0; j < m_plots.size(); j++) { PlotData2D temp_plot = (m_plots.get(j)); if (temp_plot.m_plotInstances.numInstances() > 0 && temp_plot.m_plotInstances.numAttributes() > 0) { for (int i = 0; i < temp_plot.m_plotInstances.numInstances(); i++) { if (temp_plot.m_plotInstances.instance(i).isMissing(m_xIndex) || temp_plot.m_plotInstances.instance(i).isMissing(m_yIndex)) { temp_plot.m_pointLookup[i][0] = Double.NEGATIVE_INFINITY; temp_plot.m_pointLookup[i][1] = Double.NEGATIVE_INFINITY; } else { double x = convertToPanelX(temp_plot.m_plotInstances.instance(i).value(m_xIndex)); double y = convertToPanelY(temp_plot.m_plotInstances.instance(i).value(m_yIndex)); temp_plot.m_pointLookup[i][0] = x; temp_plot.m_pointLookup[i][1] = y; } } } } } /** * Draws the data points and predictions (if provided). * * @param gx the graphics context */ private void paintData(Graphics gx) { for (int j = 0; j < m_plots.size(); j++) { PlotData2D temp_plot = (m_plots.get(j)); for (int i = 0; i < temp_plot.m_plotInstances.numInstances(); i++) { if (temp_plot.m_plotInstances.instance(i).isMissing(m_xIndex) || temp_plot.m_plotInstances.instance(i).isMissing(m_yIndex)) { } else { double x = (temp_plot.m_pointLookup[i][0] + temp_plot.m_pointLookup[i][2]); double y = (temp_plot.m_pointLookup[i][1] + temp_plot.m_pointLookup[i][3]); double prevx = 0; double prevy = 0; if (i > 0) { prevx = (temp_plot.m_pointLookup[i - 1][0] + temp_plot.m_pointLookup[i - 1][2]); prevy = (temp_plot.m_pointLookup[i - 1][1] + temp_plot.m_pointLookup[i - 1][3]); } int x_range = (int) x - m_XaxisStart; int y_range = (int) y - m_YaxisStart; if (x_range >= 0 && y_range >= 0) { if (m_drawnPoints[x_range][y_range] == i || m_drawnPoints[x_range][y_range] == 0 || temp_plot.m_shapeSize[i] == temp_plot.m_alwaysDisplayPointsOfThisSize || temp_plot.m_displayAllPoints == true) { m_drawnPoints[x_range][y_range] = i; if (temp_plot.m_plotInstances.attribute(m_cIndex).isNominal()) { if (temp_plot.m_plotInstances.attribute(m_cIndex).numValues() > m_colorList.size() && !temp_plot.m_useCustomColour) { extendColourMap(temp_plot.m_plotInstances.attribute(m_cIndex).numValues()); } Color ci; if (temp_plot.m_plotInstances.instance(i).isMissing(m_cIndex)) { ci = Color.gray; } else { int ind = (int) temp_plot.m_plotInstances.instance(i).value(m_cIndex); ci = m_colorList.get(ind); } if (!temp_plot.m_useCustomColour) { gx.setColor(ci); } else { gx.setColor(temp_plot.m_customColour); } if (temp_plot.m_plotInstances.instance(i).isMissing(m_cIndex)) { if (temp_plot.m_connectPoints[i] == true) { drawDataPoint(x, y, prevx, prevy, temp_plot.m_shapeSize[i], MISSING_SHAPE, gx); } else { drawDataPoint(x, y, temp_plot.m_shapeSize[i], MISSING_SHAPE, gx); } } else { if (temp_plot.m_shapeType[i] == CONST_AUTOMATIC_SHAPE) { if (temp_plot.m_connectPoints[i] == true) { drawDataPoint(x, y, prevx, prevy, temp_plot.m_shapeSize[i], j, gx); } else { drawDataPoint(x, y, temp_plot.m_shapeSize[i], j, gx); } } else { if (temp_plot.m_connectPoints[i] == true) { drawDataPoint(x, y, prevx, prevy, temp_plot.m_shapeSize[i], temp_plot.m_shapeType[i], gx); } else { drawDataPoint(x, y, temp_plot.m_shapeSize[i], temp_plot.m_shapeType[i], gx); } } } } else { double r; Color ci = null; if (!temp_plot.m_plotInstances.instance(i).isMissing(m_cIndex)) { r = (temp_plot.m_plotInstances.instance(i).value(m_cIndex) - m_minC) / (m_maxC - m_minC); r = (r * 240) + 15; ci = new Color((int) r, 150, (int) (255 - r)); } else { ci = Color.gray; } if (!temp_plot.m_useCustomColour) { gx.setColor(ci); } else { gx.setColor(temp_plot.m_customColour); } if (temp_plot.m_plotInstances.instance(i).isMissing(m_cIndex)) { if (temp_plot.m_connectPoints[i] == true) { drawDataPoint(x, y, prevx, prevy, temp_plot.m_shapeSize[i], MISSING_SHAPE, gx); } else { drawDataPoint(x, y, temp_plot.m_shapeSize[i], MISSING_SHAPE, gx); } } else { if (temp_plot.m_shapeType[i] == CONST_AUTOMATIC_SHAPE) { if (temp_plot.m_connectPoints[i] == true) { drawDataPoint(x, y, prevx, prevy, temp_plot.m_shapeSize[i], j, gx); } else { drawDataPoint(x, y, temp_plot.m_shapeSize[i], j, gx); } } else { if (temp_plot.m_connectPoints[i] == true) { drawDataPoint(x, y, prevx, prevy, temp_plot.m_shapeSize[i], temp_plot.m_shapeType[i], gx); } else { drawDataPoint(x, y, temp_plot.m_shapeSize[i], temp_plot.m_shapeType[i], gx); } } } } } } } } } } /* * public void determineAxisPositions(Graphics gx) { setFonts(gx); int mxs = * m_XaxisStart; int mxe = m_XaxisEnd; int mys = m_YaxisStart; int mye = * m_YaxisEnd; m_axisChanged = false; * * int h = this.getHeight(); int w = this.getWidth(); int hf = * m_labelMetrics.getAscent(); int mswx=0; int mswy=0; * * // determineBounds(); int fieldWidthX = * (int)((Math.log(m_maxX)/Math.log(10)))+1; int precisionX = 1; if * ((Math.abs(m_maxX-m_minX) < 1) && ((m_maxY-m_minX) != 0)) { precisionX = * (int)Math.abs(((Math.log(Math.abs(m_maxX-m_minX)) / Math.log(10))))+1; } * String maxStringX = Utils.doubleToString(m_maxX, fieldWidthX+1+precisionX * ,precisionX); mswx = m_labelMetrics.stringWidth(maxStringX); int * fieldWidthY = (int)((Math.log(m_maxY)/Math.log(10)))+1; int precisionY = 1; * if (Math.abs((m_maxY-m_minY)) < 1 && ((m_maxY-m_minY) != 0)) { precisionY = * (int)Math.abs(((Math.log(Math.abs(m_maxY-m_minY)) / Math.log(10))))+1; } * String maxStringY = Utils.doubleToString(m_maxY, fieldWidthY+1+precisionY * ,precisionY); String minStringY = Utils.doubleToString(m_minY, * fieldWidthY+1+precisionY ,precisionY); * * if (m_plotInstances.attribute(m_yIndex).isNumeric()) { mswy = * (m_labelMetrics.stringWidth(maxStringY) > * m_labelMetrics.stringWidth(minStringY)) ? * m_labelMetrics.stringWidth(maxStringY) : * m_labelMetrics.stringWidth(minStringY); } else { mswy = * m_labelMetrics.stringWidth("MM"); } * * m_YaxisStart = m_axisPad; m_XaxisStart = 0+m_axisPad+m_tickSize+mswy; * * m_XaxisEnd = w-m_axisPad-(mswx/2); * * m_YaxisEnd = h-m_axisPad-(2 * hf)-m_tickSize; } */ /** * Draws the axis and a spectrum if the colouring attribute is numeric * * @param gx the graphics context */ private void paintAxis(Graphics gx) { setFonts(gx); int mxs = m_XaxisStart; int mxe = m_XaxisEnd; int mys = m_YaxisStart; int mye = m_YaxisEnd; m_plotResize = false; int h = this.getHeight(); int w = this.getWidth(); int hf = m_labelMetrics.getAscent(); int mswx = 0; int mswy = 0; // determineBounds(); int precisionXmax = 1; int precisionXmin = 1; int precisionXmid = 1; /* * if ((Math.abs(m_maxX-m_minX) < 1) && ((m_maxY-m_minX) != 0)) { precisionX * = (int)Math.abs(((Math.log(Math.abs(m_maxX-m_minX)) / Math.log(10))))+1; * } */ int whole = (int) Math.abs(m_maxX); double decimal = Math.abs(m_maxX) - whole; int nondecimal; nondecimal = (whole > 0) ? (int) (Math.log(whole) / Math.log(10)) : 1; precisionXmax = (decimal > 0) ? (int) Math.abs(((Math.log(Math.abs(m_maxX)) / Math.log(10)))) + 2 : 1; if (precisionXmax > VisualizeUtils.MAX_PRECISION) { precisionXmax = 1; } String maxStringX = Utils.doubleToString(m_maxX, nondecimal + 1 + precisionXmax, precisionXmax); whole = (int) Math.abs(m_minX); decimal = Math.abs(m_minX) - whole; nondecimal = (whole > 0) ? (int) (Math.log(whole) / Math.log(10)) : 1; precisionXmin = (decimal > 0) ? (int) Math.abs(((Math.log(Math.abs(m_minX)) / Math.log(10)))) + 2 : 1; if (precisionXmin > VisualizeUtils.MAX_PRECISION) { precisionXmin = 1; } String minStringX = Utils.doubleToString(m_minX, nondecimal + 1 + precisionXmin, precisionXmin); mswx = m_labelMetrics.stringWidth(maxStringX); int precisionYmax = 1; int precisionYmin = 1; int precisionYmid = 1; whole = (int) Math.abs(m_maxY); decimal = Math.abs(m_maxY) - whole; nondecimal = (whole > 0) ? (int) (Math.log(whole) / Math.log(10)) : 1; precisionYmax = (decimal > 0) ? (int) Math.abs(((Math.log(Math.abs(m_maxY)) / Math.log(10)))) + 2 : 1; if (precisionYmax > VisualizeUtils.MAX_PRECISION) { precisionYmax = 1; } String maxStringY = Utils.doubleToString(m_maxY, nondecimal + 1 + precisionYmax, precisionYmax); whole = (int) Math.abs(m_minY); decimal = Math.abs(m_minY) - whole; nondecimal = (whole > 0) ? (int) (Math.log(whole) / Math.log(10)) : 1; precisionYmin = (decimal > 0) ? (int) Math.abs(((Math.log(Math.abs(m_minY)) / Math.log(10)))) + 2 : 1; if (precisionYmin > VisualizeUtils.MAX_PRECISION) { precisionYmin = 1; } String minStringY = Utils.doubleToString(m_minY, nondecimal + 1 + precisionYmin, precisionYmin); if (m_plotInstances.attribute(m_yIndex).isNumeric()) { mswy = (m_labelMetrics.stringWidth(maxStringY) > m_labelMetrics.stringWidth(minStringY)) ? m_labelMetrics.stringWidth(maxStringY) : m_labelMetrics.stringWidth(minStringY); mswy += m_labelMetrics.stringWidth("M"); } else { mswy = m_labelMetrics.stringWidth("MM"); } m_YaxisStart = m_axisPad; m_XaxisStart = 0 + m_axisPad + m_tickSize + mswy; m_XaxisEnd = w - m_axisPad - (mswx / 2); m_YaxisEnd = h - m_axisPad - (2 * hf) - m_tickSize; // draw axis gx.setColor(m_axisColour); if (m_plotInstances.attribute(m_xIndex).isNumeric()) { if (w > (2 * mswx)) { gx.drawString(maxStringX, m_XaxisEnd - (mswx / 2), m_YaxisEnd + hf + m_tickSize); mswx = m_labelMetrics.stringWidth(minStringX); gx.drawString(minStringX, (m_XaxisStart - (mswx / 2)), m_YaxisEnd + hf + m_tickSize); // draw the middle value if (w > (3 * mswx) && (m_plotInstances.attribute(m_xIndex).isNumeric())) { double mid = m_minX + ((m_maxX - m_minX) / 2.0); whole = (int) Math.abs(mid); decimal = Math.abs(mid) - whole; nondecimal = (whole > 0) ? (int) (Math.log(whole) / Math.log(10)) : 1; precisionXmid = (decimal > 0) ? (int) Math.abs(((Math.log(Math.abs(mid)) / Math.log(10)))) + 2 : 1; if (precisionXmid > VisualizeUtils.MAX_PRECISION) { precisionXmid = 1; } String maxString = Utils.doubleToString(mid, nondecimal + 1 + precisionXmid, precisionXmid); int sw = m_labelMetrics.stringWidth(maxString); double mx = m_XaxisStart + ((m_XaxisEnd - m_XaxisStart) / 2.0); gx.drawString(maxString, (int) (mx - ((sw) / 2.0)), m_YaxisEnd + hf + m_tickSize); gx.drawLine((int) mx, m_YaxisEnd, (int) mx, m_YaxisEnd + m_tickSize); } } } else { int numValues = m_plotInstances.attribute(m_xIndex).numValues(); int maxXStringWidth = (m_XaxisEnd - m_XaxisStart) / numValues; for (int i = 0; i < numValues; i++) { String val = m_plotInstances.attribute(m_xIndex).value(i); int sw = m_labelMetrics.stringWidth(val); int rm; // truncate string if necessary if (sw > maxXStringWidth) { int incr = (sw / val.length()); rm = (sw - maxXStringWidth) / incr; if (rm == 0) { rm = 1; } val = val.substring(0, val.length() - rm); sw = m_labelMetrics.stringWidth(val); } if (i == 0) { gx.drawString(val, (int) convertToPanelX(i), m_YaxisEnd + hf + m_tickSize); } else if (i == numValues - 1) { if ((i % 2) == 0) { gx.drawString(val, m_XaxisEnd - sw, m_YaxisEnd + hf + m_tickSize); } else { gx.drawString(val, m_XaxisEnd - sw, m_YaxisEnd + (2 * hf) + m_tickSize); } } else { if ((i % 2) == 0) { gx.drawString(val, (int) convertToPanelX(i) - (sw / 2), m_YaxisEnd + hf + m_tickSize); } else { gx.drawString(val, (int) convertToPanelX(i) - (sw / 2), m_YaxisEnd + (2 * hf) + m_tickSize); } } gx.drawLine((int) convertToPanelX(i), m_YaxisEnd, (int) convertToPanelX(i), m_YaxisEnd + m_tickSize); } } // draw the y axis if (m_plotInstances.attribute(m_yIndex).isNumeric()) { if (h > (2 * hf)) { gx.drawString(maxStringY, m_XaxisStart - mswy - m_tickSize, m_YaxisStart + (hf)); gx.drawString(minStringY, (m_XaxisStart - mswy - m_tickSize), m_YaxisEnd); // draw the middle value if (w > (3 * hf) && (m_plotInstances.attribute(m_yIndex).isNumeric())) { double mid = m_minY + ((m_maxY - m_minY) / 2.0); whole = (int) Math.abs(mid); decimal = Math.abs(mid) - whole; nondecimal = (whole > 0) ? (int) (Math.log(whole) / Math.log(10)) : 1; precisionYmid = (decimal > 0) ? (int) Math.abs(((Math.log(Math.abs(mid)) / Math.log(10)))) + 2 : 1; if (precisionYmid > VisualizeUtils.MAX_PRECISION) { precisionYmid = 1; } String maxString = Utils.doubleToString(mid, nondecimal + 1 + precisionYmid, precisionYmid); int sw = m_labelMetrics.stringWidth(maxString); double mx = m_YaxisStart + ((m_YaxisEnd - m_YaxisStart) / 2.0); gx.drawString(maxString, m_XaxisStart - sw - m_tickSize - 1, (int) (mx + ((hf) / 2.0))); gx.drawLine(m_XaxisStart - m_tickSize, (int) mx, m_XaxisStart, (int) mx); } } } else { int numValues = m_plotInstances.attribute(m_yIndex).numValues(); int div = ((numValues % 2) == 0) ? (numValues / 2) : (numValues / 2 + 1); int maxYStringHeight = (m_YaxisEnd - m_XaxisStart) / div; int sw = m_labelMetrics.stringWidth("M"); for (int i = 0; i < numValues; i++) { // can we at least print 2 characters if (maxYStringHeight >= (2 * hf)) { String val = m_plotInstances.attribute(m_yIndex).value(i); int numPrint = ((maxYStringHeight / hf) > val.length()) ? val.length() : (maxYStringHeight / hf); for (int j = 0; j < numPrint; j++) { String ll = val.substring(j, j + 1); if (val.charAt(j) == '_' || val.charAt(j) == '-') { ll = "|"; } if (i == 0) { gx.drawString(ll, m_XaxisStart - sw - m_tickSize - 1, (int) convertToPanelY(i) - ((numPrint - 1) * hf) + (j * hf) + (hf / 2)); } else if (i == (numValues - 1)) { if ((i % 2) == 0) { gx.drawString(ll, m_XaxisStart - sw - m_tickSize - 1, (int) convertToPanelY(i) + (j * hf) + (hf / 2)); } else { gx.drawString(ll, m_XaxisStart - (2 * sw) - m_tickSize - 1, (int) convertToPanelY(i) + (j * hf) + (hf / 2)); } } else { if ((i % 2) == 0) { gx.drawString(ll, m_XaxisStart - sw - m_tickSize - 1, (int) convertToPanelY(i) - (((numPrint - 1) * hf) / 2) + (j * hf) + (hf / 2)); } else { gx.drawString(ll, m_XaxisStart - (2 * sw) - m_tickSize - 1, (int) convertToPanelY(i) - (((numPrint - 1) * hf) / 2) + (j * hf) + (hf / 2)); } } } } gx.drawLine(m_XaxisStart - m_tickSize, (int) convertToPanelY(i), m_XaxisStart, (int) convertToPanelY(i)); } } gx.drawLine(m_XaxisStart, m_YaxisStart, m_XaxisStart, m_YaxisEnd); gx.drawLine(m_XaxisStart, m_YaxisEnd, m_XaxisEnd, m_YaxisEnd); if (m_XaxisStart != mxs || m_XaxisEnd != mxe || m_YaxisStart != mys || m_YaxisEnd != mye) { m_plotResize = true; } } /** * Add more colours to the colour map */ private void extendColourMap(int highest) { // System.err.println("Extending colour map"); for (int i = m_colorList.size(); i < highest; i++) { Color pc = m_DefaultColors[i % 10]; int ija = i / 10; ija *= 2; for (int j = 0; j < ija; j++) { pc = pc.brighter(); } m_colorList.add(pc); } } /** * Renders this component * * @param gx the graphics context */ @Override public void paintComponent(Graphics gx) { super.paintComponent(gx); if (m_plotInstances != null && m_plotInstances.numInstances() > 0 && m_plotInstances.numAttributes() > 0) { if (m_plotCompanion != null) { m_plotCompanion.prePlot(gx); } m_JRand = new Random(m_JitterVal); paintAxis(gx); if (m_axisChanged || m_plotResize) { int x_range = m_XaxisEnd - m_XaxisStart; int y_range = m_YaxisEnd - m_YaxisStart; if (x_range < 10) { x_range = 10; } if (y_range < 10) { y_range = 10; } m_drawnPoints = new int[x_range + 1][y_range + 1]; fillLookup(); m_plotResize = false; m_axisChanged = false; } paintData(gx); } } protected static Color checkAgainstBackground(Color c, Color background) { if (background == null) { return c; } if (c.equals(background)) { int red = c.getRed(); int blue = c.getBlue(); int green = c.getGreen(); red += (red < 128) ? (255 - red) / 2 : -(red / 2); blue += (blue < 128) ? (blue - red) / 2 : -(blue / 2); green += (green < 128) ? (255 - green) / 2 : -(green / 2); c = new Color(red, green, blue); } return c; } /** * Main method for testing this class * * @param args arguments */ public static void main(String[] args) { try { if (args.length < 1) { System.err.println("Usage : weka.gui.visualize.Plot2D " + "<dataset> [<dataset> <dataset>...]"); System.exit(1); } final javax.swing.JFrame jf = new javax.swing.JFrame("Weka Explorer: Visualize"); jf.setSize(500, 400); jf.getContentPane().setLayout(new BorderLayout()); final Plot2D p2 = new Plot2D(); jf.getContentPane().add(p2, BorderLayout.CENTER); jf.addWindowListener(new java.awt.event.WindowAdapter() { @Override public void windowClosing(java.awt.event.WindowEvent e) { jf.dispose(); System.exit(0); } }); p2.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) { p2.searchPoints(e.getX(), e.getY(), false); } else { p2.searchPoints(e.getX(), e.getY(), true); } } }); jf.setVisible(true); if (args.length >= 1) { for (int j = 0; j < args.length; j++) { System.err.println("Loading instances from " + args[j]); java.io.Reader r = new java.io.BufferedReader(new java.io.FileReader(args[j])); Instances i = new Instances(r); i.setClassIndex(i.numAttributes() - 1); PlotData2D pd1 = new PlotData2D(i); if (j == 0) { pd1.setPlotName("Master plot"); p2.setMasterPlot(pd1); p2.setXindex(2); p2.setYindex(3); p2.setCindex(i.classIndex()); } else { pd1.setPlotName("Plot " + (j + 1)); pd1.m_useCustomColour = true; pd1.m_customColour = (j % 2 == 0) ? Color.red : Color.blue; p2.addPlot(pd1); } } } } catch (Exception ex) { ex.printStackTrace(); System.err.println(ex.getMessage()); } } }