Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added demo #1

Merged
merged 1 commit into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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