/*
* 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.apps.viewer;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.Rectangle2D;
import java.io.ObjectStreamException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.logging.Logger;
import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.PCanvas;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.nodes.PPath;
import edu.umd.cs.piccolo.nodes.PText;
import edu.umd.cs.piccolo.util.PPaintContext;
import at.tuwien.ifs.somtoolbox.apps.viewer.PieChartPNode.PieChartLabelMode;
import at.tuwien.ifs.somtoolbox.apps.viewer.controls.MapOverviewPane;
import at.tuwien.ifs.somtoolbox.data.SOMLibClassInformation;
import at.tuwien.ifs.somtoolbox.data.SOMLibDataInformation;
import at.tuwien.ifs.somtoolbox.layers.Label;
import at.tuwien.ifs.somtoolbox.layers.Unit;
import at.tuwien.ifs.somtoolbox.layers.quality.QualityMeasure;
import at.tuwien.ifs.somtoolbox.layers.quality.QualityMeasureNotFoundException;
import at.tuwien.ifs.somtoolbox.util.StringUtils;
/**
* The graphical representation of one SOM Unit, including labels, data items and class pie charts. This class makes use
* of the <a href="http://www.cs.umd.edu/hcil/jazz/" target="_blank">Piccolo framework</a> and is the top {@link PNode}
* for all unit-level visualisations. This PNode has one of the four child nodes, depending on the current zoom level of
* the {@link SOMViewer} application - {@link GeneralUnitPNode#DETAIL_LEVEL_NO},
* {@link GeneralUnitPNode#DETAIL_LEVEL_LOW}, {@link GeneralUnitPNode#DETAIL_LEVEL_MEDIUM} and
* {@link GeneralUnitPNode#DETAIL_LEVEL_HIGH}. Each of the four different PNodes represents four different levels of
* information details displayed. Each those detail nodes has potentially other child nodes:
* <ul>
* <li>The pie-charts representing class information - {@link PieChartPNode}</li>
* <li>A {@link PNode} for holding the Labels, which are {@link PText} objects</li>
* <li>A {@link PNode} for holding the data item names, which are {@link PText} objects</li>
* <li>A {@link PNode} for holding the quality measure value, which is a {@link PText} object</li>
* </ul>
*
* @author Michael Dittenbach
* @author Rudolf Mayer
* @version $Id: GeneralUnitPNode.java 3969 2010-12-15 13:17:22Z mayer $
*/
public class GeneralUnitPNode extends PNode {
private static final long serialVersionUID = 1L;
public static final int DETAIL_LEVEL_NO = 0;
public static final int DETAIL_LEVEL_LOW = 1;
public static final int DETAIL_LEVEL_MEDIUM = 2;
public static final int DETAIL_LEVEL_HIGH = 3;
public static final int NUMBER_OF_DETAIL_LEVELS = 4;
/**
* Names for the different zoom/scale levels, corresponding to {@link #DETAIL_LEVEL_NO}, {@link #DETAIL_LEVEL_LOW},
* ..
*/
public static final String[] detailLevelNames = { "none", "low", "medium", "high" };
public static final int DATA_DISPLAY_VARIANT_INPUTOBJECT = 0;
public static final int DATA_DISPLAY_VARIANT_INPUTOBJECTSHIFTED = 1;
public static final int DATA_DISPLAY_VARIANT_TEXT = 2;
public static final int DATA_DISPLAY_VARIANTS = 3;
private static int[] NUMBER_OF_LABELS = { 1, 2, 3, -1 };
private static int[] NUMBER_OF_COLUMNS = { 1, 1, 1, 3 };
private static int[] FONT_SIZE_LABELS = { 25, 20, 12, 6 };
private static int[] MAX_LABEL_LENGTH = { 9, 9, 20, 10 };
private static int[] FONT_SIZE_DATA = { 40, 40, 36, 4 };
private int currentDetailLevel = DETAIL_LEVEL_NO;
private Unit u = null;
private double X = 0;
private double Y = 0;
private double width = 0;
private double height = 0;
private boolean drawBorder = true;
private Rectangle2D border = null;
private final Color borderColor = Color.gray;
private final BasicStroke borderStroke = new BasicStroke(1.0f);
private int[] selectedClassIndices = null;
private boolean showOnlySelectedClasses = false;
private boolean selected = false;
private PPath selectionMarker = null;
private ArrayList<ArrowPNode> arrowsFromThisUnit = new ArrayList<ArrowPNode>();
private double oldScale = 1;
private double currentScale = 1;
/** Nodes for details at different levels */
private PNode[] detailNodes = new PNode[NUMBER_OF_DETAIL_LEVELS];
/**
* Data input nodes for different levels, length of {@link #NUMBER_OF_DETAIL_LEVELS}, contains a {@link PText} at
* {@link #DATA_DISPLAY_VARIANT_TEXT}, and {@link InputPNode}s at {@link #DATA_DISPLAY_VARIANT_INPUTOBJECT} and
* {@link #DATA_DISPLAY_VARIANT_INPUTOBJECTSHIFTED}.
*/
private PNode[][][] dataDetail = new PNode[NUMBER_OF_DETAIL_LEVELS][DATA_DISPLAY_VARIANTS][];
private PNode[] dataCountDetail = new PNode[NUMBER_OF_DETAIL_LEVELS];
/** Label nodes for different levels */
private PNode[] labelDetailNodes = new PNode[NUMBER_OF_DETAIL_LEVELS];
/** PieChart nodes for different levels */
private PieChartPNode[] pieChartDetailNodes = new PieChartPNode[NUMBER_OF_DETAIL_LEVELS];
private SOMLibClassInformation classInfo = null;
private SOMLibDataInformation dataInfo = null;
private boolean classInfoSelectionChanged = false;
private PPath queryResultMarker;
private Point[][] locations;
private CommonSOMViewerStateData state;
/**
* Constructor for mnemonic (sparse) SOMs. Initialises an empty cell with no unit attached.
*/
public GeneralUnitPNode(int x, int y, double width, double height) {
setPickable(false);
setChildrenPickable(false);
X = x * width;
Y = y * height;
initPNodeProperties(width, height);
addChild(detailNodes[DETAIL_LEVEL_NO]);
}
public GeneralUnitPNode(Unit u, CommonSOMViewerStateData state, SOMLibClassInformation classInfo,
SOMLibDataInformation dataInfo, double width, double height) {
this(u, state, classInfo, dataInfo, null, width, height);
}
public GeneralUnitPNode(Unit u, CommonSOMViewerStateData state, SOMLibClassInformation classInfo,
SOMLibDataInformation dataInfo, Point[][] locations, double width, double height) {
this.u = u;
X = u.getXPos() * width;
Y = u.getYPos() * height;
initPNodeProperties(width, height);
this.classInfo = classInfo;
this.dataInfo = dataInfo;
this.locations = locations;
this.state = state;
if (classInfo != null) { // class information present, generate pie chart
initClassPieCharts(u, classInfo, width, height);
}
for (int i = 0; i < detailNodes.length; i++) {
if (detailNodes[i] == null) {
detailNodes[i] = new PNode();
}
}
if (u.getNumberOfMappedInputs() > 0) {
initDetails();
}
addChild(detailNodes[DETAIL_LEVEL_MEDIUM]);
}
public GeneralUnitPNode(Unit u, GeneralUnitPNode clone) {
this(u, clone.state, clone.classInfo, clone.dataInfo, clone.locations, clone.width, clone.height);
}
private void addDataChildren() {
PNode[] nodes = dataDetail[currentDetailLevel][getDataInputVariant()];
if (state.dataVisibilityMode) {
if (nodes != null) {
for (PNode node2 : nodes) {
PNode node = new PNode();
node.addChild(node2);
addChild(node);
}
}
}
}
public void reInitUnitDetails() {
if (u.getNumberOfMappedInputs() > 0) {
removeDetailNodes();
for (PNode detailNode : detailNodes) {
detailNode.removeAllChildren();
removeAllChildren();
}
initDetails();
addChild(detailNodes[currentDetailLevel]);
addDataChildren();
}
}
public void reInitUnitDetails(int detailLevel) {
if (u.getNumberOfMappedInputs() > 0) {
removeDetailNodes();
// re-init changed one
if (detailLevel >= DETAIL_LEVEL_NO && detailLevel <= DETAIL_LEVEL_HIGH) {
detailNodes[detailLevel].removeAllChildren();
removeAllChildren();
for (int i = 0; i < DATA_DISPLAY_VARIANTS; i++) {
dataDetail[detailLevel][i] = null;
}
}
initDetails(detailLevel);
addChild(detailNodes[currentDetailLevel]);
addDataChildren();
}
}
/** remove currently added detail levels. */
private void removeDetailNodes() {
for (PNode detailNode : detailNodes) {
if (detailNode.isDescendentOf(this)) {
removeChild(detailNode);
}
}
}
/**
* Initializes common properties for unit PNodes and empty PNodes
*/
private void initPNodeProperties(double width, double height) {
border = new Rectangle2D.Double();
this.width = width;
this.height = height;
border.setRect(X, Y, width, height);
this.setBounds(X, Y, width, height);
selectionMarker = PPath.createRectangle((float) X, (float) Y, (float) width, (float) height);
selectionMarker.setPaint(Color.decode("#ff7505"));
selectionMarker.setTransparency(0.5f);
queryResultMarker = PPath.createRectangle((float) X, (float) Y, (float) width, (float) height);
queryResultMarker.setPaint(Color.ORANGE);
queryResultMarker.setTransparency(0.5f);
}
public void initClassPieCharts(Unit u, SOMLibClassInformation classInfo, double width, double height) {
this.classInfo = classInfo;
if (classInfo != null) { // class information present, generate pie chart
int[] values = classInfo.computeClassDistribution(u.getMappedInputNames());
// TODO: debug output (can be used for cluster purity calculator in the future)
// DoubleMatrix1D dummy = new DenseDoubleMatrix1D(values);
// System.out.println("Cluster purity for unit ("+u.getXPos()+"/"+u.getYPos()+"):");
// for (int cc=0; cc<classInfo.numClasses(); cc++) {
// System.out.println(" "+classInfo.classNames()[cc]+" "+(values[cc]/u.getNumberOfMappedInputs()));
// }
// end debug
// FIXME: maybe reuse the PieChartPNode for each detail level?
pieChartDetailNodes[DETAIL_LEVEL_NO] = new PieChartPNode(0, 0, width, height, values,
u.getNumberOfMappedInputs());
pieChartDetailNodes[DETAIL_LEVEL_LOW] = new PieChartPNode(0, 0, width, height, values,
u.getNumberOfMappedInputs());
pieChartDetailNodes[DETAIL_LEVEL_MEDIUM] = new PieChartPNode(0, 0, width, height, values,
u.getNumberOfMappedInputs());
}
}
/**
* Initializes text labels for this node. The labels are displayed in a virtual table like structure.
*
* @param numLabels Number of labels to be shown. If '-1' the limit is set to the number of labels in unit
* description file.
* @param numCol Number of table columns. Setting to '1' means row table.
* @param fontSize Font size to use for drawing labels
* @param length Maximum label length, longer labels will be trunkated
*/
private PNode initLabels(int numLabels, final int numCol, final int fontSize, final int length) {
// -1 means we want to show all labels. additionally check if we have enough labels...
if (numLabels == -1 || u.getLabels().length < numLabels) {
numLabels = u.getLabels().length;
}
PNode labelsNode = new PNode();
if (u.getLabels() != null) {
Font labelsFont = new Font("Sans", Font.PLAIN, fontSize);
double xOffset[] = new double[numCol];
for (int i = 0; i < numCol; i++) {
xOffset[i] = 2 + i * width / numCol;
}
double yOffset = 2;
int columnIndex = 0;
for (int i = 0; i < numLabels; i++) {
PText label = new PText(u.getLabels()[i].getName());
label.setPickable(false);
// TODO: find a better way to set the colour. maybe the current palette can give the preferred label
// colour?
if (state.exactUnitPlacement && getMapPNode().getCurrentVisualization() == null) { // white labels for
// sky vis
label.setTextPaint(Color.WHITE);
} else {
label.setTextPaint(Color.BLACK);
}
label.setFont(labelsFont);
label.setOffset(xOffset[columnIndex], yOffset); // (i * labelsFont.getSize()) + 2
label.addAttribute("tooltip", u.getLabels()[i].getName() + "\nqe: "
+ StringUtils.format(u.getLabels()[i].getQe(), 3) + "\nmean: "
+ StringUtils.format(u.getLabels()[i].getValue(), 3));
label.addAttribute("type", "label");
labelsNode.addChild(label);
if (label.getBoundsReference().getWidth() > width / numCol) {
label.setText(label.getText().substring(0, Math.min(length, label.getText().length())) + "...");
}
if ((i + 1) % numCol == 0) { // Next row
yOffset += labelsFont.getSize() + 2;
columnIndex = 0;
} else { // Next column
columnIndex++;
}
}
}
return labelsNode;
}
public MapPNode getMapPNode() {
// parent is the PNode holding all GeneralUnitPNodes, the parent of which in turn is the MapPNode
return (MapPNode) getParent().getParent();
}
/**
* Initializes textual details
*
* @param threshold Displays description of mapped inputs below certain threshold (count), otherwise a count is
* displayed.
* @param fontSize Text size
* @param yInOffset Positional offset
*/
private PNode[] initData(int threshold, int fontSize, double yInOffset, int detailLevel, int variant) {
final String commonVectorLabelPrefix = state.growingLayer.getCommonVectorLabelPrefix();
int nodesToDisplay = u.getNumberOfMappedInputs() * threshold / 100;
if (variant == DATA_DISPLAY_VARIANT_INPUTOBJECT || variant == DATA_DISPLAY_VARIANT_INPUTOBJECTSHIFTED) { // Display
// stars
// or
// simply
// count
if (locations != null) {
PNode[] dataNodes = new PNode[nodesToDisplay];
for (int index = 0; index < nodesToDisplay; index++) {
PNode star = new StarPNode();
String inputName = u.getMappedInputName(index);
String inputDistance = StringUtils.format(u.getMappedInputDistance(index), 3);
String inputText = "";
try {
if (dataInfo != null) {
inputText = URLDecoder.decode(dataInfo.getDataDisplayName(inputName), "UTF-8");
} else {
inputText = URLDecoder.decode(inputName, "UTF-8");
}
} catch (Exception e) {
inputText = inputName;
}
star.addAttribute("id", inputName.replaceFirst(commonVectorLabelPrefix, ""));
star.addAttribute("type", "data");
star.setPickable(true);
if (dataInfo != null) {
try {
star.addAttribute("tooltip", inputText + "\ndistance: " + inputDistance + "\n"
+ URLDecoder.decode(dataInfo.getDataLocation(inputName), "UTF-8"));
} catch (Exception e) {
Logger.getLogger("at.tuwien.ifs.somtoolbox").warning(
"URLDecoder had problems reading the name of the datum '" + inputName + "'.");
star.addAttribute("tooltip", inputText + "\ndistance: " + inputDistance);
}
star.addAttribute("location", dataInfo.getBaseDir() + dataInfo.getDataLocation(inputName));
} else {
star.addAttribute("tooltip", inputText + "\ndistance: " + inputDistance);
star.addAttribute("location", inputName);
}
star.setOffset(this.X + locations[variant][index].getX(), this.Y + locations[variant][index].getY());
dataNodes[index] = star;
}
return dataNodes;
} else {
return new PNode[0];
}
} else {
PNode[] dataNodes = new PNode[nodesToDisplay];
if (detailLevel == DETAIL_LEVEL_HIGH) {
Font font = new Font("Sans", Font.PLAIN, fontSize);
final int numDataCol = 3;
double xDataOffsets[] = new double[numDataCol];
for (int i = 0; i < numDataCol; i++) {
xDataOffsets[i] = i * width / numDataCol + 2;
}
double yOffset = yInOffset + dataCountDetail[detailLevel].getBoundsReference().getHeight() + 2;
int numData = Math.min(dataNodes.length, u.getNumberOfMappedInputs()); // limitation by number of labels
// in unit description file
int x = 0;
for (int index = 0; index < numData; index++) {
PText pText = null;
final String inputName = u.getMappedInputNames()[index];
String inputText = null;
try {
if (dataInfo != null) {
inputText = URLDecoder.decode(dataInfo.getDataDisplayName(inputName), "UTF-8");
} else {
inputText = URLDecoder.decode(inputName, "UTF-8");
}
} catch (Exception e) {
Logger.getLogger("at.tuwien.ifs.somtoolbox").warning(
"URLDecoder had problems reading the name of the datum '" + inputName + "'.");
inputText = inputName;
}
pText = new PText(inputText.replaceFirst(commonVectorLabelPrefix, ""));
pText.addAttribute("id", inputName);
pText.addAttribute("type", "data");
pText.setFont(font);
pText.setTextPaint(Color.BLUE);
pText.setOffset(this.X + xDataOffsets[x], this.Y + yOffset);
if (dataInfo != null) {
try {
pText.addAttribute("tooltip", inputText + "\ndistance: "
+ StringUtils.format(u.getMappedInputDistances()[index], 3) + "\n"
+ URLDecoder.decode(dataInfo.getDataLocation(inputName), "UTF-8"));
} catch (Exception e) {
Logger.getLogger("at.tuwien.ifs.somtoolbox").warning(
"URLDecoder had problems reading the name of the datum '" + inputName + "'.");
pText.addAttribute("tooltip", pText.getText() + "\ndistance: "
+ StringUtils.format(u.getMappedInputDistances()[index], 3));
}
pText.addAttribute("location", dataInfo.getBaseDir() + dataInfo.getDataLocation(inputName));
} else {
pText.addAttribute("tooltip", inputText + "\ndistance: "
+ StringUtils.format(u.getMappedInputDistances()[index], 3));
pText.addAttribute("location", inputName);
}
dataNodes[index] = pText;
if (pText.getBoundsReference().getWidth() > width / numDataCol) {
pText.setText(pText.getText().substring(0, Math.min(16, pText.getText().length())) + "...");
}
x++;
if ((index + 1) % numDataCol == 0) { // next line
yOffset += font.getSize() + 2; // TODO: check qualityHighYOffset!!!!
x = 0;
}
}
return dataNodes;
}
}
return new PNode[0];
}
/**
* Initializes the details node. A pie chart is generated if class info is available (except
* {@link #DETAIL_LEVEL_HIGH}, quality measure info is added for {@link #DETAIL_LEVEL_HIGH}.
*/
private void initDetails() {
for (int level = 0; level < NUMBER_OF_DETAIL_LEVELS; level++) {
initDetails(level);
}
}
private void initDetails(int level) {
detailNodes[level].setOffset(this.X, this.Y);
double xOffset = 0;
double yOffset = 0;
// labels
if (u.getLabels() != null && state.labelVisibilityMode) {
if (labelDetailNodes[level] == null) {
labelDetailNodes[level] = initLabels(NUMBER_OF_LABELS[level], NUMBER_OF_COLUMNS[level],
FONT_SIZE_LABELS[level], MAX_LABEL_LENGTH[level]);
}
detailNodes[level].addChild(labelDetailNodes[level]);
yOffset = labelDetailNodes[level].getFullBoundsReference().getHeight();
if (yOffset > state.maxLabelYOffset[level]) {
state.maxLabelYOffset[level] = yOffset;
}
}
// quality, for high level only
if (level == DETAIL_LEVEL_HIGH) {
QualityMeasure qm = null;
if ((qm = u.getLayer().getQualityMeasure()) != null) {
PNode qualityHigh = new PNode();
Font qualityHighFont = new Font("Sans", Font.PLAIN, 4);
try {
for (int i = 0; i < qm.getUnitQualityNames().length; i++) {
PText qmText = new PText(qm.getUnitQualityNames()[i]
+ ": "
+ StringUtils.format(
qm.getUnitQualities(qm.getUnitQualityNames()[i])[u.getXPos()][u.getYPos()], 3));
qmText.setPickable(false);
qmText.setFont(qualityHighFont);
qmText.setOffset(xOffset, 0);
xOffset += qmText.getWidth() + 4;
}
} catch (QualityMeasureNotFoundException e) {
Logger.getLogger("at.tuwien.ifs.somtoolbox").severe(
e.getMessage() + " Aborting. BTW: the must be a major flaw"
+ "in the quality measure class that has been used.");
System.exit(-1);
}
double qualityHighYOffset = height - (qualityHigh.getFullBoundsReference().getHeight() + 2);
qualityHigh.setOffset(2, qualityHighYOffset);
detailNodes[level].addChild(qualityHigh);
}
}
if (dataCountDetail[level] == null) {
Font font = new Font("Sans", Font.PLAIN, FONT_SIZE_DATA[level]);
String inputCount = String.valueOf(u.getNumberOfMappedInputs());
PText numDataText = new PText();
if (level == DETAIL_LEVEL_NO || level == DETAIL_LEVEL_LOW || level == DETAIL_LEVEL_MEDIUM) {
numDataText.setText(inputCount);
// FIXME: use a calculated xOffset rather than a fixed one, e.g. as below
// double xOffsetNumData = (width - defaultToolkit.getFontMetrics(font).stringWidth(inputCount)) / 2;
numDataText.setOffset(2, (height - yOffset) / 2 - numDataText.getFont().getSize() / 2 + yOffset);
dataCountDetail[level] = numDataText;
} else if (level == DETAIL_LEVEL_HIGH) {
numDataText.setText("Number of data items: " + inputCount);
numDataText.setOffset(2, yOffset);
dataCountDetail[level] = numDataText;
}
numDataText.setPickable(false);
numDataText.setFont(font);
}
dataCountDetail[level].setVisible(state.hitsVisibilityMode);
detailNodes[level].addChild(dataCountDetail[level]);
// data
for (int i = 0; i < DATA_DISPLAY_VARIANTS; i++) {
if (dataDetail[level][i] == null) {
dataDetail[level][i] = initData(state.thresholdInputPercentage[level], FONT_SIZE_DATA[level], yOffset,
level, i);
}
}
// class info, not for high details
if (level != DETAIL_LEVEL_HIGH) {
if (classInfo != null) { // class information present, generate pie chart
double pWidth = width - xOffset;
double pHeight = height
- (level == DETAIL_LEVEL_NO ? height / 5
: dataCountDetail[level].getFullBoundsReference().getHeight());
pieChartDetailNodes[level].setBounds(xOffset, 0, pWidth, pHeight);
if (state.getClassPiechartMode() != SOMViewer.TOGGLE_PIE_CHARTS_NONE) {
detailNodes[level].addChild(pieChartDetailNodes[level]);
}
}
}
}
/** Updates the units displayed info by removing & re-creating them. */
public void updateDetailsAfterMoving() {
removeDetailNodes();
for (int detailLevel = 0; detailLevel < detailNodes.length; detailLevel++) {
detailNodes[detailLevel].removeAllChildren();
removeAllChildren();
for (int i = 0; i < DATA_DISPLAY_VARIANTS; i++) {
dataDetail[detailLevel][i] = null;
dataCountDetail[detailLevel] = null;
pieChartDetailNodes[detailLevel] = null;
}
if (u.getNumberOfMappedInputs() > 0) {
initDetails(detailLevel);
}
}
addChild(detailNodes[currentDetailLevel]);
addDataChildren();
repaint();
}
/** @see edu.umd.cs.piccolo.PNode#paint(edu.umd.cs.piccolo.util.PPaintContext) */
@Override
protected void paint(PPaintContext paintContext) {
Graphics2D g2d = paintContext.getGraphics();
if (state.exactUnitPlacement && getMapPNode().getCurrentVisualization() == null) { // black background for sky
// vis
border.setRect(X, Y, width, height);
g2d.setColor(Color.BLACK);
g2d.fill(border);
}
if (drawBorder) {
border.setRect(X, Y, width, height);
g2d.setStroke(borderStroke);
g2d.setPaint(Color.CYAN);
g2d.setColor(borderColor);
g2d.draw(border);
}
PCamera pCam = paintContext.getCamera();
if (!((PCanvas) pCam.getComponent()).getClass().equals(MapOverviewPane.MapOverviewCanvas.class)) { // only for
// main
// display
currentScale = paintContext.getScale();
if (currentScale != oldScale) {
// System.out.println("SCALE: "+currentScale);
// double os = t10.getScale();
// t10.setScale(1/currentScale);
// if (t10.getGlobalFullBounds().width>this.width) {
// t10.setScale(os);
// }
if (currentScale < state.scaleLimits[1]) { // no information
if (currentDetailLevel != DETAIL_LEVEL_NO) {
currentDetailLevel = DETAIL_LEVEL_NO;
detailChanged();
}
} else if (currentScale >= state.scaleLimits[1] && currentScale < state.scaleLimits[2]) { // little
// information
if (currentDetailLevel != DETAIL_LEVEL_LOW) {
currentDetailLevel = DETAIL_LEVEL_LOW;
detailChanged();
}
} else if (currentScale >= state.scaleLimits[2] && currentScale < state.scaleLimits[3]) { // more labels
if (currentDetailLevel != DETAIL_LEVEL_MEDIUM) {
currentDetailLevel = DETAIL_LEVEL_MEDIUM;
detailChanged();
}
} else if (currentScale >= state.scaleLimits[3]) { // detailed information
if (currentDetailLevel != DETAIL_LEVEL_HIGH) {
currentDetailLevel = DETAIL_LEVEL_HIGH;
detailChanged();
}
}
oldScale = currentScale;
}
}
}
/** @see edu.umd.cs.piccolo.PNode#setBounds(double, double, double, double) */
@Override
public boolean setBounds(double x, double y, double width, double height) {
if (super.setBounds(x, y, width, height)) {
border.setFrame(x, y, width, height);
return true;
}
return false;
}
/**
* Updates the class pie chart visibility.
*/
public void updateClassPieCharts() {
if (u.getNumberOfMappedInputs() > 0 && classInfoSelectionChanged) {
boolean pieChartVisible = false;
if (state.getClassPiechartMode() == SOMViewer.TOGGLE_PIE_CHARTS_NONE) { // hiding class info is selected
pieChartVisible = false;
} else if (selectedClassIndices == null) { // no classes selected, show pie chart in any case
pieChartVisible = true;
} else { // at least one class is selected
int[] classValues = pieChartDetailNodes[DETAIL_LEVEL_LOW].getValues();
// check whether this unit contains any mapped data items belonging to a selected class
// determines whether we show a pie chart at all
int sc = 0;
while (sc < selectedClassIndices.length && pieChartVisible == false) {
if (classValues[selectedClassIndices[sc]] > 0) {
pieChartVisible = true;
}
sc++;
}
}
for (int i = 0; i < detailNodes.length; i++) {
if (detailNodes[i] != null && pieChartDetailNodes[i] != null) {
if (state.getClassPiechartMode() == SOMViewer.TOGGLE_PIE_CHARTS_SHOW_COUNTS) {
pieChartDetailNodes[i].setShowLegend(PieChartLabelMode.Count);
} else if (state.getClassPiechartMode() == SOMViewer.TOGGLE_PIE_CHARTS_SHOW_PERCENT) {
pieChartDetailNodes[i].setShowLegend(PieChartLabelMode.Percent);
} else {
pieChartDetailNodes[i].setShowLegend(PieChartLabelMode.None);
}
if (selectedClassIndices != null) {
Color[] cs = pieChartDetailNodes[i].getLegendColors();
for (int j = 0; j < cs.length; j++) {
boolean colorVisible = !getShowOnlySelectedClasses();
if (!colorVisible) {
for (int selectedClassIndice : selectedClassIndices) {
if (j == selectedClassIndice) {
colorVisible = true;
break;
}
}
}
if (colorVisible) {
cs[j] = new Color(cs[j].getRed(), cs[j].getGreen(), cs[j].getBlue());
} else {
cs[j] = new Color(cs[j].getRed(), cs[j].getGreen(), cs[j].getBlue(), 0);
}
}
pieChartDetailNodes[i].setColors(cs);
}
boolean ancestorOf = detailNodes[i].isAncestorOf(pieChartDetailNodes[i]);
if (state.getClassPiechartMode() != SOMViewer.TOGGLE_PIE_CHARTS_NONE && pieChartVisible) {
if (!ancestorOf) {
detailNodes[i].addChild(pieChartDetailNodes[i]);
}
} else {
if (ancestorOf) {
detailNodes[i].removeChild(pieChartDetailNodes[i]);
}
}
}
}
}
classInfoSelectionChanged = false;
}
public int getDataInputVariant() {
if (state.exactUnitPlacement) {
if (state.shiftOverlappingInputs) {
return DATA_DISPLAY_VARIANT_INPUTOBJECTSHIFTED;
} else {
return DATA_DISPLAY_VARIANT_INPUTOBJECT;
}
} else {
return DATA_DISPLAY_VARIANT_TEXT;
}
}
/**
* Updates child nodes to display upon change in detail level
*/
private void detailChanged() {
removeAllChildren();
setSelected(selected);
addChild(detailNodes[currentDetailLevel]);
addDataChildren();
}
public boolean hasPieCharts() {
return pieChartDetailNodes[DETAIL_LEVEL_LOW] != null;
}
/**
* This implementation does not check for the pie charts ({@link #pieChartDetailNodes} to be initialised and should
* therefore be only used if it is for sure != null.
*/
public Color getClassLegendColorFast(int index) {
return pieChartDetailNodes[DETAIL_LEVEL_LOW].getLegendColor(index);
}
public void setClassColor(int index, Color color) {
for (PieChartPNode pieChartDetailNode : pieChartDetailNodes) {
if (pieChartDetailNode != null) {
pieChartDetailNode.setColor(index, color);
}
}
repaint();
}
public void setClassColors(Color[] colors) {
for (PieChartPNode pieChartDetailNode : pieChartDetailNodes) {
if (pieChartDetailNode != null) {
pieChartDetailNode.setColors(colors);
}
}
repaint();
}
public void updateClassSelection(int[] indices) {
selectedClassIndices = indices;
classInfoSelectionChanged = true;
updateClassPieCharts();
}
public String[] getMappedDataNames() {
return u.getMappedInputNames();
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean sel) {
selected = sel;
if (selected) {
if (!selectionMarker.isDescendentOf(this)) {
addChild(selectionMarker);
}
} else {
if (selectionMarker.isDescendentOf(this)) {
removeChild(selectionMarker);
}
}
}
public Label[] getLabels(String type) {
return u.getLabels(type);
}
public void setQueryHit() {
addChild(queryResultMarker);
}
public void removeQueryHit() {
removeChild(queryResultMarker);
}
/**
* Returns the associtated SOM unit for this node
*/
public Unit getUnit() {
return u;
}
public PieChartPNode getClassPieChart(int width, int height) {
if (classInfo != null) { // class information present, generate pie chart
int[] values = classInfo.computeClassDistribution(u.getMappedInputNames());
PieChartPNode pieChartPNode = new PieChartPNode(0, 0, width, height, values, u.getNumberOfMappedInputs());
Color[] colors = pieChartDetailNodes[DETAIL_LEVEL_NO].getLegendColors();
for (int i = 0; i < colors.length; i++) {
pieChartPNode.setColor(i, colors[i]);
}
return pieChartPNode;
} else {
return null;
}
}
// Angela: used by the Serializer -- serialize another object instead
private Object writeReplace() throws ObjectStreamException {
return new GeneralUnitPNodeSerializer(this);
}
public ArrayList<ArrowPNode> getArrows() {
return arrowsFromThisUnit;
}
public void setArrows(ArrayList<ArrowPNode> arrows) {
this.arrowsFromThisUnit = arrows;
}
public void addArrow(ArrowPNode arrow) {
this.arrowsFromThisUnit.add(arrow);
}
public void resetArrows() {
this.arrowsFromThisUnit = new ArrayList<ArrowPNode>();
}
public Point[] getLocations() {
if (state.shiftOverlappingInputs) {
return locations[DATA_DISPLAY_VARIANT_INPUTOBJECTSHIFTED];
} else {
return locations[DATA_DISPLAY_VARIANT_INPUTOBJECT];
}
}
public Point getPostion() {
return new Point((int) X, (int) Y);
}
public boolean getShowOnlySelectedClasses() {
return showOnlySelectedClasses;
}
public void setShowOnlySelectedClasses(boolean showOnlySelectedClasses) {
this.showOnlySelectedClasses = showOnlySelectedClasses;
classInfoSelectionChanged = true;
updateClassPieCharts();
}
@Override
public String toString() {
return u.toString();
}
}