diff --git a/.eslintrc b/.eslintrc
deleted file mode 100644
index 296027c..0000000
--- a/.eslintrc
+++ /dev/null
@@ -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'
diff --git a/Gruntfile.js b/Gruntfile.js
deleted file mode 100644
index 6be93b9..0000000
--- a/Gruntfile.js
+++ /dev/null
@@ -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);
-};
diff --git a/grunt/aliases.yaml b/grunt/aliases.yaml
deleted file mode 100644
index 5f71f4f..0000000
--- a/grunt/aliases.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-default:
- - 'concurrent:watch'
diff --git a/grunt/concurrent.js b/grunt/concurrent.js
deleted file mode 100644
index 7177d2c..0000000
--- a/grunt/concurrent.js
+++ /dev/null
@@ -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'
- ],
- }
-};
diff --git a/grunt/eslint.js b/grunt/eslint.js
deleted file mode 100644
index ba98c43..0000000
--- a/grunt/eslint.js
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * ESLINT
- * ======
- * Does not support 'subtasks' (like :all).
- */
-module.exports = {
- target: ['js/*.js']
-};
diff --git a/grunt/watch.js b/grunt/watch.js
deleted file mode 100644
index 3b66750..0000000
--- a/grunt/watch.js
+++ /dev/null
@@ -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
- },
- },
-};
diff --git a/index.html b/index.html
index 459cf23..075835e 100644
--- a/index.html
+++ b/index.html
@@ -1,56 +1,137 @@
-
+
D3
-
+
+
+ Quicksort discussion
+
+
+
+
+ Mergesort discussion
+
+
+
+
+
+
+
+
+
+
diff --git a/js/mergesort.js b/js/mergesort.js
new file mode 100644
index 0000000..83e2d6c
--- /dev/null
+++ b/js/mergesort.js
@@ -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;
+};
diff --git a/js/quicksort.js b/js/quicksort.js
new file mode 100644
index 0000000..2ec869d
--- /dev/null
+++ b/js/quicksort.js
@@ -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);
+};
diff --git a/js/sorter.js b/js/sorter.js
index 1d80404..2277632 100644
--- a/js/sorter.js
+++ b/js/sorter.js
@@ -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.');
};
diff --git a/js/visualizer.js b/js/visualizer.js
index ddad908..c5743c9 100644
--- a/js/visualizer.js
+++ b/js/visualizer.js
@@ -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;
+};
diff --git a/package.json b/package.json
index 4e9a545..e38ed54 100644
--- a/package.json
+++ b/package.json
@@ -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"
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..2358df0
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,4 @@
+### Installation
+
+1. Run `bower install` in root directory.
+1. Open `index.html` in a browser.