Visualizer improvements. Sort inheritance pattern built.

master
ben-burlingham 10 years ago
parent 83c367cdc5
commit 78078f1a65
  1. 17
      .eslintrc
  2. 6
      Gruntfile.js
  3. 2
      grunt/aliases.yaml
  4. 18
      grunt/concurrent.js
  5. 8
      grunt/eslint.js
  6. 18
      grunt/watch.js
  7. 151
      index.html
  8. 53
      js/mergesort.js
  9. 67
      js/quicksort.js
  10. 79
      js/sorter.js
  11. 150
      js/visualizer.js
  12. 6
      package.json
  13. 4
      readme.md

@ -1,17 +0,0 @@
rules:
indent:
- 2
- 4
quotes:
- 2
- single
linebreak-style:
- 2
- unix
semi:
- 2
- always
env:
es6: true
browser: true
extends: 'eslint:recommended'

@ -1,6 +0,0 @@
/**
* Debugging: try running "npm cache clear" if things aren't working as expected.
*/
module.exports = function(grunt) {
require('load-grunt-config')(grunt);
};

@ -1,2 +0,0 @@
default:
- 'concurrent:watch'

@ -1,18 +0,0 @@
/**
* CONCURRENT
* ==========
* Runs several multiple blocking tasks at once (like "watch").
* http://stackoverflow.com/questions/17585385/how-to-run-two-grunt-watch-tasks-simultaneously
*/
module.exports = {
options: {
logConcurrentOutput: true,
},
watch: {
tasks: [
//'nodemon',
'watch'
],
}
};

@ -1,8 +0,0 @@
/**
* ESLINT
* ======
* Does not support 'subtasks' (like :all).
*/
module.exports = {
target: ['js/*.js']
};

@ -1,18 +0,0 @@
/**
* WATCH
* =====
* Process for watching changes to files in real time.
*/
module.exports = {
js: {
files: [
'js/**/*.js'
],
tasks: [
'eslint'
],
options: {
livereload: true
},
},
};

@ -1,56 +1,137 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3</title>
<script type='text/javascript' src='vendor/d3/d3.min.js'></script>
<link rel="stylesheet" href="vendor/fontawesome/css/font-awesome.css">
<style type='text/css'>
html, body {
font-family:sans-serif;
margin:0;
}
.quicksort-random {
display:block;
height:60px;
margin:0 auto;
width:1198px;
svg {
border:10px solid dodgerblue;
width:940px;
}
.sorter {
border:1px solid #e7e7e7;
margin:20px auto;
padding:10px;
width:960px;
}
.slider {
}
.top {
background:#f88;
}
.bottom {
background:#4e0;
height:100px;
}
button {
border:1px solid #333;
background:#888;
padding:3px;
}
button.fa {
cursor:pointer;
font-size:12px;
height:20px;
line-height:14px;
margin-right:5px;
transition:background 0.2s ease;
width:20px;
}
button:hover {
background:#aaa;
}
</style>
</head>
<body>
<h1>Quicksort discussion</h1>
<div class="sorter"></div>
<h1>Mergesort discussion</h1>
<div class="sorter"></div>
<!-- <h1>Shellsort discussion</h1>
<div class="sorter"></div>
<h1>Heapsort discussion</h1>
<div class="sorter"></div>
<h1>Bubblesort discussion</h1>
<div class="sorter"></div>
<h1>Radixsort discussion</h1>
<div class="sorter"></div>
-->
<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/sorter.js'></script>
<script type='text/javascript' src='js/quicksort.js'></script>
<script type='text/javascript' src='js/mergesort.js'></script>
<script type='text/javascript'>
var data = [];
// TODO namespace this
(function populate() {
var len = 50;
var r, g, b;
for (var i = 0; i < len; i++) {
data.push({
val: Math.floor(i * 255 / len),
fill: `rgb(0, 0, ${Math.floor(i * 255 / len)})`,
fade: 'rgb(220, 220, 220)',
});
};
}());
var S = new Sorter();
S.shuffle(data);
var V = new Visualizer();
V.init(data);
V.addMarker('quicksort-left-marker');
V.moveMarker('quicksort-left-marker', 2);
var temp = Object.create(data);
S.quicksort(temp, 0, data.length - 1);
temp = null;
setTimeout(V.followInstruction.bind(V, S.instructions, 0), 500);
// var QS = new Quicksort();
// QS.addInstruction();
var MS = new Mergesort();
var data = MS.generate(15);
var a = []
data.forEach(function(obj0) {
a.push(obj0.value);
})
console.log('DATA: ' + a.join(','));
var shuffled = MS.shuffle(data);
var b = []
shuffled.forEach(function(obj0) {
b.push(obj0.value);
});
console.log('SHUFFLED: ' + b.join(','));
var ordered = Object.create(shuffled);
MS.sort(ordered, 0, ordered.length - 1);
var c = []
ordered.forEach(function(obj0) {
c.push(obj0.value);
});
console.log('ORDERED: ' + c.join(','));
// Wrap anonymous function to avoid polluting global namespace.
// (function() {
// var elements = document.querySelectorAll('.sorter');
// for (key in elements) {
// if (elements.hasOwnProperty(key)) {
// new Visualizer(elements[key]);
// }
// }
// })();
</script>
</body>
</html>

@ -0,0 +1,53 @@
/**
*
*/
var Mergesort = function() {
this.instructions = [];
};
Mergesort.prototype = Object.create(Sorter.prototype);
/**
*
*/
Mergesort.prototype.addInstruction = function(operation, left, right, mid) {
console.log({
operation: operation,
left: left,
right: right,
mid: mid
});
this.instructions.push({
operation: operation,
left: left,
right: right
});
};
var count = 0;
/**
*
*/
Mergesort.prototype.sort = function(arr, start, end) {
count++;
if (count > 30) {
console.error('count exceeded');
return;
}
if (start >= end) {
this.addInstruction('single', start, end, null);
return;
}
var mid = start + Math.floor((end - start) / 2);
// this.addInstruction('init', start, end, mid);
this.sort(arr, start, mid);
this.sort(arr, mid + 1, end);
return;
};

@ -0,0 +1,67 @@
/**
*
*/
var Quicksort = function() {
this.instructions = [];
};
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
});
};
// NOTE adds to an instruction set
/**
*
*/
Quicksort.prototype.sort = 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].value;
var tmp;
this.addInstruction('init', left, right, pivot);
while (left <= right) {
while (arr[left].value < pivotval) {
left++;
this.addInstruction('inc-left', left, right, pivot);
}
while (arr[right].value > 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.sort(arr, start, right);
this.sort(arr, left, end);
};

@ -2,78 +2,47 @@
*
*/
var Sorter = function() {};
Sorter.prototype.instructions = [];
// NOTE fisher-yates, http://bost.ocks.org/mike/algorithms/
/**
*
* Returns copy of an array shuffled using Fisher-Yates.
*/
Sorter.prototype.shuffle = function(arr) {
var n = arr.length, t, i;
var result = Object.create(arr);
var n = result.length, t, i;
while (n) {
i = Math.random() * n-- | 0; // 0 ≤ i < n
t = arr[n];
arr[n] = arr[i];
arr[i] = t;
t = result[n];
result[n] = result[i];
result[i] = t;
}
return arr;
return result;
};
/**
*
*/
Sorter.prototype.addInstruction = function(operation, left, right, pivot) {
this.instructions.push({
operation: operation,
left: left,
right: right,
pivot: pivot
});
Sorter.prototype.generate = function(n) {
var arr = [];
for (var i = 0; i < n; i++) {
arr.push({
value: Math.floor(i * 255 / n),
});
};
return arr;
};
// 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);
}
}
Sorter.prototype.sort = function(instruction) {
throw new Error('Sorter.sort() method override required.');
};
this.quicksort(arr, start, right);
this.quicksort(arr, left, end);
/**
*
*/
Sorter.prototype.addInstruction = function(instruction) {
throw new Error('Sorter.addInstruction() method override required.');
};

@ -1,37 +1,69 @@
/**
*
*/
var Visualizer = function() {};
function Visualizer(parent) {
var top = document.createElement('div');
top.className = 'top';
parent.appendChild(top);
Visualizer.spacerW = 5;
Visualizer.itemW = 14;
Visualizer.itemH = 50;
Visualizer.itemY = 20;
var bottom = document.createElement('div');
bottom.className = 'bottom';
parent.appendChild(bottom);
Visualizer.svg = null;
Visualizer.groups = null;
Visualizer.instructions = null;
var range = this.initRange();
parent.querySelector('.bottom').appendChild(range);
var controls = this.initControls();
parent.querySelector('.bottom').appendChild(controls);
this.groups = null;
this.parent = parent;
this.init(10);
// this.instructions = null;
// this.currentInstruction = 0;
};
// Public static properties (mutable)
Visualizer.spacerW = 5;
Visualizer.itemW = 14;
Visualizer.itemH = 50;
Visualizer.itemY = 20;
/**
*
*/
Visualizer.prototype.init = function(data) {
Visualizer.prototype.init = function(n) {
// Sorter setup.
var QS = new Quicksort();
var data = QS.generate(n);
var shuffled = QS.shuffle(data);
var ordered = Object.create(shuffled);
QS.sort(ordered, 0, ordered.length - 1);
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 data) {
data[i].index = n++;
for (i in shuffled) {
shuffled[i].index = n++;
}
this.svg = d3.select('body').append('svg')
.attr('class', 'quicksort-random');
var svg = d3.select(this.parent.querySelector('.top')).append('svg');
this.groups = this.svg.selectAll('g').data(data).enter().append('g')
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 d.fill; });
.attr('fill', function doFill(d) { return `rgb(0, 0, ${d.value})`; });
this.groups.transition(500)
.attr('transform', function doTransform(d, i) {
@ -39,7 +71,7 @@ Visualizer.prototype.init = function(data) {
});
this.groups.append('text')
.text(function t(d) { console.log(d.val); return d.val; })
.text(function t(d) { return d.value; })
.attr('fill', '#aaa')
.attr('font-size', 10)
.attr('font-family', 'sans-serif')
@ -47,9 +79,16 @@ Visualizer.prototype.init = function(data) {
return `rotate(90 0,0), translate(5, -3)`;
});
// V.addMarker('quicksort-left-marker');
// V.moveMarker('quicksort-left-marker', 2);
// 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
setTimeout(this.followInstruction.bind(this, QS.instructions, 0), 500);
};
/**
@ -60,7 +99,6 @@ Visualizer.prototype.swap = function(indexA, indexB) {
// 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
// TODO now that elements aren't cached, is there a need for .index?
this.groups
.transition().duration(400)
.attr('transform', function doTransform(d) {
@ -77,14 +115,14 @@ Visualizer.prototype.swap = function(indexA, indexB) {
/**
*
*/
* /
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)
@ -156,3 +194,77 @@ Visualizer.prototype.followInstruction = function(instructions, index) {
this.followInstruction(instructions, index + 1);
}
};
/**
*
*/
Visualizer.prototype.initRange = function(Visualizer) {
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.appendChild(reset);
container.appendChild(back);
container.appendChild(play);
container.appendChild(forward);
return container;
};

@ -1,7 +1,7 @@
{
"name": "ben.burlingham",
"version": "0.0.0",
"description": "",
"name": "d3-sort-visualization",
"version": "1.0.0",
"description": "Sorting visualizations using the D3 library.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"

@ -0,0 +1,4 @@
### Installation
1. Run `bower install` in root directory.
1. Open `index.html` in a browser.
Loading…
Cancel
Save