我尝试绘制图表/网络,其中节点内容应填充第三方库。到目前为止,我能够使用容器()为d3绘制图形,以便稍后填充每个节点。我的问题是,当提到绘图时,容器似乎还不存在。添加onload事件也不起作用,但使用onclick事件表明所有内容都已到位,并且很可能在实际创建DOM元素之前开始绘制。
在开始分配内容之前,确保创建d3生成的DOM元素的最佳做法是什么?我使用的第三方JS库只需要div id来绘制内容。
从Neo4j返回的节点和关系数组中,我构建了一个带有d3的图形,如下所示。要在节点中显示的图像作为Base64字符串存储在neo4j数据库中。
var width = 500, height = 500;
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var col = obj.columns;
var data = obj.data;
var nodes = obj.data[0][0].nodes;
var nodeMap = {};
nodes.forEach(function(x) { nodeMap[x.label] = x; });
var edges = obj.data[0][0].edges;
var links = edges.map(function(x) {
return { source: nodeMap[x.source], target: nodeMap[x.target], value: x.value };
});
var svg = d3.select("#graph").append("svg")
.attr("width", width)
.attr("height", height)
.attr("pointer-events", "all")
.append('g')
.call(d3.behavior.zoom().on("zoom", redraw))
.append('g');
var force = d3.layout.force()
.gravity(.3)
.distance(150)
.charge(-4000)
.size([width, height]);
var drag = force.drag()
.on("dragstart", dragstart);
force
.nodes(nodes)
.links(links)
.friction(0.8)
.start();
var link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("svg:g")
.attr("class", "node")
.on("dblclick", dblclick)
.call(force.drag);
node.append("circle")
.attr("r", 50);
node.append("image")
// display structure in nodes
.attr("xlink:href", function(d){
if (d.imgB64) {
return 'data:image/png;base64, ' + d.imgB64 ;
}
})
.attr("x", -40)
.attr("y", -40)
.attr("width", 80)
.attr("height", 80);
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
该代码工作正常。因此,我尝试做的是用第三方库生成的画布图替换Base64图像。
此库的工作原理如下:
var myCanvas = newCanvasViewer(id, data);
该函数知道如何从给定数据生成图像并将结果放入DOM元素(如果元素已经存在于DOM中),或者直接在DOM中生成新的canvas元素,但不幸的是擦掉任何其他元素现有的DOM元素。
所以,我更改了d3以上的代码,用以下代码替换了node.append(' image')块:
node.append("canvas")
.attr("id", function(d) {
var cnv = newCanvasViewer(d.name, d.data);
return d.name;
});
这显然不起作用,画布对象显示但不在节点中,可能是因为DOM元素在调用newCanvasViewer时还不存在。此外,d3图被覆盖。 设置调用newCanvasViewer的onclick函数时,图形会在点击时显示在节点内。
node.append("circle")
.attr("r", 50)
.attr("onclick", function(d) {
return "newCanvasViewer('"+d.name+"', '"+d.data+"')";
});
node.append("canvas")
.attr("id", function(d) {
return d.name;
});
由于我希望每个节点从头开始显示它的画布,我想知道何时调用newCanvasViewer函数?我猜每个节点上的oncreate函数会成功,但不存在。 回叫功能会起作用吗? d3完成整个网络绘图后,我应该调用它吗?
为了更全面,这里是带有回调函数的HTML和javascript代码,一旦d3 d3完成绘图,就会尝试绘制画布内容。我仍然卡住了,画布样本实际上已显示,但节点中没有显示画布内容,可能是由于生成画布容器和使用它们之间的时间安排。
HTML:
<body onload="init(); cypherQuery()">
<div id="graph" align="left" valign="top"></div>
<div id="sample">
<canvas id="mol" style="width: 160px; height: 160px;"></canvas>
</div>
</body>
Javascript(在HTML标题中): var xmlhttp; function init(){ xmlhttp = new XMLHttpRequest(); }
function cypherQuery() {
// perform neo4j query, extract JSON and call the network drawing (d3).
// ...
var obj = JSON.parse(xmlhttp.responseText);
drawGraph(obj, drawCanvas);
// this is just to show that canvasViewer does the
// job if the <canvas id='...'> element exists in DOM
var nodes = obj.data[0][0].nodes;
var cnv = newCanvasViewer("sample", nodes[0].data);
}
function drawGraph(obj, drawCanvas) {
// see code above from...
var width = 500, height = 500;
// ... to
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
function redraw() {
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
// node.attr("font-size", (nodeFontSize / d3.event.scale) + "px");
}
function dblclick(d) {
d3.select(this).classed("fixed", d.fixed = false);
}
if (callback && typeof(callback) === "function") {
callback(nodes);
}
}