├── .gitignore ├── Images ├── blue.JPG ├── border.JPG ├── example.JPG ├── new_line.JPG ├── no_color.JPG └── trim.JPG ├── LICENSE ├── PrettyPrintTreeJava.jar ├── README.md ├── pom.xml └── src └── main └── java └── ajs └── printutils ├── Color.java ├── Orientation.java └── PrettyPrintTree.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | target 3 | .idea -------------------------------------------------------------------------------- /Images/blue.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AharonSambol/PrettyPrintTreeJava/c357a94eb9f04a937ed5db70b2e459a3d5a76c18/Images/blue.JPG -------------------------------------------------------------------------------- /Images/border.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AharonSambol/PrettyPrintTreeJava/c357a94eb9f04a937ed5db70b2e459a3d5a76c18/Images/border.JPG -------------------------------------------------------------------------------- /Images/example.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AharonSambol/PrettyPrintTreeJava/c357a94eb9f04a937ed5db70b2e459a3d5a76c18/Images/example.JPG -------------------------------------------------------------------------------- /Images/new_line.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AharonSambol/PrettyPrintTreeJava/c357a94eb9f04a937ed5db70b2e459a3d5a76c18/Images/new_line.JPG -------------------------------------------------------------------------------- /Images/no_color.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AharonSambol/PrettyPrintTreeJava/c357a94eb9f04a937ed5db70b2e459a3d5a76c18/Images/no_color.JPG -------------------------------------------------------------------------------- /Images/trim.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AharonSambol/PrettyPrintTreeJava/c357a94eb9f04a937ed5db70b2e459a3d5a76c18/Images/trim.JPG -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 AharonSambol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PrettyPrintTreeJava.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AharonSambol/PrettyPrintTreeJava/c357a94eb9f04a937ed5db70b2e459a3d5a76c18/PrettyPrintTreeJava.jar -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PrettyPrintTree 2 | 3 | This package allows you to print the tree datastructure in a readable fashion (in Java). 4 | 5 | It supports trees with any kind of data (as long it can be turned into a string). 6 | 7 | And even supports multi lined nodes (as in strings with \n). 8 | 9 | 10 | # Install 11 | 12 | You can install the .jar directly or use jitpack: https://jitpack.io/#AharonSambol/PrettyPrintTreeJava 13 | 14 | # Documentation 15 | 16 | I tried to make this as flexible as possible, so in order to support multiple types of trees 17 | you need to explain to the program how to print your tree. The way to accomplish this is by passing 2 lambdas: 18 | 1) getChildren: Given a node of your tree type returns a List of all its children (from left to right). 19 | For example if this is your tree implementation: 20 | ```java 21 | class Tree{ 22 | private final T val; 23 | private final ArrayList> children; 24 | public Tree(T val){ 25 | this.val = val; 26 | children = new ArrayList<>(); 27 | } 28 | public Tree addChild(T child){ 29 | var c = new Tree(child); 30 | children.add(c); 31 | return c; 32 | } 33 | public T getValue(){ return this.val; } 34 | public ArrayList> getChildren() { return this.children; } 35 | } 36 | ``` 37 | Then getChildren would be as simple as: 38 | ```java 39 | (x) -> x.getChildren() 40 | ``` 41 | or even 42 | ```java 43 | Tree::getChildren 44 | ``` 45 | Or if your tree implementation is: 46 | ```java 47 | class Tree{ 48 | private final T val; 49 | private Tree rChild, lChild; 50 | public Tree(T val){ 51 | this.val = val; 52 | } 53 | public void setRightChild(T child){ 54 | rChild = new Tree(child); 55 | } 56 | public void setLeftChild(T child){ 57 | lChild = new Tree(child); 58 | } 59 | public T getValue(){ return this.val; } 60 | public Tree getRChild() { return this.rChild; } 61 | public Tree getLChild() { return this.lChild; } 62 | } 63 | ``` 64 | Then getChildren would be: 65 | ```java 66 | (x) -> new ArrayList<>(){{ 67 | add(x.getLChild()); 68 | add(x.getRChild()); 69 | }} 70 | ``` 71 | **Note: ```List.of(x.getLChild(), x.getRChild())``` is not advised because ```List.of``` has a 72 | @NotNull annotation which will throw an error if null is passed.** 73 | 74 | 75 | 2) getValue: Given a node of your tree type returns that node's value 76 | for example if your tree implementation has: 77 | ```java 78 | public String getValue(){ 79 | return this.val.toString(); 80 | } 81 | ``` 82 | then getValue would be: 83 | ```java 84 | (x) -> x.getValue() 85 | ``` 86 | or even 87 | ``` 88 | Tree::getValue 89 | ``` 90 | 91 | 92 | In order to print the tree you first need to make a PrettyPrintTree object which you pass your lambdas to, 93 | then you can set it's settings (explained in "Other Settings"), 94 | then you can call it whenever you want without needing to pass the lambdas each time. 95 | 96 | 97 | ## Examples 98 | 99 | ### Custom Tree Class 100 | 101 | ```java 102 | import java.util.*; 103 | class Tree{ 104 | private final T val; 105 | private final ArrayList> children; 106 | public Tree(T val){ 107 | this.val = val; 108 | children = new ArrayList<>(); 109 | } 110 | public Tree addChild(T child){ 111 | var c = new Tree(child); 112 | children.add(c); 113 | return c; 114 | } 115 | public T getValue(){ return this.val; } 116 | public ArrayList> getChildren() { return this.children; } 117 | } 118 | 119 | 120 | public class Example{ 121 | public static void main(String[] args){ 122 | var tree = new Tree("0"); 123 | var c1 = tree.addChild("1"); 124 | var c2 = tree.addChild("2"); 125 | var c1_1 = c1.addChild("3"); 126 | var c1_2 = c1.addChild("4"); 127 | c1_1.addChild("5"); 128 | c1_1.addChild("6"); 129 | c1_1.addChild("7"); 130 | c1_2.addChild("8"); 131 | c1_2.addChild("9"); 132 | c2.addChild("10"); 133 | var pt = new PrettyPrintTree>( 134 | Tree::getChildren, 135 | Tree::getValue 136 | ); 137 | pt.display(tree); 138 | } 139 | } 140 | ``` 141 | ![plot](Images/example.JPG) 142 | 143 | ### DefaultTreeModel 144 | 145 | Or if you already have a Swing TreeModel made from DefaultMutableTreeNodes, the example from above would look like thie: 146 | 147 | ```java 148 | import java.util.*; 149 | import javax.swing.tree.*; 150 | 151 | public class Example{ 152 | public static void main(String[] args){ 153 | DefaultMutableTreeNode tree=new DefaultMutableTreeNode("0"); 154 | DefaultMutableTreeNode c1=new DefaultMutableTreeNode("1"); 155 | DefaultMutableTreeNode c2=new DefaultMutableTreeNode("2"); 156 | DefaultMutableTreeNode c1_1=new DefaultMutableTreeNode("3"); 157 | DefaultMutableTreeNode c1_2=new DefaultMutableTreeNode("4"); 158 | tree.add(c1); 159 | tree.add(c2); 160 | c1.add(c1_1); 161 | c1.add(c1_2); 162 | c1_1.add(new DefaultMutableTreeNode("5")); 163 | c1_1.add(new DefaultMutableTreeNode("6")); 164 | c1_1.add(new DefaultMutableTreeNode("7")); 165 | c1_2.add(new DefaultMutableTreeNode("8")); 166 | c1_2.add(new DefaultMutableTreeNode("9")); 167 | c2.add(new DefaultMutableTreeNode("10")); 168 | 169 | var pt = new PrettyPrintTree( 170 | (x) -> new ArrayList(Collections.list(x.children())), 171 | (x) -> x.getUserObject().toString() 172 | ); 173 | pt.display(tree); 174 | System.out.println(); 175 | } 176 | } 177 | ``` 178 | 179 | # Other Settings 180 | 181 | In order to change the way the tree prints, you can change the settings. 182 | This is done after making the PrettyPrintTree object and before calling the display function. 183 | eg: 184 | ```java 185 | var pt = new PrettyPrintTree>(...); 186 | // settings: 187 | pt.setTrim(3); 188 | // print: 189 | pt.display(tree); 190 | ``` 191 | 192 | ## Trim 193 | Say you only want to print the first few characters of each node (in order to keep the tree small for readability), 194 | then you can set trim to a specific amount of characters. 195 | 196 | ```java 197 | pt.setTrim(3); 198 | ``` 199 | ![plot](Images/trim.JPG) 200 | 201 | 202 | ## Return Instead of Print 203 | Instead of printing the tree it can return the string instead if you prefer. 204 | 205 | Instead of calling 206 | ```java 207 | pt.display(tree); 208 | ``` 209 | You can call 210 | ```java 211 | String res = pt.toStr(tree); 212 | ``` 213 | 214 | 215 | ## Color 216 | You can change the bg color of each node, or even just not use color. 217 | 218 | ```java 219 | pt.setColor(Color.BLUE); 220 | ``` 221 | ![plot](Images/blue.JPG) 222 | ```java 223 | pt.setColor(Color.NONE); 224 | ``` 225 | ![plot](Images/no_color.JPG) 226 | 227 | 228 | ## Border 229 | You can also surround each node with a little border: 230 | ```java 231 | pt.setBorder(true); 232 | ``` 233 | ![plot](Images/border.JPG) 234 | 235 | 236 | ## Escape NewLines 237 | You can escape \n so that each node will be printed on one line. 238 | 239 | Note: \\n will be escaped into \\\\n so that you can tell the difference 240 | ```java 241 | pt.setEscapeNewline(true); 242 | ``` 243 | ![plot](Images/new_line.JPG) 244 | 245 | 246 | ## Max Depth 247 | You can specify a max depth so that it will only print nodes up to that depth. 248 | This can be done either at the start: 249 | ```java 250 | pt.setMaxDepth(2); 251 | ``` 252 | Or when calling the function: 253 | ```java 254 | pt.display(tree, 2); 255 | ``` 256 | This will override the max depth set at the start (if any) for this time only. 257 | To have no max depth, you can set it to -1. 258 | 259 | 260 | ## Dictionaries \ JSON 261 | 262 | Coming soon 263 | 264 | 265 | ## Labels 266 | 267 | Coming soon 268 | 269 | 270 | ## Horizontal 271 | 272 | Coming soon 273 | 274 | 275 | # Python 276 | 277 | I made a Python version too: 278 | https://github.com/AharonSambol/PrettyPrintTree 279 | 280 | # C# 281 | 282 | I made a C# version too: 283 | [https://github.com/AharonSambol/PrettyPrintTree](https://github.com/AharonSambol/PrettyPrintTreeCSharp) 284 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | ajs 8 | printutils 9 | 0.1.0 10 | jar 11 | 12 | 13 | UTF-8 14 | 15 | 16 | 17 | 18 | 19 | org.apache.maven.plugins 20 | maven-compiler-plugin 21 | 3.8.1 22 | 23 | 11 24 | 11 25 | 26 | 27 | 28 | 29 | org.apache.maven.plugins 30 | maven-source-plugin 31 | 3.0.1 32 | 33 | 34 | attach-sources 35 | 36 | jar 37 | 38 | 39 | 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-javadoc-plugin 45 | 3.2.0 46 | 47 | 48 | attach-javadocs 49 | 50 | jar 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/main/java/ajs/printutils/Color.java: -------------------------------------------------------------------------------- 1 | package ajs.printutils; 2 | 3 | public enum Color { 4 | RED, GREEN, YELLOW, BLUE, PINK, LIGHT_BLUE, GRAY, GREY, NONE 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/ajs/printutils/Orientation.java: -------------------------------------------------------------------------------- 1 | package ajs.printutils; 2 | 3 | public enum Orientation { 4 | VERTICAL, HORIZONTAL 5 | } -------------------------------------------------------------------------------- /src/main/java/ajs/printutils/PrettyPrintTree.java: -------------------------------------------------------------------------------- 1 | package ajs.printutils; 2 | 3 | import java.text.BreakIterator; 4 | import java.util.*; 5 | import java.util.function.Function; 6 | import java.util.regex.*; 7 | 8 | public final class PrettyPrintTree { 9 | private static final Pattern slashNRegex = Pattern.compile("(\\\\n|\n)"); 10 | private static final Map colorToNum = Map.of( 11 | Color.RED, "41", 12 | Color.GREEN, "42", 13 | Color.YELLOW, "43", 14 | Color.BLUE, "44", 15 | Color.PINK, "45", 16 | Color.LIGHT_BLUE, "46", 17 | Color.GRAY, "47", 18 | Color.GREY, "47" 19 | ); 20 | 21 | private static final Map addBranch = Map.of( 22 | '─', '┴', 23 | '┬', '┼', 24 | '┌', '├', 25 | '┐', '┤' 26 | ); 27 | private final Function> getChildren; 28 | private final Function getNodeVal; 29 | private int maxDepth = -1, trim = -1; 30 | private Color color = Color.GRAY; 31 | private boolean border = false, escapeNewline = false; 32 | public PrettyPrintTree( 33 | Function> getChildren, 34 | Function getVal 35 | ) { 36 | this.getChildren = getChildren; 37 | this.getNodeVal = getVal; 38 | } 39 | 40 | public static int getCharacterCount(String str) { 41 | BreakIterator iterator = BreakIterator.getCharacterInstance(); 42 | iterator.setText(str); 43 | int count = 0; 44 | while (iterator.next() != BreakIterator.DONE) { 45 | count++; 46 | } 47 | return count; 48 | } 49 | public PrettyPrintTree setBorder(boolean border) { this.border = border; return this; } 50 | public PrettyPrintTree setColor(Color color) { this.color = color; return this; } 51 | public PrettyPrintTree setEscapeNewline(boolean escapeNewline) { this.escapeNewline = escapeNewline; return this; } 52 | public PrettyPrintTree setMaxDepth(int maxDepth) { this.maxDepth = maxDepth; return this; } 53 | public PrettyPrintTree setTrim(int trim) { this.trim = trim; return this; } 54 | 55 | public void display(Node node, int depth) { System.out.println(toStr(node, depth)); } 56 | public void display(Node node) { System.out.println(toStr(node)); } 57 | public String toStr(Node node) { return toStr(node, 0); } 58 | 59 | public String toStr(Node node, int depth) { 60 | String[][] res = treeToStr(node, depth); 61 | var str = new StringBuilder(); 62 | for (var line: res) { 63 | for (var x: line) { 64 | str.append(isNode(x) ? colorTxt(x) : x); 65 | } 66 | str.append("\n"); 67 | } 68 | str.deleteCharAt(str.length() - 1); 69 | return str.toString(); 70 | } 71 | 72 | private String[][] getVal(Node node) { 73 | var stVal = this.getNodeVal.apply(node); 74 | if (this.trim != -1 && this.trim < getCharacterCount(stVal)) { 75 | stVal = stVal.substring(0, this.trim) + "..."; // TODO support strings like ભોવાન - _ (બંટીયા) 76 | } 77 | if (this.escapeNewline) { 78 | stVal = slashNRegex.matcher(stVal).replaceAll(x -> x.group(0).equals("\n") ? "\\\\n" : "\\\\\\\\n"); 79 | } 80 | if (!stVal.contains("\n")) { 81 | return new String[][]{ 82 | new String[]{ stVal } 83 | }; 84 | } 85 | var lstVal = stVal.split("\n"); 86 | var longest = 0; 87 | for (var item: lstVal) { 88 | longest = Math.max(getCharacterCount(item), longest); 89 | } 90 | var res = new String[lstVal.length][]; 91 | for (int i = 0; i < lstVal.length; i++) { 92 | res[i] = new String[] { lstVal[i] + " ".repeat(longest - getCharacterCount(lstVal[i])) }; 93 | } 94 | return res; 95 | } 96 | private LinkedList removeNull(List list){ 97 | var res = new LinkedList(); 98 | for (var node : list){ 99 | if(node == null){ continue; } 100 | res.addLast(node); 101 | } 102 | return res; 103 | } 104 | private String[][] treeToStr(Node node, int depth) { 105 | var val = getVal(node); 106 | var children = this.getChildren.apply(node); 107 | children = removeNull(children); 108 | if (children.isEmpty()) { 109 | String[][] res; 110 | if (val.length == 1) { 111 | res = new String[][]{new String[]{"[" + val[0][0] + "]"}}; 112 | } else { 113 | res = formatBox("", val); 114 | } 115 | return res; 116 | } 117 | var toPrint = new ArrayList>(); 118 | toPrint.add(new ArrayList<>()); 119 | var spacing_count = 0; 120 | var spacing = ""; 121 | if (depth + 1 != this.maxDepth) { 122 | for (var child: children) { 123 | var childPrint = treeToStr(child, depth + 1); 124 | for (int l = 0; l < childPrint.length; l++) { 125 | var line = childPrint[l]; 126 | if (l + 1 >= toPrint.size()) { 127 | toPrint.add(new ArrayList<>()); 128 | } 129 | if (l == 0) { 130 | var lineLen = lenJoin(List.of(line)); 131 | var middleOfChild = lineLen - (int)Math.ceil(getCharacterCount(line[line.length-1]) / 2d); 132 | var toPrint0Len = lenJoin(toPrint.get(0)); 133 | toPrint.get(0).add(" ".repeat(spacing_count - toPrint0Len + middleOfChild) + "┬"); 134 | } 135 | var toPrintNxtLen = lenJoin(toPrint.get(l + 1)); 136 | toPrint.get(l + 1).add(" ".repeat(spacing_count - toPrintNxtLen)); 137 | toPrint.get(l + 1).addAll(List.of(line)); 138 | } 139 | spacing_count = 0; 140 | for (var item: toPrint) { 141 | var itemLen = lenJoin(item); 142 | spacing_count = Math.max(itemLen, spacing_count); 143 | } 144 | spacing_count++; 145 | } 146 | int pipePos; 147 | if (toPrint.get(0).size() != 1) { 148 | var newLines = String.join("", toPrint.get(0)); 149 | var spaceBefore = getCharacterCount(newLines) - getCharacterCount(newLines = newLines.trim()); 150 | int lenOfTrimmed = getCharacterCount(newLines); 151 | newLines = " ".repeat(spaceBefore) + 152 | "┌" + newLines.substring(1, newLines.length() - 1).replace(' ', '─') + "┐"; 153 | var middle = getCharacterCount(newLines) - (int)Math.ceil(lenOfTrimmed / 2d); 154 | pipePos = middle; 155 | 156 | var newCh = addBranch.get(newLines.charAt(middle)); 157 | newLines = newLines.substring(0, middle) + newCh + newLines.substring(middle + 1); 158 | var al = new ArrayList(); 159 | al.add(newLines); 160 | toPrint.set(0, al); 161 | } else { 162 | toPrint.get(0).set(0, toPrint.get(0).get(0).substring(0, toPrint.get(0).get(0).length() - 1) + '│'); 163 | pipePos = getCharacterCount(toPrint.get(0).get(0)) - 1; 164 | } 165 | if (getCharacterCount(val[0][0]) < pipePos * 2) { 166 | spacing = " ".repeat(pipePos - (int)Math.ceil(getCharacterCount(val[0][0]) / 2d)); 167 | } 168 | } 169 | if (val.length == 1) { 170 | val = new String[][] { new String[] { spacing, "[" + val[0][0] + "]" } }; 171 | } else { 172 | val = formatBox(spacing, val); 173 | } 174 | 175 | var asArr = new String[val.length + toPrint.size()][]; 176 | int row = 0; 177 | for (var item: val) { 178 | // asArr[row] = item; 179 | asArr[row] = new String[item.length]; 180 | System.arraycopy(item, 0, asArr[row], 0, item.length); 181 | row++; 182 | } 183 | for (var item: toPrint) { 184 | asArr[row] = new String[item.size()]; 185 | int i = 0; 186 | for (var x: item) { 187 | asArr[row][i] = x; 188 | i++; 189 | } 190 | row++; 191 | } 192 | 193 | return asArr; 194 | } 195 | private static boolean isNode(String x) { 196 | if (x == null || x.equals("")) { return false; } 197 | char xat0 = x.charAt(0); 198 | if (xat0 == '[' || xat0 == '|' || (xat0 == '│' && x.trim().length() > 1)) { 199 | return true; 200 | } 201 | if (x.length() < 2) { return false; } 202 | var middle = "─".repeat(getCharacterCount(x) - 2); 203 | return x.equals("┌" + middle + "┐") || x.equals("└" + middle + "┘"); 204 | } 205 | private String colorTxt(String txt) { 206 | var spaces = " ".repeat(getCharacterCount(txt) - getCharacterCount(txt = txt.trim())); 207 | boolean is_label = txt.startsWith("|"); 208 | if (is_label) { 209 | // todo "Not implemented yet" 210 | } 211 | txt = this.border ? txt : " " + txt.substring(1, txt.length() - 1) + " "; 212 | txt = this.color == Color.NONE ? txt : "\u001b[" + colorToNum.get(this.color) + "m" + txt + "\u001b[0m"; 213 | return spaces + txt; 214 | } 215 | 216 | private int lenJoin(Collection lst) { 217 | return getCharacterCount(String.join("", lst)); 218 | } 219 | 220 | private String[][] formatBox(String spacing, String[][] val) { 221 | String[][] res; 222 | int start = 0; 223 | if (this.border) { 224 | res = new String[val.length + 2][]; 225 | start = 1; 226 | var middle = "─".repeat(getCharacterCount(val[0][0])); 227 | res[0] = new String[] { spacing, '┌' + middle + '┐' }; 228 | res[res.length - 1] = new String[] { spacing, '└' + middle + '┘' }; 229 | } else { 230 | res = new String[val.length][]; 231 | } 232 | for (int r = 0; r < val.length; r++) { 233 | res[r + start] = new String[] { spacing, "│" + val[r][0] + "│" }; 234 | } 235 | return res; 236 | } 237 | } 238 | --------------------------------------------------------------------------------