shahsank3t
shahsank3t

Reputation: 252

grouping of data for focus and context graph - d3.js

I have a focus and context bar graph developed using d3.js which works perfectly fine but when I zoom out the area, I want to show the grouping value of the graph.

As seen in the below screenshot, there are two bars of value 1 in it but when I zoom out to show data it for a month, it shows only one bar with value 1 on it.

I want to group that data so it would show two when it is zoomed out. Any help is appreciated. enter image description here

I have two files: 1. Base file:

defaults: {
        margin: {top: 10, right: 20, bottom: 100, left: 40},
        margin2: {top: 425, right: 20, bottom: 30, left: 300},
    },
    onRender: function() {
        var that = this;
        //Set up graph parameters
        var margin = this.options.margin;
        var margin2 = this.options.margin2;
        this.height = (this.options.height)? this.options.height - margin.top - margin.bottom: 960 - margin.top - margin.bottom,
        this.width = (this.options.width)? this.options.width - margin.left - margin.right: 500 - margin.left - margin.right,
        this.height2 = (this.options.height)? this.options.height - margin2.top - margin2.bottom : 500 - margin2.top - margin2.bottom,
        this.width2 = this.width * .5;

        //Set up ranges (a scaling factor to map input data to output in pixels)
        this.scales = {
            x: this.getXScale(),
            x2: this.getX2Scale(),
            y: this.getYScale(),
            y2: this.getY2Scale()
        };

        //Set up and define graph content
        //----------axis----------
        this.renderAxes();

        //Setup groups to organize layout, brush areas and perform clipping
        //----------groups----------
        this.svg = d3.select(this.el).append("svg")
            .attr("class","FCBChart")
            .attr("width", this.width + margin.left + margin.right)
            .attr("height", this.height + margin.top + margin.bottom)
            .attr("viewBox", "0 0 " + (this.width + margin.left + margin.right) + " " + (this.height + margin.top + margin.bottom) )
            .attr("preserveAspectRatio", "xMidYMid meet");
        this.svg.append("defs").append("clipPath")
            .attr("id", "clip")
          .append("rect")
            .attr("width", this.width + margin.left + margin.right)
            .attr("height", this.height + margin.top + margin.bottom);
        var aspect = (this.width + margin.left + margin.right) / (this.height + margin.top + margin.bottom);
        $(window).on("resize", function() {
            var targetWidth = $(".FCBChart").parent().width();
            $(".FCBChart").attr("width", targetWidth);
            $(".FCBChart").attr("height", targetWidth / aspect);
        });

        this.focus = this.svg.append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
            .attr("class","focusGraph"); 
        // barsGroup is for making FCB graph like Bar graph
        this.barsGroup = this.focus.append("g")
            .attr('clip-path', 'url(#clip)');

        this.context = this.svg.append("g")
            .attr("class","contextGraph")
            .attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");  

        this.setupBrush();
        this.renderData();
        return this;

    }

2.Actual file with functions:

initialize : function(option){
        this.options = $.extend(true, {}, option,this.defaults);
    },
    events : {
    },

    //Set up ranges (a scaling factor to map input data to output in pixels)
    getXScale : function(){
        return d3.time.scale().range([0, this.width])
    },
    getX2Scale : function(){
        return d3.time.scale().range([0, this.width2])
    },
    getYScale : function(){
        return d3.scale.linear().range([this.height, 0])
    },
    getY2Scale : function(){
        return d3.scale.linear().range([this.height2, 0])
    },

    //Set up and define graph content
    //----------axis----------
    renderAxes : function(){
        var that = this;
        this.xAxis = d3.svg.axis().scale(this.scales.x).orient("bottom"),
        this.xAxis2 = d3.svg.axis().scale(this.scales.x2).orient("bottom"),
        this.yAxis = d3.svg.axis().scale(this.scales.y).orient("left");

    //----------area fill----------
        this.area = d3.svg.area()
            .x(function(d) {
                var that1 = that; 
                return that1.scales.x(d.x); })
            .y0(this.height)
            .y1(function(d) { 
                var that1 = that;
            return that1.scales.y(d.y); }); 
    },

    //----------Setup brush-----------------------
    setupBrush : function(){
        var that = this;
        this.brush = d3.svg.brush()
            .x(this.scales.x2)
            .on("brush", function(){
                that.brushed(this,that);
            });
    },
    brushed : function(d3This, view) {
        var that = view;
        that.scales.x.domain(that.brush.empty() ? that.scales.x2.domain() : that.brush.extent());
        //For FCB with bar chart
        that.focusGraph.attr("x", function(d, i) { return that.scales.x(d.XPoint); });
        that.focusGraph.attr("width", 20);
        that.focus.select(".x.axis").call(that.xAxis);
    },


    renderData : function(){
        var that = this;
        var x = this.scales.x,
        y = this.scales.y,
        x2 = this.scales.x2,
        y2 = this.scales.y2,
        data = this.options.data;

        data.forEach(function(d) {
            d.XPoint = d.x;
            d.values = +d.y;
        });

        // Scale the range of the data
        x.domain(d3.extent(data, (function(d) { return d.XPoint; })));
        y.domain([0, d3.max(data, (function(d) { return d.values; }))]);

        // Scale the range of the data in context graph too
        x2.domain(x.domain());
        y2.domain(y.domain());

        // Add the area graph to focus and context

        //To get bar chart in FCB
        this.focusGraph = this.barsGroup.selectAll("rect")
          .data(data)
        .enter().append("rect")
          .attr("x", function(d, i) { return x(d.XPoint); })
          .attr("y", function(d) { return y(d.values); })
          .attr("width", 5)
          .attr("height", function(d) { return that.height - y(d.values); })
          .style("fill", "steelblue");

        this.context.selectAll("rect")
          .data(data)
        .enter().append("rect")
          .attr("x", function(d, i) { return x2(d.XPoint); })
          .attr("y", function(d) { return y2(d.values); })
          .attr("width", 5)
          .attr("height", function(d) { return that.height2 - y2(d.values); })
          .style("fill", "steelblue");


        //Functions called after all json datasets loaded
        var x0 = x.copy();

        //Axis
        this.focus.append("g")
          .attr("class", "x axis")
          .attr("transform", "translate(0," + this.height + ")")
          .call(this.xAxis);

        this.focus.append("g")
          .attr("class", "y axis")
          .call(this.yAxis);

        this.context.append("g")
          .attr("class", "x axis")
          .attr("transform", "translate(0," + this.height2 + ")")
          .call(this.xAxis2);

        this.context.append("g")
          .attr("class", "x brush")
          .attr("clip-path", "url(#clip)")
          .call(this.brush)
        .selectAll("rect")
          .attr("y", 0)
          .attr("height", this.height2);

        this.context.append("text")
            .attr("class","instructions")
            .attr('x',"345")
            .attr('y','70')
            // .attr("transform", "translate(0," + (this.height2 + 25) + ")")
            .text('*Click and drag above to zoom / pan the data');
    },

Data which i provide to graph is using an API call, which for the shown image is as shown below:

enter image description here

As we can see, on 28th Feb there are total 3 counts but when i zoom out the graph and want to analyse the week's data (as shown in below image), it still shows me count on 28th Feb as 1, where in fact it should show 3.

enter image description here

Upvotes: 2

Views: 708

Answers (1)

ckersch
ckersch

Reputation: 7687

My guess is that what's happening is that you are rendering two bars even when zoomed out. They're either directly on top of each other or else very close to it. What you probably want is something called binning. Binning is where you take your data and bin it based on frequency of appearance in your data.

To implement this, you can add a simple binning function and call it every time you zoom. Assuming that you adjust your scale to match your zoomed domain, and that you want to divide your data into 20 bins, It would look something like this:

var myDomain = zoomedScale.domain(),
    binSize = (myDomain[1] - myDomain[0]) / 20,
    bins = [],
    binIndex;

for(var i = 0; i < 20; i++){
    bins.push(0);
}

for(var i = 0; i < myData.length; i++){
    binIndex = Math.floor((myData[i].value - myDomain[0])/binSize);

    if(binIndex === 20){
        binIndex[19] += myData[i].value;
    } else {
        bins[binIndex] += myData[i].value;
    }
}

Bins would now represent the sum of the data points that fall in each bin. You can then update your plot to plot the bins rather than the raw data.

Upvotes: 1

Related Questions