An example set of coordinated views that use Crossfilter and Chiasm.
The idea with this is to introduce a general Crossfilter component that can be set up to work with any visualization components in Chiasm.
Draws from:
An example set of coordinated views that use Crossfilter and Chiasm.
The idea with this is to introduce a general Crossfilter component that can be set up to work with any visualization components in Chiasm.
Draws from:
// This is an example Chaism plugin that uses D3 to make a bar chart. | |
// Draws from this Bar Chart example http://bl.ocks.org/mbostock/3885304 | |
function BarChart() { | |
var my = ChiasmComponent({ | |
margin: { | |
left: 20, | |
top: 40, | |
right: 20, | |
bottom: 20 | |
}, | |
yColumn: Model.None, | |
xColumn: Model.None, | |
// These properties adjust spacing between bars. | |
// The names correspond to the arguments passed to | |
// d3.scale.ordinal.rangeRoundBands(interval[, padding[, outerPadding]]) | |
// https://github.com/mbostock/d3/wiki/Ordinal-Scales#ordinal_rangeRoundBands | |
barPadding: 0.1, | |
barOuterPadding: 0.1, | |
fill: "#a3a3a3", | |
stroke: "none", | |
strokeWidth: "1px", | |
brushEnabled: false, | |
brushIntervalX: Model.None, | |
title: "", | |
titleSize: "1.5em", | |
titleOffset: "-0.3em" | |
}); | |
var yScale = d3.scale.linear(); | |
// This scale is for the bars to use. | |
var xScale = d3.scale.ordinal(); | |
// This scale is for the brush to use. | |
var xScaleLinear = d3.scale.linear(); | |
var brush = d3.svg.brush() | |
.x(xScaleLinear) | |
.on("brush", onBrush); | |
my.el = document.createElement("div"); | |
var svg = d3.select(my.el).append("svg"); | |
var g = svg.append("g"); | |
var titleText = g.append("text"); | |
var barsG = g.append("g"); | |
var brushG = g.append("g").attr("class", "brush"); | |
function onBrush() { | |
my.brushIntervalX = brush.empty() ? Model.None : brush.extent(); | |
} | |
my.when("title", titleText.text, titleText); | |
my.when("titleSize", function (titleSize){ | |
titleText.style("font-size", titleSize); | |
}); | |
my.when("titleOffset", function (titleOffset){ | |
titleText.attr("dy", titleOffset); | |
}); | |
// Respond to changes in size and margin. | |
// Inspired by D3 margin convention from http://bl.ocks.org/mbostock/3019563 | |
my.when(["box", "margin"], function(box, margin){ | |
my.innerBox = { | |
width: box.width - margin.left - margin.right, | |
height: box.height - margin.top - margin.bottom | |
}; | |
svg | |
.attr("width", box.width) | |
.attr("height", box.height); | |
g.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
}); | |
my.when(["data", "xColumn", "innerBox", "barPadding", "barOuterPadding"], | |
function (data, xColumn, innerBox, barPadding, barOuterPadding){ | |
var xAccessor = function (d){ return d[xColumn]; }; | |
xScale | |
.domain(data.map(xAccessor)) | |
.rangeRoundBands([0, innerBox.width], barPadding, barOuterPadding); | |
xScaleLinear | |
.domain(d3.extent(data, xAccessor)) | |
.range([0, innerBox.width]); | |
my.x = function(d) { return xScale(xAccessor(d)); } | |
my.width = xScale.rangeBand(); | |
}); | |
my.when(["data", "yColumn", "innerBox"], | |
function (data, yColumn, innerBox){ | |
var yAccessor = function (d){ return d[yColumn]; }; | |
yScale | |
.domain([0, d3.max(data, yAccessor)]) | |
.range([innerBox.height, 0]); | |
my.y = function(d) { return yScale(yAccessor(d)); }; | |
my.height = function(d) { return innerBox.height - my.y(d); }; | |
}); | |
my.when(["data", "x", "y", "width", "height", "fill", "stroke", "strokeWidth"], | |
function (data, x, y, width, height, fill, stroke, strokeWidth){ | |
var bars = barsG.selectAll("rect").data(data); | |
bars.enter().append("rect"); | |
bars | |
.attr("x", x) | |
.attr("width", width) | |
.attr("y", y) | |
.attr("height", height) | |
.attr("fill", fill) | |
.attr("stroke", stroke) | |
.attr("stroke-width", strokeWidth); | |
bars.exit().remove(); | |
}); | |
my.when(["brushIntervalX", "innerBox", "x", "y"], | |
function (brushIntervalX, innerBox){ | |
if(brushIntervalX !== Model.None){ | |
//brush.extent(parseDates(brushIntervalX)); | |
// Uncomment this to see what the brush interval is as you drag. | |
//console.log(brushIntervalX.map(function (date){ | |
// return date.toUTCString(); | |
//})); | |
} | |
brushG.call(brush); | |
brushG.selectAll("rect") | |
.attr("y", 0) | |
.attr("height", innerBox.height); | |
}); | |
return my; | |
} |
// This function defines a Chiasm component that exposes a Crossfilter instance | |
// to visualizations via the Chaism configuration. | |
function ChiasmCrossfilter() { | |
var my = new ChiasmComponent({ | |
groups: Model.None | |
}); | |
var listeners = []; | |
my.when(["data", "groups"], function (data, groups){ | |
if(groups !== Model.None) { | |
var cf = crossfilter(data); | |
var updateFunctions = []; | |
listeners.forEach(my.cancel); | |
listeners = Object.keys(groups).map(function (groupName){ | |
var group = groups[groupName]; | |
var dimension = group.dimension; | |
var cfDimension = cf.dimension(function (d){ return d[dimension]; }); | |
// Generate an aggregate function by parsing the "aggregation" config option. | |
var aggregate; | |
if(group.aggregation === "day"){ | |
aggregate = d3.time.day; | |
} else if(group.aggregation.indexOf("floor") === 0){ | |
var interval = parseInt(group.aggregation.substr(6)); | |
aggregate = function(d) { | |
return Math.floor(d / interval) * interval; | |
}; | |
} | |
var cfGroup = cfDimension.group(aggregate); | |
var updateMyGroup = function (){ | |
my[groupName] = cfGroup.all(); | |
}; | |
updateFunctions.push(updateMyGroup); | |
updateMyGroup(); | |
return my.when(dimension + "Filter", function (extent){ | |
if(extent !== Model.None){ | |
cfDimension.filterRange(extent); | |
} else { | |
cfDimension.filterAll(); | |
} | |
updateFunctions.forEach(function (updateFunction){ | |
if(updateFunction !== updateMyGroup){ | |
updateFunction(); | |
} | |
}); | |
}); | |
}); | |
} | |
}); | |
return my; | |
} |
function ChiasmCSVLoader (){ | |
var my = ChiasmComponent({ | |
path: Model.None | |
}); | |
var parseFunctions = { | |
number: parseFloat | |
}; | |
function generateColumnParsers(metadata) { | |
if("columns" in metadata){ | |
return metadata.columns | |
.filter(function (column){ | |
return column.type !== "string"; | |
}) | |
.map(function (column){ | |
var parse = parseFunctions[column.type]; | |
var name = column.name; | |
return function (d){ | |
d[name] = parse(d[name]); | |
} | |
}); | |
} else { | |
return []; | |
} | |
} | |
my.when("path", function (path){ | |
if(path !== Model.None){ | |
d3.json(path + ".json", function(error, metadata) { | |
var columnParsers = generateColumnParsers(metadata); | |
var numColumns = columnParsers.length; | |
function type (d){ | |
// Old school for loop as an optimization. | |
for(var i = 0; i < numColumns; i++){ | |
// Each column parser function mutates the row object, | |
// replacing the column property string with its parsed variant. | |
columnParsers[i](d); | |
} | |
return d; | |
} | |
d3.csv(path + ".csv", type, function(error, data) { | |
my.data = data; | |
}); | |
}); | |
} | |
}); | |
return my; | |
} |
{ | |
"columns": [ | |
{ "name": "date", "type": "string" }, | |
{ "name": "delay", "type": "number" }, | |
{ "name": "distance", "type": "number" }, | |
{ "name": "origin", "type": "string" }, | |
{ "name": "destination", "type": "string" } | |
] | |
} |
// This does custom data preprocessing for the flight data. | |
// Modified from Crossfilter example code: https://github.com/square/crossfilter/blob/gh-pages/index.html#L231 | |
function FlightsPreprocessor() { | |
var my = new ChiasmComponent(); | |
my.when("dataIn", function (dataIn){ | |
my.dataOut = dataIn.map(function (d){ | |
d.date = parseDate(d.date); | |
d.hour = d.date.getHours() + d.date.getMinutes() / 60; | |
d.delay = Math.max(-60, Math.min(149, d.delay)); | |
d.distance = Math.min(1999, d.distance); | |
return d; | |
}); | |
}); | |
function parseDate(d) { | |
return new Date(2001, | |
d.substring(0, 2) - 1, | |
d.substring(2, 4), | |
d.substring(4, 6), | |
d.substring(6, 8)); | |
} | |
return my; | |
} |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Chiasm Crossfilter Integration</title> | |
<!-- A functional reactive model library. See github.com/curran/model --> | |
<script src="http://curran.github.io/model/cdn/model-v0.2.4.js"></script> | |
<!-- The common base for Chiasm components (depends on Model.js). --> | |
<script src="http://chiasm-project.github.io/chiasm-component/chiasm-component-v0.2.1.js"></script> | |
<!-- This script defines the BarChart component. --> | |
<script src="barChart.js"> </script> | |
<!-- Load Crossfilter and the Crossfilter Chiasm component. --> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.min.js"></script> | |
<script src="chiasm-crossfilter.js"></script> | |
<!-- This plugin loads CSV data sets. --> | |
<script src="chiasm-csv-loader.js"></script> | |
<!-- This does custom data preprocessing for the flight data. --> | |
<script src="flights-preprocessor.js"></script> | |
<!-- Chiasm.js depends on Model.js, Lodash.js, D3.js. --> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script> | |
<!-- Chiasm.js and plugins. See github.com/chiasm-project --> | |
<script src="http://chiasm-project.github.io/chiasm/chiasm-v0.2.0.js"></script> | |
<script src="http://chiasm-project.github.io/chiasm-layout/chiasm-layout-v0.2.2.js"></script> | |
<script src="http://chiasm-project.github.io/chiasm-links/chiasm-links-v0.2.1.js"></script> | |
<!-- Make the Chiasm container fill the page and have a 20px black border. --> | |
<style> | |
body { | |
background-color: black; | |
} | |
#chiasm-container { | |
background-color: white; | |
position: fixed; | |
left: 20px; | |
right: 20px; | |
top: 20px; | |
bottom: 20px; | |
} | |
/* Style the brush. Draws from http://bl.ocks.org/mbostock/4343214 */ | |
.brush .extent { | |
stroke: black; | |
fill-opacity: .2; | |
shape-rendering: crispEdges; | |
} | |
</style> | |
</head> | |
<body> | |
<!-- Chiasm component DOM elements will be injected into this div. --> | |
<div id="chiasm-container"></div> | |
<!-- This is the main program that sets up the Chiasm application. --> | |
<script> | |
// Create a new Chiasm instance. | |
var chiasm = new Chiasm(); | |
// Register plugins that the configuration can access. | |
chiasm.plugins.layout = ChiasmLayout; | |
chiasm.plugins.links = ChiasmLinks; | |
chiasm.plugins.barChart = BarChart; | |
chiasm.plugins.csvLoader = ChiasmCSVLoader; | |
chiasm.plugins.flightsPreprocessor = FlightsPreprocessor; | |
chiasm.plugins.crossfilter = ChiasmCrossfilter; | |
// Set the Chaism configuration. | |
chiasm.setConfig({ | |
"layout": { | |
"plugin": "layout", | |
"state": { | |
"containerSelector": "#chiasm-container", | |
"layout": { | |
"orientation": "vertical", | |
"children": [ | |
{ | |
"orientation": "horizontal", | |
"children": [ | |
"hour-chart", | |
"delay-chart", | |
"distance-chart" | |
] | |
}, | |
"date-chart" | |
] | |
}, | |
"sizes": { | |
"distance-chart": { | |
"size": 1.5 | |
} | |
} | |
} | |
}, | |
"hour-chart": { | |
"plugin": "barChart", | |
"state": { | |
"title": "Time of Day", | |
"yColumn": "value", | |
"xColumn": "key" | |
} | |
}, | |
"delay-chart": { | |
"plugin": "barChart", | |
"state": { | |
"title": "Arrival Delay (min.)", | |
"yColumn": "value", | |
"xColumn": "key" | |
} | |
}, | |
"distance-chart": { | |
"plugin": "barChart", | |
"state": { | |
"title": "Distance (mi.)", | |
"yColumn": "value", | |
"xColumn": "key" | |
} | |
}, | |
"date-chart": { | |
"plugin": "barChart", | |
"state": { | |
"title": "Date", | |
"yColumn": "value", | |
"xColumn": "key" | |
} | |
}, | |
"flights-dataset": { | |
"plugin": "csvLoader", | |
"state": { | |
// for development | |
//"path": "../ca04856ca6afbc563957/flights-3m" | |
// for production | |
"path": "http://bl.ocks.org/curran/raw/ca04856ca6afbc563957/flights-3m" | |
} | |
}, | |
"flights-preprocessor": { | |
"plugin": "flightsPreprocessor" | |
}, | |
"flights-crossfilter": { | |
"plugin": "crossfilter", | |
"state": { | |
"groups": { | |
"dates": { | |
"dimension": "date", | |
"aggregation": "day" | |
}, | |
"hours": { | |
"dimension": "hour", | |
"aggregation": "floor 1" | |
}, | |
"delays": { | |
"dimension": "delay", | |
"aggregation": "floor 10" | |
}, | |
"distances": { | |
"dimension": "distance", | |
"aggregation": "floor 50" | |
} | |
} | |
} | |
}, | |
"links": { | |
"plugin": "links", | |
"state": { | |
"bindings": [ | |
"flights-dataset.data -> flights-preprocessor.dataIn", | |
"flights-preprocessor.dataOut -> flights-crossfilter.data", | |
"flights-crossfilter.hours -> hour-chart.data", | |
"flights-crossfilter.delays -> delay-chart.data", | |
"flights-crossfilter.distances -> distance-chart.data", | |
"flights-crossfilter.dates -> date-chart.data", | |
"hour-chart.brushIntervalX -> flights-crossfilter.hourFilter", | |
"delay-chart.brushIntervalX -> flights-crossfilter.delayFilter", | |
"distance-chart.brushIntervalX -> flights-crossfilter.distanceFilter", | |
"date-chart.brushIntervalX -> flights-crossfilter.dateFilter" | |
] | |
} | |
} | |
}); | |
</script> | |
</body> | |
</html> |
The MIT License (MIT) | |
Copyright (c) 2015 Curran Kelleher | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. | |