2

我正在使用 ng-repeat 构建一个动态仪表板,并使用使用 AngularJS 指令构建的 D3 表盘。

当我运行 1 个指令标记时,它工作正常。当我在 ng-repeat 中有 2 个以上的指令标签时,似乎会出现一些奇怪的竞争条件,其中指令使用相同的变量。如何保证指令实例具有完全隔离的范围?

看起来我在隔离进度变量时遇到了问题。2 个刻度盘(指令实例)正在插入相同的进度值并搞砸了。我尝试将进度变量移动到指令中的不同范围,但找不到解决方案。

HTML:

 <div ng-controller="DashboardCtrl" ng-init="init();">
<div ng-repeat="item in metrics">
  <div ng-switch on="item.type">
    <div ng-switch-when="dial">
        <gh-dial val="item.data" data-format="item.data-format" metric-title="item.title" gh-target="item.target"></gh-dial>
    </div>
    <div ng-switch-when="meter">
        <gh-meter val="item.data" data-format="item.data-format" metric-title="item.title" gh-target="item.target"></gh-meter>
    </div>
  </div>
</div>
 </div>

指示:

directives.directive('ghDial', function () {

var width = 370,
    height = 370,
    twoPi = 2 * Math.PI,
    progress = 0;

return {
  restrict: 'E',
  scope: {
    val: '=',
  dataFormat: '=',
  metricTitle: '=',
  ghTarget: '='
},
link: function (scope, element, attrs) {

  console.debug(scope.dataFormat);
   var formatPercent = d3.format(scope.dataFormat);
   var total = scope.ghTarget.valueOf() ;
   var prepend = "" ;
   if (scope.dataFormat === "$") scope.prepend = "$" ;
   console.debug("prepend: "+scope.prepend);
   console.debug("data format: "+scope.dataFormat );

  // set up initial svg object
var vis = d3.select(element[0]).append("svg")
    .attr("width", width)
    .attr("height", height)
    .attr('fill', '#2E7AF9')
    .append("g")
    .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");


  scope.$watch('val', function (newVal, oldVal) {


      vis.selectAll('*').remove();


        // if 'val' is undefined, exit
        if (!newVal) {
          return;
        }

        var arc = d3.svg.arc()
            .startAngle(0)
            .innerRadius(140)
            .outerRadius(170)
        ;

        var meter = vis.append("g")
            .attr("class", "progress-meter");

        meter.append("path")
            .attr("class", "background")
            .attr("d", arc.endAngle(twoPi));

        var foreground = meter.append("path")
            .attr("class", "foreground");

        var text = meter.append("text")
            .attr("text-anchor", "middle")
            .style("font-size","14px");

        var text2 = meter.append("text")
            .attr("y", 40)
            .attr("text-anchor", "middle")
            .attr("class", "text2");

            console.debug(scope.metricTitle);
            text2.text(scope.metricTitle);

        var animate = function(percentage) {

            var i = d3.interpolate(progress, percentage/total);

            d3.transition().duration(800).tween("progress", function () {
                return function (t) {
                    progress = i(t);
                    foreground.attr("d", arc.endAngle(twoPi * progress));
                    console.debug("progress:"+progress);
                    text.text(prepend+''+percentage);
                };
            });
        }; 

        setTimeout(function () {
        console.debug(newVal);
        animate(newVal.expr0.valueOf());
        }, 500);

  });
  }
}
});
4

1 回答 1

0

我解决了这个问题。我认为主要问题是在转换线上添加一个 D3 select() 以便 D3 知道它正在转换哪个表盘。我还从指令中删除了附加 SVG,现在文档中只有一个 SVG。该指令在 SVG 标签下附加组。看起来很棒!

HTML:

<div ng-controller="DashboardCtrl" ng-init="init();">
<svg id="d3Parent" width="1000" height="1000" fill="#2E7AF9">
</svg>
<div ng-repeat="item in metrics">
  <div ng-switch on="item.type">
    <div ng-switch-when="dial">
        <gh-dial val="item.data" data-format="item.data-format" metric-title="item.title" gh-target="item.target"></gh-dial>
    </div>
    <div ng-switch-when="meter">
        <gh-meter val="item.data" data-format="item.data-format" metric-title="item.title" gh-target="item.target"></gh-meter>
    </div>
  </div>
</div>
</div>

指示:

directives.directive('ghDial', function () {

var width = 370,
    height = 370,
    twoPi = 2 * Math.PI,
    progress = 0.00001;

return {
  restrict: 'E',
transclude: true,
  scope: {
      val: '=',
  dataFormat: '=',
  metricTitle: '=',
  ghTarget: '='
  },
  link: function (scope, element, attrs) {

  console.debug(attrs['data-format']);
   var formatPercent = d3.format(scope.dataFormat);
   var total = scope.ghTarget.valueOf() ;
   var prepend = "" ;
   if (scope.dataFormat === "$") prepend = "$" ;
   console.debug("prepend: "+scope.prepend);
   console.debug("data format: "+scope.dataFormat );

   var index = document.querySelector("#d3Parent").childNodes.length-1 ;

   var column = [200, 600];
   var row = [200, 500, 750]

  // set up initial svg object
var vis = d3.select("#d3Parent")
        .append("g")
        .attr("transform", "translate(" + column[index%2] + "," + row[0] + ")");  

  scope.$watch('val', function (newVal, oldVal) {

      console.debug("directive watch fired:"+scope.metricTitle);

      vis.selectAll('*').remove();

        // if 'val' is undefined, exit
        if (!newVal) {
          return;
        }

        var arc = d3.svg.arc()
            .startAngle(0)
            .innerRadius(140)
            .outerRadius(170)
        ;

        var meter = vis.append("g")
            .attr("class", "progress-meter");

        meter.append("path")
            .attr("class", "background")
            .attr("d", arc.endAngle(twoPi));

        var foreground = meter.append("path")
            .attr("class", "foreground");

        var text = meter.append("text")
            .attr("text-anchor", "middle")
            .style("font-size","24px");

        var text2 = meter.append("text")
            .attr("y", 40)
            .attr("text-anchor", "middle")
            .attr("class", "text2");

            console.debug(scope.metricTitle);
            text2.text(scope.metricTitle);

        var percentage = newVal.expr0.valueOf() ;


            console.debug("animate progress: "+progress+" percentage:"+percentage+" total:"+total);
            var i = d3.interpolateNumber(progress, percentage/total);

            d3.select(vis).transition().duration(800).tween("progress", function () {
                return function (t) {
                    progress = i(t);
                    foreground.attr("d", arc.endAngle(twoPi * progress));
                    console.debug("progress:"+progress);
                    text.text(prepend+''+percentage);
                };
            });



  });
}
}
});
于 2013-09-10T06:24:14.400 回答