You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
295 lines
7.7 KiB
295 lines
7.7 KiB
/**
|
|
*
|
|
*/
|
|
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 controls = this.initControls();
|
|
parent.querySelector('.bottom').appendChild(controls);
|
|
|
|
var comments = this.initComments();
|
|
parent.querySelector('.bottom').appendChild(comments);
|
|
|
|
var range = this.initRange();
|
|
parent.querySelector('.bottom').appendChild(range);
|
|
|
|
this.groups = null;
|
|
this.parent = parent;
|
|
this.sorter = null;
|
|
|
|
switch(parent.attributes['data-algorithm'].value) {
|
|
case 'quick':
|
|
this.sorter = new QuickSort();
|
|
break;
|
|
|
|
case 'merge':
|
|
this.sorter = new MergeSort();
|
|
break;
|
|
|
|
case 'selection':
|
|
this.sorter = new SelectionSort();
|
|
break;
|
|
|
|
case 'bubble':
|
|
this.sorter = new BubbleSort();
|
|
break;
|
|
|
|
case 'insertion':
|
|
this.sorter = new InsertionSort();
|
|
break;
|
|
|
|
case 'shell':
|
|
this.sorter = new ShellSort();
|
|
break;
|
|
|
|
case 'radix':
|
|
this.sorter = new RadixSort();
|
|
break;
|
|
|
|
default:
|
|
throw new Error('Unrecognized sort type.');
|
|
}
|
|
|
|
// this.instructions = null;
|
|
// this.currentInstruction = 0;
|
|
|
|
this.init(10);
|
|
};
|
|
|
|
// Public static properties (mutable)
|
|
Visualizer.spacerW = 5;
|
|
Visualizer.itemW = 14;
|
|
Visualizer.itemH = 50;
|
|
Visualizer.itemY = 20;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
Visualizer.prototype.init = function(n) {
|
|
var data = this.sorter.generate(n);
|
|
var shuffled = this.sorter.shuffle(data);
|
|
var ordered = Object.create(shuffled);
|
|
ordered = this.sorter.sort(ordered, 0, ordered.length - 1);
|
|
|
|
// console.log(ordered);
|
|
|
|
// 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)`;
|
|
// });
|
|
|
|
// 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.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.initComments = function() {
|
|
var container = document.createElement('div');
|
|
container.className = 'comment-container';
|
|
|
|
var div1 = document.createElement('div');
|
|
div1.className = 'comment';
|
|
|
|
var div2 = document.createElement('div');
|
|
div2.className = 'comment';
|
|
|
|
var div3 = document.createElement('div');
|
|
div3.className = 'comment';
|
|
|
|
container.appendChild(div1);
|
|
container.appendChild(div2);
|
|
container.appendChild(div3);
|
|
|
|
return container;
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
Visualizer.prototype.initRange = function() {
|
|
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';
|
|
|
|
container.appendChild(reset);
|
|
container.appendChild(back);
|
|
container.appendChild(play);
|
|
container.appendChild(forward);
|
|
|
|
return container;
|
|
};
|
|
|