漫谈可视化Prefuse(五)---一款属于我自己的可视化工具
伴随着前期的基础积累,翻过API,读过一些Demo,总觉得自己已经摸透了Prefuse,小打小闹似乎已经无法满足内心膨胀的自己。还记得儿时看的《武状元苏乞儿》中降龙十八掌最后一张居然是空白页,在千钧一发之际以为自己要嗝屁了,一阵东风让苏乞儿明白了,最后一章要做的原来是——整合。没错,今天我们就来好好谈谈整合!
看懂API不代表你会灵活运用某个类,会用一些类不代表能实现小功能,耍的了小功能不一定会做可视化工具整合,整合之道,奥妙无穷!
前篇回顾:上篇《漫谈可视化Prefuse(四)---被玩坏的Prefuse API》主要从Prefuse API角度,单线条的依据API之道,对部分类进行剖析,通过coding实现小功能并辅之于图为证,大致提供了一条学习Prefuse的路线。期间也有众多志同道合之友前来了解Prefuse到底为何物、能做什么,问答之间着实让人兴奋不已。
本篇作为在博客园里漫谈可视化Prefuse系列的收尾篇,主要看看自己对于Prefuse的粗劣的整合,重点介绍下自己在整合期间印象较为深刻的片段和coding瞬间。其中包括有对于单例模式、匿名内部类、源码改动等认识。
1.理想与现实——总会有差距
需求催生产品,产品总是滞后不断更新的需求。
需求:一款visualization tool,像Gephi那样炫酷,像i2那样流畅强大;
产品:不断更迭中
问题:需要全手工搭建,了解Swing,熟悉Prefuse,学会借鉴,深谙拼凑整合之道
功能(目前):数据导入+连接数据库+图片另存为+布局算法切换+节点标签显示+节点形状改变+适合屏幕显示+高亮近邻显示+控制图形布局开关 等等
产品部分截图:
(1)主界面
界面顶部是实现功能的菜单栏(当然还有裹在各个菜单栏里面的菜单选项);
界面左边是信息提示框,用于显示导入图形的节点和边的相关信息,信息提示面板中还有一个隐藏的Label用于显示有向/无向/混合图信息以及一个控制布局的开关(初始状态为灰,不可用);
界面的核心部分是一个JSplitPanel,用于图形的展示;
整个图形的布局采用GridBagLayout,无需为工具因为缩放以及最大化最小化而导致控件大小不能动态调整而烦恼
(2)文件菜单栏
其中功能有"打开...":用于打开指定文件并导入数据
连接数据库:用于连接Sql server2005数据库,从中读取点和边信息并展示图形,详情点击这里。
存储为:初始状态为灰不可用,因为此时没有导入图形,当图形导入并展现后便可变为正常可用。用于存储展示图形
退出:exit tool
其他菜单栏不一一展示,在图形为加载到面板时,大多数为灰不可用状态。
布局算法菜单栏:力导向布局、圆形布局、随机布局、网格布局、FruchtermanReingold布局
标签配置菜单栏:不显示、显示文本、显示文本与图片
节点配置菜单栏:圆形、矩形、圆角矩形
控制器菜单栏:适应屏幕显示
2.源码动一动——让不可能成为可能
作为老产品,想要与Gephi等同台竞技,不仅要拼实力,更要拼灵活应对,通过读烂API都无法解决的问题,换个思维,动一动源码就会豁然开朗。
(1)节点形状
源码中,Prefuse图形展示的节点是矩形或是圆角矩形,怎么看怎么觉得有点突兀,不柔和,相比之下Gephi中的圆形看上去要舒服得多。那么问题来了……如何完成这个功能,仅仅通过传参等手段已经不够用了,所以顺藤摸瓜,找到LabelRenderer类
package prefuse.render;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RectangularShape;
import java.awt.geom.RoundRectangle2D;
import prefuse.Constants;
import prefuse.util.ColorLib;
import prefuse.util.FontLib;
import prefuse.util.GraphicsLib;
import prefuse.util.StringLib;
import prefuse.visual.VisualItem;
/**渲染一个包含或文本或图片或兼有之的label标签
* Renderer that draws a label, which consists of a text string,
* an image, or both.
*
* <p>When created using the default constructor, the renderer attempts
* to use text from the "label" field. To use a different field, use the
* appropriate constructor or use the {@link #setTextField(String)} method.
* To perform custom String selection, subclass this Renderer and override the
* {@link #getText(VisualItem)} method. When the text field is
* <code>null</code>, no text label will be shown. Labels can span multiple
* lines of text, determined by the presence of newline characters ('\n')
* within the text string.</p>
*
* <p>By default, no image is shown. To show an image, the image field needs
* to be set, either using the appropriate constructor or the
* {@link #setImageField(String)} method. The value of the image field should
* be a text string indicating the location of the image file to use. The
* string should be either a URL, a file located on the current classpath,
* or a file on the local filesystem. If found, the image will be managed
* internally by an {@link ImageFactory} instance, which maintains a
* cache of loaded images.</p>
*
* <p>The position of the image relative to text can be set using the
* {@link #setImagePosition(int)} method. Images can be placed to the
* left, right, above, or below the text. The horizontal and vertical
* alignments of either the text or the image can be set explicitly
* using the appropriate methods of this class (e.g.,
* {@link #setHorizontalTextAlignment(int)}). By default, both the
* text and images are centered along both the horizontal and
* vertical directions.</p>
*
* @author <a href="http://jheer.org">jeffrey heer</a>
*/
public class LabelRenderer extends AbstractShapeRenderer {
protected ImageFactory m_images = null;
protected String m_delim = "\n";
protected String m_labelName = "label";
protected String m_imageName = null;
protected int m_xAlign = Constants.CENTER;
protected int m_yAlign = Constants.CENTER;
protected int m_hTextAlign = Constants.CENTER;
protected int m_vTextAlign = Constants.CENTER;
protected int m_hImageAlign = Constants.CENTER;
protected int m_vImageAlign = Constants.CENTER;
protected int m_imagePos = Constants.LEFT;
protected int m_horizBorder = 2;
protected int m_vertBorder = 0;
protected int m_imageMargin = 2;
protected int m_arcWidth = 0;
protected int m_arcHeight = 0;
protected int m_maxTextWidth = -1;
/** Transform used to scale and position images */
AffineTransform m_transform = new AffineTransform();
/** The holder for the currently computed bounding box */
protected RectangularShape m_bbox = new Rectangle2D.Double(); // Prefuse框架自带的,即默认使用矩形
protected Point2D m_pt = new Point2D.Double(); // temp point
protected Font m_font; // temp font holder
protected String m_text; // label text
protected Dimension m_textDim = new Dimension(); // text width / height
/**
* Create a new LabelRenderer. By default the field "label" is used
* as the field name for looking up text, and no image is used.
*/
public LabelRenderer() {
}
/**
* Create a new LabelRenderer. Draws a text label using the given
* text data field and does not draw an image.
* @param textField the data field for the text label.
*/
public LabelRenderer(String textField) {
this.setTextField(textField);
}
/**
* Create a new LabelRenderer. Draws a text label using the given text
* data field, and draws the image at the location reported by the
* given image data field.
* @param textField the data field for the text label
* @param imageField the data field for the image location. This value
* in the data field should be a URL, a file within the current classpath,
* a file on the filesystem, or null for no image. If the
* <code>imageField</code> parameter is null, no images at all will be
* drawn.
*/
public LabelRenderer(String textField, String imageField) {
setTextField(textField);
setImageField(imageField);
}
// ------------------------------------------------------------------------
/**
* Rounds the corners of the bounding rectangle in which the text
* string is rendered. This will only be seen if either the stroke
* or fill color is non-transparent.
* @param arcWidth the width of the curved corner
* @param arcHeight the height of the curved corner
*/
public void setRoundedCorner(int arcWidth, int arcHeight) {
if ( (arcWidth == 0 || arcHeight == 0) &&
!(m_bbox instanceof Rectangle2D) ) {
m_bbox = new Rectangle2D.Double();
} else {
if ( !(m_bbox instanceof RoundRectangle2D) )
m_bbox = new RoundRectangle2D.Double();
((RoundRectangle2D)m_bbox)
.setRoundRect(0,0,10,10,arcWidth,arcHeight);
m_arcWidth = arcWidth;
m_arcHeight = arcHeight;
}
}
/**
* Get the field name to use for text labels.
* @return the data field for text labels, or null for no text
*/
public String getTextField() {
return m_labelName;
}
/**
* Set the field name to use for text labels.
* @param textField the data field for text labels, or null for no text
*/
public void setTextField(String textField) {
m_labelName = textField;
}
/**
* Sets the maximum width that should be allowed of the text label.
* A value of -1 specifies no limit (this is the default).
* @param maxWidth the maximum width of the text or -1 for no limit
*/
public void setMaxTextWidth(int maxWidth) {
m_maxTextWidth = maxWidth;
}
/**
* Returns the text to draw. Subclasses can override this class to
* perform custom text selection.
* @param item the item to represent as a <code>String</code>
* @return a <code>String</code> to draw
*/
protected String getText(VisualItem item) {
String s = null;
if ( item.canGetString(m_labelName) ) {
return item.getString(m_labelName);
}
return s;
}
// ------------------------------------------------------------------------
// Image Handling
/**
* Get the data field for image locations. The value stored
* in the data field should be a URL, a file within the current classpath,
* a file on the filesystem, or null for no image.
* @return the data field for image locations, or null for no images
*/
public String getImageField() {
return m_imageName;
}
/**
* Set the data field for image locations. The value stored
* in the data field should be a URL, a file within the current classpath,
* a file on the filesystem, or null for no image. If the
* <code>imageField</code> parameter is null, no images at all will be
* drawn.
* @param imageField the data field for image locations, or null for
* no images
*/
public void setImageField(String imageField) {
if ( imageField != null ) m_images = new ImageFactory();
m_imageName = imageField;
}
/**
* Sets the maximum image dimensions, used to control scaling of loaded
* images. This scaling is enforced immediately upon loading of the image.
* @param width the maximum width of images (-1 for no limit)
* @param height the maximum height of images (-1 for no limit)
*/
public void setMaxImageDimensions(int width, int height) {
if ( m_images == null ) m_images = new ImageFactory();
m_images.setMaxImageDimensions(width, height);
}
/**
* Returns a location string for the image to draw. Subclasses can override
* this class to perform custom image selection beyond looking up the value
* from a data field.
* @param item the item for which to select an image to draw
* @return the location string for the image to use, or null for no image
*/
protected String getImageLocation(VisualItem item) {
return item.canGetString(m_imageName)
? item.getString(m_imageName)
: null;
}
/**
* Get the image to include in the label for the given VisualItem.
* @param item the item to get an image for
* @return the image for the item, or null for no image
*/
protected Image getImage(VisualItem item) {
String imageLoc = getImageLocation(item);
return ( imageLoc == null ? null : m_images.getImage(imageLoc) );
}
// ------------------------------------------------------------------------
// Rendering
private String computeTextDimensions(VisualItem item, String text,
double size)
{
// put item font in temp member variable
m_font = item.getFont();
// scale the font as needed
if ( size != 1 ) {
m_font = FontLib.getFont(m_font.getName(), m_font.getStyle(),
size*m_font.getSize());
}
FontMetrics fm = DEFAULT_GRAPHICS.getFontMetrics(m_font);
StringBuffer str = null;
// compute the number of lines and the maximum width
int nlines = 1, w = 0, start = 0, end = text.indexOf(m_delim);
m_textDim.width = 0;
String line;
for ( ; end >= 0; ++nlines ) {
w = fm.stringWidth(line=text.substring(start,end));
// abbreviate line as needed
if ( m_maxTextWidth > -1 && w > m_maxTextWidth ) {
if ( str == null )
str = new StringBuffer(text.substring(0,start));
str.append(StringLib.abbreviate(line, fm, m_maxTextWidth));
str.append(m_delim);
w = m_maxTextWidth;
} else if ( str != null ) {
str.append(line).append(m_delim);
}
// update maximum width and substring indices
m_textDim.width = Math.max(m_textDim.width, w);
start = end+1;
end = text.indexOf(m_delim, start);
}
w = fm.stringWidth(line=text.substring(start));
// abbreviate line as needed
if ( m_maxTextWidth > -1 && w > m_maxTextWidth ) {
if ( str == null )
str = new StringBuffer(text.substring(0,start));
str.append(StringLib.abbreviate(line, fm, m_maxTextWidth));
w = m_maxTextWidth;
} else if ( str != null ) {
str.append(line);
}
// update maximum width
m_textDim.width = Math.max(m_textDim.width, w);
// compute the text height
m_textDim.height = fm.getHeight() * nlines;
return str==null ? text : str.toString();
}
/**
* @see prefuse.render.AbstractShapeRenderer#getRawShape(prefuse.visual.VisualItem)
*/
protected Shape getRawShape(VisualItem item) {
m_text = getText(item);
Image img = getImage(item);
double size = item.getSize();
// get image dimensions
double iw=0, ih=0;
if ( img != null ) {
ih = img.getHeight(null);
iw = img.getWidth(null);
}
// get text dimensions
int tw=0, th=0;
if ( m_text != null ) {
m_text = computeTextDimensions(item, m_text, size);
th = m_textDim.height;
tw = m_textDim.width;
}
// get bounding box dimensions
double w=0, h=0;
switch ( m_imagePos ) {
case Constants.LEFT:
case Constants.RIGHT:
w = tw + size*(iw +2*m_horizBorder
+ (tw>0 && iw>0 ? m_imageMargin : 0));
h = Math.max(th, size*ih) + size*2*m_vertBorder;
break;
case Constants.TOP:
case Constants.BOTTOM:
w = Math.max(tw, size*iw) + size*2*m_horizBorder;
h = th + size*(ih + 2*m_vertBorder
+ (th>0 && ih>0 ? m_imageMargin : 0));
break;
default:
throw new IllegalStateException(
"Unrecognized image alignment setting.");
}
// get the top-left point, using the current alignment settings
getAlignedPoint(m_pt, item, w, h, m_xAlign, m_yAlign);
if ( m_bbox instanceof RoundRectangle2D ) {
RoundRectangle2D rr = (RoundRectangle2D)m_bbox;
rr.setRoundRect(m_pt.getX(), m_pt.getY(), w, h,
size*m_arcWidth, size*m_arcHeight);
} else {
m_bbox.setFrame(m_pt.getX(), m_pt.getY(), w, h);
}
return m_bbox;
}
/**
* Helper method, which calculates the top-left co-ordinate of an item
* given the item's alignment.
*/
protected static void getAlignedPoint(Point2D p, VisualItem item,
double w, double h, int xAlign, int yAlign)
{
double x = item.getX(), y = item.getY();
if ( Double.isNaN(x) || Double.isInfinite(x) )
x = 0; // safety check
if ( Double.isNaN(y) || Double.isInfinite(y) )
y = 0; // safety check
if ( xAlign == Constants.CENTER ) {
x = x-(w/2);
} else if ( xAlign == Constants.RIGHT ) {
x = x-w;
}
if ( yAlign == Constants.CENTER ) {
y = y-(h/2);
} else if ( yAlign == Constants.BOTTOM ) {
y = y-h;
}
p.setLocation(x,y);
}
/**
* @see prefuse.render.Renderer#render(java.awt.Graphics2D, prefuse.visual.VisualItem)
*/
public void render(Graphics2D g, VisualItem item) {
RectangularShape shape = (RectangularShape)getShape(item);
if ( shape == null ) return;
// fill the shape, if requested
int type = getRenderType(item);
if ( type==RENDER_TYPE_FILL || type==RENDER_TYPE_DRAW_AND_FILL )
GraphicsLib.paint(g, item, shape, getStroke(item), RENDER_TYPE_FILL);
// now render the image and text
String text = m_text;
Image img = getImage(item);
if ( text == null && img == null )
return;
double size = item.getSize();
boolean useInt = 1.5 > Math.max(g.getTransform().getScaleX(),
g.getTransform().getScaleY());
double x = shape.getMinX() + size*m_horizBorder;
double y = shape.getMinY() + size*m_vertBorder;
// render image
if ( img != null ) {
double w = size * img.getWidth(null);
double h = size * img.getHeight(null);
double ix=x, iy=y;
// determine one co-ordinate based on the image position
switch ( m_imagePos ) {
case Constants.LEFT:
x += w + size*m_imageMargin;
break;
case Constants.RIGHT:
ix = shape.getMaxX() - size*m_horizBorder - w;
break;
case Constants.TOP:
y += h + size*m_imageMargin;
break;
case Constants.BOTTOM:
iy = shape.getMaxY() - size*m_vertBorder - h;
break;
default:
throw new IllegalStateException(
"Unrecognized image alignment setting.");
}
// determine the other coordinate based on image alignment
switch ( m_imagePos ) {
case Constants.LEFT:
case Constants.RIGHT:
// need to set image y-coordinate
switch ( m_vImageAlign ) {
case Constants.TOP:
break;
case Constants.BOTTOM:
iy = shape.getMaxY() - size*m_vertBorder - h;
break;
case Constants.CENTER:
iy = shape.getCenterY() - h/2;
break;
}
break;
case Constants.TOP:
case Constants.BOTTOM:
// need to set image x-coordinate
switch ( m_hImageAlign ) {
case Constants.LEFT:
break;
case Constants.RIGHT:
ix = shape.getMaxX() - size*m_horizBorder - w;
break;
case Constants.CENTER:
ix = shape.getCenterX() - w/2;
break;
}
break;
}
if ( useInt && size == 1.0 ) {
// if possible, use integer precision
// results in faster, flicker-free image rendering
g.drawImage(img, (int)ix, (int)iy, null);
} else {
m_transform.setTransform(size,0,0,size,ix,iy);
g.drawImage(img, m_transform, null);
}
}
// render text
int textColor = item.getTextColor();
if ( text != null && ColorLib.alpha(textColor) > 0 ) {
g.setPaint(ColorLib.getColor(textColor));
g.setFont(m_font);
FontMetrics fm = DEFAULT_GRAPHICS.getFontMetrics(m_font);
// compute available width
double tw;
switch ( m_imagePos ) {
case Constants.TOP:
case Constants.BOTTOM:
tw = shape.getWidth() - 2*size*m_horizBorder;
break;
default:
tw = m_textDim.width;
}
// compute available height
double th;
switch ( m_imagePos ) {
case Constants.LEFT:
case Constants.RIGHT:
th = shape.getHeight() - 2*size*m_vertBorder;
break;
default:
th = m_textDim.height;
}
// compute starting y-coordinate
y += fm.getAscent();
switch ( m_vTextAlign ) {
case Constants.TOP:
break;
case Constants.BOTTOM:
y += th - m_textDim.height;
break;
case Constants.CENTER:
y += (th - m_textDim.height)/2;
}
// render each line of text
int lh = fm.getHeight(); // the line height
int start = 0, end = text.indexOf(m_delim);
for ( ; end >= 0; y += lh ) {
drawString(g, fm, text.substring(start, end), useInt, x, y, tw);
start = end+1;
end = text.indexOf(m_delim, start);
}
drawString(g, fm, text.substring(start), useInt, x, y, tw);
}
// draw border
if (type==RENDER_TYPE_DRAW || type==RENDER_TYPE_DRAW_AND_FILL) {
GraphicsLib.paint(g,item,shape,getStroke(item),RENDER_TYPE_DRAW);
}
}
private final void drawString(Graphics2D g, FontMetrics fm, String text,
boolean useInt, double x, double y, double w)
{
// compute the x-coordinate
double tx;
switch ( m_hTextAlign ) {
case Constants.LEFT:
tx = x;
break;
case Constants.RIGHT:
tx = x + w - fm.stringWidth(text);
break;
case Constants.CENTER:
tx = x + (w - fm.stringWidth(text)) / 2;
break;
default:
throw new IllegalStateException(
"Unrecognized text alignment setting.");
}
// use integer precision unless zoomed-in
// results in more stable drawing
if ( useInt ) {
g.drawString(text, (int)tx, (int)y);
} else {
g.drawString(text, (float)tx, (float)y);
}
}
/**
* Returns the image factory used by this renderer.
* @return the image factory
*/
public ImageFactory getImageFactory() {
if ( m_images == null ) m_images = new ImageFactory();
return m_images;
}
/**
* Sets the image factory used by this renderer.
* @param ifact the image factory
*/
public void setImageFactory(ImageFactory ifact) {
m_images = ifact;
}
// ------------------------------------------------------------------------
/**
* Get the horizontal text alignment within the layout. One of
* {@link prefuse.Constants#LEFT}, {@link prefuse.Constants#RIGHT}, or
* {@link prefuse.Constants#CENTER}. The default is centered text.
* @return the horizontal text alignment
*/
public int getHorizontalTextAlignment() {
return m_hTextAlign;
}
/**
* Set the horizontal text alignment within the layout. One of
* {@link prefuse.Constants#LEFT}, {@link prefuse.Constants#RIGHT}, or
* {@link prefuse.Constants#CENTER}. The default is centered text.
* @param halign the desired horizontal text alignment
*/
public void setHorizontalTextAlignment(int halign) {
if ( halign != Constants.LEFT &&
halign != Constants.RIGHT &&
halign != Constants.CENTER )
throw new IllegalArgumentException(
"Illegal horizontal text alignment value.");
m_hTextAlign = halign;
}
/**
* Get the vertical text alignment within the layout. One of
* {@link prefuse.Constants#TOP}, {@link prefuse.Constants#BOTTOM}, or
* {@link prefuse.Constants#CENTER}. The default is centered text.
* @return the vertical text alignment
*/
public int getVerticalTextAlignment() {
return m_vTextAlign;
}
/**
* Set the vertical text alignment within the layout. One of
* {@link prefuse.Constants#TOP}, {@link prefuse.Constants#BOTTOM}, or
* {@link prefuse.Constants#CENTER}. The default is centered text.
* @param valign the desired vertical text alignment
*/
public void setVerticalTextAlignment(int valign) {
if ( valign != Constants.TOP &&
valign != Constants.BOTTOM &&
valign != Constants.CENTER )
throw new IllegalArgumentException(
"Illegal vertical text alignment value.");
m_vTextAlign = valign;
}
/**
* Get the horizontal image alignment within the layout. One of
* {@link prefuse.Constants#LEFT}, {@link prefuse.Constants#RIGHT}, or
* {@link prefuse.Constants#CENTER}. The default is a centered image.
* @return the horizontal image alignment
*/
public int getHorizontalImageAlignment() {
return m_hImageAlign;
}
/**
* Set the horizontal image alignment within the layout. One of
* {@link prefuse.Constants#LEFT}, {@link prefuse.Constants#RIGHT}, or
* {@link prefuse.Constants#CENTER}. The default is a centered image.
* @param halign the desired horizontal image alignment
*/
public void setHorizontalImageAlignment(int halign) {
if ( halign != Constants.LEFT &&
halign != Constants.RIGHT &&
halign != Constants.CENTER )
throw new IllegalArgumentException(
"Illegal horizontal text alignment value.");
m_hImageAlign = halign;
}
/**
* Get the vertical image alignment within the layout. One of
* {@link prefuse.Constants#TOP}, {@link prefuse.Constants#BOTTOM}, or
* {@link prefuse.Constants#CENTER}. The default is a centered image.
* @return the vertical image alignment
*/
public int getVerticalImageAlignment() {
return m_vImageAlign;
}
/**
* Set the vertical image alignment within the layout. One of
* {@link prefuse.Constants#TOP}, {@link prefuse.Constants#BOTTOM}, or
* {@link prefuse.Constants#CENTER}. The default is a centered image.
* @param valign the desired vertical image alignment
*/
public void setVerticalImageAlignment(int valign) {
if ( valign != Constants.TOP &&
valign != Constants.BOTTOM &&
valign != Constants.CENTER )
throw new IllegalArgumentException(
"Illegal vertical text alignment value.");
m_vImageAlign = valign;
}
/**
* Get the image position, determining where the image is placed with
* respect to the text. One of {@link Constants#LEFT},
* {@link Constants#RIGHT}, {@link Constants#TOP}, or
* {@link Constants#BOTTOM}. The default is left.
* @return the image position
*/
public int getImagePosition() {
return m_imagePos;
}
/**
* Set the image position, determining where the image is placed with
* respect to the text. One of {@link Constants#LEFT},
* {@link Constants#RIGHT}, {@link Constants#TOP}, or
* {@link Constants#BOTTOM}. The default is left.
* @param pos the desired image position
*/
public void setImagePosition(int pos) {
if ( pos != Constants.TOP &&
pos != Constants.BOTTOM &&
pos != Constants.LEFT &&
pos != Constants.RIGHT &&
pos != Constants.CENTER )
throw new IllegalArgumentException(
"Illegal image position value.");
m_imagePos = pos;
}
// ------------------------------------------------------------------------
/**
* Get the horizontal alignment of this node with respect to its
* x, y coordinates.
* @return the horizontal alignment, one of
* {@link prefuse.Constants#LEFT}, {@link prefuse.Constants#RIGHT}, or
* {@link prefuse.Constants#CENTER}.
*/
public int getHorizontalAlignment() {
return m_xAlign;
}
/**
* Get the vertical alignment of this node with respect to its
* x, y coordinates.
* @return the vertical alignment, one of
* {@link prefuse.Constants#TOP}, {@link prefuse.Constants#BOTTOM}, or
* {@link prefuse.Constants#CENTER}.
*/
public int getVerticalAlignment() {
return m_yAlign;
}
/**
* Set the horizontal alignment of this node with respect to its
* x, y coordinates.
* @param align the horizontal alignment, one of
* {@link prefuse.Constants#LEFT}, {@link prefuse.Constants#RIGHT}, or
* {@link prefuse.Constants#CENTER}.
*/
public void setHorizontalAlignment(int align) {
m_xAlign = align;
}
/**
* Set the vertical alignment of this node with respect to its
* x, y coordinates.
* @param align the vertical alignment, one of
* {@link prefuse.Constants#TOP}, {@link prefuse.Constants#BOTTOM}, or
* {@link prefuse.Constants#CENTER}.
*/
public void setVerticalAlignment(int align) {
m_yAlign = align;
}
/**
* Returns the amount of padding in pixels between the content
* and the border of this item along the horizontal dimension.
* @return the horizontal padding
*/
public int getHorizontalPadding() {
return m_horizBorder;
}
/**
* Sets the amount of padding in pixels between the content
* and the border of this item along the horizontal dimension.
* @param xpad the horizontal padding to set
*/
public void setHorizontalPadding(int xpad) {
m_horizBorder = xpad;
}
/**
* Returns the amount of padding in pixels between the content
* and the border of this item along the vertical dimension.
* @return the vertical padding
*/
public int getVerticalPadding() {
return m_vertBorder;
}
/**
* Sets the amount of padding in pixels between the content
* and the border of this item along the vertical dimension.
* @param ypad the vertical padding
*/
public void setVerticalPadding(int ypad) {
m_vertBorder = ypad;
}
/**
* Get the padding, in pixels, between an image and text.
* @return the padding between an image and text
*/
public int getImageTextPadding() {
return m_imageMargin;
}
/**
* Set the padding, in pixels, between an image and text.
* @param pad the padding to use between an image and text
*/
public void setImageTextPadding(int pad) {
m_imageMargin = pad;
}
} // end of class LabelRenderer
从该类的红色代码可以发现,这里只有两种形状RoundRectangle2D和Rectangle2D,所以需要修改源码,添加第三种形状圆形即Arc2D。
改动如下:
package prefuse.render; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Arc2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RectangularShape; import java.awt.geom.RoundRectangle2D; import java.util.Iterator; import prefuse.Constants; import prefuse.data.Node; import prefuse.util.ColorLib; import prefuse.util.FontLib; import prefuse.util.GraphicsLib; import prefuse.util.StringLib; import prefuse.visual.NodeItem; import prefuse.visual.VisualItem; /** * 渲染一个包含或文本或图片或兼有之的label标签 Renderer that draws a label, which consists of a * text string, an image, or both. * * <p> * When created using the default constructor, the renderer attempts to use text * from the "label" field. To use a different field, use the appropriate * constructor or use the {@link #setTextField(String)} method. To perform * custom String selection, subclass this Renderer and override the * {@link #getText(VisualItem)} method. When the text field is <code>null</code> * , no text label will be shown. Labels can span multiple lines of text, * determined by the presence of newline characters ('\n') within the text * string. * </p> * * <p> * By default, no image is shown. To show an image, the image field needs to be * set, either using the appropriate constructor or the * {@link #setImageField(String)} method. The value of the image field should be * a text string indicating the location of the image file to use. The string * should be either a URL, a file located on the current classpath, or a file on * the local filesystem. If found, the image will be managed internally by an * {@link ImageFactory} instance, which maintains a cache of loaded images. * </p> * * <p> * The position of the image relative to text can be set using the * {@link #setImagePosition(int)} method. Images can be placed to the left, * right, above, or below the text. The horizontal and vertical alignments of * either the text or the image can be set explicitly using the appropriate * methods of this class (e.g., {@link #setHorizontalTextAlignment(int)}). By * default, both the text and images are centered along both the horizontal and * vertical directions. * </p> * * @author <a href="http://jheer.org">jeffrey heer</a> */ public class LabelRenderer extends AbstractShapeRenderer { public static enum LabelShape{ Rectangle2D, Arc2D } protected ImageFactory m_images = null; protected String m_delim = "\n"; protected String m_labelName = "label"; protected String m_imageName = null; protected int m_xAlign = Constants.CENTER; protected int m_yAlign = Constants.CENTER; protected int m_hTextAlign = Constants.CENTER; protected int m_vTextAlign = Constants.CENTER; protected int m_hImageAlign = Constants.CENTER; protected int m_vImageAlign = Constants.CENTER; protected int m_imagePos = Constants.LEFT; protected int m_horizBorder = 2; protected int m_vertBorder = 1; protected int m_imageMargin = 2; protected int m_arcWidth = 0; protected int m_arcHeight = 0; protected int m_maxTextWidth = -1; /** Transform used to scale and position images */ AffineTransform m_transform = new AffineTransform(); /** The holder for the currently computed bounding box */ protected RectangularShape m_bbox = new Rectangle2D.Double(); // Prefuse框架自带的,即默认使用矩形 protected Point2D m_pt = new Point2D.Double(); // temp point protected Font m_font; // temp font holder protected String m_text; // label text protected Dimension m_textDim = new Dimension(); // text width / height /** * Create a new LabelRenderer. By default the field "label" is used as the * field name for looking up text, and no image is used. */ public LabelRenderer() { } /** * Create a new LabelRenderer. Draws a text label using the given text data * field and does not draw an image. * * @param textField * the data field for the text label. */ public LabelRenderer(String textField) { this.setTextField(textField); } /** * * @param textField * @param isRectangle * true-矩形 false-圆形 */ public LabelRenderer(String textField, LabelShape shape) { if (shape == LabelShape.Rectangle2D) { m_bbox = new Rectangle2D.Double(); } else { m_bbox = new Arc2D.Double(); } this.setTextField(textField); } /** * Create a new LabelRenderer. Draws a text label using the given text data * field, and draws the image at the location reported by the given image * data field. * * @param textField * the data field for the text label * @param imageField * the data field for the image location. This value in the data * field should be a URL, a file within the current classpath, a * file on the filesystem, or null for no image. If the * <code>imageField</code> parameter is null, no images at all * will be drawn. */ public LabelRenderer(String textField, String imageField) { setTextField(textField); setImageField(imageField); } // ------------------------------------------------------------------------ /** * Rounds the corners of the bounding rectangle in which the text string is * rendered. This will only be seen if either the stroke or fill color is * non-transparent. * * @param arcWidth * the width of the curved corner * @param arcHeight * the height of the curved corner */ public void setRoundedCorner(int arcWidth, int arcHeight) { if ((arcWidth == 0 || arcHeight == 0) && !(m_bbox instanceof Rectangle2D)) { m_bbox = new Rectangle2D.Double(); } else { if (!(m_bbox instanceof RoundRectangle2D)) m_bbox = new RoundRectangle2D.Double(); ((RoundRectangle2D) m_bbox).setRoundRect(0, 0, 10, 10, arcWidth, arcHeight); m_arcWidth = arcWidth; m_arcHeight = arcHeight; } } /** * Get the field name to use for text labels. * * @return the data field for text labels, or null for no text */ public String getTextField() { return m_labelName; } /** * Set the field name to use for text labels. * * @param textField * the data field for text labels, or null for no text */ public void setTextField(String textField) { m_labelName = textField; } /** * Sets the maximum width that should be allowed of the text label. A value * of -1 specifies no limit (this is the default). * * @param maxWidth * the maximum width of the text or -1 for no limit */ public void setMaxTextWidth(int maxWidth) { m_maxTextWidth = maxWidth; } /** * Returns the text to draw. Subclasses can override this class to perform * custom text selection. * * @param item * the item to represent as a <code>String</code> * @return a <code>String</code> to draw */ protected String getText(VisualItem item) { String s = null; if (item.canGetString(m_labelName)) { return item.getString(m_labelName); } return s; } // ------------------------------------------------------------------------ // Image Handling /** * Get the data field for image locations. The value stored in the data * field should be a URL, a file within the current classpath, a file on the * filesystem, or null for no image. * * @return the data field for image locations, or null for no images */ public String getImageField() { return m_imageName; } /** * Set the data field for image locations. The value stored in the data * field should be a URL, a file within the current classpath, a file on the * filesystem, or null for no image. If the <code>imageField</code> * parameter is null, no images at all will be drawn. * * @param imageField * the data field for image locations, or null for no images */ public void setImageField(String imageField) { if (imageField != null) m_images = new ImageFactory(); m_imageName = imageField; } /** * Sets the maximum image dimensions, used to control scaling of loaded * images. This scaling is enforced immediately upon loading of the image. * * @param width * the maximum width of images (-1 for no limit) * @param height * the maximum height of images (-1 for no limit) */ public void setMaxImageDimensions(int width, int height) { if (m_images == null) m_images = new ImageFactory(); m_images.setMaxImageDimensions(width, height); } /** * Returns a location string for the image to draw. Subclasses can override * this class to perform custom image selection beyond looking up the value * from a data field. * * @param item * the item for which to select an image to draw * @return the location string for the image to use, or null for no image */ protected String getImageLocation(VisualItem item) { return item.canGetString(m_imageName) ? item.getString(m_imageName) : null; } /** * Get the image to include in the label for the given VisualItem. * * @param item * the item to get an image for * @return the image for the item, or null for no image */ protected Image getImage(VisualItem item) { String imageLoc = getImageLocation(item); return (imageLoc == null ? null : m_images.getImage(imageLoc)); } // ------------------------------------------------------------------------ // Rendering private String computeTextDimensions(VisualItem item, String text, double size) { // put item font in temp member variable m_font = item.getFont(); // scale the font as needed if (size != 1) { m_font = FontLib.getFont(m_font.getName(), m_font.getStyle(), size * m_font.getSize()); } FontMetrics fm = DEFAULT_GRAPHICS.getFontMetrics(m_font); StringBuffer str = null; // compute the number of lines and the maximum width int nlines = 1, w = 0, start = 0, end = text.indexOf(m_delim); m_textDim.width = 0; String line; for (; end >= 0; ++nlines) { w = fm.stringWidth(line = text.substring(start, end)); // abbreviate line as needed if (m_maxTextWidth > -1 && w > m_maxTextWidth) { if (str == null) str = new StringBuffer(text.substring(0, start)); str.append(StringLib.abbreviate(line, fm, m_maxTextWidth)); str.append(m_delim); w = m_maxTextWidth; } else if (str != null) { str.append(line).append(m_delim); } // update maximum width and substring indices m_textDim.width = Math.max(m_textDim.width, w); start = end + 1; end = text.indexOf(m_delim, start); } w = fm.stringWidth(line = text.substring(start)); // abbreviate line as needed if (m_maxTextWidth > -1 && w > m_maxTextWidth) { if (str == null) str = new StringBuffer(text.substring(0, start)); str.append(StringLib.abbreviate(line, fm, m_maxTextWidth)); w = m_maxTextWidth; } else if (str != null) { str.append(line); } // update maximum width m_textDim.width = Math.max(m_textDim.width, w); // compute the text height m_textDim.height = fm.getHeight() * nlines; return str == null ? text : str.toString(); } /** * @see prefuse.render.AbstractShapeRenderer#getRawShape(prefuse.visual.VisualItem) */ protected Shape getRawShape(VisualItem item) { m_text = getText(item); Image img = getImage(item); double size = item.getSize(); // get image dimensions double iw=0, ih=0; if ( img != null ) { ih = img.getHeight(null); iw = img.getWidth(null); } // get text dimensions int tw=0, th=0; if ( m_text != null ) { m_text = computeTextDimensions(item, m_text, size); th = m_textDim.height; tw = m_textDim.width; } // get bounding box dimensions double w=0, h=0; switch ( m_imagePos ) { case Constants.LEFT: case Constants.RIGHT: w = tw + size*(iw +2*m_horizBorder + (tw>0 && iw>0 ? m_imageMargin : 0)); h = Math.max(th, size*ih) + size*2*m_vertBorder; break; case Constants.TOP: case Constants.BOTTOM: w = Math.max(tw, size*iw) + size*2*m_horizBorder; h = th + size*(ih + 2*m_vertBorder + (th>0 && ih>0 ? m_imageMargin : 0)); break; default: throw new IllegalStateException( "Unrecognized image alignment setting."); } // get the top-left point, using the current alignment settings getAlignedPoint(m_pt, item, w, h, m_xAlign, m_yAlign); if (m_bbox instanceof RoundRectangle2D) {// 圆角矩形 RoundRectangle2D rr = (RoundRectangle2D) m_bbox; rr.setRoundRect(m_pt.getX(), m_pt.getY(), w, h, size * m_arcWidth, size * m_arcHeight); } else if (m_bbox instanceof Arc2D) {// 圆形 Arc2D rr = (Arc2D) m_bbox; rr.setArc(m_pt.getX(), m_pt.getY(), w, w, 360, 360, Arc2D.OPEN); } else { m_bbox.setFrame(m_pt.getX(), m_pt.getY(), w, h);// 矩形 } return m_bbox; } /** * Helper method, which calculates the top-left co-ordinate of an item given * the item's alignment. */ protected static void getAlignedPoint(Point2D p, VisualItem item, double w, double h, int xAlign, int yAlign) { double x = item.getX(), y = item.getY(); if (Double.isNaN(x) || Double.isInfinite(x)) x = 0; // safety check if (Double.isNaN(y) || Double.isInfinite(y)) y = 0; // safety check if (xAlign == Constants.CENTER) { x = x - (w / 2); } else if (xAlign == Constants.RIGHT) { x = x - w; } if (yAlign == Constants.CENTER) { y = y - (h / 2); } else if (yAlign == Constants.BOTTOM) { y = y - h; } p.setLocation(x, y); } /** * @see prefuse.render.Renderer#render(java.awt.Graphics2D, * prefuse.visual.VisualItem) */ public void render(Graphics2D g, VisualItem item) { RectangularShape shape = (RectangularShape) getShape(item); if (shape == null) return; // fill the shape, if requested int type = getRenderType(item); if (type == RENDER_TYPE_FILL || type == RENDER_TYPE_DRAW_AND_FILL) GraphicsLib.paint(g, item, shape, getStroke(item), RENDER_TYPE_FILL); // now render the image and text String text = m_text; Image img = getImage(item); if (text == null && img == null) return; double size = item.getSize(); boolean useInt = 1.5 > Math.max(g.getTransform().getScaleX(), g.getTransform().getScaleY()); double x = shape.getMinX() + size * m_horizBorder; double y = shape.getMinY() + size * m_vertBorder; // render image if (img != null) { double w = size * img.getWidth(null); double h = size * img.getHeight(null); double ix = x, iy = y; // determine one co-ordinate based on the image position switch (m_imagePos) { case Constants.LEFT: x += w + size * m_imageMargin; break; case Constants.RIGHT: ix = shape.getMaxX() - size * m_horizBorder - w; break; case Constants.TOP: y += h + size * m_imageMargin; break; case Constants.BOTTOM: iy = shape.getMaxY() - size * m_vertBorder - h; break; default: throw new IllegalStateException("Unrecognized image alignment setting."); } // determine the other coordinate based on image alignment switch (m_imagePos) { case Constants.LEFT: case Constants.RIGHT: // need to set image y-coordinate switch (m_vImageAlign) { case Constants.TOP: break; case Constants.BOTTOM: iy = shape.getMaxY() - size * m_vertBorder - h; break; case Constants.CENTER: iy = shape.getCenterY() - h / 2; break; } break; case Constants.TOP: case Constants.BOTTOM: // need to set image x-coordinate switch (m_hImageAlign) { case Constants.LEFT: break; case Constants.RIGHT: ix = shape.getMaxX() - size * m_horizBorder - w; break; case Constants.CENTER: ix = shape.getCenterX() - w / 2; break; } break; } if (useInt && size == 1.0) { // if possible, use integer precision // results in faster, flicker-free image rendering g.drawImage(img, (int) ix, (int) iy, null); } else { m_transform.setTransform(size, 0, 0, size, ix, iy); g.drawImage(img, m_transform, null); } } // render text int textColor = item.getTextColor(); if (text != null && ColorLib.alpha(textColor) > 0) { g.setPaint(ColorLib.getColor(textColor)); g.setFont(m_font); FontMetrics fm = DEFAULT_GRAPHICS.getFontMetrics(m_font); // compute available width double tw; switch (m_imagePos) { case Constants.TOP: case Constants.BOTTOM: tw = shape.getWidth() - 2 * size * m_horizBorder; break; default: tw = m_textDim.width; } // compute available height double th; switch (m_imagePos) { case Constants.LEFT: case Constants.RIGHT: th = shape.getHeight() - 2 * size * m_vertBorder; break; default: th = m_textDim.height; } // compute starting y-coordinate y += fm.getAscent(); switch (m_vTextAlign) { case Constants.TOP: break; case Constants.BOTTOM: y += th - m_textDim.height; break; case Constants.CENTER: y += (th - m_textDim.height) / 2; } // render each line of text int lh = fm.getHeight(); // the line height int start = 0, end = text.indexOf(m_delim); for (; end >= 0; y += lh) { drawString(g, fm, text.substring(start, end), useInt, x, y, tw); start = end + 1; end = text.indexOf(m_delim, start); } drawString(g, fm, text.substring(start), useInt, x, y, tw); } // draw border if (type == RENDER_TYPE_DRAW || type == RENDER_TYPE_DRAW_AND_FILL) { GraphicsLib.paint(g, item, shape, getStroke(item), RENDER_TYPE_DRAW); } } private final void drawString(Graphics2D g, FontMetrics fm, String text, boolean useInt, double x, double y, double w) { // compute the x-coordinate double tx; switch (m_hTextAlign) { case Constants.LEFT: tx = x; break; case Constants.RIGHT: tx = x + w - fm.stringWidth(text); break; case Constants.CENTER: tx = x + (w - fm.stringWidth(text)) / 2; break; default: throw new IllegalStateException("Unrecognized text alignment setting."); } // use integer precision unless zoomed-in // results in more stable drawing if (useInt) { g.drawString(text, (int) tx, (int) y); } else { g.drawString(text, (float) tx, (float) y); } } /** * Returns the image factory used by this renderer. * * @return the image factory */ public ImageFactory getImageFactory() { if (m_images == null) m_images = new ImageFactory(); return m_images; } /** * Sets the image factory used by this renderer. * * @param ifact * the image factory */ public void setImageFactory(ImageFactory ifact) { m_images = ifact; } // ------------------------------------------------------------------------ /** * Get the horizontal text alignment within the layout. One of * {@link prefuse.Constants#LEFT}, {@link prefuse.Constants#RIGHT}, or * {@link prefuse.Constants#CENTER}. The default is centered text. * * @return the horizontal text alignment */ public int getHorizontalTextAlignment() { return m_hTextAlign; } /** * Set the horizontal text alignment within the layout. One of * {@link prefuse.Constants#LEFT}, {@link prefuse.Constants#RIGHT}, or * {@link prefuse.Constants#CENTER}. The default is centered text. * * @param halign * the desired horizontal text alignment */ public void setHorizontalTextAlignment(int halign) { if (halign != Constants.LEFT && halign != Constants.RIGHT && halign != Constants.CENTER) throw new IllegalArgumentException("Illegal horizontal text alignment value."); m_hTextAlign = halign; } /** * Get the vertical text alignment within the layout. One of * {@link prefuse.Constants#TOP}, {@link prefuse.Constants#BOTTOM}, or * {@link prefuse.Constants#CENTER}. The default is centered text. * * @return the vertical text alignment */ public int getVerticalTextAlignment() { return m_vTextAlign; } /** * Set the vertical text alignment within the layout. One of * {@link prefuse.Constants#TOP}, {@link prefuse.Constants#BOTTOM}, or * {@link prefuse.Constants#CENTER}. The default is centered text. * * @param valign * the desired vertical text alignment */ public void setVerticalTextAlignment(int valign) { if (valign != Constants.TOP && valign != Constants.BOTTOM && valign != Constants.CENTER) throw new IllegalArgumentException("Illegal vertical text alignment value."); m_vTextAlign = valign; } /** * Get the horizontal image alignment within the layout. One of * {@link prefuse.Constants#LEFT}, {@link prefuse.Constants#RIGHT}, or * {@link prefuse.Constants#CENTER}. The default is a centered image. * * @return the horizontal image alignment */ public int getHorizontalImageAlignment() { return m_hImageAlign; } /** * Set the horizontal image alignment within the layout. One of * {@link prefuse.Constants#LEFT}, {@link prefuse.Constants#RIGHT}, or * {@link prefuse.Constants#CENTER}. The default is a centered image. * * @param halign * the desired horizontal image alignment */ public void setHorizontalImageAlignment(int halign) { if (halign != Constants.LEFT && halign != Constants.RIGHT && halign != Constants.CENTER) throw new IllegalArgumentException("Illegal horizontal text alignment value."); m_hImageAlign = halign; } /** * Get the vertical image alignment within the layout. One of * {@link prefuse.Constants#TOP}, {@link prefuse.Constants#BOTTOM}, or * {@link prefuse.Constants#CENTER}. The default is a centered image. * * @return the vertical image alignment */ public int getVerticalImageAlignment() { return m_vImageAlign; } /** * Set the vertical image alignment within the layout. One of * {@link prefuse.Constants#TOP}, {@link prefuse.Constants#BOTTOM}, or * {@link prefuse.Constants#CENTER}. The default is a centered image. * * @param valign * the desired vertical image alignment */ public void setVerticalImageAlignment(int valign) { if (valign != Constants.TOP && valign != Constants.BOTTOM && valign != Constants.CENTER) throw new IllegalArgumentException("Illegal vertical text alignment value."); m_vImageAlign = valign; } /** * Get the image position, determining where the image is placed with * respect to the text. One of {@link Constants#LEFT}, * {@link Constants#RIGHT}, {@link Constants#TOP}, or * {@link Constants#BOTTOM}. The default is left. * * @return the image position */ public int getImagePosition() { return m_imagePos; } /** * Set the image position, determining where the image is placed with * respect to the text. One of {@link Constants#LEFT}, * {@link Constants#RIGHT}, {@link Constants#TOP}, or * {@link Constants#BOTTOM}. The default is left. * * @param pos * the desired image position */ public void setImagePosition(int pos) { if (pos != Constants.TOP && pos != Constants.BOTTOM && pos != Constants.LEFT && pos != Constants.RIGHT && pos != Constants.CENTER) throw new IllegalArgumentException("Illegal image position value."); m_imagePos = pos; } // ------------------------------------------------------------------------ /** * Get the horizontal alignment of this node with respect to its x, y * coordinates. * * @return the horizontal alignment, one of {@link prefuse.Constants#LEFT}, * {@link prefuse.Constants#RIGHT}, or * {@link prefuse.Constants#CENTER}. */ public int getHorizontalAlignment() { return m_xAlign; } /** * Get the vertical alignment of this node with respect to its x, y * coordinates. * * @return the vertical alignment, one of {@link prefuse.Constants#TOP}, * {@link prefuse.Constants#BOTTOM}, or * {@link prefuse.Constants#CENTER}. */ public int getVerticalAlignment() { return m_yAlign; } /** * Set the horizontal alignment of this node with respect to its x, y * coordinates. * * @param align * the horizontal alignment, one of * {@link prefuse.Constants#LEFT}, * {@link prefuse.Constants#RIGHT}, or * {@link prefuse.Constants#CENTER}. */ public void setHorizontalAlignment(int align) { m_xAlign = align; } /** * Set the vertical alignment of this node with respect to its x, y * coordinates. * * @param align * the vertical alignment, one of {@link prefuse.Constants#TOP}, * {@link prefuse.Constants#BOTTOM}, or * {@link prefuse.Constants#CENTER}. */ public void setVerticalAlignment(int align) { m_yAlign = align; } /** * Returns the amount of padding in pixels between the content and the * border of this item along the horizontal dimension. * * @return the horizontal padding */ public int getHorizontalPadding() { return m_horizBorder; } /** * Sets the amount of padding in pixels between the content and the border * of this item along the horizontal dimension. * * @param xpad * the horizontal padding to set */ public void setHorizontalPadding(int xpad) { m_horizBorder = xpad; } /** * Returns the amount of padding in pixels between the content and the * border of this item along the vertical dimension. * * @return the vertical padding */ public int getVerticalPadding() { return m_vertBorder; } /** * Sets the amount of padding in pixels between the content and the border * of this item along the vertical dimension. * * @param ypad * the vertical padding */ public void setVerticalPadding(int ypad) { m_vertBorder = ypad; } /** * Get the padding, in pixels, between an image and text. * * @return the padding between an image and text */ public int getImageTextPadding() { return m_imageMargin; } /** * Set the padding, in pixels, between an image and text. * * @param pad * the padding to use between an image and text */ public void setImageTextPadding(int pad) { m_imageMargin = pad; } } // end of class LabelRenderer
这样就可以完成如下的转变:
(2)节点与标签无关联显示
虽然(1)实现了图形的圆形节点显示,但是存在一个问题:节点的大小会因为节点上标签的不同而不同,显然这个节点大小是不具备任何意义的,所以,源码要改。我们要做的就是降低节点与标签的大小形状依赖,同时让节点的形状具有自己的意义,这里通过按照每个节点度数来决定节点的大小,为了提高健壮性,这里采用log函数对度数进行归一化处理,另外还对于节点添加边框显示。最终得到的结果如下:
从以上两个例子,我们可以发现源码并不是不可撼动,源码随着时代的变迁也会有力不从心的那一天,
3.三思而行——设计很重要 基础要扎实
(1)单例模式:
在Prefuse讲求一个数据中心Visualization的概念,所有的约束和效果都会添加到Visualization中,那么如何保证在不同类中还能获得到具有相同配置参数的数据中心对象。我们知道,不同的对象会有不同的参数、成员,所以,这里我们采用单例模式。关于什么是单例模式想必大家都不陌生,有饿汉式和懒汉式,不清楚的,网上可以找资料了解下。采用单例模式的好处是,在任何地方我们只要创建某个类的对象都是同一对象,从而保证了该对象的一致性,比如在可视化工具中要向一个已有的数据可视化中心对象visualization中添加一个布局Action效果,如果不采用传值等方法而是新建一个对象,则会造成新建的数据中心对象中只有这个新添加的布局Action,从而"丢失"了前面的所有参数。
该工具中的数据中心对象就是采用单例模式,部分代码如下:
private static Visualization visInstance; public static Visualization getVisualization(){ if(visInstance == null){ visInstance = new Visualization(); } return visInstance; }
(2)匿名内部类:
以前只是知道匿名内部类有其特有的优越性和方便性,简约,有其在Swing编程中经常被使用,但是一些使用细节不曾注意。
在实际功能编写中,遇到类似于添加监听器这样的操作,经常出现在匿名内部类中的变量报错,提示信息也不清楚,后来网上一查,才知道原来在匿名内部类中使用的外部变量都需要为final类型,了解后对于匿名内部类与外部类之间的关系与生命周期又有了新的认识。关于匿名内部类与final的渊源详见这里。
4.工具概览
下面从一个图形的导入开始,通过图形大致了解工具的一些功能:
(1)点击"打开..."菜单项后
(2)选择socialnet.xml并点击'"打开"
(3)点击"确定",图形展示
可以看到左边信息提示面板显示了图的节点和边的数量以及图的性质,同时将鼠标放在节点"Jeff"上,会有高亮显示和近邻节点显示
(4)点击"FruchtermanReingold布局"
(5)点击标签设置中的"不显示"
(6)点击节点设置中的"矩形"
(7)数据量稍大时展现不吃力
虽然只是一个简单的工具,虽然这个工具还不成气候,虽然还有太多的体会没有及时提及,但是却真真实实的检验了自己也清醒的认识了自己。
作为一名码农,应该恪守"知行合一"的铁律,没做整合之前不要说自己能做到如何狂拽炫酷,也不用说自己如何举步维艰。做好可行性分析,踏踏实实写好每一行代码,不断思考是否有新的思路和更高效的设计和方法才是王道。
如果觉得有用,记得点赞哦^_^
本文链接:《漫谈可视化Prefuse(五)---一款属于我自己的可视化工具》
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。
友情赞助
如果你觉得博主的文章对你那么一点小帮助,恰巧你又有想打赏博主的小冲动,那么事不宜迟,赶紧扫一扫,小额地赞助下,攒个奶粉钱,也是让博主有动力继续努力,写出更好的文章^^。
1. 支付宝 2. 微信