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