Как использовать директивы angularjs в сгенерированном d3 html?

Я пытаюсь использовать угловую директиву tooltip в своей визуализации d3, поэтому я есть что-то вроде

var node = svg.selectAll(".node")
    .data(nodes)
    .enter().append("circle")
        .attr("tooltip-append-to-body", true)
        .attr("tooltip", function(d) {
            return d.name;
        })
// ... attributes

Однако всплывающие подсказки не отображаются. Мне нужно $compile или что-то в этом роде? Я тоже пытался обернуть его вокруг $timeout, но это не сработало.


person zlog    schedule 05.12.2013    source источник
comment
Вы пытались добавить всплывающую подсказку в svg-circle без какого-либо d3? Возможно, вам придется обернуть его внутри постороннего объекта.   -  person Adam Pearce    schedule 05.12.2013
comment
Да, у меня он работает с d3 и простым загрузочным интерфейсом без каких-либо дополнительных элементов. Однако не могу понять, как это сделать с angular-ui.   -  person zlog    schedule 05.12.2013
comment
Если вы используете angular для добавления HTML в DOM (через директиву), то этот ответ должен помочь: stackoverflow.com/questions/19656365/ Опубликуйте еще немного кода о как этот элемент DOM добавляется.   -  person musically_ut    schedule 10.12.2013
comment
Я сталкивался с этими статьями, затрагивающими эту проблему, но кажется, что d3 и angularjs могут нуждаться в некоторой глубокой доработке, чтобы они были совместимы: alexandros.resin.io/angular-d3-svg, ng-newsletter.com/posts/d3-on-angular.html. Потребуются дополнительные исследования...   -  person zlog    schedule 11.12.2013


Ответы (5)


У меня была похожая проблема, и да, я решил ее с помощью $compile. Я предполагаю, что ваш код d3 находится внутри пользовательской директивы. Оттуда вы можете добавить свои атрибуты всплывающей подсказки, удалить свой собственный атрибут директивы, чтобы $compile запускался только один раз, и вызвать $compile:

    myApp.directive('myNodes', ['$compile', function ($compile) {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            var nodes = [{"name": "foo"}, {"name": "bar"}] 
            var mySvg = d3.select(element[0])
                  .append("svg")
                  .attr("width", 100)
                  .attr("height", 100);

            var node = mySvg.selectAll(".node")
             .data(nodes)
             .enter()
             .append("circle")
             .attr("cx", function(d,i){
                return 20+i*50;
             })
             .attr("cy", 50)
             .attr("r", 10)
             .attr("tooltip-append-to-body", true)
             .attr("tooltip", function(d){
                 return d.name;
             });

            element.removeAttr("my-nodes");
            $compile(element)(scope);
            }
        };
    }]);

Служба $compile гарантирует, что ваш элемент скомпилирован с атрибутами, добавленными вашей директивой.

Вот рабочая скрипта, использующая приведенный выше код. Надеюсь, это то, что вы ищете!

person jbll    schedule 13.12.2013
comment
У меня был код d3 в контроллере директив вместо функции ссылки, так что это больше похоже на: jsfiddle.net/ FBFR6/3, но спасибо за подсказку о том, как использовать $compile! - person zlog; 27.01.2014
comment
Также обратите внимание, что важно добавить element.removeAttr("my-nodes");, чтобы $compile не перекомпилировал директиву и не удалил узлы d3. Кроме того, это лучше всего работает как атрибут, а не как элемент, поэтому вы можете удалять и добавлять директиву по мере необходимости. - person zlog; 27.01.2014
comment
откуда element в качестве аргумента этой функции, непонятно как он выбирается, хотя вроде работает правильно.. - person qwwqwwq; 09.02.2014
comment
ответьте на мой собственный вопрос: при объявлении директивы AngularJS следует соглашение об именах - camelCase. Например, мы бы определили имя директивы как «fundooDatepicker». Но когда вы на самом деле используете директиву в своем HTML, это версия, разделенная тире. То есть наш виджет будет ‘‹fundoo-datepicker›’, а не ‘‹fundooDatepicker›». - person qwwqwwq; 09.02.2014
comment
Я использовал ваше решение с использованием $compile(element)(scope), чтобы заставить это работать на моей собственной диаграмме, но у меня есть проблема, когда я использую переходы для изменения данных. В этом случае мне нужно изменить всплывающую подсказку. Но когда я перекомпилирую, теперь всплывают несколько всплывающих подсказок. Есть ли у вас какие-либо предложения о том, как этого избежать? Поставщик $tooltip, похоже, не имеет метода уничтожения. - person Richard Bender; 22.03.2014
comment
@RichardBender, у вас есть скрипка или планкр, иллюстрирующие вашу проблему? - person jbll; 25.03.2014
comment
@RichardBender Не могли бы вы использовать одну подсказку, которая перемещается по диаграмме, а не создавать новые подсказки? - person Union find; 10.02.2015
comment
@jbll, это работает, но это хак. Вы уничтожаете директиву, чтобы вручную скомпилировать элемент. Если директиву не удалить, то компиляция работать не будет. Пожалуйста, смотрите ответ david004. - person James; 30.01.2017

Довольно хороший ответ от @jbll. Но, вероятно, будет лучше связать компиляцию директивы с концом этапа ввода. Важно иметь фазы ввода и фазы обновления, чтобы графика могла реагировать на обновления данных без повторного создания каждого элемента. В предыдущем ответе каждая директива на каждом узле компилировалась бы всякий раз, когда модель изменялась. Это может быть то, что нужно, но, вероятно, нет.

Следующий код показывает обновление графики d3 при каждом изменении переменной $scope.nodes.

Это также немного аккуратнее, потому что не требует удаления и воссоздания исходной директивы, что выглядит как хак.

Вот скрипка

Добавьте кнопку в html:

<button ng-click="moveDots()">Move the dots</button>

А затем измените файл JavaScript на:

var myApp = angular.module('myApp', ['ui.bootstrap']);

myApp.controller('myCtrl', ['$scope', function($scope){
    $scope.nodes = [
        {"name": "foo", x: 50, y: 50},
        {"name": "bar", x: 100, y: 100}
    ];
    $scope.moveDots = function(){
        for(var n = 0; n < $scope.nodes.length; n++){
            var node = $scope.nodes[n];
            node.x = Math.random() * 200 + 20;
            node.y = Math.random() * 200 + 20;
        }
    }
}]);

myApp.directive('myNodes', ['$compile', function ($compile) {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {

            var mySvg = d3.select(element[0])
                .append("svg")
                .attr("width", 250)
                .attr("height", 250);

            renderDots();

            scope.$watch("nodes", renderDots, true);

            function renderDots(){

                // ENTER PHASE

                mySvg.selectAll("circle")
                    .data(scope.nodes)
                    .enter()
                    .append("circle")
                    .attr("tooltip-append-to-body", true)
                    .attr("tooltip", function(d){
                        return d.name;
                    })
                    .call(function(){
                        $compile(this[0].parentNode)(scope);
                    });

                // UPDATE PHASE - no call to enter(nodes) so all circles are selected

                mySvg.selectAll("circle")
                    .attr("cx", function(d,i){
                        return d.x;
                    })
                    .attr("cy", function(d,i){
                        return d.y;
                    })
                    .attr("r", 10);

                // todo: EXIT PHASE (remove any elements with deleted data)
            }
        }
    };
}]);
person dave walker    schedule 06.06.2014
comment
Это правильный ответ. Удаление директивы является взломом. - person James; 30.01.2017

если html генерируется чем-то другим, кроме angularjs, и вставляется в DOM, вам нужно будет скомпилировать html, который включает ваши атрибуты директивы, прежде чем вставлять его в DOM, чтобы angular знал об этом.

person btm1    schedule 13.12.2013

Мне этот метод нравится намного больше, так как вам не нужно вызывать removeAttr (похоже на хак)

myApp.directive('myNodes', ['$compile', function ($compile) {
return {
    restrict: 'A',
    link: function(scope, element, attrs) {
        var nodes = [{"name": "foo"}, {"name": "bar"}] 
        var mySvg = d3.select(element[0])
              .append("svg")
              .attr("width", 100)
              .attr("height", 100);

        var node = mySvg.selectAll(".node")
         .data(nodes)
         .enter()
         .append("circle")
         .attr("cx", function(d,i){
            return 20+i*50;
         })
         .attr("cy", 50)
         .attr("r", 10)
         .attr("tooltip-append-to-body", true)
         .attr("tooltip", function(d){
             return d.name;
         });

        $compile(svg[0])(scope);
        }
    };
}]);
person NTyler    schedule 08.07.2014

@david004 хорошо замечает, что нужно использовать цепочку с enter(), поэтому $compile вызывается только один раз для каждого входящего элемента. Но вместо вызова $compile на parentNode я использую вызов для $compile каждый отдельный входящий элемент:

// Entering
myD3Selection.enter()
  .append( 'rect' )
  .attr( {foo: 'bar'} )
  .call( compile );

// Compile encapsulated in reusable function, so can be used on multiple enter() chains
function compile( d3Selection )
{
  d3Selection.each( function( d, i )
  {
    // this is the actual DOM element
    $compile( this )( scope );
  } );
}
person Philip Bulley    schedule 15.10.2014