A 2D/3D heatmap of the iris dataset.
Click and drag to zoom. Single click to zoom out.
A 2D/3D heatmap of the iris dataset.
Click and drag to zoom. Single click to zoom out.
| var candidateIntervals = [2, 2.5, 5, 10]; | |
| function prettyRange (initRange, size) { | |
| var minimum = initRange[0]; | |
| var maximum = initRange[1]; | |
| if (minimum != maximum) { | |
| var diff = (maximum - minimum) / size; | |
| var tens = Math.floor(Math.log10(diff)); | |
| var tscale = Math.pow(10, tens); | |
| var rem = diff / tscale; | |
| var minIndex; | |
| var minDiff = 1E20; | |
| for (i in candidateIntervals) { | |
| var c = candidateIntervals[i]; | |
| var cDiff = c - rem; | |
| if (cDiff > 0 && cDiff < minDiff) { | |
| minDiff = cDiff; | |
| minIndex = i; | |
| } | |
| } | |
| var interval = candidateIntervals[minIndex] * tscale; | |
| var newMin = interval * Math.floor(minimum / interval); | |
| var newMax = newMin + interval * size; | |
| if (newMax < maximum) { | |
| return prettyRange([newMin, newMax], size); | |
| } else if (newMax == maximum) { | |
| if (newMax == 0) { | |
| newMax = 1E-8; | |
| } else { | |
| newMax = newMax * 1.000001; | |
| } | |
| return prettyRange([newMin, newMax], size); | |
| } else { | |
| return [newMin, newMax]; | |
| } | |
| } else { | |
| return [minimum, maximum]; | |
| } | |
| } | |
| function truncate (val) { | |
| return Math.round(1E5 * val) / 1E5; | |
| } | |
| function pickEdges(range, size) { | |
| var newRange = prettyRange(range, size); | |
| var step = truncate((newRange[1] - newRange[0]) / size); | |
| var edges = []; | |
| var truncatedEnd = truncate(range[1]); | |
| for (var i = 0; i <= size; i++) { | |
| var prevEdge = truncate(newRange[0] + (step * (i - 1))); | |
| var edge = truncate(prevEdge + step); | |
| if (prevEdge <= truncatedEnd) { | |
| edges.push(edge); | |
| } | |
| } | |
| return {step: step, edges: edges, range: [edges[0], edges[edges.length - 1]]}; | |
| } | |
| function findField(selection, data) { | |
| if (selection.id == null) return null; | |
| var fieldIds = data.fields.map(function (field) {return field.id;}); | |
| var column = fieldIds.indexOf(selection.id); | |
| data.fields[column].column = column; | |
| return data.fields[column]; | |
| } | |
| function makeAxisIndexer (selection, data, numBins, catBins) { | |
| var field = findField(selection, data); | |
| var range = selection.range; | |
| if (range == null) { | |
| if (field.optype == "numeric") { | |
| range = [field.minimum, field.maximum]; | |
| } else { | |
| range = field.categories; | |
| if (range.length > catBins) { | |
| range = range.slice(0, catBins); | |
| } | |
| } | |
| } | |
| var axis; | |
| if (field.optype == "numeric") { | |
| axis = pickEdges(range, numBins); | |
| axis.binCount = axis.edges.length - 1; | |
| var edges = axis.edges; | |
| var minRange = edges[0]; | |
| var maxRange = edges[edges.length - 1]; | |
| var step = axis.step; | |
| axis.indexer = function (val) { | |
| var index = -1; | |
| if (val != null && val >= minRange && val <= maxRange) { | |
| index = Math.floor(truncate((val - minRange) / step)); | |
| } | |
| return index; | |
| } | |
| } else { | |
| axis = {range: range, binCount: range.length}; | |
| var indexMap = field.categories.map(function (cat) { return range.indexOf(cat)}); | |
| axis.indexer = function (val) { | |
| var index = -1; | |
| if (val != null) { | |
| index = indexMap[val];; | |
| } | |
| return index; | |
| } | |
| } | |
| axis.optype = field.optype; | |
| axis.column = field.column; | |
| return axis; | |
| } | |
| function genHeatmap(data, xSelection, ySelection, cSelection, numBins, catBins) { | |
| var colorField = findField(cSelection, data); | |
| var xIndexInfo = makeAxisIndexer(xSelection, data, numBins, catBins); | |
| var xNumeric = xIndexInfo.optype == "numeric"; | |
| var xIndexer = xIndexInfo.indexer; | |
| var yIndexInfo = makeAxisIndexer(ySelection, data, numBins, catBins); | |
| var yNumeric = yIndexInfo.optype == "numeric"; | |
| var yIndexer = yIndexInfo.indexer; | |
| var counts = []; | |
| for (var i = 0; i < xIndexInfo.binCount; i++) { | |
| counts.push(new Int32Array(yIndexInfo.binCount)); | |
| } | |
| var fullCatCounts = null; | |
| var catCounts = null; | |
| var sums = null; | |
| var targetCat = null; | |
| if (cSelection.targetCat != null) { | |
| targetCat = colorField.categories.indexOf(cSelection.targetCat); | |
| } | |
| if (colorField != null) { | |
| if (colorField.optype == "numeric") { | |
| sums = []; | |
| for (var i = 0; i < xIndexInfo.binCount; i++) { | |
| sums.push(new Float64Array(yIndexInfo.binCount)); | |
| } | |
| } else { | |
| if (targetCat != null) { | |
| catCounts = []; | |
| for (var i = 0; i < xIndexInfo.binCount; i++) { | |
| catCounts.push(new Int32Array(yIndexInfo.binCount)); | |
| } | |
| } else { | |
| var cats = colorField.categories.length; | |
| fullCatCounts = []; | |
| for (var i = 0; i < xIndexInfo.binCount; i++) { | |
| var row = []; | |
| for (var j = 0; j < yIndexInfo.binCount; j++) { | |
| row.push(new Int32Array(cats)); | |
| } | |
| fullCatCounts.push(row); | |
| } | |
| } | |
| } | |
| } | |
| var xData = data.data[xIndexInfo.column]; | |
| var yData = data.data[yIndexInfo.column]; | |
| var cData = null; | |
| if (colorField != null) { | |
| cData = data.data[colorField.column]; | |
| } | |
| var maxBinCount = 0; | |
| var totalCount = 0; | |
| var rowCounts = new Int32Array(xIndexInfo.binCount); | |
| var colCounts = new Int32Array(yIndexInfo.binCount); | |
| for (i in xData) { | |
| var x = xIndexer(xData[i]); | |
| var y = yIndexer(yData[i]); | |
| if (x >= 0 && x < xIndexInfo.binCount && y >= 0 && y < yIndexInfo.binCount) { | |
| if (cData != null && cData[i] == null) { | |
| continue; | |
| } | |
| counts[x][y]++; | |
| rowCounts[x]++; | |
| colCounts[y]++; | |
| totalCount++; | |
| maxBinCount = Math.max(maxBinCount, counts[x][y]); | |
| if (cData != null) { | |
| if (sums != null && cData[i]) { | |
| sums[x][y] += cData[i]; | |
| } | |
| if (catCounts != null && cData[i] == targetCat) { | |
| catCounts[x][y]++; | |
| } | |
| if (fullCatCounts != null) { | |
| fullCatCounts[x][y][cData[i]]++; | |
| } | |
| } | |
| } | |
| } | |
| var result = {x: xIndexInfo, | |
| y: yIndexInfo, | |
| c: colorField, | |
| totalCount: totalCount, | |
| counts: counts}; | |
| if (sums != null) { | |
| result.sums = sums; | |
| } | |
| if (catCounts != null) { | |
| result.catCounts = catCounts; | |
| } | |
| if (fullCatCounts != null) { | |
| catCounts = []; | |
| var mostCommon = []; | |
| for (var x = 0; x < xIndexInfo.binCount; x++) { | |
| mostCommon.push(new Int16Array(yIndexInfo.binCount)); | |
| catCounts.push(new Int32Array(yIndexInfo.binCount)); | |
| for (var y = 0; y < yIndexInfo.binCount; y++) { | |
| var dist = fullCatCounts[x][y]; | |
| var maxCat = -1; | |
| var maxCount = -1; | |
| for (var z = 0; z < dist.length; z++) { | |
| if (dist[z] > maxCount) { | |
| maxCat = z; | |
| maxCount = dist[z]; | |
| } | |
| } | |
| mostCommon[x][y] = maxCat; | |
| catCounts[x][y] = maxCount; | |
| } | |
| } | |
| result.catCounts = catCounts; | |
| result.mostCommon = mostCommon; | |
| } | |
| return result; | |
| } | |
| function differenceSum(counts, xNumeric, yNumeric) { | |
| var rows = counts.length; | |
| var cols = counts[0].length; | |
| var total = 0; | |
| var diff = 0; | |
| for (var i = 0; i < rows; i++) { | |
| for (var j = 0; j < cols; j++) { | |
| var pop = counts[i][j]; | |
| total += pop; | |
| if (xNumeric) { | |
| if (i < rows - 1) { | |
| diff += Math.abs(pop - counts[i+1][j]); | |
| } else { | |
| diff += pop; | |
| } | |
| if (xNumeric && i > 0) { | |
| diff += Math.abs(pop - counts[i-1][j]); | |
| } else { | |
| diff += pop; | |
| } | |
| } | |
| if (yNumeric) { | |
| if (j < cols - 1) { | |
| diff += Math.abs(pop - counts[i][j+1]); | |
| } else { | |
| diff += pop; | |
| } | |
| if (j > 0) { | |
| diff += Math.abs(pop - counts[i][j-1]); | |
| } else { | |
| diff += pop; | |
| } | |
| } | |
| } | |
| } | |
| return diff / total; | |
| } | |
| function heatmap(data, xSelection, ySelection, cSelection, opts) { | |
| var xNumeric = findField(xSelection, data).optype == "numeric"; | |
| var yNumeric = findField(ySelection, data).optype == "numeric"; | |
| if (opts.numBins != null || (!xNumeric && !yNumeric)) { | |
| return genHeatmap(data, xSelection, ySelection, cSelection, | |
| opts.numBins, opts.catBins); | |
| } else { | |
| var binCandidates; | |
| if (xNumeric && yNumeric) { | |
| binCandidates = [256, 128, 64, 32, 16, 8, 4]; | |
| } else { | |
| binCandidates = [128, 64, 32, 16, 8, 4]; | |
| } | |
| var minScore = null; | |
| var bestResult = null; | |
| for (i in binCandidates) { | |
| var result = genHeatmap(data, xSelection, ySelection, cSelection, | |
| binCandidates[i], opts.catBins); | |
| var score = differenceSum(result.counts, xNumeric, yNumeric); | |
| if (minScore == null || minScore >= score) { | |
| minScore = score; | |
| bestResult = result; | |
| } | |
| } | |
| } | |
| return bestResult; | |
| } |
| function cartesianProduct() { | |
| var args = [].slice.call(arguments); | |
| var end = args.length - 1; | |
| var result = []; | |
| function addTo(curr, start) { | |
| var first = args[start]; | |
| var last = (start === end); | |
| for (var i = 0; i < first.length; ++i) { | |
| var copy = curr.slice(); | |
| copy.push(first[i]); | |
| if (last) { | |
| result.push(copy); | |
| } else { | |
| addTo(copy, start + 1); | |
| } | |
| } | |
| } | |
| if (args.length) { | |
| addTo([], 0); | |
| } else { | |
| result.push([]); | |
| } | |
| return result; | |
| } | |
| function range (v) { | |
| return Array.apply(null, Array(v)).map(function (_, i) {return i;}) | |
| } | |
| function binList (grid) { | |
| return cartesianProduct(range(grid.x.binCount), range(grid.y.binCount)); | |
| } |
| </script> | |
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| .domain { | |
| stroke: none; | |
| fill: none; | |
| } | |
| .field { | |
| cursor: pointer; | |
| -moz-user-select: none; | |
| -khtml-user-select: none; | |
| -webkit-user-select: none; | |
| -ms-user-select: none; | |
| user-select: none; | |
| } | |
| .brush .extent { | |
| stroke: #fff; | |
| fill-opacity: .125; | |
| shape-rendering: crispEdges; | |
| } | |
| svg { | |
| -webkit-user-select: none; /* webkit (safari, chrome) browsers */ | |
| -moz-user-select: none; /* mozilla browsers */ | |
| -khtml-user-select: none; /* webkit (konqueror) browsers */ | |
| -ms-user-select: none; /* IE10+ */ | |
| } | |
| #shading-slider { | |
| width: 90px; | |
| } | |
| .selector { | |
| margin-bottom: 15px; | |
| } | |
| #axis { | |
| position:absolute; | |
| } | |
| #heatmap { | |
| } | |
| div#pdp { | |
| float: left; | |
| margin-right: 5px; | |
| } | |
| div#sidebar { | |
| padding-top: 20px; | |
| } | |
| div.field-info { | |
| margin-bottom: 10px; | |
| } | |
| div.field-name { | |
| color: #999; | |
| margin-right: 10px; | |
| } | |
| div.field-value { | |
| } | |
| </style> | |
| <body oncontextmenu="return false;"> | |
| <div id="pdp"></div> | |
| <div id="sidebar"> | |
| <form id="controls-form"> | |
| <div class="selector"> | |
| <div>X-Axis</div> | |
| <select id="x-select"></select> | |
| </div> | |
| <div class="selector"> | |
| <div>Y-Axis</div> | |
| <select id="y-select"></select> | |
| </div> | |
| <div class="selector"> | |
| <div>Color Field</div> | |
| <select id="color-select"></select> | |
| </div> | |
| <div class="selector"> | |
| <div>Focus Category</div> | |
| <select id="focus-select"></select> | |
| </div> | |
| <div class="selector"> | |
| <div>Density Shading</div> | |
| <input id="shading-slider" type="range" min="0" max="1" step="0.1" value="0.5"/> | |
| </div> | |
| <div class="selector"> | |
| <div>Max Bins</div> | |
| <select id="bins-select"> | |
| <option value="dynamic" selected>Dynamic</option> | |
| <option value="256">256</option> | |
| <option value="128">128</option> | |
| <option value="64">64</option> | |
| <option value="32"">32</option> | |
| <option value="16">16</option> | |
| <option value="8">8</option> | |
| </select> | |
| </div> | |
| </form> | |
| <div id="x-field" class="field-info"> | |
| <div id="x-name" class="field-name"></div> | |
| <div id="x-value" class="field-value"></div> | |
| </div> | |
| <div id="y-field" class="field-info"> | |
| <div id="y-name" class="field-name"></div> | |
| <div id="y-value" class="field-value"></div> | |
| </div> | |
| <div id="c-field" class="field-info"> | |
| <div id="c-name" class="field-name"></div> | |
| <div id="c-value" class="field-value"></div> | |
| </div> | |
| </div> | |
| <script src="http://d3js.org/d3.v3.min.js"></script> | |
| <script src="iris.js"></script> | |
| <script src="data.js"></script> | |
| <script src="grid.js"></script> | |
| <script> | |
| function toggle_option (selectId, index, disabled) { | |
| document.getElementById(selectId) | |
| .getElementsByTagName("option")[index].disabled = disabled; | |
| } | |
| function getParam(key) { | |
| if(key=(new RegExp('[?&]'+encodeURIComponent(key)+'=([^&]*)')) | |
| .exec(location.search)) | |
| return decodeURIComponent(key[1]); | |
| } | |
| function setParam(key, value) { | |
| key = encodeURI(key); value = encodeURI(value); | |
| var s = document.location.search; | |
| var kvp = key+"="+value; | |
| var r = new RegExp("(&|\\?)"+key+"=[^\&]*"); | |
| s = s.replace(r,"$1"+kvp); | |
| if(!RegExp.$1) {s += (s.length>0 ? '&' : '?') + kvp;}; | |
| window.history.replaceState({}, "", s); | |
| } | |
| function removeParam(key) { | |
| var sourceURL = document.location.search; | |
| var rtn = sourceURL.split("?")[0], | |
| param, | |
| params_arr = [], | |
| queryString = (sourceURL.indexOf("?") !== -1) ? sourceURL.split("?")[1] : ""; | |
| if (queryString !== "") { | |
| params_arr = queryString.split("&"); | |
| for (var i = params_arr.length - 1; i >= 0; i -= 1) { | |
| param = params_arr[i].split("=")[0]; | |
| if (param === key) { | |
| params_arr.splice(i, 1); | |
| } | |
| } | |
| rtn = rtn + "?" + params_arr.join("&"); | |
| } | |
| window.history.replaceState({}, "", rtn); | |
| } | |
| var margin = {bottom: 40, left: 50, right: 20, top: 20}; | |
| var width = 640 - margin.left; | |
| var height = 480 - margin.bottom; | |
| var axisSVG = d3.select("#pdp").append("svg") | |
| .attr("id", "axis") | |
| .attr("width", width + margin.left + margin.right) | |
| .attr("height", height + margin.bottom + margin.top) | |
| .append("g") | |
| .attr("transform", | |
| "translate(" + margin.left + "," + margin.top + ")"); | |
| var canvas = d3.select("#pdp").append("canvas") | |
| .attr("id", "heatmap") | |
| .attr("width", width + margin.left + margin.right) | |
| .attr("height", height + margin.bottom + margin.top); | |
| var context = canvas.node().getContext("2d"); | |
| d3.json("iris-dataset.json", function(error, dataset) { | |
| if ('dataset' in dataset) { | |
| dataset = dataset.dataset; | |
| } | |
| var fields = dataset.fields; | |
| if (!Array.isArray(fields)){ | |
| var fieldsArr = []; | |
| for (fid in fields) { | |
| var field = fields[fid]; | |
| field.id = fid; | |
| fieldsArr.push(field); | |
| } | |
| fields = fieldsArr; | |
| } | |
| var maxNumBins = 128; | |
| var maxCatBins = 16; | |
| var x = getParam("x"); | |
| if (x == null) x = 2; | |
| x = Number(x); | |
| setParam("x", x); | |
| var y = getParam("y"); | |
| if (y == null) y = 3; | |
| y = Number(y); | |
| setParam("y", y); | |
| var c = getParam("c"); | |
| if (c == null) c = fields.length - 1; | |
| c = Number(c); | |
| setParam("c", c); | |
| var targetFields = [fields[x], fields[y]]; | |
| var xSelection = {id: fields[x].id}; | |
| var ySelection = {id: fields[y].id}; | |
| var cSelection = {}; | |
| if (c >= 0) cSelection.id = fields[c].id; | |
| var opts = {maxNumBins: maxNumBins, maxCatBins: maxCatBins}; | |
| var binCanvasHeight; | |
| var binCanvasWidth; | |
| var hm; | |
| var colorSelector = d3.select("#color-select"); | |
| colorSelector.append("option").attr("value", "#noValue").text("None"); | |
| colorSelector.selectAll(".c-option") | |
| .data(fields).enter() | |
| .append("option") | |
| .attr("class", "c-option") | |
| .attr("value", function(d) {return d.id;}) | |
| .text(function(d) {return d.name;}); | |
| document.getElementById("color-select").selectedIndex = c + 1; | |
| colorSelector.on("change", | |
| function () { | |
| c = this.selectedIndex - 1; | |
| setParam("c", c); | |
| updateColorSelector(); | |
| resetFocusSelector(); | |
| updateHeatMap(); | |
| updateColorFn(); | |
| redraw(true); | |
| }); | |
| updateColorSelector(); | |
| var focusSelector = d3.select("#focus-select"); | |
| focusSelector.append("option").attr("value", "#noValue").text("Most common"); | |
| focusSelector.on("change", | |
| function () { | |
| if (this.selectedIndex > 0) { | |
| cSelection.targetCat = | |
| fields[c].summary.categories[this.selectedIndex - 1][0]; | |
| } else { | |
| delete cSelection.targetCat; | |
| } | |
| setParam("focus", this.selectedIndex - 1); | |
| updateHeatMap(); | |
| updateColorFn(); | |
| redraw(false); | |
| }); | |
| updateFocusSelector(); | |
| var focusParam = getParam("focus"); | |
| if (focusParam == null) { | |
| focusParam = -1; | |
| } | |
| focusParam = Number(focusParam); | |
| document.getElementById("focus-select").selectedIndex = focusParam + 1; | |
| if (focusParam >= 0) { | |
| cSelection.targetCat = fields[c].summary.categories[focusParam][0]; | |
| } | |
| var binsIndex = getParam("bins"); | |
| var binSelect = document.getElementById("bins-select"); | |
| if (binsIndex == null) binsIndex = 0; | |
| binsIndex = Number(binsIndex); | |
| binSelect.selectedIndex = binsIndex; | |
| if (binsIndex > 0) { | |
| opts.numBins = Number(binSelect.options[binsIndex].value); | |
| } | |
| d3.select("#bins-select") | |
| .on("change", | |
| function () { | |
| if (this.value == "dynamic") { | |
| delete opts.numBins; | |
| } else { | |
| opts.numBins = this.value; | |
| } | |
| setParam("bins", this.selectedIndex); | |
| updateHeatMap(); | |
| updateColorFn(); | |
| redraw(true); | |
| }); | |
| updateHeatMap(); | |
| updateColorFn(); | |
| d3.select("#x-name").text(fields[x].name); | |
| d3.select("#x-value").text("-"); | |
| d3.select("#y-name").text(fields[y].name); | |
| d3.select("#y-value").text("-"); | |
| d3.select("#c-name").text("Total Count"); | |
| d3.select("#objective-value").text("-"); | |
| var xSelector = d3.select("#x-select"); | |
| xSelector.selectAll(".x-option") | |
| .data(fields).enter() | |
| .append("option") | |
| .attr("class", "x-option") | |
| .attr("value", function(d) {return d.id;}) | |
| .text(function(d) {return d.name;}); | |
| document.getElementById("x-select").selectedIndex = x; | |
| toggle_option("x-select", y, true); | |
| xSelector.on("change", | |
| function () { | |
| var oldX = x; | |
| var newX = this.selectedIndex; | |
| toggle_option("y-select", oldX, false); | |
| toggle_option("y-select", newX, true); | |
| document.getElementById("y-select") | |
| .getElementsByTagName("option")[newX].disabled = true; | |
| x = newX; | |
| setParam("x", x); | |
| d3.select("#x-name").text(fields[x].name); | |
| targetFields[0] = fields[x]; | |
| xSelection = {id: fields[x].id}; | |
| delete ySelection.range; | |
| updateHeatMap(); | |
| redraw(true); | |
| }); | |
| var ySelector = d3.select("#y-select"); | |
| ySelector.selectAll(".y-option") | |
| .data(fields).enter() | |
| .append("option") | |
| .attr("class", "y-option") | |
| .attr("value", function(d) {return d.id;}) | |
| .text(function(d) {return d.name;}); | |
| document.getElementById("y-select").selectedIndex = y; | |
| toggle_option("y-select", x, true); | |
| ySelector.on("change", | |
| function () { | |
| var oldY = y; | |
| var newY = this.selectedIndex; | |
| toggle_option("x-select", oldY, false); | |
| toggle_option("x-select", newY, true); | |
| document.getElementById("x-select") | |
| .getElementsByTagName("option")[newY].disabled = true; | |
| y = newY; | |
| setParam("y", y); | |
| d3.select("#y-name").text(fields[y].name); | |
| targetFields[1] = fields[y]; | |
| ySelection = {id: fields[y].id}; | |
| delete xSelection.range; | |
| updateHeatMap(); | |
| redraw(true); | |
| }); | |
| var shading = getParam("shading"); | |
| if (shading == null) { | |
| shading = 3; | |
| } | |
| shading = Number(shading) / 10; | |
| document.getElementById("shading-slider").value = shading; | |
| d3.select("#shading-slider") | |
| .on("input", | |
| function () { | |
| shading = this.value; | |
| setParam("shading", shading * 10); | |
| updateDensityFn(); | |
| updateCells(); | |
| }); | |
| var brushX = d3.scale.identity().domain([0, width]); | |
| var brushY = d3.scale.identity().domain([0, height]); | |
| function brushed() { | |
| brushExtent = brush.extent(); | |
| } | |
| function sortNumber(a,b) { | |
| return a - b; | |
| } | |
| function findRange(axis, canvasRange, scale, isX) { | |
| var newRange = []; | |
| if (axis.optype == "numeric") { | |
| newRange.push(scale.invert(canvasRange[0])); | |
| newRange.push(scale.invert(canvasRange[1])); | |
| newRange.sort(sortNumber); | |
| } else { | |
| var tempRange = []; | |
| if (isX) { | |
| tempRange.push(Math.floor(canvasRange[0] / binCanvasWidth)); | |
| tempRange.push(Math.floor(canvasRange[1] / binCanvasWidth)); | |
| } else { | |
| tempRange.push(Math.floor((height - canvasRange[0]) / binCanvasHeight)); | |
| tempRange.push(Math.floor((height - canvasRange[1]) / binCanvasHeight)); | |
| } | |
| tempRange.sort(sortNumber); | |
| newRange = axis.range.slice(tempRange[0], tempRange[1] + 1); | |
| } | |
| return newRange; | |
| } | |
| function brushended() { | |
| if (!d3.event.sourceEvent) return; // only transition after input | |
| var canvasX = [brushExtent[0][0], brushExtent[1][0]]; | |
| var canvasY = [brushExtent[0][1], brushExtent[1][1]]; | |
| if (canvasX[0] == canvasX[1] || canvasY[0] == canvasY[1]) { | |
| delete xSelection.range; | |
| delete ySelection.range; | |
| } else { | |
| xSelection.range = findRange(hm.x, canvasX, xScale, true); | |
| ySelection.range = findRange(hm.y, canvasY, yScale, false); | |
| } | |
| d3.select(this).call(brush.extent([[0, 0], [0, 0]])); | |
| updateHeatMap(); | |
| updateColorFn(); | |
| redraw(true); | |
| } | |
| var gx, gy; | |
| var xAxis, yAxis; | |
| var xScale; | |
| var yScale; | |
| var predictor; | |
| var densityFn; | |
| var pMin, pMax; | |
| makeAxis(); | |
| updateColorSelector(); | |
| redraw(true); | |
| var brushExtent; | |
| var brush = d3.svg.brush() | |
| .x(brushX) | |
| .y(brushY) | |
| .on("brush", brushed) | |
| .on("brushend", brushended); | |
| axisSVG.append("g") | |
| .attr("class", "brush") | |
| .on("mousemove", | |
| function(d) { | |
| var coords = d3.mouse(this); | |
| var inputs = []; | |
| var gridIndex = coordsToGridIndex(coords); | |
| var gx = gridIndex[0]; | |
| var gy = gridIndex[1]; | |
| if (hm.x.optype == "numeric") { | |
| inputs.push(xScale.invert(coords[0])); | |
| } else { | |
| inputs.push(hm.x.range[gx]); | |
| } | |
| if (hm.y.optype == "numeric") { | |
| inputs.push(yScale.invert(coords[1])); | |
| } else { | |
| inputs.push(hm.y.range[gy]); | |
| } | |
| var output; | |
| var xOut; | |
| var yOut; | |
| if (gx < hm.x.binCount && gy < hm.y.binCount) { | |
| if (c < 0) { | |
| output = prettyVal(hm.counts[gx][gy]); | |
| } else { | |
| if (fields[c].optype == "numeric") { | |
| var sum = hm.sums[gx][gy]; | |
| var pop = hm.counts[gx][gy]; | |
| if (pop == 0) { | |
| output = "No data"; | |
| } else { | |
| output = prettyVal(sum / pop) + " (" + pop + " points)"; | |
| } | |
| } else { | |
| var cat; | |
| if (cSelection.targetCat == null) { | |
| cat = hm.c.categories[hm.mostCommon[gx][gy]]; | |
| } else { | |
| cat = cSelection.targetCat; | |
| } | |
| var total = hm.counts[gx][gy]; | |
| if (total > 0) { | |
| var catCount = hm.catCounts[gx][gy]; | |
| if (catCount == 0) { | |
| cat = "---"; | |
| } | |
| output = cat + " (" + catCount + " of " + total + ")"; | |
| } else { | |
| output = "No data"; | |
| } | |
| } | |
| } | |
| xOut = binText(inputs[0], hm.x); | |
| yOut = binText(inputs[1], hm.y); | |
| } else { | |
| output = "-"; | |
| xOut = "-"; | |
| yOut = "-"; | |
| } | |
| d3.select("#x-value").text(prettyVal(xOut)); | |
| d3.select("#y-value").text(prettyVal(yOut)); | |
| d3.select("#c-value").text(output); | |
| }) | |
| .on("mouseout", function(d) { | |
| d3.select("#x-value").text("-"); | |
| d3.select("#y-value").text("-"); | |
| d3.select("#c-value").text("-"); | |
| }) | |
| .call(brush) | |
| .call(brush.event); | |
| function updateColorSelector() { | |
| if (c >= 0) { | |
| var text = fields[c].name; | |
| if (fields[c].optype == "numeric") { | |
| text += " (mean)"; | |
| } | |
| d3.select("#c-name").text(text); | |
| cSelection = {id: fields[c].id}; | |
| } else { | |
| d3.select("#c-name").text("Total Count"); | |
| cSelection = {}; | |
| } | |
| } | |
| function resetFocusSelector() { | |
| updateFocusSelector(); | |
| document.getElementById("focus-select").selectedIndex = 0; | |
| setParam("focus", -1); | |
| } | |
| function updateFocusSelector() { | |
| if (c >= 0 && fields[c].optype == "categorical") { | |
| var categories = fields[c].summary.categories.map(function (d) {return d[0];}); | |
| focusSelector.selectAll(".f-option").remove(); | |
| focusSelector.selectAll(".f-option") | |
| .data(categories).enter() | |
| .append("option") | |
| .attr("class", "f-option") | |
| .attr("value", function(d) {return categories.indexOf(d);}) | |
| .text(function(d) {return d}); | |
| document.getElementById("focus-select").disabled = false; | |
| } else { | |
| focusSelector.selectAll(".f-option").remove(); | |
| document.getElementById("focus-select").disabled = true; | |
| } | |
| } | |
| function binText(val, axis) { | |
| if (axis.optype == "numeric") { | |
| var index = axis.indexer(val); | |
| var edges = axis.edges; | |
| return edges[index] + " to " + edges[index + 1]; | |
| } else { | |
| return val; | |
| } | |
| } | |
| function updateDensityMax() { | |
| pMax = -1E20; | |
| var counts; | |
| if (cSelection.targetCat == null) { | |
| counts = hm.counts; | |
| } else { | |
| counts = hm.catCounts; | |
| } | |
| var binIds = binList(hm); | |
| for (var i = 0; i < binIds.length; i++) { | |
| var p = counts[binIds[i][0]][binIds[i][1]]; | |
| pMax = Math.max(pMax, p); | |
| } | |
| } | |
| function updateDensityFn() { | |
| var densityScale; | |
| if (shading == 0) { | |
| densityScale = function (d) {return 1}; | |
| } else { | |
| densityScale = d3.scale.pow().exponent(shading).domain([0, pMax]); | |
| } | |
| if (cSelection.targetCat == null) { | |
| counts = hm.counts; | |
| } else { | |
| counts = hm.catCounts; | |
| } | |
| densityFn = function (d) { | |
| return densityScale(counts[d[0]][d[1]]); | |
| }; | |
| } | |
| var colorFn; | |
| function updateColorFn() { | |
| if (c < 0) { | |
| var white = d3.rgb("#fff"); | |
| colorFn = function (d) {return white}; | |
| } else if (fields[c].optype == "categorical") { | |
| var catColors; | |
| if (hm.c.categories.length > 10) { | |
| catColors = d3.scale.category20(); | |
| } else { | |
| catColors = d3.scale.category10(); | |
| } | |
| catColors.domain(hm.c.categories); | |
| colorFn = function (d) { | |
| var cat; | |
| if (hm.catCounts[d[0]][d[1]] == 0) { | |
| return d3.rgb("#000"); | |
| } else if (cSelection.targetCat == null) { | |
| cat = hm.c.categories[hm.mostCommon[d[0]][d[1]]] | |
| } else { | |
| cat = cSelection.targetCat; | |
| } | |
| return catColors(cat); | |
| }; | |
| } else { | |
| var meanMin = 1E20; | |
| var meanMax = -1E20; | |
| var binIds = binList(hm); | |
| for (var i = 0; i < binIds.length; i++) { | |
| var sum = hm.sums[binIds[i][0]][binIds[i][1]]; | |
| var pop = hm.counts[binIds[i][0]][binIds[i][1]]; | |
| if (pop > 0) { | |
| var mean = sum / pop; | |
| meanMin = Math.min(meanMin, mean); | |
| meanMax = Math.max(meanMax, mean); | |
| } | |
| } | |
| var numColors = d3.scale.linear().domain([meanMin, meanMax]); | |
| numColors = numColors.range(["#11f", "#f11"]); | |
| var black = d3.rgb("#000"); | |
| colorFn = function (d) { | |
| var sum = hm.sums[d[0]][d[1]]; | |
| var pop = hm.counts[d[0]][d[1]]; | |
| if (pop > 0) { | |
| return numColors(sum / pop); | |
| } else { | |
| return black; | |
| } | |
| }; | |
| } | |
| } | |
| function redraw(redrawAxis) { | |
| updateDensityMax(); | |
| updateDensityFn(); | |
| if (redrawAxis) { | |
| updateScales(); | |
| updateAxis(); | |
| } | |
| updateCells(); | |
| } | |
| function makeAxis() { | |
| gy = axisSVG.append("g").attr("class", "y-axis"); | |
| gx = axisSVG.append("g") | |
| .attr("class", "x-axis") | |
| .attr("transform", "translate(" + 0 + "," + height + ")"); | |
| } | |
| function updateAxis() { | |
| var currentY = gy.transition().duration(700).call(yAxis); | |
| if (targetFields[1].optype == "categorical") { | |
| currentY.selectAll("text") | |
| .attr("y", (binCanvasHeight / 2) - 10) | |
| .attr("x", 4) | |
| .style("fill", "#fff") | |
| .style("stroke", "#000") | |
| .style("stroke-width", 0.3) | |
| .style("font-size", "13") | |
| .style("font-weight", "bolder") | |
| .style("font-family", "Monospace") | |
| .style("stroke-linecap", "butt") | |
| .style("stroke-linejoin", "miter") | |
| .style("text-anchor", "start"); | |
| } | |
| var currentX = gx.transition().duration(700).call(xAxis); | |
| if (targetFields[0].optype == "categorical") { | |
| currentX.selectAll("text") | |
| .attr("y", (binCanvasWidth / 2) - 15) | |
| .attr("x", 4) | |
| .attr("transform", "rotate(-90)") | |
| .style("fill", "#fff") | |
| .style("stroke", "#000") | |
| .style("stroke-width", 0.3) | |
| .style("font-size", "13") | |
| .style("font-weight", "bolder") | |
| .style("font-family", "Monospace") | |
| .style("stroke-linecap", "butt") | |
| .style("stroke-linejoin", "miter") | |
| .style("text-anchor", "start"); | |
| } | |
| } | |
| function customAxisFormat(d) { | |
| return d3.format("s")(Math.round(d * 1E4) / 1E4); | |
| } | |
| function updateScales () { | |
| if (targetFields[0].optype == "numeric") { | |
| xScale = d3.scale.linear() | |
| .range([0, width]) | |
| .domain([hm.x.range[0], | |
| hm.x.range[1]]); | |
| } else { | |
| xScale = d3.scale.ordinal() | |
| .domain(hm.x.range) | |
| .rangePoints([0, width], 1); | |
| } | |
| if (targetFields[1].optype == "numeric") { | |
| yScale = d3.scale.linear() | |
| .range([height, 0]) | |
| .domain([hm.y.range[0], | |
| hm.y.range[1]]); | |
| } else { | |
| yScale = d3.scale.ordinal() | |
| .domain(hm.y.range) | |
| .rangePoints([height, 0], 1); | |
| } | |
| xAxis = d3.svg.axis() | |
| .scale(xScale) | |
| .orient("bottom"); | |
| yAxis = d3.svg.axis() | |
| .scale(yScale) | |
| .orient("left"); | |
| if (targetFields[0].optype == "numeric") { | |
| xAxis.tickFormat(customAxisFormat); | |
| } | |
| if (targetFields[1].optype == "numeric") { | |
| yAxis.tickFormat(customAxisFormat); | |
| } | |
| } | |
| function prettyVal(d) { | |
| if (typeof d === 'string' || d instanceof String) { | |
| return d; | |
| } else { | |
| return d = Math.round(d * 1000) / 1000; | |
| } | |
| } | |
| function densityColorShift(color, loc) { | |
| var density = densityFn(loc); | |
| color = d3.rgb(color); | |
| color.r = Math.round(color.r * density); | |
| color.g = Math.round(color.g * density); | |
| color.b = Math.round(color.b * density); | |
| return color; | |
| } | |
| function updateCells() { | |
| var cxFn = canvasXFn(); | |
| var cyFn = canvasYFn(); | |
| var maxBins = Math.max(hm.x.binCount, hm.y.binCount); | |
| var alpha = Math.pow(Math.max((220 - maxBins), 0) / 256, 2); | |
| var lineColor = "rgba(0, 0, 0, " + alpha + ")"; | |
| context.strokeStyle=lineColor; | |
| binList(hm).forEach(function(d, i) { | |
| context.beginPath(); | |
| context.rect(cxFn(d) + margin.left, | |
| cyFn(d) + margin.top, | |
| binCanvasWidth + 1, | |
| binCanvasHeight + 1); | |
| context.fillStyle=densityColorShift(colorFn(d), d); | |
| context.fill(); | |
| /* context.stroke(); */ | |
| context.closePath(); | |
| }); | |
| } | |
| function updateHeatMap() { | |
| hm = heatmap(data, xSelection, ySelection, cSelection, opts); | |
| binCanvasWidth = width / hm.x.binCount; | |
| binCanvasHeight = height / hm.y.binCount; | |
| } | |
| function coordsToGridIndex (coords) { | |
| var x = Math.floor(coords[0] / binCanvasWidth); | |
| var y = Math.floor((height - coords[1]) / binCanvasHeight); | |
| return [x, y]; | |
| } | |
| function canvasXFn () { | |
| return function(binId) {return binId[0] * binCanvasWidth}; | |
| } | |
| function canvasYFn () { | |
| return function(binId) { | |
| return height - binCanvasHeight - (binId[1] * binCanvasHeight) | |
| }; | |
| } | |
| }); | |
| </script> | |
| </body> |
| { | |
| "dataset" : { | |
| "term_limit" : 500, | |
| "missing_tokens" : [ "", "NaN", "NULL", "N/A", "null", "-", "#REF!", "#VALUE!", "?", "#NULL!", "#NUM!", "#DIV/0", "n/a", "#NAME?", "NIL", "nil", "na", "#N/A", "NA" ], | |
| "locale" : "en_US", | |
| "fields" : [ { | |
| "id" : "000000", | |
| "preferred" : true, | |
| "summary" : { | |
| "skewness" : 0.31175, | |
| "splits" : [ 4.51526, 4.67252, 4.81113, 4.89582, 4.96139, 5.01131, 5.05992, 5.11148, 5.18177, 5.35681, 5.44129, 5.5108, 5.58255, 5.65532, 5.71658, 5.77889, 5.85381, 5.97078, 6.05104, 6.13074, 6.23023, 6.29578, 6.35078, 6.41459, 6.49383, 6.63013, 6.70719, 6.79218, 6.92597, 7.20423, 7.64746 ], | |
| "mean" : 5.84333, | |
| "sum_squares" : 5223.85, | |
| "bins" : [ [ 4.3, 1 ], [ 4.425, 4 ], [ 4.6, 4 ], [ 4.77143, 7 ], [ 4.9625, 16 ], [ 5.1, 9 ], [ 5.2, 4 ], [ 5.3, 1 ], [ 5.4, 6 ], [ 5.5, 7 ], [ 5.6, 6 ], [ 5.7, 8 ], [ 5.8, 7 ], [ 5.9, 3 ], [ 6, 6 ], [ 6.1, 6 ], [ 6.2, 4 ], [ 6.3, 9 ], [ 6.4, 7 ], [ 6.5, 5 ], [ 6.6, 2 ], [ 6.7, 8 ], [ 6.8, 3 ], [ 6.9, 4 ], [ 7, 1 ], [ 7.1, 1 ], [ 7.2, 3 ], [ 7.3, 1 ], [ 7.4, 1 ], [ 7.6, 1 ], [ 7.7, 4 ], [ 7.9, 1 ] ], | |
| "maximum" : 7.9, | |
| "missing_count" : 0, | |
| "variance" : 0.68569, | |
| "median" : 5.8, | |
| "population" : 150, | |
| "minimum" : 4.3, | |
| "standard_deviation" : 0.82807, | |
| "kurtosis" : -0.57357, | |
| "sum" : 876.5 | |
| }, | |
| "datatype" : "double", | |
| "order" : 0, | |
| "optype" : "numeric", | |
| "name" : "sepal length", | |
| "column_number" : 0 | |
| }, { | |
| "id" : "000001", | |
| "preferred" : true, | |
| "summary" : { | |
| "skewness" : 0.31577, | |
| "counts" : [ [ 2, 1 ], [ 2.2, 3 ], [ 2.3, 4 ], [ 2.4, 3 ], [ 2.5, 8 ], [ 2.6, 5 ], [ 2.7, 9 ], [ 2.8, 14 ], [ 2.9, 10 ], [ 3, 26 ], [ 3.1, 11 ], [ 3.2, 13 ], [ 3.3, 6 ], [ 3.4, 12 ], [ 3.5, 6 ], [ 3.6, 4 ], [ 3.7, 3 ], [ 3.8, 6 ], [ 3.9, 2 ], [ 4, 1 ], [ 4.1, 1 ], [ 4.2, 1 ], [ 4.4, 1 ] ], | |
| "mean" : 3.05733, | |
| "sum_squares" : 1430.4, | |
| "maximum" : 4.4, | |
| "missing_count" : 0, | |
| "variance" : 0.18998, | |
| "median" : 3, | |
| "population" : 150, | |
| "minimum" : 2, | |
| "standard_deviation" : 0.43587, | |
| "kurtosis" : 0.18098, | |
| "sum" : 458.6 | |
| }, | |
| "datatype" : "double", | |
| "order" : 1, | |
| "optype" : "numeric", | |
| "name" : "sepal width", | |
| "column_number" : 1 | |
| }, { | |
| "id" : "000002", | |
| "preferred" : true, | |
| "summary" : { | |
| "skewness" : -0.27213, | |
| "splits" : [ 1.25138, 1.32426, 1.37171, 1.40962, 1.44567, 1.48173, 1.51859, 1.56301, 1.6255, 1.74645, 3.23033, 3.675, 3.94203, 4.0469, 4.18243, 4.34142, 4.45309, 4.51823, 4.61771, 4.72566, 4.83445, 4.93363, 5.03807, 5.1064, 5.20938, 5.43979, 5.5744, 5.6646, 5.81496, 6.02913, 6.38125 ], | |
| "mean" : 3.758, | |
| "sum_squares" : 2582.71, | |
| "bins" : [ [ 1, 1 ], [ 1.16667, 3 ], [ 1.3, 7 ], [ 1.4, 13 ], [ 1.5, 13 ], [ 1.6, 7 ], [ 1.7, 4 ], [ 1.9, 2 ], [ 3, 1 ], [ 3.3, 2 ], [ 3.5, 2 ], [ 3.6, 1 ], [ 3.75, 2 ], [ 3.9, 3 ], [ 4.0375, 8 ], [ 4.23333, 6 ], [ 4.46667, 12 ], [ 4.6, 3 ], [ 4.74444, 9 ], [ 4.94444, 9 ], [ 5.1, 8 ], [ 5.25, 4 ], [ 5.46, 5 ], [ 5.6, 6 ], [ 5.75, 6 ], [ 5.95, 4 ], [ 6.1, 3 ], [ 6.3, 1 ], [ 6.4, 1 ], [ 6.6, 1 ], [ 6.7, 2 ], [ 6.9, 1 ] ], | |
| "maximum" : 6.9, | |
| "missing_count" : 0, | |
| "variance" : 3.11628, | |
| "median" : 4.35, | |
| "population" : 150, | |
| "minimum" : 1, | |
| "standard_deviation" : 1.7653, | |
| "kurtosis" : -1.39554, | |
| "sum" : 563.7 | |
| }, | |
| "datatype" : "double", | |
| "order" : 2, | |
| "optype" : "numeric", | |
| "name" : "petal length", | |
| "column_number" : 2 | |
| }, { | |
| "id" : "000003", | |
| "preferred" : true, | |
| "summary" : { | |
| "skewness" : -0.10193, | |
| "counts" : [ [ 0.1, 5 ], [ 0.2, 29 ], [ 0.3, 7 ], [ 0.4, 7 ], [ 0.5, 1 ], [ 0.6, 1 ], [ 1, 7 ], [ 1.1, 3 ], [ 1.2, 5 ], [ 1.3, 13 ], [ 1.4, 8 ], [ 1.5, 12 ], [ 1.6, 4 ], [ 1.7, 2 ], [ 1.8, 12 ], [ 1.9, 5 ], [ 2, 6 ], [ 2.1, 6 ], [ 2.2, 3 ], [ 2.3, 8 ], [ 2.4, 3 ], [ 2.5, 3 ] ], | |
| "mean" : 1.19933, | |
| "sum_squares" : 302.33, | |
| "maximum" : 2.5, | |
| "missing_count" : 0, | |
| "variance" : 0.58101, | |
| "median" : 1.3, | |
| "population" : 150, | |
| "minimum" : 0.1, | |
| "standard_deviation" : 0.76224, | |
| "kurtosis" : -1.33607, | |
| "sum" : 179.9 | |
| }, | |
| "datatype" : "double", | |
| "order" : 3, | |
| "optype" : "numeric", | |
| "name" : "petal width", | |
| "column_number" : 3 | |
| }, { | |
| "id" : "000004", | |
| "preferred" : true, | |
| "summary" : { | |
| "missing_count" : 0, | |
| "categories" : [ [ "Iris-setosa", 50 ], [ "Iris-versicolor", 50 ], [ "Iris-virginica", 50 ] ] | |
| }, | |
| "datatype" : "string", | |
| "order" : 4, | |
| "term_analysis" : { | |
| "enabled" : true | |
| }, | |
| "optype" : "categorical", | |
| "name" : "species", | |
| "column_number" : 4 | |
| } ], | |
| "input_fields" : [ "000000", "000001", "000002", "000003", "000004" ], | |
| "missing_numeric_count" : 0, | |
| "datasink" : "file://localhost/tmp/wintermute-tests.cheesinglee/regression/datasink", | |
| "datasource_id" : "1426541387842", | |
| "serialized_bytes" : 6150, | |
| "count" : 150, | |
| "objective" : { | |
| "id" : "000004", | |
| "datatype" : "string", | |
| "order" : 4, | |
| "term_analysis" : { | |
| "enabled" : true | |
| }, | |
| "optype" : "categorical", | |
| "name" : "species", | |
| "column_number" : 4 | |
| }, | |
| "used_bytes" : 4609 | |
| } | |
| } |
| var data = {"fields":[{"maximum":7.9,"minimum":4.3,"missings":false,"datatype":"double","optype":"numeric","id":"000000"},{"maximum":4.4,"minimum":2.0,"missings":false,"datatype":"double","optype":"numeric","id":"000001"},{"maximum":6.9,"minimum":1.0,"missings":false,"datatype":"double","optype":"numeric","id":"000002"},{"maximum":2.5,"minimum":0.1,"missings":false,"datatype":"double","optype":"numeric","id":"000003"},{"categories":["Iris-setosa","Iris-versicolor","Iris-virginica"],"missings":false,"datatype":"byte","optype":"categorical","id":"000004"}],"data":[[5.1,4.9,4.7,4.6,5.0,5.4,4.6,5.0,4.4,4.9,5.4,4.8,4.8,4.3,5.8,5.7,5.4,5.1,5.7,5.1,5.4,5.1,4.6,5.1,4.8,5.0,5.0,5.2,5.2,4.7,4.8,5.4,5.2,5.5,4.9,5.0,5.5,4.9,4.4,5.1,5.0,4.5,4.4,5.0,5.1,4.8,5.1,4.6,5.3,5.0,7.0,6.4,6.9,5.5,6.5,5.7,6.3,4.9,6.6,5.2,5.0,5.9,6.0,6.1,5.6,6.7,5.6,5.8,6.2,5.6,5.9,6.1,6.3,6.1,6.4,6.6,6.8,6.7,6.0,5.7,5.5,5.5,5.8,6.0,5.4,6.0,6.7,6.3,5.6,5.5,5.5,6.1,5.8,5.0,5.6,5.7,5.7,6.2,5.1,5.7,6.3,5.8,7.1,6.3,6.5,7.6,4.9,7.3,6.7,7.2,6.5,6.4,6.8,5.7,5.8,6.4,6.5,7.7,7.7,6.0,6.9,5.6,7.7,6.3,6.7,7.2,6.2,6.1,6.4,7.2,7.4,7.9,6.4,6.3,6.1,7.7,6.3,6.4,6.0,6.9,6.7,6.9,5.8,6.8,6.7,6.7,6.3,6.5,6.2,5.9],[3.5,3.0,3.2,3.1,3.6,3.9,3.4,3.4,2.9,3.1,3.7,3.4,3.0,3.0,4.0,4.4,3.9,3.5,3.8,3.8,3.4,3.7,3.6,3.3,3.4,3.0,3.4,3.5,3.4,3.2,3.1,3.4,4.1,4.2,3.1,3.2,3.5,3.6,3.0,3.4,3.5,2.3,3.2,3.5,3.8,3.0,3.8,3.2,3.7,3.3,3.2,3.2,3.1,2.3,2.8,2.8,3.3,2.4,2.9,2.7,2.0,3.0,2.2,2.9,2.9,3.1,3.0,2.7,2.2,2.5,3.2,2.8,2.5,2.8,2.9,3.0,2.8,3.0,2.9,2.6,2.4,2.4,2.7,2.7,3.0,3.4,3.1,2.3,3.0,2.5,2.6,3.0,2.6,2.3,2.7,3.0,2.9,2.9,2.5,2.8,3.3,2.7,3.0,2.9,3.0,3.0,2.5,2.9,2.5,3.6,3.2,2.7,3.0,2.5,2.8,3.2,3.0,3.8,2.6,2.2,3.2,2.8,2.8,2.7,3.3,3.2,2.8,3.0,2.8,3.0,2.8,3.8,2.8,2.8,2.6,3.0,3.4,3.1,3.0,3.1,3.1,3.1,2.7,3.2,3.3,3.0,2.5,3.0,3.4,3.0],[1.4,1.4,1.3,1.5,1.4,1.7,1.4,1.5,1.4,1.5,1.5,1.6,1.4,1.1,1.2,1.5,1.3,1.4,1.7,1.5,1.7,1.5,1.0,1.7,1.9,1.6,1.6,1.5,1.4,1.6,1.6,1.5,1.5,1.4,1.5,1.2,1.3,1.4,1.3,1.5,1.3,1.3,1.3,1.6,1.9,1.4,1.6,1.4,1.5,1.4,4.7,4.5,4.9,4.0,4.6,4.5,4.7,3.3,4.6,3.9,3.5,4.2,4.0,4.7,3.6,4.4,4.5,4.1,4.5,3.9,4.8,4.0,4.9,4.7,4.3,4.4,4.8,5.0,4.5,3.5,3.8,3.7,3.9,5.1,4.5,4.5,4.7,4.4,4.1,4.0,4.4,4.6,4.0,3.3,4.2,4.2,4.2,4.3,3.0,4.1,6.0,5.1,5.9,5.6,5.8,6.6,4.5,6.3,5.8,6.1,5.1,5.3,5.5,5.0,5.1,5.3,5.5,6.7,6.9,5.0,5.7,4.9,6.7,4.9,5.7,6.0,4.8,4.9,5.6,5.8,6.1,6.4,5.6,5.1,5.6,6.1,5.6,5.5,4.8,5.4,5.6,5.1,5.1,5.9,5.7,5.2,5.0,5.2,5.4,5.1],[0.2,0.2,0.2,0.2,0.2,0.4,0.3,0.2,0.2,0.1,0.2,0.2,0.1,0.1,0.2,0.4,0.4,0.3,0.3,0.3,0.2,0.4,0.2,0.5,0.2,0.2,0.4,0.2,0.2,0.2,0.2,0.4,0.1,0.2,0.2,0.2,0.2,0.1,0.2,0.2,0.3,0.3,0.2,0.6,0.4,0.3,0.2,0.2,0.2,0.2,1.4,1.5,1.5,1.3,1.5,1.3,1.6,1.0,1.3,1.4,1.0,1.5,1.0,1.4,1.3,1.4,1.5,1.0,1.5,1.1,1.8,1.3,1.5,1.2,1.3,1.4,1.4,1.7,1.5,1.0,1.1,1.0,1.2,1.6,1.5,1.6,1.5,1.3,1.3,1.3,1.2,1.4,1.2,1.0,1.3,1.2,1.3,1.3,1.1,1.3,2.5,1.9,2.1,1.8,2.2,2.1,1.7,1.8,1.8,2.5,2.0,1.9,2.1,2.0,2.4,2.3,1.8,2.2,2.3,1.5,2.3,2.0,2.0,1.8,2.1,1.8,1.8,1.8,2.1,1.6,1.9,2.0,2.2,1.5,1.4,2.3,2.4,1.8,1.8,2.1,2.4,2.3,1.9,2.3,2.5,2.3,1.9,2.0,2.3,1.8],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]]}; |