Added sort properties.

master
ben-burlingham 10 years ago
parent efbf0d5112
commit 18563eb6ec
  1. 133
      index.html
  2. 50
      js/quicksort.js
  3. 140
      js/visualizer-actions.js
  4. 159
      js/visualizer-inits.js
  5. 65
      js/visualizer.js

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
@ -22,76 +22,85 @@
}
* {
box-sizing:border-box;
}
html, body {
font-family:sans-serif;
margin:0;
}
svg {
width:940px;
}
.sorter {
border:1px solid #e7e7e7;
background:#f4f4f4;
border:1px solid #bbb;
height:250px;
margin:20px auto;
padding:10px;
position:relative;
width:960px;
}
.range-container {
display:inline-block;
height:100%;
text-align:right;
vertical-align:top;
width:25%;
.sorter-sidebar {
height:230px;
position:absolute;
right:10px;
top:10px;
width:160px;
}
.range-container input {
height:50px;
width:80%;
.sorter-svg {
background:#fff;
border:1px solid #ccc;
height:180px;
left:10px;
position:absolute;
top:10px;
width:770px;
}
.message-container {
background:khaki;
display:inline-block;
height:100%;
vertical-align:top;
width:50%;
.sorter-svg .marker {
fill:#bbb;
}
.message-container .message {
font-size:13px;
.sorter-svg .marker text {
fill:#2E4E7F;
font-size:10px;
}
.top {
.sorter-properties {
height:40px;
left:10px;
position:absolute;
top:200px;
width:770px;
}
.bottom {
height:100px;
.sorter-properties .property {
border-right:1px solid #ddd;
float:left;
text-align:center;
width:128px;
}
.top .marker {
fill:#bbb;
.sorter-properties .p6 {
border:0;
}
.top .marker text {
fill:#2E4E7F;
.sorter-properties .property .title {
color:#777;
font-size:10px;
}
#marker1 {
height:30px;
left:10px;
top:50px;
width:30px;
.sorter-properties .property .value {
font-size:13px;
margin-top:10px;
}
.controls-container {
display:inline-block;
height:100%;
vertical-align:top;
width:25%;
height:50px;
padding-bottom:10px;
text-align:center;
}
.controls-container button {
@ -102,7 +111,7 @@
font-size:12px;
height:30px;
line-height:30px;
margin:0 5px 10px 0;
margin:0 2px;
text-align:center;
transition:background 0.2s ease, border 0.2s ease;
vertical-align: top;
@ -114,11 +123,43 @@
border:1px solid #2E4E7F;
}
.controls-container button:focus {
outline:0;
}
.controls-container .stat {
font-size:13px;
font-weight:bold;
line-height:30px;
}
.message-container {
border-top:1px solid #ddd;
font-size:13px;
height:100px;
line-height:20px;
text-align:right;
}
.message-container .message {
border-bottom:1px solid #ddd;
font-size:13px;
}
.range-container {
height:50px;
margin-top:30px;
text-align:center;
}
.range-container input {
margin-bottom:10px;
}
.range-container .msg {
font-size:13px;
}
</style>
</head>
<body>
@ -127,7 +168,15 @@
<h1>Quicksort discussion</h1>
used by chrome. <br>
swap. highlight. (un)fade.
<div class="sorter" data-algorithm='quick'></div>
<div class="sorter"
data-algorithm='quick'
data-stable='Maybe'
data-adaptive='Maybe'
data-worst-perf='O(n + m + 3k)'
data-avg-perf='O(n + m + 3k)'
data-best-perf='O(n + m + 3k)'
data-worst-memory='0 (in place)'
></div>
<!-- <h1>Mergesort discussion</h1>

@ -20,9 +20,9 @@ QuickSort.prototype.instruct = function() {
*/
QuickSort.prototype.sort = function(arr, start, end) {
if (end - start <= 0) {
// this.instruct('highlight', 500, end)
// .instruct('message1', 500, 'Start: ' + start)
// .instruct('message2', 500, 'End: ' + end)
// this//.instruct('highlight', 500, end)
// .instruct('message', 0, 3, 'Start: ' + start)
// .instruct('message', 0, 4, 'End: ' + end)
return arr;
}
@ -34,29 +34,33 @@ QuickSort.prototype.sort = function(arr, start, end) {
var tmp;
this//.instruct('initSection', left, right)
.instruct('marker1', 500, left, 'L')
.instruct('marker2', 500, right, 'R');
// .instruct('unhighlight')
// .instruct('highlight', 500, pivot)
// .instruct('message3', 500, 'Pivot value: ' + pivotval);
.instruct('showMarker', 0, 1)
.instruct('marker', 100, 1, left, 'L')
.instruct('message', 0, 3, 'Left: ' + left)
.instruct('showMarker', 0, 2)
.instruct('marker', 100, 2, right, 'R')
.instruct('message', 0, 4, 'Right: ' + right)
.instruct('unhighlight')
.instruct('highlight', 100, pivot)
.instruct('message', 0, 5, 'Pivot value: ' + pivotval + ', pivot index: ' + pivot);
while (left <= right) {
while (arr[left].value < pivotval) {
left++;
this.comparisons++;
this.instruct('marker1', 500, left)
.instruct('message1', 500, 'Left: ' + left)
.instruct('stats1', 500, 'Comparisons: ' + this.comparisons)
this.instruct('marker', 100, 1, left)
.instruct('message', 0, 3, 'Left: ' + left)
.instruct('message', 0, 1, 'Comparisons: ' + this.comparisons)
}
while (arr[right].value > pivotval) {
right--;
this.comparisons++;
this.instruct('marker2', 500, right)
.instruct('message2', 500, 'Right: ' + right)
.instruct('stats1', 500, 'Comparisons: ' + this.comparisons)
this.instruct('marker', 100, 2, right)
.instruct('message', 0, 4, 'Right: ' + right)
.instruct('message', 0, 1, 'Comparisons: ' + this.comparisons)
}
if (left <= right) {
@ -64,19 +68,25 @@ QuickSort.prototype.sort = function(arr, start, end) {
arr[left] = arr[right];
arr[right] = tmp;
this.instruct('swap', 500, left, right);
left++;
right--;
this.swaps++;
this//.instruct('swap', left, right)
.instruct('stats2', 500, 'Swaps: ' + this.swaps)
.instruct('message1', 500, 'Left: ' + left)
.instruct('message2', 500, 'Right: ' + right)
.instruct('marker1', 500, left)
.instruct('marker2', 500, right);
this.instruct('message', 0, 2, 'Swaps: ' + this.swaps)
.instruct('marker', 100, 1, left)
.instruct('message', 0, 3, 'Left: ' + left)
.instruct('marker', 100, 2, right)
.instruct('message', 0, 4, 'Right: ' + right);
}
}
this.instruct('unhighlight', 0)
.instruct('hideMarker', 0, 1)
.instruct('hideMarker', 0, 2);
this.sort(arr, start, right);
this.sort(arr, left, end);

@ -1,31 +1,25 @@
/**
* Instructions contain a string with the name of a function in this object which is called to perform an action.
*/
Visualizer.prototype.initSection = function() {
// console.warn('INIT SECTION');
};
/**
*
*/
Visualizer.prototype.swap = function(indexA, indexB) {
// console.warn('SWAP');
// Move u;
// 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.swap = function(args) {
var indexA = args[2];
var indexB = args[3];
// 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(${Visualizer.calculateX(d.index)}, ${Visualizer.itemY})`;
})
};
/**
@ -39,7 +33,11 @@ Visualizer.prototype.highlight = function(args) {
return;
}
this.svg.select(`g:nth-child(${index})`).select('rect').attr('fill', 'red');
this.groups.each(function(d, i) {
if (d.index === index) {
d3.select(this).select('rect').attr('fill', 'orangered')
}
});
};
/**
@ -49,6 +47,11 @@ Visualizer.prototype.unhighlight = function() {
this.svg.selectAll('.item').attr('fill', function(d) { return d.fill; });
};
// TODO restart instructions on re-init
// TODO on 'next step', skip past all zero-length timeouts -> do this in followInstruction by checking if timeout is zero
// TODO add adaptive/stable bools below canvas
// TODO add tabs for best/worst cases
// /**
// * Greys out an item.
// */
@ -77,79 +80,44 @@ Visualizer.prototype.unhighlight = function() {
// };
/**
* Moves marker 1 to an index and sets its text label.
*
*/
Visualizer.prototype.marker1 = function(args) {
var index = args[2];
var label = args[3];
if (label !== undefined) {
label = label[0].toString();
this.svg.select('#marker1 text').text(label)
}
var x = index * (Visualizer.itemW + Visualizer.spacerW);
this.svg.select('#marker1')
.transition().duration(300)
.attr('transform', `translate(${x},0)`)
Visualizer.prototype.hideMarker = function(args) {
var which = args[2];
this.svg.select(`#marker${which}`).attr('style', 'display:none');
};
/**
* Moves marker 2 to an index and sets its text label.
*/
Visualizer.prototype.marker2 = function(args) {
var index = args[2];
var label = args[3];
if (label !== undefined) {
label = label[0].toString();
this.svg.select('#marker2 text').text(label)
}
var x = index * (Visualizer.itemW + Visualizer.spacerW);
this.svg.select('#marker2')
.transition().duration(300)
.attr('transform', `translate(${x},0)`)
};
/**
* Updates message in stats 1 div.
*
*/
Visualizer.prototype.stats1 = function(args) {
var msg = args[2];
this.parent.querySelectorAll('.stat')[0].innerHTML = msg;
Visualizer.prototype.showMarker = function(args) {
var which = args[2];
this.svg.select(`#marker${which}`).attr('style', 'display:block');
};
/**
* Updates message in stats 2 div.
* Marker movement.
*/
Visualizer.prototype.stats2 = function(args) {
var msg = args[2];
this.parent.querySelectorAll('.stat')[1].innerHTML = msg;
};
Visualizer.prototype.marker = function(args) {
var which = args[2];
var index = args[3];
var label = args[4];
/**
* Updates message in message 1 div.
*/
Visualizer.prototype.message1 = function(args) {
var msg = args[2];
this.parent.querySelector('.message:nth-child(2)').innerHTML = msg;
};
if (label !== undefined) {
label = label[0].toString();
this.svg.select(`#marker${which} text`).text(label)
}
/**
* Updates message in message 2 div.
*/
Visualizer.prototype.message2 = function(args) {
var msg = args[2];
this.parent.querySelector('.message:nth-child(2)').innerHTML = msg;
this.svg.select(`#marker${which}`)
.transition().duration(100)
.attr('transform', `translate(${Visualizer.calculateX(index)},${Visualizer.spacerW})`)
};
/**
* Updates message in message 3 div.
* Message updates.
*/
Visualizer.prototype.message3 = function(args) {
var msg = args[2];
this.parent.querySelector('.message:nth-child(3)').innerHTML = msg;
Visualizer.prototype.message = function(args) {
var which = args[2];
var msg = args[3];
this.parent.querySelector(`.message:nth-child(${which})`).innerHTML = msg;
};

@ -13,6 +13,14 @@ Visualizer.prototype.initItems = function(n) {
shuffled[i].index = n++;
}
if (this.svg !== undefined) {
this.svg.remove();
}
this.instructionIndex = 0;
this.svg = d3.select(this.parent).append('svg')
.attr('class', 'sorter-svg');
// Items
this.groups = this.svg.selectAll('g').data(shuffled).enter().insert('g')
.attr('transform', `translate(0, ${Visualizer.itemY})`);
@ -25,7 +33,7 @@ Visualizer.prototype.initItems = function(n) {
this.groups.transition(500)
.attr('transform', function doTransform(d, i) {
return `translate(${i * (Visualizer.itemW + Visualizer.spacerW)}, ${Visualizer.itemY})`;
return `translate(${i * (Visualizer.itemW + Visualizer.spacerW) + Visualizer.spacerW}, ${Visualizer.itemY})`;
});
// Item labels
@ -41,7 +49,8 @@ Visualizer.prototype.initItems = function(n) {
// Markers
var m1 = this.svg.append('g')
.attr('class', 'marker')
.attr('id', 'marker1');
.attr('id', 'marker1')
.attr('transform', `translate(${Visualizer.spacerW}, ${Visualizer.spacerW})`);
m1.append('rect')
.attr('height', Visualizer.itemW)
@ -53,10 +62,9 @@ Visualizer.prototype.initItems = function(n) {
.attr('y', 10);
var m2 = this.svg.append('g')
//temp
.attr('transform', 'translate(19,0)')
//temp
.attr('transform', `translate(${Visualizer.spacerW}, ${Visualizer.spacerW})`)
.attr('class', 'marker')
.attr('style', 'display:none')
.attr('id', 'marker2');
m2.append('rect')
@ -67,8 +75,6 @@ Visualizer.prototype.initItems = function(n) {
.attr('text-anchor', 'middle')
.attr('x', (Visualizer.itemW / 2))
.attr('y', 10);
setTimeout(this.followInstruction.bind(this, 0), 500);
};
/**
@ -87,9 +93,25 @@ Visualizer.prototype.initMessages = function() {
var div3 = document.createElement('div');
div3.className = 'message';
var div4 = document.createElement('div');
div4.className = 'message';
div4.innerHTML = 'Comparisons: 0';
var div5 = document.createElement('div');
div5.className = 'message';
div5.innerHTML = 'Swaps: 0';
div1.innerHTML = 'testing';
div2.innerHTML = 'testing';
div3.innerHTML = 'testing';
div4.innerHTML = 'testing';
div5.innerHTML = 'testing';
container.appendChild(div1);
container.appendChild(div2);
container.appendChild(div3);
container.appendChild(div4);
container.appendChild(div5);
return container;
};
@ -106,7 +128,6 @@ Visualizer.prototype.initRange = function() {
this.initItems(event.target.value);
};
var container = document.createElement('div');
container.className = 'range-container';
@ -118,7 +139,7 @@ Visualizer.prototype.initRange = function() {
range.setAttribute('type', 'range');
range.setAttribute('value', 10);
range.setAttribute('min', 10);
range.setAttribute('max', 80);
range.setAttribute('max', 40);
range.addEventListener('change', rangeChange.bind(this));
range.addEventListener('input', rangeInput.bind(null, msg));
@ -144,23 +165,26 @@ Visualizer.prototype.initControls = function() {
var playclick = function() {
this.paused = !this.paused;
updatePlayPause(this.paused);
this.followInstruction(this.instruction + 1);
this.followInstruction();
};
var backclick = function() {
this.paused = true;
this.followInstruction(this.instruction - 1);
this.instructionIndex--;
this.followInstruction();
};
var forwardclick = function() {
this.paused = true;
this.followInstruction(this.instruction + 1);
this.followInstruction();
this.instructionIndex++;
};
var restartclick = function() {
this.paused = false;
updatePlayPause(this.paused);
this.followInstruction(0);
this.instructionIndex = 0;
this.followInstruction();
};
var play = document.createElement('button');
@ -183,14 +207,6 @@ Visualizer.prototype.initControls = function() {
reset.title = 'Restart'
reset.addEventListener('click', restartclick.bind(this));
var stat1 = document.createElement('div');
stat1.className = 'stat';
stat1.innerHTML = 'Comparisons: 999';
var stat2 = document.createElement('div');
stat2.className = 'stat';
stat2.innerHTML = 'Swaps: 999';
var container = document.createElement('div');
container.className = 'controls-container';
@ -198,8 +214,105 @@ Visualizer.prototype.initControls = function() {
container.appendChild(back);
container.appendChild(play);
container.appendChild(forward);
container.appendChild(stat1);
container.appendChild(stat2);
return container;
};
Visualizer.prototype.initProperties = function() {
// Div 1
var div1 = document.createElement('div');
div1.className = 'property p1';
var title1 = document.createElement('div');
title1.innerHTML = 'Stable';
title1.className = 'title';
div1.appendChild(title1);
var value1 = document.createElement('div');
value1.className = 'value';
value1.innerHTML = this.parent.attributes['data-stable'].value;
div1.appendChild(value1);
// Div 2
var div2 = document.createElement('div');
div2.className = 'property p2';
var title2 = document.createElement('div');
title2.innerHTML = 'Adaptive';
title2.className = 'title';
div2.appendChild(title2);
var value2 = document.createElement('div');
value2.className = 'value';
value2.innerHTML = this.parent.attributes['data-adaptive'].value;
div2.appendChild(value2);
// Div 3
var div3 = document.createElement('div');
div3.className = 'property p3';
var title3 = document.createElement('div');
title3.innerHTML = 'Worst Performance';
title3.className = 'title';
div3.appendChild(title3);
var value3 = document.createElement('div');
value3.className = 'value';
value3.innerHTML = this.parent.attributes['data-worst-perf'].value;
div3.appendChild(value3);
// Div 4
var div4 = document.createElement('div');
div4.className = 'property p4';
var title4 = document.createElement('div');
title4.innerHTML = 'Average Performance';
title4.className = 'title';
div4.appendChild(title4);
var value4 = document.createElement('div');
value4.className = 'value';
value4.innerHTML = this.parent.attributes['data-avg-perf'].value;
div4.appendChild(value4);
// Div 5
var div5 = document.createElement('div');
div5.className = 'property p5';
var title5 = document.createElement('div');
title5.innerHTML = 'Best Performance';
title5.className = 'title';
div5.appendChild(title5);
var value5 = document.createElement('div');
value5.className = 'value';
value5.innerHTML = this.parent.attributes['data-best-perf'].value;
div5.appendChild(value5);
// Div 6
var div6 = document.createElement('div');
div6.className = 'property p6';
var title6 = document.createElement('div');
title6.innerHTML = 'Worst Memory';
title6.className = 'title';
div6.appendChild(title6);
var value6 = document.createElement('div');
value6.className = 'value';
value6.innerHTML = this.parent.attributes['data-worst-memory'].value;
div6.appendChild(value6);
// Container
var container = document.createElement('div');
container.className = 'sorter-properties';
container.appendChild(div1);
container.appendChild(div2);
container.appendChild(div3);
container.appendChild(div4);
container.appendChild(div5);
container.appendChild(div6);
return container;
};

@ -2,27 +2,25 @@
*
*/
function Visualizer(parent) {
var top = document.createElement('div');
top.className = 'top';
parent.appendChild(top);
this.parent = parent;
this.sorter = null;
this.paused = true;
var bottom = document.createElement('div');
bottom.className = 'bottom';
parent.appendChild(bottom);
var sorterSidebarContainer = document.createElement('div');
sorterSidebarContainer.className = 'sorter-sidebar';
parent.appendChild(sorterSidebarContainer);
var properties = this.initProperties();
parent.appendChild(properties);
var controls = this.initControls();
parent.querySelector('.bottom').appendChild(controls);
parent.querySelector('.sorter-sidebar').appendChild(controls);
var messages = this.initMessages();
parent.querySelector('.bottom').appendChild(messages);
parent.querySelector('.sorter-sidebar').appendChild(messages);
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;
parent.querySelector('.sorter-sidebar').appendChild(range);
switch(parent.attributes['data-algorithm'].value) {
case 'quick':
@ -57,13 +55,7 @@ function Visualizer(parent) {
throw new Error('Unrecognized sort type.');
}
// this.instructions = null;
// this.currentInstruction = 0;
this.initItems(10);
this.instruction = 0;
this.paused = false;
};
// Public static properties (mutable)
@ -75,29 +67,34 @@ Visualizer.itemY = 20;
/**
*
*/
Visualizer.prototype.followInstruction = function(index) {
if (index >= this.sorter.instructions.length) {
Visualizer.calculateX = function(index) {
return Visualizer.spacerW + index * (Visualizer.itemW + Visualizer.spacerW)
};
/**
* Instructions contain a string with the name of a function in this object which is called to perform an action.
*/
Visualizer.prototype.followInstruction = function() {
if (this.instructionIndex >= this.sorter.instructions.length) {
return;
}
this.instruction = index;
var instruction = this.sorter.instructions[this.instructionIndex];
var operation = this[instruction[0]];
var delay = instruction[1];
var i = this.sorter.instructions[index];
console.log(i);
console.log(instruction);
// NOTE interesting pattern here
if (typeof this[i[0]] === 'function') {
this[i[0]](i);
// NOTE interesting (anti?)pattern here.
// NOTE use of call() vs apply() (apply only delivered first array item as string)
if (typeof operation === 'function') {
operation.call(this, instruction);
if (this.paused === false) {
var delay = i[1];
setTimeout(this.followInstruction.bind(this, index + 1), delay);
this.instructionIndex++;
setTimeout(this.followInstruction.bind(this), delay);
}
}
// TODO document this
else if (this[i[0]] !== undefined) {
console.warn(this[i[0]]);
}
else {
console.error(i);
throw new Error('Unidentified instruction.');

Loading…
Cancel
Save