├── LICENSE.txt ├── README.md ├── bower.json ├── demo ├── demo.css ├── demo.csv ├── demo.html └── demo.png ├── scatter-matrix.css └── scatter-matrix.js /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2014 Ginkgo Bioworks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # scatter-matrix.js 3 | 4 | scatter-matrix.js (SM) is a JavaScript library for drawing scatterplot matrix. 5 | SM handles matrix data in CSV format: rows represent samples and columns 6 | represent observations. SM interprets the first row as a header. All numeric 7 | columns appear as rows and columns of the scatterplot matrix. 8 | 9 | SM is a simple extension/generalization of [Mike Bostock's scatterplot matrix 10 | example](http://mbostock.github.io/d3/talk/20111116/iris-splom.html). 11 | Additional features include 12 | 13 | * User can color dots by values of a non-numeric observation. 14 | * User can filter data by values of a non-numeric observation. 15 | * User can decide what numeric observations to include in the matrix. 16 | * User can expand the matrix and view data by fixing one or more observations at set values. 17 | 18 | For demo, see http://benjiec.github.io/scatter-matrix/demo/demo.html 19 | 20 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scatter-matrix", 3 | "version": "0.1.0", 4 | "main": ["scatter-matrix.css", "scatter-matrix.js"], 5 | "ignore": [ "demo", ".gitignore", ".git" ], 6 | "dependencies": { 7 | "d3": "3.3.10" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /demo/demo.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: auto; 3 | width: 1200px; 4 | font-weight: 300; font-family: "Helvetica Neue"; 5 | overflow: auto; 6 | } 7 | 8 | h2 { font-weight: inherit; margin: 0; } 9 | pre { background-color: #eee; border-radius: 10px; border: 1px solid #ddd; width: 600px; padding: 10px; } 10 | 11 | .scatter-matrix-control { float: left; margin-right: 20px; } 12 | .scatter-matrix-variable-control ul { list-style: none; padding-left: 10px; } 13 | .scatter-matrix-svg { float: left; margin-top: 15px; width: 1000px; } 14 | 15 | -------------------------------------------------------------------------------- /demo/demo.csv: -------------------------------------------------------------------------------- 1 | species,sepal length,sepal width,petal length,petal width 2 | setosa,5.1,3.5,1.4,0.2 3 | setosa,4.9,3,1.4,0.2 4 | setosa,4.7,3.2,1.3,0.2 5 | setosa,4.6,3.1,1.5,0.2 6 | setosa,5,3.6,1.4,0.2 7 | setosa,5.4,3.9,1.7,0.4 8 | setosa,4.6,3.4,1.4,0.3 9 | setosa,5,3.4,1.5,0.2 10 | setosa,4.4,2.9,1.4,0.2 11 | setosa,4.9,3.1,1.5,0.1 12 | setosa,5.4,3.7,1.5,0.2 13 | setosa,4.8,3.4,1.6,0.2 14 | setosa,4.8,3,1.4,0.1 15 | setosa,4.3,3,1.1,0.1 16 | setosa,5.8,4,1.2,0.2 17 | setosa,5.7,4.4,1.5,0.4 18 | setosa,5.4,3.9,1.3,0.4 19 | setosa,5.1,3.5,1.4,0.3 20 | setosa,5.7,3.8,1.7,0.3 21 | setosa,5.1,3.8,1.5,0.3 22 | setosa,5.4,3.4,1.7,0.2 23 | setosa,5.1,3.7,1.5,0.4 24 | setosa,4.6,3.6,1,0.2 25 | setosa,5.1,3.3,1.7,0.5 26 | setosa,4.8,3.4,1.9,0.2 27 | setosa,5,3,1.6,0.2 28 | setosa,5,3.4,1.6,0.4 29 | setosa,5.2,3.5,1.5,0.2 30 | setosa,5.2,3.4,1.4,0.2 31 | setosa,4.7,3.2,1.6,0.2 32 | setosa,4.8,3.1,1.6,0.2 33 | setosa,5.4,3.4,1.5,0.4 34 | setosa,5.2,4.1,1.5,0.1 35 | setosa,5.5,4.2,1.4,0.2 36 | setosa,4.9,3.1,1.5,0.2 37 | setosa,5,3.2,1.2,0.2 38 | setosa,5.5,3.5,1.3,0.2 39 | setosa,4.9,3.6,1.4,0.1 40 | setosa,4.4,3,1.3,0.2 41 | setosa,5.1,3.4,1.5,0.2 42 | setosa,5,3.5,1.3,0.3 43 | setosa,4.5,2.3,1.3,0.3 44 | setosa,4.4,3.2,1.3,0.2 45 | setosa,5,3.5,1.6,0.6 46 | setosa,5.1,3.8,1.9,0.4 47 | setosa,4.8,3,1.4,0.3 48 | setosa,5.1,3.8,1.6,0.2 49 | setosa,4.6,3.2,1.4,0.2 50 | setosa,5.3,3.7,1.5,0.2 51 | setosa,5,3.3,1.4,0.2 52 | versicolor,7,3.2,4.7,1.4 53 | versicolor,6.4,3.2,4.5,1.5 54 | versicolor,6.9,3.1,4.9,1.5 55 | versicolor,5.5,2.3,4,1.3 56 | versicolor,6.5,2.8,4.6,1.5 57 | versicolor,5.7,2.8,4.5,1.3 58 | versicolor,6.3,3.3,4.7,1.6 59 | versicolor,4.9,2.4,3.3,1 60 | versicolor,6.6,2.9,4.6,1.3 61 | versicolor,5.2,2.7,3.9,1.4 62 | versicolor,5,2,3.5,1 63 | versicolor,5.9,3,4.2,1.5 64 | versicolor,6,2.2,4,1 65 | versicolor,6.1,2.9,4.7,1.4 66 | versicolor,5.6,2.9,3.6,1.3 67 | versicolor,6.7,3.1,4.4,1.4 68 | versicolor,5.6,3,4.5,1.5 69 | versicolor,5.8,2.7,4.1,1 70 | versicolor,6.2,2.2,4.5,1.5 71 | versicolor,5.6,2.5,3.9,1.1 72 | versicolor,5.9,3.2,4.8,1.8 73 | versicolor,6.1,2.8,4,1.3 74 | versicolor,6.3,2.5,4.9,1.5 75 | versicolor,6.1,2.8,4.7,1.2 76 | versicolor,6.4,2.9,4.3,1.3 77 | versicolor,6.6,3,4.4,1.4 78 | versicolor,6.8,2.8,4.8,1.4 79 | versicolor,6.7,3,5,1.7 80 | versicolor,6,2.9,4.5,1.5 81 | versicolor,5.7,2.6,3.5,1 82 | versicolor,5.5,2.4,3.8,1.1 83 | versicolor,5.5,2.4,3.7,1 84 | versicolor,5.8,2.7,3.9,1.2 85 | versicolor,6,2.7,5.1,1.6 86 | versicolor,5.4,3,4.5,1.5 87 | versicolor,6,3.4,4.5,1.6 88 | versicolor,6.7,3.1,4.7,1.5 89 | versicolor,6.3,2.3,4.4,1.3 90 | versicolor,5.6,3,4.1,1.3 91 | versicolor,5.5,2.5,4,1.3 92 | versicolor,5.5,2.6,4.4,1.2 93 | versicolor,6.1,3,4.6,1.4 94 | versicolor,5.8,2.6,4,1.2 95 | versicolor,5,2.3,3.3,1 96 | versicolor,5.6,2.7,4.2,1.3 97 | versicolor,5.7,3,4.2,1.2 98 | versicolor,5.7,2.9,4.2,1.3 99 | versicolor,6.2,2.9,4.3,1.3 100 | versicolor,5.1,2.5,3,1.1 101 | versicolor,5.7,2.8,4.1,1.3 102 | virginica,6.3,3.3,6,2.5 103 | virginica,5.8,2.7,5.1,1.9 104 | virginica,7.1,3,5.9,2.1 105 | virginica,6.3,2.9,5.6,1.8 106 | virginica,6.5,3,5.8,2.2 107 | virginica,7.6,3,6.6,2.1 108 | virginica,4.9,2.5,4.5,1.7 109 | virginica,7.3,2.9,6.3,1.8 110 | virginica,6.7,2.5,5.8,1.8 111 | virginica,7.2,3.6,6.1,2.5 112 | virginica,6.5,3.2,5.1,2 113 | virginica,6.4,2.7,5.3,1.9 114 | virginica,6.8,3,5.5,2.1 115 | virginica,5.7,2.5,5,2 116 | virginica,5.8,2.8,5.1,2.4 117 | virginica,6.4,3.2,5.3,2.3 118 | virginica,6.5,3,5.5,1.8 119 | virginica,7.7,3.8,6.7,2.2 120 | virginica,7.7,2.6,6.9,2.3 121 | virginica,6,2.2,5,1.5 122 | virginica,6.9,3.2,5.7,2.3 123 | virginica,5.6,2.8,4.9,2 124 | virginica,7.7,2.8,6.7,2 125 | virginica,6.3,2.7,4.9,1.8 126 | virginica,6.7,3.3,5.7,2.1 127 | virginica,7.2,3.2,6,1.8 128 | virginica,6.2,2.8,4.8,1.8 129 | virginica,6.1,3,4.9,1.8 130 | virginica,6.4,2.8,5.6,2.1 131 | virginica,7.2,3,5.8,1.6 132 | virginica,7.4,2.8,6.1,1.9 133 | virginica,7.9,3.8,6.4,2 134 | virginica,6.4,2.8,5.6,2.2 135 | virginica,6.3,2.8,5.1,1.5 136 | virginica,6.1,2.6,5.6,1.4 137 | virginica,7.7,3,6.1,2.3 138 | virginica,6.3,3.4,5.6,2.4 139 | virginica,6.4,3.1,5.5,1.8 140 | virginica,6,3,4.8,1.8 141 | virginica,6.9,3.1,5.4,2.1 142 | virginica,6.7,3.1,5.6,2.4 143 | virginica,6.9,3.1,5.1,2.3 144 | virginica,5.8,2.7,5.1,1.9 145 | virginica,6.8,3.2,5.9,2.3 146 | virginica,6.7,3.3,5.7,2.5 147 | virginica,6.7,3,5.2,2.3 148 | virginica,6.3,2.5,5,1.9 149 | virginica,6.5,3,5.2,2 150 | virginica,6.2,3.4,5.4,2.3 151 | virginica,5.9,3,5.1,1.8 152 | -------------------------------------------------------------------------------- /demo/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |19 | Explore matrix data (samples as rows, observations as columns) using 20 | scatterplot matrix. Simply supply a CSV with a header. For example: 21 |
22 | 23 |24 | <script type="text/javascript"> 25 | var sm = new ScatterMatrix('http://my.domain/data.csv'); 26 | sm.render(); 27 | </script> 28 |29 | 30 | 31 | 32 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /demo/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjiec/scatter-matrix/f8340aca14d6165d8b59f9896a7b7df0f270a376/demo/demo.png -------------------------------------------------------------------------------- /scatter-matrix.css: -------------------------------------------------------------------------------- 1 | svg { font-size: 14px; } 2 | 3 | .axis { shape-rendering: crispEdges; } 4 | .axis line { stroke: #ddd; stroke-width: 1px; } 5 | .axis path { display: none; } 6 | .axis text { font-size: 0.8em; } 7 | 8 | rect.extent { fill: #000; fill-opacity: .125; stroke: #fff; } 9 | rect.frame { fill: #fff; fill-opacity: .7; stroke: #aaa; } 10 | 11 | circle { fill: #ccc; fill-opacity: .5; } 12 | 13 | .legend circle { fill-opacity: 1; } 14 | .legend text { font-size: 18px; font-style: oblique; } 15 | 16 | .cell text { pointer-events: none; } 17 | 18 | .color-0 { fill: #800; } 19 | .color-1 { fill: #080; } 20 | .color-2 { fill: #008; } 21 | .color-3 { fill: #440; } 22 | .color-4 { fill: #044; } 23 | .color-5 { fill: #404; } 24 | .color-6 { fill: #400; } 25 | .color-7 { fill: #040; } 26 | .color-8 { fill: #004; } 27 | .color-9 { fill: #cc0; } 28 | .color-10 { fill: #0cc; } 29 | .color-11 { fill: #c0c; } 30 | .color-12 { fill: #880; } 31 | .color-13 { fill: #088; } 32 | .color-14 { fill: #808; } 33 | .color-15 { fill: #c00; } 34 | .color-16 { fill: #0c0; } 35 | .color-17 { fill: #00c; } 36 | 37 | .scatter-matrix-filter-control ul { list-style: none; padding-left: 10px; } 38 | .scatter-matrix-variable-control ul { list-style: none; padding-left: 10px; } 39 | .scatter-matrix-drill-control ul { list-style: none; padding-left: 10px; } 40 | 41 | -------------------------------------------------------------------------------- /scatter-matrix.js: -------------------------------------------------------------------------------- 1 | // Heavily influenced by Mike Bostock's Scatter Matrix example 2 | // http://mbostock.github.io/d3/talk/20111116/iris-splom.html 3 | // 4 | 5 | ScatterMatrix = function(url, data, dom_id, el) { 6 | this.__url = url; 7 | if (data === undefined || data === null) { this.__data = undefined; } 8 | else { this.__data = d3.csv.parse(data); } 9 | this.__cell_size = 140; 10 | if (dom_id === undefined) { this.__dom_id = 'body'; } 11 | else { this.__dom_id = "#"+dom_id; } 12 | if (el) 13 | this.__dom_id = el; 14 | }; 15 | 16 | ScatterMatrix.prototype.cellSize = function(n) { 17 | this.__cell_size = n; 18 | return this; 19 | }; 20 | 21 | ScatterMatrix.prototype.onData = function(cb) { 22 | if (this.__data) { cb(); return; } 23 | var self = this; 24 | d3.csv(self.__url, function(data) { 25 | self.__data = data; 26 | cb(); 27 | }); 28 | }; 29 | 30 | ScatterMatrix.prototype._numeric_to_str_key = function(k) { return k+'_'; }; 31 | ScatterMatrix.prototype._is_numeric_str_key = function(k) { return k[k.length-1] === '_'; }; 32 | ScatterMatrix.prototype._str_to_numeric_key = function(k) { 33 | if (this._is_numeric_str_key(k)) { return k.slice(0, k.length-1); } 34 | return null; 35 | }; 36 | 37 | ScatterMatrix.prototype.render = function () { 38 | var self = this; 39 | 40 | var container = d3.select(this.__dom_id).append('div') 41 | .attr('class', 'scatter-matrix-container'); 42 | var control = container.append('div') 43 | .attr('class', 'scatter-matrix-control'); 44 | var svg = container.append('div') 45 | .attr('class', 'scatter-matrix-svg') 46 | .html('Loading data...'); 47 | 48 | this.onData(function() { 49 | var data = self.__data; 50 | 51 | // Divide variables into string and numeric variables 52 | 53 | var string_variables = []; 54 | var original_numeric_variables = []; 55 | self.__string_variable_values = {}; 56 | self.__numeric_variables = []; 57 | 58 | for (k in data[0]) { 59 | var is_numeric = true; 60 | data.forEach(function(d) { 61 | var v = d[k]; 62 | if (isNaN(+v)) is_numeric = false; 63 | }); 64 | if (is_numeric) { 65 | self.__numeric_variables.push(k); 66 | original_numeric_variables.push(k); 67 | } 68 | else { 69 | string_variables.push(k); 70 | self.__string_variable_values[k] = []; 71 | } 72 | } 73 | 74 | // For string variables, make a numeric counterpart that has as value the 75 | // index of the value 76 | 77 | data.forEach(function(d) { 78 | for (var j in string_variables) { 79 | var k = string_variables[j]; 80 | var value = d[k]; 81 | if (self.__string_variable_values[k].indexOf(value) < 0) 82 | self.__string_variable_values[k].push(value); 83 | } 84 | }); 85 | 86 | // sort, then assign index 87 | for (var j in string_variables) { 88 | var k = string_variables[j]; 89 | self.__string_variable_values[k].sort(); 90 | } 91 | data.forEach(function(d) { 92 | for (var j in string_variables) { 93 | var k = string_variables[j]; 94 | var value = d[k]; 95 | var index = self.__string_variable_values[k].indexOf(value); 96 | d[self._numeric_to_str_key(k)] = index; 97 | } 98 | }); 99 | 100 | for (var j in string_variables) { 101 | var k = string_variables[j]; 102 | self.__numeric_variables.push(self._numeric_to_str_key(k)); 103 | } 104 | 105 | // Add controls on the left 106 | 107 | var size_control = control.append('div').attr('class', 'scatter-matrix-size-control'); 108 | var color_control = control.append('div').attr('class', 'scatter-matrix-color-control'); 109 | var filter_control = control.append('div').attr('class', 'scatter-matrix-filter-control'); 110 | var variable_control = control.append('div').attr('class', 'scatter-matrix-variable-control'); 111 | var drill_control = control.append('div').attr('class', 'scatter-matrix-drill-control'); 112 | 113 | // shared control states 114 | var to_include = self.__numeric_variables.slice(0, 5); 115 | var color_variable = undefined; 116 | var selected_colors = undefined; 117 | var drill_variables = []; 118 | 119 | function set_filter(variable) { 120 | filter_control.selectAll('*').remove(); 121 | if (variable) { 122 | // Get unique values for this variable 123 | var values = []; 124 | data.forEach(function(d) { 125 | var v = d[variable]; 126 | if (values.indexOf(v) < 0) { values.push(v); } 127 | }); 128 | 129 | selected_colors = values.slice(0, 5); 130 | 131 | var filter_li = 132 | filter_control 133 | .append('p').text('Filter by '+variable+': ') 134 | .append('ul') 135 | .selectAll('li') 136 | .data(values) 137 | .enter().append('li'); 138 | 139 | filter_li.append('input') 140 | .attr('type', 'checkbox') 141 | .attr('checked', function(d, i) { 142 | if (selected_colors.indexOf(d) >= 0) 143 | return 'checked'; 144 | return null; 145 | }) 146 | .on('click', function(d, i) { 147 | var new_selected_colors = []; 148 | for (var j in selected_colors) { 149 | var v = selected_colors[j]; 150 | if (v !== d || this.checked) { new_selected_colors.push(v); } 151 | } 152 | if (this.checked) { new_selected_colors.push(d); } 153 | selected_colors = new_selected_colors; 154 | self.__draw(self.__cell_size, svg, color_variable, 155 | selected_colors, to_include, drill_variables); 156 | }); 157 | filter_li.append('label') 158 | .html(function(d) { return d; }); 159 | } 160 | } 161 | 162 | size_a = size_control.append('p').text('Change plot size: '); 163 | size_a.append('a') 164 | .attr('href', 'javascript:void(0);') 165 | .html('-') 166 | .on('click', function() { 167 | self.__cell_size *= 0.75; 168 | self.__draw(self.__cell_size, svg, color_variable, selected_colors, to_include, drill_variables); 169 | }); 170 | size_a.append('span').html(' '); 171 | size_a.append('a') 172 | .attr('href', 'javascript:void(0);') 173 | .html('+') 174 | .on('click', function() { 175 | self.__cell_size *= 1.25; 176 | self.__draw(self.__cell_size, svg, color_variable, selected_colors, to_include, drill_variables); 177 | }); 178 | 179 | color_control.append('p').text('Select a variable to color:'); 180 | color_control 181 | .append('ul') 182 | .selectAll('li') 183 | .data([undefined].concat(string_variables)) 184 | .enter().append('li') 185 | .append('a') 186 | .attr('href', 'javascript:void(0);') 187 | .text(function(d) { return d ? d : 'None'; }) 188 | .on('click', function(d, i) { 189 | color_variable = d; 190 | set_filter(d); 191 | self.__draw(self.__cell_size, svg, color_variable, selected_colors, to_include, drill_variables); 192 | }); 193 | 194 | var variable_li = 195 | variable_control 196 | .append('p').text('Include variables: ') 197 | .append('ul') 198 | .selectAll('li') 199 | .data(self.__numeric_variables) 200 | .enter().append('li'); 201 | 202 | variable_li.append('input') 203 | .attr('type', 'checkbox') 204 | .attr('checked', function(d, i) { if (to_include.indexOf(d) >= 0) return "checked"; return null; }) 205 | .on('click', function(d, i) { 206 | var new_to_include = []; 207 | for (var j in to_include) { 208 | var v = to_include[j]; 209 | if (v !== d || this.checked) { new_to_include.push(v); } 210 | } 211 | if (this.checked) { new_to_include.push(d); } 212 | to_include = new_to_include; 213 | self.__draw(self.__cell_size, svg, color_variable, selected_colors, to_include, drill_variables); 214 | }); 215 | variable_li.append('label') 216 | .html(function(d) { 217 | var i = self.__numeric_variables.indexOf(d)+1; 218 | return ''+i+': '+d; 219 | }); 220 | 221 | drill_li = 222 | drill_control 223 | .append('p').text('Drill and Expand: ') 224 | .append('ul') 225 | .selectAll('li') 226 | .data(original_numeric_variables.concat(string_variables)) 227 | .enter().append('li'); 228 | 229 | drill_li.append('input') 230 | .attr('type', 'checkbox') 231 | .on('click', function(d, i) { 232 | var new_drill_variables = []; 233 | for (var j in drill_variables) { 234 | var v = drill_variables[j]; 235 | if (v !== d || this.checked) { new_drill_variables.push(v); } 236 | } 237 | if (this.checked) { new_drill_variables.push(d); } 238 | drill_variables = new_drill_variables; 239 | self.__draw(self.__cell_size, svg, color_variable, selected_colors, to_include, drill_variables); 240 | }); 241 | drill_li.append('label') 242 | .html(function(d) { return d; }); 243 | 244 | self.__draw(self.__cell_size, svg, color_variable, selected_colors, to_include, drill_variables); 245 | }); 246 | }; 247 | 248 | ScatterMatrix.prototype.__draw = 249 | function(cell_size, container_el, color_variable, selected_colors, to_include, drill_variables) { 250 | var self = this; 251 | this.onData(function() { 252 | var data = self.__data; 253 | 254 | // filter data by selected colors 255 | if (color_variable && selected_colors) { 256 | data = []; 257 | self.__data.forEach(function(d) { 258 | if (selected_colors.indexOf(d[color_variable]) >= 0) { data.push(d); } 259 | }); 260 | } 261 | 262 | container_el.selectAll('*').remove(); 263 | 264 | // If no data, don't do anything 265 | if (data.length == 0) { return; } 266 | 267 | // Parse headers from first row of data 268 | var variables_to_draw = to_include.slice(0); 269 | 270 | // Get values of the string variable 271 | var colors = []; 272 | if (color_variable) { 273 | // Using self.__data (all data), instead of data (data to be drawn), so 274 | // our css classes are consistent when we filter by value. 275 | self.__data.forEach(function(d) { 276 | var s = d[color_variable]; 277 | if (colors.indexOf(s) < 0) { colors.push(s); } 278 | }); 279 | } 280 | 281 | function color_class(d) { 282 | var c = d; 283 | if (color_variable && d[color_variable]) { c = d[color_variable]; } 284 | return colors.length > 0 ? 'color-'+colors.indexOf(c) : 'color-2'; 285 | } 286 | 287 | // Size parameters 288 | var size = cell_size, padding = 10, 289 | axis_width = 20, axis_height = 15, legend_width = 200, label_height = 15; 290 | 291 | // Get x and y scales for each numeric variable 292 | var x = {}, y = {}; 293 | variables_to_draw.forEach(function(trait) { 294 | // Coerce values to numbers. 295 | data.forEach(function(d) { d[trait] = +d[trait]; }); 296 | 297 | var value = function(d) { return d[trait]; }, 298 | domain = [d3.min(data, value), d3.max(data, value)], 299 | range_x = [padding / 2, size - padding / 2], 300 | range_y = [padding / 2, size - padding / 2]; 301 | 302 | x[trait] = d3.scale.linear().domain(domain).range(range_x); 303 | y[trait] = d3.scale.linear().domain(domain).range(range_y.reverse()); 304 | }); 305 | 306 | // When drilling, user select one or more variables. The first drilled 307 | // variable becomes the x-axis variable for all columns, and each column 308 | // contains only data points that match specific values for each of the 309 | // drilled variables other than the first. 310 | 311 | var drill_values = []; 312 | var drill_degrees = [] 313 | drill_variables.forEach(function(variable) { 314 | // Skip first one, since that's just the x axis 315 | if (drill_values.length == 0) { 316 | drill_values.push([]); 317 | drill_degrees.push(1); 318 | } 319 | else { 320 | var values = []; 321 | data.forEach(function(d) { 322 | var v = d[variable]; 323 | if (v !== undefined && values.indexOf(v) < 0) { values.push(v); } 324 | }); 325 | values.sort(); 326 | drill_values.push(values); 327 | drill_degrees.push(values.length); 328 | } 329 | }); 330 | var total_columns = 1; 331 | drill_degrees.forEach(function(d) { total_columns *= d; }); 332 | 333 | // Pick out stuff to draw on horizontal and vertical dimensions 334 | 335 | if (drill_variables.length > 0) { 336 | // First drill is now the x-axis variable for all columns 337 | x_variables = []; 338 | for (var i=0; i