net.alantea.swing.mindmap.MindMapViewer Maven / Gradle / Ivy
package net.alantea.swing.mindmap;
import java.awt.Canvas;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.util.LinkedList;
import java.util.List;
@SuppressWarnings("serial")
public class MindMapViewer extends Canvas
{
private MindMapNode rootNode = new MindMapNode<>(null, null);
private T selected = null;
public MindMapViewer()
{
super();
}
public MindMapNode getRootNode()
{
return rootNode;
}
public void setSelected(T target)
{
selected = target;
repaint();
}
public T getSelected()
{
return selected;
}
public void setRootNode(MindMapNode node)
{
rootNode = node;
repaint();
}
private static final int DELTA_X = 30;
private static final int START_X = 5;
private static final int GAP_Y = 8;
private int centralX = Integer.MAX_VALUE;
private int centralY = Integer.MAX_VALUE;
public void setCenter(int x, int y)
{
centralX = x;
centralY = y;
repaint();
}
public void resetCenter()
{
centralX = Integer.MAX_VALUE;
centralY = Integer.MAX_VALUE;
repaint();
}
@Override
public void paint(Graphics gc)
{
Rectangle rect = getBounds();
int x = (centralX == Integer.MAX_VALUE) ? (rect.width / 2) : centralX;
int y = (centralY == Integer.MAX_VALUE) ? (rect.height / 2) : centralY;
drawNodeCenter(getRootNode(), gc, x, y);
}
/***
* x : length of text in pixels
* y : base of text in pixels
* width : width including subnodes
* height : height including subnodes
*
* @param node
* @param gc
* @return
*/
private Rectangle calculateSpan(MindMapNode node, Graphics graphics)
{
Graphics2D gc = (Graphics2D) graphics;
Point txtSize = stringExtent(gc, node.toString());
txtSize.y += GAP_Y;
Rectangle ret = new Rectangle(0, 0, txtSize.x + DELTA_X, 0);
for (MindMapNode subnode : node.getChildren())
{
Rectangle subret = calculateSpan(subnode, gc);
ret.width = Math.max(ret.width, subret.width + txtSize.x + DELTA_X);
ret.height += subret.height;
}
if (ret.height < txtSize.y)
{
ret.height = txtSize.y;
}
ret.x = txtSize.x;
ret.y = (ret.height + txtSize.y) / 2;
return ret;
}
private void drawNodeCenter(MindMapNode node, Graphics graphics, int xcenter, int ycenter)
{
Graphics2D gc = (Graphics2D) graphics;
List sizes = new LinkedList<>();
int allHeights = 0;
for (MindMapNode subnode : node.getChildren())
{
Rectangle subret = calculateSpan(subnode, gc);
sizes.add(subret);
allHeights += subret.height;
}
int n = -1;
int midHeights = 0;
int rightHeight = 0;
int leftHeight = 0;
int rightWidth = 0;
int leftWidth = 0;
for (int i = 0; i < sizes.size(); i++)
{
Rectangle size = sizes.get(i);
midHeights += size.height;
if (n == -1)
{
rightHeight += size.height;
rightWidth = Math.max(rightWidth, size.width);
if (midHeights >= allHeights / 2)
{
n = i;
}
}
else
{
leftHeight += size.height;
leftWidth = Math.max(leftWidth, size.width);
}
}
Font oldFont = gc.getFont();
Font newFont = new Font(oldFont.getFontName(), oldFont.getStyle(), oldFont.getSize() * 2);
gc.setFont(newFont);
Point txtSize = stringExtent(gc, node.toString());
FontMetrics fm = gc.getFontMetrics();
txtSize.y += GAP_Y;
int y = ycenter - txtSize.y / 2;
gc.drawString(node.toString(), xcenter - txtSize.x / 2, y + fm.getAscent() + GAP_Y / 2);
gc.setFont(oldFont);
gc.drawRoundRect(xcenter - txtSize.x / 2 - START_X, y, txtSize.x + 2 * START_X - 1, txtSize.y, START_X, START_X);
List> subnodes = node.getChildren();
int suby = ycenter - rightHeight / 2;
for (int i = 0; i <= n; i++)
{
drawNodeRight(subnodes.get(i), gc, xcenter + txtSize.x / 2 + DELTA_X, suby - GAP_Y / 2);
Point subtxtSize = stringExtent(gc, subnodes.get(i).toString());
System.out.println(subtxtSize);
subtxtSize.y += GAP_Y;
gc.drawRoundRect(xcenter + txtSize.x / 2 + DELTA_X - START_X,
suby + sizes.get(i).y - subtxtSize.y + GAP_Y / 4,
subtxtSize.x + 2 * START_X - 1,
subtxtSize.y - GAP_Y / 2,
START_X, START_X);
int yf = suby + sizes.get(i).y - subtxtSize.y + GAP_Y / 4 + (subtxtSize.y - GAP_Y / 2) / 2;
gc.drawLine(xcenter + txtSize.x / 2 + START_X, ycenter,
xcenter + txtSize.x / 2 + DELTA_X - START_X, yf);
suby += sizes.get(i).height;
}
suby = ycenter + leftHeight / 2;
for (int i = n + 1; i node, Graphics graphics, int x, int y)
{
Graphics2D gc = (Graphics2D) graphics;
Point txtSize = stringExtent(gc, node.toString());
txtSize.y += GAP_Y;
Rectangle span = calculateSpan(node, gc);
int dry = y + (span.height - txtSize.y) / 2;
FontMetrics fm = gc.getFontMetrics();
gc.drawString(node.toString(), x, dry + GAP_Y + fm.getAscent());
int subx = x + txtSize.x + DELTA_X;
int suby = y;
int maxdx = 0;
for (MindMapNode subnode : node.getChildren())
{
Rectangle subret = calculateSpan(subnode, gc);
drawNodeRight(subnode, gc, subx, suby);
int middlex = (subx + x + txtSize.x) / 2;
gc.drawLine(x + txtSize.x + START_X, dry + txtSize.y, middlex, dry + txtSize.y);
gc.drawLine(middlex, dry + txtSize.y, middlex, suby + subret.y);
gc.drawLine(middlex, suby + subret.y, subx + subret.x, suby + subret.y);
suby += subret.y;
if (maxdx < subret.x)
{
maxdx = subret.x;
}
}
if (suby == 0)
{
suby = txtSize.y;
}
return new Rectangle(x, y, maxdx + txtSize.x, suby);
}
private Rectangle drawNodeLeft(MindMapNode node, Graphics graphics, int x, int y)
{
Graphics2D gc = (Graphics2D) graphics;
Point txtSize = stringExtent(gc, node.toString());
txtSize.y += GAP_Y;
Rectangle span = calculateSpan(node, gc);
int dry = y + (span.height - txtSize.y) / 2;
FontMetrics fm = gc.getFontMetrics();
gc.drawString(node.toString(), x + span.width - txtSize.x, dry + GAP_Y + fm.getAscent());
int subx = x + span.width - span.x - DELTA_X;
int suby = y;
int maxdx = 0;
for (MindMapNode subnode : node.getChildren())
{
Rectangle subret = calculateSpan(subnode, gc);
drawNodeLeft(subnode, gc, subx - subret.width, suby);
gc.drawLine(x + span.width - span.x - START_X, dry + txtSize.y,
x + span.width - span.x - DELTA_X / 2, dry + txtSize.y);
gc.drawLine(x + span.width - span.x - DELTA_X / 2, dry + txtSize.y,
x + span.width - span.x - DELTA_X / 2, suby + subret.y);
gc.drawLine(x + span.width - span.x - DELTA_X / 2, suby + subret.y,
x + span.width - span.x - DELTA_X - subret.x, suby + subret.y);
suby += subret.y;
if (maxdx < subret.x)
{
maxdx = subret.x;
}
}
if (suby == 0)
{
suby = txtSize.y;
}
return new Rectangle(x, y, maxdx + txtSize.x, suby);
}
private Point stringExtent(Graphics gc, String text)
{
Font font = gc.getFont();
AffineTransform affinetransform = new AffineTransform();
FontRenderContext frc = new FontRenderContext(affinetransform,true,true);
int textwidth = (int)(font.getStringBounds(text, frc).getWidth());
int textheight = (int)(font.getStringBounds(text, frc).getHeight());
return new Point(textwidth, textheight);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy