es6/render/html_memory.es6
/* eslint no-unused-vars: 0 */
import {$, d3, uuid, jsplumb} from "nbtutor-deps";
export class MemoryModelUI{
constructor(trace_history, d3Root){
this.trace_history = trace_history;
this.d3Root = d3Root;
this.connectors = [];
this.objects_rendered = [];
this.jsplumb = jsplumb.getInstance({
Container: this.d3Root[0],
});
}
_setHover(cls, state){
d3.select(".nbtutor-var-object." + cls).select(".nbtutor-var-value")
.classed("nbtutor-hover", state);
d3.select(".nbtutor-var-object." + cls).select("table")
.classed("nbtutor-hover", state);
d3.selectAll("svg." + cls).classed("jtk-hover", state);
d3.selectAll("path." + cls).classed("jtk-hover", state);
}
_connectObjects(){
let that = this;
this.connectors.map((con) => {
let stateMachineConnector = {
paintStyle: {lineWidth: 2, stroke: "#056"},
endpoint: "Blank",
anchors: ["Right", "Left"],
connector: ["Bezier", {"curviness": 80}],
detachable: false,
overlays: [
["Arrow", {length: 10, width: 10, location: 1}]
],
};
stateMachineConnector.cssClass = con.from;
stateMachineConnector.overlays[0][1].cssClass = con.from;
if (con.from[0] == 'r'){
stateMachineConnector.anchors = [
"Top", ["Left", "Right"]
];
}
d3.select("#" + con.to).classed(con.from, true);
this.jsplumb.connect({
source: con.from,
target: con.to
}, stateMachineConnector);
});
}
_createHeapDiv(object){
let position = object.options.position || 'right';
let d3Obj = this.d3Root.select(".nbtutor-heap")
.append("div")
.attr("class", "nbtutor-heap-row-" + position)
.append("div")
.attr("class", "nbtutor-var-object")
.attr("id", object.uuid);
return d3Obj;
}
_createPrimitive(object){
let d3Obj = this._createHeapDiv(object);
d3Obj.append("div")
.attr("class", "nbtutor-var-type")
.text(object.type);
d3Obj.append("div")
.attr("class", "nbtutor-var-value")
.text(object.value);
}
_createSequence(object, tracestep){
let that = this;
let heap_history = this.trace_history.heap_history;
object.values.map((obj) => {
let child = heap_history.getObjectById(tracestep, obj.id);
if (!object.options.inline || child.catagory != 'primitive'){
obj.uuid = 'r-' + uuid.v4();
that._createObject(child, tracestep);
that.connectors.push({
from: obj.uuid,
to: child.uuid
});
}
});
let d3Obj = this._createHeapDiv(object);
d3Obj.append("div")
.attr("class", "nbtutor-var-type")
.text(object.type);
let d3Table = d3Obj.append("table")
.attr("class", "nbtutor-seq-" + object.type);
let d3IndRow = d3Table.append("tr");
let d3ValRow = d3Table.append("tr");
// Create sequence index numbers
let indexes = [];
for (let i=0; i<object.values.length; i++){
indexes.push(i);
}
// Add an ellipsis to the end of a long sequence
let ind = object.values.length-1;
if (object.options.ellipsis){
if (object.values[ind] === '...'){
indexes[ind] = "...";
} else {
indexes.push("...");
object.values.push("...");
}
}
// Add index numbers
d3IndRow.selectAll("td")
.data(indexes)
.enter()
.append("td")
.attr("class", "nbtutor-var-index")
.text((d) => d);
// Add sequence anchors
let d3Refs = d3ValRow.selectAll("td")
.data(object.values)
.enter()
.append("td")
.attr("class", "nbtutor-anchor-from");
d3Refs.append("div")
.each(function(d){
if (object.options.ellipsis && d === "..."){
d3.select(this).text("...");
} else {
let child = heap_history.getObjectById(tracestep, d.id);
if (object.options.inline && child.catagory === 'primitive'){
d3.select(this).text(child.value);
} else {
d3.select(this).attr("id", (d) => d.uuid);
}
}
});
// Toggle mouse hover over ref
d3Refs.on('mouseover', function(d){
d3.select(this).classed("nbtutor-hover", true);
that._setHover(d.uuid, true);
});
d3Refs.on('mouseout', function(d){
d3.select(this).classed("nbtutor-hover", false);
that._setHover(d.uuid, false);
});
}
_createArray(object, tracestep){
let d3Obj = this._createHeapDiv(object);
d3Obj.append("div")
.attr("class", "nbtutor-var-type")
.text(object.type);
let d3Table = d3Obj.append("table")
.attr("class", "nbtutor-seq-array");
let d3IndRow = d3Table.append("tr");
let d3ValRow = d3Table.append("tr");
// Create sequence index numbers
let indexes = [];
for (let i=0; i<object.values.length; i++){
indexes.push(i);
}
// Add an ellipsis to the end of a long sequence
let ind = object.values.length-1;
if (object.options.ellipsis){
if (object.values[ind] === '...'){
indexes[ind] = "...";
} else {
indexes.push("...");
object.values.push("...");
}
}
// Add index numbers
d3IndRow.selectAll("td")
.data(indexes)
.enter()
.append("td")
.attr("class", "nbtutor-var-index")
.text((d) => d);
// Add sequence anchors
d3ValRow.selectAll("td")
.data(object.values)
.enter()
.append("td")
.attr("class", "nbtutor-anchor-from")
.append("div")
.text((d) => d);
}
_createKeyValue(object, tracestep){
let that = this;
let heap_history = this.trace_history.heap_history;
object.values.map((obj) => {
let key = heap_history.getObjectById(tracestep, obj.key_id);
if (!object.options.inline_keys || key.catagory != 'primitive'){
obj.key_uuid = 'r-' + uuid.v4();
that._createObject(key, tracestep);
that.connectors.push({
from: obj.key_uuid,
to: key.uuid
});
}
let value = heap_history.getObjectById(tracestep, obj.val_id);
if (!object.options.inline_vals || value.catagory != 'primitive'){
obj.val_uuid = 'r-' + uuid.v4();
that._createObject(value, tracestep);
that.connectors.push({
from: obj.val_uuid,
to: value.uuid
});
}
});
let d3Obj = this._createHeapDiv(object);
d3Obj.append("div")
.attr("class", "nbtutor-var-type")
.text(object.type);
let d3Table = d3Obj.append("table")
.attr("class", "nbtutor-seq-key-value");
// Add an ellipsis to the end of a long sequence
let ind = object.values.length-1;
if (object.options.ellipsis){
if (object.values[ind] != '...'){
object.values.push("...");
}
}
let d3Rows = d3Table.selectAll("tr")
.data(object.values)
.enter()
.append("tr");
let d3Keys = d3Rows.append("td")
.attr("class", "nbtutor-anchor-from");
let d3Vals = d3Rows.append("td")
.attr("class", "nbtutor-anchor-from");
d3Keys.append("div")
.each(function(d){
if (object.options.ellipsis && d === "..."){
d3.select(this).text("...");
} else {
let key = heap_history.getObjectById(tracestep, d.key_id);
if (object.options.inline_keys && key.catagory === 'primitive'){
d3.select(this).text(key.value);
} else {
d3.select(this).attr("id", (d) => d.key_uuid);
}
}
});
d3Vals.append("div")
.each(function(d){
if (object.options.ellipsis && d === "..."){
d3.select(this).text("...");
} else {
let value = heap_history.getObjectById(tracestep, d.val_id);
if (object.options.inline_vals && value.catagory === 'primitive'){
d3.select(this).text(value.value);
} else {
d3.select(this).attr("id", (d) => d.val_uuid);
}
}
});
// Toggle mouse hover over ref
d3Keys.on('mouseover', function(d){
d3.select(this).classed("nbtutor-hover", true);
that._setHover(d.key_uuid, true);
});
d3Keys.on('mouseout', function(d){
d3.select(this).classed("nbtutor-hover", false);
that._setHover(d.key_uuid, false);
});
d3Vals.on('mouseover', function(d){
d3.select(this).classed("nbtutor-hover", true);
that._setHover(d.val_uuid, true);
});
d3Vals.on('mouseout', function(d){
d3.select(this).classed("nbtutor-hover", false);
that._setHover(d.val_uuid, false);
});
}
_createObject(object, tracestep){
if (this.objects_rendered.indexOf(object.uuid) >= 0){
return;
}
this.objects_rendered.push(object.uuid);
switch (object.catagory){
case "primitive":
this._createPrimitive(object);
break;
case "sequence":
this._createSequence(object, tracestep);
break;
case "array":
this._createArray(object, tracestep);
break;
case "key-value":
this._createKeyValue(object, tracestep);
break;
default:
this._createPrimitive({
uuid: object.uuid,
type: object.type,
options: object.options,
value: 'OBJECT',
});
}
}
create(tracestep){
// First destroy any previous visualization
this.destroy();
// Init parent div
this.d3Root.append("div")
.attr("class", "nbtutor-stack");
this.d3Root.append("div")
.attr("class", "nbtutor-heap");
let stack_history = this.trace_history.stack_history;
let heap_history = this.trace_history.heap_history;
let stack_frames = stack_history.getStackFrames(tracestep);
// Create tables for each frame
let d3Frames = this.d3Root.select(".nbtutor-stack").selectAll("div")
.data(stack_frames, (d) => d.uuid)
.enter()
.append("div")
.attr("class", "nbtutor-frame");
d3Frames.append("table")
.attr("id", (d) => d.uuid)
.append("thead")
.append("tr")
.append("th")
.attr("colspan", 2)
.text((d) => d.name);
d3Frames.select("table").append("tbody");
d3Frames.select("table").append("tfoot");
// Add names to each frame
let d3Names = d3Frames.select("tbody").selectAll("tr")
.data((d) => {
return d.vars.map((v) => {
return {object: v, options: d.options};
});
}, (d) => d.object.name)
.enter()
.append("tr");
let that = this;
d3Names.append("td")
.attr("class", "nbtutor-var-name")
.text((d) => d.object.name);
d3Names.append("td")
.attr("class", "nbtutor-anchor-from")
.append("div")
.attr("id", (d) => d.object.uuid)
.each(function(d){
let object = heap_history.getObjectById(tracestep, d.object.id);
if (d.options.inline && object.catagory === 'primitive'){
d3.select(this).text(object.value);
} else {
that._createObject(object, tracestep);
that.connectors.push({
from: d.object.uuid,
to: object.uuid
});
}
});
// Create connector lines
this._connectObjects();
// Toggle active frame
let d3Tables = this.d3Root.select(".nbtutor-stack").selectAll("table");
d3Tables.classed("nbtutor-active", false);
d3.select(d3Tables[0].pop()).classed("nbtutor-active", true);
// Toggle mouse hover over name
d3Names.on('mouseover', function(d){
d3.select(this).classed("nbtutor-hover", true);
that._setHover(d.object.uuid, true);
});
d3Names.on('mouseout', function(d){
d3.select(this).classed("nbtutor-hover", false);
that._setHover(d.object.uuid, false);
});
$(window).resize(function(){
that.jsplumb.repaintEverything();
});
}
destroy(){
this.jsplumb.empty(this.d3Root[0]);
this.d3Root.selectAll("div").remove();
this.connectors = [];
this.objects_rendered = [];
}
}