Saurabh Sinha
Saurabh Sinha

Reputation: 1800

issue while using d3 brush in a line+bar chart (zoom in line+bar chart)

I am trying to use brush to zoom my custom chart in d3.js but its not behaving the way it should.Any idea what could be wrong here is js fiddle http://fiddle.jshell.net/saurabh_nitc10/od8gfsd3/9/

like in this fiddle http://fiddle.jshell.net/CjaD3/1/ after brushing the bars are going out of yaxis. any idea

what is wrong with the existing fiddle.Its not behaving the way it is supposed to do after zooming.please help.I just updated the fiddle

here is the plugin I have created.

(function($) {
$.dualAxis = {};
var xMapObject=[];
var svg = '';
var focus = '';
var tip = '';
function DualAxis(element, options) {
    this.$element = $(element);
    this.options = options;
    this.margin = {top: 20, right: 80, bottom: 30, left: 40},
    width = $(this.$element.selector).width() - this.margin.left - this.margin.right,
    height = $(this.$element.selector).height() - this.margin.top - this.margin.bottom;
    this.options.height = (this.options.height == null? height: this.options.height);
    this.options.width = (this.options.width == null? width: this.options.width);
    this.rangeMax = this.getMaxX().length*100;
    if (this.rangeMax > 14000) this.rangeMax = 14000;
    this.enabled = true;
}

DualAxis.prototype = {
    draw: function(){
        this.options.data.bar.forEach(function(d) {
            d.value = +d.value;
        });
        this.options.data.line.forEach(function(d) {
            d.value = +d.value;
        });
        tip = d3.tip()
          .attr('class', 'd3-tip')
          .html(function(d) { 
            var name= xMapObject[d.date];if(name == undefined){name=d.date;}
            var table = '<table class="table table-condensed">'
                        + '<thead>'
                        + '<tr><th colspan="2" style="text-align:center"class="city">'+name+'</th></tr>'
                        + '</thead>'
                        + '<tbody>'
                        + '<tr><td>Total Sales</td><td class="visits">'+d.value+'</td></tr>'
                        + '</tbody>'
                        + '</table>';
            return table; 
          })
          .style({border: '1px solid #fff', 'box-shadow': '1px 1px 4px rgba(0,0,0,0.5)', 'border-radius': 'none','background':'#fff','color':'#555'})
          .offset([-12, 0]);
        // if(this.options.data.events.length > 1){
            // xOffset = (this.options.width/this.options.data.events.length) + 40;
        // } else {
            // xOffset = this.options.width/2;
        // }
        xOffset = 62;
        svg = this.getSvg();
        svg == null ? svg = d3.select(this.$element.selector).append("svg"): svg;
        svg = svg
        .attr("height", this.options.height+this.margin.bottom+this.margin.top)
        .attr("width",this.options.width+this.margin.right+this.margin.left)
        .attr('class',this.options.svgClassName);
        svg.append("defs").append("clipPath")
            .attr("id", "clip")
            .append("rect")
            .attr("width", this.options.width)
            .attr("height", this.options.height);
        focus = svg.append("g").attr("class", "focus").attr("transform", "translate(" + xOffset + "," + 40 + ")");
        svg.call(tip);
        //this.drawBackground(focus);

        this.drawRect(focus,tip);
        this.drawXAxis(focus);
        this.drawY1Axis(focus);
        this.drawY2Axis(focus);
        this.drawLine(focus);
        this.drawLineLow(focus);
        this.drawLineMedium(focus);
        this.drawLineHigh(focus);
        if (this.options.showLegend)this.drawLegend(focus);
        this.zoomBehaviour(focus);
    },
    drawRect: function(svg,tip){
        x = this.getX();
        x2 = this.getX();
        y1 = this.getY1();
        y2 = this.getY2();
        height = this.options.height - (this.options.height / 3);
        transTime = 0;
        if(this.options.animate) transTime = 1000;

        svg.selectAll("rect.bar").data(this.options.data.bar).enter().append("rect").attr("class","bar").attr("width", this.options.width/this.options.data.bar.length).attr("x", function (d) {
            return x(d.date);
        }).attr("y", height).attr("height", 0).style("fill", function (d,i) {
            return "#89A54E";
        }).on('mouseover', tip.show)
        .on('mouseout', tip.hide).transition().duration(transTime).attr("y", function (d) {
            if(isNaN(y1(d.value))) return 0;return y1(d.value);
        }).attr("height", function (d) {
             if(isNaN(y1(d.value))) return 0; return (height - y1(d.value));
        }).style("fill", function(d) { return "#89A54E";}).attr("rx","1.5").attr("ry","1.5");
    },

    drawXAxis:function(svg){
        svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + height + ")").call(this.getXAxis()).append("text").attr("transform", "translate(" + xOffset + "," + 40 + ")").style("text-anchor", "end").text(this.options.xAxisText).style("font-weight", "bold");
    },
    drawY1Axis:function(svg){

        svg.append("g").attr("class", "y axis").call(this.getY1Axis()).append("text").attr('id','y1AxisText').attr("transform", "rotate(-90)").attr("y",-36 ).attr("x", -(this.options.height/5)).style("text-anchor", "end").style("fill", "#266866").style("font-weight", "bold").style("letter-spacing", "1px").text(this.options.y1AxisText);
    },
    drawY2Axis:function(svg){
        svg.append("g").attr("class", "y axis").call(this.getY2Axis()).attr("transform", "translate(" + this.options.width + " ,0)")    .append("text").attr('id','y2AxisText').attr("transform", "rotate(-90)").attr("y",47 ).attr("x", -(this.options.height/5)).style("text-anchor", "end").style("fill", "#266866").style("font-weight", "bold").style("letter-spacing", "1px").text(this.options.y2AxisText);
    },
    drawLegend: function(svg){
        legend = svg.append("g").attr("class", "legend").attr('transform', 'translate(-30,'+(height+50)+')');
        max = d3.max(this.options.values,function(d){return d.length;});
        legend.selectAll('rect').data(this.options.values).enter().append("rect").attr("x", function(d,i){return (i*149);}).attr("y", "0").attr("width", 12).attr("height", 12).style("fill", function(d,i){return color(i);});
        legend.selectAll('text').data(this.options.values).enter().append("text").attr("x", function (d, i) {return (i*149+15);}).attr("y", "10").text(function (d) {return d;});
    },
    getSvg: function(){
        return this.options.svg;
    },
    getXAxis:function(){
        //var x = d3.scale.ordinal().rangeRoundBands([0, this.options.width], 0);
        return  d3.svg.axis().scale(x).tickFormat(d3.time.format("%b'%y")).orient("bottom");
        //return d3.svg.axis().scale(x).tickValues(x.domain().filter(function(d, i) {return !(i % 10); })).orient("bottom");
    },
    getY1Axis:function(){
        return d3.svg.axis().scale(y1).orient("left").tickFormat(d3.format(".2s"));
    },
    getY2Axis:function(){
        return d3.svg.axis().scale(y2).orient("right").tickFormat(d3.format(".2s"));
    },
    getMaxX: function(){ //for ordinal we dont know what to scale 

        return  this.options.data.dateForxAxis.map(function (d) {
            return d.date;});
    },
    getMaxY1: function(){
        return d3.max(this.options.data.bar, function (d) {return d.value;});
    },
    getMaxY2: function(){
        return d3.max(this.options.data.line, function (d) {return d.value;});
    },
    getX: function(){
        return d3.time.scale().range([0, this.options.width]).domain(d3.extent(this.options.data.dateForxAxis, function(d) { return d.date; }));
    },
    getY1: function(){
        return d3.scale.linear().range([this.options.height - (this.options.height / 3), 0]).domain([0, this.getMaxY1()]);
    },
    getY2: function(){
        return d3.scale.linear().range([this.options.height - (this.options.height / 3), 0]).domain([0, this.getMaxY2()]);
    },
    drawBackground: function(vis){
        vis.append("rect").attr("x", 0).attr("y", 0).attr("width", this.options.width ).attr("height", this.options.height - 50).style("fill", "grey").attr("transform", "translate(0,0)").style("opacity", "0").attr("class","background").attr("id", "background");
    },
    zoomBehaviour: function(vis){
        var that = this;
        //zoomBehaviour = d3.behavior.zoom().scaleExtent([1, 1]).on("zoom", zoom);
        brush = d3.svg.brush().x(x2).on("brush", brushed);
        width = this.options.width;
        vis.append("g").attr("class","x brush").call(brush).selectAll("rect").attr("y",-6).attr("height",this.options.height+7);
        function brushed(){
            x.domain(brush.empty() ? x2.domain() : brush.extent());
            vis.selectAll("rect.bar")
            .attr("transform", function(d) { return "translate(" + x(d.date) + ",0)"; })
             vis.select(".x.axis").call(that.getXAxis());
             //vis.select(".line").attr("d", that.getLine());
        }
    },
    drawLine:function(svg){
        svg.append('path').datum(this.options.data.line).attr("class", "line").attr("d", this.getLine());
    },
    getLine:function(){
        return d3.svg.line().interpolate("basis") .x(function(d) {return x(d.date); }).y(function(d) { return y2(d.value); });
    },
    drawLineLow:function(svg){
        svg.append('path').datum(this.options.data.future).attr("class", "lineLow").attr("d", this.getLineLow()).style("stroke-dasharray", ("3, 3"));
    },
    getLineLow:function(){
        return d3.svg.line().interpolate("basis") .x(function(d) {return x(d.date); }).y(function(d) { return y2(d.low); });
    },
    drawLineMedium:function(svg){
        svg.append('path').datum(this.options.data.future).attr("class", "lineMedium").attr("d", this.getLineMedium());
    },
    getLineMedium:function(){
        return d3.svg.line().interpolate("basis") .x(function(d) {return x(d.date); }).y(function(d) { return y2(d.medium); });
    },
    drawLineHigh:function(svg){
        svg.append('path').datum(this.options.data.future).attr("class", "lineHigh").attr("d", this.getLineHigh()).style("stroke-dasharray", ("3, 3"));
    },
    getLineHigh:function(){
        return d3.svg.line().interpolate("basis") .x(function(d) {return x(d.date); }).y(function(d) { return y2(d.high); });
    },

};
$.fn.dualAxis = function(options){

    options = $.extend({}, $.fn.dualAxis.defaults, options);
    if(options.data === '' || options.data === null){
        var err = 'dualAxis Error: Data Attribute Cannot be Empty or Null.';
        (typeof(console) != 'undefined' && console.error) ? 
            console.error(err) : 
            alert(err);
    }
    function elementOptions(ele, options) {
        return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
    };
    function get(ele) {
        var gb = $.data(ele, 'gb');
        if (!gb) {
            gb = new DualAxis(ele,elementOptions(ele, options));
            $.data(ele, 'gb', gb);
        }
        return gb;
    }
    var gb = get(this);
    gb.draw();
};

$.fn.dualAxis.defaults = {
        svgClassName: "dualAxis",
        svg: null,
        xAxisText: 'x-axis',
        y1AxisText: 'y1-axis',
        y2AxisText: 'y2-axis',
        maxX: null,
        maxyY: null,
        height:null,
        width:null,
        animate: true,
        color: ["#3366cc", "#dc3912", "#ff9900", "#109618"],
        showLegend:true,
        data: '',
    };
})(jQuery);

here is the format of data

{
"bar": [
    {
        "date": "50_2012",
        "value": "88787"
    },
    {
        "date": "155_2012",
        "value": "50573"
    },
     {
        "date": "155_2013",
        "value": "5057"
    }
],
"dateForxAxis": [
    {
        "date": "45_2012"
    },
    {
        "date": "155_2012"
    },
    {
        "date": "260_2013"
    }
],
"future": [
    {
        "high": "87878",
        "medium": "55535",
        "low": "1212"
    },
    {
        "high": "187878",
        "medium": "255535",
        "low": "14212"
    }
],
"line": [
    {
        "date": "50_2012",
        "value": "8787"
    },
    {
        "date": "60_2012",
        "value": "47474"
    },
     {
        "date": "168_2012",
        "value": "49474"
    }
]
};

calling the plugin

$('#dualAxis').dualAxis({
            data: viewData1,
            xAxisText: 'Time',
            y1AxisText:'Transactions',
            y2AxisText:'Sales',
            animate:true,
            showLegend:false
        });

Please suggest what Am I doing wrong.when it is zooming it should zoom even for line bar and the three other lines.

Upvotes: 8

Views: 1325

Answers (2)

brenzy
brenzy

Reputation: 1986

In the brushed method, you are translating the bars instead of setting a new x position (causing them to be translated based on their original x position outside the graph). Re-setting the x position in the brushed method will place the bars correctly when scaling:

vis.selectAll("rect.bar").attr("x", function (d) {
  return x(d.date);
});

A fork of your jsfiddle with this change at http://fiddle.jshell.net/brendaz/zr6kkgaa/

Upvotes: 1

Deepak Kumar
Deepak Kumar

Reputation: 43

Every thing looks fine except that you are not updating width of rect.bar elements in brushed() function. you are only updating x co-ordinate. rect.bar element width in your case is always constant. And i also noticed that when you drag the brush, elements is getting out of the confined area because of extra padding of left and right side(may be it is intentional).

So just fix the with problem and you are good to go.

Upvotes: 0

Related Questions