Skip to content

Commit 4a1ca49

Browse files
authored
Merge pull request #1 from behrangsa/demo
Added demo
2 parents 5bd4232 + e7bc782 commit 4a1ca49

11 files changed

Lines changed: 303 additions & 98 deletions

File tree

.github/workflows/gradle-publish.yml

Lines changed: 0 additions & 45 deletions
This file was deleted.

.github/workflows/gradle.yml

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
1-
# This workflow uses actions that are not certified by GitHub.
2-
# They are provided by a third-party and are governed by
3-
# separate terms of service, privacy policy, and support
4-
# documentation.
5-
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
6-
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
7-
8-
name: Java CI with Gradle
1+
name: CI
92

103
on:
114
push:
12-
branches: [ "master" ]
135
pull_request:
146
branches: [ "master" ]
157

@@ -20,13 +12,13 @@ jobs:
2012
build:
2113
runs-on: ubuntu-latest
2214
steps:
23-
- uses: actions/checkout@v3
24-
- name: Set up JDK 17
25-
uses: actions/setup-java@v3
26-
with:
27-
java-version: '17'
28-
distribution: 'temurin'
29-
- name: Build with Gradle
30-
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
31-
with:
32-
arguments: build
15+
- uses: actions/checkout@v3
16+
- name: Set up JDK 17
17+
uses: actions/setup-java@v3
18+
with:
19+
java-version: '17'
20+
distribution: 'temurin'
21+
- name: Build with Gradle
22+
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
23+
with:
24+
arguments: build

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ dependencies {
1919
}
2020

2121
group = 'org.behrang.algorithm'
22-
version = '1.0.0'
22+
version = '1.0-SNAPSHOT'
2323
description = 'John Q Walker Node Positioning Algorithm'
2424

2525
java {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.behrang.algorithm.tree;
2+
3+
import java.awt.*;
4+
import java.awt.geom.Dimension2D;
5+
import java.util.concurrent.atomic.AtomicReference;
6+
7+
public class DimensionCalculator {
8+
public static <T> Dimension2D calculateTreeDimension(Node<T> root) {
9+
final AtomicReference<Double> minX = new AtomicReference<>(Double.MAX_VALUE);
10+
final AtomicReference<Double> minY = new AtomicReference<>(Double.MAX_VALUE);
11+
final AtomicReference<Double> maxX = new AtomicReference<>(Double.MIN_VALUE);
12+
final AtomicReference<Double> maxY = new AtomicReference<>(Double.MIN_VALUE);
13+
14+
TreeTraversal.preorder(root, n -> {
15+
if (n.getX() < minX.get()) {
16+
minX.set(n.getX());
17+
}
18+
19+
if (n.getY() < minY.get()) {
20+
minY.set(n.getY());
21+
}
22+
23+
double x2 = n.getX() + n.getWidth();
24+
if (x2 > maxX.get()) {
25+
maxX.set(x2);
26+
}
27+
28+
double y2 = n.getY() + n.getHeight();
29+
if (y2 > maxY.get()) {
30+
maxY.set(y2);
31+
}
32+
});
33+
34+
double width = (maxX.get() - minX.get());
35+
double height = (maxY.get() - minY.get());
36+
37+
Dimension dim = new Dimension();
38+
dim.setSize(width, height);
39+
40+
return dim;
41+
}
42+
43+
public static <T> Dimension2D calculateMaxNodeDimension(Node<T> root) {
44+
final AtomicReference<Double> width = new AtomicReference<>(Double.MIN_VALUE);
45+
final AtomicReference<Double> height = new AtomicReference<>(Double.MIN_VALUE);
46+
47+
TreeTraversal.preorder(root, n -> {
48+
if (width.get() < n.getWidth()) {
49+
width.set(n.getWidth());
50+
}
51+
52+
if (height.get() < n.getHeight()) {
53+
height.set(n.getHeight());
54+
}
55+
});
56+
57+
Dimension dim = new Dimension();
58+
dim.setSize(width.get(), height.get());
59+
60+
return dim;
61+
}
62+
63+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.behrang.algorithm.tree;
2+
3+
import org.behrang.algorithm.tree.Node;
4+
5+
import java.util.function.Consumer;
6+
7+
public class TreeTraversal {
8+
public static <T> void preorder(Node<T> root, Consumer<Node<T>> consumer) {
9+
consumer.accept(root);
10+
11+
root.getChildren().forEach(ch -> {
12+
preorder(ch, consumer);
13+
});
14+
}
15+
}

src/main/java/org/behrang/algorithm/tree/WalkerAlgorithm.java

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,7 @@
66
public class WalkerAlgorithm<T> {
77

88
private static final long MAX_DEPTH = Long.MAX_VALUE;
9-
10-
/**
11-
* During the operation of the algorithm, we walk the tree two times.
12-
* <p>
13-
* Whenever we visit a new node {@code N} at a level {@code L}, we store it in this map.
14-
* <p>
15-
* Then whenever we need to look up a node's neighboring node to the left, we can consult this map.
16-
* <p>
17-
* For example, consider the following tree:
18-
* <pre>
19-
* A
20-
* ┌───┼───┐
21-
* B C D
22-
* │ │
23-
* E F
24-
* </pre>
25-
* <p>
26-
* As you can see, node {@code A} is at level 0, nodes {@code B}, {@code C}, {@code D} are at level 1, and nodes
27-
* {@code E} and {@code F} are at level 2.
28-
* <p>
29-
* When we arrive at node {@code F}, we can consult to look up its left neighbor {@code E} using this table:
30-
* <pre>{@code
31-
* // leftNeighbor will resolve to node "E"
32-
* var leftNeighbor = previousNodeAtLevel.get(2);
33-
* }</pre>
34-
* <p>
35-
* Similarly, when we arrive at node {@code C}:
36-
* <pre>{@code
37-
* // leftNeighbor will resolve to node "B"
38-
* var leftNeighbor = previousNodeAtLevel.get(1);
39-
* }</pre>
40-
*/
9+
4110
private final Map<Integer, Node<T>> previousNodeAtLevel;
4211

4312
private final double siblingSeparation;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.behrang.algorithm.tree.demo;
2+
3+
import javax.swing.*;
4+
import java.awt.*;
5+
6+
public class DemoFrame extends JFrame {
7+
8+
private JPanel mainPanel;
9+
10+
public DemoFrame() {
11+
mainPanel = new TreePanel<>(SampleTree.newUniformInstance(64, 64));
12+
add(mainPanel, BorderLayout.CENTER);
13+
}
14+
15+
public static void main(String[] args) {
16+
DemoFrame demoFrame = new DemoFrame();
17+
demoFrame.setTitle("Walker Algorithm Demo");
18+
demoFrame.setSize(1024, 768);
19+
demoFrame.setResizable(false);
20+
demoFrame.setLocationByPlatform(true);
21+
demoFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
22+
demoFrame.setVisible(true);
23+
}
24+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package org.behrang.algorithm.tree.demo;
2+
3+
import org.behrang.algorithm.tree.Node;
4+
5+
public final class SampleTree {
6+
7+
private SampleTree() {
8+
}
9+
10+
public static Node<String> newUniformInstance() {
11+
return newUniformInstance(2, 2);
12+
}
13+
14+
public static Node<String> newUniformInstance(double nodeWidth, double nodeHeight) {
15+
Node<String> a, b, c, d, e, f, g, h, i, j, k, l, m, n, o;
16+
17+
o = node("O", nodeWidth, nodeHeight);
18+
e = node("E", nodeWidth, nodeHeight);
19+
f = node("F", nodeWidth, nodeHeight);
20+
n = node("N", nodeWidth, nodeHeight);
21+
link(o, e, f, n);
22+
23+
a = node("A", nodeWidth, nodeHeight);
24+
d = node("D", nodeWidth, nodeHeight);
25+
link(e, a, d);
26+
27+
b = node("B", nodeWidth, nodeHeight);
28+
c = node("C", nodeWidth, nodeHeight);
29+
link(d, b, c);
30+
31+
g = node("G", nodeWidth, nodeHeight);
32+
m = node("M", nodeWidth, nodeHeight);
33+
link(n, g, m);
34+
35+
h = node("H", nodeWidth, nodeHeight);
36+
i = node("I", nodeWidth, nodeHeight);
37+
j = node("J", nodeWidth, nodeHeight);
38+
k = node("K", nodeWidth, nodeHeight);
39+
l = node("L", nodeWidth, nodeHeight);
40+
link(m, h, i, j, k, l);
41+
42+
return o;
43+
}
44+
45+
static Node<String> node(String value, double nodeWidth, double nodeHeight) {
46+
Node<String> node = new Node<>(value);
47+
node.setWidth(nodeWidth);
48+
node.setHeight(nodeHeight);
49+
50+
return node;
51+
}
52+
53+
@SafeVarargs
54+
static void link(Node<String> parent, Node<String>... children) {
55+
for (var child : children) {
56+
parent.getChildren().add(child);
57+
child.setParent(parent);
58+
}
59+
}
60+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.behrang.algorithm.tree.demo;
2+
3+
import org.behrang.algorithm.tree.Node;
4+
import org.behrang.algorithm.tree.TreeTraversal;
5+
import org.behrang.algorithm.tree.WalkerAlgorithm;
6+
7+
import javax.swing.*;
8+
import javax.swing.border.EmptyBorder;
9+
import java.awt.*;
10+
import java.awt.geom.Line2D;
11+
import java.awt.geom.Rectangle2D;
12+
13+
import static org.behrang.algorithm.tree.DimensionCalculator.calculateMaxNodeDimension;
14+
15+
public class TreePanel<T> extends JPanel {
16+
17+
private Node<T> root;
18+
19+
public TreePanel(Node<T> root) {
20+
setLayout(null);
21+
setRoot(root);
22+
}
23+
24+
public void setRoot(Node<T> root) {
25+
this.root = root;
26+
repaint();
27+
}
28+
29+
@Override
30+
protected void paintComponent(Graphics g) {
31+
super.paintComponent(g);
32+
if (root == null) {
33+
return;
34+
}
35+
36+
var dim = calculateMaxNodeDimension(root);
37+
38+
var walker = new WalkerAlgorithm<T>(
39+
dim.getWidth(),
40+
dim.getWidth(),
41+
20,
42+
20,
43+
dim.getHeight() * 2
44+
);
45+
46+
walker.position(root);
47+
48+
var g2 = (Graphics2D) g;
49+
TreeTraversal.preorder(root, n -> {
50+
drawNode(g2, n);
51+
});
52+
}
53+
54+
void drawNode(Graphics2D g, Node<T> node) {
55+
double labelMarginX = 12.0;
56+
double labelMarginY = 1.0;
57+
58+
g.setColor(Color.BLACK);
59+
Rectangle2D rect = new Rectangle2D.Double(
60+
node.getX(),
61+
node.getY(),
62+
node.getWidth(),
63+
node.getHeight()
64+
);
65+
g.draw(rect);
66+
67+
String label = node.getValue().toString();
68+
69+
g.setColor(Color.DARK_GRAY);
70+
Font font = new Font(Font.SERIF, Font.ITALIC, 24);
71+
g.setFont(font);
72+
73+
FontMetrics fontMetrics = g.getFontMetrics(font);
74+
Rectangle2D labelBounds = fontMetrics.getStringBounds(label, g);
75+
76+
g.drawString(label,
77+
(float) (node.getX() + labelMarginX),
78+
(float) (node.getY() + labelMarginY + labelBounds.getHeight())
79+
);
80+
81+
if (node.hasParent()) {
82+
Node<T> parent = node.getParent();
83+
double x0 = parent.getX() + parent.getWidth() / 2;
84+
double y0 = parent.getY() + parent.getHeight();
85+
double x1 = node.getX() + node.getWidth() / 2;
86+
double y1 = node.getY();
87+
Line2D line2D = new Line2D.Double(x0, y0, x1, y1);
88+
g.draw(line2D);
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)