Focus + Context Scatter Plots using Chiasm.
Draws from
Focus + Context Scatter Plots using Chiasm.
Draws from
| // 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 type = function (d){ | |
| numericColumns.forEach(function (column){ | |
| d[column.name] = +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="scatterPlot.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: gray; | |
| fill-opacity: .125; | |
| 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.scatterPlot = ScatterPlot; | |
| // Set the Chaism configuration. | |
| chiasm.setConfig({ | |
| "layout": { | |
| "plugin": "layout", | |
| "state": { | |
| "containerSelector": "#chiasm-container", | |
| "layout": { | |
| "orientation": "horizontal", | |
| "children": [ | |
| "context", | |
| "focus" | |
| ] | |
| } | |
| } | |
| }, | |
| "scatterPlotData": { | |
| "plugin": "dataLoader", | |
| "state": { | |
| "path": "scatterPlotData" | |
| } | |
| }, | |
| "focus": { | |
| "plugin": "scatterPlot", | |
| "state": { | |
| "xColumn": "sepal_length", | |
| "yColumn": "petal_length" | |
| } | |
| }, | |
| "context": { | |
| "plugin": "scatterPlot", | |
| "state": { | |
| "xColumn": "sepal_length", | |
| "yColumn": "petal_length", | |
| "brushEnabled": true, | |
| "brushIntervalX": [5.3, 6.5], | |
| "brushIntervalY": [3.2, 5.6] | |
| } | |
| }, | |
| "links": { | |
| "plugin": "links", | |
| "state": { | |
| "bindings": [ | |
| "scatterPlotData.data -> focus.data", | |
| "scatterPlotData.data -> context.data", | |
| "context.brushIntervalX -> focus.xScaleDomain", | |
| "context.brushIntervalY -> focus.yScaleDomain" | |
| ] | |
| } | |
| } | |
| }); | |
| </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. | |
| // This is an example Chaism plugin that uses D3 to make a scatter plot. | |
| // Draws from this Scatter Plot example http://bl.ocks.org/curran/134ed87c99257e3f2e31 | |
| function ScatterPlot() { | |
| var my = ChiasmComponent({ | |
| margin: { | |
| left: 20, | |
| top: 20, | |
| right: 20, | |
| bottom: 20 | |
| }, | |
| xColumn: Model.None, | |
| yColumn: Model.None, | |
| xScaleDomain: Model.None, | |
| yScaleDomain: Model.None, | |
| // "r" stands for radius. | |
| rColumn: Model.None, | |
| // The circle radius used if rColumn is not specified. | |
| rDefault: 10, | |
| // The range of the radius scale if rColumn is specified. | |
| rMin: 0, | |
| rMax: 10, | |
| fill: "black", | |
| stroke: "none", | |
| strokeWidth: "1px", | |
| brushEnabled: false, | |
| brushIntervalX: Model.None, | |
| brushIntervalY: Model.None | |
| }); | |
| var xScale = d3.scale.linear(); | |
| var yScale = d3.scale.linear(); | |
| var rScale = d3.scale.sqrt(); | |
| var brush = d3.svg.brush() | |
| .x(xScale) | |
| .y(yScale) | |
| .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("fill", "lightgray") | |
| .style("stroke", "black") | |
| .style("stroke-width", 1); | |
| var circlesG = g.append("g") | |
| .style("clip-path", "url(#clip)"); | |
| var brushG = g.append("g") | |
| .attr("class", "brush"); | |
| // 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(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(d3.extent(data, function (d){ return d[yColumn]; })); | |
| } | |
| yScale.range([innerBox.height, 0]); | |
| my.y = function (d){ return yScale(d[yColumn]); }; | |
| } | |
| }); | |
| // Generate a function or constant for circle radius, | |
| // depending on whether or not rColumn is defined. | |
| my.when(["data", "rColumn", "rDefault", "rMin", "rMax"], | |
| function (data, rColumn, rDefault, rMin, rMax){ | |
| if(rColumn === Model.None){ | |
| my.r = rDefault; | |
| } else { | |
| rScale | |
| .domain(d3.extent(data, function (d){ return d[rColumn]; })) | |
| .range([rMin, rMax]); | |
| my.r = function (d){ return rScale(d[rColumn]); }; | |
| } | |
| }); | |
| my.when([ "data", "x", "y", "r", "fill", "stroke", "strokeWidth" ], | |
| function (data, x, y, r, fill, stroke, strokeWidth){ | |
| // Render the circles of the scatter plot. | |
| var circles = circlesG.selectAll("circle").data(data); | |
| circles.enter().append("circle"); | |
| circles.exit().remove(); | |
| circles | |
| .attr("cx", x) | |
| .attr("cy", y) | |
| .attr("r", r) | |
| .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() { | |
| if(brush.empty()){ | |
| my.brushIntervalX = Model.None; | |
| my.brushIntervalY = Model.None; | |
| } else { | |
| var e = brush.extent(); | |
| my.brushIntervalX = [e[0][0], e[1][0]]; | |
| my.brushIntervalY = [e[0][1], e[1][1]]; | |
| } | |
| } | |
| my.when(["brushIntervalX", "brushIntervalY", "x", "y"], | |
| function (brushIntervalX, brushIntervalY){ | |
| if(brushIntervalX !== Model.None && brushIntervalY !== Model.None){ | |
| brush.extent([ | |
| [brushIntervalX[0], brushIntervalY[0]], | |
| [brushIntervalX[1], brushIntervalY[1]] | |
| ]); | |
| } | |
| brushG.call(brush); | |
| }); | |
| return my; | |
| } |
| sepal_length | sepal_width | petal_length | petal_width | class | |
|---|---|---|---|---|---|
| 5.0 | 3.4 | 1.5 | 0.2 | setosa | |
| 4.8 | 3.4 | 1.6 | 0.2 | setosa | |
| 5.1 | 3.5 | 1.4 | 0.3 | setosa | |
| 5.4 | 3.4 | 1.7 | 0.2 | setosa | |
| 5.0 | 3.4 | 1.6 | 0.4 | setosa | |
| 4.4 | 3.0 | 1.3 | 0.2 | setosa | |
| 5.0 | 3.5 | 1.3 | 0.3 | setosa | |
| 4.6 | 3.2 | 1.4 | 0.2 | setosa | |
| 6.9 | 3.1 | 4.9 | 1.5 | versicolor | |
| 5.9 | 3.0 | 4.2 | 1.5 | versicolor | |
| 5.8 | 2.7 | 4.1 | 1.0 | versicolor | |
| 6.1 | 2.8 | 4.0 | 1.3 | versicolor | |
| 5.7 | 2.6 | 3.5 | 1.0 | versicolor | |
| 5.5 | 2.4 | 3.8 | 1.1 | versicolor | |
| 5.6 | 3.0 | 4.1 | 1.3 | versicolor | |
| 5.8 | 2.6 | 4.0 | 1.2 | versicolor | |
| 5.1 | 2.5 | 3.0 | 1.1 | versicolor | |
| 5.8 | 2.7 | 5.1 | 1.9 | virginica | |
| 7.3 | 2.9 | 6.3 | 1.8 | virginica | |
| 6.8 | 3.0 | 5.5 | 2.1 | virginica | |
| 6.0 | 2.2 | 5.0 | 1.5 | virginica | |
| 5.6 | 2.8 | 4.9 | 2.0 | virginica | |
| 6.3 | 2.8 | 5.1 | 1.5 | virginica | |
| 6.9 | 3.1 | 5.4 | 2.1 | virginica | |
| 6.2 | 3.4 | 5.4 | 2.3 | virginica |
| { | |
| "columns": [ | |
| { "name": "sepal_length", "type": "number" }, | |
| { "name": "sepal_width", "type": "number" }, | |
| { "name": "petal_length", "type": "number" }, | |
| { "name": "petal_width", "type": "number" }, | |
| { "name": "class", "type": "string" } | |
| ] | |
| } |