perf: Optimise Rendering

- optimise get_children function

- use promises instead of callbacks

- optimise selectors

- use const wherever possible

- use pure js instead of jquery for connectors for faster rendering
diff --git a/erpnext/hr/page/organizational_chart/organizational_chart.py b/erpnext/hr/page/organizational_chart/organizational_chart.py
index 77b8df7..46578f3 100644
--- a/erpnext/hr/page/organizational_chart/organizational_chart.py
+++ b/erpnext/hr/page/organizational_chart/organizational_chart.py
@@ -2,33 +2,25 @@
 import frappe
 
 @frappe.whitelist()
-def get_children(parent=None, company=None, exclude_node=None, is_root=False, is_tree=False, fields=None):
+def get_children(parent=None, company=None):
 
 	filters = [['status', '!=', 'Left']]
 	if company and company != 'All Companies':
 		filters.append(['company', '=', company])
 
-	if not fields:
-		fields = ['employee_name as name', 'name as id', 'reports_to', 'image', 'designation as title']
-
-	if is_root:
-		parent = ''
-
-	if exclude_node:
-		filters.append(['name', '!=', exclude_node])
-
 	if parent and company and parent != company:
 		filters.append(['reports_to', '=', parent])
 	else:
 		filters.append(['reports_to', '=', ''])
 
-	employees = frappe.get_list('Employee', fields=fields,
-		filters=filters, order_by='name')
+	employees = frappe.get_list('Employee',
+		fields=['employee_name as name', 'name as id', 'reports_to', 'image', 'designation as title'],
+		filters=filters,
+		order_by='name'
+	)
 
 	for employee in employees:
-		is_expandable = frappe.get_all('Employee', filters=[
-			['reports_to', '=', employee.get('id')]
-		])
+		is_expandable = frappe.db.count('Employee', filters={'reports_to': employee.get('id')})
 		employee.connections = get_connections(employee.id)
 		employee.expandable = 1 if is_expandable else 0
 
diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js
index e89a98a..bf36679 100644
--- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js
+++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js
@@ -7,7 +7,6 @@
 			- this method should return id, name, title, image, and connections for each node
 	*/
 	constructor(doctype, wrapper, method) {
-		this.wrapper = $(wrapper);
 		this.page = wrapper.page;
 		this.method = method;
 		this.doctype = doctype;
@@ -61,6 +60,8 @@
 		frappe.breadcrumbs.add('HR');
 
 		let me = this;
+		if ($(`[data-fieldname="company"]`).length) return;
+
 		let company = this.page.add_field({
 			fieldtype: 'Link',
 			options: 'Company',
@@ -131,32 +132,30 @@
 			method: me.method,
 			args: {
 				company: me.company
-			},
-			callback: function(r) {
-				if (r.message.length) {
-					let nodes = r.message;
-					let node = undefined;
-					let first_root = undefined;
+			}
+		}).then(r => {
+			if (r.message.length) {
+				let node = undefined;
+				let first_root = undefined;
 
-					$.each(nodes, (i, data) => {
-						node = new me.Node({
-							id: data.id,
-							parent: $('<li class="child-node"></li>').appendTo(me.$hierarchy.find('.node-children')),
-							parent_id: undefined,
-							image: data.image,
-							name: data.name,
-							title: data.title,
-							expandable: true,
-							connections: data.connections,
-							is_root: true
-						});
-
-						if (i == 0)
-							first_root = node;
+				$.each(r.message, (i, data) => {
+					node = new me.Node({
+						id: data.id,
+						parent: $('<li class="child-node"></li>').appendTo(me.$hierarchy.find('.node-children')),
+						parent_id: undefined,
+						image: data.image,
+						name: data.name,
+						title: data.title,
+						expandable: true,
+						connections: data.connections,
+						is_root: true
 					});
 
-					me.expand_node(first_root);
-				}
+					if (i == 0)
+						first_root = node;
+				});
+
+				me.expand_node(first_root);
 			}
 		});
 	}
@@ -204,18 +203,14 @@
 	}
 
 	get_child_nodes(node_id) {
-		let me = this;
 		return new Promise(resolve => {
 			frappe.call({
 				method: this.method,
 				args: {
 					parent: node_id,
-					company: me.company
-				},
-				callback: (r) => {
-					resolve(r.message);
+					company: this.company
 				}
-			});
+			}).then(r => resolve(r.message));
 		});
 	}
 
@@ -266,27 +261,28 @@
 	}
 
 	add_connector(parent_id, child_id) {
+		// using pure javascript for better performance
 		const parent_node = document.querySelector(`#${parent_id}`);
 		const child_node = document.querySelector(`#${child_id}`);
 
 		let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
 
 		// we need to connect right side of the parent to the left side of the child node
-		let pos_parent_right = {
+		const pos_parent_right = {
 			x: parent_node.offsetLeft + parent_node.offsetWidth,
 			y: parent_node.offsetTop + parent_node.offsetHeight / 2
 		};
-		let pos_child_left = {
+		const pos_child_left = {
 			x: child_node.offsetLeft - 5,
 			y: child_node.offsetTop + child_node.offsetHeight / 2
 		};
 
-		let connector = this.get_connector(pos_parent_right, pos_child_left);
+		const connector = this.get_connector(pos_parent_right, pos_child_left);
 
 		path.setAttribute('d', connector);
 		this.set_path_attributes(path, parent_id, child_id);
 
-		$('#connectors').append(path);
+		document.getElementById('connectors').appendChild(path);
 	}
 
 	get_connector(pos_parent_right, pos_child_left) {
@@ -330,12 +326,13 @@
 	set_path_attributes(path, parent_id, child_id) {
 		path.setAttribute("data-parent", parent_id);
 		path.setAttribute("data-child", child_id);
+		const parent = $(`#${parent_id}`);
 
-		if ($(`#${parent_id}`).hasClass('active')) {
+		if (parent.hasClass('active')) {
 			path.setAttribute("class", "active-connector");
 			path.setAttribute("marker-start", "url(#arrowstart-active)");
 			path.setAttribute("marker-end", "url(#arrowhead-active)");
-		} else if ($(`#${parent_id}`).hasClass('active-path')) {
+		} else if (parent.hasClass('active-path')) {
 			path.setAttribute("class", "collapsed-connector");
 			path.setAttribute("marker-start", "url(#arrowstart-collapsed)");
 			path.setAttribute("marker-end", "url(#arrowhead-collapsed)");
@@ -343,8 +340,9 @@
 	}
 
 	set_selected_node(node) {
-		// remove .active class from the current node
-		$('.active').removeClass('active');
+		// remove active class from the current node
+		if (this.selected_node)
+			this.selected_node.$link.removeClass('active');
 
 		// add active class to the newly selected node
 		this.selected_node = node;
@@ -411,9 +409,9 @@
 	}
 
 	remove_levels_after_node(node) {
-		let level = $(`#${node.id}`).parent().parent().parent();
+		let level = $(`#${node.id}`).parent().parent().parent().index();
 
-		level = $('.hierarchy > li:eq('+ level.index() + ')');
+		level = $('.hierarchy > li:eq('+ level + ')');
 		level.nextAll('li').remove();
 
 		let nodes = level.find('.node-card');
@@ -431,8 +429,8 @@
 	remove_orphaned_connectors() {
 		let paths = $('#connectors > path');
 		$.each(paths, (_i, path) => {
-			let parent = $(path).data('parent');
-			let child = $(path).data('child');
+			const parent = $(path).data('parent');
+			const child = $(path).data('child');
 
 			if ($(`#${parent}`).length && $(`#${child}`).length)
 				return;
diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js
index 5eee27b..17062e2 100644
--- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js
+++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js
@@ -7,7 +7,6 @@
 			- this method should return id, name, title, image, and connections for each node
 	*/
 	constructor(doctype, wrapper, method) {
-		this.wrapper = $(wrapper);
 		this.page = wrapper.page;
 		this.method = method;
 		this.doctype = doctype;
@@ -63,6 +62,8 @@
 		frappe.breadcrumbs.add('HR');
 
 		let me = this;
+		if ($(`[data-fieldname="company"]`).length) return;
+
 		let company = this.page.add_field({
 			fieldtype: 'Link',
 			options: 'Company',
@@ -139,24 +140,21 @@
 			args: {
 				company: me.company
 			},
-			callback: function(r) {
-				if (r.message.length) {
-					let nodes = r.message;
-
-					$.each(nodes, (_i, data) => {
-						return new me.Node({
-							id: data.id,
-							parent: me.$hierarchy.find('.root-level'),
-							parent_id: undefined,
-							image: data.image,
-							name: data.name,
-							title: data.title,
-							expandable: true,
-							connections: data.connections,
-							is_root: true
-						});
+		}).then(r => {
+			if (r.message.length) {
+				$.each(r.message, (_i, data) => {
+					return new me.Node({
+						id: data.id,
+						parent: me.$hierarchy.find('.root-level'),
+						parent_id: undefined,
+						image: data.image,
+						name: data.name,
+						title: data.title,
+						expandable: true,
+						connections: data.connections,
+						is_root: true
 					});
-				}
+				});
 			}
 		});
 	}
@@ -237,11 +235,8 @@
 					parent: node_id,
 					company: me.company,
 					exclude_node: exclude_node
-				},
-				callback: (r) => {
-					resolve(r.message);
 				}
-			});
+			}).then(r => resolve(r.message));
 		});
 	}
 
@@ -286,10 +281,10 @@
 	}
 
 	add_connector(parent_id, child_id) {
-		let parent_node = document.querySelector(`#${parent_id}`);
-		let child_node = document.querySelector(`#${child_id}`);
+		const parent_node = document.querySelector(`#${parent_id}`);
+		const child_node = document.querySelector(`#${child_id}`);
 
-		let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
+		const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
 
 		let connector = undefined;
 
@@ -299,10 +294,10 @@
 			connector = this.get_connector_for_collapsed_node(parent_node, child_node);
 		}
 
-		path.setAttribute("d", connector);
+		path.setAttribute('d', connector);
 		this.set_path_attributes(path, parent_id, child_id);
 
-		$('#connectors').append(path);
+		document.getElementById('connectors').appendChild(path);
 	}
 
 	get_connector_for_active_node(parent_node, child_node) {
@@ -351,19 +346,21 @@
 	set_path_attributes(path, parent_id, child_id) {
 		path.setAttribute("data-parent", parent_id);
 		path.setAttribute("data-child", child_id);
+		const parent = $(`#${parent_id}`);
 
-		if ($(`#${parent_id}`).hasClass('active')) {
+		if (parent.hasClass('active')) {
 			path.setAttribute("class", "active-connector");
 			path.setAttribute("marker-start", "url(#arrowstart-active)");
 			path.setAttribute("marker-end", "url(#arrowhead-active)");
-		} else if ($(`#${parent_id}`).hasClass('active-path')) {
+		} else if (parent.hasClass('active-path')) {
 			path.setAttribute("class", "collapsed-connector");
 		}
 	}
 
 	set_selected_node(node) {
 		// remove .active class from the current node
-		$('.active').removeClass('active');
+		if (this.selected_node)
+			this.selected_node.$link.removeClass('active');
 
 		// add active class to the newly selected node
 		this.selected_node = node;
@@ -494,9 +491,9 @@
 	}
 
 	remove_levels_after_node(node) {
-		let level = $(`#${node.id}`).parent().parent();
+		let level = $(`#${node.id}`).parent().parent().index();
 
-		level = $('.hierarchy-mobile > li:eq('+ (level.index()) + ')');
+		level = $('.hierarchy-mobile > li:eq('+ level + ')');
 		level.nextAll('li').remove();
 
 		let current_node = level.find(`#${node.id}`);
@@ -512,8 +509,8 @@
 	remove_orphaned_connectors() {
 		let paths = $('#connectors > path');
 		$.each(paths, (_i, path) => {
-			let parent = $(path).data('parent');
-			let child = $(path).data('child');
+			const parent = $(path).data('parent');
+			const child = $(path).data('child');
 
 			if ($(`#${parent}`).length && $(`#${child}`).length)
 				return;