Folders and files
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Repository files navigation
* Tree structure visualization function library [[https://travis-ci.org/akicho8/tree_support.png]] ** Installation Install as a standalone gem #+BEGIN_SRC shell $ gem install tree_support #+END_SRC Or install within application using Gemfile #+BEGIN_SRC shell $ bundle add tree_support $ bundle install #+END_SRC ** How to use in one line Just pass the object that has the parent, children method to =TreeSupport.tree= #+BEGIN_SRC ruby require "tree_support" puts TreeSupport.tree(TreeSupport.example) # >> *root* # >> ├─Battle # >> │ ├─Attack # >> │ │ ├─Shake the sword # >> │ │ ├─Attack magic # >> │ │ │ ├─Summoned Beast X # >> │ │ │ └─Summoned Beast Y # >> │ │ └─Repel sword in length # >> │ └─Defense # >> ├─Withdraw # >> │ ├─To stop # >> │ │ ├─Place a trap # >> │ │ └─Shoot a bow and arrow # >> │ └─To escape # >> └─Break # >> ├─Stop # >> └─Recover # >> ├─Recovery magic # >> └─Drink recovery medicine #+END_SRC ** Detailed usage *** Prepare a node class like this #+BEGIN_SRC ruby class Node attr_accessor :name, :parent, :children def initialize(name = nil, &block) @name = name @children = [] if block_given? instance_eval(&block) end end def add(*args, &block) tap do children << self.class.new(*args, &block).tap { |v| v.parent = self } end end end #+END_SRC *** Create a tree #+BEGIN_SRC ruby root = Node.new("*root*") do add "Battle" do add "Attack" do add "Shake the sword" add "Attack magic" do add "Summoned Beast X" add "Summoned Beast Y" end add "Repel sword in length" end add "Defense" end add "Withdraw" do add "To stop" do add "Place a trap" add "Shoot a bow and arrow" end add "To escape" end add "Break" do add "Stop" add "Recover" do add "Recovery magic" add "Drink recovery medicine" end end end #+END_SRC *** Visualization #+BEGIN_SRC ruby puts TreeSupport.tree(root) # >> *root* # >> ├─Battle # >> │ ├─Attack # >> │ │ ├─Shake the sword # >> │ │ ├─Attack magic # >> │ │ │ ├─Summoned Beast X # >> │ │ │ └─Summoned Beast Y # >> │ │ └─Repel sword in length # >> │ └─Defense # >> ├─Withdraw # >> │ ├─To stop # >> │ │ ├─Place a trap # >> │ │ └─Shoot a bow and arrow # >> │ └─To escape # >> └─Break # >> ├─Stop # >> └─Recover # >> ├─Recovery magic # >> └─Drink recovery medicine #+END_SRC *** Troublesome writing TreeSupport.tree =include TreeSupport::Stringify= #+BEGIN_SRC ruby Node.include(TreeSupport::Stringify) puts root.to_s_tree # >> *root* # >> ├─Battle # >> │ ├─Attack # >> │ │ ├─Shake the sword # >> │ │ ├─Attack magic # >> │ │ │ ├─Summoned Beast X # >> │ │ │ └─Summoned Beast Y # >> │ │ └─Repel sword in length # >> │ └─Defense # >> ├─Withdraw # >> │ ├─To stop # >> │ │ ├─Place a trap # >> │ │ └─Shoot a bow and arrow # >> │ └─To escape # >> └─Break # >> ├─Stop # >> └─Recover # >> ├─Recovery magic # >> └─Drink recovery medicine #+END_SRC *** How do I change the label of a node? We look for =to_s_tree_name=, =name=, =subject=, =title=, =to_s= defined by =TreeSupport.name_methods= in that order, so we define the method by considering the priority *** How do I change labels without defining methods? Add a block to tree #+BEGIN_SRC ruby puts TreeSupport.tree(root) { |node| node.object_id } # >> 70308514816100 # >> ├─70308514815920 # >> │ ├─70308514815780 # >> │ │ ├─70308514815680 # >> │ │ ├─70308514815580 # >> │ │ │ ├─70308514815480 # >> │ │ │ └─70308514815420 # >> │ │ └─70308514815360 # >> │ └─70308514815300 # >> ├─70308514815220 # >> │ ├─70308514815080 # >> │ │ ├─70308514814980 # >> │ │ └─70308514814920 # >> │ └─70308514814860 # >> └─70308514814780 # >> ├─70308514814680 # >> └─70308514814580 # >> ├─70308514814480 # >> └─70308514814420 #+END_SRC *** How to use methods that are common in tree structure? The following methods become available in include of =TreeSupport::Treeable= - root - root? - leaf? - each - each_node - descendants - self_and_descendants - ancestors - self_and_ancestors - siblings - self_and_siblings *** How to convert to Gviz object? #+BEGIN_SRC ruby gv = TreeSupport.graphviz(root) #+END_SRC *** How to image it? #+BEGIN_SRC ruby gv.output("tree.png") #+END_SRC [[https://raw.github.com/akicho8/tree_support/master/images/tree.png]] *** How do I change the color of a particular node? Return the graphviz attribute as a hash in TreeSupport.graphviz block #+BEGIN_SRC ruby gv = TreeSupport.graphviz(root) do |node| if node.name.include?("Attack") {fillcolor: "lightblue", style: "filled"} elsif node.name.include?("Recover") {fillcolor: "lightpink", style: "filled"} end end gv.output("tree_color.png") #+END_SRC [[https://raw.github.com/akicho8/tree_support/master/images/tree_color.png]] *** How do I change the label of a particular node? As with the above method, it returns a hash containing the label value #+BEGIN_SRC ruby gv = TreeSupport.graphviz(root) do |node| {label: node.name.chars.first} end gv.output("tree_label.png") #+END_SRC [[https://raw.github.com/akicho8/tree_support/master/images/tree_label.png]] *** How can I check the dot format of Graphviz? #+BEGIN_SRC ruby puts gv.to_dot # >> digraph n70146110700700 { # >> graph [charset = "UTF-8", rankdir = "LR"]; # >> n70146110700700 [label = "*root*"]; # >> n70146110700700 -> {n70146110698600; n70146110691220; n70146110689500;}; # >> n70146110698600 [label = "Battle"]; # >> n70146110698600 -> {n70146110698320; n70146110691720;}; # >> n70146110698320 [label = "Attack"]; # >> n70146110698320 -> {n70146110697900; n70146110697240; n70146110692060;}; # >> n70146110697900 [label = "Shake the sword"]; # >> n70146110697240 [label = "Attack magic"]; # >> n70146110697240 -> {n70146110695080; n70146110694480;}; # >> n70146110695080 [label = "Summoned Beast X"]; # >> n70146110694480 [label = "Summoned Beast Y"]; # >> n70146110692060 [label = "Repel sword in length"]; # >> n70146110691720 [label = "Defense"]; # >> n70146110691220 [label = "Withdraw"]; # >> n70146110691220 -> {n70146110690400; n70146110689620;}; # >> n70146110690400 [label = "To stop"]; # >> n70146110690400 -> {n70146110690220; n70146110689820;}; # >> n70146110690220 [label = "Place a trap"]; # >> n70146110689820 [label = "Shoot a bow and arrow"]; # >> n70146110689620 [label = "To escape"]; # >> n70146110689500 [label = "Break"]; # >> n70146110689500 -> {n70146110688500; n70146110687660;}; # >> n70146110688500 [label = "Stop"]; # >> n70146110687660 [label = "Recover"]; # >> n70146110687660 -> {n70146110686920; n70146110686220;}; # >> n70146110686920 [label = "Recovery magic"]; # >> n70146110686220 [label = "Drink recovery medicine"]; # >> } #+END_SRC *** How can I check the image conversion immediately when debugging? #+BEGIN_SRC ruby TreeSupport.graph_open(root) #+END_SRC Equivalent to the next shortcut #+BEGIN_SRC ruby TreeSupport.graphviz(root).output("_output.png") `open _output.png` #+END_SRC *** Troublesome making node classes yourself You can use =TreeSupport::Node= as it is. #+BEGIN_SRC ruby TreeSupport::Node.new("*root*") do add "Battle" do add "Attack" do add "Shake the sword" add "Attack magic" do add "Summoned Beast X" add "Summoned Beast Y" end end end end #+END_SRC *** Troublesome making trees #+BEGIN_SRC ruby TreeSupport.example #+END_SRC There is a simple sample tree *** How to trace leaves? If you include =TreeSupport::Treeable= you can use each_node #+BEGIN_SRC ruby root = TreeSupport.example root.each_node.with_index { |n, i| p [i, n.name] } # >> [0, "*root*"] # >> [1, "Battle"] # >> [2, "Attack"] # >> [3, "Shake the sword"] # >> [4, "Attack magic"] # >> [5, "Summoned Beast X"] # >> [6, "Summoned Beast Y"] # >> [7, "Repel sword in length"] # >> [8, "Defense"] # >> [9, "Withdraw"] # >> [10, "To stop"] # >> [11, "Place a trap"] # >> [12, "Shoot a bow and arrow"] # >> [13, "To escape"] # >> [14, "Break"] # >> [15, "Stop"] # >> [16, "Recover"] # >> [17, "Recovery magic"] # >> [18, "Drink recovery medicine"] #+END_SRC *** I do not want to display the root #+BEGIN_SRC ruby puts TreeSupport.tree(root, drop: 1) # >> Battle # >> ├─Attack # >> │ ├─Shake the sword # >> │ ├─Attack magic # >> │ │ ├─Summoned Beast X # >> │ │ └─Summoned Beast Y # >> │ └─Repel sword in length # >> └─Defense # >> Withdraw # >> ├─To stop # >> │ ├─Place a trap # >> │ └─Shoot a bow and arrow # >> └─To escape # >> Break # >> ├─Stop # >> └─Recover # >> ├─Recovery magic # >> └─Drink recovery medicine #+END_SRC *** Since the trees are too big, it is enough up to the depth 3 #+BEGIN_SRC ruby puts TreeSupport.tree(root, take: 3) # >> *root* # >> ├─Battle # >> │ ├─Attack # >> │ └─Defense # >> ├─Withdraw # >> │ ├─To stop # >> │ └─To escape # >> └─Break # >> ├─Stop # >> └─Recover #+END_SRC *** When you combine both #+BEGIN_SRC ruby puts TreeSupport.tree(root, take: 3, drop: 1) # >> Battle # >> ├─Attack # >> └─Defense # >> Withdraw # >> ├─To stop # >> └─To escape # >> Break # >> ├─Stop # >> └─Recover #+END_SRC *** Image version also has similar options #+BEGIN_SRC ruby gv = TreeSupport.graphviz(root, drop: 1) gv.output("drop.png") #+END_SRC [[https://raw.github.com/akicho8/tree_support/master/images/drop.png]] #+BEGIN_SRC ruby gv = TreeSupport.graphviz(root, take: 3) gv.output("take.png") #+END_SRC [[https://raw.github.com/akicho8/tree_support/master/images/take.png]] #+BEGIN_SRC ruby gv = TreeSupport.graphviz(root, take: 3, drop: 1) gv.output("take_drop.png") #+END_SRC [[https://raw.github.com/akicho8/tree_support/master/images/take_drop.png]] *** Methods for easily making trees from data like CSV Conversion from Array to Tree #+BEGIN_SRC ruby require "tree_support" records = [ {key: :a, parent: nil}, {key: :b, parent: :a}, {key: :c, parent: :b}, ] # When the first node is regarded as a parent puts TreeSupport.records_to_tree(records).first.to_s_tree # >> a # >> └─b # >> └─c # When you make a parent parenting the whole puts TreeSupport.records_to_tree(records, root_key: :root).to_s_tree # >> root # >> └─a # >> └─b # >> └─c #+END_SRC Conversion from Tree to Array #+BEGIN_SRC ruby tree = TreeSupport.records_to_tree(records) pp TreeSupport.tree_to_records(tree.first) # >> [ # >> {:key=>:a, :parent=>nil}, # >> {:key=>:b, :parent=>:a}, # >> {:key=>:c, :parent=>:b}, # >> ] #+END_SRC *** How to use acts_as_tree equivalent? Migration #+BEGIN_SRC ruby create_table :nodes do |t| t.belongs_to :parent end #+END_SRC Model #+BEGIN_SRC ruby class Node < ActiveRecord::Base ar_tree_model end #+END_SRC Difference from https://github.com/amerine/acts_as_tree - simple - Safely delete all safe_destroy_all (accident with destroy_all in combination with acts_as_list) - Node.roots is defined by scope - Arguments are different. =:order => :id= if you want to do it =scope: -> { order(:id) }=. By doing this you can also pass the where condition. *** How do I correspond to memory_record gem? Just as with ordinary classes, we need parent and children methods #+BEGIN_SRC ruby class TreeModel include MemoryRecord memory_record [ {key: :a, parent: nil}, {key: :b, parent: :a}, {key: :c, parent: :b}, ] include TreeSupport::Treeable include TreeSupport::Stringify def parent self.class[super] end def children self.class.find_all { |e| e.parent == self } end end puts TreeModel.find_all(&:root?).collect(&:to_s_tree) # >> A # >> └─B # >> └─C #+END_SRC ** With concern - Since Gviz extends the standard class, concerns about future interference when combined with Rails (Active Support) etc.