/*
* Copyright 2004-2010 Information & Software Engineering Group (188/1)
* Institute of Software Technology and Interactive Systems
* Vienna University of Technology, Austria
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.ifs.tuwien.ac.at/dm/somtoolbox/license.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package at.tuwien.ifs.somtoolbox.util;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Stroke;
import java.awt.image.BufferedImage;
import at.tuwien.ifs.somtoolbox.layers.LayerAccessException;
import at.tuwien.ifs.somtoolbox.layers.Unit;
import at.tuwien.ifs.somtoolbox.models.GrowingSOM;
/**
* This class gathers utility methods for SOM visualisations, such as drawing a unit grid or borders around already
* created visualisations.
*
* @author Rudolf Mayer
* @version $Id: VisualisationUtils.java 3696 2010-07-16 13:51:26Z mayer $
*/
public class VisualisationUtils {
/** draws a black border around the image */
public static void drawBorder(BufferedImage buffImage) {
Graphics2D g2D = buffImage.createGraphics();
int width = buffImage.getWidth();
int height = buffImage.getHeight();
g2D.setColor(Color.BLACK);
g2D.drawLine(0, height - 1, width - 1, height - 1);
g2D.drawLine(width - 1, 0, width - 1, height - 1);
g2D.drawLine(0, 0, width - 1, 0);
g2D.drawLine(0, 0, 0, height - 1);
}
/** Draws a thick line of the given width and height, between the given coordinates */
public static void drawThickLine(Graphics2D g, int x1, int y1, int x2, int y2, int lineWidth, int lineHeight) {
if (Math.max(lineWidth, lineHeight) == 1) { // draw a normal line
g.drawLine(x1, y1, x2, y2);
} else { // draw think line, as a polygon
int dX = x2 - x1;
int dY = y2 - y1;
// line length
double lineLength = Math.sqrt(dX * dX + dY * dY);
double scaleX = lineWidth / (2 * lineLength);
double scaleY = lineHeight / (2 * lineLength);
// The x,y increments from an endpoint needed to create a rectangle
double ddx = -scaleX * dY;
double ddy = scaleY * dX;
ddx += ddx > 0 ? 0.5 : -0.5;
ddy += ddy > 0 ? 0.5 : -0.5;
int dx = (int) ddx;
int dy = (int) ddy;
// Now we can compute the corner points
int xPoints[] = new int[4];
int yPoints[] = new int[4];
xPoints[0] = x1 + dx;
yPoints[0] = y1 + dy;
xPoints[1] = x1 - dx;
yPoints[1] = y1 - dy;
xPoints[2] = x2 - dx;
yPoints[2] = y2 - dy;
xPoints[3] = x2 + dx;
yPoints[3] = y2 + dy;
g.fillPolygon(xPoints, yPoints, 4);
}
}
/**
* Draws a think line between the two given points. The line width and height are computed as 1/10 of the given
* unitWidth/Height values
*/
public static void drawThickLine(Graphics2D g, Point p1, Point p2, int unitWidth, int unitHeight) {
// draw the line & circle approx. 1/10 of the unitWidth
int lineWidth = Math.round(unitWidth / 10);
int lineHeight = Math.round(unitHeight / 10);
VisualisationUtils.drawThickLine(g, p1.x, p1.y, p2.x, p2.y, lineWidth, lineHeight);
}
/** Draws a thick line from the centre of u1 to u2, using {@link #drawThickLine(Graphics2D, Point, Point, int, int)} */
public static void drawThickLine(Graphics2D g, Unit u1, Unit u2, int unitWidth, int unitHeight) {
VisualisationUtils.drawThickLine(g, VisualisationUtils.getUnitCentreLocation(u1, unitWidth, unitHeight),
VisualisationUtils.getUnitCentreLocation(u2, unitWidth, unitHeight), unitWidth, unitHeight);
}
/**
* Draws a marker in the centre of the given unit. The unitWidth & unitHeight are needed to compute the pixel
* location, markerWidth and markerHeight indicate the size of the circle/oval to draw.
*/
public static void drawUnitCentreMarker(Graphics2D g, Unit unit, int unitWidth, int unitHeight, int markerWidth,
int markerHeight) {
Point unitCentre = VisualisationUtils.getUnitCentreLocation(unit, unitWidth, unitHeight, markerWidth,
markerHeight);
drawMarker(g, markerWidth, markerHeight, unitCentre);
}
/** Draws a circle-marker on the given position */
public static void drawMarker(Graphics2D g, int markerWidth, int markerHeight, Point location) {
int x = location.x;
int y = location.y;
g.fillOval(x, y, markerWidth - 1, markerHeight - 1);
// FIXME: for some reason, fillOval doesn't work inside the SOMViewer, maybe an issue with piccollo... for that
// reason, we also use drawOval
g.drawOval(x, y, markerWidth - 1, markerHeight - 1);
}
/** Draws a black grid of units on the {@link BufferedImage} */
public static void drawUnitGrid(BufferedImage bufferedImage, GrowingSOM gsom, int width, int height) {
drawUnitGrid((Graphics2D) bufferedImage.getGraphics(), gsom, width, height);
}
/** Draws a black grid of units on the {@link Graphics2D} object */
public static void drawUnitGrid(Graphics2D g, GrowingSOM gsom, int width, int height) {
VisualisationUtils.drawUnitGrid(g, gsom, width, height, Color.BLACK);
}
/**
* Draws a grid of units on the {@link Graphics2D} object in the given colour. The width of the grid lines depends
* on the image resolution, and is 1/20 of the unit width.
*/
public static void drawUnitGrid(Graphics2D g, GrowingSOM gsom, int width, int height, Color colour) {
int unitWidth = width / gsom.getLayer().getXSize();
int unitHeight = height / gsom.getLayer().getYSize();
int lineWidth = Math.max(1, Math.max(unitWidth / 20, unitHeight / 20));
g.setColor(colour);
Stroke stroke = g.getStroke();
g.setStroke(new BasicStroke(lineWidth));
for (int x = 0; x < gsom.getLayer().getXSize(); x++) {
for (int y = 0; y < gsom.getLayer().getYSize(); y++) {
try {
if (gsom.getLayer().getUnit(x, y) != null) { // check needed for mnemonic SOMs
// to avoid having thinner lines at the edges, we need to do some modifications
// 1. start with an offset on the left and upper edge units to have the line to be the same
// width
int offsetX = x == 0 ? lineWidth / 2 : 0;
int offsetY = y == 0 ? lineWidth / 2 : 0;
// 2. don't draw the whole width in the edge units, as:
// - in the left & upper edge we start with the offset
// - in the right & lower edge units, we want the line to be same width, so we need to start it
// earlier
int gridWidth = gsom.getLayer().isEdgeColumn(x) ? unitWidth - lineWidth / 2 : unitWidth;
int gridHeight = gsom.getLayer().isEdgeRow(y) ? unitHeight - lineWidth / 2 : unitHeight;
g.drawRect(x * unitWidth + offsetX, y * unitHeight + offsetY, gridWidth, gridHeight);
}
} catch (LayerAccessException e) {
// should never happen
e.printStackTrace();
}
}
}
g.setStroke(stroke);
}
public static Point getUnitCentreLocation(int xPos, int yPos, double unitWidth, double unitHeight) {
return new Point((int) ((xPos + 0.5) * unitWidth), (int) ((yPos + 0.5) * unitHeight));
}
public static Point getUnitCentreLocation(Unit unit, double unitWidth, double unitHeight) {
return new Point((int) ((unit.getXPos() + 0.5) * unitWidth), (int) ((unit.getYPos() + 0.5) * unitHeight));
}
public static Point getUnitCentreLocation(Unit unit, double unitWidth, double unitHeight, int offsetX, int offsetY) {
return new Point((int) ((unit.getXPos() + 0.5) * unitWidth) - offsetX / 2,
(int) ((unit.getYPos() + 0.5) * unitHeight) - offsetY / 2);
}
}