SkyWalker
SkyWalker

Reputation: 14309

D3 & Angular: What's the best way to resize multiple plots on window resize?

I have a reports page containing three D3 JS v3 plots and some tabular data. The page looks great while the window is maximized but when the user resizes the browser windows it starts to behave oddly i.e. each plot overflows the containing div etc.

After a bit of research I found that I just need to set the viewBox attribute of the root svg for each plot i.e.

var margin = {top: 40, right: 20, bottom: 20, left: 20},
    width = 450 - margin.left - margin.right,
    height = 370 - margin.top - margin.bottom;
var chartSvg = d3.select("#myPlotDivId")
            .append("svg")
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
            .attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
            .attr("preserveAspectRatio", "xMidYMid meet")
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

This I can do within the specific AngularJS controller and is no problem. However, I also need to update all three plot svgs each time the window is resized. There is a nice JSFiddle demonstrating this:

var aspect = chartSvg.width() / chartSvg.height(),
    container = chartSvg.parent();
$(window).on("resize", function() {
   var targetWidth = container.width();
   chartSvg.attr("width", targetWidth);
   chartSvg.attr("height", Math.round(targetWidth / aspect));
}).trigger("resize");

However, I'd prefer to do this within the controller and the function from where I create the plots ... How can I do this? I would not like to have a Window event hook out in the page calling my controllers or creating a global directive just for this. Any ideas?

Upvotes: 0

Views: 812

Answers (2)

SkyWalker
SkyWalker

Reputation: 14309

Finally I did it as follows i.e. injecting the window component into my controller and defining an onresize event handler:

class ReportCtrl {
    // 1) inject the window into my controller
    constructor(private $window: IWindowService, ...) {
        // etc ...

        // 2) reusable function to resize a d3 js plot
        var resizePlot = function (divId: string) {
            var container = d3.select(divId);
            var svg = container.select("svg");
            var aspect = svg.attr("width") / svg.attr("height");
            var targetWidth = container.node().getBoundingClientRect().width;
            svg.attr("width", targetWidth);
            svg.attr("height", Math.round(targetWidth / aspect));         
        };

        // 3) hook on the window onresize event
        $window.onresize = function (ev: UIEvent) {
            resizePlot("#hist");
            resizePlot("#scatter1");
            resizePlot("#scatter2");
        };  
    }
}

Upvotes: 1

Petr Averyanov
Petr Averyanov

Reputation: 9476

First of all, I hope you have some angular component/directive wrapping d3 code, if not - this is something you should do. Then this component should accept width and height:

app.component('myd3Plot', ...
width: '<',
height: '<'

And in parent controller/component you have listener for window resize and adjust child plots sizes:

window.on('resize', function() {
   vm.child1.height = ...
   vm.child2.height = ...
})

<myd3-plot height="$ctrl.child1.height"...
<myd3-plot height="$ctrl.child2.height"...

Upvotes: 2

Related Questions