This program makes a scatter plot from Iris data set, showcasing how a single reusable visualization module can gracefully support optional visualization configuration properties like size and color.
Based on Data Canvas Part 3 - Bar Chart.
This program makes a scatter plot from Iris data set, showcasing how a single reusable visualization module can gracefully support optional visualization configuration properties like size and color.
Based on Data Canvas Part 3 - Bar Chart.
// Generates n distinct colors. | |
// l is the fixed lightness, the L in LAB color space. | |
// r is the radius of the circle in AB space along which points are taken. | |
// Documented at http://bl.ocks.org/curran/dd73d3d8925cdf50df86 | |
define([], function (){ | |
var lDefault = 50, | |
rDefault = 100; | |
return function generateColors(n, l, r){ | |
var colors = [], a, b, θ; | |
l = l || lDefault; | |
r = r || rDefault; | |
for(var i = 0; i < n; i++){ | |
θ = (i / n) * Math.PI * 2; | |
a = Math.sin(θ) * r; | |
b = Math.cos(θ) * r; | |
colors.push(d3.lab(l, a, b).toString()); | |
} | |
return colors; | |
} | |
}); |
<!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 RequireJS paths for third party libraries. --> | |
<script> | |
requirejs.config({ | |
paths: { | |
model: "//curran.github.io/cdn/model-v0.2.1/dist/model-min", | |
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", "generateColors"], function (ScatterPlot, generateColors) { | |
// 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 | |
}; | |
var scatterPlot1 = ScatterPlot(options); | |
var scatterPlot2 = ScatterPlot(options); | |
var scatterPlot3 = ScatterPlot(options); | |
var scatterPlot4 = 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); | |
scatterPlot3.set(xyOptions); | |
scatterPlot4.set(xyOptions); | |
// Use X, Y, and size for the second scatter plot. | |
scatterPlot2.sizeColumn = sizeColumn; | |
// Use X, Y, and color for the third scatter plot. | |
scatterPlot3.colorColumn = colorColumn; | |
scatterPlot3.colorRange = d3.scale.category10().range(); | |
// Use X, Y, size, and color for the fourth scatter plot. | |
scatterPlot4.sizeColumn = sizeColumn; | |
scatterPlot4.colorColumn = colorColumn; | |
scatterPlot4.colorRange = scatterPlot3.colorRange; | |
// 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; | |
scatterPlot3.data = data; | |
scatterPlot4.data = data; | |
}); | |
}); | |
// 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 * 4) / 3, | |
plotHeight = (height - padding * 3) / 2; | |
scatterPlot1.box = { | |
x: padding, | |
y: height / 2 - plotHeight / 2, | |
width: plotWidth, | |
height: plotHeight | |
}; | |
scatterPlot2.box = { | |
x: plotWidth + padding * 2, | |
y: padding, | |
width: plotWidth, | |
height: plotHeight | |
}; | |
scatterPlot3.box = { | |
x: plotWidth + padding * 2, | |
y: plotHeight + padding * 2, | |
width: plotWidth, | |
height: plotHeight | |
}; | |
scatterPlot4.box = { | |
x: plotWidth * 2 + padding * 3, | |
y: height / 2 - plotHeight / 2, | |
width: plotWidth, | |
height: plotHeight | |
}; | |
} | |
// once to initialize `model.box`, and | |
computeBoxes(); | |
// whenever the browser window resizes in the future. | |
window.addEventListener("resize", computeBoxes); | |
}); |
// A reusable scatter plot module. | |
// Curran Kelleher March 2015 | |
define(["d3", "model"], function (d3, Model) { | |
// A representation for an optional Model property that is not specified. | |
// This allows the "when" approach to support optional properties. | |
// Inspired by Scala's Option type. | |
// See http://alvinalexander.com/scala/using-scala-option-some-none-idiom-function-java-null | |
var None = Model.None; | |
// The constructor function, accepting default values. | |
return function ScatterPlot(defaults) { | |
// Create a Model instance for the visualization. | |
// This will serve as its public API. | |
var model = Model(); | |
// Create an SVG element from the container DOM element. | |
model.when("container", function (container) { | |
model.svg = d3.select(container).append("svg") | |
// Use CSS `position: absolute;` | |
// so setting `left` and `top` later will | |
// position the SVG relative to the container div. | |
.style("position", "absolute"); | |
}); | |
// Adjust the size of the SVG based on the `box` property. | |
model.when(["svg", "box"], function (svg, box) { | |
// Set the CSS `left` and `top` properties | |
// to move the SVG to `(box.x, box.y)` | |
// relative to the container div. | |
svg | |
.style("left", box.x + "px") | |
.style("top", box.y + "px") | |
.attr("width", box.width) | |
.attr("height", box.height); | |
}); | |
// Create an SVG group that will contain the visualization. | |
model.when("svg", function (svg) { | |
model.g = svg.append("g"); | |
}); | |
// Adjust the SVG group translation based on the margin. | |
model.when(["g", "margin"], function (g, margin) { | |
g.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
}); | |
// Create the title text element. | |
model.when("g", function (g){ | |
model.titleText = g.append("text").attr("class", "title-text"); | |
}); | |
// Center the title text when width changes. | |
model.when(["titleText", "width"], function (titleText, width) { | |
titleText.attr("x", width / 2); | |
}); | |
// Update the title text based on the `title` property. | |
model.when(["titleText", "title"], function (titleText, title){ | |
titleText.text(title); | |
}); | |
// Update the title text offset. | |
model.when(["titleText", "titleOffset"], function (titleText, titleOffset){ | |
titleText.attr("dy", titleOffset + "em"); | |
}); | |
// Compute the inner box from the outer box and margin. | |
// See Margin Convention http://bl.ocks.org/mbostock/3019563 | |
model.when(["box", "margin"], function (box, margin) { | |
model.width = box.width - margin.left - margin.right; | |
model.height = box.height - margin.top - margin.bottom; | |
}); | |
// Generate a function for getting the X value. | |
model.when(["data", "xColumn"], function (data, xColumn) { | |
model.getX = function (d) { return d[xColumn]; }; | |
}); | |
// Compute the domain of the X attribute. | |
// Allow the API client to optionally specify fixed min and max values. | |
model.xDomainMin = None; | |
model.xDomainMax = None; | |
model.when(["data", "getX", "xDomainMin", "xDomainMax"], | |
function (data, getX, xDomainMin, xDomainMax) { | |
if(xDomainMin === None && xDomainMax === None){ | |
model.xDomain = d3.extent(data, getX); | |
} else { | |
if(xDomainMin === None){ | |
xDomainMin = d3.min(data, getX); | |
} | |
if(xDomainMax === None){ | |
xDomainMax = d3.max(data, getX); | |
} | |
model.xDomain = [xDomainMin, xDomainMax] | |
} | |
}); | |
// Compute the X scale. | |
model.when(["xDomain", "width"], function (xDomain, width) { | |
model.xScale = d3.scale.linear().domain(xDomain).range([0, width]); | |
}); | |
// Generate a function for getting the scaled X value. | |
model.when(["data", "xScale", "getX"], function (data, xScale, getX) { | |
model.getXScaled = function (d) { return xScale(getX(d)); }; | |
}); | |
// Set up the X axis. | |
model.when("g", function (g) { | |
model.xAxisG = g.append("g").attr("class", "x axis"); | |
model.xAxisText = model.xAxisG.append("text").style("text-anchor", "middle"); | |
}); | |
// Move the X axis label based on its specified offset. | |
model.when(["xAxisText", "xAxisLabelOffset"], function (xAxisText, xAxisLabelOffset){ | |
xAxisText.attr("dy", xAxisLabelOffset + "em"); | |
}); | |
// Update the X axis transform when height changes. | |
model.when(["xAxisG", "height"], function (xAxisG, height) { | |
xAxisG.attr("transform", "translate(0," + height + ")"); | |
}); | |
// Center the X axis label when width changes. | |
model.when(["xAxisText", "width"], function (xAxisText, width) { | |
xAxisText.attr("x", width / 2); | |
}); | |
// Update the X axis based on the X scale. | |
model.when(["xAxisG", "xScale"], function (xAxisG, xScale) { | |
xAxisG.call(d3.svg.axis().orient("bottom").scale(xScale)); | |
}); | |
// Update X axis label. | |
model.when(["xAxisText", "xAxisLabel"], function (xAxisText, xAxisLabel) { | |
xAxisText.text(xAxisLabel); | |
}); | |
// Generate a function for getting the Y value. | |
model.when(["data", "yColumn"], function (data, yColumn) { | |
model.getY = function (d) { return d[yColumn]; }; | |
}); | |
// Compute the domain of the Y attribute. | |
// Allow the API client to optionally specify fixed min and max values. | |
model.yDomainMin = None; | |
model.yDomainMax = None; | |
model.when(["data", "getY", "yDomainMin", "yDomainMax"], | |
function (data, getY, yDomainMin, yDomainMax) { | |
if(yDomainMin === None && yDomainMax === None){ | |
model.yDomain = d3.extent(data, getY); | |
} else { | |
if(yDomainMin === None){ | |
yDomainMin = d3.min(data, getY); | |
} | |
if(yDomainMax === None){ | |
yDomainMax = d3.max(data, getY); | |
} | |
model.yDomain = [yDomainMin, yDomainMax] | |
} | |
}); | |
// Compute the Y scale. | |
model.when(["data", "yDomain", "height"], function (data, yDomain, height) { | |
model.yScale = d3.scale.linear().domain(yDomain).range([height, 0]); | |
}); | |
// Generate a function for getting the scaled Y value. | |
model.when(["data", "yScale", "getY"], function (data, yScale, getY) { | |
model.getYScaled = function (d) { return yScale(getY(d)); }; | |
}); | |
// Set up the Y axis. | |
model.when("g", function (g) { | |
model.yAxisG = g.append("g").attr("class", "y axis"); | |
model.yAxisText = model.yAxisG.append("text") | |
.style("text-anchor", "middle") | |
.attr("transform", "rotate(-90)") | |
.attr("y", 0); | |
}); | |
// Move the Y axis label based on its specified offset. | |
model.when(["yAxisText", "yAxisLabelOffset"], function (yAxisText, yAxisLabelOffset){ | |
yAxisText.attr("dy", "-" + yAxisLabelOffset + "em") | |
}); | |
// Center the Y axis label when height changes. | |
model.when(["yAxisText", "height"], function (yAxisText, height) { | |
yAxisText.attr("x", -height / 2); | |
}); | |
// Update Y axis label. | |
model.when(["yAxisText", "yAxisLabel"], function (yAxisText, yAxisLabel) { | |
yAxisText.text(yAxisLabel); | |
}); | |
// Update the Y axis based on the Y scale. | |
model.when(["yAxisG", "yScale"], function (yAxisG, yScale) { | |
yAxisG.call(d3.svg.axis().orient("left").scale(yScale)); | |
}); | |
// Allow the API client to optionally specify a size column. | |
model.sizeColumn = None; | |
// The default radius of circles in pixels. | |
model.sizeDefault = 2; | |
// The min and max circle radius in pixels. | |
model.sizeMin = 0.5; | |
model.sizeMax = 6; | |
// Set up the size scale. | |
model.when(["sizeColumn", "data", "sizeDefault", "sizeMin", "sizeMax"], | |
function (sizeColumn, data, sizeDefault, sizeMin, sizeMax){ | |
if(sizeColumn !== None){ | |
var getSize = function (d){ return d[sizeColumn] }, | |
sizeScale = d3.scale.linear() | |
.domain(d3.extent(data, getSize)) | |
.range([sizeMin, sizeMax]); | |
model.getSizeScaled = function (d){ return sizeScale(getSize(d)); }; | |
} else { | |
model.getSizeScaled = function (d){ return sizeDefault; }; | |
} | |
}); | |
// Allow the API client to optionally specify a color column. | |
model.colorColumn = None; | |
model.colorRange = None; | |
// The default color of circles (CSS color string). | |
model.colorDefault = "black"; | |
// Set up the color scale. | |
model.when(["colorColumn", "data", "colorDefault", "colorRange"], | |
function (colorColumn, data, colorDefault, colorRange){ | |
if(colorColumn !== None && colorRange !== None){ | |
var getColor = function (d){ return d[colorColumn] }, | |
colorScale = d3.scale.ordinal() | |
.domain(data.map(getColor)) | |
.range(colorRange); | |
model.getColorScaled = function (d){ return colorScale(getColor(d)); }; | |
} else { | |
model.getColorScaled = function (d){ return colorDefault; }; | |
} | |
}); | |
// Add an SVG group to contain the marks. | |
model.when("g", function (g) { | |
model.circlesG = g.append("g"); | |
}); | |
// Draw the circles of the scatter plot. | |
model.when(["data", "circlesG", "getXScaled", "getYScaled", "getSizeScaled", "getColorScaled"], | |
function (data, circlesG, getXScaled, getYScaled, getSizeScaled, getColorScaled){ | |
var circles = circlesG.selectAll("circle").data(data); | |
circles.enter().append("circle"); | |
circles | |
.attr("cx", getXScaled) | |
.attr("cy", getYScaled) | |
.attr("r", getSizeScaled) | |
.attr("fill", getColorScaled); | |
circles.exit().remove(); | |
}); | |
// Set defaults at the end so they override optional properties set to None. | |
model.set(defaults); | |
return 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; | |
} |