Skip to content

Commit 461f9a0

Browse files
committed
start refactoring globals frame to use d3
1 parent 46bb789 commit 461f9a0

File tree

1 file changed

+95
-42
lines changed

1 file changed

+95
-42
lines changed

PyTutorGAE/js/pytutor.js

Lines changed: 95 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ function ExecutionVisualizer(domRootID, dat, params) {
100100
// create a unique ID, which is often necessary so that jsPlumb doesn't get confused
101101
// due to multiple ExecutionVisualizer instances being displayed simultaneously
102102
ExecutionVisualizer.prototype.generateID = function(original_id) {
103-
return this.visualizerID + '__' + original_id;
103+
// (it's safer to start names with a letter rather than a number)
104+
return 'v' + this.visualizerID + '__' + original_id;
104105
}
105106

106107

@@ -165,6 +166,14 @@ ExecutionVisualizer.prototype.render = function() {
165166
</tr>\
166167
</table>');
167168

169+
170+
// create a persistent globals frame
171+
this.domRoot.find("#stack").append('<div class="stackFrame" id="'
172+
+ myViz.generateID('globals') + '"><div id="' + myViz.generateID('globals_header')
173+
+ '" class="stackFrameHeader">Global variables</div><table class="stackFrameVarTable" id="'
174+
+ myViz.generateID('global_table') + '"></table></div>');
175+
176+
168177
if (this.params && this.params.codeDivHeight) {
169178
this.domRoot.find('#pyCodeOutputDiv').css('max-height', this.params.codeDivHeight);
170179
}
@@ -592,17 +601,12 @@ ExecutionVisualizer.prototype.renderPyCodeOutput = function() {
592601
.data(this.codeOutputLines)
593602
.enter().append('tr')
594603
.selectAll('td')
595-
.data(function(e, i){return [e, e];}) // bind an alias of the element to both table columns
604+
.data(function(d, i){return [d, d] /* map full data item down both columns */;})
596605
.enter().append('td')
597606
.attr('class', function(d, i) {return (i == 0) ? 'lineNo' : 'cod';})
598607
.style('cursor', function(d, i) {return 'pointer'})
599608
.html(function(d, i) {
600-
if (i == 0) {
601-
return d.lineNumber;
602-
}
603-
else {
604-
return htmlspecialchars(d.text);
605-
}
609+
return (i == 0) ? d.lineNumber : htmlspecialchars(d.text);
606610
})
607611
.on('mouseover', function() {
608612
setHoverBreakpoint(this);
@@ -1123,11 +1127,6 @@ ExecutionVisualizer.prototype.renderDataStructures = function() {
11231127
var curEntry = this.curTrace[this.curInstr];
11241128
var curToplevelLayout = this.curTraceLayouts[this.curInstr];
11251129

1126-
// clear the stack and render it from scratch.
1127-
// the heap must be PERSISTENT so that d3 can render heap transitions.
1128-
this.domRoot.find("#stack").empty();
1129-
this.domRoot.find("#stack").append('<div id="stackHeader">Frames</div>');
1130-
11311130

11321131
// Heap object rendering phase:
11331132

@@ -1164,20 +1163,20 @@ ExecutionVisualizer.prototype.renderDataStructures = function() {
11641163
.selectAll('table.heapRow')
11651164
.data(curToplevelLayout, function(objLst) {
11661165
return objLst[0]; // return first element, which is the row ID tag
1167-
})
1166+
});
11681167

11691168

11701169
// update an existing heap row
11711170
var heapColumns = heapRows
11721171
//.each(function(objLst, i) { console.log('UPDATE ROW:', objLst, i); })
11731172
.selectAll('td')
11741173
.data(function(d, i) {return d.slice(1, d.length);}, /* map over each row, skipping row ID tag */
1175-
function(objID) {return objID;} /* each object ID is unique for constancy */)
1174+
function(objID) {return objID;} /* each object ID is unique for constancy */);
11761175

11771176
// ENTER
11781177
heapColumns.enter().append('td')
11791178
.attr('class', 'toplevelHeapObject')
1180-
.attr('id', function(d, i) {return 'toplevel_heap_object_' + d;})
1179+
.attr('id', function(d, i) {return 'toplevel_heap_object_' + d;});
11811180
// remember that the enter selection is added to the update
11821181
// selection so that we can process it later ...
11831182

@@ -1191,11 +1190,11 @@ ExecutionVisualizer.prototype.renderDataStructures = function() {
11911190
// Right now, just delete the old element and render a new one in its place
11921191
$(this).empty();
11931192
renderCompoundObject(objID, $(this), true);
1194-
})
1193+
});
11951194

11961195
// EXIT
11971196
heapColumns.exit()
1198-
.remove()
1197+
.remove();
11991198

12001199

12011200
// insert new heap rows
@@ -1450,58 +1449,112 @@ ExecutionVisualizer.prototype.renderDataStructures = function() {
14501449
}
14511450

14521451

1453-
// Render globals and then stack frames:
1454-
// TODO: could convert to using d3 to map globals and stack frames directly into stack frame divs
1455-
// (which might make it easier to do smooth transitions)
1456-
// However, I need to think carefully about what to use as object keys for stack objects.
1457-
// Perhaps a combination of function name and current position index? This might handle
1458-
// recursive calls well (i.e., when there are multiple invocations of the same function
1459-
// on the stack)
1452+
// Render globals and then stack frames using d3:
1453+
1454+
// TODO: However, I need to think carefully about what to use as
1455+
// object keys for stack objects. Perhaps a combination of function
1456+
// name and current position index? This might handle recursive calls
1457+
// well (i.e., when there are multiple invocations of the same
1458+
// function on the stack)
1459+
14601460

14611461
// render all global variables IN THE ORDER they were created by the program,
14621462
// in order to ensure continuity:
1463-
if (curEntry.ordered_globals.length > 0) {
14641463

1465-
var globalsID = myViz.generateID('globals');
1466-
var globalTblID = myViz.generateID('global_table');
1464+
// Derive a list where each element contains a pair of
1465+
// [varname, value] as long as value is NOT undefined.
1466+
// (Sometimes entries in curEntry.ordered_globals are undefined,
1467+
// so filter those out.)
1468+
var realGlobalsLst = [];
1469+
$.each(curEntry.ordered_globals, function(i, varname) {
1470+
var val = curEntry.globals[varname];
1471+
1472+
// (use '!==' to do an EXACT match against undefined)
1473+
if (val !== undefined) { // might not be defined at this line, which is OKAY!
1474+
realGlobalsLst.push([varname, varname]); /* purposely map varname down both columns */
1475+
}
1476+
});
14671477

1468-
this.domRoot.find("#stack").append('<div class="stackFrame" id="' + globalsID + '"><div id="' + myViz.generateID('globals_header') + '" class="stackFrameHeader">Global variables</div></div>');
1469-
this.domRoot.find('#stack #' + globalsID).append('<table class="stackFrameVarTable" id="' + globalTblID + '"></table>');
1478+
var globalsID = myViz.generateID('globals');
1479+
var globalTblID = myViz.generateID('global_table');
14701480

1471-
var tbl = this.domRoot.find('#' + globalTblID);
1481+
var globalsD3 = myViz.domRootD3.select('#' + globalTblID)
1482+
.selectAll('tr')
1483+
.data(realGlobalsLst, function(d) {
1484+
return d[0]; // use variable name as key
1485+
});
1486+
14721487

1473-
$.each(curEntry.ordered_globals, function(i, varname) {
1474-
var val = curEntry.globals[varname];
1475-
// (use '!==' to do an EXACT match against undefined)
1476-
if (val !== undefined) { // might not be defined at this line, which is OKAY!
1477-
tbl.append('<tr><td class="stackFrameVar">' + varname + '</td><td class="stackFrameValue"></td></tr>');
1478-
var curTr = tbl.find('tr:last');
1488+
// ENTER
1489+
globalsD3.enter()
1490+
.append('tr')
1491+
.selectAll('td')
1492+
.data(function(d, i){return d;}) /* map varname down both columns */
1493+
.enter()
1494+
.append('td')
1495+
.attr('class', function(d, i) {return (i == 0) ? 'stackFrameVar' : 'stackFrameValue';})
1496+
.html(function(d, i) {
1497+
return (i == 0) ? d : '' /* initialize in each() later */;
1498+
})
1499+
// remember that the enter selection is added to the update
1500+
// selection so that we can process it later ...
1501+
1502+
// UPDATE
1503+
globalsD3
1504+
.order() // VERY IMPORTANT to put in the order corresponding to data elements
1505+
.selectAll('td')
1506+
.data(function(d, i){return d;}) /* map varname down both columns */
1507+
.each(function(varname, i) {
1508+
console.log('EACH!', i, varname);
1509+
1510+
if (i == 1) {
1511+
var val = curEntry.globals[varname];
14791512

14801513
if (isPrimitiveType(val)) {
1481-
renderPrimitiveObject(val, curTr.find("td.stackFrameValue"));
1514+
$(this).empty(); // crude but effective; maybe soften with a transition later
1515+
renderPrimitiveObject(val, $(this));
14821516
}
1483-
else{
1517+
else {
1518+
$(this).empty(); // crude but effective; maybe soften with a transition later
1519+
1520+
// or even better, simply keep <div id=varDivID> around if it already exists
1521+
// so that jsPlumb connectors can persist.
1522+
1523+
14841524
// add a stub so that we can connect it with a connector later.
14851525
// IE needs this div to be NON-EMPTY in order to properly
14861526
// render jsPlumb endpoints, so that's why we add an "&nbsp;"!
14871527

14881528
// make sure varname doesn't contain any weird
14891529
// characters that are illegal for CSS ID's ...
14901530
var varDivID = myViz.generateID('global__' + varnameToCssID(varname));
1491-
curTr.find("td.stackFrameValue").append('<div id="' + varDivID + '">&nbsp;</div>');
1531+
$(this).append('<div id="' + varDivID + '">&nbsp;</div>');
14921532

14931533
assert(!connectionEndpointIDs.has(varDivID));
14941534
var heapObjID = myViz.generateID('heap_object_' + getRefID(val));
14951535
connectionEndpointIDs.set(varDivID, heapObjID);
14961536
}
14971537
}
14981538
});
1539+
1540+
globalsD3.exit()
1541+
.remove();
1542+
1543+
1544+
// for aesthetics, hide globals if there aren't any globals to display
1545+
if (curEntry.ordered_globals.length == 0) {
1546+
this.domRoot.find('#' + globalsID).hide();
1547+
}
1548+
else {
1549+
this.domRoot.find('#' + globalsID).show();
14991550
}
15001551

15011552

1553+
/*
15021554
$.each(curEntry.stack_to_render, function(i, e) {
15031555
renderStackFrame(e, i, e.is_zombie);
15041556
});
1557+
*/
15051558

15061559

15071560
function renderStackFrame(frame, ind, is_zombie) {
@@ -1601,9 +1654,9 @@ ExecutionVisualizer.prototype.renderDataStructures = function() {
16011654
var c = allConnections[i];
16021655

16031656
// this is VERY VERY fragile code, since it assumes that going up
1604-
// five layers of parent() calls will get you from the source end
1657+
// FOUR layers of parent() calls will get you from the source end
16051658
// of the connector to the enclosing stack frame
1606-
var stackFrameDiv = c.source.parent().parent().parent().parent().parent();
1659+
var stackFrameDiv = c.source.parent().parent().parent().parent();
16071660

16081661
// if this connector starts in the selected stack frame ...
16091662
if (stackFrameDiv.attr('id') == frameID) {

0 commit comments

Comments
 (0)