/* * #%L * gitools-ui-app * %% * Copyright (C) 2013 Universitat Pompeu Fabra - Biomedical Genomics group * %% * 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/gpl-3.0.html>. * #L% */ /******************************************************************************* * Copyright 2013 Lars Behnke * * 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.apache.org/licenses/LICENSE-2.0 * * 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 org.gitools.ui.app.analysis.clustering.visualization; import org.gitools.heatmap.header.HierarchicalCluster; import javax.swing.*; import java.awt.*; import java.awt.geom.Rectangle2D; public class DendrogramPanel extends JPanel { private static final long serialVersionUID = 1L; final static BasicStroke solidStroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); private HierarchicalCluster model; private ClusterComponent component; private Color lineColor = Color.BLACK; private boolean showDistanceValues = false; private boolean showScale = true; private int borderTop = 20; private int borderLeft = 20; private int borderRight = 20; private int borderBottom = 20; private int scalePadding = 10; private int scaleTickLength = 4; private int scaleTickLabelPadding = 4; private double scaleValueInterval = 0; private int scaleValueDecimals = 0; private double xModelOrigin = 0.0; private double yModelOrigin = 0.0; private double wModel = 0.0; private double hModel = 0.0; public boolean isShowDistanceValues() { return showDistanceValues; } public void setShowDistances(boolean showDistanceValues) { this.showDistanceValues = showDistanceValues; } public boolean isShowScale() { return showScale; } public void setShowScale(boolean showScale) { this.showScale = showScale; } public int getScalePadding() { return scalePadding; } public void setScalePadding(int scalePadding) { this.scalePadding = scalePadding; } public int getScaleTickLength() { return scaleTickLength; } public void setScaleTickLength(int scaleTickLength) { this.scaleTickLength = scaleTickLength; } public double getScaleValueInterval() { return scaleValueInterval; } public void setScaleValueInterval(double scaleTickInterval) { this.scaleValueInterval = scaleTickInterval; } public int getScaleValueDecimals() { return scaleValueDecimals; } public void setScaleValueDecimals(int scaleValueDecimals) { this.scaleValueDecimals = scaleValueDecimals; } public int getBorderTop() { return borderTop; } public void setBorderTop(int borderTop) { this.borderTop = borderTop; } public int getBorderLeft() { return borderLeft; } public void setBorderLeft(int borderLeft) { this.borderLeft = borderLeft; } public int getBorderRight() { return borderRight; } public void setBorderRight(int borderRight) { this.borderRight = borderRight; } public int getBorderBottom() { return borderBottom; } public void setBorderBottom(int borderBottom) { this.borderBottom = borderBottom; } public Color getLineColor() { return lineColor; } public void setLineColor(Color lineColor) { this.lineColor = lineColor; } public HierarchicalCluster getModel() { return model; } public void setModel(HierarchicalCluster model) { this.model = model; component = createComponent(model); updateModelMetrics(); } private void updateModelMetrics() { double minX = component.getRectMinX(); double maxX = component.getRectMaxX(); double minY = component.getRectMinY(); double maxY = component.getRectMaxY(); xModelOrigin = minX; yModelOrigin = minY; wModel = maxX - minX; hModel = maxY - minY; } private ClusterComponent createComponent(HierarchicalCluster cluster, VCoord initCoord, double clusterHeight) { ClusterComponent comp = null; if (cluster != null) { comp = new ClusterComponent(cluster, cluster.isLeaf(), initCoord); double leafHeight = clusterHeight / cluster.countLeafs(); double yChild = initCoord.getY() - (clusterHeight / 2); double distance = cluster.getDistance() == null ? 0 : cluster.getDistance(); for (HierarchicalCluster child : cluster.getChildren()) { int childLeafCount = child.countLeafs(); double childHeight = childLeafCount * leafHeight; double childDistance = child.getDistance() == null ? 0 : child.getDistance(); VCoord childInitCoord = new VCoord(initCoord.getX() + (distance - childDistance), yChild + childHeight / 2.0); yChild += childHeight; /* Traverse cluster node tree */ ClusterComponent childComp = createComponent(child, childInitCoord, childHeight); childComp.setLinkPoint(initCoord); comp.getChildren().add(childComp); } } return comp; } private ClusterComponent createComponent(HierarchicalCluster model) { double virtualModelHeight = 1; VCoord initCoord = new VCoord(0, virtualModelHeight / 2); ClusterComponent comp = createComponent(model, initCoord, virtualModelHeight); comp.setLinkPoint(initCoord); return comp; } @Override public void paint(Graphics g) { super.paint(g); paintTree(g); } public void paintTree(Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(lineColor); g2.setStroke(solidStroke); int wDisplay = getWidth() - borderLeft - borderRight; int hDisplay = getHeight() - borderTop - borderBottom; int xDisplayOrigin = borderLeft; int yDisplayOrigin = borderBottom; if (component != null) { int nameGutterWidth = component.getMaxNameWidth(g2, false) + component.getNamePadding(); wDisplay -= nameGutterWidth; if (showScale) { Rectangle2D rect = g2.getFontMetrics().getStringBounds("0", g2); int scaleHeight = (int) rect.getHeight() + scalePadding + scaleTickLength + scaleTickLabelPadding; hDisplay -= scaleHeight; yDisplayOrigin += scaleHeight; } /* Calculate conversion factor and offset for display */ double xFactor = wDisplay / wModel; double yFactor = hDisplay / hModel; int xOffset = (int) (xDisplayOrigin - xModelOrigin * xFactor); int yOffset = (int) (yDisplayOrigin - yModelOrigin * yFactor); component.paint(g2, xOffset, yOffset, xFactor, yFactor, showDistanceValues); if (showScale) { int x1 = xDisplayOrigin; int y1 = yDisplayOrigin - scalePadding; int x2 = x1 + wDisplay; int y2 = y1; g2.drawLine(x1, y1, x2, y2); double totalDistance = component.getCluster().getTotalDistance(); double xModelInterval; if (scaleValueInterval <= 0) { xModelInterval = totalDistance / 10.0; } else { xModelInterval = scaleValueInterval; } int xTick = xDisplayOrigin + wDisplay; y1 = yDisplayOrigin - scalePadding; y2 = yDisplayOrigin - scalePadding - scaleTickLength; double distanceValue = 0; double xDisplayInterval = xModelInterval * xFactor; while (xTick >= xDisplayOrigin) { g2.drawLine(xTick, y1, xTick, y2); String distanceValueStr = String.format("%." + scaleValueDecimals + "f", distanceValue); Rectangle2D rect = g2.getFontMetrics().getStringBounds(distanceValueStr, g2); g2.drawString(distanceValueStr, (int) (xTick - (rect.getWidth() / 2)), y2 - scaleTickLabelPadding); xTick -= xDisplayInterval; distanceValue += xModelInterval; } } } else { /* No data available */ String str = "No data"; Rectangle2D rect = g2.getFontMetrics().getStringBounds(str, g2); int xt = (int) (wDisplay / 2.0 - rect.getWidth() / 2.0); int yt = (int) (hDisplay / 2.0 - rect.getHeight() / 2.0); g2.drawString(str, xt, yt); } } }