Well, it took a little longer than I wanted (10 month age has no patience)

The basic concept revolves around the idea that you need to move from one state to another over a period of time.
Given the start time and current time, we can calculate the time during which the animation was started, and given the total animation time, the current progress.
Using this (and some clever math) we can calculate the current state from our initial state in the direction of our target state.
I also made a move, so there might be a little more kill, but the basic premise remains the same.
I post information about the nodes that need to be changed in the animation properties class, and use javax.swing.Timer to tick the animation (at a fairly steady speed). Then I update the state of each node as needed and repaint the screen.
import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Ellipse2D; import java.awt.geom.Line2D; import java.text.NumberFormat; import java.util.HashMap; import java.util.Map; import java.util.Random; import javax.management.StringValueExp; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; public class AnimateNode { public static void main(String[] args) { new AnimateNode(); } public AnimateNode() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { } JFrame frame = new JFrame("Testing"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new BorderLayout()); frame.add(new NodePane()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public interface Node { public void paint(JComponent parent, Graphics2D g2d); public void setColor(Color color); public Color getColor(); public Node getParent(); public Node getLeft(); public Node getRight(); public void setLeftNode(Node node); public void setRightNode(Node node); public Point getLocation(); public void setLocation(Point p); } public class DefaultNode implements Node { private int number; private Node parent; private Node left; private Node right; private Point location; private Color color; public DefaultNode(int number, Node parent) { this.parent = parent; color = UIManager.getColor("Panel.background"); this.number = number; } public void setLeftNode(Node left) { this.left = left; } public void setRightNode(Node right) { this.right = right; } public Node getParent() { return parent; } public Node getLeft() { return left; } public Node getRight() { return right; } @Override public Point getLocation() { return location; } @Override public void setLocation(Point location) { this.location = location; } @Override public void paint(JComponent parent, Graphics2D g2d) { FontMetrics fm = g2d.getFontMetrics(); int radius = fm.getHeight(); Point p = getLocation(); int x = px - (radius / 2); int y = py - (radius / 2); Ellipse2D node = new Ellipse2D.Float(x, y, radius, radius); g2d.setColor(getColor()); g2d.fill(node); g2d.setColor(Color.GRAY); g2d.draw(node); String text = String.valueOf(number); x = x + ((radius - fm.stringWidth(text)) / 2); y = y + (((radius - fm.getHeight()) / 2) + fm.getAscent()); g2d.drawString(text, x, y); } @Override public void setColor(Color color) { this.color = color; } @Override public Color getColor() { return color; } @Override public String toString() { return number + " @ " + getLocation(); } } public class AnimationProperties { private Point startPoint; private Point targetPoint; private Color startColor; private Color endColor; private Node node; public AnimationProperties(Node node) { this.node = node; } public Node getNode() { return node; } public void setTargetColor(Color endColor) { this.endColor = endColor; } public void setStartColor(Color startColor) { this.startColor = startColor; } public void setStartPoint(Point startPoint) { this.startPoint = startPoint; } public void setTargetPoint(Point targetPoint) { this.targetPoint = targetPoint; } public Color getTargetColor() { return endColor; } public Color getStartColor() { return startColor; } public Point getStartPoint() { return startPoint; } public Point getTargetPoint() { return targetPoint; } public Point getLocation(float progress) { return calculateProgress(getStartPoint(), getTargetPoint(), progress); } public Color getColor(float progress) { return blend(getStartColor(), getTargetColor(), 1f - progress); } public void update(float progress) { node.setLocation(getLocation(progress)); node.setColor(getColor(progress)); } } public class NodePane extends JPanel { private int number; private Node root; private Map<Node, AnimationProperties> aniProperties; private Timer animationTimer; private Timer startTimer; private long startTime; private int runTime = 1000; public NodePane() { aniProperties = new HashMap<>(25); root = addLeftNode(null); root.setColor(getBackground()); addMouseListener(new MouseAdapter() { private Random rand; @Override public void mouseClicked(MouseEvent e) { generateNextNode(root); revalidate();
Refresh with a simple example;)
Ok, this is a simple example. Basically, it just blinks node ...
import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Ellipse2D; import java.text.NumberFormat; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; public class BlinkNode { public static void main(String[] args) { new BlinkNode(); } public BlinkNode() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { } JFrame frame = new JFrame("Testing"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new BorderLayout()); frame.add(new TestPane()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public class TestPane extends JPanel {