无法获取树映射以使用新的JSON数据进行渲染



我在具有backbone.js的应用程序中使用d3.jstreemap。树映射使用第一个JSOn数据正确渲染,但随后使用不同JSOn数据的调用不会导致树映射重新渲染。

我的HTML如下所示:

<html>
<head>
<title>Jenkins analytics</title>
<!-- stylesheets -->
<link rel="stylesheet" href="css/spa.css" type="text/css"/>
<link rel="stylesheet" href="css/treemap.css" type="text/css"/>
</head>
<body>
<nav>
<form>
<fieldset>
<label for="chart">Chart:</label>
<select id="chart" name="chart">
<option value="treemap" selected>Treemap</option>
<option value="motion">MotionChart</option>
</select>
</fieldset>
</form>
<form>
<fieldset>
<label for="period">Period:</label>
<select id="period" name="period">
<option value="lastday" selected>Day</option>
<option value="lastweek">Week</option>
<option value="lastmonth">Month</option>
</select>
<label for="team">Team:</label>
<select id="team" name="team">
<option value="all" selected>all</option>
<option value="spg">spg</option>
<option value="beacon">beacon</option>
<option value="disco">disco</option>
</select>
<label for="status">Status:</label>
<select id="status" name="status">
<option value="" selected>all</option>
<option value="SUCCESS">success</option>
<option value="FAILURE">failure</option>
<option value="ABORTED">aborted</option>
</select>
</fieldset>
</form>
<form>
<fieldset>
<label for="duration">Duration</label>
<input id="duration" type="radio" name="mode" value="size" checked />
<label for="count">Count</label>
<input id="count" type="radio" name="mode" value="count" />
<label for="average">Average</label>
<input id="average" type="radio" name="mode" value="avg" />
</fieldset>
</form>
</nav>
<div id="container" />
<!-- Third party javascript -->
<script type="text/javascript" src="http://code.jquery.com/jquery-2.0.3.min.js" charset="utf-8"></script>
<script type="text/javascript" src="script/underscore/underscore.js" charset="utf-8"></script>
<script type="text/javascript" src="script/backbone/backbone.js" charset="utf-8"></script>
<script type="text/javascript" src="script/d3/d3.v3.js" charset="utf-8"></script>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<!-- Application javascript -->
<script type="text/javascript" src="script/module.js"></script>
<!-- Startup -->
<script>
var navview = new NavViewer();
</script>
</body>
</html>

script/module.js看起来像这样:

var NavViewer = Backbone.View.extend({
el: 'nav',
events: {
"change #chart": "change_chart",
"change #period": "change_period",
"change #team": "change_team",
"change #status": "change_status"
},
initialize: function() {
console.log("NavViewer.initialize");
this.d3view = new D3Viewer();
this.active_view = this.d3view;
},
change_chart: function(e) {
console.log("NavViewer.change_chart");
},
change_period: function(e) {
var _period = $('#period').val();
console.log("NavViewer.change_period to " + _period);
this.active_view.load();
},
change_team: function(e) {
var _team = $('#team').val();
console.log("NavViewer.change_team to "+ _team);
this.active_view.load();
},
change_status: function(e) {
var _status = $('#status').val();
console.log("NavViewer.change_status to " + _status);
this.active_view.load();
}
});
var JenkinsViewer = Backbone.View.extend({
el: '#container',
server: "http://192.168.1.100:5000",
url_fragment: function() {
var _period = $('#period').val();
var _team = $('#team').val();
var _status = $('#status').val();
return "when=" + _period +
(_team == "all" ? "" : ("&" + "team=" + _team)) +
(_status == "" ? "" : ("&" + "status=" + _status));
}
});
var D3Viewer = JenkinsViewer.extend({
initialize: function() {
this.margin = {top: 8, right: 0, bottom: 0, left: 0};
this.width = 1200 - this.margin.left - this.margin.right;
this.height = 800 - this.margin.top - this.margin.bottom - 60;
this.container = d3.select(this.el);
this.color = d3.scale.category20c();
this.base_url = this.server + "/team_build";
this.treemap = d3.layout.treemap()
.size([this.width, this.height])
.sticky(true)
.value(function(d) { return d.size; });
this.position = function() {
this.style("left", function(d) { return d.x + "px"; })
.style("top", function(d) { return d.y + "px"; })
.style("width", function(d) { return Math.max(0, d.dx - 1) + "px"; })
.style("height", function(d) { return Math.max(0, d.dy - 1) + "px"; });
};
/* style the container */
this.container
.style("position", "relative")
.style("width", this.width + "px")
.style("height", this.height + "px")
.style("left", this.margin.left + "px")
.style("top", this.margin.top + "px")
.style("border", "1px solid black");
/* tootlip is appended to container */
this.tooltip = this.container.append("div")
.attr('class', 'tooltip')
.style("visibility", "hidden")
.style("background-color", "#ffffff");
this.load();
},
load: function() {
var $container = this.container;
var color = this.color;
var treemap = this.treemap;
var position = this.position;
var tooltip = this.tooltip;
var url = this.base_url + "?" + this.url_fragment();
console.log("D3View.load: " + url);
d3.json(url, function(error, root) {
/* 'root' actually means the data retrieved by the xhr call */
var node = $container.datum(root)
.selectAll(".node")
.data(treemap.nodes);
node.enter().append("div")
.attr("class", "node")
.call(position)
.style("background", function(d) { return d.children ? color(d.name) : null; })
.text(function(d) { return d.children ? null : d.name; })
.on("mouseover", function(d) {
tooltip.html(d.name + ": " + Math.floor(d.value))
.style("visibility", "visible");
this.style.cursor = "hand";
})
.on("mouseout", function(){
tooltip.style("visibility", "hidden");
});
d3.selectAll("input").on("change", function change() {
var functions = {
count: function(d) { return d.count; },
size: function(d) { return d.size; },
avg: function(d) { return d.size / d.count; }
};
var value = functions[this.value];
node
.data(treemap.value(value).nodes)
.transition()
.duration(1500)
.call(position);
});
});
return true;
}
});

以下是我做过的事情:

  • 读取d3.json和d3.layout.treemap的d3.js代码
  • 在谷歌上搜索了一下
  • 阅读Lars Kotthoff在StackOverflow上的d3.js答案
  • 阅读一些文章:Enter and Exit,D3.js:如何处理动态JSON数据
  • 已尝试treemap.sticky(false)
  • 尝试添加node.exit().remove()

我认为问题可能与粘性或没有呼叫node.exit().remove()有关。我试着修改了这两个,但都没有成功。但是,要获得交互式客户端UI,我想我需要使用treemap.sticky(true)

我已经确认,每次使用REST API服务时都会得到不同的JSON。我已经确认container.datum().children在两次调用之间的大小会发生变化,这对我来说是一个树映射而不是重新渲染的问题。

D3.js: How to handle dynamic JSON Data看起来高度相关:

  • "值得一提的是,如果您已经有了具有相同密钥的数据,d3.js将把数据存储在节点中,但仍将使用原始数据。">
  • "当我开始玩d3.js时,我认为渲染的数据会发生事件,并对我的缩放比例、域等的变化做出反应。事实并非如此。你必须告诉d3.js更新当前的内容,否则它将保持不变,不同步。">
  • "我将data()的结果分配给一个变量,因为enter()只影响未绑定到节点的新数据。">

以下是我在load方法中完成的操作:

load: function() {
var $container = this.container;
var color = this.color;
var treemap = this.treemap;
var position = this.position;
var tooltip = this.tooltip;
var url = this.base_url + "?" + this.url_fragment();
console.log("D3View.load: " + url);
d3.json(url, function(error, root) {
var tooltip_mouseover = function(d) {
tooltip.html(d.name + ": " + Math.floor(d.value))
.style("visibility", "visible");
this.style.cursor = "hand";
};
var tooltip_mouseout = function(){
tooltip.style("visibility", "hidden");
};
var background_color = function(d) { return d.children ? color(d.name) : null; };
var text_format = function(d) { return d.children ? null : d.name; };
/*
* Refresh sticky bit
* https://github.com/mbostock/d3/wiki/Treemap-Layout
* "Implementation note: sticky treemaps cache the array of nodes internally; therefore, it
* is not possible to reuse the same layout instance on multiple datasets. To reset the
* cached state when switching datasets with a sticky layout, call sticky(true) again."
*/
treemap.sticky(true);
/* 'root' actually means the data retrieved by the xhr call */
var nodes = $container.datum(root)
.selectAll(".node")
.data(treemap.nodes);
var enter = nodes.enter().append("div")
.attr("class", "node")
.call(position)
.style("background", background_color)
.text(text_format)
.on("mouseover", tooltip_mouseover)
.on("mouseout", tooltip_mouseout);
var exit = nodes.exit().remove();
var update = nodes.style("background", background_color)
.call(position)
.text(text_format);
d3.selectAll("input").on("change", function change() {
var functions = {
count: function(d) { return d.count; },
size: function(d) { return d.size; },
avg: function(d) { return d.size / d.count; }
};
var value = functions[this.value];
$container.selectAll(".node")
.data(treemap.value(value).nodes)
.transition()
.duration(1500)
.call(position);
});
});

有两个重要的变化:

  1. 重置树图粘性位
  2. 对新的/丢失的/更改的节点/数据使用进入/退出/更新选项

还请注意,enter节点具有添加类nodediv的副作用,并且exitupdate节点不引用该div类——除非它们在创建nodes时引用。如果在这些地方的node-class上再添加一个选择,则您的选择将为空,并且exitupdate代码将没有操作。

感谢AmeliaBR发布了一条非常有用的评论,并附上了她撰写的SO答案的链接。

其他有用的阅读:

  • https://github.com/mbostock/d3/wiki/Selections
  • http://bost.ocks.org/mike/join/

最新更新