Focus + Context Area Charts using Chiasm.
Draws from
Focus + Context Area Charts using Chiasm.
Draws from
function AreaChart() { | |
var my = ChiasmComponent({ | |
margin: { | |
left: 20, | |
top: 20, | |
right: 20, | |
bottom: 20 | |
}, | |
xColumn: Model.None, | |
yColumn: Model.None, | |
xScaleDomain: Model.None, | |
yScaleDomain: Model.None, | |
fill: "black", | |
stroke: "none", | |
strokeWidth: "1px", | |
backgroundColor: "lightgray", | |
border: "black", | |
brushEnabled: false, | |
brushIntervalX: Model.None | |
}); | |
var xScale = d3.time.scale(); | |
var yScale = d3.scale.linear(); | |
var brush = d3.svg.brush() | |
.x(xScale) | |
.on("brush", onBrush); | |
var svg = d3.select(my.initSVG()); | |
var clipRect = svg | |
.append("clipPath") | |
.attr("id", "clip") | |
.append("rect"); | |
var g = svg.append("g"); | |
var borderRect = g.append("rect") | |
.style("stroke-width", 2); | |
var areaG = g.append("g") | |
.style("clip-path", "url(#clip)"); | |
var path = areaG.append("path"); | |
var area = d3.svg.area() | |
.interpolate("monotone"); | |
var brushG = g.append("g") | |
.attr("class", "brush"); | |
my.when("backgroundColor", function (backgroundColor){ | |
borderRect.style("fill", backgroundColor); | |
}); | |
my.when("border", function (border){ | |
borderRect.style("stroke", border) | |
}); | |
// 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 | |
}; | |
g.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
}); | |
my.when(["innerBox"], function (innerBox){ | |
borderRect | |
.attr("width", innerBox.width) | |
.attr("height", innerBox.height); | |
clipRect | |
.attr("width", innerBox.width) | |
.attr("height", innerBox.height); | |
}); | |
my.when(["data", "innerBox", "xColumn", "xScaleDomain"], | |
function (data, innerBox, xColumn, xScaleDomain){ | |
if(xColumn !== Model.None){ | |
xScale.range([0, innerBox.width]); | |
if(xScaleDomain !== Model.None){ | |
xScale.domain(parseDates(xScaleDomain)); | |
} else { | |
xScale.domain(d3.extent(data, function (d){ return d[xColumn]; })); | |
} | |
my.x = function (d){ return xScale(d[xColumn]); }; | |
} | |
}); | |
my.when(["data", "innerBox", "yColumn", "yScaleDomain"], | |
function (data, innerBox, yColumn, yScaleDomain){ | |
if(yColumn !== Model.None){ | |
if(yScaleDomain !== Model.None){ | |
yScale.domain(yScaleDomain); | |
} else { | |
yScale.domain([0, d3.max(data, function (d){ return d[yColumn]; })]); | |
} | |
yScale.range([innerBox.height, 0]); | |
my.y = function (d){ return yScale(d[yColumn]); }; | |
} | |
}); | |
my.when([ "data", "x", "y", "innerBox", "fill", "stroke", "strokeWidth" ], | |
function (data, x, y, innerBox, fill, stroke, strokeWidth){ | |
area | |
.x(x) | |
.y0(innerBox.height) | |
.y1(y) | |
path | |
.datum(data) | |
.attr("d", area) | |
.attr("fill", fill) | |
.attr("stroke", stroke) | |
.attr("stroke-width", strokeWidth); | |
}); | |
my.when("brushEnabled", function (brushEnabled){ | |
brushG.remove(); | |
if(brushEnabled){ | |
g.node().appendChild(brushG.node()); | |
} | |
}); | |
function onBrush() { | |
my.brushIntervalX = brush.empty() ? Model.None : brush.extent(); | |
} | |
function parseDates(dates){ | |
return dates.map(function (date){ | |
if(typeof date === Date){ | |
return date; | |
} else { | |
return new Date(date); | |
} | |
}); | |
} | |
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; | |
} |
function Bridge() { | |
var my = ChiasmComponent({ | |
margin: { | |
left: 20, | |
top: 0, | |
right: 20, | |
bottom: 0 | |
}, | |
xColumn: Model.None, | |
yColumn: Model.None, | |
xScaleDomain: Model.None, | |
yScaleDomain: Model.None, | |
stroke: "black", | |
strokeWidth: "1px", | |
brushIntervalX: Model.None | |
}); | |
var xScale = d3.time.scale(); | |
var yScale = d3.scale.linear(); | |
var svg = d3.select(my.initSVG()); | |
var clipRect = svg | |
.append("clipPath") | |
.attr("id", "clip") | |
.append("rect"); | |
var g = svg.append("g"); | |
var areaG = g.append("g") | |
.style("clip-path", "url(#clip)"); | |
var path = areaG.append("path"); | |
var area = d3.svg.area() | |
.interpolate("monotone"); | |
// 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 | |
}; | |
g.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
}); | |
my.when(["data", "brushIntervalX", "innerBox", "xColumn", "xScaleDomain", "stroke", "strokeWidth"], | |
function (data, brushIntervalX, innerBox, xColumn, xScaleDomain, stroke, strokeWidth){ | |
if(xColumn !== Model.None){ | |
var extent = d3.extent(data, function (d){ return d[xColumn]; }); | |
xScale.range([0, innerBox.width]); | |
xScale.domain(extent); | |
var linesData = []; | |
if(brushIntervalX !== Model.None){ | |
brushIntervalX = parseDates(brushIntervalX); | |
var w = innerBox.width; | |
var h = innerBox.height; | |
var linesData = [ | |
{ | |
x1: xScale(brushIntervalX[1]), | |
y1: h, | |
x2: w, | |
y2: 0 | |
}, | |
{ | |
x1: xScale(brushIntervalX[0]), | |
y1: h, | |
x2: 0, | |
y2: 0 | |
} | |
]; | |
} | |
var lines = g.selectAll("line").data(linesData); | |
lines.enter().append("line"); | |
lines | |
.attr("x1", function (d) { return d.x1; }) | |
.attr("y1", function (d) { return d.y1; }) | |
.attr("x2", function (d) { return d.x2; }) | |
.attr("y2", function (d) { return d.y2; }) | |
.style("stroke-width", strokeWidth) | |
.style("stroke", stroke); | |
lines.exit().remove(); | |
} | |
}); | |
function parseDates(dates){ | |
return dates.map(function (date){ | |
if(typeof date === Date){ | |
return date; | |
} else { | |
return new Date(date); | |
} | |
}); | |
} | |
return my; | |
} |
// A Chiasm plugin for loading DSV data sets. | |
function DataLoader (){ | |
var my = ChiasmComponent({ | |
path: Model.None | |
}); | |
my.when("path", function (path){ | |
if(path !== Model.None){ | |
d3.json(path + ".json", function(error, schema) { | |
var numericColumns = schema.columns.filter(function (column){ | |
return column.type === "number"; | |
}); | |
var dateColumns = schema.columns.filter(function (column){ | |
return column.type === "date"; | |
}); | |
var type = function (d){ | |
numericColumns.forEach(function (column){ | |
d[column.name] = +d[column.name]; | |
}); | |
dateColumns.forEach(function (column){ | |
d[column.name] = new Date(d[column.name]); | |
}); | |
return d; | |
} | |
d3.csv(path + ".csv", type, function(error, data) { | |
my.data = data; | |
}); | |
}); | |
} | |
}); | |
return my; | |
} |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Focus + Context Scatter Plots</title> | |
<!-- Chiasm depends on Lodash, D3.js, and Model.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> | |
<!-- A functional reactive model library. github.com/curran/model --> | |
<script src="http://curran.github.io/model/cdn/model-v0.2.4.js"></script> | |
<!-- Chiasm core and plugins. 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-component/chiasm-component-v0.2.1.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> | |
<!-- Custom Chiasm components for this example. --> | |
<script src="dataLoader.js"></script> | |
<script src="areaChart.js"></script> | |
<script src="bridge.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 instances will be injected into this div. --> | |
<div id="chiasm-container"></div> | |
<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.dataLoader = DataLoader; | |
chiasm.plugins.areaChart = AreaChart; | |
chiasm.plugins.bridge = Bridge; | |
// Set the Chaism configuration. | |
chiasm.setConfig({ | |
"layout": { | |
"plugin": "layout", | |
"state": { | |
"containerSelector": "#chiasm-container", | |
"layout": { | |
"orientation": "vertical", | |
"children": [ | |
"focus", | |
"bridge", | |
"context" | |
] | |
}, | |
"sizes": { | |
"focus": { | |
"size": 3 | |
}, | |
"bridge": { | |
"size": "20px" | |
} | |
} | |
} | |
}, | |
"sp500": { | |
"plugin": "dataLoader", | |
"state": { | |
"path": "sp500" | |
} | |
}, | |
"focus": { | |
"plugin": "areaChart", | |
"state": { | |
"xColumn": "date", | |
"yColumn": "price", | |
"fill": "gray", | |
"margin": { | |
"left": 20, | |
"top": 20, | |
"right": 20, | |
"bottom": 0 | |
} | |
} | |
}, | |
"context": { | |
"plugin": "areaChart", | |
"state": { | |
"xColumn": "date", | |
"yColumn": "price", | |
"fill": "#aaaaaa", | |
"backgroundColor": "white", | |
"border": "none", | |
"brushEnabled": true, | |
"brushIntervalX": [ | |
"Sun, 30 Sep 2007 06:16:37 GMT", | |
"Mon, 19 Jan 2009 10:19:25 GMT" | |
], | |
"margin": { | |
"left": 20, | |
"top": 0, | |
"right": 20, | |
"bottom": 20 | |
} | |
} | |
}, | |
"bridge": { | |
"plugin": "bridge", | |
"state": { | |
"xColumn": "date" | |
} | |
}, | |
"links": { | |
"plugin": "links", | |
"state": { | |
"bindings": [ | |
"sp500.data -> focus.data", | |
"sp500.data -> context.data", | |
"sp500.data -> bridge.data", | |
"context.brushIntervalX -> focus.xScaleDomain", | |
"context.brushIntervalX -> bridge.brushIntervalX" | |
] | |
} | |
} | |
}); | |
</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. | |
date | price | |
---|---|---|
Jan 2000 | 1394.46 | |
Feb 2000 | 1366.42 | |
Mar 2000 | 1498.58 | |
Apr 2000 | 1452.43 | |
May 2000 | 1420.6 | |
Jun 2000 | 1454.6 | |
Jul 2000 | 1430.83 | |
Aug 2000 | 1517.68 | |
Sep 2000 | 1436.51 | |
Oct 2000 | 1429.4 | |
Nov 2000 | 1314.95 | |
Dec 2000 | 1320.28 | |
Jan 2001 | 1366.01 | |
Feb 2001 | 1239.94 | |
Mar 2001 | 1160.33 | |
Apr 2001 | 1249.46 | |
May 2001 | 1255.82 | |
Jun 2001 | 1224.38 | |
Jul 2001 | 1211.23 | |
Aug 2001 | 1133.58 | |
Sep 2001 | 1040.94 | |
Oct 2001 | 1059.78 | |
Nov 2001 | 1139.45 | |
Dec 2001 | 1148.08 | |
Jan 2002 | 1130.2 | |
Feb 2002 | 1106.73 | |
Mar 2002 | 1147.39 | |
Apr 2002 | 1076.92 | |
May 2002 | 1067.14 | |
Jun 2002 | 989.82 | |
Jul 2002 | 911.62 | |
Aug 2002 | 916.07 | |
Sep 2002 | 815.28 | |
Oct 2002 | 885.76 | |
Nov 2002 | 936.31 | |
Dec 2002 | 879.82 | |
Jan 2003 | 855.7 | |
Feb 2003 | 841.15 | |
Mar 2003 | 848.18 | |
Apr 2003 | 916.92 | |
May 2003 | 963.59 | |
Jun 2003 | 974.5 | |
Jul 2003 | 990.31 | |
Aug 2003 | 1008.01 | |
Sep 2003 | 995.97 | |
Oct 2003 | 1050.71 | |
Nov 2003 | 1058.2 | |
Dec 2003 | 1111.92 | |
Jan 2004 | 1131.13 | |
Feb 2004 | 1144.94 | |
Mar 2004 | 1126.21 | |
Apr 2004 | 1107.3 | |
May 2004 | 1120.68 | |
Jun 2004 | 1140.84 | |
Jul 2004 | 1101.72 | |
Aug 2004 | 1104.24 | |
Sep 2004 | 1114.58 | |
Oct 2004 | 1130.2 | |
Nov 2004 | 1173.82 | |
Dec 2004 | 1211.92 | |
Jan 2005 | 1181.27 | |
Feb 2005 | 1203.6 | |
Mar 2005 | 1180.59 | |
Apr 2005 | 1156.85 | |
May 2005 | 1191.5 | |
Jun 2005 | 1191.33 | |
Jul 2005 | 1234.18 | |
Aug 2005 | 1220.33 | |
Sep 2005 | 1228.81 | |
Oct 2005 | 1207.01 | |
Nov 2005 | 1249.48 | |
Dec 2005 | 1248.29 | |
Jan 2006 | 1280.08 | |
Feb 2006 | 1280.66 | |
Mar 2006 | 1294.87 | |
Apr 2006 | 1310.61 | |
May 2006 | 1270.09 | |
Jun 2006 | 1270.2 | |
Jul 2006 | 1276.66 | |
Aug 2006 | 1303.82 | |
Sep 2006 | 1335.85 | |
Oct 2006 | 1377.94 | |
Nov 2006 | 1400.63 | |
Dec 2006 | 1418.3 | |
Jan 2007 | 1438.24 | |
Feb 2007 | 1406.82 | |
Mar 2007 | 1420.86 | |
Apr 2007 | 1482.37 | |
May 2007 | 1530.62 | |
Jun 2007 | 1503.35 | |
Jul 2007 | 1455.27 | |
Aug 2007 | 1473.99 | |
Sep 2007 | 1526.75 | |
Oct 2007 | 1549.38 | |
Nov 2007 | 1481.14 | |
Dec 2007 | 1468.36 | |
Jan 2008 | 1378.55 | |
Feb 2008 | 1330.63 | |
Mar 2008 | 1322.7 | |
Apr 2008 | 1385.59 | |
May 2008 | 1400.38 | |
Jun 2008 | 1280 | |
Jul 2008 | 1267.38 | |
Aug 2008 | 1282.83 | |
Sep 2008 | 1166.36 | |
Oct 2008 | 968.75 | |
Nov 2008 | 896.24 | |
Dec 2008 | 903.25 | |
Jan 2009 | 825.88 | |
Feb 2009 | 735.09 | |
Mar 2009 | 797.87 | |
Apr 2009 | 872.81 | |
May 2009 | 919.14 | |
Jun 2009 | 919.32 | |
Jul 2009 | 987.48 | |
Aug 2009 | 1020.62 | |
Sep 2009 | 1057.08 | |
Oct 2009 | 1036.19 | |
Nov 2009 | 1095.63 | |
Dec 2009 | 1115.1 | |
Jan 2010 | 1073.87 | |
Feb 2010 | 1104.49 | |
Mar 2010 | 1140.45 |
{ | |
"columns": [ | |
{ | |
"name": "date", | |
"type": "date" | |
}, | |
{ | |
"name": "price", | |
"type": "number" | |
} | |
] | |
} |