Skip to content

Instantly share code, notes, and snippets.

@curran
Last active June 28, 2016 13:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save curran/f4041cac02f19ee460dfe8b709dc24e7 to your computer and use it in GitHub Desktop.
Save curran/f4041cac02f19ee460dfe8b709dc24e7 to your computer and use it in GitHub Desktop.
Linked Scatter Plot and Bar Chart
license: mit
sepalLength sepalWidth petalLength petalWidth 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.2 setosa
5.0 3.2 1.2 0.2 setosa
5.5 3.5 1.3 0.2 setosa
4.9 3.6 1.4 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
<!--
An example of linked views using model.js.
Curran Kelleher August 2014
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<!-- A functional reactive model library. github.com/curran/model -->
<script src="http://curran.github.io/model/cdn/model-v0.2.4.js"></script>
<style>
/* CSS for the visualization.
* Curran Kelleher 4/17/2014 */
/* Size the visualization container. */
#container {
position: fixed;
top: 30px;
bottom: 30px;
left: 30px;
right: 30px;
}
/* Style the visualization.
* This CSS is copied verbatim from the
* D3 scatter plot example found at
* http://bl.ocks.org/mbostock/3887118 */
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.dot {
fill: black;
}
/* The following CSS is for brushing,
* adapted from http://bl.ocks.org/mbostock/4343214 */
.dot.selected {
fill: red;
}
.brush .extent {
stroke: gray;
fill-opacity: .125;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
function BarChart (container) {
var defaults = {
margin: {
top: 20,
right: 20,
bottom: 30,
left: 40
},
yAxisNumTicks: 10,
yAxisTickFormat: ""
},
model = Model(defaults),
xAxis = d3.svg.axis().orient("bottom"),
yAxis = d3.svg.axis().orient("left")
svg = d3.select(container).append('svg')
// Use absolute positioning on the SVG element
// so that CSS can be used to position the div later
// according to the model `box.x` and `box.y` properties.
.style('position', 'absolute'),
g = svg.append("g"),
xAxisG = g.append("g").attr("class", "x axis"),
yAxisG = g.append("g").attr("class", "y axis"),
yAxisText = yAxisG.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end");
// Encapsulate D3 Conventional Margins.
// See also 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;
});
model.when("margin", function (margin) {
g.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
});
// Adjust Y axis tick mark parameters.
// See https://github.com/mbostock/d3/wiki/Quantitative-Scales#linear_tickFormat
model.when(['yAxisNumTicks', 'yAxisTickFormat'], function (count, format) {
yAxis.ticks(count, format);
});
// Respond to changes in size and offset.
model.when("box", function (box) {
// Resize the svg element that contains the visualization.
svg.attr("width", box.width).attr("height", box.height);
// Set the CSS `left` and `top` properties
// to move the SVG element to `(box.x, box.y)`
// relative to the container div to apply the offset.
svg
.style('left', box.x + 'px')
.style('top', box.y + 'px');
});
model.when("height", function (height) {
xAxisG.attr("transform", "translate(0," + height + ")");
});
model.when(["data", "xAttribute", "width"], function (data, xAttribute, width) {
model.xScale = d3.scale.ordinal()
.rangeRoundBands([0, width], .1)
.domain(data.map(function(d) { return d[xAttribute]; }));
});
model.when(["data", "yAttribute", "height"], function (data, yAttribute, height) {
model.yScale = d3.scale.linear()
.range([height, 0])
.domain([0, d3.max(data, function(d) { return d[yAttribute]; })]);
});
model.when(["xScale"], function (xScale) {
xAxis.scale(xScale)
xAxisG.call(xAxis);
});
model.when(["yScale"], function (yScale) {
yAxis.scale(yScale)
yAxisG.call(yAxis);
});
model.when("yAxisLabel", yAxisText.text, yAxisText);
model.when(["data", "xAttribute", "yAttribute", "xScale", "yScale", "height"],
function (data, xAttribute, yAttribute, xScale, yScale, height) {
var bars = g.selectAll(".bar").data(data);
bars.enter().append("rect").attr("class", "bar");
bars
.attr("x", function(d) { return xScale(d[xAttribute]); })
.attr("width", xScale.rangeBand())
.attr("y", function(d) { return yScale(d[yAttribute]); })
.attr("height", function(d) { return height - yScale(d[yAttribute]); });
bars.exit().remove();
});
return model;
}
// An adaptation of the [D3 scatter plot example](http://bl.ocks.org/mbostock/3887118)
// that uses `model.js`. This version, unlike the original example,
// is model driven and reactive. When a part of the model updates,
// only the parts of the visualization that depend on those parts
// of the model are updated. There are no redundant calls to visualization
// update code when multiple properties are changed simultaneously.
//
// Draws from this brushing example for interaction:
// http://bl.ocks.org/mbostock/4343214
//
// See also docs on quadtree:
// https://github.com/mbostock/d3/wiki/Quadtree-Geom
//
// Define the AMD module using the `define()` function
// provided by Require.js.
//define(['d3', 'model'], function (d3, Model) {
function ScatterPlot (div){
var x = d3.scale.linear(),
y = d3.scale.linear(),
xAxis = d3.svg.axis().scale(x).orient('bottom'),
yAxis = d3.svg.axis().scale(y).orient('left'),
// Use absolute positioning so that CSS can be used
// to position the div according to the model.
svg = d3.select(div).append('svg').style('position', 'absolute'),
g = svg.append('g'),
xAxisG = g.append('g').attr('class', 'x axis'),
yAxisG = g.append('g').attr('class', 'y axis'),
xAxisLabel = xAxisG.append('text')
.attr('class', 'label')
.attr('y', -6)
.style('text-anchor', 'end'),
yAxisLabel = yAxisG.append('text')
.attr('class', 'label')
.attr('transform', 'rotate(-90)')
.attr('y', 6)
.attr('dy', '.71em')
.style('text-anchor', 'end'),
// Add the dots group before the brush group,
// so that mouse events go to the brush
// rather than to the dots, even when the mouse is
// on top of a dot.
dotsG = g.append('g'),
brushG = g.append('g')
.attr('class', 'brush'),
brush = d3.svg.brush()
.on('brush', brushed),
dots,
quadtree,
model = Model();
model.when('xLabel', xAxisLabel.text, xAxisLabel);
model.when('yLabel', yAxisLabel.text, yAxisLabel);
model.when('margin', function (margin) {
g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
});
model.when('box', function (box) {
svg.attr('width', box.width)
.attr('height', box.height);
// Set the CSS `left` and `top` properties
// to move the SVG element to `(box.x, box.y)`
// relative to the container div.
svg
.style('left', box.x + 'px')
.style('top', box.y + 'px')
});
model.when(['box', 'margin'], function (box, margin) {
model.width = box.width - margin.left - margin.right;
model.height = box.height - margin.top - margin.bottom;
});
model.when('width', function (width) {
xAxisLabel.attr('x', width);
});
model.when('height', function (height) {
xAxisG.attr('transform', 'translate(0,' + height + ')');
});
model.when(['width', 'height'], function (width, height) {
brush.x(d3.scale.identity().domain([0, width]));
brush.y(d3.scale.identity().domain([0, height]));
brushG
.call(brush)
.call(brush.event);
});
model.when(['width', 'height', 'data', 'xField', 'yField'], function (width, height, data, xField, yField) {
// Updated the scales
x.domain(d3.extent(data, function(d) { return d[xField]; })).nice();
y.domain(d3.extent(data, function(d) { return d[yField]; })).nice();
x.range([0, width]);
y.range([height, 0]);
// update the quadtree
quadtree = d3.geom.quadtree()
.x(function(d) { return x(d[xField]); })
.y(function(d) { return y(d[yField]); })
(data);
// update the axes
xAxisG.call(xAxis);
yAxisG.call(yAxis);
// Plot the data as dots
dots = dotsG.selectAll('.dot').data(data);
dots.enter().append('circle')
.attr('class', 'dot')
.attr('r', 3.5);
dots
.attr('cx', function(d) { return x(d[xField]); })
.attr('cy', function(d) { return y(d[yField]); });
dots.exit().remove();
});
return model;
function brushed() {
var e = brush.extent(), selectedData;
if(dots) {
dots.each(function(d) { d.selected = false; });
selectedData = search(e[0][0], e[0][1], e[1][0], e[1][1]);
dots.classed('selected', function(d) { return d.selected; });
}
model.selectedData = brush.empty() ? model.data : selectedData;
}
// Find the nodes within the specified rectangle.
function search(x0, y0, x3, y3) {
var selectedData = [];
quadtree.visit(function(node, x1, y1, x2, y2) {
var d = node.point, x, y;
if (d) {
x = node.x;
y = node.y;
d.visited = true;
if(x >= x0 && x < x3 && y >= y0 && y < y3){
d.selected = true;
selectedData.push(d);
}
}
return x1 >= x3 || y1 >= y3 || x2 < x0 || y2 < y0;
});
return selectedData;
}
}
// The main program that assembles the linked views.
//
// Curran Kelleher 4/17/2014
//require(['d3', 'scatterPlot', 'barChart'], function (d3, ScatterPlot, BarChart) {
function main(){
// Grab the container div from the DOM.
var div = document.getElementById('container'),
// Add both visualizations to the same div.
// Each will create its own SVG element.
scatterPlot = ScatterPlot(div),
barChart = BarChart(div);
// Configure the scatter plot to use the iris data.
scatterPlot.set({
xField: 'sepalWidth',
yField: 'sepalLength',
xLabel: 'Sepal Width (cm)',
yLabel: 'Sepal Length (cm)',
margin: { 'top': 20, 'right': 20, 'bottom': 30, 'left': 40 }
});
// Configure the bar chart to use the aggregated iris data.
barChart.set({
xAttribute: 'species',
yAttribute: 'count',
yAxisLabel: 'number of irises',
margin: { 'top': 20, 'right': 20, 'bottom': 30, 'left': 40 }
});
// Compute the aggregated iris data in response to brushing
// in the scatter plot, and pass it into the bar chart.
scatterPlot.when('selectedData', function (scatterData) {
var speciesCounts = {};
// Aggregate scatter plot data by counting
// the number of irises for each species.
scatterData.forEach(function (d) {
if(!speciesCounts[d.species]){
speciesCounts[d.species] = 0;
}
speciesCounts[d.species]++;
});
// Flatten the object containing species counts into an array.
// Update the bar chart with the aggregated data.
barChart.data = Object.keys(speciesCounts).map(function (species) {
return {
species: species,
count: speciesCounts[species]
};
});
});
// Load the iris data.
d3.tsv('data.tsv', function (d) {
d.sepalLength = +d.sepalLength;
d.sepalWidth = +d.sepalWidth;
return d;
}, function(error, data) {
// Set sizes once to initialize.
setSizes();
// Set sizes when the user resizes the browser.
window.addEventListener('resize', setSizes);
// Set the data.
scatterPlot.data = data;
});
// Sets the `box` property on each visualization
// to arrange them within the container div.
function setSizes(){
// Put the scatter plot on the left.
scatterPlot.box = {
x: 0,
y: 0,
width: div.clientWidth / 2,
height: div.clientHeight
};
// Put the bar chart on the right.
barChart.box = {
x: div.clientWidth / 2,
y: 0,
width: div.clientWidth / 2,
height: div.clientHeight
};
}
}
main();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment