I have been learning JS (mostly using D3) and I wanted to put the Module Design Pattern to use. I wrote a simple script that lets you build a bar chart with more ease / readable code.
The end user code:
canvas.initialise()
canvas.margin(20, 100, 60, 20)
canvas.width(500)
canvas.height(400)
canvas.finalise()
svg = canvas.get_canvas()
my_data = [["A", 10], ["B", -3], ["C", 4]]
bar_chart.data(my_data)
bar_chart.x_scale([0, canvas.get_width()], .1, 1)
bar_chart.y_scale([canvas.get_height(), 0])
bar_chart.formatting(".2f")
bar_chart.add_to(svg)
The library code which uses the MDP:
var canvas = (function ()
{
// private
var svg;
var margin, width, height, x_pos, y_pos;
var initialise = function () { svg = d3.select("body").append("svg") }
var set_position = function (x, y) { x_pos = x; y_pos = y;}
var set_margins = function (top, bottom, left, right) { margin = { top: top, bottom: bottom, left: left, right: right }; }
var set_width = function (val) { width = val - margin.left - margin.right; svg.attr("width", val + margin.left + margin.right); }
var set_height = function (val) { height = val - margin.top - margin.bottom; svg.attr("height", val + margin.top + margin.bottom); }
var finalise = function ()
{
svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("x", x_pos)
.attr("y", y_pos)
}
return {
initialise: initialise,
position: set_position,
margin: set_margins,
width: set_width,
height: set_height,
finalise: finalise,
get_canvas: function () { return svg },
get_margin: function () { return margin },
get_width: function () { return width },
get_height: function () { return height }
}
}());
var bar_chart = (function ()
{
var data,
x, y,
xAxis, yAxis,
formatting;
var set_data = function(data_input)
{
data = data_input;
}
var set_x_scale = function (interval, padding, outer_padding)
{
x = d3.scale.ordinal()
.rangeRoundBands(interval, padding, outer_padding)
}
var set_y_scale = function (interval)
{
y = d3.scale.linear()
.range(interval)
}
var set_formatting = function (fmt)
{
formatting = d3.format(fmt);
}
var add_to = function(svg)
{
console.log(data)
xAxis = d3.svg.axis()
.scale(x) // call ticks?
yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatting)
x.domain(data.map(function (d) { return d[0] }))
y.domain([-5, 15]) //make general
// Put the axes on the canvas
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + canvas.get_margin().left + "," + canvas.get_height() + ")")
.call(xAxis)
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + canvas.get_margin().left + ",0)")
.call(yAxis)
// Put the data in rectangle elements and put on canvas
var bars = svg.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("class", "bar")
.attr("width", x.rangeBand())
.attr("x", function (d) { return x(d[0]) + canvas.get_margin().left }) // hmm...
.attr("y", function (d)
{
if (d[1] < 0)
{
return y(0);
}
else
return y(d[1])
})
.attr("height", function (d) { return y(0) - y(Math.abs(d[1])) });
}
return {
data: set_data,
formatting: set_formatting,
x_scale: set_x_scale,
y_scale: set_y_scale,
add_to: add_to
}
}());
My concerns:
- I feel that having to make sure to call some functions before others means my structure is off (sometimes unavoidable, but comments on this would be useful)
- is this a suitable use of the design pattern?
- can usability be enhanced by using the pattern differently?
- is a different pattern more suitable in this case?
camelCase
, notsnake_case
. \$\endgroup\$ – somebody May 31 '16 at 12:17return y(d[1])
. You should probably run your code throught JSHint to fix small code style inconsistencies before you post it here. \$\endgroup\$ – somebody May 31 '16 at 12:22data()
callredraw()
(which would be aclear()
followed by anadd_to()
) ifsvg
has been set (i.e.if (svg) { this.redraw(); }
. Otherwise, if you don't want the graph to be editable, set an internal variable totrue
oncedata()
is called, so you can warn or throw and error whendata()
is called again. \$\endgroup\$ – somebody May 31 '16 at 12:31