javascriptdata-visualizationprotovispentaho-ctoolsccc

Draw an arbitrary number of lines on a protovis chart


My goal

I'm trying to add an arbitrary number of vertical lines to my chart in protovis. Given an array of x-intercept values, I'd like to loop over this array and draw a vertical line for every intercept value. Right now, I'm able to draw a fixed number of lines, but am having trouble generalizing.

What I've done

I've made a jsfiddle showing how I can add a fixed number of lines to a graph, and also reproduced that code below. Here, I've added 2 lines by explicitly coding x_value0 and x_value1. In the code, there are two pieces of protovis code that are relevant, which I've labelled Section A and Section B. Section A is a protovis function which defines where the line will be drawn, and Section B calls those functions.

// X intercept values hard coded
var x_value0 = 30;
var x_value1 = 70;

new pvc.MetricDotChart({
    canvas:   "cccExample",
    width:    500,
    height:   400,
    axisGrid: true,
    axisZeroLine: false,
    selectable: true,

    extensionPoints: {

        plot_add: function() {
            var panel = new pv.Panel()
                // Quadrant rules above grid, but below dots.
                .zOrder(-1)

                ////
                // Section A Begin
                ////

                // Line 1
                .def('l0', function() {
                    var ccc = this.getContext();
                    var scale = ccc.chart.axes.base.scale;
                    var li = ccc.panel._layoutInfo;
                    return li.paddings.left + scale(x_value0);
                })

                // Line 2
                .def('l1', function() {
                    var ccc = this.getContext();
                    var scale = ccc.chart.axes.base.scale;
                    var li = ccc.panel._layoutInfo;
                    return li.paddings.left + scale(x_value1);
                })

                ////
                // Section A End
                ////

            ;

        ////
        // Section B Begin
        ////   

        // Line 1
            panel
            .add(pv.Rule)
            .top(0)
            .left  (function() { return this.parent.l0();     })
            .height(function() { return this.parent.height(); })
            .width(null)
            ;      

        // Line 2
            panel
            .add(pv.Rule)
            .top(0)
            .left  (function() { return this.parent.l1();     })
            .height(function() { return this.parent.height(); })
            .width(null)
            ;            

        ////
        // Section B End
        ////               

            return panel;      

        }
    }
})

.setData(testLDot2)
.render();  

What I'd like to do instead is define xvalues as an array, and loop over that. I'm kind of half way there in my attempt. I made a second jsfiddle where I try to move the xvalues into an array. The problem is that I can't seem to successfully wrap the relevant part in a for-loop. The code from that jsfiddle is:

// X intercept values in an array
var x_values = [30, 60];

new pvc.MetricDotChart({
    canvas:   "cccExample",
    width:    500,
    height:   400,
    axisGrid: true,
    axisZeroLine: false,
    selectable: true,

    extensionPoints: {

        plot_add: function() {
            var panel = new pv.Panel()
                // Quadrant rules above grid, but below dots.
                .zOrder(-1)

                ////
                // Section A Begin - How can I put this inside of a loop?
                ////

                // Line 1
                .def('l0', function() {
                    var ccc = this.getContext();
                    var scale = ccc.chart.axes.base.scale;
                    var li = ccc.panel._layoutInfo;
                    return li.paddings.left + scale(x_values[0]);
                })

                // Line 2
                .def('l1', function() {
                    var ccc = this.getContext();
                    var scale = ccc.chart.axes.base.scale;
                    var li = ccc.panel._layoutInfo;
                    return li.paddings.left + scale(x_values[1]);
                })

                ////
                // Section A End
                ////

            ;

        ////
        // Section B Begin - I'm able to successfully loop this part
        ////   

            for (i = 0; i < x_values.length; i++) {
                // The name of the .def function defined above
                var fn_name = 'this.parent.l' + i + '()';
                // Draw the lines
                    panel
                    .add(pv.Rule)
                    .top(0)
                    .left  (function() { return eval(fn_name);     })
                    .height(function() { return this.parent.height(); })
                    .width(null)
;      

            }

        ////
        // Section B End
        ////               

            return panel;      

        }
    }
})

.setData(testLDot2)
.render();

I'm able to wrap Section B inside of a for-loop, and I'd like to do something similar for Section A:

for (i = 0; i < x_values.length; i++) {
    .def('l'+i, function() {
        var ccc = this.getContext();
        var scale = ccc.chart.axes.base.scale;
        var li = ccc.panel._layoutInfo;
        return li.paddings.left + scale(x_values[i]);
    })
}

or something similar. But the problem is that protovis doesn't seem to allow me to put any code around this .def block.

I've also tried generating a string for each item in the x_values array, which contains the definition of the Section A function, and then calling it inside of the protovis code using eval(), but that hasn't worked so far.

Any help here would be greatly appreciated!

Edit - more progress

I seem to have gotten closer to what I want by eliminating Section A and moving that function inside of Section B. See my latest jsfiddle for that code. Previously, in Section B, the .left line called one of the functions defined in Section A. Instead, I moved that function's definition inside of the .left line of code, like so:

////
// Section B Begin
////   

for (var i = 0; i < x_values.length; i++) {
    var x = x_values[i];
    // Lines
    panel
    .add(pv.Rule)
    .top(0)
    .left  (function() {                 
        var ccc = this.parent.getContext();
        var scale = ccc.chart.axes.base.scale;
        var li = ccc.panel._layoutInfo;
        return li.paddings.left + scale(x);                                     
    })
    .height(function() { return this.parent.height(); })
    .width(null)
    ;      
}

This is running now, but still not quite right: it's only drawing the last line in the x_values array and overwriting all previous ones. Any ideas?


Solution

  • Replace your loop by the following code

    panel
        .add(pv.Rule)
        .data(x_values)
        .top(0)
        .left  (
            function(d) {                 
            this.index * 20 + 15
            var ccc = this.parent.getContext();
            var scale = ccc.chart.axes.base.scale;
            var li = ccc.panel._layoutInfo;
            return li.paddings.left + scale(d);                                     
        })
        .height(function() { return this.parent.height(); })
        .width(null)
        ;     
    

    Fiddle http://jsfiddle.net/f9uhzaz9/