Start on the foundations of a tree-drawing algorithm
I don't actually know what I'm doing. I've done some reading and from that I'm doing experiments until I can better understand what I've read.
This commit is contained in:
parent
57aadd7597
commit
e587d269e9
|
@ -16,9 +16,9 @@ You should have received a copy of the GNU General Public License along with On
|
||||||
|
|
||||||
use cairo::Context;
|
use cairo::Context;
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{ prelude::*, subclass::prelude::*, };
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
use sgf::GameRecord;
|
use sgf::{GameNode, GameRecord};
|
||||||
use std::{rc::Rc, cell::RefCell};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
const WIDTH: i32 = 200;
|
const WIDTH: i32 = 200;
|
||||||
const HEIGHT: i32 = 800;
|
const HEIGHT: i32 = 800;
|
||||||
|
@ -61,5 +61,177 @@ impl ReviewTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redraw(&self, ctx: &Context, width: i32, height: i32) {
|
pub fn redraw(&self, ctx: &Context, width: i32, height: i32) {
|
||||||
|
// Implement the tree-drawing algorithm here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://llimllib.github.io/pymag-trees/
|
||||||
|
// I want to take advantage of the Wetherell Shannon algorithm, but I want some variations. In
|
||||||
|
// their diagram, they got a tree that looks like this.
|
||||||
|
//
|
||||||
|
// O
|
||||||
|
// |\
|
||||||
|
// O O
|
||||||
|
// |\ \ \
|
||||||
|
// O O O O
|
||||||
|
// |\ |\
|
||||||
|
// O O O O
|
||||||
|
//
|
||||||
|
// In the same circumstance, what I want is this:
|
||||||
|
//
|
||||||
|
// O--
|
||||||
|
// | \
|
||||||
|
// O O
|
||||||
|
// |\ |\
|
||||||
|
// O O O O
|
||||||
|
// |\
|
||||||
|
// O O
|
||||||
|
//
|
||||||
|
// In order to keep things from being overly smooshed, I want to ensure that if a branch overlaps
|
||||||
|
// with another branch, there is some extra drawing space. This might actually be similar to adding
|
||||||
|
// the principal that "A parent should be centered over its children".
|
||||||
|
//
|
||||||
|
// So, given a tree, I need to know how many children exist at each level. Then I build parents
|
||||||
|
// atop the children. At level 3, I have four children, and that happens to be the maximum width of
|
||||||
|
// the graph.
|
||||||
|
//
|
||||||
|
// A bottom-up traversal:
|
||||||
|
// - Figure out the number of nodes at each depth
|
||||||
|
|
||||||
|
/*
|
||||||
|
struct Tree {
|
||||||
|
width: Vec<usize>, // the total width of the tree at each depth
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Given a node, do a postorder traversal to figure out the width of the node based on all of its
|
||||||
|
// children. This is equivalent to the widest of all of its children at all depths.
|
||||||
|
//
|
||||||
|
// There are some collapse rules that I could take into account here, but that I haven't figured
|
||||||
|
// out yet. If two nodes are side by side, and one of them has some wide children but the other has
|
||||||
|
// no children, then they are effectively the same width. The second node only needs to be moved
|
||||||
|
// out if it has children that would overlap the children of the first node.
|
||||||
|
//
|
||||||
|
// My algorithm right now is likely to generate unnecessarily wide trees in a complex game review.
|
||||||
|
fn node_width(node: &GameNode) -> usize {
|
||||||
|
let children: &Vec<GameNode> = match node {
|
||||||
|
GameNode::MoveNode(mn) => &mn.children,
|
||||||
|
GameNode::SetupNode(sn) => &sn.children,
|
||||||
|
};
|
||||||
|
|
||||||
|
if children.len() == 0 {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is more than one child, run node_width on each one and add them together.
|
||||||
|
children.iter().fold(0, |acc, child| acc + node_width(child))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since I know the width of a node, now I want to figure out its placement in the larger scheme of
|
||||||
|
// things.
|
||||||
|
//
|
||||||
|
// One thought I have is that I could just develop a grid virtually and start placing nodes.
|
||||||
|
// Whenever I notice a collision, I can just move the node over. But I'd like to see if I can be a
|
||||||
|
// bit smarter than doing it as just a vec into which I place things, as though it's a game board.
|
||||||
|
// So, given a game node, I want to figure out it's position along the X axis.
|
||||||
|
//
|
||||||
|
// Just having the node is greatly insufficient. I can get better results if I'm calculating the
|
||||||
|
// position of its children.
|
||||||
|
fn node_children_columns(node: &GameNode) -> Vec<usize> {
|
||||||
|
vec![0, 1, 2]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use sgf::{Color, GameNode, Move, MoveNode};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_calculates_width_for_single_node() {
|
||||||
|
let node = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
||||||
|
|
||||||
|
assert_eq!(node_width(&node), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_calculates_width_for_node_with_children() {
|
||||||
|
let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
let node_b = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
||||||
|
let node_c = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
||||||
|
let node_d = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
||||||
|
|
||||||
|
node_a.children.push(node_b);
|
||||||
|
node_a.children.push(node_c);
|
||||||
|
node_a.children.push(node_d);
|
||||||
|
|
||||||
|
assert_eq!(node_width(&GameNode::MoveNode(node_a)), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A
|
||||||
|
// B E
|
||||||
|
// C D
|
||||||
|
#[test]
|
||||||
|
fn it_calculates_width_with_one_deep_child() {
|
||||||
|
let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
let mut node_b = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
let mut node_c = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
let mut node_d = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
let mut node_e = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
|
||||||
|
node_b.children.push(GameNode::MoveNode(node_c));
|
||||||
|
node_b.children.push(GameNode::MoveNode(node_d));
|
||||||
|
assert_eq!(node_width(&GameNode::MoveNode(node_b.clone())), 2);
|
||||||
|
|
||||||
|
node_a.children.push(GameNode::MoveNode(node_b));
|
||||||
|
node_a.children.push(GameNode::MoveNode(node_e));
|
||||||
|
assert_eq!(node_width(&GameNode::MoveNode(node_a)), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A
|
||||||
|
// B G H
|
||||||
|
// C I
|
||||||
|
// D E F
|
||||||
|
#[test]
|
||||||
|
fn it_calculates_a_complex_tree() {
|
||||||
|
let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
let mut node_b = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
let mut node_c = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
let mut node_d = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
let mut node_e = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
let mut node_f = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
let mut node_g = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
let mut node_h = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
let mut node_i = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
|
||||||
|
node_c.children.push(GameNode::MoveNode(node_d));
|
||||||
|
node_c.children.push(GameNode::MoveNode(node_e));
|
||||||
|
node_c.children.push(GameNode::MoveNode(node_f));
|
||||||
|
assert_eq!(node_width(&GameNode::MoveNode(node_c.clone())), 3);
|
||||||
|
|
||||||
|
node_b.children.push(GameNode::MoveNode(node_c));
|
||||||
|
assert_eq!(node_width(&GameNode::MoveNode(node_b.clone())), 3);
|
||||||
|
|
||||||
|
node_h.children.push(GameNode::MoveNode(node_i));
|
||||||
|
|
||||||
|
node_a.children.push(GameNode::MoveNode(node_b));
|
||||||
|
node_a.children.push(GameNode::MoveNode(node_g));
|
||||||
|
node_a.children.push(GameNode::MoveNode(node_h));
|
||||||
|
// This should be 4 if I were collapsing levels correctly, but it is 5 until I return to
|
||||||
|
// figure that step out.
|
||||||
|
assert_eq!(node_width(&GameNode::MoveNode(node_a.clone())), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_nodes_children_get_separate_columns() {
|
||||||
|
let mut node_a = MoveNode::new(Color::Black, Move::Move("dp".to_owned()));
|
||||||
|
let node_b = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
||||||
|
let node_c = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
||||||
|
let node_d = GameNode::MoveNode(MoveNode::new(Color::Black, Move::Move("dp".to_owned())));
|
||||||
|
|
||||||
|
node_a.children.push(node_b.clone());
|
||||||
|
node_a.children.push(node_c.clone());
|
||||||
|
node_a.children.push(node_d.clone());
|
||||||
|
|
||||||
|
assert_eq!(node_children_columns(&GameNode::MoveNode(node_a)), vec![0, 1, 2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -460,8 +460,8 @@ impl TryFrom<&parser::Node> for MoveNode {
|
||||||
pub struct SetupNode {
|
pub struct SetupNode {
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
|
|
||||||
positions: Vec<parser::SetupInstr>,
|
pub positions: Vec<parser::SetupInstr>,
|
||||||
children: Vec<GameNode>,
|
pub children: Vec<GameNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SetupNode {
|
impl SetupNode {
|
||||||
|
|
Loading…
Reference in New Issue