{"id":20032530,"url":"https://github.com/akicho8/tree_support","last_synced_at":"2025-05-05T05:30:40.029Z","repository":{"id":8476573,"uuid":"10078303","full_name":"akicho8/tree_support","owner":"akicho8","description":"Tree structure visualization function library","archived":false,"fork":false,"pushed_at":"2023-01-19T11:15:43.000Z","size":1650,"stargazers_count":9,"open_issues_count":7,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-28T21:58:32.974Z","etag":null,"topics":["activerecord","library","rails","ruby","tree","tree-structure","visualization"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/akicho8.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-05-15T12:39:08.000Z","updated_at":"2023-11-02T20:43:09.000Z","dependencies_parsed_at":"2023-02-10T23:00:36.216Z","dependency_job_id":null,"html_url":"https://github.com/akicho8/tree_support","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akicho8%2Ftree_support","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akicho8%2Ftree_support/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akicho8%2Ftree_support/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akicho8%2Ftree_support/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/akicho8","download_url":"https://codeload.github.com/akicho8/tree_support/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252445619,"owners_count":21749089,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","library","rails","ruby","tree","tree-structure","visualization"],"created_at":"2024-11-13T09:38:10.745Z","updated_at":"2025-05-05T05:30:39.734Z","avatar_url":"https://github.com/akicho8.png","language":"Ruby","readme":"* Tree structure visualization function library\n\n  [[https://travis-ci.org/akicho8/tree_support.png]]\n\n** Installation\n\n   Install as a standalone gem\n\n#+BEGIN_SRC shell\n$ gem install tree_support\n#+END_SRC\n\nOr install within application using Gemfile\n\n#+BEGIN_SRC shell\n$ bundle add tree_support\n$ bundle install\n#+END_SRC\n\n** How to use in one line\n\n   Just pass the object that has the parent, children method to =TreeSupport.tree=\n\n#+BEGIN_SRC ruby\nrequire \"tree_support\"\nputs TreeSupport.tree(TreeSupport.example)\n# \u003e\u003e *root*\n# \u003e\u003e ├─Battle\n# \u003e\u003e │   ├─Attack\n# \u003e\u003e │   │   ├─Shake the sword\n# \u003e\u003e │   │   ├─Attack magic\n# \u003e\u003e │   │   │   ├─Summoned Beast X\n# \u003e\u003e │   │   │   └─Summoned Beast Y\n# \u003e\u003e │   │   └─Repel sword in length\n# \u003e\u003e │   └─Defense\n# \u003e\u003e ├─Withdraw\n# \u003e\u003e │   ├─To stop\n# \u003e\u003e │   │   ├─Place a trap\n# \u003e\u003e │   │   └─Shoot a bow and arrow\n# \u003e\u003e │   └─To escape\n# \u003e\u003e └─Break\n# \u003e\u003e     ├─Stop\n# \u003e\u003e     └─Recover\n# \u003e\u003e         ├─Recovery magic\n# \u003e\u003e         └─Drink recovery medicine\n#+END_SRC\n\n** Detailed usage\n\n*** Prepare a node class like this\n\n#+BEGIN_SRC ruby\nclass Node\n  attr_accessor :name, :parent, :children\n\n  def initialize(name = nil, \u0026block)\n    @name = name\n    @children = []\n    if block_given?\n      instance_eval(\u0026block)\n    end\n  end\n\n  def add(*args, \u0026block)\n    tap do\n      children \u003c\u003c self.class.new(*args, \u0026block).tap { |v| v.parent = self }\n    end\n  end\nend\n#+END_SRC\n\n*** Create a tree\n\n#+BEGIN_SRC ruby\nroot = Node.new(\"*root*\") do\n  add \"Battle\" do\n    add \"Attack\" do\n      add \"Shake the sword\"\n      add \"Attack magic\" do\n        add \"Summoned Beast X\"\n        add \"Summoned Beast Y\"\n      end\n      add \"Repel sword in length\"\n    end\n    add \"Defense\"\n  end\n  add \"Withdraw\" do\n    add \"To stop\" do\n      add \"Place a trap\"\n      add \"Shoot a bow and arrow\"\n    end\n    add \"To escape\"\n  end\n  add \"Break\" do\n    add \"Stop\"\n    add \"Recover\" do\n      add \"Recovery magic\"\n      add \"Drink recovery medicine\"\n    end\n  end\nend\n#+END_SRC\n\n*** Visualization\n\n#+BEGIN_SRC ruby\nputs TreeSupport.tree(root)\n# \u003e\u003e *root*\n# \u003e\u003e ├─Battle\n# \u003e\u003e │   ├─Attack\n# \u003e\u003e │   │   ├─Shake the sword\n# \u003e\u003e │   │   ├─Attack magic\n# \u003e\u003e │   │   │   ├─Summoned Beast X\n# \u003e\u003e │   │   │   └─Summoned Beast Y\n# \u003e\u003e │   │   └─Repel sword in length\n# \u003e\u003e │   └─Defense\n# \u003e\u003e ├─Withdraw\n# \u003e\u003e │   ├─To stop\n# \u003e\u003e │   │   ├─Place a trap\n# \u003e\u003e │   │   └─Shoot a bow and arrow\n# \u003e\u003e │   └─To escape\n# \u003e\u003e └─Break\n# \u003e\u003e     ├─Stop\n# \u003e\u003e     └─Recover\n# \u003e\u003e         ├─Recovery magic\n# \u003e\u003e         └─Drink recovery medicine\n#+END_SRC\n\n*** Troublesome writing TreeSupport.tree\n\n    =include TreeSupport::Stringify=\n\n#+BEGIN_SRC ruby\nNode.include(TreeSupport::Stringify)\nputs root.to_s_tree\n# \u003e\u003e *root*\n# \u003e\u003e ├─Battle\n# \u003e\u003e │   ├─Attack\n# \u003e\u003e │   │   ├─Shake the sword\n# \u003e\u003e │   │   ├─Attack magic\n# \u003e\u003e │   │   │   ├─Summoned Beast X\n# \u003e\u003e │   │   │   └─Summoned Beast Y\n# \u003e\u003e │   │   └─Repel sword in length\n# \u003e\u003e │   └─Defense\n# \u003e\u003e ├─Withdraw\n# \u003e\u003e │   ├─To stop\n# \u003e\u003e │   │   ├─Place a trap\n# \u003e\u003e │   │   └─Shoot a bow and arrow\n# \u003e\u003e │   └─To escape\n# \u003e\u003e └─Break\n# \u003e\u003e     ├─Stop\n# \u003e\u003e     └─Recover\n# \u003e\u003e         ├─Recovery magic\n# \u003e\u003e         └─Drink recovery medicine\n#+END_SRC\n\n*** How do I change the label of a node?\n\n    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\n\n*** How do I change labels without defining methods?\n\n   Add a block to tree\n\n#+BEGIN_SRC ruby\nputs TreeSupport.tree(root) { |node| node.object_id }\n# \u003e\u003e 70308514816100\n# \u003e\u003e ├─70308514815920\n# \u003e\u003e │   ├─70308514815780\n# \u003e\u003e │   │   ├─70308514815680\n# \u003e\u003e │   │   ├─70308514815580\n# \u003e\u003e │   │   │   ├─70308514815480\n# \u003e\u003e │   │   │   └─70308514815420\n# \u003e\u003e │   │   └─70308514815360\n# \u003e\u003e │   └─70308514815300\n# \u003e\u003e ├─70308514815220\n# \u003e\u003e │   ├─70308514815080\n# \u003e\u003e │   │   ├─70308514814980\n# \u003e\u003e │   │   └─70308514814920\n# \u003e\u003e │   └─70308514814860\n# \u003e\u003e └─70308514814780\n# \u003e\u003e      ├─70308514814680\n# \u003e\u003e      └─70308514814580\n# \u003e\u003e           ├─70308514814480\n# \u003e\u003e           └─70308514814420\n#+END_SRC\n\n*** How to use methods that are common in tree structure?\n\n    The following methods become available in include of =TreeSupport::Treeable=\n\n- root\n- root?\n- leaf?\n- each\n- each_node\n- descendants\n- self_and_descendants\n- ancestors\n- self_and_ancestors\n- siblings\n- self_and_siblings\n\n*** How to convert to Gviz object?\n\n#+BEGIN_SRC ruby\ngv = TreeSupport.graphviz(root)\n#+END_SRC\n\n*** How to image it?\n\n#+BEGIN_SRC ruby\ngv.output(\"tree.png\")\n#+END_SRC\n\n   [[https://raw.github.com/akicho8/tree_support/master/images/tree.png]]\n\n*** How do I change the color of a particular node?\n\n    Return the graphviz attribute as a hash in TreeSupport.graphviz block\n\n#+BEGIN_SRC ruby\ngv = TreeSupport.graphviz(root) do |node|\n  if node.name.include?(\"Attack\")\n    {fillcolor: \"lightblue\", style: \"filled\"}\n  elsif node.name.include?(\"Recover\")\n    {fillcolor: \"lightpink\", style: \"filled\"}\n  end\nend\ngv.output(\"tree_color.png\")\n#+END_SRC\n\n   [[https://raw.github.com/akicho8/tree_support/master/images/tree_color.png]]\n\n*** How do I change the label of a particular node?\n\n    As with the above method, it returns a hash containing the label value\n\n#+BEGIN_SRC ruby\ngv = TreeSupport.graphviz(root) do |node|\n  {label: node.name.chars.first}\nend\ngv.output(\"tree_label.png\")\n#+END_SRC\n\n   [[https://raw.github.com/akicho8/tree_support/master/images/tree_label.png]]\n\n*** How can I check the dot format of Graphviz?\n\n#+BEGIN_SRC ruby\nputs gv.to_dot\n# \u003e\u003e digraph n70146110700700 {\n# \u003e\u003e   graph [charset = \"UTF-8\", rankdir = \"LR\"];\n# \u003e\u003e   n70146110700700 [label = \"*root*\"];\n# \u003e\u003e   n70146110700700 -\u003e {n70146110698600; n70146110691220; n70146110689500;};\n# \u003e\u003e   n70146110698600 [label = \"Battle\"];\n# \u003e\u003e   n70146110698600 -\u003e {n70146110698320; n70146110691720;};\n# \u003e\u003e   n70146110698320 [label = \"Attack\"];\n# \u003e\u003e   n70146110698320 -\u003e {n70146110697900; n70146110697240; n70146110692060;};\n# \u003e\u003e   n70146110697900 [label = \"Shake the sword\"];\n# \u003e\u003e   n70146110697240 [label = \"Attack magic\"];\n# \u003e\u003e   n70146110697240 -\u003e {n70146110695080; n70146110694480;};\n# \u003e\u003e   n70146110695080 [label = \"Summoned Beast X\"];\n# \u003e\u003e   n70146110694480 [label = \"Summoned Beast Y\"];\n# \u003e\u003e   n70146110692060 [label = \"Repel sword in length\"];\n# \u003e\u003e   n70146110691720 [label = \"Defense\"];\n# \u003e\u003e   n70146110691220 [label = \"Withdraw\"];\n# \u003e\u003e   n70146110691220 -\u003e {n70146110690400; n70146110689620;};\n# \u003e\u003e   n70146110690400 [label = \"To stop\"];\n# \u003e\u003e   n70146110690400 -\u003e {n70146110690220; n70146110689820;};\n# \u003e\u003e   n70146110690220 [label = \"Place a trap\"];\n# \u003e\u003e   n70146110689820 [label = \"Shoot a bow and arrow\"];\n# \u003e\u003e   n70146110689620 [label = \"To escape\"];\n# \u003e\u003e   n70146110689500 [label = \"Break\"];\n# \u003e\u003e   n70146110689500 -\u003e {n70146110688500; n70146110687660;};\n# \u003e\u003e   n70146110688500 [label = \"Stop\"];\n# \u003e\u003e   n70146110687660 [label = \"Recover\"];\n# \u003e\u003e   n70146110687660 -\u003e {n70146110686920; n70146110686220;};\n# \u003e\u003e   n70146110686920 [label = \"Recovery magic\"];\n# \u003e\u003e   n70146110686220 [label = \"Drink recovery medicine\"];\n# \u003e\u003e }\n#+END_SRC\n\n*** How can I check the image conversion immediately when debugging?\n\n#+BEGIN_SRC ruby\nTreeSupport.graph_open(root)\n#+END_SRC\n\n    Equivalent to the next shortcut\n\n#+BEGIN_SRC ruby\nTreeSupport.graphviz(root).output(\"_output.png\")\n`open _output.png`\n#+END_SRC\n\n*** Troublesome making node classes yourself\n\n    You can use =TreeSupport::Node= as it is.\n\n#+BEGIN_SRC ruby\nTreeSupport::Node.new(\"*root*\") do\n  add \"Battle\" do\n    add \"Attack\" do\n      add \"Shake the sword\"\n      add \"Attack magic\" do\n        add \"Summoned Beast X\"\n        add \"Summoned Beast Y\"\n      end\n    end\n  end\nend\n#+END_SRC\n\n*** Troublesome making trees\n\n#+BEGIN_SRC ruby\nTreeSupport.example\n#+END_SRC\n\n    There is a simple sample tree\n\n*** How to trace leaves?\n\n    If you include =TreeSupport::Treeable= you can use each_node\n\n#+BEGIN_SRC ruby\nroot = TreeSupport.example\nroot.each_node.with_index { |n, i| p [i, n.name] }\n# \u003e\u003e [0, \"*root*\"]\n# \u003e\u003e [1, \"Battle\"]\n# \u003e\u003e [2, \"Attack\"]\n# \u003e\u003e [3, \"Shake the sword\"]\n# \u003e\u003e [4, \"Attack magic\"]\n# \u003e\u003e [5, \"Summoned Beast X\"]\n# \u003e\u003e [6, \"Summoned Beast Y\"]\n# \u003e\u003e [7, \"Repel sword in length\"]\n# \u003e\u003e [8, \"Defense\"]\n# \u003e\u003e [9, \"Withdraw\"]\n# \u003e\u003e [10, \"To stop\"]\n# \u003e\u003e [11, \"Place a trap\"]\n# \u003e\u003e [12, \"Shoot a bow and arrow\"]\n# \u003e\u003e [13, \"To escape\"]\n# \u003e\u003e [14, \"Break\"]\n# \u003e\u003e [15, \"Stop\"]\n# \u003e\u003e [16, \"Recover\"]\n# \u003e\u003e [17, \"Recovery magic\"]\n# \u003e\u003e [18, \"Drink recovery medicine\"]\n#+END_SRC\n\n*** I do not want to display the root\n\n#+BEGIN_SRC ruby\nputs TreeSupport.tree(root, drop: 1)\n# \u003e\u003e Battle\n# \u003e\u003e ├─Attack\n# \u003e\u003e │   ├─Shake the sword\n# \u003e\u003e │   ├─Attack magic\n# \u003e\u003e │   │   ├─Summoned Beast X\n# \u003e\u003e │   │   └─Summoned Beast Y\n# \u003e\u003e │   └─Repel sword in length\n# \u003e\u003e └─Defense\n# \u003e\u003e Withdraw\n# \u003e\u003e ├─To stop\n# \u003e\u003e │   ├─Place a trap\n# \u003e\u003e │   └─Shoot a bow and arrow\n# \u003e\u003e └─To escape\n# \u003e\u003e Break\n# \u003e\u003e ├─Stop\n# \u003e\u003e └─Recover\n# \u003e\u003e     ├─Recovery magic\n# \u003e\u003e     └─Drink recovery medicine\n#+END_SRC\n\n*** Since the trees are too big, it is enough up to the depth 3\n\n#+BEGIN_SRC ruby\nputs TreeSupport.tree(root, take: 3)\n# \u003e\u003e *root*\n# \u003e\u003e ├─Battle\n# \u003e\u003e │   ├─Attack\n# \u003e\u003e │   └─Defense\n# \u003e\u003e ├─Withdraw\n# \u003e\u003e │   ├─To stop\n# \u003e\u003e │   └─To escape\n# \u003e\u003e └─Break\n# \u003e\u003e     ├─Stop\n# \u003e\u003e     └─Recover\n#+END_SRC\n\n*** When you combine both\n\n#+BEGIN_SRC ruby\nputs TreeSupport.tree(root, take: 3, drop: 1)\n# \u003e\u003e Battle\n# \u003e\u003e ├─Attack\n# \u003e\u003e └─Defense\n# \u003e\u003e Withdraw\n# \u003e\u003e ├─To stop\n# \u003e\u003e └─To escape\n# \u003e\u003e Break\n# \u003e\u003e ├─Stop\n# \u003e\u003e └─Recover\n#+END_SRC\n\n*** Image version also has similar options\n\n#+BEGIN_SRC ruby\ngv = TreeSupport.graphviz(root, drop: 1)\ngv.output(\"drop.png\")\n#+END_SRC\n\n    [[https://raw.github.com/akicho8/tree_support/master/images/drop.png]]\n\n#+BEGIN_SRC ruby\ngv = TreeSupport.graphviz(root, take: 3)\ngv.output(\"take.png\")\n#+END_SRC\n\n    [[https://raw.github.com/akicho8/tree_support/master/images/take.png]]\n\n#+BEGIN_SRC ruby\ngv = TreeSupport.graphviz(root, take: 3, drop: 1)\ngv.output(\"take_drop.png\")\n#+END_SRC\n\n    [[https://raw.github.com/akicho8/tree_support/master/images/take_drop.png]]\n\n*** Methods for easily making trees from data like CSV\n\nConversion from Array to Tree\n\n#+BEGIN_SRC ruby\nrequire \"tree_support\"\n\nrecords = [\n  {key: :a, parent: nil},\n  {key: :b, parent: :a},\n  {key: :c, parent: :b},\n]\n\n# When the first node is regarded as a parent\nputs TreeSupport.records_to_tree(records).first.to_s_tree\n# \u003e\u003e a\n# \u003e\u003e └─b\n# \u003e\u003e     └─c\n\n# When you make a parent parenting the whole\nputs TreeSupport.records_to_tree(records, root_key: :root).to_s_tree\n# \u003e\u003e root\n# \u003e\u003e └─a\n# \u003e\u003e     └─b\n# \u003e\u003e         └─c\n#+END_SRC\n\nConversion from Tree to Array\n\n#+BEGIN_SRC ruby\ntree = TreeSupport.records_to_tree(records)\npp TreeSupport.tree_to_records(tree.first)\n# \u003e\u003e [\n# \u003e\u003e   {:key=\u003e:a, :parent=\u003enil},\n# \u003e\u003e   {:key=\u003e:b, :parent=\u003e:a},\n# \u003e\u003e   {:key=\u003e:c, :parent=\u003e:b},\n# \u003e\u003e ]\n#+END_SRC\n\n*** How to use acts_as_tree equivalent?\n\n    Migration\n\n#+BEGIN_SRC ruby\ncreate_table :nodes do |t|\n  t.belongs_to :parent\nend\n#+END_SRC\n\n    Model\n\n#+BEGIN_SRC ruby\nclass Node \u003c ActiveRecord::Base\n  ar_tree_model\nend\n#+END_SRC\n\n    Difference from https://github.com/amerine/acts_as_tree\n\n    - simple\n    - Safely delete all safe_destroy_all (accident with destroy_all in combination with acts_as_list)\n    - Node.roots is defined by scope\n    - Arguments are different. =:order =\u003e :id= if you want to do it =scope: -\u003e { order(:id) }=. By doing this you can also pass the where condition.\n\n*** How do I correspond to memory_record gem?\n\n    Just as with ordinary classes, we need parent and children methods\n\n#+BEGIN_SRC ruby\nclass TreeModel\n  include MemoryRecord\n  memory_record [\n    {key: :a, parent: nil},\n    {key: :b, parent: :a},\n    {key: :c, parent: :b},\n  ]\n\n  include TreeSupport::Treeable\n  include TreeSupport::Stringify\n\n  def parent\n    self.class[super]\n  end\n\n  def children\n    self.class.find_all { |e| e.parent == self }\n  end\nend\n\nputs TreeModel.find_all(\u0026:root?).collect(\u0026:to_s_tree)\n# \u003e\u003e A\n# \u003e\u003e └─B\n# \u003e\u003e     └─C\n#+END_SRC\n\n** With concern\n\n   - Since Gviz extends the standard class, concerns about future interference when combined with Rails (Active Support) etc.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakicho8%2Ftree_support","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fakicho8%2Ftree_support","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakicho8%2Ftree_support/lists"}