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" | |
| } | |
| ] | |
| } |