Skip to content

Instantly share code, notes, and snippets.

@curran
Last active August 29, 2015 14:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save curran/9aafca5fba0c7fde13aa to your computer and use it in GitHub Desktop.
Save curran/9aafca5fba0c7fde13aa to your computer and use it in GitHub Desktop.
Reusable Scatter Plot with Model.js

This is a scatter plot of the Iris data set.

The purpose of this example is to show one approach for using Model.js to make reusable visualization modules. This also introduces a way to isolate reusable reactive flows that are reusable between visualizations.

Notice that if you open this in a new window, it responds when you resize the browser window. This is coded in such a way that the visualization size can be controlled via CSS.

This example is a lead-in to The Reactivis Concept, which takes generalization of D3 patterns further.

The code for this is derived from example 106 of the screencast Introduction to D3.js.

<!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 ScatterPlot(container){
var model = Model();
var svg = container.append("svg");
var g = svg.append("g");
var xAxisG = g.append("g")
.attr("class", "x axis");
var xAxisLabel = xAxisG.append("text")
.style("text-anchor", "middle")
.attr("class", "label");
var yAxisG = g.append("g")
.attr("class", "y axis");
var yAxisLabel = yAxisG.append("text")
.style("text-anchor", "middle")
.attr("class", "label");
var xAxis = d3.svg.axis()
.orient("bottom")
.tickFormat(d3.format("s"))
.outerTickSize(0);
var yAxis = d3.svg.axis()
.orient("left")
.tickFormat(d3.format("s"))
.outerTickSize(0);
// This encapsulates the D3 margin convention from http://bl.ocks.org/mbostock/3019563
model.when(["outerWidth", "outerHeight", "margin"],
function(outerWidth , outerHeight , margin){
model.set({
innerWidth: outerWidth - margin.left - margin.right,
innerHeight: outerHeight - margin.top - margin.bottom
});
});
model.when(["outerWidth" ], function (outerWidth ){ svg.attr("width" , outerWidth ); });
model.when(["outerHeight"], function (outerHeight){ svg.attr("height", outerHeight); });
model.when("margin", function (margin){
g.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
});
model.when("innerHeight", function (innerHeight){
xAxisG.attr("transform", "translate(0," + innerHeight + ")");
});
model.when(["innerWidth", "xAxisLabelOffset", "xAxisLabelText"],
function(innerWidth, xAxisLabelOffset, xAxisLabelText){
xAxisLabel
.attr("x", innerWidth / 2)
.attr("y", xAxisLabelOffset)
.text(xAxisLabelText);
});
model.when(["innerHeight", "yAxisLabelOffset", "yAxisLabelText"],
function(innerHeight, yAxisLabelOffset, yAxisLabelText){
yAxisLabel
.attr("transform", "translate(-" + yAxisLabelOffset + "," + (innerHeight / 2) + ") rotate(-90)")
.text(yAxisLabelText);
});
(function (){
var xScale = d3.scale.linear();
model.when(["data", "xAccessor"], function(data, xAccessor){
model.xScaleDomain = d3.extent(data, xAccessor);
});
model.when(["xScaleDomain", "innerWidth"], function (xScaleDomain, innerWidth){
model.xScale = xScale.domain(xScaleDomain).range([0, innerWidth]);
});
}());
(function (){
var yScale = d3.scale.linear();
model.when(["data", "yAccessor"], function(data, yAccessor){
model.yScaleDomain = d3.extent(data, yAccessor);
});
model.when(["yScaleDomain", "innerHeight"], function (yScaleDomain, innerHeight){
model.yScale = yScale.domain(yScaleDomain).range([innerHeight, 0]);
});
}());
(function (){
var rScale = d3.scale.linear();
model.when(["data", "rColumn"], function(data, rColumn){
model.rScaleDomain = d3.extent(data, function (d){ return d[rColumn]; });
});
model.when(["rScaleDomain", "rMin", "rMax"], function (rScaleDomain, rMin, rMax){
model.rScale = rScale.domain(rScaleDomain).range([rMin, rMax]);
});
}());
model.when(["xScale", "xAxisNumTicks"], function (xScale, xAxisNumTicks){
xAxisG.call(xAxis.scale(xScale).ticks(xAxisNumTicks));
});
model.when(["yScale", "yAxisNumTicks"], function (yScale, yAxisNumTicks){
yAxisG.call(yAxis.scale(yScale).ticks(yAxisNumTicks));
});
model.when("xColumn", function (xColumn){
model.xAccessor = function (d){ return d[xColumn]; };
});
model.when(["xScale", "xAccessor"], function (xScale, xAccessor){
model.x = compose(xScale, xAccessor);
});
model.when("yColumn", function(yColumn){
model.yAccessor = function (d){ return d[yColumn]; };
});
model.when(["yScale", "yAccessor"], function (yScale, yAccessor){
model.y = compose(yScale, yAccessor);
});
model.when("rColumn", function(rColumn){
model.rAccessor = function (d){ return d[rColumn]; };
});
model.when(["rScale", "rAccessor"], function (rScale, rAccessor){
model.r = compose(rScale, rAccessor);
});
model.when("colorColumn", function(colorColumn){
model.colorAccessor = function (d){ return d[colorColumn]; };
});
model.when(["colorScale", "colorAccessor"], function (colorScale, colorAccessor){
model.color = compose(colorScale, colorAccessor);
});
model.when(["data", "x", "y", "r", "color"], function(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;
}
// http://en.wikipedia.org/wiki/Function_composition
function compose(g, f){
return function(d){ return g(f(d)); };
}
function main(){
var container = d3.select("#scatterPlotContainer");
var scatterPlot = ScatterPlot(container);
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",
colorScale: d3.scale.category10(),
xAxisLabelText: "Sepal Length (cm)",
xAxisLabelOffset: 65,
xAxisNumTicks: 10,
yAxisLabelText: "Petal Length (cm)",
yAxisLabelOffset: 35,
yAxisNumTicks: 5
});
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;
}
d3.csv("iris.csv", type, function (data) {
scatterPlot.data = data;
});
function setSize(){
var containerNode = container.node();
scatterPlot.set({
outerWidth: containerNode.clientWidth,
outerHeight: containerNode.clientHeight
});
}
d3.select(window).on("load" , setSize);
d3.select(window).on("resize", setSize);
}
main();
</script>
</body>
</html>
sepal_length sepal_width petal_length petal_width species
5.1 3.5 1.4 0.2 setosa
4.9 3.0 1.4 0.2 setosa
4.7 3.2 1.3 0.2 setosa
4.6 3.1 1.5 0.2 setosa
5.0 3.6 1.4 0.2 setosa
5.4 3.9 1.7 0.4 setosa
4.6 3.4 1.4 0.3 setosa
5.0 3.4 1.5 0.2 setosa
4.4 2.9 1.4 0.2 setosa
4.9 3.1 1.5 0.1 setosa
5.4 3.7 1.5 0.2 setosa
4.8 3.4 1.6 0.2 setosa
4.8 3.0 1.4 0.1 setosa
4.3 3.0 1.1 0.1 setosa
5.8 4.0 1.2 0.2 setosa
5.7 4.4 1.5 0.4 setosa
5.4 3.9 1.3 0.4 setosa
5.1 3.5 1.4 0.3 setosa
5.7 3.8 1.7 0.3 setosa
5.1 3.8 1.5 0.3 setosa
5.4 3.4 1.7 0.2 setosa
5.1 3.7 1.5 0.4 setosa
4.6 3.6 1.0 0.2 setosa
5.1 3.3 1.7 0.5 setosa
4.8 3.4 1.9 0.2 setosa
5.0 3.0 1.6 0.2 setosa
5.0 3.4 1.6 0.4 setosa
5.2 3.5 1.5 0.2 setosa
5.2 3.4 1.4 0.2 setosa
4.7 3.2 1.6 0.2 setosa
4.8 3.1 1.6 0.2 setosa
5.4 3.4 1.5 0.4 setosa
5.2 4.1 1.5 0.1 setosa
5.5 4.2 1.4 0.2 setosa
4.9 3.1 1.5 0.1 setosa
5.0 3.2 1.2 0.2 setosa
5.5 3.5 1.3 0.2 setosa
4.9 3.1 1.5 0.1 setosa
4.4 3.0 1.3 0.2 setosa
5.1 3.4 1.5 0.2 setosa
5.0 3.5 1.3 0.3 setosa
4.5 2.3 1.3 0.3 setosa
4.4 3.2 1.3 0.2 setosa
5.0 3.5 1.6 0.6 setosa
5.1 3.8 1.9 0.4 setosa
4.8 3.0 1.4 0.3 setosa
5.1 3.8 1.6 0.2 setosa
4.6 3.2 1.4 0.2 setosa
5.3 3.7 1.5 0.2 setosa
5.0 3.3 1.4 0.2 setosa
7.0 3.2 4.7 1.4 versicolor
6.4 3.2 4.5 1.5 versicolor
6.9 3.1 4.9 1.5 versicolor
5.5 2.3 4.0 1.3 versicolor
6.5 2.8 4.6 1.5 versicolor
5.7 2.8 4.5 1.3 versicolor
6.3 3.3 4.7 1.6 versicolor
4.9 2.4 3.3 1.0 versicolor
6.6 2.9 4.6 1.3 versicolor
5.2 2.7 3.9 1.4 versicolor
5.0 2.0 3.5 1.0 versicolor
5.9 3.0 4.2 1.5 versicolor
6.0 2.2 4.0 1.0 versicolor
6.1 2.9 4.7 1.4 versicolor
5.6 2.9 3.6 1.3 versicolor
6.7 3.1 4.4 1.4 versicolor
5.6 3.0 4.5 1.5 versicolor
5.8 2.7 4.1 1.0 versicolor
6.2 2.2 4.5 1.5 versicolor
5.6 2.5 3.9 1.1 versicolor
5.9 3.2 4.8 1.8 versicolor
6.1 2.8 4.0 1.3 versicolor
6.3 2.5 4.9 1.5 versicolor
6.1 2.8 4.7 1.2 versicolor
6.4 2.9 4.3 1.3 versicolor
6.6 3.0 4.4 1.4 versicolor
6.8 2.8 4.8 1.4 versicolor
6.7 3.0 5.0 1.7 versicolor
6.0 2.9 4.5 1.5 versicolor
5.7 2.6 3.5 1.0 versicolor
5.5 2.4 3.8 1.1 versicolor
5.5 2.4 3.7 1.0 versicolor
5.8 2.7 3.9 1.2 versicolor
6.0 2.7 5.1 1.6 versicolor
5.4 3.0 4.5 1.5 versicolor
6.0 3.4 4.5 1.6 versicolor
6.7 3.1 4.7 1.5 versicolor
6.3 2.3 4.4 1.3 versicolor
5.6 3.0 4.1 1.3 versicolor
5.5 2.5 4.0 1.3 versicolor
5.5 2.6 4.4 1.2 versicolor
6.1 3.0 4.6 1.4 versicolor
5.8 2.6 4.0 1.2 versicolor
5.0 2.3 3.3 1.0 versicolor
5.6 2.7 4.2 1.3 versicolor
5.7 3.0 4.2 1.2 versicolor
5.7 2.9 4.2 1.3 versicolor
6.2 2.9 4.3 1.3 versicolor
5.1 2.5 3.0 1.1 versicolor
5.7 2.8 4.1 1.3 versicolor
6.3 3.3 6.0 2.5 virginica
5.8 2.7 5.1 1.9 virginica
7.1 3.0 5.9 2.1 virginica
6.3 2.9 5.6 1.8 virginica
6.5 3.0 5.8 2.2 virginica
7.6 3.0 6.6 2.1 virginica
4.9 2.5 4.5 1.7 virginica
7.3 2.9 6.3 1.8 virginica
6.7 2.5 5.8 1.8 virginica
7.2 3.6 6.1 2.5 virginica
6.5 3.2 5.1 2.0 virginica
6.4 2.7 5.3 1.9 virginica
6.8 3.0 5.5 2.1 virginica
5.7 2.5 5.0 2.0 virginica
5.8 2.8 5.1 2.4 virginica
6.4 3.2 5.3 2.3 virginica
6.5 3.0 5.5 1.8 virginica
7.7 3.8 6.7 2.2 virginica
7.7 2.6 6.9 2.3 virginica
6.0 2.2 5.0 1.5 virginica
6.9 3.2 5.7 2.3 virginica
5.6 2.8 4.9 2.0 virginica
7.7 2.8 6.7 2.0 virginica
6.3 2.7 4.9 1.8 virginica
6.7 3.3 5.7 2.1 virginica
7.2 3.2 6.0 1.8 virginica
6.2 2.8 4.8 1.8 virginica
6.1 3.0 4.9 1.8 virginica
6.4 2.8 5.6 2.1 virginica
7.2 3.0 5.8 1.6 virginica
7.4 2.8 6.1 1.9 virginica
7.9 3.8 6.4 2.0 virginica
6.4 2.8 5.6 2.2 virginica
6.3 2.8 5.1 1.5 virginica
6.1 2.6 5.6 1.4 virginica
7.7 3.0 6.1 2.3 virginica
6.3 3.4 5.6 2.4 virginica
6.4 3.1 5.5 1.8 virginica
6.0 3.0 4.8 1.8 virginica
6.9 3.1 5.4 2.1 virginica
6.7 3.1 5.6 2.4 virginica
6.9 3.1 5.1 2.3 virginica
5.8 2.7 5.1 1.9 virginica
6.8 3.2 5.9 2.3 virginica
6.7 3.3 5.7 2.5 virginica
6.7 3.0 5.2 2.3 virginica
6.3 2.5 5.0 1.9 virginica
6.5 3.0 5.2 2.0 virginica
6.2 3.4 5.4 2.3 virginica
5.9 3.0 5.1 1.8 virginica
// This file comes from https://github.com/curran/model
!function(){function n(n){function t(n,t,r){r=r||this,n=n instanceof Array?n:[n];var u=o(function(){var o=n.map(function(n){return p[n]});e(o)&&t.apply(r,o)});return u(),n.forEach(function(n){f(n,u)}),u}function o(n){var t=!1;return function(){t||(t=!0,setTimeout(function(){t=!1,n()},0))}}function e(n){return!n.some(function(n){return"undefined"==typeof n||null===n})}function f(n,t,o){o=o||this,r(n).push(t),u(n,o)}function r(n){return d[n]||(d[n]=[])}function u(n,t){n in l||(l[n]=!0,p[n]=s[n],Object.defineProperty(s,n,{get:function(){return p[n]},set:function(o){var e=p[n];p[n]=o,r(n).forEach(function(n){n.call(t,o,e)})}}))}function i(n){for(var t in d)c(t,n)}function c(n,t){d[n]=d[n].filter(function(n){return n!==t})}function a(n){for(var t in n)s[t]=n[t]}var s={},p={},d={},l={};return a(n),s.when=t,s.cancel=i,s.on=f,s.off=c,s.set=a,s}n.None="__NONE__","function"==typeof define&&define.amd?define([],function(){return n}):"object"==typeof exports?module.exports=n:this.Model=n}();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment