diff --git a/tree_design.py b/tree_design.py new file mode 100644 index 0000000..f338b2b --- /dev/null +++ b/tree_design.py @@ -0,0 +1,116 @@ +import json +import sys +import random +from tree_maker import Tree +from fpdf import FPDF +# ref https://pyfpdf.readthedocs.io/en/latest/ + +# declare input data +inputfile = ( + "/home/mara/webdev/sortingtree/cleaned_data/year_name_fam_subset.json" +) +in_data = open(inputfile, "r") +data = in_data.read() +in_data.close() +data_json = json.loads(data) + +reddit_file = open( + "/home/mara/webdev/sortingtree/cleaned_data/fam_reddit_subset.json" +) +reddit_data = reddit_file.read() +reddit_file.close() +reddit_json = json.loads(reddit_data) + + +# declare pdf format and margins +margin = 15 + +zine = FPDF(orientation="L", unit="mm", format="A4") + +# set font for all text +# zine.add_font( +# 'CasaleNBP', '', r"./fonts/CasaletwoNbp-Bp4V.ttf", uni=True) +# tree_font = 'CasaleNBP' + +zine.add_font( + 'Kp', '', r"./fonts/KpProgrammerAlternatesNbp-Zg1q.ttf", uni=True) +tree_font = 'Kp' +font_size = 15 +zine.set_font(tree_font, '', font_size) + +zine.set_margins(margin, margin, margin) +zine.add_page() +zine.set_xy(zine.w/2+margin, margin) + +tree = Tree(zine) +root = tree.keys_sorting(data_json) + + +def make_bin_tree(output, zine, tree, root): + global data_json + global margin + + tree.draw_year_name(data_json, root, zine.h, zine.w, margin) + zine.output(output) + print("PDF saved as {}".format(output)) + + +def make_bin_tree_reddit(output, zine, tree, root, reddit_json): + global data_json + global margin + + tree.draw_year_name_reddit( + data_json, reddit_json, root, zine.h, zine.w, margin) + zine.output(output) + print("PDF saved as {}".format(output)) + + +def make_bin_tree_position_nodes(output, zine, tree, root): + global data_json + global margin + + tree.preorder_position_nodes(data_json, root, zine.h, zine.w, margin) + print("PDF saved as {}".format(output)) + + +def make_bin_tree_weird_position_nodes(output, zine, tree, root): + global data_json + global margin + + tree.weird_position_nodes(data_json, root, zine.h, zine.w, margin) + print("PDF saved as {}".format(output)) + + +if __name__ == '__main__': + # input text and output pdf + if len(sys.argv) > 1: + if len(sys.argv) == 4: + output = sys.argv[3] + inputfile = sys.argv[2] + version = sys.argv[1] + else: + version = sys.argv[1] + output = "./trees/test_%d.pdf" % int(version) + + else: + version = random.randint(1, 100) + inputfile = "./trees/array.txt" + output = "./trees/test_%d.pdf" % int(version) + +# make_bin_tree(output, zine, tree, root) +# make_bin_tree_reddit(output, zine, tree, root, reddit_json) +# make_bin_tree_position_nodes(output, zine, tree, root) + make_bin_tree_weird_position_nodes(output, zine, tree, root) + + # TODO: add functions of the following tree methods +# tree.draw_year(json_data, root, x, y, zine.h, zine.w, margin, steps=30) +# tree.print_nodes(root) +# tree.draw_year_name_reddit( +# treejson_data, root, x, y, max_height, margin, width) +# tree.print_bin_tree(root, x, y, max_height, margin, width, steps=max_height/3) +# tree.print_tree_terminal(root) +# tree.design_tree(json_data, font_size, margin, steps=10) +# root = zine.tree_sorting([4, 8, 7, 9, 10, 1, 20, 5, 15, 11]) +# root = zine.tree_sorting(["BIN", "ARY", "IS", "FOR", "SORTING", "TREES"]) +# sentence = "BIN ARY IS FOR SORTING TREES" +# root = tree.tree_sorting(sentence.split()) diff --git a/tree_maker.py b/tree_maker.py new file mode 100644 index 0000000..8ecfc23 --- /dev/null +++ b/tree_maker.py @@ -0,0 +1,585 @@ +#!/bin/python +""" +A module for generating tree sorting layout +based on pyFPDF. +GPL3 License, Mara Karayanni December 2022 +""" +import glob +import json +import random +import re +import subprocess + + +class Tree: + def __init__(self, zine): + self.zine = zine + + def design_tree(self, title, size, margin, steps): + """ Simple layout of branching with black color""" + initial_steps = steps + for letter in title: + + if self.zine.y + size > self.zine.page_break_trigger: # self.zine.h: + print(self.zine.h) + print(self.zine.get_y()) + self.zine.add_page() + center_x = self.zine.w/2 + margin + self.zine.set_xy(center_x, margin) + steps = initial_steps + + point_a = self.zine.get_x() + point_b = self.zine.get_y() + print(point_a, point_b) + self.zine.cell(size, size, letter) # letter here is sorted by the tree + node_right = self.zine.get_x() + steps / 2 + node_left = point_a - steps / 2 + point_bb = point_b + steps + self.zine.line(point_a, point_b, node_left, point_bb) + self.zine.line(point_a, point_b, node_right, point_bb) + self.zine.set_xy(node_left, point_bb) + steps *= 2 + + def keys_sorting(self, data): + """ Make a list of sorted keys using Python's sorted function""" + global res + if type(data) is dict: + for key in sorted(data.keys()): + res.append(key) + else: + for key in sorted(data): + res.append() + return res + + def tree_sorting(self, data): + """ Make a list of keys, which are first sorted + by the insert_node function + """ + global node + if type(data) is dict: + for key in data.keys(): + node = self.insert_node(node, key) + else: + for key in data: + node = self.insert_node(node, key) + return node + + def insert_node(self, node, key): + """ Sorting key elements by recursive call of the function + and the use of the class Node, which has left and right keys + """ + + if node is None: + node = Node(key) + return node + if (key < node.key): + + # recurse + node.left = self.insert_node(node.left, key) + + elif (key > node.key): + # recurse + node.right = self.insert_node(node.right, key) + + return node + + def print_nodes(self, node): + """ Print left and right keys of an instance of the class Node, + mostly for debugging purposes + """ + global res + if (node is not None): + self.print_nodes(node.left) + print("LEFT SORTED KEY IS", node.key, end="\n") + res.append(node.key) + self.print_nodes(node.right) + print("RIGHT SORTED KEY IS", node.key, end="\n") + return res + + def draw_year(self, data, root, x, y, + page_height, width, margin, steps=30): + """ Tree method draw_year layout: + Simple year stamps in vertical position and progressively + horizontally to the left side + """ + letter_size = 8 + right_margin = margin + parent_x = x + parent_y = y + + if root is not None: + sorted_root = self.keys_sorting(data) + print("SORTED ROOT", sorted_root) + for key in sorted_root: + + if parent_y >= page_height-margin*2: + self.zine.add_page() + self.zine.set_xy(width/2, margin) + print("NEW PAGE", "Y", self.zine.get_y()) + if parent_x >= width or parent_x <= margin: + self.zine.set_xy(width/2, parent_y) + + parent_x = self.zine.get_x() + parent_y = self.zine.get_y() + + # draw the year + self.zine.cell(letter_size, letter_size, str(key)) # draw the root + print("YEAR", key, "YEAR X Y", parent_x, parent_y) + + parent_x -= steps/5 + parent_y += steps + self.zine.set_xy(parent_x, parent_y) + + def draw_year_name(self, data, root, page_height, margin, steps=30): + """ Tree method draw_year_name layout: + Simple year and name/letters stamps in vertical position + and progressively horizontally to the left side + """ + letter_size = 8 + width = self.zine.w + parent_x = self.zine.get_x() + parent_y = self.zine.get_y() + + if root is not None: + sorted_root = self.keys_sorting(data) + print("SORTED ROOT", sorted_root) + for key in sorted_root: + + if parent_y >= page_height-margin*2: + self.zine.add_page() + self.zine.set_xy(width/2, margin) + print("NEW PAGE", "Y", self.zine.get_y()) + if parent_x >= width or parent_x <= margin: + self.zine.set_xy(width/2, parent_y) + + parent_x = self.zine.get_x() + parent_y = self.zine.get_y() + + # draw the year + self.zine.cell(letter_size, letter_size, str(key)) # draw the root + print("YEAR", key, "YEAR X Y", parent_x, parent_y) + + # draw the tree names + val = data[key]["scientificName"] + child_x = parent_x - steps + child_y = parent_y + letter_size + if child_x <= margin: + print("CHILD OFFSET X", "PARENT X Y", parent_x, parent_y, "STEPS", steps) + if child_y >= page_height: + print("CHILD OFFSET Y", "PARENT X Y", parent_x, parent_y, "STEPS", steps) + if type(val) is list: + for v in val: + self.zine.set_draw_color(r=255, g=128, b=0) + self.zine.line(parent_x, parent_y, child_x, child_y) + self.zine.set_xy(child_x, child_y) + self.zine.cell(letter_size, letter_size, str(v)) # draw child + child_y +=5 + child_x += self.zine.get_x() + 5 + else: + self.zine.line(parent_x, parent_y, child_x, child_y) + self.zine.set_xy(child_x, child_y) + self.zine.cell(letter_size, letter_size, str(val)) # draw child + child_x += self.zine.get_x() + 5 + + previous_x = parent_x + previous_y = parent_y + parent_x -= steps/5 + parent_y += steps + self.zine.set_xy(parent_x, parent_y) + self.zine.set_draw_color(r=128, g=255, b=0) + self.zine.line( + previous_x, previous_y, + self.zine.get_x(), self.zine.get_y() + ) + + def draw_year_name_reddit(self, tree_json, reddit_json, root, page_height, + width, margin, steps=30): + """ Tree method draw_year_name_reddit layout: + Branching sorted year followed by trees named that year on the + left side and reddit blog titles related to those trees + on the right side""" + + letter_size = 8 + right_margin = margin + parent_x = self.zine.get_x() + parent_y = self.zine.get_y() + + if root is not None: + sorted_root = self.keys_sorting(tree_json) + print("SORTED ROOT", sorted_root) + for key in sorted_root: + + if parent_y >= page_height-margin*2: + self.zine.add_page() + self.zine.set_xy(width/2, margin) + print("NEW PAGE", "Y", self.zine.get_y()) + if parent_x >= width or parent_x <= margin: + self.zine.set_xy(width/2, parent_y) + + parent_x = self.zine.get_x() + parent_y = self.zine.get_y() + + # draw the year + self.zine.cell(letter_size, letter_size, str(key)) # draw the root + print("YEAR", key, "YEAR X Y", parent_x, parent_y) + + # draw the tree names + val = tree_json[key]["scientificName"] + family = tree_json[key]["family"] + child_x = parent_x - steps + child_y = parent_y + letter_size + if type(val) is list: + for v in val: + self.zine.set_draw_color(r=255, g=0, b=0) + self.zine.line(parent_x, parent_y, child_x, child_y) + self.zine.set_xy(child_x, child_y) + self.zine.cell(letter_size, letter_size, str(v)) # draw child + + # draw reddit + self.zine.set_draw_color(r=0, g=0, b=255) + for r in reddit_json.keys(): + if r in family: + reddit_list = reddit_json[r] + print("REDIT", reddit_list) + reddit_x = width -2*margin - child_x + reddit_y = child_y + for reddit in reddit_list: + reddit_y += steps/5 + self.zine.set_xy(reddit_x, reddit_y) + self.zine.cell(letter_size, letter_size, str(reddit)) # draw child + self.zine.line(child_x, child_y, reddit_x, reddit_y) + self.zine.set_xy(child_x, child_y) + + child_y +=5 + child_x += self.zine.get_x() + 5 + else: + self.zine.line(parent_x, parent_y, child_x, child_y) + self.zine.set_xy(child_x, child_y) + self.zine.cell(letter_size, letter_size, str(val)) # draw child + child_x += self.zine.get_x() + 5 + + previous_x = parent_x + previous_y = parent_y + parent_x -= steps/5 + parent_y += steps + self.zine.set_xy(parent_x, parent_y) + self.zine.set_draw_color(r=128, g=255, b=0) + self.zine.line(previous_x, previous_y, self.zine.get_x(), self.zine.get_y()) + + def preorder_position_nodes(self, data, root, page_height, + width, margin, steps=30): + letter_size = 5 + right_margin = margin + parent_x = self.zine.get_x() + parent_y = self.zine.get_y() + + if root is not None: + sorted_root = self.keys_sorting(data) + print("SORTED ROOT", sorted_root) + for key in sorted_root: + + if parent_y >= page_height: + self.zine.add_page() + self.zine.set_xy(width/2, margin) + print("NEW PAGE", "Y", self.zine.get_y()) + if parent_x >= width or parent_x <= margin: + self.zine.set_xy(width/2, parent_y) + + parent_x = self.zine.get_x() + parent_y = self.zine.get_y() + + # draw the year + self.zine.cell(letter_size, letter_size, str(key)) # draw the root + print("YEAR", key, "YEAR X Y", parent_x, parent_y) + + # draw the tree names + val = data[key]["scientificName"] + child_x = parent_x - steps + child_y = parent_y + letter_size + if child_x >= right_margin: + print("CHILD OFFSET", "PARENT X Y", parent_x, parent_y, "STEPS", steps) + print("VALUE", val, "X", child_x, "Y", child_y) + if type(val) is list: + for v in val: + self.zine.set_draw_color(r=255, g=128, b=0) + self.zine.line(parent_x, parent_y, child_x, child_y) + self.zine.set_xy(child_x, child_y) + self.zine.cell(letter_size, letter_size, str(v)) # draw child + child_y +=5 + child_x += self.zine.get_x() + 5 + else: + self.zine.line(parent_x, parent_y, child_x, child_y) + self.zine.set_xy(child_x, child_y) + self.zine.cell(letter_size, letter_size, str(val)) # draw child + child_x += self.zine.get_x() + 5 + +# parent_x -= steps/5 +# parent_y += steps*2 +# self.zine.set_draw_color(r=128, g=255, b=0) +# self.zine.line(self.zine.get_x(), self.zine.get_y(), parent_x, parent_y) + + + def weird_position_nodes(self, data, root, page_height, width, margin, steps=30): + letter_size = 5 + right_margin = margin + + x = self.zine.get_x() + y = self.zine.get_y() + + + if root is None: + root = self.keys_sorting(data) + print("SORTED ROOT", root) + for key in root: + parent_x = x + parent_y = y + + # draw the year + self.zine.cell(letter_size, letter_size, str(key)) # draw the root + print("YEAR", key, "YEAR X Y", parent_x, parent_y) + + # draw the tree names + val = data[key]["scientificName"] + child_x = parent_x - steps + child_y = parent_y + letter_size + if child_x >= right_margin: + print("CHILD OFFSET", "PARENT X Y", parent_x, parent_y, "STEPS", steps) + print("VALUE", val, "X", child_x, "Y", child_y) + if type(val) is list: + for v in val: + self.zine.set_draw_color(r=255, g=128, b=0) + self.zine.line(parent_x, parent_y, child_x, child_y) + self.zine.set_xy(child_x, child_y) + self.zine.cell(letter_size, letter_size, str(v)) # draw child + child_y +=5 + child_x += self.zine.get_x() + 5 + else: + self.zine.line(parent_x, parent_y, child_x, child_y) + self.zine.set_xy(child_x, child_y) + self.zine.cell(letter_size, letter_size, str(val)) # draw child + child_x += self.zine.get_x() + 5 + + x -= steps/5 + y += steps + self.zine.set_draw_color(r=128, g=255, b=0) + self.zine.line(parent_x, parent_y, x, y) +# self.zine.set_xy(x, y) + + if y >= page_height: + print("NEW PAGE") + self.zine.add_page() + self.zine.set_xy(x, margin) + print("Y", self.zine.get_y()) + if x >= width or x <= margin: + self.zine.set_xy(width/2, y) +# else: +# self.zine.set_xy(x, y) + +# self.wrap_page(x, y, page_height, width, margin) + + def position_nodes(self, root, x, y, page_height, margin, steps=26): + letter_size = 10 + if self.zine.get_y() >= page_height: + self.zine.add_page() + self.zine.set_xy(margin, margin) + x = self.zine.get_x() + y = self.zine.get_y() + else: + self.zine.set_xy(x, y) + root_x = x + root_y = y + + if root is not None: +# if root.left: # left display node name + + # draw left branch + self.zine.set_draw_color(r=255, g=128, b=0) + node_left_x = root_x - steps / 2 + node_left_y = root_y + steps + self.zine.cell(letter_size, letter_size, str(root.key)) # letter is sorted + self.zine.line(root_x, root_y, node_left_x, node_left_y) + print("LEFT NODE", root.key, node_left_x, node_left_y) + + # is there more nodes at left side? + steps *= 2 + self.zine.set_xy(node_left_x, node_left_y) + self.position_nodes(root.left, node_left_x, node_left_y, page_height, margin) + +# if root.right: # right display node name + + # draw right branch + self.zine.set_draw_color(r=128, g=255, b=0) + node_right_x = root_x + steps / 2 + node_right_y = root_y + steps + self.zine.cell(letter_size, letter_size, str(root.key)) # letter is sorted + self.zine.line(root_x, root_y, node_right_x, node_right_y) + print("RIGHT NODE", root.key, node_right_x, node_right_y) + + # is there more nodes at right side? + steps *= 2 + self.zine.set_xy(node_right_x, node_right_y) + self.position_nodes(root.right, node_right_x, node_right_y, page_height, margin) + + def print_2d_tree(self, root, space=0, LEVEL_SPACE = 5): + if (root == None): return + space += LEVEL_SPACE + self.print_2d_tree(root.right, space) + # print() # neighbor space + for i in range(LEVEL_SPACE, space): print(end = " ") + print("|" + str(root.key) + "|<") + self.print_2d_tree(root.left, space) + + def draw_tree(self, node, level=0, res=[]): + if level < len(res): + if node: + res[level].append(node.key) + else: + res[level].append(" ") + else: + if node: + res.append([node.key]) + else: + res.append([" "]) + if not node: + return + self.draw_tree(node.left, level+1, res) + self.draw_tree(node.right, level+1, res) + print("RES from draw tree is", res) + return res + + def print_tree(self, node, x, y, width, margin, page_height, steps=5): + letter_size = 20 + + tree_array = self.draw_tree(node) + h = len(tree_array) + white_spaces = (2**h)-1 + + def print_spaces(n, x, i): + for i in range(n): + if i % 2 == 0: + x += float(steps/2) + else: + x -= float(steps/2) + self.zine.set_xy(x, y) + + prev_x = None + prev_y = None + for level in tree_array: + print("LEVEL is", level, "AND OF TYPE", type(level)) + for i, key in enumerate(level): + if i==0 and len(level) == 1: + self.zine.cell(letter_size, letter_size, str(key)) # letter is sorted + print("FIRST NODE", key, "POSITION OF KEY", x, y, "i is", i) + prev_x = x + prev_y = y + self.zine.set_draw_color(r=0, g=255, b=0) + print_spaces(1+2*white_spaces, i, x) +# self.zine.line(prev_x, prev_y, self.zine.get_x(), self.zine.get_y()) + print("NEW xy after first node", self.zine.get_x(), self.zine.get_y()) + else: + if key == " ": + pass + else: + print("POSITION OF KEY", key, self.zine.get_x(), self.zine.get_y(), "i is", i) + self.zine.line(prev_x, prev_y, self.zine.get_x(), self.zine.get_y()) + self.zine.cell(letter_size, letter_size, str(key)) # letter is sorted + print_spaces(1+2*white_spaces, i, x) + y += steps/2 + if i % 2 == 0: + x += float(steps) + else: + x -= float(steps) + self.zine.set_xy(x, y) + print() + + def print_bin_tree(self, node, x, y, width, margin, page_height, steps=5): + letter_size = 20 + + tree_array = self.draw_tree(node) + h = len(tree_array) + white_spaces = (2**h)-1 + + def print_spaces(n, x, i): + for i in range(n): + if i % 2 == 0: + x += float(steps/2) + else: + x -= float(steps/2) + self.zine.set_xy(x, y) + + prev_x = None + prev_y = None + count = 0 + pos_level = {} + for level in tree_array: + # TODO: count the levels and revisit them, keep a dict of level and + # x,y + count += 1 + pos_level[count] = (x, y) + print("LEVEL is", level, "AND OF TYPE", type(level)) + for i, key in enumerate(level): + if i==0 and len(level) == 1: + prev_x = x + prev_y = y + self.zine.cell(letter_size, letter_size, str(key)) # letter is sorted + print("FIRST NODE", key, "POSITION OF KEY", x, y, "i is", i) + print_spaces(1+2*white_spaces, i, x) + print("NEW xy after first node", self.zine.get_x(), self.zine.get_y()) + else: + if key == " ": + pass + else: + print("POSITION OF KEY", key, self.zine.get_x(), self.zine.get_y(), "i is", i) + self.zine.cell(letter_size, letter_size, str(key)) # letter is sorted + print_spaces(1+2*white_spaces, i, x) + y += steps/2 + if i % 2 == 0: + x += float(steps) + else: + x -= float(steps) + self.zine.set_xy(x, y) + print("LEVELS ARE", pos_level) + for count, coord in pos_level.items(): + self.zine.set_draw_color(r=0, g=255, b=0) + try: + self.zine.line(coord[0], coord[1], pos_level[count+1][0], pos_level[count+1][1]) + except Exception as e: + print(e) + + def print_tree_terminal(self, node): + + tree_array = self.draw_tree(node) + h = len(tree_array) + white_spaces = (2**h)-1 + + def print_spaces(n): + for i in range(n): + print(" ",end="") + + for level in tree_array: + white_spaces = white_spaces//2 + for i, key in enumerate(level): + if i==0: + print_spaces(white_spaces) + print(key, end="") + print_spaces(1+2*white_spaces) + print() + + def wrap_page(self, x, y, page_height, page_width, margin): + right_margin = page_width/2 + if y >= page_height: + self.zine.add_page() + self.zine.set_xy(right_margin, margin) + elif x >= page_width: + self.zine.set_xy(right_margin, y) + else: + self.zine.set_xy(x, y) + +class Node: + + def __init__(self, item = 0): + self.key = item + self.left, self.right = None, None + +root = None +res = [] diff --git a/trees/test_draw_year_name_reddit_WIP.pdf b/trees/test_draw_year_name_reddit_WIP.pdf new file mode 100644 index 0000000..58d2e74 Binary files /dev/null and b/trees/test_draw_year_name_reddit_WIP.pdf differ diff --git a/trees/test_year_name_pair_layout.pdf b/trees/test_year_name_pair_layout.pdf new file mode 100644 index 0000000..b1124ab Binary files /dev/null and b/trees/test_year_name_pair_layout.pdf differ diff --git a/trees/test_years_simple_layout.pdf b/trees/test_years_simple_layout.pdf new file mode 100644 index 0000000..098a4b3 Binary files /dev/null and b/trees/test_years_simple_layout.pdf differ diff --git a/trees/test_years_tree_layout.pdf b/trees/test_years_tree_layout.pdf new file mode 100644 index 0000000..79fe491 Binary files /dev/null and b/trees/test_years_tree_layout.pdf differ