Instruction set finalized. Visualizer split into different files.

master
ben-burlingham 10 years ago
parent 397691a463
commit 89a20ac073
  1. 84
      index.html
  2. 2
      js/bubblesort.js
  3. 2
      js/insertionsort.js
  4. 46
      js/quicksort.js
  5. 4
      js/selectionsort.js
  6. 2
      js/shellsort.js
  7. 125
      js/visualizer-actions.js
  8. 132
      js/visualizer-inits.js
  9. 221
      js/visualizer.js

@ -13,7 +13,6 @@
}
svg {
border:10px solid dodgerblue;
width:940px;
}
@ -24,37 +23,66 @@
width:960px;
}
.slider {
.range-container {
display:inline-block;
height:100%;
text-align:right;
vertical-align:top;
width:25%;
}
.range-container input {
height:50px;
width:80%;
}
.comment-container {
background:khaki;
display:inline-block;
height:100%;
vertical-align:top;
width:50%;
}
.msg {
font-size:13px;
font-weight:bold;
line-height:30px;
}
.top {
background:#f88;
}
.bottom {
background:#4e0;
height:100px;
}
button {
border:1px solid #333;
background:#888;
padding:3px;
.controls-container {
display:inline-block;
height:100%;
vertical-align:top;
width:25%;
}
button.fa {
.controls-container button {
border:1px solid #fff;
background:#d4d4d4;
color:#aaa;
cursor:pointer;
font-size:12px;
height:20px;
line-height:14px;
margin-right:5px;
transition:background 0.2s ease;
width:20px;
height:30px;
line-height:30px;
margin:0 5px 10px 0;
text-align:center;
transition:background 0.2s ease, border 0.2s ease;
width:30px;
}
button:hover {
background:#aaa;
.controls-container button:hover {
background:#bbb;
border:1px solid #aaa;
color:#888;
}
</style>
</head>
@ -67,7 +95,7 @@
<div class="sorter" data-algorithm='quick'></div>
<h1>Mergesort discussion</h1>
<!-- <h1>Mergesort discussion</h1>
used by firefox and safari. <br>
<div class="sorter" data-algorithm='merge'></div>
@ -97,7 +125,7 @@
<h1>radix sort discussion</h1>
<div class="sorter" data-algorithm='radix'></div>
-->
<!--
<h1>Heapsort discussion</h1>
@ -115,6 +143,8 @@
<script type='text/javascript' src='vendor/d3/d3.min.js'></script>
<script type='text/javascript' src='js/visualizer.js'></script>
<script type='text/javascript' src='js/visualizer-actions.js'></script>
<script type='text/javascript' src='js/visualizer-inits.js'></script>
<script type='text/javascript' src='js/sorter.js'></script>
<script type='text/javascript' src='js/quicksort.js'></script>
<script type='text/javascript' src='js/mergesort.js'></script>
@ -126,27 +156,14 @@
<script type='text/javascript'>
var dump = function(arr) {
console.log(arr)
var d = [];
arr.forEach(function(obj) {
d.push(obj.value);
})
// return d.join(',');
return d.join(',');
};
// var SS = new RadixSort();
// var data = SS.generate(15);
// console.log('DATA: ' + dump(data));
// var shuffled = SS.shuffle(data);
// console.log('SHUFFLED: ' + dump(shuffled));
// var ordered = Object.create(shuffled);
// ordered = SS.sort(ordered, 0, ordered.length - 1);
// console.log('ORDERED: ' + dump(ordered));
// Wrap anonymous function to avoid polluting global namespace.
(function() {
@ -154,8 +171,7 @@
var V;
for (key in elements) {
if (elements.hasOwnProperty(key)) {
V = new Visualizer(elements[key]);
// V.init();
new Visualizer(elements[key]);
}
}
})();

@ -27,4 +27,6 @@ BubbleSort.prototype.sort = function(arr) {
if (swapped === true) {
this.sort(arr);
}
return arr;
};

@ -33,4 +33,6 @@ InsertionSort.prototype.sort = function(arr) {
}
}
}
return arr;
};

@ -3,20 +3,15 @@
*/
var QuickSort = function() {
this.instructions = [];
this.swaps = 0;
this.comparisons = 0;
};
QuickSort.prototype = Object.create(Sorter.prototype);
/**
*
*/
QuickSort.prototype.addInstruction = function(operation, left, right, pivot) {
this.instructions.push({
operation: operation,
left: left,
right: right,
pivot: pivot
});
QuickSort.prototype.instruct = function() {
this.instructions.push(arguments);
return this;
};
// NOTE adds to an instruction set
@ -25,7 +20,9 @@ QuickSort.prototype.addInstruction = function(operation, left, right, pivot) {
*/
QuickSort.prototype.sort = function(arr, start, end) {
if (end - start <= 0) {
this.addInstruction('single', start, end, null);
this.instruct('highlight', end)
.instruct('message1', 'Start: ' + start)
.instruct('message2', 'End: ' + end)
return arr;
}
@ -36,17 +33,27 @@ QuickSort.prototype.sort = function(arr, start, end) {
var pivotval = arr[pivot].value;
var tmp;
this.addInstruction('init', left, right, pivot);
this.instruct('initSection', left, right)
.instruct('highlight', pivot)
.instruct('message3', 'Pivot value: ' + pivotval);
while (left <= right) {
while (arr[left].value < pivotval) {
left++;
this.addInstruction('inc-left', left, right, pivot);
this.comparisons++;
this.instruct('marker1', left, 'Left')
.instruct('message1', 'Left: ' + left)
.instruct('stats1', 'Comparisons: ' + this.comparisons)
}
while (arr[right].value > pivotval) {
right--;
this.addInstruction('dec-right', left, right, pivot);
this.comparisons++;
this.instruct('marker2', right, 'Right')
.instruct('message2', 'Right: ' + right)
.instruct('stats1', 'Comparisons: ' + this.comparisons)
}
if (left <= right) {
@ -54,11 +61,16 @@ QuickSort.prototype.sort = function(arr, start, end) {
arr[left] = arr[right];
arr[right] = tmp;
this.addInstruction('swap', left, right, pivot);
left++;
right--;
this.addInstruction('swap-inc-dec', left, right, pivot);
this.swaps++;
this.instruct('swap', left, right)
.instruct('stats2', 'Swaps: ' + this.swaps)
.instruct('message1', 'Left: ' + left)
.instruct('message2', 'Right: ' + right)
.instruct('marker1', left)
.instruct('marker2', right);
}
}

@ -11,6 +11,8 @@ SelectionSort.prototype = Object.create(Sorter.prototype);
*
*/
SelectionSort.prototype.sort = function(arr) {
console.error('selection sort is broken.');
var len = arr.length;
var i;
var j;
@ -37,4 +39,6 @@ SelectionSort.prototype.sort = function(arr) {
}
// console.info(`swaps: ${swaps}, comparisons: ${comparisons} `);
return arr;
};

@ -21,6 +21,8 @@ ShellSort.prototype.sort = function(arr) {
var IS = new InsertionSort();
IS.sort(arr);
return arr;
};
/**

@ -0,0 +1,125 @@
/**
* Instructions contain a string with the name of a function in this object which is called to perform an action.
*/
Visualizer.prototype.actions = {
/**
*
*/
initSection: function() {
console.warn('INIT SECTION');
},
/**
*
*/
swap: function(indexA, indexB) {
console.warn('SWAP');
// 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})`;
// })
},
/**
* Highlights an index.
*/
highlight: function(index) {
console.warn('HIGHLIGHT');
},
/**
* Un-highlights an index.
*/
unhighlight: function(index) {
console.warn('UNHIGHLIGHT');
},
// /**
// * Greys out an item.
// */
// 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;
// });
// };
// /**
// * Restores all items to un-greyed state.
// */
// unfade: function() {
// this.svg.selectAll('rect')
// .attr('fill', function fill(d) {
// console.log(`unfade ${d.index}`)
// return d.fill;
// })
// };
/**
* Moves marker 1 to an index and sets its text label.
*/
marker1: function(index, label) {
console.warn('MARKER1')
},
/**
* Moves marker 2 to an index and sets its text label.
*/
marker2: function(index, label) {
console.warn('MARKER2')
},
/**
* Updates message in stats 1 div.
*/
stats1: function(message) {
console.warn('STATS1')
},
/**
* Updates message in stats 2 div.
*/
stats2: function(message) {
console.warn('STATS2')
},
/**
* Updates message in message 1 div.
*/
message1: function(message) {
console.warn('MESSAGE1')
},
/**
* Updates message in message 2 div.
*/
message2: function(message) {
console.warn('MESSAGE2')
},
/**
* Updates message in message 3 div.
*/
message3: function(message) {
console.warn('MESSAGE3')
},
};

@ -0,0 +1,132 @@
/**
*
*/
Visualizer.prototype.initItems = 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);
// 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++;
}
this.groups = this.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, 0), 500);
};
/**
*
*/
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 msg = document.createElement('div');
msg.className = 'msg';
msg.innerHTML = "Number of items (n) = 10";
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, msg));
container.appendChild(range);
container.appendChild(msg);
return container;
};
/**
*
*/
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 msg1 = document.createElement('div');
msg1.className = 'msg';
msg1.innerHTML = 'Swaps: 999';
var msg2 = document.createElement('div');
msg2.className = 'msg';
msg2.innerHTML = 'Comparisons: 999';
var container = document.createElement('div');
container.className = 'controls-container';
container.appendChild(reset);
container.appendChild(back);
container.appendChild(play);
container.appendChild(forward);
container.appendChild(msg1);
container.appendChild(msg2);
return container;
};

@ -19,6 +19,7 @@ function Visualizer(parent) {
var range = this.initRange();
parent.querySelector('.bottom').appendChild(range);
this.svg = d3.select(parent.querySelector('.top')).append('svg');
this.groups = null;
this.parent = parent;
this.sorter = null;
@ -59,7 +60,7 @@ function Visualizer(parent) {
// this.instructions = null;
// this.currentInstruction = 0;
this.init(10);
this.initItems(10);
};
// Public static properties (mutable)
@ -71,185 +72,40 @@ 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) {
Visualizer.prototype.followInstruction = function(index) {
if (index >= this.sorter.instructions.length) {
return;
}
var i = instructions[index];
var i = this.sorter.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);
// NOTE interesting pattern here
if (typeof this.actions[i[0]] === 'function') {
this.actions[i[0]](i);
if (this.actions[i[1]] === true) {
setTimeout(this.followInstruction.bind(this, index + 1), 400);
}
else {
this.followInstruction(index + 1);
}
}
else if (i.operation === 'swap') {
this.followInstruction(instructions, index + 1);
// TODO document this
else if (this.actions[i[0]] !== undefined) {
console.warn(this[i[0]]);
}
else {
this.followInstruction(instructions, index + 1);
console.error(i);
throw new Error('Unidentified instruction.');
}
};
/**
*
*/
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;
label.innerHTML = 'Number of items (n) = ' + event.target.value;
};
/**
@ -258,38 +114,3 @@ Visualizer.prototype.rangeInput = function(label, event) {
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;
};

Loading…
Cancel
Save