Published
Edited
Sep 30, 2022
1 star
Insert cell
Insert cell
Insert cell
/**
* Class representing a Node in a tree.
* You should not need to edit this.
*/
class Node {
/**
* Creates a Node and intializes the following fields to null/empty:
* parentNode, children, parentName,level,position
* @param {string} nodeName - The name of the node.
* @param {string} parentName - The name of the parent node.
*/
constructor(nodeName, parentName) {
/** String of Node Name */
this.name = nodeName;

/** String of Parent Name */
this.parentName = parentName;

/** Reference to parent Node Object. */
this.parentNode = null;

/** Array of Children. */
this.children = [];

/** Level of the node. */
this.level = null;

/**
* Position of the node.
* Initialize to -1
*/
this.position = -1;
}

/**
* Add child to current Node.
* @param {Node} childNode - add a child to this node.
*/
addChild(childNode) {
this.children.push(childNode);
}
}
Insert cell
/**
* Class representing a Tree.
*/
class Tree {
/**
* Creates a Tree Object
* Populates a single attribute that contains a list (array) of Node objects to be used by the other functions in this class
* note: Node objects will have a name, parentNode, parentName, children, level, and position
* @param {json[]} json - array of json object with name and parent fields
*/
constructor(json) {
// Create nodes here.
// Assign parents and children.
this.nodes = []
for (const [index, item] of json.entries()) {
var new_node = new Node(item.name, item.parent);
if (index !== 0) {
new_node.parentNode = this.nodes[index - 1]
}
this.nodes.push(new_node);
}
for (var node of this.nodes) {
var node_name = node.name;
node.children = this.nodes.filter(node => node.parentName === node_name);
}
}

/**
* Function that builds a tree from a list of nodes with parent refs
*/
buildLayout() {
this.assignLevelAndPosition(this.nodes[0], 0, 0);
return this;
}

/**
* Assign levels and positions to nodes. The root should be level 0, its children level 1, etc.
*/
assignLevelAndPosition(node, level, position) {
node.level = level
level += 1
node.position = position
if (node.children.length === 0) {
position += 1
} else {
for (var child of node.children) {
child.parentNode = node
position = this.assignLevelAndPosition(child, level, position)
}
}
return position
}

render() {
// Create an SVG and render the tree into it.
var svg = d3.create('svg')
.attr('width', 700)
.attr('height', 700);
// Create axis scales.
const posScale = d3.scaleLinear()
.domain([0, 4])
.range([0, 300])
const levelScale = d3.scaleLinear()
.domain([0, 8])
.range([0, 800])

// Draw edges.
svg.selectAll('line') // This is to make sure that the circle is over the lines, could not find a better way to do it.
.data(this.nodes)
.join("line")
.attr('transform', row => `translate(200,100)`)
.attr("x1", d => levelScale(d.parentNode === null ? 0 : d.parentNode.level))
.attr("y1", d => posScale(d.parentNode === null ? 0 : d.parentNode.position))
.attr("x2", d => levelScale(d.level))
.attr("y2", d => posScale(d.position));

// For nodes to be drawn on.
const g = svg.selectAll('g')
.data(this.nodes)
.join('g')
.attr('transform', row => `translate(200, 100)`);

// Draw nodes.
const node_radius = 35
g.classed("nodeGroup", true).append('circle')
.attr('cx', node => levelScale(node.level))
.attr('cy', node => posScale(node.position))
.attr('r', node_radius)
.attr("text", node => node.name);

// Add text.
g.classed("label", true).append('text')
.attr('x', node => levelScale(node.level))
.attr('y', node => posScale(node.position))
.text(node => node.name)
return svg.node();
}
}
Insert cell
Insert cell
// Uncommenting the line below should return a list of nodes with their parent and children assigned.
new Tree(data).nodes
Insert cell
Insert cell
// Uncommenting the line below should return a list of nodes with their position and layers assigned.
new Tree(data).buildLayout().nodes
Insert cell
Insert cell
// Uncommenting the line below should render the tree as an SVG.
new Tree(data).buildLayout().render()
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more