|
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; |
|
} |