|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<title>D3 Example</title> |
|
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> |
|
<script src="model-min.js"></script> |
|
<link href='http://fonts.googleapis.com/css?family=Poiret+One' rel='stylesheet' type='text/css'> |
|
<style> |
|
|
|
body { |
|
background-color: lightgray; |
|
} |
|
|
|
.axis text { |
|
font-family: 'Poiret One', cursive; |
|
font-size: 16pt; |
|
} |
|
.axis .label { |
|
font-size: 20pt; |
|
} |
|
|
|
.axis path, .axis line { |
|
fill: none; |
|
stroke: #000; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
.visualization { |
|
position: fixed; |
|
top: 50px; |
|
bottom: 50px; |
|
left: 280px; |
|
right: 280px; |
|
background-color: white; |
|
border-radius: 25px; |
|
border-style: dotted; |
|
border-width: 2px; |
|
} |
|
|
|
circle:hover { |
|
stroke-width: 2px; |
|
stroke: black; |
|
} |
|
|
|
</style> |
|
</head> |
|
<body> |
|
<div class="visualization" id="scatterPlotContainer"></div> |
|
<script> |
|
|
|
function Reactivis(model){ |
|
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 (){ |
|
var xScale = d3.scale.linear(); |
|
|
|
model.when("xColumn", function (xColumn){ |
|
model.xAccessor = get(xColumn); |
|
}); |
|
|
|
model.when(["data", "xAccessor"], function(data, xAccessor){ |
|
model.xDomain = d3.extent(data, xAccessor); |
|
}); |
|
|
|
model.when("innerWidth", function (innerWidth){ |
|
model.xRange = [0, innerWidth]; |
|
}); |
|
|
|
model.when(["xDomain", "xRange"], function (xDomain, xRange){ |
|
model.xScale = xScale |
|
.domain(xDomain) |
|
.range(xRange); |
|
}); |
|
|
|
model.when(["xScale", "xAccessor"], function (xScale, xAccessor){ |
|
model.x = compose(xScale, xAccessor); |
|
}); |
|
|
|
return reactivis; |
|
}, |
|
|
|
xAxis: function (){ |
|
var xAxis = d3.svg.axis() |
|
.orient("bottom") |
|
.tickFormat(d3.format("s")) |
|
.outerTickSize(0); |
|
|
|
model.when(["xScale", "xAxisNumTicks"], function (xScale, xAxisNumTicks){ |
|
model.xAxis = xAxis |
|
.scale(xScale) |
|
.ticks(xAxisNumTicks); |
|
}); |
|
|
|
model.when(["xAxisG", "xAxis"], function (xAxisG, xAxis){ |
|
xAxisG.call(xAxis); |
|
}); |
|
|
|
model.when("g", function (g){ |
|
|
|
model.xAxisG = g.append("g") |
|
.attr("class", "x axis"); |
|
|
|
model.xAxisLabel = model.xAxisG.append("text") |
|
.style("text-anchor", "middle") |
|
.attr("class", "label"); |
|
}); |
|
|
|
model.when(["xAxisG", "innerHeight"], function (xAxisG, innerHeight){ |
|
xAxisG.attr("transform", "translate(0," + innerHeight + ")"); |
|
}); |
|
|
|
model.when(["xAxisLabel", "innerWidth", "xAxisLabelOffset", "xAxisLabelText"], |
|
function(xAxisLabel, innerWidth, xAxisLabelOffset, xAxisLabelText){ |
|
xAxisLabel |
|
.attr("x", innerWidth / 2) |
|
.attr("y", xAxisLabelOffset) |
|
.text(xAxisLabelText); |
|
}); |
|
return reactivis; |
|
}, |
|
|
|
yScale: function (){ |
|
var yScale = d3.scale.linear(); |
|
|
|
model.when("yColumn", function(yColumn){ |
|
model.yAccessor = get(yColumn); |
|
}); |
|
|
|
model.when(["data", "yAccessor"], function(data, yAccessor){ |
|
model.yDomain = d3.extent(data, yAccessor); |
|
}); |
|
|
|
model.when("innerHeight", function (innerHeight){ |
|
model.yRange = [innerHeight, 0]; |
|
}); |
|
|
|
model.when(["yDomain", "yRange"], function (yDomain, yRange){ |
|
model.yScale = yScale |
|
.domain(yDomain) |
|
.range(yRange); |
|
}); |
|
|
|
model.when(["yScale", "yAccessor"], function (yScale, yAccessor){ |
|
model.y = compose(yScale, yAccessor); |
|
}); |
|
|
|
return reactivis; |
|
}, |
|
|
|
yAxis: function (){ |
|
var yAxis = d3.svg.axis() |
|
.orient("left") |
|
.tickFormat(d3.format("s")) |
|
.outerTickSize(0); |
|
|
|
model.when(["yScale", "yAxisNumTicks"], function (yScale, yAxisNumTicks){ |
|
model.yAxis = yAxis.scale(yScale).ticks(yAxisNumTicks); |
|
}); |
|
|
|
model.when(["yAxisG", "yAxis"], function (yAxisG, yAxis){ |
|
yAxisG.call(yAxis); |
|
}); |
|
|
|
model.when("g", function (g){ |
|
|
|
model.yAxisG = g.append("g") |
|
.attr("class", "y axis"); |
|
|
|
model.yAxisLabel = model.yAxisG.append("text") |
|
.style("text-anchor", "middle") |
|
.attr("class", "label"); |
|
}); |
|
|
|
model.when(["yAxisLabel", "innerHeight", "yAxisLabelOffset", "yAxisLabelText"], |
|
function(yAxisLabel, innerHeight, yAxisLabelOffset, yAxisLabelText){ |
|
yAxisLabel |
|
.attr("transform", "translate(-" + yAxisLabelOffset + "," + (innerHeight / 2) + ") rotate(-90)") |
|
.text(yAxisLabelText); |
|
}); |
|
|
|
return reactivis; |
|
}, |
|
|
|
rScale: function (){ |
|
var rScale = d3.scale.linear(); |
|
|
|
model.when("rColumn", function(rColumn){ |
|
model.rAccessor = get(rColumn); |
|
}); |
|
|
|
model.when(["data", "rAccessor"], function(data, rAccessor){ |
|
model.rDomain = d3.extent(data, rAccessor); |
|
}); |
|
|
|
model.when(["rMin", "rMax"], function (rMin, rMax){ |
|
model.rRange = [rMin, rMax]; |
|
}); |
|
|
|
model.when(["rDomain", "rRange"], function (rDomain, rRange){ |
|
model.rScale = rScale |
|
.domain(rDomain) |
|
.range(rRange); |
|
}); |
|
|
|
model.when(["rScale", "rAccessor"], function (rScale, rAccessor){ |
|
model.r = compose(rScale, rAccessor); |
|
}); |
|
|
|
return reactivis; |
|
}, |
|
|
|
colorScale: function (){ |
|
var colorScale = d3.scale.ordinal(); |
|
|
|
model.when("colorColumn", function(colorColumn){ |
|
model.colorAccessor = get(colorColumn); |
|
}); |
|
|
|
model.when(["data", "colorAccessor"], function(data, colorAccessor){ |
|
model.colorDomain = data.map(colorAccessor); |
|
}); |
|
|
|
model.when(["colorDomain", "colorRange"], function (colorDomain, colorRange){ |
|
model.colorScale = colorScale |
|
.domain(colorDomain) |
|
.range(colorRange); |
|
}); |
|
|
|
model.when(["colorScale", "colorAccessor"], function (colorScale, colorAccessor){ |
|
model.color = compose(colorScale, colorAccessor); |
|
}); |
|
|
|
return reactivis; |
|
} |
|
}; |
|
return reactivis; |
|
} |
|
|
|
// 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]; }; |
|
} |
|
|
|
function ScatterPlot(){ |
|
|
|
var model = Model(); |
|
|
|
Reactivis(model) |
|
.svg() |
|
.margin() |
|
.xScale().xAxis() |
|
.yScale().yAxis() |
|
.rScale() |
|
.colorScale(); |
|
|
|
model.when(["g", "data", "x", "y", "r", "color"], function(g, data, x, y, r, color){ |
|
var circles = g.selectAll("circle").data(data); |
|
circles.enter().append("circle"); |
|
circles |
|
.attr("cx", x) |
|
.attr("cy", y) |
|
.attr("r", r) |
|
.attr("fill", color) |
|
circles.exit().remove(); |
|
}); |
|
|
|
return model; |
|
} |
|
|
|
(function main(){ |
|
|
|
var container = d3.select("#scatterPlotContainer"); |
|
|
|
var scatterPlot = ScatterPlot(); |
|
|
|
scatterPlot.set({ |
|
outerWidth: 100, |
|
outerHeight: 500, |
|
margin: { left: 80, top: 10, right: 15, bottom: 80 }, |
|
rMin: 2, // "r" stands for radius |
|
rMax: 15, |
|
xColumn: "sepal_length", |
|
yColumn: "petal_length", |
|
rColumn: "sepal_width", |
|
colorColumn: "species", |
|
colorRange: d3.scale.category10().range(), |
|
xAxisLabelText: "Sepal Length (cm)", |
|
xAxisLabelOffset: 65, |
|
xAxisNumTicks: 10, |
|
yAxisLabelText: "Petal Length (cm)", |
|
yAxisLabelOffset: 35, |
|
yAxisNumTicks: 5, |
|
container: container |
|
}); |
|
|
|
d3.csv("iris.csv", type, function (data) { |
|
scatterPlot.data = data; |
|
}); |
|
|
|
function type(d){ |
|
d.sepal_length = +d.sepal_length; |
|
d.sepal_width = +d.sepal_width; |
|
d.petal_length = +d.petal_length; |
|
d.petal_width = +d.petal_width; |
|
return d; |
|
} |
|
|
|
// Allow CSS to drive visualization size. |
|
function setSize(){ |
|
var containerNode = container.node(); |
|
scatterPlot.set({ |
|
outerWidth: containerNode.clientWidth, |
|
outerHeight: containerNode.clientHeight |
|
}); |
|
} |
|
|
|
d3.select(window) |
|
.on("load" , setSize) |
|
.on("resize", setSize); |
|
|
|
}()); |
|
</script> |
|
</body> |
|
</html> |