Skip to content

Commit

Permalink
Merge pull request #1 from behrangsa/demo
Browse files Browse the repository at this point in the history
Added demo
  • Loading branch information
behrangsa committed Oct 20, 2023
2 parents 5bd4232 + e7bc782 commit 4a1ca49
Show file tree
Hide file tree
Showing 11 changed files with 303 additions and 98 deletions.
45 changes: 0 additions & 45 deletions .github/workflows/gradle-publish.yml

This file was deleted.

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

name: Java CI with Gradle
name: CI

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

Expand All @@ -20,13 +12,13 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
with:
arguments: build
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
with:
arguments: build
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ dependencies {
}

group = 'org.behrang.algorithm'
version = '1.0.0'
version = '1.0-SNAPSHOT'
description = 'John Q Walker Node Positioning Algorithm'

java {
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/org/behrang/algorithm/tree/DimensionCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.behrang.algorithm.tree;

import java.awt.*;
import java.awt.geom.Dimension2D;
import java.util.concurrent.atomic.AtomicReference;

public class DimensionCalculator {
public static <T> Dimension2D calculateTreeDimension(Node<T> root) {
final AtomicReference<Double> minX = new AtomicReference<>(Double.MAX_VALUE);
final AtomicReference<Double> minY = new AtomicReference<>(Double.MAX_VALUE);
final AtomicReference<Double> maxX = new AtomicReference<>(Double.MIN_VALUE);
final AtomicReference<Double> maxY = new AtomicReference<>(Double.MIN_VALUE);

TreeTraversal.preorder(root, n -> {
if (n.getX() < minX.get()) {
minX.set(n.getX());
}

if (n.getY() < minY.get()) {
minY.set(n.getY());
}

double x2 = n.getX() + n.getWidth();
if (x2 > maxX.get()) {
maxX.set(x2);
}

double y2 = n.getY() + n.getHeight();
if (y2 > maxY.get()) {
maxY.set(y2);
}
});

double width = (maxX.get() - minX.get());
double height = (maxY.get() - minY.get());

Dimension dim = new Dimension();
dim.setSize(width, height);

return dim;
}

public static <T> Dimension2D calculateMaxNodeDimension(Node<T> root) {
final AtomicReference<Double> width = new AtomicReference<>(Double.MIN_VALUE);
final AtomicReference<Double> height = new AtomicReference<>(Double.MIN_VALUE);

TreeTraversal.preorder(root, n -> {
if (width.get() < n.getWidth()) {
width.set(n.getWidth());
}

if (height.get() < n.getHeight()) {
height.set(n.getHeight());
}
});

Dimension dim = new Dimension();
dim.setSize(width.get(), height.get());

return dim;
}

}
15 changes: 15 additions & 0 deletions src/main/java/org/behrang/algorithm/tree/TreeTraversal.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.behrang.algorithm.tree;

import org.behrang.algorithm.tree.Node;

import java.util.function.Consumer;

public class TreeTraversal {
public static <T> void preorder(Node<T> root, Consumer<Node<T>> consumer) {
consumer.accept(root);

root.getChildren().forEach(ch -> {
preorder(ch, consumer);
});
}
}
33 changes: 1 addition & 32 deletions src/main/java/org/behrang/algorithm/tree/WalkerAlgorithm.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,7 @@
public class WalkerAlgorithm<T> {

private static final long MAX_DEPTH = Long.MAX_VALUE;

/**
* During the operation of the algorithm, we walk the tree two times.
* <p>
* Whenever we visit a new node {@code N} at a level {@code L}, we store it in this map.
* <p>
* Then whenever we need to look up a node's neighboring node to the left, we can consult this map.
* <p>
* For example, consider the following tree:
* <pre>
* A
* ┌───┼───┐
* B C D
* │ │
* E F
* </pre>
* <p>
* As you can see, node {@code A} is at level 0, nodes {@code B}, {@code C}, {@code D} are at level 1, and nodes
* {@code E} and {@code F} are at level 2.
* <p>
* When we arrive at node {@code F}, we can consult to look up its left neighbor {@code E} using this table:
* <pre>{@code
* // leftNeighbor will resolve to node "E"
* var leftNeighbor = previousNodeAtLevel.get(2);
* }</pre>
* <p>
* Similarly, when we arrive at node {@code C}:
* <pre>{@code
* // leftNeighbor will resolve to node "B"
* var leftNeighbor = previousNodeAtLevel.get(1);
* }</pre>
*/

private final Map<Integer, Node<T>> previousNodeAtLevel;

private final double siblingSeparation;
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/org/behrang/algorithm/tree/demo/DemoFrame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.behrang.algorithm.tree.demo;

import javax.swing.*;
import java.awt.*;

public class DemoFrame extends JFrame {

private JPanel mainPanel;

public DemoFrame() {
mainPanel = new TreePanel<>(SampleTree.newUniformInstance(64, 64));
add(mainPanel, BorderLayout.CENTER);
}

public static void main(String[] args) {
DemoFrame demoFrame = new DemoFrame();
demoFrame.setTitle("Walker Algorithm Demo");
demoFrame.setSize(1024, 768);
demoFrame.setResizable(false);
demoFrame.setLocationByPlatform(true);
demoFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
demoFrame.setVisible(true);
}
}
60 changes: 60 additions & 0 deletions src/main/java/org/behrang/algorithm/tree/demo/SampleTree.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.behrang.algorithm.tree.demo;

import org.behrang.algorithm.tree.Node;

public final class SampleTree {

private SampleTree() {
}

public static Node<String> newUniformInstance() {
return newUniformInstance(2, 2);
}

public static Node<String> newUniformInstance(double nodeWidth, double nodeHeight) {
Node<String> a, b, c, d, e, f, g, h, i, j, k, l, m, n, o;

o = node("O", nodeWidth, nodeHeight);
e = node("E", nodeWidth, nodeHeight);
f = node("F", nodeWidth, nodeHeight);
n = node("N", nodeWidth, nodeHeight);
link(o, e, f, n);

a = node("A", nodeWidth, nodeHeight);
d = node("D", nodeWidth, nodeHeight);
link(e, a, d);

b = node("B", nodeWidth, nodeHeight);
c = node("C", nodeWidth, nodeHeight);
link(d, b, c);

g = node("G", nodeWidth, nodeHeight);
m = node("M", nodeWidth, nodeHeight);
link(n, g, m);

h = node("H", nodeWidth, nodeHeight);
i = node("I", nodeWidth, nodeHeight);
j = node("J", nodeWidth, nodeHeight);
k = node("K", nodeWidth, nodeHeight);
l = node("L", nodeWidth, nodeHeight);
link(m, h, i, j, k, l);

return o;
}

static Node<String> node(String value, double nodeWidth, double nodeHeight) {
Node<String> node = new Node<>(value);
node.setWidth(nodeWidth);
node.setHeight(nodeHeight);

return node;
}

@SafeVarargs
static void link(Node<String> parent, Node<String>... children) {
for (var child : children) {
parent.getChildren().add(child);
child.setParent(parent);
}
}
}
91 changes: 91 additions & 0 deletions src/main/java/org/behrang/algorithm/tree/demo/TreePanel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.behrang.algorithm.tree.demo;

import org.behrang.algorithm.tree.Node;
import org.behrang.algorithm.tree.TreeTraversal;
import org.behrang.algorithm.tree.WalkerAlgorithm;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;

import static org.behrang.algorithm.tree.DimensionCalculator.calculateMaxNodeDimension;

public class TreePanel<T> extends JPanel {

private Node<T> root;

public TreePanel(Node<T> root) {
setLayout(null);
setRoot(root);
}

public void setRoot(Node<T> root) {
this.root = root;
repaint();
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (root == null) {
return;
}

var dim = calculateMaxNodeDimension(root);

var walker = new WalkerAlgorithm<T>(
dim.getWidth(),
dim.getWidth(),
20,
20,
dim.getHeight() * 2
);

walker.position(root);

var g2 = (Graphics2D) g;
TreeTraversal.preorder(root, n -> {
drawNode(g2, n);
});
}

void drawNode(Graphics2D g, Node<T> node) {
double labelMarginX = 12.0;
double labelMarginY = 1.0;

g.setColor(Color.BLACK);
Rectangle2D rect = new Rectangle2D.Double(
node.getX(),
node.getY(),
node.getWidth(),
node.getHeight()
);
g.draw(rect);

String label = node.getValue().toString();

g.setColor(Color.DARK_GRAY);
Font font = new Font(Font.SERIF, Font.ITALIC, 24);
g.setFont(font);

FontMetrics fontMetrics = g.getFontMetrics(font);
Rectangle2D labelBounds = fontMetrics.getStringBounds(label, g);

g.drawString(label,
(float) (node.getX() + labelMarginX),
(float) (node.getY() + labelMarginY + labelBounds.getHeight())
);

if (node.hasParent()) {
Node<T> parent = node.getParent();
double x0 = parent.getX() + parent.getWidth() / 2;
double y0 = parent.getY() + parent.getHeight();
double x1 = node.getX() + node.getWidth() / 2;
double y1 = node.getY();
Line2D line2D = new Line2D.Double(x0, y0, x1, y1);
g.draw(line2D);
}
}
}
Loading

0 comments on commit 4a1ca49

Please sign in to comment.