├── app.js ├── functions.js ├── index.html ├── style.css └── worker.js /app.js: -------------------------------------------------------------------------------- 1 | var currentFn = null; 2 | var currentCirc = null; 3 | var radius = 5; 4 | var hoverRadius = 8; 5 | var circleData = []; 6 | var tHover = d3 7 | .transition() 8 | .duration(500) 9 | .ease(d3.easeLinear); 10 | 11 | document.addEventListener("DOMContentLoaded", function() { 12 | hljs.initHighlightingOnLoad(); 13 | setUpFunctions(); 14 | d3.select(".btn").dispatch("click"); 15 | setUpGraph(); 16 | 17 | d3.select("#plot").on("submit", function() { 18 | d3.event.preventDefault(); 19 | var button = d3.select("#plot > button"); 20 | var calculating = d3.select(".calculating"); 21 | if (!button.classed("disabled")) { 22 | button.classed("disabled", true); 23 | calculating.classed("hidden", false); 24 | var value = +d3.event.target.fn_input.value; 25 | var worker = createWorker(currentFn.fn.name, value); 26 | worker.onmessage = function(event) { 27 | button.classed("disabled", false); 28 | calculating.classed("hidden", true); 29 | 30 | circleData.push({ 31 | x: value, 32 | y: event.data.time, 33 | color: event.data.color 34 | }); 35 | 36 | updateGraph(d3.select("svg"), circleData, currentFn); 37 | }; 38 | } 39 | }); 40 | 41 | d3.select("body").on("keydown", function() { 42 | if (currentCirc) { 43 | handleXAdd(); 44 | } 45 | }); 46 | 47 | d3.select("body").on("keyup", function() { 48 | handleXRemove(); 49 | }); 50 | }); 51 | 52 | function setUpFunctions() { 53 | d3 54 | .select(".btn-area") 55 | .selectAll("button") 56 | .data(functions) 57 | .enter() 58 | .append("button") 59 | .attr("type", "button") 60 | .classed("btn", true) 61 | .style("background-color", d => d.color) 62 | .style("border-color", d => d.color) 63 | .html(d => `
${d.fn.name}`) 64 | .on("click", setCurrentFunction); 65 | } 66 | 67 | function setCurrentFunction(d) { 68 | currentFn = d; 69 | var codeBlock = d3.select(".js").text(d.fn.toString()); 70 | hljs.highlightBlock(codeBlock.node()); 71 | } 72 | 73 | function setUpGraph() { 74 | var width = document.querySelector(".col-6").offsetWidth - 30; 75 | var height = width * 3 / 4; 76 | var padding = { 77 | top: 10, 78 | right: 10, 79 | bottom: 40, 80 | left: 60 81 | }; 82 | 83 | var svg = d3 84 | .select("svg") 85 | .attr("width", width) 86 | .attr("height", height) 87 | .datum({ padding: padding }); 88 | 89 | svg 90 | .append("g") 91 | .attr("transform", "translate(0, " + (height - padding.bottom) + ")") 92 | .classed("x-axis", true); 93 | 94 | svg 95 | .append("g") 96 | .attr("transform", "translate(" + padding.left + ", 0)") 97 | .classed("y-axis", true); 98 | 99 | svg 100 | .append("text") 101 | .classed("label", true) 102 | .attr( 103 | "transform", 104 | "translate(" + width / 2 + ", " + (height - padding.bottom / 4) + ")" 105 | ) 106 | .text("n"); 107 | 108 | svg 109 | .append("text") 110 | .classed("label", true) 111 | .attr("transform", "rotate(-90)") 112 | .attr("x", (-height + padding.top + padding.bottom) / 2) 113 | .attr("y", 15) 114 | .text("Time Elapsed (seconds)"); 115 | } 116 | 117 | function updateGraph(svg, circleData, currentFn) { 118 | var width = +svg.attr("width"); 119 | var height = +svg.attr("height"); 120 | var padding = svg.datum().padding; 121 | var t = d3.transition().duration(1000); 122 | var tNew = d3 123 | .transition() 124 | .delay(500) 125 | .duration(1000) 126 | .ease(d3.easeElasticOut); 127 | 128 | var xScale = d3 129 | .scaleLinear() 130 | .domain(d3.extent(circleData, d => d.x)) 131 | .range([padding.left, width - padding.right]); 132 | 133 | var yScale = d3 134 | .scaleLinear() 135 | .domain(d3.extent(circleData, d => d.y / 1000)) 136 | .range([height - padding.bottom, padding.top]); 137 | 138 | // update axes 139 | svg 140 | .select(".x-axis") 141 | .transition(t) 142 | .call( 143 | d3 144 | .axisBottom(xScale) 145 | .tickFormat(d3.format(".2s")) 146 | .tickSize(-height + padding.top + padding.bottom) 147 | .tickSizeOuter(0) 148 | ); 149 | 150 | svg 151 | .select(".y-axis") 152 | .transition(t) 153 | .call( 154 | d3 155 | .axisLeft(yScale) 156 | .tickFormat(d3.format(".2s")) 157 | .tickSize(-width + padding.left + padding.right) 158 | .tickSizeOuter(0) 159 | ); 160 | 161 | // update circles 162 | var circles = svg 163 | .selectAll("circle") 164 | .data(circleData, d => `${d.x}-${d.y}-${d.color}`); 165 | 166 | circles.exit().remove(); 167 | 168 | circles 169 | .transition(t) 170 | .attr("cx", d => xScale(d.x)) 171 | .attr("cy", d => yScale(d.y / 1000)); 172 | 173 | circles 174 | .enter() 175 | .append("circle") 176 | .attr("fill", d => d.color) 177 | .attr("r", 0) 178 | .attr("cx", d => xScale(d.x)) 179 | .attr("cy", d => yScale(d.y / 1000)) 180 | .on("mousemove", handleHover) 181 | .on("mouseout", handleMouseOut) 182 | .on("click", handleClick) 183 | .transition(tNew) 184 | .attr("r", radius); 185 | 186 | // update lines 187 | var lines = svg 188 | .selectAll("path.line") 189 | .data(getLineData(circleData), d => d.key); 190 | 191 | lines.exit().remove(); 192 | 193 | lines 194 | .enter() 195 | .append("path") 196 | .classed("line", true) 197 | .attr("stroke", d => d.key) 198 | .merge(lines) 199 | .transition(t) 200 | .attr("d", d => 201 | d3 202 | .line() 203 | .x(d => xScale(d.x)) 204 | .y(d => yScale(d.y / 1000))(d.value.sort((d1, d2) => d1.x - d2.x)) 205 | ); 206 | } 207 | 208 | function handleHover(d) { 209 | currentCirc = d3.select(this); 210 | currentCirc 211 | .interrupt() 212 | .transition(tHover) 213 | .attr("r", hoverRadius); 214 | 215 | handleXAdd(); 216 | } 217 | 218 | function handleMouseOut(d) { 219 | currentCirc = null; 220 | d3 221 | .selectAll("circle") 222 | .interrupt() 223 | .transition(tHover) 224 | .attr("r", radius); 225 | 226 | handleXRemove(); 227 | } 228 | 229 | function handleClick() { 230 | var selection; 231 | if (d3.event.metaKey) selection = currentCirc; 232 | if (d3.event.shiftKey) 233 | selection = d3.selectAll(`circle[fill="${currentCirc.attr("fill")}"]`); 234 | if (selection) { 235 | var data = selection.data(); 236 | data.forEach(d => { 237 | var idx = circleData.findIndex(c => c === d); 238 | circleData.splice(idx, 1); 239 | }); 240 | updateGraph(d3.select("svg"), circleData, currentFn); 241 | } 242 | currentCirc = null; 243 | handleXRemove(); 244 | } 245 | 246 | function handleXAdd() { 247 | var selection; 248 | if (d3.event.metaKey) selection = currentCirc; 249 | if (d3.event.shiftKey) 250 | selection = d3.selectAll(`circle[fill="${currentCirc.attr("fill")}"]`); 251 | if (selection) { 252 | selection.each(function(d, i) { 253 | var circle = d3.select(this); 254 | var newX = d3 255 | .select("svg") 256 | .append("g") 257 | .classed("circle-remove", true); 258 | var stroke = circle.attr("fill") === "#dc3545" ? "#343a40" : "#dc3545"; 259 | newX 260 | .append("line") 261 | .attr("x1", +circle.attr("cx") - hoverRadius) 262 | .attr("x2", +circle.attr("cx") + hoverRadius) 263 | .attr("y1", +circle.attr("cy") + hoverRadius) 264 | .attr("y2", +circle.attr("cy") - hoverRadius) 265 | .attr("stroke", stroke) 266 | .attr("stroke-width", hoverRadius / 3); 267 | newX 268 | .append("line") 269 | .attr("x1", +circle.attr("cx") - hoverRadius) 270 | .attr("x2", +circle.attr("cx") + hoverRadius) 271 | .attr("y1", +circle.attr("cy") - hoverRadius) 272 | .attr("y2", +circle.attr("cy") + hoverRadius) 273 | .attr("stroke", stroke) 274 | .attr("stroke-width", hoverRadius / 3); 275 | newX 276 | .interrupt() 277 | .transition(tHover) 278 | .style("opacity", 1); 279 | }); 280 | } 281 | } 282 | 283 | function handleXRemove() { 284 | var e = d3.event; 285 | var isMouseOut = e.type === "mouseout"; 286 | var isClick = e.type === "click"; 287 | var metaOff = e.type === "keyup" && !e.metaKey; 288 | var shiftOff = e.type === "keyup" && !e.shiftKey; 289 | if (isMouseOut || isClick || shiftOff) { 290 | d3 291 | .selectAll(".circle-remove") 292 | .interrupt() 293 | .transition(tHover) 294 | .style("opacity", 0) 295 | .remove(); 296 | } 297 | if (!metaOff && !isClick) handleXAdd(); 298 | } 299 | 300 | function getLineData(circleData) { 301 | return d3 302 | .nest() 303 | .key(d => d.color) 304 | .rollup(points => 305 | points.reduce((avgs, pt) => { 306 | var cur = avgs.find(d => d.x === pt.x); 307 | if (!cur) 308 | avgs.push({ 309 | x: pt.x, 310 | y: d3.mean(points, d => (d.x === pt.x ? d.y : undefined)) 311 | }); 312 | return avgs; 313 | }, []) 314 | ) 315 | .entries(circleData); 316 | } 317 | 318 | function createWorker(name, input) { 319 | var worker = new Worker("worker.js"); 320 | worker.postMessage({ name: name, input: input }); 321 | return worker; 322 | } 323 | -------------------------------------------------------------------------------- /functions.js: -------------------------------------------------------------------------------- 1 | // O(n^2) 2 | function pairCountFirst(n) { 3 | var count = 0; 4 | for (var i = 1; i <= n; i++) { 5 | for (var j = i + 1; j <= n; j++) { 6 | count++; 7 | } 8 | } 9 | return count; 10 | } 11 | 12 | // O(n) 13 | function pairCountSecond(n) { 14 | var count = 0; 15 | for (var i = 1; i <= n; i++) { 16 | count += n - 1; 17 | } 18 | return count / 2; 19 | } 20 | 21 | function pairCountThird(n) { 22 | return n * (n - 1) / 2; 23 | } 24 | 25 | // Other O(n) 26 | function countUpAndDown(n) { 27 | console.log("Going up!"); 28 | for (var i = 0; i < n; i++) { 29 | console.log(i); 30 | } 31 | console.log("At the top!\nGoing down..."); 32 | for (var j = n - 1; j >= 0; j--) { 33 | console.log(j); 34 | } 35 | console.log("Back down. Bye!"); 36 | } 37 | 38 | // O(n^2) 39 | function multiplicationTable(n) { 40 | for (var i = 0; i < n; i++) { 41 | for (var j = 0; j < n; j++) { 42 | console.log(i + " * " + j + " = " + i * j + "."); 43 | } 44 | } 45 | } 46 | 47 | // O(log(n)) 48 | function numberOfHalves(n) { 49 | var count = 0; 50 | while (n > 1) { 51 | n /= 2; 52 | count++; 53 | } 54 | return count; 55 | } 56 | 57 | // O(n * log(n)) 58 | function totalNumberOfHalves(n) { 59 | var total = 0; 60 | for (var i = 0; i < n; i++) { 61 | total += numberOfHalves(n); 62 | } 63 | return total; 64 | } 65 | 66 | // O(2^n) 67 | function logAllBinaries(n) { 68 | var count = 0; 69 | var lastNum = "1".repeat(n); 70 | while (count.toString(2) !== lastNum) { 71 | console.log(count.toString(2).padStart(n, "0")); 72 | count++; 73 | } 74 | console.log(lastNum); 75 | } 76 | 77 | var functions = [ 78 | { 79 | fn: pairCountFirst, 80 | color: "#007bff" 81 | }, 82 | { 83 | fn: pairCountSecond, 84 | color: "#8426b8" 85 | }, 86 | { 87 | fn: pairCountThird, 88 | color: "#868e96" 89 | }, 90 | { 91 | fn: countUpAndDown, 92 | color: "#28a745" 93 | }, 94 | { 95 | fn: multiplicationTable, 96 | color: "#dc3545" 97 | }, 98 | { 99 | fn: numberOfHalves, 100 | color: "#ffc107" 101 | }, 102 | { 103 | fn: totalNumberOfHalves, 104 | color: "#17a2b8" 105 | }, 106 | { 107 | fn: logAllBinaries, 108 | color: "#343a40" 109 | } 110 | ]; 111 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
36 |
37 |
38 |