parent
9101efa9f7
commit
be5c97a897
4 changed files with 281 additions and 140 deletions
@ -1,2 +1,3 @@ |
|||||||
vendor |
vendor |
||||||
node_modules |
node_modules |
||||||
|
.todo |
||||||
|
@ -0,0 +1,79 @@ |
|||||||
|
/** |
||||||
|
* |
||||||
|
*/ |
||||||
|
var Sorter = function() {}; |
||||||
|
Sorter.prototype.instructions = []; |
||||||
|
|
||||||
|
// NOTE fisher-yates, http://bost.ocks.org/mike/algorithms/
|
||||||
|
/** |
||||||
|
* |
||||||
|
*/ |
||||||
|
Sorter.prototype.shuffle = function(arr) { |
||||||
|
var n = arr.length, t, i; |
||||||
|
while (n) { |
||||||
|
i = Math.random() * n-- | 0; // 0 ≤ i < n
|
||||||
|
t = arr[n]; |
||||||
|
arr[n] = arr[i]; |
||||||
|
arr[i] = t; |
||||||
|
} |
||||||
|
return arr; |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
*/ |
||||||
|
Sorter.prototype.addInstruction = function(operation, left, right, pivot) { |
||||||
|
this.instructions.push({ |
||||||
|
operation: operation, |
||||||
|
left: left, |
||||||
|
right: right, |
||||||
|
pivot: pivot |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
// NOTE adds to an instruction set
|
||||||
|
/** |
||||||
|
* |
||||||
|
*/ |
||||||
|
Sorter.prototype.quicksort = function(arr, start, end) { |
||||||
|
if (end - start <= 0) { |
||||||
|
this.addInstruction('single', start, end, null); |
||||||
|
return this.visualizer; |
||||||
|
} |
||||||
|
|
||||||
|
var left = start; |
||||||
|
var right = end; |
||||||
|
|
||||||
|
var pivot = Math.floor((right + left) / 2); |
||||||
|
var pivotval = arr[pivot].val; |
||||||
|
var tmp; |
||||||
|
|
||||||
|
this.addInstruction('init', left, right, pivot); |
||||||
|
|
||||||
|
while (left <= right) { |
||||||
|
while (arr[left].val < pivotval) { |
||||||
|
left++; |
||||||
|
this.addInstruction('inc-left', left, right, pivot); |
||||||
|
} |
||||||
|
|
||||||
|
while (arr[right].val > pivotval) { |
||||||
|
right--; |
||||||
|
this.addInstruction('dec-right', left, right, pivot); |
||||||
|
} |
||||||
|
|
||||||
|
if (left <= right) { |
||||||
|
tmp = arr[left]; |
||||||
|
arr[left] = arr[right]; |
||||||
|
arr[right] = tmp; |
||||||
|
|
||||||
|
this.addInstruction('swap', left, right, pivot); |
||||||
|
|
||||||
|
left++; |
||||||
|
right--; |
||||||
|
this.addInstruction('swap-inc-dec', left, right, pivot); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
this.quicksort(arr, start, right); |
||||||
|
this.quicksort(arr, left, end); |
||||||
|
}; |
@ -0,0 +1,178 @@ |
|||||||
|
/** |
||||||
|
* |
||||||
|
*/ |
||||||
|
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); |
||||||
|
} |
||||||
|
}; |
Loading…
Reference in new issue