Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set the dynamic or static tick size in a Rickshaw.js plot?

I am creating a Rickshaw.js-powered graph much like in this example: http://code.shutterstock.com/rickshaw/tutorial/example_07.html based on my own data that is returned via an AJAX call. The data is either measured in bytes (typical values range in a few gigabytes or hundreds of MBs) or seconds (anywhere between 10s and 50 minutes). I tried using a Rickshaw.Fixtures.Number.formatBase1024KMGTP formatter for the bytes and wrote my own for the seconds, which does its part well. The problem is that I need to position the tick lines in a smart way - preferably dynamically, but even static settings (e.g. place a tick every 1024*1024*1024=1 GB or every 60 s) would be fine.

I tried setting the tickSize to 1024^3 like so:

var y_axis = new Rickshaw.Graph.Axis.Y({
    graph: graph,
    tickSize: 1073741824 // 1 GB
});
y_axis.render();

but I ended up seeing no ticks at all. What am I doing wrong and what would be the right way?

like image 332
Hamish Grubijan Avatar asked Apr 25 '13 20:04

Hamish Grubijan


2 Answers

Basically, you need to adapt the tickOffsets() function of the Axis.X, Axis.Y and Axis.Time classes in Rickshaw.

tickSize will not help you with that as - like @Old Pro stated correctly - it indicates the size of the bold tick lines in pixels. It has nothing to do with spacing.

For Time-based Axes

My solution essentially consists of replacing the standard tickOffsets() function in those files

this.tickOffsets = function() {
    var domain = this.graph.x.domain();

    var unit = this.fixedTimeUnit || this.appropriateTimeUnit();
    var count = Math.ceil((domain[1] - domain[0]) / unit.seconds);

    var runningTick = domain[0];
    var offsets = [];

    for (var i = 0; i < count; i++) {
        var tickValue = time.ceil(runningTick, unit);
        runningTick = tickValue + unit.seconds / 2;

        offsets.push( { value: tickValue, unit: unit } );
    }

    return offsets;
};

by a custom routine. This is gonna do the trick:

this.tickOffsets = function() {
    var domain = this.graph.x.domain();
    var unit = this.fixedTimeUnit || this.appropriateTimeUnit();

    var tickSpacing = args.tickSpacing || unit.seconds;
    var count = Math.ceil((domain[1] - domain[0]) / tickSpacing);

    var runningTick = domain[0];
    var offsets = [];

    for (var i = 0; i < count; i++) {
        var tickValue = time.ceil(runningTick, unit);
        runningTick = tickValue + tickSpacing;

        offsets.push( { value: tickValue, unit: unit } );
    }
    return offsets;
};

With that in place, you can write something like

var time = new Rickshaw.Fixtures.Time();
var timeUnit = time.unit('year');

var x_axis = new Rickshaw.Graph.Axis.ExtendedTime( 
    { 
        graph: graph, 
        tickSpacing: 60*60*24*365*13, // 13 years
        timeUnit: timeUnit
    } );

to have ticks spaced out evenly at every 13 years.

For Value-based Axes

For value-based Axes, you would need to extend the render() function to include a facility that "manually" sets the ticks for the axis. I did it like this:

this.render = function() {

    if (this.graph.height !== this._renderHeight) this.setSize({ auto: true });

    var axis = d3.svg.axis().scale(this.graph.y).orient(this.orientation);

    if (this.tickSpacing) {
        var tickValues = [];

        var min = Math.ceil(axis.scale().domain()[0]/this.tickSpacing);
        var max = Math.floor(axis.scale().domain()[1]/this.tickSpacing);

        for (i = min * this.tickSpacing; i < max; i += 1) {
            console.log(i);
            tickValues.push(i * this.tickSpacing);
        }
        axis.tickValues(tickValues);
    }

    axis.tickFormat( args.tickFormat || function(y) { return y } );

    if (this.orientation == 'left') {
        var berth = this.height * berthRate;
        var transform = 'translate(' + this.width + ', ' + berth + ')';
    }

    if (this.element) {
        this.vis.selectAll('*').remove();
    }

    this.vis
        .append("svg:g")
        .attr("class", ["y_ticks", this.ticksTreatment].join(" "))
        .attr("transform", transform)
        .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));

    var gridSize = (this.orientation == 'right' ? 1 : -1) * this.graph.width;

    this.graph.vis
        .append("svg:g")
        .attr("class", "y_grid")
        .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize));

    this._renderHeight = this.graph.height;
};

The important part here are the statements in the if (this.tickSpacing) clause. They compute ticks given by the tickSpacing variable in the config array, and assign them to the axis in the axis.tickValues(tickValues) statement. Note that this.tickValues is assigned in the this.tickSpacing = args.tickSpacing statement in the initialize() function, not stated above.

Try it yourself

Have a look at this jsfiddle, where the complete code is available. This will certainly give you some pointers. If you want, you can create your own jsfiddle with your values and tell me if you need anything else.

like image 60
likeitlikeit Avatar answered Oct 20 '22 10:10

likeitlikeit


tickSize is the size of the ticks in pixels. Not what you want to be setting to a huge number.

Set ticks to the number of ticks you want on the graph and Rickshaw (actually d3) will do some magic to give you pretty values of ticks that generate about that number of ticks on the graph.

If you want further control you're going to have to dig into d3, where you will be able to explicitly set the tick values using axis.tickValues(). I'd probably copy the existing Rickshaw.Graph.Axis.Y code and create my own Y axis class that includes access to tickValues or the ability to use my own scale. It's a little unclean in that Rickshaw creates the Y scale in the graph.render() function, so you can't easily override the Y scale, but the Y scale Rickshaw creates does have the range set from the graph data, which is information you will want when creating your own tick values.

like image 1
Old Pro Avatar answered Oct 20 '22 10:10

Old Pro