/** * */ function Visualizer(parent) { var top = document.createElement('div'); top.className = 'top'; parent.appendChild(top); var bottom = document.createElement('div'); bottom.className = 'bottom'; parent.appendChild(bottom); var range = this.initRange(); parent.querySelector('.bottom').appendChild(range); var controls = this.initControls(); parent.querySelector('.bottom').appendChild(controls); this.groups = null; this.parent = parent; this.init(10); // this.instructions = null; // this.currentInstruction = 0; }; // Public static properties (mutable) Visualizer.spacerW = 5; Visualizer.itemW = 14; Visualizer.itemH = 50; Visualizer.itemY = 20; /** * */ Visualizer.prototype.init = function(n) { // Sorter setup. var QS = new Quicksort(); var data = QS.generate(n); var shuffled = QS.shuffle(data); var ordered = Object.create(shuffled); QS.sort(ordered, 0, ordered.length - 1); var x = [] ordered.forEach(function(obj0) { x.push(obj0.value); }) console.log(x.join(',')); // A swap on the dataset will not take effect until after transition is complete, so custom index is required. var n = 0; for (i in shuffled) { shuffled[i].index = n++; } var svg = d3.select(this.parent.querySelector('.top')).append('svg'); this.groups = svg.selectAll('g').data(shuffled).enter().append('g') .attr('transform', `translate(0, ${Visualizer.itemY})`); this.groups.append('rect') .attr('height', Visualizer.itemH) .attr('width', Visualizer.itemW) .attr('fill', function doFill(d) { return `rgb(0, 0, ${d.value})`; }); this.groups.transition(500) .attr('transform', function doTransform(d, i) { return `translate(${i * (Visualizer.itemW + Visualizer.spacerW)}, ${Visualizer.itemY})`; }); this.groups.append('text') .text(function t(d) { return d.value; }) .attr('fill', '#aaa') .attr('font-size', 10) .attr('font-family', 'sans-serif') .attr('transform', function doTransform(d) { return `rotate(90 0,0), translate(5, -3)`; }); // V.addMarker('quicksort-left-marker'); // V.moveMarker('quicksort-left-marker', 2); // this.svg.append('sometext').id('left-marker'); L // this.svg.append('sometext').id('right-marker'); R // this.svg.append('sometext').id('pivot-marker'); triangle or arrow setTimeout(this.followInstruction.bind(this, QS.instructions, 0), 500); }; /** * */ Visualizer.prototype.swap = function(indexA, indexB) { // Move up // NOTE Two way binding here between dataset and function parameter? // NOTE swapping will not reorder index and i parameter will be off // NOTE discuss chained transitions: http://bl.ocks.org/mbostock/1125997 this.groups .transition().duration(400) .attr('transform', function doTransform(d) { if (d.index === indexA) { d.index = indexB; } else if (d.index === indexB) { d.index = indexA; } return `translate(${d.index * (Visualizer.itemW + Visualizer.spacerW)}, ${Visualizer.itemY})`; }) }; /** * * / Visualizer.prototype.moveMarker = function(id, toIndex) { this.svg.select('#' + id).attr('x', toIndex * 22); }; /** * * / Visualizer.prototype.addMarker = function(id) { // this.svg.append('text') // .attr('id', id) // .attr('x', 53) // .attr('y', 50) // .attr('width', 20) // .attr('height', 20) // .attr('fill', '#aaa') // .attr('font-size', 20) // .attr('alignment-baseline', 'middle') // .attr('text-anchor', 'middle') // .attr('transform', 'rotate(0 53,50)') // .text('P'); }; /** * */ Visualizer.prototype.fade = function(startIndex, endIndex) { console.log(`fading from ${startIndex} to ${endIndex}`) this.svg.selectAll('rect') // NOTE this replaces the fill function reference for each rectangle - key point! .attr('fill', function fill(d) { if (d.index >= startIndex && d.index <= endIndex) { console.log(`${d.index} to ${d.fade}`) return d.fade; } return d.fill; }); }; /** * */ Visualizer.prototype.unfade = function() { this.svg.selectAll('rect') .attr('fill', function fill(d) { console.log(`unfade ${d.index}`) return d.fill; }) }; /** * */ Visualizer.prototype.followInstruction = function(instructions, index) { if (index >= instructions.length) { return; } var i = instructions[index]; console.log(i); if (i.operation === 'swap' && i.left < i.right) { this.swap(i.left, i.right); setTimeout(this.followInstruction.bind(this, instructions, index + 1), 400); } else if (i.operation === 'init') { // this.unfade(); // this.fade(0, i.left - 1); // this.fade(i.right + 1, 1000) this.followInstruction(instructions, index + 1); } else if (i.operation === 'swap') { this.followInstruction(instructions, index + 1); } else { this.followInstruction(instructions, index + 1); } }; /** * */ Visualizer.prototype.initRange = function(Visualizer) { var container = document.createElement('div'); container.className = 'range-container'; var label = document.createElement('div'); label.innerHTML = "n: 40"; var range = document.createElement('input'); range.setAttribute('type', 'range'); range.setAttribute('value', 40); range.setAttribute('min', 5); range.setAttribute('max', 80); range.addEventListener('change', this.rangeChange.bind(this)); range.addEventListener('input', this.rangeInput.bind(null, label)); container.appendChild(range); container.appendChild(label); return container; }; /** * */ Visualizer.prototype.rangeInput = function(label, event) { label.innerHTML = 'Items: ' + event.target.value; }; /** * */ Visualizer.prototype.rangeChange = function(event) { this.init(event.target.value); }; /** * */ Visualizer.prototype.initControls = function() { var play = document.createElement('button'); play.className = 'fa fa-play'; play.title = 'Play' play.addEventListener('click', onclick); var back = document.createElement('button'); back.className = 'fa fa-step-backward'; back.title = 'Step Backward'; back.addEventListener('click', onclick); var forward = document.createElement('button'); forward.className = 'fa fa-step-forward' forward.title = 'Step Forward'; forward.addEventListener('click', onclick); var reset = document.createElement('button'); reset.className = 'fa fa-fast-backward'; reset.title = 'Restart' reset.addEventListener('click', onclick); var container = document.createElement('div'); container.className = 'controls'; container.appendChild(reset); container.appendChild(back); container.appendChild(play); container.appendChild(forward); return container; };