function Reactivis(model){ var scales = { linear: d3.scale.linear, ordinal: d3.scale.ordinal }; function scale(prefix, type){ type = type || "linear"; var scale = scales[type](); var columnProperty = prefix + "Column"; var accessorProperty = prefix + "Accessor"; var domainProperty = prefix + "Domain"; var rangeProperty = prefix + "Range"; var scaleProperty = prefix + "Scale"; model.when(columnProperty, function (column){ model[accessorProperty] = get(column); }); model.when(["data", accessorProperty], function(data, accessor){ if(type === "linear"){ model[domainProperty] = d3.extent(data, accessor); } else if(type === "ordinal"){ model[domainProperty] = data.map(accessor); } }); model.when([domainProperty, rangeProperty], function (domain, range){ model[scaleProperty] = scale .domain(domain) .range(range); }); model.when([scaleProperty, accessorProperty], function (scale, accessor){ model[prefix] = compose(scale, accessor); }); } function axis(prefix){ var axis = d3.svg.axis() .outerTickSize(0); var scaleProperty = prefix + "Scale"; var axisProperty = prefix + "Axis"; var axisGProperty = axisProperty + "G"; var ticksProperty = axisProperty + "Ticks"; var labelProperty = axisProperty + "Label"; var textProperty = labelProperty + "Text"; var tickFormatProperty = axisProperty + "TickFormat"; model.when([scaleProperty, ticksProperty, tickFormatProperty], function(scale, ticks, tickFormat){ model[axisProperty] = axis .scale(scale) .ticks(ticks) .tickFormat(tickFormat); }); model.when([axisGProperty, axisProperty], function (axisG, axis){ axisG.call(axis); }); model.when("g", function (g){ var axisG = g.append("g") .attr("class", prefix + " axis"); model[axisGProperty] = axisG; model[labelProperty] = axisG.append("text") .style("text-anchor", "middle") .attr("class", "label"); }); model.when([labelProperty, textProperty], function(label, text){ label.text(text); }); return axis; } // http://en.wikipedia.org/wiki/Function_composition function compose(g, f){ return function(d){ return g(f(d)); }; } // Abstracts the common pattern of accessing an object property. function get(property){ return function(d){ return d[property]; }; } var reactivis = { svg: function (){ model.when("container", function (container) { model.svg = container.append("svg"); }); model.when(["svg", "outerWidth"], function(svg, outerWidth){ svg.attr("width", outerWidth); }); model.when(["svg", "outerHeight"], function(svg, outerHeight){ svg.attr("height", outerHeight); }); model.when("svg", function (svg){ model.g = svg.append("g"); }); model.when(["g", "margin"], function (g, margin){ g.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); }); return reactivis; }, // This encapsulates the D3 margin convention from http://bl.ocks.org/mbostock/3019563 margin: function (){ model.when(["outerWidth", "margin"], function(outerWidth, margin){ model.innerWidth = outerWidth - margin.left - margin.right; }); model.when(["outerHeight", "margin"], function(outerHeight, margin){ model.innerHeight = outerHeight - margin.top - margin.bottom }); return reactivis; }, xScale: function (type){ scale("x", type); model.when("innerWidth", function (innerWidth){ model.xRange = [0, innerWidth]; }); return reactivis; }, yScale: function (type){ scale("y", type); model.when("innerHeight", function (innerHeight){ model.yRange = [innerHeight, 0]; }); return reactivis; }, xAxis: function (){ axis("x").orient("bottom"); model.when(["xAxisG", "innerHeight"], function (xAxisG, innerHeight){ xAxisG.attr("transform", "translate(0," + innerHeight + ")"); }); model.when(["xAxisLabel", "innerWidth"], function(xAxisLabel, innerWidth){ xAxisLabel.attr("x", innerWidth / 2); }); model.when(["xAxisLabel", "xAxisLabelOffset"], function(xAxisLabel, xAxisLabelOffset){ xAxisLabel.attr("y", xAxisLabelOffset) }); return reactivis; }, yAxis: function (){ axis("y").orient("left"); model.when(["yAxisLabel", "innerHeight", "yAxisLabelOffset"], function(yAxisLabel, innerHeight, yAxisLabelOffset){ yAxisLabel .attr("transform", "translate(-" + yAxisLabelOffset + "," + (innerHeight / 2) + ") rotate(-90)") }); return reactivis; }, rScale: function (){ scale("r"); model.when(["rMin", "rMax"], function (rMin, rMax){ model.rRange = [rMin, rMax]; }); return reactivis; }, colorScale: function (){ scale("color", "ordinal"); return reactivis; }, resize: function (){ model.when("container", function (container){ function setSize(){ var containerNode = container.node(); model.set({ outerWidth: containerNode.clientWidth, outerHeight: containerNode.clientHeight }); } d3.select(window).on("resize", setSize); setSize(); }); } }; return reactivis; }