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

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more