/* * 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.viewer; import java.awt.BorderLayout; import java.awt.Color; import java.awt.FlowLayout; 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.Collections; import java.util.Iterator; import java.util.LinkedList; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import com.rapidminer.gui.plotter.PlotterAdapter; import com.rapidminer.gui.tools.SwingTools; import com.rapidminer.operator.similarity.SimilarityMatrix; import com.rapidminer.operator.similarity.SimilarityMeasure; import com.rapidminer.tools.LogService; /** * A k-distance visualization for similarities. * * @author Peter B. Volk, Michael Wurst, Ingo Mierswa * @version $Id: SimilarityKDistanceVisualization.java,v 1.7 2008/07/07 07:06:37 ingomierswa Exp $ */ public class SimilarityKDistanceVisualization extends PlotterAdapter implements ActionListener { private static final long serialVersionUID = -3774235976141625821L; private static final int DEFAULT_K_NUMBER = 3; private static final int LABEL_MARGIN_X = 50; private static final int LABEL_MARGIN_Y = 15; private static final Font SCALED_LABEL_FONT = LABEL_FONT.deriveFont(AffineTransform.getScaleInstance(1, -1)); private double minX, maxX, minY, maxY, xTicSize, yTicSize; private SimilarityMatrix simMatrix = null; private SimilarityMeasure sim = null; private JTextField k_distance_jtext; private int updatePanelHeight; private LinkedList<Double> kDistanceValues; private int k = DEFAULT_K_NUMBER; public SimilarityKDistanceVisualization(SimilarityMeasure sim) { super(); this.sim = sim; setBackground(Color.white); setLayout(new BorderLayout()); JLabel label = null; label = new JLabel("k : "); JButton updateButton = new JButton("Update"); updateButton.addActionListener(this); k_distance_jtext = new JTextField(); k_distance_jtext.setText(Integer.toString(this.k)); k_distance_jtext.setColumns(5); JPanel updatePanel = new JPanel(new FlowLayout()); updatePanel.add(label); updatePanel.add(k_distance_jtext); updatePanel.add(updateButton); JPanel updatePanelAligned = new JPanel(new BorderLayout()); updatePanelAligned.add(updatePanel, BorderLayout.WEST); add(updatePanelAligned, BorderLayout.NORTH); this.updatePanelHeight = updatePanelAligned.getHeight(); } public JComponent getOptionsComponent(int index) { JLabel label = new JLabel("K:"); label.setToolTipText("Set the k which should be displayed."); return label; } /** Indicates how many bins should be used for the distribution plot. */ public void setK(int k) { this.k = k; repaint(); } protected void prepareData() { this.minX = Double.POSITIVE_INFINITY; this.maxX = Double.NEGATIVE_INFINITY; this.minY = Double.POSITIVE_INFINITY; this.maxY = Double.NEGATIVE_INFINITY; // expection handling. The k is larger than the number of points in the map if (this.simMatrix == null) this.simMatrix = new SimilarityMatrix(this.sim); if (this.k >= this.simMatrix.getNumXLabels()) { LogService.getGlobal().log("KDistanceVisualization: k is larger than the number of labels", LogService.WARNING); k = this.simMatrix.getNumXLabels(); } this.minX = 0; this.maxX = this.simMatrix.getNumYLabels(); Iterator itX = this.simMatrix.getXLabels(), itY = null; String tempX = null, tempY = null; double tempDistance = 0; LinkedList<Double> sortList = null; this.kDistanceValues = new LinkedList<Double>(); while (itX.hasNext()) { sortList = new LinkedList<Double>(); tempX = (String) itX.next(); itY = this.simMatrix.getYEntries(tempX); while (itY.hasNext()) { tempY = (String) itY.next(); tempDistance = this.simMatrix.getEntry(tempX, tempY); tempY = null; sortList.add(tempDistance); } tempX = null; // sort the list Collections.sort(sortList); double currentValue = sortList.get(this.k - 1); this.minY = Math.min(minY, currentValue); this.maxY = Math.max(maxY, currentValue); this.kDistanceValues.add(currentValue); sortList = null; } Collections.sort(this.kDistanceValues); Collections.reverse(this.kDistanceValues); xTicSize = getNumericalTicSize(minX, maxX); yTicSize = getNumericalTicSize(minY, maxY); minX = Math.floor(minX / xTicSize) * xTicSize; maxX = Math.ceil(maxX / xTicSize) * xTicSize; minY = Math.floor(minY / yTicSize) * yTicSize; maxY = Math.ceil(maxY / yTicSize) * yTicSize; } protected void drawPoints(Graphics2D g, double dx, double dy, double sx, double sy) { if (this.kDistanceValues != null && this.kDistanceValues.size() <= 2) { LogService.getGlobal().log("KDistanceVisualization: No values in value map", LogService.WARNING); return; } if (this.kDistanceValues != null) { Iterator it = this.kDistanceValues.iterator(); double offset = 0; while (it.hasNext()) { drawPoint(g, offset + dx, ((Double) it.next() + dy) * sy, Color.RED, Color.BLACK); offset += sx; } } } private void drawGrid(Graphics2D g, double dx, double dy, double sx, double sy) { DecimalFormat format = new DecimalFormat("0.00E0"); g.setFont(SCALED_LABEL_FONT); int numberOfXTics = (int) Math.ceil((maxX - minX) / xTicSize) + 1; for (int i = 0; i < numberOfXTics; i++) { drawVerticalTic(g, i, format, dx, dy, sx, sy); } int numberOfYTics = (int) Math.ceil((maxY - minY) / yTicSize) + 1; for (int i = 0; i < numberOfYTics; i++) { drawHorizontalTic(g, i, format, dx, dy, sx, sy); } } private void drawVerticalTic(Graphics2D g, int ticNumber, DecimalFormat format, double dx, double dy, double sx, double sy) { double x = ticNumber * xTicSize + minX; g.setColor(GRID_COLOR); g.draw(new Line2D.Double((x + dx) * sx, (minY + dy) * sy, (x + dx) * sx, (maxY + dy) * sy)); g.setColor(Color.black); } private void drawHorizontalTic(Graphics2D g, int ticNumber, DecimalFormat format, double dx, double dy, double sx, double sy) { double y = ticNumber * yTicSize + minY; g.setColor(GRID_COLOR); g.draw(new Line2D.Double((minX + dx) * sx, (y + dy) * sy, (maxX + dx) * sx, (y + dy) * sy)); g.setColor(Color.black); String label = format.format(y) + " "; Rectangle2D stringBounds = SCALED_LABEL_FONT.getStringBounds(label, g.getFontRenderContext()); g.drawString(label, (float) ((minX + dx) * sx - stringBounds.getWidth()), (float) ((y + dy) * sy - stringBounds.getHeight() / 2 - stringBounds.getY())); } private void drawPoints(Graphics2D g, int pixWidth, int pixHeight) { double sx = 0.0d; double sy = 0.0d; sx = ((double) pixWidth - LABEL_MARGIN_X) / (maxX - minX); sy = ((double) pixHeight - LABEL_MARGIN_Y) / (maxY - minY); Graphics2D coordinateSpace = (Graphics2D) g.create(); coordinateSpace.translate(LABEL_MARGIN_X, LABEL_MARGIN_Y); drawGrid(coordinateSpace, -minX, -minY, sx, sy); drawPoints(coordinateSpace, -minX, -minY, sx, sy); coordinateSpace.dispose(); } public void paintComponent(Graphics graphics) { super.paintComponent(graphics); int pixWidth = getWidth() - 2 * MARGIN; int pixHeight = getHeight() - 2 * MARGIN - this.updatePanelHeight - 50; Graphics2D translated = (Graphics2D) graphics.create(); translated.translate(MARGIN, MARGIN); paintGraph(translated, pixWidth, pixHeight); } public void paintGraph(Graphics graphics, int pixWidth, int pixHeight) { Graphics2D g = (Graphics2D) graphics; Graphics2D scaled = (Graphics2D) g.create(); scaled.translate(0, pixHeight + 1 + this.updatePanelHeight + 50); // prepare data prepareData(); scaled.scale(1, -1); g.setColor(Color.black); drawPoints(scaled, pixWidth, pixHeight); scaled.dispose(); // x-axis label String xAxisLabel = "sorted k-distances"; Rectangle2D stringBounds = SCALED_LABEL_FONT.getStringBounds(xAxisLabel, g.getFontRenderContext()); g.drawString(xAxisLabel, MARGIN + (float)(pixWidth / 2.0d - stringBounds.getWidth() / 2.0d), MARGIN + (float)(pixHeight - 2.0d * stringBounds.getHeight()) + 3); // y-axis label String yAxisLabel = "k-distance value"; stringBounds = LABEL_FONT.getStringBounds(yAxisLabel, g.getFontRenderContext()); g.drawString(yAxisLabel, MARGIN, (int) (MARGIN + stringBounds.getHeight() + 6)); } public void actionPerformed(ActionEvent arg0) { try { Integer.parseInt(k_distance_jtext.getText()); } catch (NumberFormatException e) { SwingTools.showVerySimpleErrorMessage("Please enter a integer value for k"); return; } this.k = Integer.parseInt(k_distance_jtext.getText()); this.kDistanceValues = null; repaint(); } }