问题
I'm working on a visualization that involves updating a text element that appear inside of a circle. Something like:
<g>
<circle></circle>
<text>My text that needs updating</text>
</g>
Along with this small visual is a d3 brush being used as a slider. On brush/brushend, I need to <text>Update the text here</text>, based on data associated with a time scale along the slider's x-axis.
I have two Angular directives. One that contains the circle, the other that is the slider. In my slider directive I'm calling:
$rootScope.$emit('brushing'); and setting a rootScope variable according to my data, $rootScope.myNewValue = newValue; as sliding occurs
Then I'm listening for this in my other directive, and updating the text.attr to be the rootScope var:
$rootScope.$on('brushing', function () {
myText.text($rootScope.myNewValue);
});
Where:
var myText = svg.append('text')
.text('I'm trying to update this text..');
... in the d3 portion of the code
Doing things this way seems to work, but I'm wondering if there is a way to bind the data directly when initializing myText:
var myText = svg.append('text)
.text($rootScope.myNewValue);
So that the value updates directly without having to $emit and listen. I've tried scope.$apply in the directive but that seems to have no effect.
Has anyone encountered a similar scenario when using d3 with Angular?
回答1:
You can wrap up the D3JS code in a directive then use a property that is passed in and watch it to see if it changes to update the D3JS code accordingly (your saving a reference to the svg text is still probably the best bet). For an example see my directive below where I use val to update the data used in a bar chart display (note I didn't deal with transitions here yet, and the codes a bit of a mess but hopefully you see the general point)
directive('barChart', function ( /* dependencies */ ) {
// define constants and helpers used for the directive
var width = 600,
height = 80;
return {
restrict: 'E', // the directive can be invoked only by using <bar-chart></bar-chart> tag in the template
scope: { // attributes bound to the scope of the directive
val: '='
},
link: function (scope, element, attrs) {
// initialization, done once per my-directive tag in template. If my-directive is within an
// ng-repeat-ed template then it will be called every time ngRepeat creates a new copy of the template.
// set up initial svg object
var vis = d3.select(element[0])
.append("svg")
.attr("class", "chart")
.attr("width", width)
.attr("height", height);
// whenever the bound 'exp' expression changes, execute this
scope.$watch('val', function (newVal, oldVal) {
// clear the elements inside of the directive
vis.selectAll('*').remove();
vis.attr("height", newVal.length*22);
// if 'val' is undefined, exit
if (!newVal) {
return;
}
var totalDataSetSize = 0;
for (var i = 0; i < newVal.length; i++) {
totalDataSetSize += newVal[i].data.length
};
function calcBarWidth(d) {
return (totalDataSetSize==0)?0:d.data.length/totalDataSetSize*420;
}
vis.selectAll("rect")
.data(newVal)
.enter().append("rect")
.attr("y", function(d, i) { return i*20; })
.attr("width", calcBarWidth)
.attr("height", function(d) {return 20});
vis.selectAll("text")
.data(newVal)
.enter().append("text")
.attr("x", function(d) { return calcBarWidth(d)+10})
.attr("y", function(d, i) { return (i+1)*20; })
.attr("dy", "-.3em") // vertical-align: middle
.style("font-size", ".7em")
.attr("fill", "black")
.attr("text-anchor", "beginning") // text-align: right
.text(function(d,i){ return d.data.length.toString() + " " + d.label})
},true);
}
};
})
来源:https://stackoverflow.com/questions/22482101/angular-d3-js-data-binding-with-svg-text-attr