我正在使用Angular.js创建一些可视化。我得到了一个条形图的指令,如下所示:
angular.module('MissionControlApp').directive('d3Bars', ['d3', function(d3) {
return {
restrict: 'EA',
scope: {
data: "=",
label: "=",
onClick: "&"
},
link: function(scope, iElement) {
var svg = d3.select(iElement[0])
.append("svg")
.attr("width", "100%");
// on window resize, re-render d3 canvas
window.onresize = function() {
return scope.$apply();
};
scope.$watch(function(){
return angular.element(window)[0].innerWidth;
}, function(){
return scope.render(scope.data);
}
);
// watch for data changes and re-render
scope.$watch('data', function(newVals) {
return scope.render(newVals);
}, true);
// define render function
scope.render = function(data){
if(data === undefined){
return;
}
// remove all previous items before render
svg.selectAll("*").remove();
// setup variables
var width, height, max;
var margin = {top: 5, right: 30, bottom: 10, left: 150};
width = d3.select(iElement[0])[0][0].offsetWidth - margin.left - margin.right;
height = (scope.data.length * 35) + margin.top + margin.bottom;
max = 100;
// set the height based on the calculations above
svg.attr('height', height);
var x = d3.scale.linear()
.domain([0, 100])
.range([0, width]);
var y = d3.scale.ordinal()
.domain(data.map(function (d) { return d.user; }))
.rangeBands([0, height], 0.1, 0.35);
// var color = d3.scale.category20c();
var color = d3.scale.linear()
.domain([0, 25, 50, 75, 100])
.range(["#51b75d", "#90eb9d","#ffff8c","#f5c93f","#c45c44"])
.interpolate(d3.interpolateHcl);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.innerTickSize(-(height-5));
//create the rectangles for the bar chart
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("fill", function(d){return color(d.value); })
.on("click", function(d){return scope.onClick({item: d});})
.attr("height", y.rangeBand()) // height of each bar
.attr("width", 0) // initial width of 0 for transition
.attr("x", margin.left) // half of the 20 side margin specified above
.attr("y", function (d) { return y(d.user); }) // height + margin between bars
.transition()
.duration(1000)
.attr("width", function(d){ return d.value/(max/width); });
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("fill", "#000")
.attr("y", function(d){return y(d.user) + (y.rangeBand() / 2);})
.attr("x", 150)
.attr("text-anchor", "end")
.attr("dy", ".35em")
.attr("dx", -5)
.text(function(d){return d[scope.label];});
svg.append("g")
.selectAll("valueLabels")
.data(data)
.enter()
.append("text")
.attr("fill", "#000")
.attr("y", function(d){return y(d.user) + (y.rangeBand() / 2);})
.attr("x", function(d){return x(d.value) + margin.left;})
.attr("dx", 5)
.attr("dy", ".35em")
.text(function(d){return parseFloat(d.value).toFixed(0) + "%";})
.attr("fill-opacity", 0)
.transition()
.duration(1500)
.attr("fill-opacity", 1);
svg.append("g")
.attr("class", "x axisHorizontal")
.attr("transform", "translate(" + margin.left + "," + (height-margin.bottom) + ")")
.call(xAxis);
};
}
};
}]);
这定义了一个" onClick"事件依次在我的控制器中设置一段数据:
vm.d3OnClick = function(item){
// item {user: konrad.sobon, value: 26}
SetUserData(item);
};
function SetUserData (item) {
var allData = vm.selectedWorkset.onOpened;
var userData = [];
allData.forEach(function (d) {
if(d.user === item.user){
userData.push({
name: d.user,
value: (d.opened * 100) / (d.closed + d.opened),
createdOn: d.createdOn
})
}
});
console.log("SettingData to: ", userData);
vm.userData = userData;
}
vm.userData用于构建不同的图表,如下所示:
angular.module('MissionControlApp').directive('d3WorksetUser', ['d3', function(d3) {
return {
restrict: 'E',
scope: {
data: "=",
label: "=",
onClick: "&"
},
link: function(scope, iElement) {
var svg = d3.select(iElement[0])
.append("svg")
.attr("width", "100%");
// on window resize, re-render d3 canvas
window.onresize = function() {
return scope.$apply();
};
scope.$watch(function(){
return angular.element(window)[0].innerWidth;
}, function(){
return scope.render(scope.data);
}
);
// watch for data changes and re-render
scope.$watch('data', function(newVals) {
return scope.render(newVals);
}, true);
// define render function
scope.render = function(data){
if(data === undefined){
return;
}
// remove all previous items before render
svg.selectAll("*").remove();
// setup variables
var width, height;
var margin = {top: 5, right: 10, bottom: 20, left: 25};
width = d3.select(iElement[0])[0][0].offsetWidth - margin.left - margin.right;
height = 300 - margin.top - margin.bottom;
// set the height based on the calculations above
svg.attr('height', height + margin.top + margin.bottom);
var parseDate = d3.time.format('%Y-%m-%dT%H:%M:%S.%LZ').parse;
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(9);
var line = d3.svg.line()
.x(function(d) { return x(d.date) + margin.left; })
.y(function(d) { return y(d.value); });
data.forEach(function(d) {
d.date = parseDate(d.createdOn);
d.value = +d.value;
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, 100]);
// Add the valueline path.
svg.append("path")
.datum(data)
.attr("d", line)
.attr("stroke", "steelblue")
.attr("fill", "none")
.attr("stroke-width", 1.5);
// Add the X Axis
svg.append("g")
.attr("class", "axisMain")
.attr("transform", "translate(" + margin.left + "," + (height + margin.top) + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "axisMain")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(yAxis);
};
}
};
}]);
当我在Chrome中调试时,我可以看到我的点击事件触发了我的控制器上的相应方法,并且vm.userData被设置为新数据,但我的折线图没有更新...基本上没有发生。是否有一些我不知道的东西会使一个图表中的click事件刷新/重绘另一个图表?
HTML:
<div class="row">
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading" data-toggle="collapse" data-parent="#accordion" data-target="#collapseOne">
<h4 class="panel-title">Workset on Opened</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse in">
<div class="panel-body">
<d3-bars data="vm.DataOpened" label="vm.d3Label" on-click="vm.d3OnClick(item)"></d3-bars>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="panel-group" id="userDetail">
<div class="panel panel-default">
<div class="panel-heading" data-toggle="collapse" data-parent="#userDetail" data-target="#collapseUserDetail">
<h4 class="panel-title">User detail info.</h4>
</div>
<div id="collapseUserDetail" class="panel-collapse collapse in">
<div class="panel-body">
<d3-workset-user data="vm.userData"></d3-workset-user>
</div>
</div>
</div>
</div>
</div>
两个图表在第一次加载时绘制正常,但第二次图表永远不会更新。
这是支持以下内容的控制台日志:
其中:当我第一次从服务器获取数据并进行设置时,控制器会触发将数据设置为:。
然后我的scope.$watch
会响起两次。我认为第一个是数据仍然是undefined
,然后在数据解析后再次出现。
最后,还有与鼠标点击相关的事件和正在更新的数据,但正如您所见,watch
永远不会重新启动。
答案 0 :(得分:1)
将您的第二个$watch
功能更改为以下内容:
// watch for data changes and re-render
scope.$watch('data', function(newVals) {
$timeout(function() { scope.render(newVals) });
}, true);
我仍然保留$watch
并使用$timeout
代替$interval
,这样您的应用程序就不会浪费摘要周期。
这是因为范围。$ watch在摘要周期内被触发,并且因为angular是基于事件的,所以更改周期内的值不会重新触发监视。具有$timeout
超时的0 ms
会在当前的摘要周期之后安排另一个摘要周期。基本上(iirc),$timeout
是setTimeout
,内部有scope.$apply
。