聚焦和上下文图的数据分组 - d3.js

时间:2014-02-28 15:22:55

标签: javascript d3.js

我有一个使用d3.js开发的焦点和上下文条形图,它工作得很好但是当我缩小区域时,我想显示图形的分组值。

如下面的屏幕截图所示,其中有两个值为1的条形图,但当我缩小显示数据一个月时,它只显示一个值为1的条形图。

我想对这些数据进行分组,以便在缩小时显示两个数据。任何帮助表示赞赏。 enter image description here

我有两个文件: 1.基础文件:

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.具有功能的实际文件:

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');
    },

我提供给图表的数据是使用API​​调用,对于显示的图像如下所示:

enter image description here

正如我们所看到的,2月28日共有3个计数,但当我缩小图表并想分析周的数据时(如下图所示),它仍然显示我在2月28日的数量为1,其中实际上它应该显示3。

enter image description here

1 个答案:

答案 0 :(得分:1)

我的猜测是,即使缩小,你正在渲染两个柱。它们要么直接在彼此之上,要么非常接近它。你可能想要的是一种叫做binning的东西。 Binning是您获取数据并根据数据中出现频率对其进行分类的地方。

要实现此功能,您可以添加一个简单的分箱功能,并在每次缩放时调用它。假设您调整比例以匹配缩放的域,并且您希望将数据分成20个分箱,它看起来像这样:

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现在表示每个bin中的数据点的总和。然后,您可以更新绘图以绘制箱而不是原始数据。