Reusable and customizable charting library on top of d3js

55 views
Skip to first unread message

coolscitist

unread,
Apr 22, 2014, 10:16:46 AM4/22/14
to d3...@googlegroups.com
I have started building a charting library on top of d3js using javascript's inheritance. My goal is to develop reusable and fully customizable chart components. I read the article: Towards Reusable Charts. Then, I went through the [source code of nvd3

NVD3 uses boiler plate code in each chart like: copying and pasting definitions for width, height, margin etc. BUT I would rather use inheritance to avoid such boiler plate code. I would like properties like: dimensions, axes and functions like zooming and event listeners to be reusable among all charts and customizable through any instance (object) of the chart.

Here is what my current library looks like. (It only supports bubble chart for now.)

    var BaseChart = function(){
        this.width = 200; 
        this.height = 200;//and others like: margin/ chart title
    }

    //XYChart inherits from BaseChart
    var XYChart = function(){
        //It contains members for axes, axis groups, labels, ticks, domains and ranges
        //Example: One of the members it contains is "this.yAxis"
        this.yAxis = d3.svg.axis().scale(this.scaleY)
                .orient("left")
                .ticks(this.yTickCnt)
                .tickSize(-this.width);
    }

    //Extends XYChart and adds zooming
    var ZoomableXYChart = function(){
        this.zoom = function(){
            this.svg.selectAll(this.dataShape)
                .attr("transform", function(d, i) {
                    var translate = that.calculateTranslation(d);
                    return "translate(" + translate.x + "," + translate.y + ")";
                });
        }
    }

    //Extends zoomable XY chart and adds support for drawing shapes on data points
    var BubbleChart = function(){
        this.dataShape = "path";
    
        this.drawChart = function()
    {
            this.prepareChart();//Attaches zooming, draws axes etc.
    var that = this;
    this.svg.selectAll(that.dataShape)
                .data(that.data)
                .enter()
                .append(that.dataShape)
                .attr("transform", function(d) {
                    return that.transformXYData(d);
                })
                .attr("d", symbolGenerator().size(100).type(function(d) {
                    return that.generateShape(d);
                }))
                .attr("fill", function(d) {
                    return that.generateColor(d);
                })
                .on("click", function(d) {
                    that.onClickListener(this, d);
                })
    }
    }

I can create a chart in this way:

    var chart = new BubbleChart();
    chart.data = someData;
    chart.width = 900;
    chart.elementId = "#myChart";
    chart.onClickListener = function(this,d){}
    chart.drawChart();

This solution allows me to use common properties and functions across all charts. In addition to that, it allows any instance of any chart to override default properties and functions (like: onClickListener).

Do you see any limitations with such a solution? I haven't really seen javascript's inheritance used for d3.js and I wonder why? Is "chaining" that important? Using Mike Bostock's suggestions, how can we share functions like zooming across all XY charts? Isn't inheritance absolutely necessary to share functions and properties?

Miguel Pignatelli

unread,
Apr 22, 2014, 10:57:28 AM4/22/14
to d3...@googlegroups.com
Unless I'm missing something (which is quite possible) your example
doesn't look as a good example of inheritance in javascript. BubbleChart
doesn't seem to inherit from ZoomableXYChart (or any other object
defined in your sample). You have this 'prepareChart' method which is
not defined in your snippet...

Anyway, trying to answer your questions...

> Do you see any limitations with such a solution?

In my understanding you can use prototypal inheritance with d3, and I
don't think you will find any limitation following that road. I'm not a
big fan of prototypal inheritance myself and prefer other (more
expressive in my opinion) ways to achieve similar results.

> I haven't really seen
> javascript's inheritance used for d3.js and I wonder why?

I suppose this is probably a matter of taste (at least for me). The
reusable pattern works great in many situations (even complex ones).

> Is "chaining"
> that important?

I think it allows to write very expressive and fluent APIs (see for
example [1]).
It is probably becoming a standard for javascript library creation.

> Using Mike Bostock's suggestions, how can we share
> functions like zooming across all XY charts?

I find this pattern very useful (not tested!)

var base = function () {
var foo;

var b = {};

b.foo = function (new_val) {
if (! arguments.length) {
return foo;
}
foo = new_val;
return b; // allowing method chaining
};
};

var specific = function () {
var bar;

var b = base();

b.bar = function (new_val) {
if (!arguments.length) {
return bar;
}
bar = new_val;
return b; // allowing method chaining;
};

return base;
};

Client code:

var my_obj = specific()
.foo(1)
.bar(2);

ie. "specific" *extends* "base" instead of "specific" *inherits from*
"base".
While there are limitations to this pattern (for example if 'specific'
defines its own 'foo' method, 'base's 'foo' method is not accessible
through the API) I find it very easy to reason about and use.

It is probably a matter of taste though.

> Isn't inheritance
> absolutely necessary to share functions and properties?

Not really. "Traditional" oo-programming is rarely the best programming
style in javascript. It makes sense for developers coming from a
strong/strict oo background (java, for example), but javascript's
functional programming capabilities allow you to express in richer ways.

M;

[1]
http://www.smashingmagazine.com/2012/10/09/designing-javascript-apis-usability/

On 22/04/14 15:16, coolscitist wrote:
> I have started building a charting library on top of d3js using
> javascript's inheritance. My goal is to develop reusable and fully
> customizable chart components. I read the article: Towards Reusable
> Charts < http://bost.ocks.org/mike/chart/>. Then, I went through the
> [source code of nvd3 <https://github.com/novus/nvd3>.
> --
> You received this message because you are subscribed to the Google
> Groups "d3-js" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to d3-js+un...@googlegroups.com
> <mailto:d3-js+un...@googlegroups.com>.
> For more options, visit https://groups.google.com/d/optout.

Miguel Pignatelli

unread,
Apr 22, 2014, 11:02:59 AM4/22/14
to d3...@googlegroups.com
Sorry, posted before checking for typos:
(still untested)

var base = function () {
var foo;

var b = {};

b.foo = function (new_val) {
if (! arguments.length) {
return foo;
}
foo = new_val;
return b; // allowing method chaining
};

return b;
};

var specific = function () {
var bar;

var b = base();

b.bar = function (new_val) {
if (!arguments.length) {
return bar;
}
bar = new_val;
return b; // allowing method chaining;
};

return b;
};

var my_obj = specific()
.foo(1)
.bar(2);

M;


On 22/04/14 15:16, coolscitist wrote:
> I have started building a charting library on top of d3js using
> javascript's inheritance. My goal is to develop reusable and fully
> customizable chart components. I read the article: Towards Reusable
> Charts < http://bost.ocks.org/mike/chart/>. Then, I went through the
> [source code of nvd3 <https://github.com/novus/nvd3>.

coolscitist

unread,
Apr 22, 2014, 9:59:30 PM4/22/14
to d3...@googlegroups.com
Thank you for the reply.
I am implementing inheritance in this way:

SuperClass = function(){}

SubClass = function()
{
SuperClass.call(this);
}
SubClass.prototype = Object.create(SuperClass.prototype); 

I will try your suggestions and ask more questions about them.
Reply all
Reply to author
Forward
0 new messages