001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 The ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| You may obtain a copy of the License at | 008| | 009| http://www.apache.org/licenses/LICENSE-2.0 | 010| | 011| Unless required by applicable law or agreed to in writing, software | 012| distributed under the License is distributed on an "AS IS" BASIS, | 013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 014| See the License for the specific language governing permissions and | 015| limitations under the License. | 016+-------------------------------------------------------------------------*/ 017package org.conqat.lib.commons.treemap; 018 019import java.awt.Color; 020import java.awt.Graphics2D; 021import java.awt.geom.Rectangle2D; 022import java.util.regex.Pattern; 023 024/** 025 * A simple renderer that draws tree map node texts into the tree map. 026 * 027 * @author juergens 028 */ 029public class NodeTextRenderer implements ITreeMapRenderer { 030 031 /** Padding between a node's text label and its rectangle border */ 032 private static final int TEXT_PADDING = 5; 033 034 /** Color in which text is drawn */ 035 private final Color textColor; 036 037 /** Separation pattern used to isolate local name */ 038 private final Pattern separationPattern; 039 040 /** Constructor */ 041 public NodeTextRenderer(Color textColor, Pattern separationPattern) { 042 this.textColor = textColor; 043 this.separationPattern = separationPattern; 044 } 045 046 /** {@inheritDoc} */ 047 @Override 048 public <T> void renderTreeMap(ITreeMapNode<T> node, Graphics2D graphics) { 049 if (node.getChildren().isEmpty()) { 050 Rectangle2D nodeArea = node.getLayoutRectangle(); 051 if (nodeArea == null) { 052 return; 053 } 054 if (enoughSpace(nodeArea)) { 055 drawText(node.getText(), nodeArea, graphics); 056 } 057 } else { 058 for (ITreeMapNode<T> child : node.getChildren()) { 059 renderTreeMap(child, graphics); 060 } 061 } 062 } 063 064 /** Determines if node area is large enough */ 065 private static boolean enoughSpace(Rectangle2D nodeArea) { 066 return nodeArea.getWidth() > 3 * TEXT_PADDING && nodeArea.getHeight() > 3 * TEXT_PADDING; 067 } 068 069 /** Draws the node text */ 070 private void drawText(String text, Rectangle2D availableSpace, Graphics2D graphics) { 071 072 // cut text to size 073 String fittedText = clipTextToWidth(text, availableSpace.getWidth(), graphics); 074 if (fittedText.length() < text.length()) { 075 fittedText += "..."; 076 } 077 078 // compute text position 079 int x = (int) availableSpace.getCenterX() - (actualWidth(fittedText, graphics) / 2); 080 int y = (int) availableSpace.getCenterY() + (actualHeight(fittedText, graphics) / 2); 081 082 // draw label 083 graphics.setColor(textColor); 084 graphics.drawString(fittedText, x, y); 085 } 086 087 /** Clips a string to a certain width */ 088 private String clipTextToWidth(String text, double width, Graphics2D graphics) { 089 double availableWidth = width - 2 * TEXT_PADDING; 090 091 // try to prune to last name part 092 if (separationPattern != null && actualWidth(text, graphics) > availableWidth) { 093 String[] parts = separationPattern.split(text); 094 if (parts.length > 0) { 095 text = parts[parts.length - 1]; 096 } 097 } 098 099 // reserve space for trailing "..." 100 if (actualWidth(text, graphics) > availableWidth) { 101 availableWidth -= actualWidth("...", graphics); 102 } 103 104 // clip until small enough 105 while (text.length() > 0 && actualWidth(text, graphics) > availableWidth) { 106 text = text.substring(0, text.length() - 1); 107 } 108 return text; 109 } 110 111 /** Determines the width a string requires in the current font */ 112 private static int actualWidth(String label, Graphics2D graphics) { 113 return (int) actualBounds(label, graphics).getWidth(); 114 } 115 116 /** Determines the height a string requires in the current font */ 117 private static int actualHeight(String label, Graphics2D graphics) { 118 return (int) actualBounds(label, graphics).getHeight(); 119 } 120 121 /** Computes the bounds of a string in the current font */ 122 private static Rectangle2D actualBounds(String label, Graphics2D graphics) { 123 return graphics.getFont().getStringBounds(label, graphics.getFontRenderContext()); 124 } 125 126}