Angular-ui + D3: how to implement contextual menu

2019-02-07 08:37发布

问题:

Given the following use-case:

I use D3js to render objects which are managed by AngularJS. I would like to add interactivity to the D3 chart. When clicking on a svg element I would like to have a kind of popup menu allowing to modify the object properties. These properties are required by AngularJS but are not rendered by D3.

The D3-Angular integration is derived from http://bl.ocks.org/biovisualize/5372077 which uses a closure.

Current implementation:

As of today I am using the $modal service from angular-ui bootstrap to create the popup menu. From a functionnality point of view it works pretty well: When clicking on a svg element, D3 dispatches an event That event is catched by Angular which calls the $modal service Within the modal window I modify the object properties

However I am not satisfied with the rendering. I would like the popup menu to look like a popover. It should be placed close to the svg element which was clicked.

As far as I understand, I have two options:

  1. Continue to use the $modal service and modify its look. What approach should be taken? Using the windowClass option?
  2. Stop using the $modal service and start hacking on the popover directive. The problem is that I do not think it is possible to add such a directive to an svg element. The solution would be to create a popover service close to the $modal service.

Which option should be chosen? and how to implement it?

EDIT:

Working plunker using a custom my-popover directive: http://plnkr.co/edit/5KYvxi?p=preview

回答1:

It is possible to add a directives to code generated by d3, only thing you need to ensure is that you call the $compile service on the content after it has been rendered.

For the given example, it would look something like this:

    .directive('barChart', function($compile){  // inject $compile
        var chart = d3.custom.barChart();
        return {
            restrict: 'E',
            replace: true,
            template: '<div class="chart"></div>',
            scope:{
                height: '=height',
                data: '=data',
                hovered: '&hovered'
            },
            link: function(scope, element, attrs) {
                var chartEl = d3.select(element[0]);
                chart.on('customHover', function(d, i){
                    scope.hovered({args:d});
                });

                scope.$watch('data', function (newVal, oldVal) {
                    chartEl.datum(newVal).call(chart);
                    $compile(element.contents())(scope);   // <-- call to $compile
                });

                scope.$watch('height', function(d, i){
                    chartEl.call(chart.height(scope.height));
                    $compile(element.contents())(scope); // <-- call to $compile
                })
            }
        }

And in the d3's drawing function:

       bars.enter().append('rect')
            .classed('bar', true)
            .attr('myPopover', 'Text to show') // <-- Adding an attribute here.
            .attr({x: chartW,
                width: barW,
                y: function(d, i) { return y1(d); },
                height: function(d, i) { return chartH - y1(d); }
            })
            .on('mouseover', dispatch.customHover);

Demo