I've been working with this library for a week or so now, and I'm really happy with the functionality so far. Thanks for all the work you guys have done!
One thing I've struggled with is modularizing graph components in a clean way.
The current mixin functionality (as far as I understand) provides a mechanism for superimposing components on top of each other, but requires them all to be stand-alone charts that don't share any attributes or properties.
This works fine until you want to abstract components that rely on the attributes of the original chart.
For example, imagine I want to provide a tooltip that displays the x and y values on chart mouseover. I'll start by defining a translucent overlay layer that records mouse movements over my chart:
chart.layer('overlay', chart.areas.overlay, {
dataBind: function (data) {
return this.selectAll('rect')
.data([data]);
},
insert: function(){
return this.append('rect')
.style('fill', 'rgba(0,0,0,0.0)')
.attr("width", chart.w - chart.margins.left)
.attr("height", chart.h - chart.margins.bottom)
.on("mousemove", function() {
var mouseX = chart.x.invert(d3.mouse(this)[0]),
mouseY = chart.y.invert(d3.mouse(this)[1]);
// display mouse cords in previously defined layer
chart.areas.focus.text(mouseX +' '+ mouseY);
})
}
});
I'd like to be able to abstract this functionality so I can apply it to multiple graphs across my site. The issue is that it relies on previously defined chart attributes (my x and y scales, as well as chart.h and chart.w), and therefore can't be decoupled fully from my base graph.
The other option would be extending an existing linegraph to include the overlay functionality like so:
d3.chart('Line', {
initialize: function() {
var chart = this;
chart.w = chart.base.attr('width') || 960;
chart.h = chart.base.attr('height') || 600;
chart.x = d3.time.scale()
.range([0, chart.w - chart.margins.left]);
chart.y = d3.scale.linear()
.range([chart.h - chart.margins.bottom, 0]);
chart.areas = {};
...
})
d3.chart('Line').extend("LineWithOverlay", {
initialize: function() {
chart.layer('overlay', chart.areas.overlay, {
dataBind: function(data) {
...
},
insert: function() {
...
}
});
}
});
This works, but seems kludgy as I'd have to create an new extended chart every time I want to use the overlay functionality.
Is there a better way to modularize chart components that I'm not seeing? If not, is allowing mixins with context a possibility? ex:
d3.chart('Line', {
var chart = this;
initialize: function() {
this.overlay = this.mixin("overlay", chart, this.base.append("g"));
this.axis = this.mixin("axis", chart, this.base.append("g"));
}
});
Would love to hear your thoughts!