I was trying to write a plugin for d3.js recently and have been confused with something perhaps trivial. There is an explanation on d3's website on how to go about creating reusable charts. The pattern looks something like this (only the most important details, full code is here):
Design Pattern 1: From d3 website
function timeSeriesChart() {
var margin = {top: 20, right: 20, bottom: 20, left: 20},
...
area = d3.svg.area().x(X).y1(Y),
line = d3.svg.line().x(X).y(Y);
function chart(selection) {
selection.each(function(data) {
// Convert data to standard representation greedily;
// this is needed for nondeterministic accessors.
data = data.map(function(d, i) {
return [xValue.call(data, d, i), yValue.call(data, d, i)];
});
// Update the x-scale.
...
// Update the y-scale.
...
// Select the svg element, if it exists.
var svg = d3.select(this).selectAll("svg").data([data]);
...
// Otherwise, create the skeletal chart.
var gEnter = svg.enter().append("svg").append("g");
...
}
// The x-accessor for the path generator; xScale ∘ xValue.
function X(d) { }
// The x-accessor for the path generator; yScale ∘ yValue.
function Y(d) { }
chart.margin = function(_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
return chart;
};
chart.height = function(_) {
if (!arguments.length) return height;
height = _;
return chart;
};
chart.x = function(_) {
if (!arguments.length) return xValue;
xValue = _;
return chart;
};
chart.y = function(_) {
if (!arguments.length) return yValue;
yValue = _;
return chart;
};
return chart;
}
I have no doubt that this pattern is robust especially because it was suggested by the creator of d3 himself. However, I have been using the following pattern for some time without problems before I came across that post (similar to how plugins are created in general):
Design Pattern 2: General way of creating plugins
(function() {
var kg = {
version: '0.1a'
};
window.kg = kg;
kg.chart = {};
// ==========================================================
// CHART::SAMPLE CHART TYPE
// ==========================================================
kg.chart.samplechart = {
// ----------------------------------------------------------
// CONFIGURATION PARAMETERS
// ----------------------------------------------------------
WIDTH: 900,
HEIGHT: 500,
MARGINS: {
top: 20,
right: 20,
bottom: 20,
left: 60,
padding: 40
},
xRange: d3.time.scale(),
yRange: d3.scale.linear(),
xAxis: d3.svg.axis(),
yAxis: d3.svg.axis(),
data: {},
// ----------------------------------------------------------
// INIT FUNCTION
// ----------------------------------------------------------
init: function() {
// Initialize and add graph to the given div
this.update();
},
// ----------------------------------------------------------
// Redraws the graph
// ----------------------------------------------------------
update: function() {
var parentThis = this;
var newData = parentThis.data;
// Continue with adding points/lines to the chart
},
// ----------------------------------------------------------
// Gets random data for graph demonstration
// ----------------------------------------------------------
getRandomData: function() {
// Useful for demo purposes
}
};
// ==========================================================
// HELPER FUNCTIONS
// ==========================================================
}());
// EXAMPLE: This renders the chart.
kg.chart.samplechart.vis = d3.select("#visualization");
kg.chart.samplechart.data = kg.chart.samplechart.getRandomData();
kg.chart.samplechart.init();
I have been using Design Pattern 2 for some time without any problems (I agree it is not super clean but I'm working on it). After looking at Design Pattern 1, I just felt it had too much redundancy. For instance, look at the last blocks of code that make the internal variables accessible (chart.margin = function(_) {}
etc.).
Perhaps this is good practice but it makes maintenance cumbersome because this has to be repeated for every different chart type (as seen here in a library called NVD3, currently under development) and increases both development effort and risk of bugs.
I would like to know what kind of serious problems I would face if I continue with my pattern or how my pattern can be improved or made closer to spirit of Design Pattern 1. I am trying to avoid changing patterns at this point because that would require a full rewrite and will introduce new bugs into a somewhat stable mini-library. Any suggestions?