Skip to content

Instantly share code, notes, and snippets.

@vazexqi
Last active June 30, 2016 14:34
Show Gist options
  • Save vazexqi/5385456 to your computer and use it in GitHub Desktop.
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>
<!--
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