This program makes a scatter plot from Iris data set. Brushing in one plot zooms in the other.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- Use RequireJS for module loading. -->
<script src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.14/require.js"></script>
<!-- Configure AMD modules. -->
<script>
requirejs.config({
paths: {
d3: "//d3js.org/d3.v3.min",
jquery: "//code.jquery.com/jquery-2.1.1.min"
}
});
</script>
<!-- Include CSS that styles the visualization. -->
<link rel="stylesheet" href="styles.css">
<title>Scatter Plot</title>
</head>
<body>
<!-- The visualization will be injected into this div. -->
<div id="container"></div>
<!-- Run the main program. -->
<script src="main.js"></script>
</body>
</html>
{
"sepal_length":{
"type": "Q",
"label": "sepal length (cm)"
},
"sepal_width": {
"type": "Q",
"label": "sepal width (cm)"
},
"petal_length": {
"type": "Q",
"label": "petal length (cm)"
},
"petal_width": {
"type": "Q",
"label": "petal width (cm)"
},
"class": {
"type": "N",
"label": "species"
}
}
sepal_length,sepal_width,petal_length,petal_width,class
5.1,3.5,1.4,0.2,Iris-setosa
4.9,3.0,1.4,0.2,Iris-setosa
4.7,3.2,1.3,0.2,Iris-setosa
4.6,3.1,1.5,0.2,Iris-setosa
5.0,3.6,1.4,0.2,Iris-setosa
5.4,3.9,1.7,0.4,Iris-setosa
4.6,3.4,1.4,0.3,Iris-setosa
5.0,3.4,1.5,0.2,Iris-setosa
4.4,2.9,1.4,0.2,Iris-setosa
4.9,3.1,1.5,0.1,Iris-setosa
5.4,3.7,1.5,0.2,Iris-setosa
4.8,3.4,1.6,0.2,Iris-setosa
4.8,3.0,1.4,0.1,Iris-setosa
4.3,3.0,1.1,0.1,Iris-setosa
5.8,4.0,1.2,0.2,Iris-setosa
5.7,4.4,1.5,0.4,Iris-setosa
5.4,3.9,1.3,0.4,Iris-setosa
5.1,3.5,1.4,0.3,Iris-setosa
5.7,3.8,1.7,0.3,Iris-setosa
5.1,3.8,1.5,0.3,Iris-setosa
5.4,3.4,1.7,0.2,Iris-setosa
5.1,3.7,1.5,0.4,Iris-setosa
4.6,3.6,1.0,0.2,Iris-setosa
5.1,3.3,1.7,0.5,Iris-setosa
4.8,3.4,1.9,0.2,Iris-setosa
5.0,3.0,1.6,0.2,Iris-setosa
5.0,3.4,1.6,0.4,Iris-setosa
5.2,3.5,1.5,0.2,Iris-setosa
5.2,3.4,1.4,0.2,Iris-setosa
4.7,3.2,1.6,0.2,Iris-setosa
4.8,3.1,1.6,0.2,Iris-setosa
5.4,3.4,1.5,0.4,Iris-setosa
5.2,4.1,1.5,0.1,Iris-setosa
5.5,4.2,1.4,0.2,Iris-setosa
4.9,3.1,1.5,0.1,Iris-setosa
5.0,3.2,1.2,0.2,Iris-setosa
5.5,3.5,1.3,0.2,Iris-setosa
4.9,3.1,1.5,0.1,Iris-setosa
4.4,3.0,1.3,0.2,Iris-setosa
5.1,3.4,1.5,0.2,Iris-setosa
5.0,3.5,1.3,0.3,Iris-setosa
4.5,2.3,1.3,0.3,Iris-setosa
4.4,3.2,1.3,0.2,Iris-setosa
5.0,3.5,1.6,0.6,Iris-setosa
5.1,3.8,1.9,0.4,Iris-setosa
4.8,3.0,1.4,0.3,Iris-setosa
5.1,3.8,1.6,0.2,Iris-setosa
4.6,3.2,1.4,0.2,Iris-setosa
5.3,3.7,1.5,0.2,Iris-setosa
5.0,3.3,1.4,0.2,Iris-setosa
7.0,3.2,4.7,1.4,Iris-versicolor
6.4,3.2,4.5,1.5,Iris-versicolor
6.9,3.1,4.9,1.5,Iris-versicolor
5.5,2.3,4.0,1.3,Iris-versicolor
6.5,2.8,4.6,1.5,Iris-versicolor
5.7,2.8,4.5,1.3,Iris-versicolor
6.3,3.3,4.7,1.6,Iris-versicolor
4.9,2.4,3.3,1.0,Iris-versicolor
6.6,2.9,4.6,1.3,Iris-versicolor
5.2,2.7,3.9,1.4,Iris-versicolor
5.0,2.0,3.5,1.0,Iris-versicolor
5.9,3.0,4.2,1.5,Iris-versicolor
6.0,2.2,4.0,1.0,Iris-versicolor
6.1,2.9,4.7,1.4,Iris-versicolor
5.6,2.9,3.6,1.3,Iris-versicolor
6.7,3.1,4.4,1.4,Iris-versicolor
5.6,3.0,4.5,1.5,Iris-versicolor
5.8,2.7,4.1,1.0,Iris-versicolor
6.2,2.2,4.5,1.5,Iris-versicolor
5.6,2.5,3.9,1.1,Iris-versicolor
5.9,3.2,4.8,1.8,Iris-versicolor
6.1,2.8,4.0,1.3,Iris-versicolor
6.3,2.5,4.9,1.5,Iris-versicolor
6.1,2.8,4.7,1.2,Iris-versicolor
6.4,2.9,4.3,1.3,Iris-versicolor
6.6,3.0,4.4,1.4,Iris-versicolor
6.8,2.8,4.8,1.4,Iris-versicolor
6.7,3.0,5.0,1.7,Iris-versicolor
6.0,2.9,4.5,1.5,Iris-versicolor
5.7,2.6,3.5,1.0,Iris-versicolor
5.5,2.4,3.8,1.1,Iris-versicolor
5.5,2.4,3.7,1.0,Iris-versicolor
5.8,2.7,3.9,1.2,Iris-versicolor
6.0,2.7,5.1,1.6,Iris-versicolor
5.4,3.0,4.5,1.5,Iris-versicolor
6.0,3.4,4.5,1.6,Iris-versicolor
6.7,3.1,4.7,1.5,Iris-versicolor
6.3,2.3,4.4,1.3,Iris-versicolor
5.6,3.0,4.1,1.3,Iris-versicolor
5.5,2.5,4.0,1.3,Iris-versicolor
5.5,2.6,4.4,1.2,Iris-versicolor
6.1,3.0,4.6,1.4,Iris-versicolor
5.8,2.6,4.0,1.2,Iris-versicolor
5.0,2.3,3.3,1.0,Iris-versicolor
5.6,2.7,4.2,1.3,Iris-versicolor
5.7,3.0,4.2,1.2,Iris-versicolor
5.7,2.9,4.2,1.3,Iris-versicolor
6.2,2.9,4.3,1.3,Iris-versicolor
5.1,2.5,3.0,1.1,Iris-versicolor
5.7,2.8,4.1,1.3,Iris-versicolor
6.3,3.3,6.0,2.5,Iris-virginica
5.8,2.7,5.1,1.9,Iris-virginica
7.1,3.0,5.9,2.1,Iris-virginica
6.3,2.9,5.6,1.8,Iris-virginica
6.5,3.0,5.8,2.2,Iris-virginica
7.6,3.0,6.6,2.1,Iris-virginica
4.9,2.5,4.5,1.7,Iris-virginica
7.3,2.9,6.3,1.8,Iris-virginica
6.7,2.5,5.8,1.8,Iris-virginica
7.2,3.6,6.1,2.5,Iris-virginica
6.5,3.2,5.1,2.0,Iris-virginica
6.4,2.7,5.3,1.9,Iris-virginica
6.8,3.0,5.5,2.1,Iris-virginica
5.7,2.5,5.0,2.0,Iris-virginica
5.8,2.8,5.1,2.4,Iris-virginica
6.4,3.2,5.3,2.3,Iris-virginica
6.5,3.0,5.5,1.8,Iris-virginica
7.7,3.8,6.7,2.2,Iris-virginica
7.7,2.6,6.9,2.3,Iris-virginica
6.0,2.2,5.0,1.5,Iris-virginica
6.9,3.2,5.7,2.3,Iris-virginica
5.6,2.8,4.9,2.0,Iris-virginica
7.7,2.8,6.7,2.0,Iris-virginica
6.3,2.7,4.9,1.8,Iris-virginica
6.7,3.3,5.7,2.1,Iris-virginica
7.2,3.2,6.0,1.8,Iris-virginica
6.2,2.8,4.8,1.8,Iris-virginica
6.1,3.0,4.9,1.8,Iris-virginica
6.4,2.8,5.6,2.1,Iris-virginica
7.2,3.0,5.8,1.6,Iris-virginica
7.4,2.8,6.1,1.9,Iris-virginica
7.9,3.8,6.4,2.0,Iris-virginica
6.4,2.8,5.6,2.2,Iris-virginica
6.3,2.8,5.1,1.5,Iris-virginica
6.1,2.6,5.6,1.4,Iris-virginica
7.7,3.0,6.1,2.3,Iris-virginica
6.3,3.4,5.6,2.4,Iris-virginica
6.4,3.1,5.5,1.8,Iris-virginica
6.0,3.0,4.8,1.8,Iris-virginica
6.9,3.1,5.4,2.1,Iris-virginica
6.7,3.1,5.6,2.4,Iris-virginica
6.9,3.1,5.1,2.3,Iris-virginica
5.8,2.7,5.1,1.9,Iris-virginica
6.8,3.2,5.9,2.3,Iris-virginica
6.7,3.3,5.7,2.5,Iris-virginica
6.7,3.0,5.2,2.3,Iris-virginica
6.3,2.5,5.0,1.9,Iris-virginica
6.5,3.0,5.2,2.0,Iris-virginica
6.2,3.4,5.4,2.3,Iris-virginica
5.9,3.0,5.1,1.8,Iris-virginica
// This is the main program that sets up a scatter plot to visualize the Iris data set.
// Curran Kelleher March 2015
require(["scatterPlot"], function (ScatterPlot) {
// Initialize the scatter plot.
var options = {
// Tell the visualization which DOM element to insert itself into.
container: d3.select("#container").node(),
// Specify the margin and text label offsets.
margin: {
top: 10,
right: 10,
bottom: 45,
left: 55
},
yAxisLabelOffset: 1.8, // Unit is CSS "em"s
xAxisLabelOffset: 1.9,
titleOffset: 0.3
},
scatterPlot1 = ScatterPlot(options),
scatterPlot2 = ScatterPlot(options);
// Fetch the column metadata.
d3.json("iris-metadata.json", function (metadata) {
var xColumn = "sepal_length",
yColumn = "petal_length",
sizeColumn = "petal_width",
colorColumn = "class",
xyOptions = {
xColumn: xColumn,
xAxisLabel: metadata[xColumn].label,
yColumn: yColumn,
yAxisLabel: metadata[yColumn].label
};
// Use the same X and Y for all plots.
scatterPlot1.set(xyOptions);
scatterPlot2.set(xyOptions);
// Load the data from a CSV file.
d3.csv("iris.csv", function (data){
// Parse quantitative values from strings to numbers.
var quantitativeColumns = Object.keys(metadata).filter(function (column){
return metadata[column].type === "Q";
});
data.forEach(function (d){
quantitativeColumns.forEach(function (column){
d[column] = parseFloat(d[column]);
});
});
// Pass the data into the plots.
scatterPlot1.data = data;
scatterPlot2.data = data;
});
// Use the first plot to zoom in the second plot.
scatterPlot1.brushEnabled = true;
scatterPlot1.when("brushedIntervals", function (brushedIntervals){
scatterPlot2.xDomainMin = brushedIntervals[xColumn][0];
scatterPlot2.xDomainMax = brushedIntervals[xColumn][1];
scatterPlot2.yDomainMin = brushedIntervals[yColumn][0];
scatterPlot2.yDomainMax = brushedIntervals[yColumn][1];
});
// Initialize the default brush.
scatterPlot1.brushedIntervals = {
"sepal_length": [ 4.82, 7.77 ],
"petal_length": [ 2.84, 6.80 ]
};
});
// Sets the `box` model property
// based on the size of the container,
function computeBoxes(){
var width = container.clientWidth,
height = container.clientHeight,
padding = 10,
plotWidth = (width - padding * 2) / 2,
plotHeight = height - padding * 2;
scatterPlot1.box = {
x: padding,
y: padding,
width: plotWidth,
height: plotHeight
};
scatterPlot2.box = {
x: plotWidth + padding * 2,
y: padding,
width: plotWidth,
height: plotHeight
};
}
// once to initialize `model.box`, and
computeBoxes();
// whenever the browser window resizes in the future.
window.addEventListener("resize", computeBoxes);
});
// A functional reactive model library.
//
(function(){
// The D3 conventional graph representation.
// See https://github.com/mbostock/d3/wiki/Force-Layout#nodes
var nodes, links, idCounter, map;
function resetFlowGraph(){
nodes = [];
links = [];
idCounter = 0;
map = {};
}
function getFlowGraph(){
return {
nodes: nodes,
links: links
};
}
resetFlowGraph();
// Adds the nodes and links to the data flow graph for one
// particular reactive function.
function updateLambda(modelId, lambdaId, inProperties, outProperties){
var lambda = lambdaNode(lambdaId);
inProperties.forEach(function(property){
link(propertyNode(modelId, property), lambda);
});
outProperties.forEach(function(property){
link(lambda, propertyNode(modelId, property));
});
}
function lambdaNode(id){
return getOrCreate(id, nodes, createLambda);
}
function createLambda(index){
return {
type: "lambda",
index: index
};
}
function propertyNode(modelId, property){
var id = modelId + "." + property;
return getOrCreate(id, nodes, createPropertyNode(property));
}
function createPropertyNode(property){
return function(index){
return {
type: "property",
index: index,
property: property
};
};
}
function link(sourceNode, targetNode){
var source = sourceNode.index,
target = targetNode.index,
id = source + "-" + target;
getOrCreate(id, links, createLink(source, target));
}
function createLink(source, target){
return function(index){
return {
source: source,
target: target
};
};
}
function getOrCreate(id, things, createThing){
var thing = map[id];
if(!thing){
thing = map[id] = createThing(things.length);
things.push(thing);
}
return thing;
}
// The constructor function, accepting default values.
function Model(defaults){
// The returned public API object.
var model = {},
// The internal stored values for tracked properties. { property -> value }
values = {},
// The callback functions for each tracked property. { property -> [callback] }
listeners = {},
// The set of tracked properties. { property -> true }
trackedProperties = {},
modelId = idCounter++,
changedProperties = {};
// The functional reactive "when" operator.
//
// * `properties` An array of property names (can also be a single property string).
// * `callback` A callback function that is called:
// * with property values as arguments, ordered corresponding to the properties array,
// * only if all specified properties have values,
// * once for initialization,
// * whenever one or more specified properties change,
// * on the next tick of the JavaScript event loop after properties change,
// * only once as a result of one or more synchronous changes to dependency properties.
function when(properties, callback, thisArg){
var lambdaId = idCounter++;
// Make sure the default `this` becomes
// the object you called `.on` on.
thisArg = thisArg || this;
// Handle either an array or a single string.
properties = (properties instanceof Array) ? properties : [properties];
// This function will trigger the callback to be invoked.
var triggerCallback = debounce(function (){
var args = properties.map(function(property){
return values[property];
});
if(allAreDefined(args)){
changedProperties = {};
callback.apply(thisArg, args);
updateLambda(modelId, lambdaId, properties, Object.keys(changedProperties));
}
});
// Trigger the callback once for initialization.
triggerCallback();
// Trigger the callback whenever specified properties change.
properties.forEach(function(property){
on(property, triggerCallback);
});
// Return this function so it can be removed later.
return triggerCallback;
}
// Returns a debounced version of the given function.
// See http://underscorejs.org/#debounce
function debounce(callback){
var queued = false;
return function () {
if(!queued){
queued = true;
setTimeout(function () {
queued = false;
callback();
}, 0);
}
};
}
// Returns true if all elements of the given array are defined, false otherwise.
function allAreDefined(arr){
return !arr.some(function (d) {
return typeof d === 'undefined' || d === null;
});
}
// Adds a change listener for a given property with Backbone-like behavior.
// Similar to http://backbonejs.org/#Events-on
function on(property, callback, thisArg){
// Make sure the default `this` becomes
// the object you called `.on` on.
thisArg = thisArg || this;
getListeners(property).push(callback);
track(property, thisArg);
}
// Gets or creates the array of listener functions for a given property.
function getListeners(property){
return listeners[property] || (listeners[property] = []);
}
// Tracks a property if it is not already tracked.
function track(property, thisArg){
if(!(property in trackedProperties)){
trackedProperties[property] = true;
values[property] = model[property];
Object.defineProperty(model, property, {
get: function () { return values[property]; },
set: function(newValue) {
var oldValue = values[property];
values[property] = newValue;
getListeners(property).forEach(function(callback){
callback.call(thisArg, newValue, oldValue);
});
changedProperties[property] = true;
}
});
}
}
// Removes a listener added using `when()`.
function cancel(listener){
for(var property in listeners){
off(property, listener);
}
}
// Removes a change listener added using `on`.
function off(property, callback){
listeners[property] = listeners[property].filter(function (listener) {
return listener !== callback;
});
}
// Sets all of the given values on the model.
// `newValues` is an object { property -> value }.
function set(newValues){
for(var property in newValues){
model[property] = newValues[property];
}
}
// Transfer defaults passed into the constructor to the model.
set(defaults);
// Expose the public API.
model.when = when;
model.cancel = cancel;
model.on = on;
model.off = off;
model.set = set;
return model;
}
Model.getFlowGraph = getFlowGraph;
Model.resetFlowGraph = resetFlowGraph;
// Support AMD (RequireJS), CommonJS (Node), and browser globals.
// Inspired by https://github.com/umdjs/umd
if (typeof define === "function" && define.amd) {
define([], function () { return Model; });
} else if (typeof exports === "object") {
module.exports = Model;
} else {
this.Model = Model;
}
})();
/* Remove the default margin. */
body {
margin: 0px;
}
/* Make the visualization container fill the page. */
#container {
/* Use the default size from bl.ocks.org */
width: 960px;
height: 500px;
}
/* Put a border around each plot. */
svg {
border-style: solid;
border-color: lightgray;
border-width: 1px;
}
/* Style the visualization. Draws from http://bl.ocks.org/mbostock/3887118 */
/* Tick mark labels */
.axis .tick text {
font: 8pt sans-serif;
}
/* Axis labels */
.axis text {
font: 14pt sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: black;
stroke-width: 1.5px;
}
.title-text {
text-anchor: middle;
font: 24pt sans-serif;
}
/* Style the brush. Draws from http://bl.ocks.org/mbostock/4343214 */
.brush .extent {
stroke: gray;
fill-opacity: .125;
shape-rendering: crispEdges;
}