Pergunta

When drawing a linechart with gRaphael using milliseconds along the x-axis I commonly get inconsistencies in the placement of the data points. Most commonly the initial data points are to the left of the y-axis (as seen in the fiddle below), sometimes the last data-point will be beyond the right side of the view-box/past the termination of the x-axis.

Does anyone know: 1) Why this occurs, 2) How to prevent it, &/or 3) How to check for it (I can use transform to move the lines/points if I know when it has happened/by how much).

my code:

var r = Raphael("holder"),
txtattr = { font: "12px sans-serif" };
var r2 = Raphael("holder2"),
txtattr2 = { font: "12px sans-serif" };

var x = [], y = [], y2 = [], y3 = [];

for (var i = 0; i < 1e6; i++) {
    x[i] = i * 10;
    y[i] = (y[i - 1] || 0) + (Math.random() * 7) - 3;
}
var demoX = [[1, 2, 3, 4, 5, 6, 7],[3.5, 4.5, 5.5, 6.5, 7, 8]];
var demoY = [[12, 32, 23, 15, 17, 27, 22], [10, 20, 30, 25, 15, 28]];

var xVals = [1288885800000, 1289929440000, 1290094500000, 1290439560000, 1300721700000,   1359499228000, 1359499308000, 1359499372000];
var yVals = [80, 76, 70, 74, 74, 78, 77, 72];
var xVals2 = [1288885800000, 1289929440000];
var yVals2 = [80, 76];

var lines = r.linechart(10, 10, 300, 220, xVals, yVals, { nostroke: false, axis: "0 0 1 1", symbol: "circle", smooth: true })
.hoverColumn(function () {
    this.tags = r.set();

    for (var i = 0, ii = this.y.length; i < ii; i++) {
        this.tags.push(r.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{ fill: "#fff" }, { fill: this.symbols[i].attr("fill") }]));
        }
    }, function () {
        this.tags && this.tags.remove();
        });

lines.symbols.attr({ r: 3 });


var lines2 = r2.linechart(10, 10, 300, 220, xVals2, yVals2, { nostroke: false, axis: "0 0 1 1", symbol: "circle", smooth: true })
.hoverColumn(function () {
    this.tags = r2.set();

    for (var i = 0, ii = this.y.length; i < ii; i++) {
        this.tags.push(r.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{ fill: "#fff" }, { fill: this.symbols[i].attr("fill") }]));
        }
    }, function () {
        this.tags && this.tags.remove();
        });

lines2.symbols.attr({ r: 3 });

I do have to use gRaphael and the x-axis has to be in milliseconds (it is labeled later w/customized date strings)

Primary example fiddle: http://jsfiddle.net/kcar/aNJxf/

Secondary example fiddle (4th example on page frequently shows both axis errors): http://jsfiddle.net/kcar/saBnT/

root cause is the snapEnds function (line 718 g.raphael.js), the rounding it does, while fine in some cases, is adding or subtracting years from/to the date in other cases.

Haven't stepped all the way through after this point, but since the datapoints are misplaced every time the rounding gets crazy and not when it doesn't, I'm going to go ahead and assume this is causing issues with calculating the chart columns, also before being sent to snapEnds the values are spot on just to confirm its not just receiving miscalculated data.

code of that function from g.raphael.js

snapEnds: function(from, to, steps) {
    var f = from,
        t = to;

    if (f == t) {
        return {from: f, to: t, power: 0};
    }

    function round(a) {
        return Math.abs(a - .5) < .25 ? ~~(a) + .5 : Math.round(a);
    }

    var d = (t - f) / steps,
        r = ~~(d),
        R = r,
        i = 0;

    if (r) {
        while (R) {
            i--;
            R = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
        }

        i ++;
    } else {
        if(d == 0 || !isFinite(d)) {
            i = 1;
        } else {
            while (!r) {
                i = i || 1;
                r = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
                i++;
            }
        }

        i && i--;
    }

    t = round(to * Math.pow(10, i)) / Math.pow(10, i);

    if (t < to) {
        t = round((to + .5) * Math.pow(10, i)) / Math.pow(10, i);
    }

    f = round((from - (i > 0 ? 0 : .5)) * Math.pow(10, i)) / Math.pow(10, i);
    return { from: f, to: t, power: i };
},
Foi útil?

Solução

removed the rounding nonsense from snapEnds and no more issues, not noticed any downside from either axis or any other area of the chart. If you see one I'd love to hear it though.

code of that function from g.raphael.js now:

snapEnds: function(from, to, steps) {
     return {from: from, to: to, power: 0};       
},

Outras dicas

Hi if you comment this:

        if (valuesy[i].length > width - 2 * gutter) {
            valuesy[i] = shrink(valuesy[i], width - 2 * gutter);
            len = width - 2 * gutter;
        }

        if (valuesx[i] && valuesx[i].length > width - 2 * gutter) {
            valuesx[i] = shrink(valuesx[i], width - 2 * gutter);
        }

in g.line.js, It seems to solve the problem, and it also solves a similar problem with the values in the y axis.

Upgrading from v0.50 to v0.51 fixed the issue for me.

Still not sure why it occurs, adding in a transparent set was not a desirable option. The simplest way to check for if the datapoints were rendered outside of the graph seems to be getting a bounding box for the axis set and a bounding box for the datapoints and checking the difference between the x and x2 values.

If anyone can help me with scaling the datapoint set, or figure out how to make this not happen at all, I will still happily appreciate/up vote answers

//assuming datapoints  is the Raphael Set for the datapoints, axes is the 
//Raphael Set for the axis, and datalines is the Raphael Set for the 
//datapoint lines
var pointsBBox = datapoints.getBBox();
var axesBBox = axes.getBBox();
var xGapLeft = Math.ceil(axesBBox.x - pointsBBox.x); 
//rounding up to integer to simplify, and the extra boost from y-axis doesn't 
//hurt, <1 is a negligible distance in transform
var xGapRight = Math.ceil(axesBBox.x2 - pointsBBox.x2);
var xGap = 0;
if(xGapLeft > 0){
     datapoints.transform('t' +xGapLeft +',0');
     datalines.transform('t' +xGapLeft +',0');
     xGap = xGapLeft;
}else if (xGapRight < 0) { //using else if because if it is a scale issue it will
//be too far right & too far left, meaning both are true and using transform will
//just shift it right then left and you are worse off than before, using 
//set.transform(scale) works great on dataline but when using on datapoints scales
// symbol radius not placement
     if (xGapLeft < 0 && xGapRight < xGapLeft) { xGapRight = xGapLeft; }  
//in this case the initial point is right of y-axis, the end point is right of 
//x-axis termination, and the difference between last point/axis is greater than
//difference between first point/axis

     datapoints.transform('t' +xGapRight +',0');
     datalines.transform('t' +xGapRight +',0');
     xGap = xGapRight;
}
rehookHoverOverEvent(xGap);  //there are so many ways to do this just leaving it
//here as a call to do so, if you don't the hover tags will be over the original 
//datapoints instead of the new location, at least they were in my case.
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top