Last active
June 30, 2016 14:34
-
-
Save vazexqi/5385456 to your computer and use it in GitHub Desktop.
Router Result Visualization. See it in action <https://www.youtube.com/watch?v=4m0V6fpwROA>
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
Modified and derived from | |
<http://www.html5rocks.com/en/tutorials/file/dndfiles/#toc-selecting-files-dnd> | |
<http://bl.ocks.org/mbostock/4063663> | |
Uses $.contextMenu jQuery plug-in from <https://github.com/medialize/jQuery-contextMenu> | |
It's dual licensed so we are choosing its MIT License option. | |
Code by the Illinois VLSI CAD teaching staff. | |
Released under the University of Illinois/NCSA Open Source License | |
--> | |
<html xmlns="http://www.w3.org/1999/xhtml"> | |
<head> | |
<meta http-equiv="content-type" content="text/html; charset=utf-8"/> | |
<title> | |
Visualize Routes for Programming Assignment #4 | |
</title> | |
<!-- Serve locally because we need https from inner frame (if we use it) in Google Chrome--> | |
<script type="text/javascript" src="https://spark-public.s3.amazonaws.com/vlsicad/javascript_tools/d3.min.js" charset="utf-8"></script> | |
<script type="text/javascript" src="https://spark-public.s3.amazonaws.com/vlsicad/javascript_tools/contextMenu.js" charset="utf-8"></script> | |
<!-- This is the CSS needed for right-click menu --> | |
<link type="text/css" rel="stylesheet" href="https://spark-public.s3.amazonaws.com/vlsicad/javascript_tools/jQuery.contextMenu.css"></link> | |
<style> | |
/* For drag-n-drop */ | |
.drop_zone{ | |
border:2px dashed #bbb; | |
-moz-border-radius:5px; | |
-webkit-border-radius:5px; | |
border-radius:5px; | |
padding:20px; | |
margin-bottom: 5px; | |
margin-top: 5px; | |
text-align:center; | |
font:20pt bold,"Vollkorn"; | |
color:#bbb | |
} | |
/* For actual chart */ | |
svg { | |
font: 8px sans-serif; | |
padding: 10px; | |
} | |
.axis, | |
.frame { | |
shape-rendering: crispEdges; | |
} | |
.axis line { | |
stroke: #ddd; | |
} | |
.axis path { | |
display: none; | |
} | |
.frame { | |
fill: none; | |
stroke: #aaa; | |
} | |
/* For tooltip */ | |
div.tooltip { | |
position: absolute; | |
text-align: center; | |
width: 70px; | |
height: 10px; | |
padding: 8px; | |
font: 10px sans-serif; | |
background: #ddd; | |
border: solid 1px #aaa; | |
border-radius: 5px; | |
pointer-events: none; | |
} | |
/* Placeholder to facilite selection of route elements */ | |
.layer1_obstacles {} /* Denotes obstacles */ | |
.layer1_cost {} /* Denotes non-unit grids */ | |
.layer2_obstacles {} /* Denote obstacles */ | |
.layer2_cost {} /* Denotes non-unit grids */ | |
.routes {} /* Denotes the routes */ | |
.vias {} /* Denotes the via markers */ | |
.startPositionMarkers {} /* Denotes the start markers */ | |
.endPositionMarkers {} /* Denotes the end markers */ | |
</style> | |
</head> | |
<body> | |
<div id="benchmark_zone" class="drop_zone" style="float:left; width:45%">1) Drop benchmark file here</div> | |
<div id="router_zone" class="drop_zone" style="float:right; width:45%">2) Drop router results file here</div> | |
<script> | |
var edu = { | |
illinois: { | |
vlsicad: (function() { | |
////////////// | |
// DRAG-N-DROP | |
////////////// | |
var handleFileSelectBenchmark = function(evt) { | |
evt.stopPropagation(); | |
evt.preventDefault(); | |
var files = evt.dataTransfer.files; // FileList object. | |
var gridFile = files[0]; | |
var reader = new FileReader(); | |
reader.onload = function(event) { | |
var NUM_LAYERS = 2; | |
var rawText = event.target.result; | |
var lines = rawText.split("\n"); | |
var data = []; | |
var numVCells, numHCells, lineIndex, rawLine, splitData; | |
// First line specifies size of grid | |
splitData = lines[0].trim().split(/\s+/); | |
numHCells = parseInt(splitData[0]); | |
numVCells = parseInt(splitData[1]); | |
// Subsequent lines specify data for each grid | |
// We know that we have numVCells * NUM_LAYERS of lines to read | |
var currentX = 0, currentY = 0, layer = 1, attribute; | |
for(lineIndex = 1; lineIndex <= numVCells * NUM_LAYERS; lineIndex++) { | |
rawLine = lines[lineIndex]; | |
if(!isBlank(rawLine)) { | |
splitData = rawLine.trim().split(/\s+/); | |
for(currentX = 0; currentX < numHCells; currentX++) { | |
attribute = parseInt(splitData[currentX]); | |
data.push({x: currentX, y: currentY, layer: layer, attribute: attribute}); | |
} | |
} | |
currentY++; | |
if(currentY === numVCells) { | |
currentY = 0; | |
layer++; | |
} | |
} | |
drawGrid(numHCells, numVCells, data); | |
}; | |
reader.readAsText(gridFile); | |
} | |
var isBlank = function(string) { | |
return (!string || /^\s*$/.test(string)); | |
} | |
var isNetStartingSymbol = function(string) { | |
var trimmed = string.trim(); | |
return trimmed.split(/\s+/).length == 1 | |
&& trimmed.match(/\d+/) !== null | |
&& !isNetTerminatingSymbol(string); | |
} | |
var isNetTerminatingSymbol = function(string) { | |
return string === '0'; | |
} | |
var isPossibleRoutingCell = function(string) { | |
return string.trim().split(/\s+/).length === 3; | |
} | |
var handleFileSelectRouter = function(evt) { | |
evt.stopPropagation(); | |
evt.preventDefault(); | |
var files = evt.dataTransfer.files; // FileList object. | |
// User can drag-n-drop multiple files at once so we pick only the first one. | |
// I don't know of a way to prevent drag-n-drop of multiple files at once. | |
var router = files[0]; | |
var reader = new FileReader(); | |
reader.onload = function(event) { | |
var rawText = event.target.result; | |
var lines = rawText.split("\n"); | |
var data = []; | |
var numNets, netNumber, currentNetData, lineIndex, rawLine, splitData, layer, x, y; | |
// First line specify number of nets | |
numNets = parseInt(lines[0]); // No need to use it for now | |
// Subsequent lines specify the position routes (which cells are on) | |
for(lineIndex = 1; lineIndex < lines.length; lineIndex++) { | |
rawLine = lines[lineIndex].trim(); | |
if(!isBlank(rawLine)) { | |
if(isNetStartingSymbol(rawLine)) { | |
netNumber = parseInt(rawLine); | |
currentNetData = {net: netNumber, points: []}; | |
} | |
else if(isPossibleRoutingCell(rawLine)) { | |
splitData = rawLine.split(/\s+/); | |
layer = parseInt(splitData[0]); | |
x = parseInt(splitData[1]); | |
y = parseInt(splitData[2]); | |
currentNetData['points'].push({layer: layer, x: x, y: y}); | |
} else if(isNetTerminatingSymbol(rawLine)){ | |
data.push(currentNetData); | |
} else { | |
// TODO: Throw some exception since we have found a line that doesn't match | |
} | |
} | |
} | |
drawRoutes(data); | |
}; | |
reader.readAsText(router); | |
} | |
var handleDragOver = function(evt) { | |
evt.stopPropagation(); | |
evt.preventDefault(); | |
evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy. | |
} | |
//////////////// | |
// VISUALIZATION | |
//////////////// | |
// Local to this module | |
// Shared variables between both drawing routines | |
var x, y, yShift | |
var numHCells, numVCells; | |
var hSize, vSize; | |
var CELL_SIZE = 16; | |
var padding; | |
var drawGrid = function(_numHCells, _numVCells, data) { | |
d3.select("svg").remove(); | |
padding = 70, | |
numHCells = _numHCells; | |
numVCells = _numVCells; | |
hSize = CELL_SIZE * numHCells, | |
vSize = CELL_SIZE * numVCells; | |
// SVG | |
////// | |
var svg = d3.select("body").append("svg") | |
drawAxes(data); | |
drawCells(data); | |
} | |
var drawAxes = function(data) { | |
// AXES | |
//////// | |
var SUBDIVISION = 5; | |
var svg = d3.select("svg"); | |
x = d3.scale.linear() | |
.domain([0, numHCells]) | |
.range([padding / 2, hSize + padding / 2]); | |
y = d3.scale.linear() | |
.domain([0, numVCells]) | |
.range([vSize + padding / 2, padding / 2]); | |
var xAxisBottom = d3.svg.axis() | |
.scale(x) | |
.orient("bottom") | |
.ticks(numHCells / SUBDIVISION) | |
.tickSubdivide(SUBDIVISION - 1) | |
.tickSize(-(vSize + padding / 2)); | |
var xAxisTop = d3.svg.axis() | |
.scale(x) | |
.orient("top") | |
.ticks(numHCells / SUBDIVISION) | |
.tickSubdivide(SUBDIVISION - 1) | |
.tickSize((vSize + padding / 2)); | |
var spacer = 20; // The y-axis display needs a bit more room to display since it is left flushed | |
var yAxisLeft = d3.svg.axis() | |
.scale(y) | |
.orient("left") | |
.ticks(numVCells / SUBDIVISION) | |
.tickSubdivide(SUBDIVISION - 1) | |
.tickSize(-(hSize + padding / 2)); | |
var yAxisRight = d3.svg.axis() | |
.scale(y) | |
.orient("right") | |
.ticks(numVCells / SUBDIVISION) | |
.tickSubdivide(SUBDIVISION - 1) | |
.tickSize((hSize + padding / 2)); | |
svg.attr("width", hSize + padding * 2) | |
.attr("height", vSize + padding * 2) | |
.append("g") | |
.attr("transform", "translate(" + padding / 2 + "," + padding / 2 + ")"); | |
var xAxisBottomGroup = svg.append("g") | |
.attr("class", "xaxis axis") | |
.attr("transform", "translate(0," + (vSize + padding / 2 + spacer) + ")") | |
.call(xAxisBottom); | |
var xAxisTopGroup = svg.append("g") | |
.attr("class", "xaxis axis") | |
.attr("transform", "translate(0," + (vSize + padding / 2 + spacer) + ")") | |
.call(xAxisTop); | |
var yAxisLeftGroup = svg.append("g") | |
.attr("class", "yaxis axis") | |
.attr("transform", "translate(" + spacer + ",0)") | |
.call(yAxisLeft); | |
var yAxisRightGroup = svg.append("g") | |
.attr("class", "yaxis axis") | |
.attr("transform", "translate(" + spacer + ",0)") | |
.call(yAxisRight); | |
// Shift the text so that the are in the middle of the grid | |
svg.selectAll(".yaxis text") | |
.attr("transform", function(d) { | |
return "translate(0," + -(CELL_SIZE / 2) + ")"; | |
}); | |
svg.selectAll(".xaxis text") | |
.attr("transform", function(d) { | |
return "translate(" + (CELL_SIZE / 2) + ",0)"; | |
}); | |
} | |
var drawCells = function(data){ | |
// CELLS | |
//////// | |
var svg = d3.select("svg"); | |
// Need to shift the y-axis by cellHeight so that (0,0) is within the cell | |
// Recall that the position of rect is determined by its top left corner | |
yShift = d3.scale.linear() | |
.domain([0, numVCells]) | |
.range([vSize + padding / 2 - CELL_SIZE, padding / 2 - CELL_SIZE]); | |
// Populate with new data | |
var LAYER1_COLOR = "#9ecae1"; // Blue | |
var LAYER2_COLOR = "#fdae6b"; // Orange | |
var grayScale = d3.scale.linear() | |
.domain([1, 40]) | |
.range(["white", "black"]); | |
svg.selectAll("rect") | |
.data(removeEmptyData(data)) | |
.enter() | |
.append("rect") | |
.attr("x", function(d) { | |
return x(d.x)}) | |
.attr("y", function(d) { | |
return yShift(d.y)}) | |
.attr("width", CELL_SIZE) | |
.attr("height", CELL_SIZE) | |
.attr("fill-opacity", 0.3) | |
.attr("class", function(d) { | |
if(d.attribute === -1) // Blockage | |
return d.layer === 1 ? "layer1_obstacles" : "layer2_obstacles"; | |
else | |
return d.layer === 1 ? "layer1_cost" : "layer2_cost"; | |
}) | |
.style("fill", function(d) { | |
if(d.attribute === -1) // Blockage | |
return d.layer === 1 ? LAYER1_COLOR : LAYER2_COLOR; | |
else if(d.attribute !== 1) | |
return grayScale(d.attribute); | |
else | |
return "none"; // Special case unit-cost so that we don't have off white colors | |
}); | |
} | |
var removeEmptyData = function(data) { | |
return data.filter(function(element, index, array) { | |
return element.attribute != 1; // Don't draw unit cost cells | |
}); | |
} | |
var xCenter, yCenter; // Functions that will be used in the drawing routines | |
var drawRoutes = function(data) { | |
xCenter = d3.scale.linear() | |
.domain([0, numHCells]) | |
.range([padding / 2 + CELL_SIZE / 2, hSize + padding / 2 + CELL_SIZE / 2]); | |
yCenter = d3.scale.linear() | |
.domain([0, numVCells]) | |
.range([vSize + padding / 2 - CELL_SIZE / 2, padding / 2 - CELL_SIZE / 2]); | |
var svg = d3.select("svg"); | |
if(svg) { // only perform if we have an existing grid from benchmark file | |
drawPaths(data); // Draw the normal routes | |
markStartAndEndForPath(data); // Explicitly show the start and end points for routes | |
drawVias(data); // Explicitly show via points as well | |
} | |
} | |
var drawPaths = function(data) { | |
var STROKE_WIDTH = 4; | |
var svg = d3.select("svg"); | |
var color = d3.scale.category10(); | |
var routerPath = d3.svg.line() | |
.x(function(d) { | |
return xCenter(d.x); }) | |
.y(function(d) { | |
return yCenter(d.y); }) | |
.interpolate("linear"); | |
svg.selectAll(".cells").remove(); // Clear previous routes, if any. | |
d3.selectAll(".tooltip").remove(); // Clear previous tooltips, if any | |
var div = d3.select("body").append("div") | |
.attr("class", "tooltip") | |
.style("opacity", 0); | |
svg.selectAll(".cells") | |
.data(data) | |
.enter() | |
.append("g") | |
.attr("class", "cells") | |
.attr("stroke-opacity", 0.5) | |
.selectAll("path") | |
.data(createPathDataPoints) | |
.enter() | |
.append("path") | |
.attr("stroke-width", STROKE_WIDTH) | |
.attr("fill", "none") | |
.attr("stroke", function(d) { return d ? color(d.layer) : null;}) | |
.attr("d", function(d){ return d ? routerPath(d.points) : null;}) | |
.on("mouseover", function(d) { | |
div.transition() | |
.duration(10) | |
.style("opacity", 0.8); | |
div.html("Net #" + d.net) | |
.style("left", (d3.event.pageX) + "px") | |
.style("top", (d3.event.pageY) + "px"); | |
}) | |
.on("mouseout", function(d) { | |
div.transition() | |
.duration(10) | |
.style("opacity", 0); | |
}); | |
} | |
var markStartAndEndForPath = function(data) { | |
var START_MARKER = "square"; | |
var START_COLOR = "#2ca02c"; // Green | |
var END_MARKER = "square"; | |
var END_COLOR = "#d62728"; // Red | |
var STROKE_WIDTH = 2; | |
var svg = d3.select("svg"); | |
var color = d3.scale.category20(); | |
svg.selectAll(".startPositionMarkers").remove(); // Clear previous markers, if any | |
svg.selectAll(".endPositionMarkers").remove(); // Clear previous markers, if any | |
// Draw start | |
svg.selectAll(".startPositionMarkers") | |
.data(filterPoints(data, function(array) { return array[0]; })) | |
.enter() | |
.append("path") | |
.attr("stroke-width", STROKE_WIDTH) | |
.attr("stroke", START_COLOR) | |
.attr("fill", "none") | |
.attr("class", "startPositionMarkers") | |
.attr("transform", function(d) { | |
return "translate(" + xCenter(d.x) + "," + yCenter(d.y) + ")"; | |
}) | |
.attr("d", d3.svg.symbol().type(START_MARKER).size(128)); | |
// Draw end | |
svg.selectAll(".endPositionMarkers") | |
.data(filterPoints(data, function(array) { return array[array.length - 1]; })) | |
.enter() | |
.append("path") | |
.attr("stroke-width", STROKE_WIDTH) | |
.attr("stroke", END_COLOR) | |
.attr("fill", "none") | |
.attr("class", "endPositionMarkers") | |
.attr("transform", function(d) { | |
return "translate(" + xCenter(d.x) + "," + yCenter(d.y) + ")"; | |
}) | |
.attr("d", d3.svg.symbol().type(END_MARKER).size(128)); | |
} | |
var drawVias = function(data) { | |
var svg = d3.select("svg"); | |
svg.selectAll(".vias").remove(); // Clear previous vias, if any | |
svg.selectAll(".vias") | |
.data(filterViaPoints(data)) | |
.enter() | |
.append("path") | |
.attr("class", "vias") | |
.attr("transform", function(d) { return "translate(" + xCenter(d.x) + "," + yCenter(d.y) + ")"; }) | |
.attr("d", d3.svg.symbol().type("cross")); | |
} | |
var createPathDataPoints = function(d) { | |
var UNKNOWN_LAYER = -1; | |
var processedData = []; | |
var index, x, y, currentLayerData, currentLayer = UNKNOWN_LAYER; | |
for(index = 0; index < d.points.length; index++) { | |
if(currentLayer === UNKNOWN_LAYER) { | |
currentLayer = d.points[index].layer; | |
currentLayerData = {layer: currentLayer, net: d.net, points: []}; | |
} | |
if(d.points[index].layer === currentLayer) { | |
x = d.points[index].x; | |
y = d.points[index].y; | |
currentLayerData.points.push({x: x, y:y}); | |
} else { | |
// Skips this layer via datapoint | |
// STRONG ASSUMPTION: All transitions between layers must be accompanied with a | |
// via as mentioned in the write up | |
processedData.push(currentLayerData); | |
currentLayer = UNKNOWN_LAYER; | |
currentLayerData = null; | |
} | |
} | |
// There is the possibility that we never switch layers, in that case, | |
// push the value here | |
if(currentLayerData !== null) { | |
processedData.push(currentLayerData); | |
} | |
return processedData; | |
} | |
var filterPoints = function(d, filterFn) { | |
var points = []; | |
d.forEach(function(element, index, array) { | |
if(element.points.length !== 0) { | |
var pointData = filterFn(element.points); | |
points.push({x: pointData.x, y: pointData.y}); | |
} | |
}); | |
return points; | |
} | |
var filterViaPoints = function(d) { | |
var VIA_LAYER = 3; | |
var viaPoints = []; | |
d.forEach(function(element, index, array) { | |
element.points.forEach(function(element, index, array) { | |
if(element.layer === VIA_LAYER) { | |
viaPoints.push({x: element.x, y: element.y}); | |
} | |
}); | |
}); | |
return viaPoints; | |
} | |
// TOGGLE LAYERS | |
///////////////// | |
var toggleFunctionFactory = function(CSSClass) { | |
var isDisplayed = true; | |
var localClass = CSSClass; | |
return function() { | |
var svg = d3.select("svg"); | |
var elements = svg.selectAll(localClass); | |
if(isDisplayed) { | |
elements.style("visibility", "hidden"); | |
isDisplayed = false; | |
} else { | |
elements.style("visibility", "visible"); | |
isDisplayed = true; | |
} | |
}; | |
} | |
return { | |
handleDragOver: handleDragOver, | |
handleFileSelectBenchmark: handleFileSelectBenchmark, | |
handleFileSelectRouter: handleFileSelectRouter, | |
toggleLayer1ObstacleCells: toggleFunctionFactory(".layer1_obstacles"), | |
toggleLayer2ObstacleCells: toggleFunctionFactory(".layer2_obstacles") | |
}; | |
})() | |
} | |
}; | |
if (window.File && window.FileReader && window.FileList && window.Blob) { | |
// Great success! All the File APIs are supported. | |
} else { | |
alert('The File APIs are not fully supported in this browser. Consider using one from http://www.html5rocks.com/en/features/file_access'); | |
} | |
// Setup the dnd listeners. | |
var benchmarkZone = document.getElementById('benchmark_zone'); | |
benchmarkZone.addEventListener('dragover', edu.illinois.vlsicad.handleDragOver, false); | |
benchmarkZone.addEventListener('drop', edu.illinois.vlsicad.handleFileSelectBenchmark, false); | |
var routerZone = document.getElementById('router_zone'); | |
routerZone.addEventListener('dragover', edu.illinois.vlsicad.handleDragOver, false); | |
routerZone.addEventListener('drop', edu.illinois.vlsicad.handleFileSelectRouter, false); | |
// Setup the right-click menu | |
$(function(){ | |
$.contextMenu({ | |
selector: 'svg', | |
callback: function(key, options) { | |
switch(key) | |
{ | |
case 'layer1-toggle-obstacle': | |
edu.illinois.vlsicad.toggleLayer1ObstacleCells(); | |
break; | |
case 'layer2-toggle-obstacle': | |
edu.illinois.vlsicad.toggleLayer2ObstacleCells(); | |
break; | |
default: | |
} | |
}, | |
// There is a bug with the current version of contextMenu. If you don't have | |
// submenus, the evt is fired twice in quick succession. That is why we have | |
// submenus even though it is unneccessary | |
items: { | |
"obstacles": { | |
"name": "Obstacles", | |
"items": { | |
"layer1-toggle-obstacle": {"name": "Toggle Layer 1 Obstacles"}, | |
"layer2-toggle-obstacle": {"name": "Toggle Layer 2 Obstacles"}, | |
} | |
}, | |
} | |
}); | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment