Hi all
If this is the wrong place to post please let me know
THE GOAL
To build a 3D tree of life visualization (see photo below) in Unity for a education project that is real-time intractable like at https://itol.embl.de/itol.cgi (for
eg if you mouse over a line it triggers the line changes color, triggers pop-up information from the nodes, etc).
THE CHALLENGE / OPPORTUNITY?
Understand this is basic for ppl who get data structures and C# and I'm happy to keep pushing. However after a few days, I'm still struggling to converting the XML data into something
usable in Unity.
THE QUESTIONS
1) what's the best way to convert phyloXML data into objects I can vizualize as a 3D TREE OF LIFE?
2) Am I the right track (see below)? If no, what am I missing? What mistakes am I making? And what else do I need to consider to make this real-time interactible tree of life?
WHAT I'VE DONE THUS FAR
From my research, most trees of life are represented in a phyloXML data format (such as https://itol.embl.de/itol.cgi)
and happily, I found this phyloXML file which I've been using to form the tree: http://www.wellcometreeoflife.org/resources/tree-of-life-files/?sort=filetype&order=asc#xml
But how to convert this data into a 3d tree that surrounds a user?
My understanding (please correct if there's a better way), is I need to:
1) have some code that parses through the entire XML file and deserializes it into new data objects I can use to build a graph in Unity
2) use some more code in Unity to instantiate Node and Link objects (from the classes generated in 1) to produce the 3d tree
Re 1 – I came across this tutorial http://collaboradev.com/2014/03/12/visualizing-3d-network-topologies-using-unity/which
was helpful in teaching me that I probably need the StreamReader and XMLDocument classes. However, its data uses a different schema (Graph ML) and after hours of playing around as a c# noob, I've been unable to map it to my tree.xml (phyloXML) data.
See github of my adaptation of Jason's tutorial and attempt on github here: https://github.com/maxmagna2k/treelife/tree/master/ToL.01
Code (doesn't fn) here but the key parts aimed at mapping XML to node (or in pholoXML speak a 'clade' is lines 55-71):
using UnityEngine; using System.Collections; using System.Xml; using System.IO; using UnityEngine.UI; namespace Topology { public class GraphController : MonoBehaviour { public Node nodePrefab; public Link linkPrefab; private Hashtable nodes; private Hashtable links; private Text statusText; private int nodeCount = 0; private int linkCount = 0; private Text nodeCountText; private Text linkCountText; //Method for loading the GraphML layout file private IEnumerator LoadLayout() { string sourceFile = Application.dataPath + "/Data/tree-added-phylo-xml-schema.xml"; statusText.text = "Loading file: " + sourceFile; //determine which platform to load for string xml = null; StreamReader sr = new StreamReader(sourceFile); xml = sr.ReadToEnd(); sr.Close(); XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(xml); statusText.text = "Loading Topology"; int scale = 1; XmlElement root = xmlDoc.FirstChild as XmlElement; for (int i = 0; i < root.ChildNodes.Count; i++) { XmlElement xmlGraph = root.ChildNodes[i] as XmlElement; for (int j = 0; j < xmlGraph.ChildNodes.Count; j++) { XmlElement xmlNode = xmlGraph.ChildNodes[j] as XmlElement; //create nodes if (xmlNode.Name == "clade") { //float x = float.Parse(xmlNode.Attributes["x"].Value) / scale; float y = float.Parse(xmlNode.Attributes["age_mya"].Value) / scale; //float z = float.Parse(xmlNode.Attributes["z"].Value) / scale; Node nodeObject = Instantiate(nodePrefab, new Vector3(0,y,0), Quaternion.identity) as Node; nodeObject.nodeText.text = xmlNode.Attributes["name"].Value; nodeObject.id = xmlNode.Attributes["name"].Value; //skip other node attributes for now (branch_length, age_mya, etc) nodes.Add(nodeObject.id, nodeObject); statusText.text = "Loading Topology: Node " + nodeObject.id; nodeCount++; nodeCountText.text = "Nodes: " + nodeCount; } //create links if (xmlNode.Name == "edge") { Link linkObject = Instantiate(linkPrefab, new Vector3(0, 0, 0), Quaternion.identity) as Link; linkObject.id = xmlNode.Attributes["id"].Value; linkObject.sourceId = xmlNode.Attributes["source"].Value; linkObject.targetId = xmlNode.Attributes["target"].Value; linkObject.status = xmlNode.Attributes["status"].Value; links.Add(linkObject.id, linkObject); statusText.text = "Loading Topology: Edge " + linkObject.id; linkCount++; linkCountText.text = "Edges: " + linkCount; } //every 100 cycles return control to unity if (j % 100 == 0) yield return true; } } //map node edges MapLinkNodes(); statusText.text = ""; } //Method for mapping links to nodes private void MapLinkNodes() { foreach (string key in links.Keys) { Link link = links[key] as Link; link.source = nodes[link.sourceId] as Node; link.target = nodes[link.targetId] as Node; } } void Start() { nodes = new Hashtable(); links = new Hashtable(); //initial stats nodeCountText = GameObject.Find("NodeCount").GetComponent<Text>(); nodeCountText.text = "Nodes: 0"; linkCountText = GameObject.Find("LinkCount").GetComponent<Text>(); linkCountText.text = "Edges: 0"; statusText = GameObject.Find("Status").GetComponent<Text>(); statusText.text = ""; StartCoroutine(LoadLayout()); } } }
Going to be great to get this up and running ;)