/** * */ var Visualizer = function() { this.itemW = 20; this.itemH = 50; this.svg = null; this.instructions = null; }; Visualizer.prototype.svg = null; Visualizer.prototype.instructions = null; /** * */ Visualizer.prototype.init = function(data) { // 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 data) { data[i].index = n++; } this.svg = d3.select('body').append('svg') .attr('class', 'quicksort-random'); this.svg.selectAll('rect').data(data).enter() .append('rect') .attr('height', function h(d) { return d.h; }) .attr('width', function w(d) { return d.w; }) .attr('fill', function fill(d) { return d.fill; }) .attr('y', function y(d) { return d.y; }) .attr('x', function x(d) { return d.x; }) .append('text') .text(function t(d) { return d.val; }) .attr('fill', '#000') .attr('x', function x(d) { return d.x; }) .transition(500) .attr('x', function expand(d, i) { data[i].x = i * (d.w + 2); return data[i].x; }); // 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 }; /** * */ 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 this.svg.selectAll('rect') // .transition() // .duration(100) // .attr('y', function ya(d) { // if (d.index === indexA || d.index === indexB) { // d.y = 5; // } // return d.y; // NOTE d[i].y has been modified too. But, the bound value in the dataset // // will not be updated until after the chain is completed, so it has to be explicitly defined here. // }) // Switch places // NOTE discuss chained transitions: http://bl.ocks.org/mbostock/1125997 // TODO now that elements aren't cached, is there a need for .index? .transition() .duration(400) .attr('x', function xa(d) { if (d.index === indexA) { d.index = indexB; d.x = d.index * (d.w + 2); } else if (d.index === indexB) { d.index = indexA; d.x = d.index * (d.w + 2); } return d.x; }) // Move down // .transition() // .duration(100) // .attr('y', function ya(d) { // if (d.index === indexA || d.index === indexB) { // d.y = 20; // } // return d.y; // }); }; /** * */ 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', 0) .attr('y', 10) .attr('width', 20) .attr('height', 20) .attr('fill', '#333') .attr('font-size', 20) .attr('alignment-baseline', 'middle') .attr('text-anchor', 'middle') .attr('transform', 'rotate(90 10,10)') .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 || index > 25) { 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); } };