Live D3 using Backbone - graph moves slower than axis

I am trying to create realtime timeouts with D3 and Backbone. The problem I am facing is that the graph moves slower than the x axis. The X-axis is exactly the current time, so I know this is a graphics issue. I am basing the code on this example by Mike Bostock (last graph at the bottom of the post). I don't seem to see the problem - my code is following closely the example just implemented with Backbone.

The application is configured with a websocket aggregator and events so that when a new data point is received, the data point model is added to the collection, and the addition of the model triggers the "newPoint" function in the "TimeseriesView" View. "NewPoint" pushes the number to the "data" array and this is where the data for the graphics line comes in. Here is the relevant look. (Please excuse the code if it's a little messy - I'm new to Backbone so I suspect there is a cleaner way to do this)

TimeseriesView = Backbone.View.extend({
    el: "#timeseries",
    initialize: function (options) {
        var self = this;
        /*
         * Create timeseries
         */
        self.n = 243;
        self.duration = 750;
        self.now = new Date(Date.now() - self.duration);
        self.data = d3.range(self.n).map(function() { return 0; });

        self.margin = { top: 6, right: 0, bottom: 20, left: 40};
        self.width = 960 - self.margin.right;
        self.height = 120 - self.margin.top - self.margin.bottom;

        self.x = d3.time.scale()
            .domain([self.now - (self.n-2) * self.duration, self.now - self.duration])
            .range([0, self.width]);

        self.y = d3.scale.linear()
            .range([self.height, 0]);

        var x = self.x;
        var y = self.y;
        var now = self.now;
        var duration = self.duration;
        var n = self.n;
        var height = self.height;
        var width = self.width;
        var margin = self.margin;

        self.line = d3.svg.line()
            .x(function(d, i) { return x(now - (n - 1 - i) * duration); })
            .y(function(d, i) { return y(d); });

        var timeseries = d3.select("#timeseries").append("p").append("svg")
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
          .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")")

        timeseries.append("defs").append("clipPath")
            .attr("id", "clip")
          .append("rect")
            .attr("width", width)
            .attr("height", height);

        self.x.axis = d3.svg.axis().scale(x).orient("bottom");

        self.axis = timeseries.append("g")
            .attr("class", "x axis")
            .attr("transform", "translate(0," + height + ")")
            .call(x.axis);


        self.path = timeseries.append("g")
            .attr("clip-path", "url(#clip)")
          .append("path")
            .data([self.data])
            .attr("class", "line");

        self.dataSet = options.dataSet;
        self.dataSet.on('add', this.newPoint, this);
    },
    newPoint: function (pt, context) {
        var self = this;
        if (isNaN(parseFloat(pt.attributes.auth_amt))) return;
        self.data.push(parseFloat(pt.attributes.auth_amt));

        self.now = new Date();

        var now = self.now;
        var duration = self.duration;
        var n = self.n;
        var x = self.x;
        var y = self.y;
        var width = this.width;
        var height = this.height;
        console.log('self', self);
        x.axis = d3.svg.axis().scale(x).orient("bottom");

        // update the domains
        self.x.domain([now - (n - 2) * duration,
                now - duration]);
        self.y.domain([0, d3.max(self.data)]); 

        self.line = d3.svg.line()
            .x(function(d, i) {
                return x(now - (n - 1 - i) * duration); })
            .y(function(d, i) { return y(d); });

        // redraw the line
        d3.select(".line")
            .attr("d", self.line)
            .attr("transform", null);

        // slide the x-axis to the left
        self.axis.transition()
            .duration(duration)
            .ease("linear")
            .call(x.axis);

        self.x = d3.time.scale()
            .domain([now - (n-2) * duration, now - duration])
            .range([0, width]);

        var x = self.x;

        // slide the line left
        self.path.transition()
            .duration(duration)
            .ease("linear")
            .attr("transform", "translate(" +  x(now - (n - 1) * duration) + ")");

        // pop the old dat point off the front
        self.data.shift();
    }
});

      

+3


source to share


1 answer


The example you have based your code on is deeply entwined, shifting the x axis and adding data. It works great when you are polling something like in the example, not so much with data coming in at a different interval, or accidentally, as I suppose, this happens in your web site setup (see http://jsfiddle.net / K8rub / 1 / for reliable reproduction of your code)

The first thing to do is decouple the sliding (function tick

) and data injection: http://jsfiddle.net/K8rub/2/ : each anchor point has a timestamp that is used to fetch the x value, the data is initially empty and tick

only the graph slides ...

By applying these changes to your code, we get



TimeseriesView = Backbone.View.extend({

    initialize: function (options) {
        _.bindAll(this, 'tick');

       // ...

        self.dataSet = options.dataSet;
        self.dataSet.on('add', this.newPoint, this);
        self.tick();
    },

    newPoint: function(model) {
        this.data.push(model);
    },

    tick: function () {
        var self = this;
        self.now = new Date();

        var now = self.now;
        var duration = self.duration;
        var n = self.n;
        var x = self.x;
        var y = self.y;
        var width = this.width;
        var height = this.height;

        x.axis = d3.svg.axis().scale(x).orient("bottom");

        // update the domains
        self.x.domain([now - (n - 2) * duration,
                now - duration]);
        self.y.domain([0, d3.max(self.dataSet.pluck('auth_amt'))]); 


        self.line = d3.svg.line()
            .x(function(d) { return x(d.get('stamp')); })
            .y(function(d) { return y(d.get('auth_amt')); });

        // redraw the line
        d3.select(".line")
            .attr("d", self.line)
            .attr("transform", null);

        // slide the x-axis to the left
        self.axis.transition()
            .duration(duration)
            .ease("linear")
            .call(x.axis);

        self.x = d3.time.scale()
            .domain([now - (n-2) * duration, now - duration])
            .range([0, width]);

        var x = self.x;

        // slide the line left
        self.path.transition()
            .duration(duration)
            .ease("linear")
            .attr("transform", "translate(" +  x(now - (n - 1) * duration) + ")")
            .each("end", self.tick);
    }
});

      

http://jsfiddle.net/K8rub/4/

+1


source







All Articles