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

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

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

@ -3,20 +3,15 @@
*/ */
var QuickSort = function() { var QuickSort = function() {
this.instructions = []; this.instructions = [];
this.swaps = 0;
this.comparisons = 0;
}; };
QuickSort.prototype = Object.create(Sorter.prototype); QuickSort.prototype = Object.create(Sorter.prototype);
/** QuickSort.prototype.instruct = function() {
* this.instructions.push(arguments);
*/ return this;
QuickSort.prototype.addInstruction = function(operation, left, right, pivot) {
this.instructions.push({
operation: operation,
left: left,
right: right,
pivot: pivot
});
}; };
// NOTE adds to an instruction set // 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) { QuickSort.prototype.sort = function(arr, start, end) {
if (end - start <= 0) { if (end - start <= 0) {
this.addInstruction('single', start, end, null); this.instruct('highlight', end)
.instruct('message1', 'Start: ' + start)
.instruct('message2', 'End: ' + end)
return arr; return arr;
} }
@ -36,17 +33,27 @@ QuickSort.prototype.sort = function(arr, start, end) {
var pivotval = arr[pivot].value; var pivotval = arr[pivot].value;
var tmp; 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 (left <= right) {
while (arr[left].value < pivotval) { while (arr[left].value < pivotval) {
left++; 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) { while (arr[right].value > pivotval) {
right--; 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) { if (left <= right) {
@ -54,11 +61,16 @@ QuickSort.prototype.sort = function(arr, start, end) {
arr[left] = arr[right]; arr[left] = arr[right];
arr[right] = tmp; arr[right] = tmp;
this.addInstruction('swap', left, right, pivot);
left++; left++;
right--; 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) { SelectionSort.prototype.sort = function(arr) {
console.error('selection sort is broken.');
var len = arr.length; var len = arr.length;
var i; var i;
var j; var j;
@ -37,4 +39,6 @@ SelectionSort.prototype.sort = function(arr) {
} }
// console.info(`swaps: ${swaps}, comparisons: ${comparisons} `); // console.info(`swaps: ${swaps}, comparisons: ${comparisons} `);
return arr;
}; };

@ -21,6 +21,8 @@ ShellSort.prototype.sort = function(arr) {
var IS = new InsertionSort(); var IS = new InsertionSort();
IS.sort(arr); 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(); var range = this.initRange();
parent.querySelector('.bottom').appendChild(range); parent.querySelector('.bottom').appendChild(range);
this.svg = d3.select(parent.querySelector('.top')).append('svg');
this.groups = null; this.groups = null;
this.parent = parent; this.parent = parent;
this.sorter = null; this.sorter = null;
@ -59,7 +60,7 @@ function Visualizer(parent) {
// this.instructions = null; // this.instructions = null;
// this.currentInstruction = 0; // this.currentInstruction = 0;
this.init(10); this.initItems(10);
}; };
// Public static properties (mutable) // Public static properties (mutable)
@ -71,185 +72,40 @@ Visualizer.itemY = 20;
/** /**
* *
*/ */
Visualizer.prototype.init = function(n) { Visualizer.prototype.followInstruction = function(index) {
var data = this.sorter.generate(n); if (index >= this.sorter.instructions.length) {
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; return;
} }
var i = instructions[index]; var i = this.sorter.instructions[index];
console.log(i); console.log(i);
if (i.operation === 'swap' && i.left < i.right) { // NOTE interesting pattern here
this.swap(i.left, i.right); if (typeof this.actions[i[0]] === 'function') {
setTimeout(this.followInstruction.bind(this, instructions, index + 1), 400); this.actions[i[0]](i);
}
else if (i.operation === 'init') { if (this.actions[i[1]] === true) {
// this.unfade(); setTimeout(this.followInstruction.bind(this, index + 1), 400);
// this.fade(0, i.left - 1); }
// this.fade(i.right + 1, 1000) else {
this.followInstruction(instructions, index + 1); this.followInstruction(index + 1);
}
} }
else if (i.operation === 'swap') { // TODO document this
this.followInstruction(instructions, index + 1); else if (this.actions[i[0]] !== undefined) {
console.warn(this[i[0]]);
} }
else { 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) { 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) { Visualizer.prototype.rangeChange = function(event) {
this.init(event.target.value); 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