将d3 svg附加到当前的ng-repeat元素

时间:2015-07-07 09:04:21

标签: javascript angularjs d3.js angularjs-ng-repeat

我想将d3.js饼图附加到使用ng-repeat生成的每个li元素。

@interface ObjectiveCClass : UIViewController  {

    int someVarialbe;

}
- (void)someFunction;
- (void)dealloc;

@end

@implementation ObjectiveCClass
- (void)someFunction{
       //Log of someFunction
}
- (void)dealloc {
       //Log line of objective-c dealloc
      [super dealloc];
}
@end

class CPlusPlusClass{
      ObjectiveCClass obj;          // have a objective c member

      CPlusPlusClass(){
           obj = [[ObjectiveCClass alloc] init];
      ~CPlusPlusClass(){
           //Log line of C++ class destructor 
           obj.someFunction; 
           [obj release];
      }
};

我的$ scope.hashtag是一个包含主题标签参与属性的对象数组:

 <ol>
 <li ng-repeat="h in hashtags | orderBy:predicate:reverse | limitTo: limit">
    <div class="hashtag">
       <a ng-click="showTweetsForHashtag(h)">#{{h.Hashtag}}</a>
    </div>
    <div class="frequency">
       {{h.Frequency}} times
    </div>
    <div class="engagement">
       {{h.Engagement}}
       <pie-chart data="h" on-click="showTweetsForHashtag(item)"></pie-chart>
    </div>
 </li>
</ol>

对于ng-repeat的坦克,每次调用饼图指令时,我只传递一个h对象:

[{
   "Favorites": 0,
   "Frequency": 1,
   "Hashtag": "19h30",
   "Replies": 0,
   "Retweets": 1,
   "Engagement":2,
   "tweetId": 615850479952785400
}, {
   "Favorites": 0,
   "Frequency": 1,
   "Hashtag": "80s",
   "Replies": 0,
   "Retweets": 2,
   "Engagement":2,
   "tweetId": [
         616521677275533300,
         617319253738393600
      ] 
}{
   "Favorites": 1,
   "Frequency": 1,
   "Hashtag": "AloeBlacc",
   "Replies": 0,
   "Retweets": 1,
   "Engagement":2,
   "tweetId": 617309488572420100
}, {
   "Favorites": 2,
   "Frequency": 1,
   "Hashtag": "Alpes",
   "Replies": 0,
   "Retweets": 1,
   "Engagement":3,
   "tweetId": 615481266348146700
}]

然后我手动&#34;映射&#34;进入那种格式:

{
   "Favorites": 2,
   "Frequency": 1,
   "Hashtag": "Alpes",
   "Replies": 0,
   "Retweets": 1,
   "Engagement":3,
   "tweetId": 615481266348146700
}

最后,我希望我的指令将pie添加到当前var mapped = [{ "label": "Retweets", "value": data.Retweets }, { "label": "Favorites", "value": data.Favorites }, { "label": "Replies", "value": data.Replies }]; (在指令模板中生成)中,并使用已传递的当前h对象的映射数据。但正如ocket-san提到的<div class="pie_chart"></div>只匹配DOM中的第一个元素。

这是我的指示:

d3.select(someElement)

问题在于指令

.directive('pieChart', ['d3', function(d3) {
    return {
        restrict: 'E',
        scope: {
            data: '=',
            onClick: '&'
        },
        template: '<div class="pie_chart"></div>',
        link: function(scope, iElement, iAttrs) {


            // watch for data changes and re-render
            scope.$watch('data', function(newVals, oldVals) {
                if (newVals) {
                    scope.render(newVals);
                }
            }, true);

            scope.render = function(data) {
                var w = 50, //width
                    h = 50, //height
                    r = data.Engagement / 3, // adapt radius to engagement value
                    color = d3.scale.ordinal().range(["#77b255", "#ffac33", "#07c"]); //custom range of colors

                // map data to to be used by pie chart directive
                var mapped = [{
                    "label": "Retweets",
                    "value": data.Retweets
                }, {
                    "label": "Favorites",
                    "value": data.Favorites
                }, {
                    "label": "Replies",
                    "value": data.Replies
                }];
                data = mapped;


                // Courtesy of https://gist.github.com/enjalot/1203641

                var vis = d3.select(".pie_chart")
                    .append("svg:svg") //create the SVG element inside the <body>
                    .data([data]) //associate our data with the document
                    .attr("width", w) //set the width and height of our visualization (these will be attributes of the <svg> tag
                    .attr("height", h)
                    .append("svg:g") //make a group to hold our pie chart
                    .attr("transform", "translate(" + r + "," + r + ")") //move the center of the pie chart from 0, 0 to radius, radius

                var arc = d3.svg.arc() //this will create <path> elements for us using arc data
                    .outerRadius(r);

                var pie = d3.layout.pie() //this will create arc data for us given a list of values
                    .value(function(d) {
                        return d.value;
                    }); //we must tell it out to access the value of each element in our data array

                var arcs = vis.selectAll("g.slice") //this selects all <g> elements with class slice (there aren't any yet)
                    .data(pie) //associate the generated pie data (an array of arcs, each having startAngle, endAngle and value properties)
                    .enter() //this will create <g> elements for every "extra" data element that should be associated with a selection. The result is creating a <g> for every object in the data array
                    .append("svg:g") //create a group to hold each slice (we will have a <path> and a <text> element associated with each slice)
                    .attr("class", "slice"); //allow us to style things in the slices (like text)

                arcs.append("svg:path")
                    .attr("fill", function(d, i) {
                        return color(i);
                    }) //set the color for each slice to be chosen from the color function defined above
                    .attr("d", arc); //this creates the actual SVG path using the associated data (pie) with the arc drawing function
            };
        }
    }
}]);

使用pie_chart类将所有饼图附加到第一个div。

我尝试将其更改为d3.select(iElement)(...)但它没有用。

有什么建议吗?

提前致谢! Q值。

你可以在那里看到当前的输出: http://i61.tinypic.com/wqqc0z.png

5 个答案:

答案 0 :(得分:3)

问题在于ViewTreeObserver.OnGlobalLayoutListener选择与正文中的此类匹配的第一个元素,而不是在指令模板中。要实现此目的,您应该使用onWindowFocusChanged()函数中提供的d3.select('.pie_chart')对象。在你的情况下:

element

我创建了一个简化的fiddle试图展示这个。

希望它有所帮助。

答案 1 :(得分:2)

当我们一起使用Angularjsd3js时,我们需要更新d3.select('body')选项以使用相对于指令 d3.select(element[0])而不是整个DOM。我们必须使用element[0]而不仅仅是元素的原因是因为元素“是”jQuery 包装选择而不是普通的DOM对象。做element[0]只给我们一个简单的旧DOM元素。 (我在引号中说“是”因为它在技术上是一个jqlite包装的DOM元素.jqlite本质上是jQuery的精简版。)

因此,您需要将代码更新为:

.directive('pieChart', ['d3', function(d3) {
return {
    restrict: 'E',
    scope: {
        data: '=',
        onClick: '&'
    },
    template: '<div class="pie_chart"></div>',
    link: function(scope, iElement, iAttrs) {


        // watch for data changes and re-render
        scope.$watch('data', function(newVals, oldVals) {
            if (newVals) {
                scope.render(newVals);
            }
        }, true);

        scope.render = function(data) {
            var w = 50, //width
                h = 50, //height
                r = data.Engagement / 3, // adapt radius to engagement value
                color = d3.scale.ordinal().range(["#77b255", "#ffac33", "#07c"]); //custom range of colors

            // map data to to be used by pie chart directive
            var mapped = [{
                "label": "Retweets",
                "value": data.Retweets
            }, {
                "label": "Favorites",
                "value": data.Favorites
            }, {
                "label": "Replies",
                "value": data.Replies
            }];
            data = mapped;


            // Courtesy of https://gist.github.com/enjalot/1203641
            //Part need Update
            var vis = d3.select(iElement[0])
                .append("svg:svg") //create the SVG element inside the <body>
                .data([data]) //associate our data with the document
                .attr("width", w) //set the width and height of our visualization (these will be attributes of the <svg> tag
                .attr("height", h)
                .append("svg:g") //make a group to hold our pie chart
                .attr("transform", "translate(" + r + "," + r + ")") //move the center of the pie chart from 0, 0 to radius, radius

            var arc = d3.svg.arc() //this will create <path> elements for us using arc data
                .outerRadius(r);

            var pie = d3.layout.pie() //this will create arc data for us given a list of values
                .value(function(d) {
                    return d.value;
                }); //we must tell it out to access the value of each element in our data array

            var arcs = vis.selectAll("g.slice") //this selects all <g> elements with class slice (there aren't any yet)
                .data(pie) //associate the generated pie data (an array of arcs, each having startAngle, endAngle and value properties)
                .enter() //this will create <g> elements for every "extra" data element that should be associated with a selection. The result is creating a <g> for every object in the data array
                .append("svg:g") //create a group to hold each slice (we will have a <path> and a <text> element associated with each slice)
                .attr("class", "slice"); //allow us to style things in the slices (like text)

            arcs.append("svg:path")
                .attr("fill", function(d, i) {
                    return color(i);
                }) //set the color for each slice to be chosen from the color function defined above
                .attr("d", arc); //this creates the actual SVG path using the associated data (pie) with the arc drawing function
        };
    }
}
}]);    

当您更新代码时,directive('pieChart')功能会动态选择<pie-chart/>代码。如果您有特定的课程,请将您的代码更新为:

   var vis = d3.select(iElement[0]).select(".pie_chart") 

更新1

您需要将$index添加到ng-repeat,因为:

  

Angular告诉我们的是ng-repeat中的每个元素都必须是唯一的。然而,   我们可以告诉Angular使用数组中的元素索引,而不是通过$index添加跟踪来确定唯一性。

 <ol>
   <li ng-repeat="h in hashtags track by $index" | orderBy:predicate:reverse | limitTo: limit">
<div class="hashtag">
   <a ng-click="showTweetsForHashtag(h)">#{{h.Hashtag}}</a>
</div>
  <div class="frequency">
   {{h.Frequency}} times
  </div>
  <div class="engagement">
       {{h.Engagement}}
   <pie-chart data="h" on-click="showTweetsForHashtag(item)"></pie-chart>
  </div>
 </li>
</ol>

答案 2 :(得分:2)

我发现这里的答案在我的案例中是不正确的。

Jarandaf - 是最接近但我的解决方案是删除类选择器。

并使用以下代码:

d3.select(element[0]).append('svg')

答案 3 :(得分:0)

d3.select(“element”)始终选择它找到的第一个元素。例如:假设您有以下html结构:

<body>
    <p></p>
    <p></p>
    <p></p> 
</body>

你会编码:d3.select(“p”)。append(“svg”),结果将是

<body>
        <p>
           <svg></svg>
        </p>
        <p></p>
        <p></p> 
</body>

您需要使用 d3.selectAll(element),这将为您提供包含适合选择器的所有项目的d3选择。

编辑:

好的,所以我认为你的最终html结构看起来像这样:

<ol>
 <li ng-repeat="h in hashtags | orderBy:predicate:reverse | limitTo: limit">
    <div class="hashtag">
       <a ng-click="showTweetsForHashtag(h)">#{{h.Hashtag}}</a>
    </div>
    <div class="frequency">
       {{h.Frequency}} times
    </div>
    <div class="engagement">
       {{h.Engagement}}
       <div id="pie_chart">
         <svg> your piechart goes here</svg>
       </div>
    </div>
 </li>
<li ng-repeat="h in hashtags | orderBy:predicate:reverse | limitTo: limit">
    <div class="hashtag">
       <a ng-click="showTweetsForHashtag(h)">#{{h.Hashtag}}</a>
    </div>
    <div class="frequency">
       {{h.Frequency}} times
    </div>
    <div class="engagement">
       {{h.Engagement}}
       <div id="pie_chart">
         <svg> another piechart goes here</svg>
       </div>
    </div>
 </li>
</ol>

所以假设html结构已经存在而没有标签(因为我不知道任何关于角度或指令的事情:-))并且你想要附加svg标签并将一个标签附加到每个具有类“pie_chart”的div,你需要这样做:

var piecharts = d3.selectAll(".pie_chart").append("svg");

结果将是上面的html结构。

如果这不是你想要的,那么我很抱歉,我想我完全误解了这个问题: - )

答案 4 :(得分:0)

谢谢Gabriel的回答!

与此同时,我找到了一个解决方法(它可能不是最漂亮的,但它确实有效!)

指令:

.directive('pieChart', ['d3', function(d3) {
    return {
        restrict: 'E',
        scope: {
            data: '=',
            max: '@',
            item: '@',
            onClick: '&'
        },
        template: '<div class="pie_chart"></div>',
        link: function(scope, iElement, iAttrs) {


            // watch for data changes and re-render
            scope.$watch('data', function(newVals, oldVals) {
                if (newVals) {
                    scope.render(newVals);
                }
            }, true);

            scope.render = function(data) {
                // Courtesy of https://gist.github.com/enjalot/1203641

                var vis = d3.selectAll(".pie_chart")
                    .each(function(d, i) {
                        if (scope.item == i) {
                            var w = 50, //width
                                h = 50, //height
                                normalized = 50 * (data.Engagement) / (scope.max),
                                r = normalized/2, // adapt radius to engagement value
                                color = d3.scale.ordinal().range(["#77b255", "#ffac33", "#07c"]); //custom range of colors

                            // map data to to be used by pie chart directive
                            var mapped = [{
                                "label": "Retweets",
                                "value": data.Retweets
                            }, {
                                "label": "Favorites",
                                "value": data.Favorites
                            }, {
                                "label": "Replies",
                                "value": data.Replies
                            }];
                            var vis = d3.select(this)
                                .append("svg:svg") //create the SVG element inside the template
                                .data([mapped]) //associate our data with the document
                                .attr("width", w) //set the width and height of our visualization (these will be attributes of the <svg> tag
                                .attr("height", h)
                                .append("svg:g") //make a group to hold our pie chart
                                .attr("transform", "translate(" + (w/2) + "," + (h/2) + ")") //move the center of the pie chart from 0, 0 to radius, radius
                                .on("click", function(d, i){
                                   return scope.onClick({item: data});
                                });

                            var arc = d3.svg.arc() //this will create <path> elements for us using arc data
                                .outerRadius(r);

                            var pie = d3.layout.pie() //this will create arc data for us given a list of values
                                .value(function(d) {
                                    return d.value;
                                }); //we must tell it out to access the value of each element in our data array

                            var arcs = vis.selectAll("g.slice") //this selects all <g> elements with class slice (there aren't any yet)
                                .data(pie) //associate the generated pie data (an array of arcs, each having startAngle, endAngle and value properties)
                                .enter() //this will create <g> elements for every "extra" data element that should be associated with a selection. The result is creating a <g> for every object in the data array
                                .append("svg:g") //create a group to hold each slice (we will have a <path> and a <text> element associated with each slice)
                                .attr("class", "slice"); //allow us to style things in the slices (like text)

                            arcs.append("svg:path")
                                .attr("fill", function(d, i) {
                                    return color(i);
                                }) //set the color for each slice to be chosen from the color function defined above
                                .attr("d", arc); //this creates the actual SVG path using the associated data (pie) with the arc drawing function
                        }
                    })
            };
        }
    }
}])

HTML

<ol>
  <li ng-repeat="h in hashtags | orderBy:predicate:reverse | limitTo: limit">
    <div class="hashtag">
      <a ng-click="showTweetsForHashtag(h)">
        #{{h.Hashtag}}
      </a>
    </div>
    <div class="frequency">
      {{h.Frequency}} times
    </div>
    <div class="engagement">
      <pie-chart data="h" max="{{hashtagMaxEngagement}}" item="{{$index}}" on-click="showTweetsForHashtag(item)">
      </pie-chart>
    </div>
  </li>
</ol>

感谢大家的帮助!

Q值。