diff --git a/algorithm/tree/binary_search_tree/bst_insert/code.js b/algorithm/tree/binary_search_tree/bst_insert/code.js index 8dad4b60..f2a2479e 100644 --- a/algorithm/tree/binary_search_tree/bst_insert/code.js +++ b/algorithm/tree/binary_search_tree/bst_insert/code.js @@ -1,34 +1,32 @@ -function bst_insert ( root, element, parent ) { // node = current node , parent = previous node +function bst_insert ( root, element, parent ) { // root = current node , parent = previous node tracer._visit ( root, parent )._wait (); + var treeNode = T[root]; + var propName = ''; if ( element < root ) { - if ( T[root][0] === -1 ) { // insert as left child of root - T[root][0] = element; - G[root][element] = 1; // update in graph - tracer._visit ( element, root )._wait (); + propName = 'left'; + } else if ( element > root) { + propName = 'right'; + } + if(propName !== '') { + if ( !treeNode.hasOwnProperty(propName) ) { // insert as left child of root + treeNode[propName] = element; + T[element] = {}; + tracer._addNode(element, root)._wait(); logger._print( element + ' Inserted '); } else { - //tracer._clear (); - bst_insert ( T[root][0], element, root ); - } - } else if ( element > root ) { // insert as right child of root - if( T[root][1] === -1 ) { - T[root][1] = element; - G[root][element] = 1; // update in graph - tracer._visit ( element, root )._wait (); - logger._print ( element + ' Inserted '); - } else { - //tracer._clear (); - bst_insert ( T[root][1], element, root ); + bst_insert ( treeNode[propName], element, root ); } } } var Root = elements[0]; // take first element as root +T[Root] = {}; +tracer._addRoot(Root); logger._print ( Root + ' Inserted as root of tree '); -tracer._setTreeData ( G, Root ); for (var i = 1; i < elements.length; i++) { tracer2._select ( i )._wait(); bst_insert ( Root, elements[i] ); // insert ith element - tracer2._deselect( i ); + tracer2._deselect( i )._wait(); + tracer._clearTraversal(); } diff --git a/algorithm/tree/binary_search_tree/bst_insert/data.js b/algorithm/tree/binary_search_tree/bst_insert/data.js index d062c779..f7f3e256 100644 --- a/algorithm/tree/binary_search_tree/bst_insert/data.js +++ b/algorithm/tree/binary_search_tree/bst_insert/data.js @@ -1,34 +1,7 @@ -var G = [ // G[i][j] indicates whether the path from the i-th node to the j-th node exists or not - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] -]; - - -var T = [ // mapping to G as a binary tree , [i][0] indicates left child, [i][1] indicates right child - [-1,-1], - [-1,-1], - [-1,-1], - [-1,-1], - [-1,-1], - [-1,-1], - [-1,-1], - [-1,-1], - [-1,-1], - [-1,-1], - [-1,-1] -]; +var T = {}; var elements = [5,8,10,3,1,6,9,7,2,0,4]; // item to be searched -var tracer = new DirectedGraphTracer( " BST - Elements marked red indicates the current status of tree "); +var tracer = new DirectedGraphConstructTracer( " BST - Elements marked red indicates the current status of tree ", 0); var tracer2 = new Array1DTracer ( " Elements ")._setData ( elements ); var logger = new LogTracer ( " Log "); tracer.attach ( logger ); \ No newline at end of file diff --git a/js/module/tracer/directed_graph_construct.js b/js/module/tracer/directed_graph_construct.js new file mode 100644 index 00000000..8ccd4e81 --- /dev/null +++ b/js/module/tracer/directed_graph_construct.js @@ -0,0 +1,479 @@ +'use strict'; + +const Tracer = require('./tracer'); + +const { + refineByType +} = require('../../tracer_manager/util/index'); + +class DirectedGraphConstructTracer extends Tracer { + static getClassName() { + return 'DirectedGraphConstructTracer'; + } + + constructor(name, nodePlacement = null) { + super(name); + this.nodePlacement = nodePlacement; + this.nodeCollection = []; + if (this.isNew) initView(this); + } + + _addRoot(root) { + this.manager.pushStep(this.capsule, { + type: 'addRoot', + arguments: arguments + }); + return this; + } + + _addNode(element, parentElement = null) { + this.manager.pushStep(this.capsule, { + type: 'addNode', + arguments: arguments + }); + return this; + } + + _findNode(val) { + var idToFind = this.n(val); + var G = this.nodeCollection; + var result = null; + for (let i = 0; i < G.length; i++) { + if(G[i].id === idToFind) { + result = G[i]; + break; + } + } + return result; + } + + _visit(target, source) { + this.manager.pushStep(this.capsule, { + type: 'visit', + target: target, + source: source + }); + return this; + } + + _leave(target, source) { + this.manager.pushStep(this.capsule, { + type: 'leave', + target: target, + source: source + }); + return this; + } + + _setNodePositions(positions) { + this.manager.pushStep(this.capsule, { + type: 'setNodePositions', + positions: positions + }); + return this; + } + + _clearTraversal() { + this.manager.pushStep(this.capsule, { + type: 'clear' + }); + return this; + } + + processStep(step, options) { + switch (step.type) { + case 'clear': + this.clear.apply(this); + break; + case 'setNodePositions': + $.each(this.graph.nodes(), (i, node) => { + if (i >= step.positions.length) return false; + const position = step.positions[i]; + node.x = position.x; + node.y = position.y; + }); + break; + case 'addRoot': + this.addRoot.apply(this, step.arguments); + break; + case 'addNode': + this.addNode.apply(this, step.arguments); + break; + case 'visit': + case 'leave': + var visit = step.type == 'visit'; + var nodeObject = this._findNode(step.target); + nodeObject.visited = visit; + nodeObject.isNew = false; + var targetNode = this.graph.nodes(this.n(step.target)); + var color = visit ? this.color.visited : this.color.left; + if(targetNode) { + targetNode.color = color; + if (step.source !== undefined) { + var edgeId = this.e(step.source, step.target); + var edge = this.graph.edges(edgeId); + edge.color = color; + this.graph.dropEdge(edgeId).addEdge(edge); + } + } + if (this.logTracer) { + var source = step.source; + if (source === undefined) source = ''; + this.logTracer.print(visit ? source + ' -> ' + step.target : source + ' <- ' + step.target); + } + break; + default: + super.processStep(step, options); + } + } + + addRoot(root) { + if(this.rootObject) throw 'Root for this graph is already added'; + this.rootObject = this.createGraphNode(root); + this.drawGraph(this.rootObject.level); + } + + addNode(node, parent) { + var nodeObject = this.createGraphNode(node, parent) + this.drawGraph(nodeObject.level); + } + + createGraphNode(node, parent) { + var nodeObject = this.nodeConstruct(node); + var parentObject = this._findNode(parent); + if (parentObject) { + nodeObject.parent = parentObject; + nodeObject.level = parentObject.level + 1; + if (this.nodePlacement === null) { + parentObject.children.push(nodeObject); + } else if (this.nodePlacement === 0) { + var isSpliced = false; + var insertIndex = 0; + if (parentObject.children.length > 0) { + for(let i = 0; i < parentObject.children.length; i++) { + var child = parentObject.children[i]; + if(child.originalVal > node) { + isSpliced = true; + break; + } + insertIndex++; + } + } + if(isSpliced) { + parentObject.children.splice(insertIndex, 0, nodeObject); + } else { + parentObject.children.push(nodeObject); + } + } + } + this.nodeCollection.push(nodeObject); + return nodeObject; + } + + nodeConstruct(val) { + var nodeObject = { + id: this.n(val), + originalVal: val, + isNew: true, + visited: false, + children: [], + level: 1, + parent: null + } + return nodeObject; + } + + drawGraph(nodeLevel) { + const nodes = []; + const edges = []; + var tracer = this; + + var arrangeChildNodes = function(node, offsetWidth) { + if(node.children.length > 1){ + var midPoint = Math.floor(node.children.length / 2); + for (let i = 0; i < node.children.length; i++) { + if (i===midPoint) { + offsetWidth += (node.children.length % 2 === 0 ? 1 : 0); + addGraphNode(node, offsetWidth); + } + offsetWidth = arrangeChildNodes(node.children[i], offsetWidth); + addEdge(node, node.children[i]); + } + } else { + if (node.children.length === 0) { + offsetWidth += 1; + } else { + offsetWidth = arrangeChildNodes(node.children[0], offsetWidth); + addEdge(node, node.children[0]); + } + addGraphNode(node, offsetWidth); + } + return offsetWidth; + }; + + var addGraphNode = function (node, calculatedX) { + var color = getColor(node.isNew, node.visited, tracer.color); + nodes.push({ + id: node.id, + label: '' + node.originalVal, + x: calculatedX, + y: node.level - 1, + size: 1, + color: color, + weight: 0 + }); + }; + + var addEdge = function (node, childNode) { + var color = getColor(node.visited && childNode.isNew, node.visited && childNode.visited, tracer.color); + edges.push({ + id: tracer.e(node.originalVal, childNode.originalVal), + source: node.id, + target: childNode.id, + color: color, + size: 1, + weight: refineByType(childNode.originalVal) + }); + }; + + var getColor = function (isNew, isVisited, colorPalete) { + return isNew ? colorPalete.selected : + (isVisited ? colorPalete.visited : colorPalete.default); + }; + arrangeChildNodes(this.rootObject, 0); + + this.graph.clear(); + this.graph.read({ + nodes: nodes, + edges: edges + }); + this.s.camera.goTo({ + x: 0, + y: nodeLevel, + angle: 0, + ratio: 1 + }); + this.refresh(); + + return false; + } + + resize() { + super.resize(); + + this.s.renderers[0].resize(); + this.refresh(); + } + + refresh() { + super.refresh(); + + this.s.refresh(); + } + + clear() { + super.clear(); + + this.clearGraphColor(); + this.refresh(); + } + + clearGraphColor() { + var tracer = this; + this.nodeCollection.forEach(function(node){ + node.visited = node.isNew = false; + }); + + this.graph.nodes().forEach(function (node) { + node.color = tracer.color.default; + }); + this.graph.edges().forEach(function (edge) { + edge.color = tracer.color.default; + }); + } + + n(v) { + return 'n' + v; + } + + e(v1, v2) { + return 'e' + v1 + '_' + v2; + } + + getColor(edge, source, target, settings) { + var color = edge.color, + edgeColor = settings('edgeColor'), + defaultNodeColor = settings('defaultNodeColor'), + defaultEdgeColor = settings('defaultEdgeColor'); + if (!color) + switch (edgeColor) { + case 'source': + color = source.color || defaultNodeColor; + break; + case 'target': + color = target.color || defaultNodeColor; + break; + default: + color = defaultEdgeColor; + break; + } + + return color; + } + + drawLabel(node, context, settings) { + var fontSize, + prefix = settings('prefix') || '', + size = node[prefix + 'size']; + + if (size < settings('labelThreshold')) + return; + + if (!node.label || typeof node.label !== 'string') + return; + + fontSize = (settings('labelSize') === 'fixed') ? + settings('defaultLabelSize') : + settings('labelSizeRatio') * size; + + context.font = (settings('fontStyle') ? settings('fontStyle') + ' ' : '') + + fontSize + 'px ' + settings('font'); + context.fillStyle = (settings('labelColor') === 'node') ? + (node.color || settings('defaultNodeColor')) : + settings('defaultLabelColor'); + + context.textAlign = 'center'; + context.fillText( + node.label, + Math.round(node[prefix + 'x']), + Math.round(node[prefix + 'y'] + fontSize / 3) + ); + } + + drawArrow(edge, source, target, color, context, settings) { + var prefix = settings('prefix') || '', + size = edge[prefix + 'size'] || 1, + tSize = target[prefix + 'size'], + sX = source[prefix + 'x'], + sY = source[prefix + 'y'], + tX = target[prefix + 'x'], + tY = target[prefix + 'y'], + angle = Math.atan2(tY - sY, tX - sX), + dist = 3; + sX += Math.sin(angle) * dist; + tX += Math.sin(angle) * dist; + sY += -Math.cos(angle) * dist; + tY += -Math.cos(angle) * dist; + var aSize = Math.max(size * 2.5, settings('minArrowSize')), + d = Math.sqrt(Math.pow(tX - sX, 2) + Math.pow(tY - sY, 2)), + aX = sX + (tX - sX) * (d - aSize - tSize) / d, + aY = sY + (tY - sY) * (d - aSize - tSize) / d, + vX = (tX - sX) * aSize / d, + vY = (tY - sY) * aSize / d; + + context.strokeStyle = color; + context.lineWidth = size; + context.beginPath(); + context.moveTo(sX, sY); + context.lineTo( + aX, + aY + ); + context.stroke(); + + context.fillStyle = color; + context.beginPath(); + context.moveTo(aX + vX, aY + vY); + context.lineTo(aX + vY * 0.6, aY - vX * 0.6); + context.lineTo(aX - vY * 0.6, aY + vX * 0.6); + context.lineTo(aX + vX, aY + vY); + context.closePath(); + context.fill(); + } + + drawOnHover(node, context, settings, next) { + var tracer = this; + + context.setLineDash([5, 5]); + var nodeIdx = node.id.substring(1); + this.graph.edges().forEach(function (edge) { + var ends = edge.id.substring(1).split("_"); + if (ends[0] == nodeIdx) { + var color = '#0ff'; + var source = node; + var target = tracer.graph.nodes('n' + ends[1]); + tracer.drawArrow(edge, source, target, color, context, settings); + if (next) next(edge, source, target, color, context, settings); + } else if (ends[1] == nodeIdx) { + var color = '#ff0'; + var source = tracer.graph.nodes('n' + ends[0]); + var target = node; + tracer.drawArrow(edge, source, target, color, context, settings); + if (next) next(edge, source, target, color, context, settings); + } + }); + } +} + +const initView = (tracer) => { + tracer.s = tracer.capsule.s = new sigma({ + renderer: { + container: tracer.$container[0], + type: 'canvas' + }, + settings: { + minArrowSize: 8, + defaultEdgeType: 'arrow', + maxEdgeSize: 2.5, + labelThreshold: 4, + font: 'Roboto', + defaultLabelColor: '#fff', + zoomMin: 0.6, + zoomMax: 1.2, + skipErrors: true, + minNodeSize: .5, + maxNodeSize: 12, + labelSize: 'proportional', + labelSizeRatio: 1.3, + funcLabelsDef(node, context, settings) { + tracer.drawLabel(node, context, settings); + }, + funcHoversDef(node, context, settings, next) { + tracer.drawOnHover(node, context, settings, next); + }, + funcEdgesArrow(edge, source, target, context, settings) { + var color = tracer.getColor(edge, source, target, settings); + tracer.drawArrow(edge, source, target, color, context, settings); + } + } + }); + sigma.plugins.dragNodes(tracer.s, tracer.s.renderers[0]); + tracer.graph = tracer.capsule.graph = tracer.s.graph; +}; + +sigma.canvas.labels.def = function (node, context, settings) { + var func = settings('funcLabelsDef'); + if (func) { + func(node, context, settings); + } +}; +sigma.canvas.hovers.def = function (node, context, settings) { + var func = settings('funcHoversDef'); + if (func) { + func(node, context, settings); + } +}; +sigma.canvas.edges.def = function (edge, source, target, context, settings) { + var func = settings('funcEdgesDef'); + if (func) { + func(edge, source, target, context, settings); + } +}; +sigma.canvas.edges.arrow = function (edge, source, target, context, settings) { + var func = settings('funcEdgesArrow'); + if (func) { + func(edge, source, target, context, settings); + } +}; + +module.exports = DirectedGraphConstructTracer; diff --git a/js/module/tracer/index.js b/js/module/tracer/index.js index 45f82759..d3605202 100644 --- a/js/module/tracer/index.js +++ b/js/module/tracer/index.js @@ -7,6 +7,7 @@ const Array2DTracer = require('./array2d'); const ChartTracer = require('./chart'); const CoordinateSystemTracer = require('./coordinate_system'); const DirectedGraphTracer = require('./directed_graph'); +const DirectedGraphConstructTracer = require('./directed_graph_construct'); const UndirectedGraphTracer = require('./undirected_graph'); const WeightedDirectedGraphTracer = require('./weighted_directed_graph'); const WeightedUndirectedGraphTracer = require('./weighted_undirected_graph'); @@ -19,6 +20,7 @@ module.exports = { ChartTracer, CoordinateSystemTracer, DirectedGraphTracer, + DirectedGraphConstructTracer, UndirectedGraphTracer, WeightedDirectedGraphTracer, WeightedUndirectedGraphTracer