Visualization built.

master
ben-burlingham 10 years ago
parent 9101efa9f7
commit be5c97a897
  1. 1
      .gitignore
  2. 163
      index.html
  3. 79
      js/sorter.js
  4. 178
      js/visualizer.js

1
.gitignore vendored

@ -1,2 +1,3 @@
vendor
node_modules
.todo

@ -19,162 +19,45 @@
</style>
</head>
<body>
<script type='text/javascript' src='js/visualizer.js'></script>
<script type='text/javascript' src='js/sorter.js'></script>
<script type='text/javascript'>
var swatchH = 50;
var swatchW = 10;
var dataset = [];
var data = [];
// TODO namespace this
(function populate() {
var len = 100;
var len = 10;
var r, g, b;
for (var i = 0; i < len; i++) {
dataset.push({
data.push({
x: 0,
y: 20,
r: 0, //Math.floor((len - i) * 255 / len),
g: 0,
b: Math.floor(i * 255 / len)
});
};
}());
dataset = shuffle(dataset);
w: 10,
h: 50,
var svg = d3.select('body').append('svg')
.attr('class', 'quicksort-random');
val: Math.floor(i * 255 / len),
var rectangles = svg.selectAll('rect')
.data(dataset).enter()
.append('rect')
.attr('height', swatchH)
.attr('width', swatchW)
// .attr('')
.attr('fill', function fill(d) {
return `rgb(${d.r}, ${d.g}, ${d.b})`;
})
.attr('y', function y(d) { return d.y; })
.attr('x', function x(d) { return d.x; })
.transition(500)
.attr('x', function xb(d, i) {
dataset[i].x = i * (swatchW + 2);
return dataset[i].x;
fill: `rgb(0, 0, ${Math.floor(i * 255 / len)})`,
fade: 'rgb(220, 220, 220)',
});
};
}());
function visualSwap(indexA, indexB) {
// Move up
// NOTE Two way binding here between dataset and function parameter
rectangles
.transition()
.duration(100)
.attr('y', function ya(d, i) {
if (i === indexA || i === indexB) {
dataset[i].y = 5;
}
return dataset[i].y; // NOTE d[i].y has been modified too. But, the bound value in the dataset
// will not be updated until after the chain is completed, so it has to be explicitly defined here.
})
// Switch places
// NOTE discuss chained transitions: http://bl.ocks.org/mbostock/1125997
.transition()
.duration(500)
.attr('x', function xa(d, i) {
if (i === indexA) {
var tmp = dataset[indexA].x;
dataset[indexA].x = dataset[indexB].x;
dataset[indexB].x = tmp;
}
return d.x;
})
// Move down
.transition()
.duration(100)
.attr('y', function ya(d, i) {
if (i === indexA || i === indexB) {
dataset[i].y = 20;
}
return dataset[i].y;
})
};
function highlight(index) {
svg.selectAll('rect')
.attr('stroke', function(d, i) { if (i === index) { return 'lime'; } })
.attr('stroke-width', function(d, i) { if (i === index) { return 4; } });
};
function unhighlightAll(index) {
svg.selectAll('rect')
.attr('stroke', function(d, i) { return null; })
.attr('stroke-width', function(d, i) { return null; });
};
// NOTE fisher-yates, http://bost.ocks.org/mike/algorithms/
function shuffle(arr) {
var n = arr.length, t, i;
while (n) {
i = Math.random() * n-- | 0; // 0 ≤ i < n
t = arr[n];
arr[n] = arr[i];
arr[i] = t;
}
return arr;
};
// visualSwap(0, 1);
function quicksort(arr, startIndex, endIndex) {
if (endIndex - startIndex <= 0) {
return;
}
var left = startIndex;
var right = endIndex;
unhighlightAll();
var pivotIndex = Math.floor((right + left) / 2);
var pval = arr[pivotIndex].b;
var tmp;
highlight(pivotIndex);
while (left <= right) {
while (arr[left].b < pval) {
left++;
}
while (arr[right].b > pval) {
right--;
}
if (left <= right) {
tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
visualSwap(left, right);
left++;
right--;
}
}
quicksort(arr, startIndex, right);
quicksort(arr, left, endIndex);
};
var S = new Sorter();
S.shuffle(data);
quicksort(dataset, 0, dataset.length - 1);
console.error(dataset);
var V = new Visualizer();
V.init(data);
V.addMarker('quicksort-left-marker');
V.moveMarker('quicksort-left-marker', 2);
// var instructions = [];
// instructions.push('M ' + (50 * i) + ',50');
// instructions.push('A 50,50 0 0,1 ' + (50 * (i + 1)) + ',0');
// instructions.push('L ' + (50 * (i + 1)) + ',50');
// instructions.push('L ' + (50 * i) + ',50');
var temp = Object.create(data);
S.quicksort(temp, 0, data.length - 1);
temp = null;
// setTimeout(V.followInstruction.bind(V, S.instructions, 0), 500);
</script>
</body>
</html>

@ -0,0 +1,79 @@
/**
*
*/
var Sorter = function() {};
Sorter.prototype.instructions = [];
// NOTE fisher-yates, http://bost.ocks.org/mike/algorithms/
/**
*
*/
Sorter.prototype.shuffle = function(arr) {
var n = arr.length, t, i;
while (n) {
i = Math.random() * n-- | 0; // 0 ≤ i < n
t = arr[n];
arr[n] = arr[i];
arr[i] = t;
}
return arr;
};
/**
*
*/
Sorter.prototype.addInstruction = function(operation, left, right, pivot) {
this.instructions.push({
operation: operation,
left: left,
right: right,
pivot: pivot
});
};
// 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);
}
}
this.quicksort(arr, start, right);
this.quicksort(arr, left, end);
};

@ -0,0 +1,178 @@
/**
*
*/
var Visualizer = function() {
this.itemW = 20;
this.itemH = 50;
this.svg = null;
this.instructions = null;
};
Visualizer.prototype.svg = null;
Visualizer.prototype.instructions = null;
/**
*
*/
Visualizer.prototype.init = function(data) {
// 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++;
}
this.svg = d3.select('body').append('svg')
.attr('class', 'quicksort-random');
this.svg.selectAll('rect').data(data).enter()
.append('rect')
.attr('height', function h(d) { return d.h; })
.attr('width', function w(d) { return d.w; })
.attr('fill', function fill(d) { return d.fill; })
.attr('y', function y(d) { return d.y; })
.attr('x', function x(d) { return d.x; })
.append('text')
.text(function t(d) { return d.val; })
.attr('fill', '#000')
.attr('x', function x(d) { return d.x; })
.transition(500)
.attr('x', function expand(d, i) {
data[i].x = i * (d.w + 2);
return data[i].x;
});
// 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
};
/**
*
*/
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
this.svg.selectAll('rect')
// .transition()
// .duration(100)
// .attr('y', function ya(d) {
// if (d.index === indexA || d.index === indexB) {
// d.y = 5;
// }
// return d.y; // NOTE d[i].y has been modified too. But, the bound value in the dataset
// // will not be updated until after the chain is completed, so it has to be explicitly defined here.
// })
// Switch places
// NOTE discuss chained transitions: http://bl.ocks.org/mbostock/1125997
// TODO now that elements aren't cached, is there a need for .index?
.transition()
.duration(400)
.attr('x', function xa(d) {
if (d.index === indexA) {
d.index = indexB;
d.x = d.index * (d.w + 2);
}
else if (d.index === indexB) {
d.index = indexA;
d.x = d.index * (d.w + 2);
}
return d.x;
})
// Move down
// .transition()
// .duration(100)
// .attr('y', function ya(d) {
// if (d.index === indexA || d.index === indexB) {
// d.y = 20;
// }
// return d.y;
// });
};
/**
*
*/
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)
.attr('x', 0)
.attr('y', 10)
.attr('width', 20)
.attr('height', 20)
.attr('fill', '#333')
.attr('font-size', 20)
.attr('alignment-baseline', 'middle')
.attr('text-anchor', 'middle')
.attr('transform', 'rotate(90 10,10)')
.text('P');
};
/**
*
*/
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 || index > 25) {
return;
}
var i = 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);
}
else if (i.operation === 'swap') {
this.followInstruction(instructions, index + 1);
}
else {
this.followInstruction(instructions, index + 1);
}
};
Loading…
Cancel
Save