|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"> |
|
<title>Z-Order Curve</title> |
|
<script |
|
src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"> |
|
|
|
</script> |
|
<script src="http://d3js.org/d3.v3.min.js"></script> |
|
|
|
<style type="text/css"> |
|
body { |
|
background: #fff; |
|
} |
|
|
|
.brush .extent { |
|
stroke: #fff; |
|
fill-opacity: .125; |
|
shape-rendering: crispEdges; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="brushArea" style="float:left"> </div> |
|
<div class="debugSvg"> </div> |
|
<div class="debugArea"></div> |
|
<script type="text/javascript"> |
|
/** |
|
* init variables |
|
*/ |
|
var w = 512, h = 512, scale = 32; |
|
var minCellNumber = 0; |
|
var maxCellNumber = w / scale - 1; |
|
var zMapFunction = "01010101"; |
|
// "0101010101010101"; |
|
var resolution = 4; |
|
|
|
/** |
|
* compute bitString for x and y |
|
* we take only a highest bits |
|
*/ |
|
function mapToBitStrings(x, y, mapFunction) { |
|
var xResolution = 0; |
|
var yResolution = 0; |
|
// compute x resolution |
|
for (var i = 0; i < mapFunction.length; i++) { |
|
var dimension = mapFunction.charAt(i); |
|
if (dimension == "0") {// x dimension |
|
xResolution++; |
|
} else { |
|
yResolution++; |
|
} |
|
} |
|
// |
|
var xBinaryString = x.toString(2); |
|
// binary rep |
|
var yBinaryString = y.toString(2); |
|
// binary rep |
|
// extend both string or cut |
|
if (resolution > xBinaryString.length) { |
|
var nullbits = ""; |
|
var diff = xResolution - xBinaryString.length; |
|
for (var i = 0; i < diff; i++) { |
|
nullbits = nullbits + "0"; |
|
} |
|
xBinaryString = nullbits + xBinaryString; |
|
} |
|
// |
|
if (resolution > yBinaryString.length) { |
|
var nullbits = ""; |
|
var diff = yResolution - yBinaryString.length; |
|
for (var i = 0; i < diff; i++) { |
|
nullbits = nullbits + "0"; |
|
} |
|
yBinaryString = nullbits + yBinaryString; |
|
} |
|
return { |
|
"xBitString" : xBinaryString, |
|
"yBitString" : yBinaryString |
|
}; |
|
} |
|
|
|
/** |
|
* |
|
*/ |
|
function computeZCodeBitString(xBinaryString, yBinaryString, |
|
mapFunction) { |
|
var key = ""; |
|
var bitsX = xBinaryString.length; |
|
var bitsY = yBinaryString.length; |
|
for (var i = 0; i < mapFunction.length; i++) { |
|
var dimension = mapFunction.charAt(i); |
|
var index; |
|
var pointVal; |
|
if (dimension == "0") {// x dimension |
|
pointVal = xBinaryString; |
|
index = xBinaryString.length - bitsX; |
|
bitsX--; |
|
} else {// y dimension |
|
pointVal = yBinaryString; |
|
index = xBinaryString.length - bitsY; |
|
bitsY--; |
|
} |
|
if (index < pointVal.length) { |
|
// get the current bit value |
|
var bit = pointVal.charAt(index); |
|
key = key + bit; |
|
} |
|
} |
|
return key; |
|
} |
|
|
|
/** |
|
* computes z code based on mapFunction points x,y e.g. mapfunction e.g. |
|
* [0,1,0,1,0,1] number of bits used for the function |
|
*/ |
|
function computeZCode(x, y, mapFunction) { |
|
var bitStrings = mapToBitStrings(x, y, mapFunction); |
|
var xBinaryString = bitStrings.xBitString; |
|
var yBinaryString = bitStrings.yBitString; |
|
return computeZCodeBitString(xBinaryString, yBinaryString, |
|
mapFunction); |
|
} |
|
|
|
/** |
|
* convert to bisString and optionally extend to the length of the mapfunction |
|
* |
|
*/ |
|
function toBitString(zCode, mapFunction) { |
|
var zCodeString = zCode.toString(2); |
|
if (mapFunction.length > zCodeString.length) { |
|
var diff = mapFunction.length - zCodeString.length; |
|
var nullbits = ""; |
|
for (var i = 0; i < diff; i++) { |
|
nullbits = nullbits + "0"; |
|
} |
|
return nullbits + zCodeString; |
|
} |
|
return zCodeString.substr(zCode.lenth - mapFunction.length, |
|
mapFunction.length); |
|
} |
|
|
|
/** |
|
* -1 function computes x |
|
*/ |
|
function getBitXYStringsFromZCode(zBitString, mapFunction) { |
|
var xString = ""; |
|
var yString = ""; |
|
for (var i = 0; i < mapFunction.length; i++) { |
|
if (mapFunction.charAt(i) == "0") {// append to x |
|
xString = xString + zBitString.charAt(i); |
|
} else {// appen to y |
|
yString = yString + zBitString.charAt(i); |
|
} |
|
} |
|
return { |
|
"xStr" : xString, |
|
"yStr" : yString |
|
}; |
|
} |
|
|
|
/** |
|
* Computes based on the zcode and coordinates the x and y values parameters are |
|
* bit strings |
|
*/ |
|
function getRanges(hyperPlanePosition, queryRec, mapFunction, highstBit) { |
|
// query rec |
|
var xLow = queryRec.xLow; |
|
var yLow = queryRec.yLow; |
|
var xHigh = queryRec.xHigh; |
|
var yHigh = queryRec.yHigh; |
|
// the same length z code |
|
var zCodeLow = computeZCode(xLow, yLow, mapFunction); |
|
var zCodeHigh = computeZCode(xHigh, yHigh, mapFunction); |
|
// compute first index starting from hyperplane position such as values a |
|
// diffrenet |
|
// find i l highst bit of hightest dimension |
|
var index = 0; |
|
var hyperplaneDimension = "0"; |
|
// x |
|
var hyperplaneCutIndex = [ -1, -1 ]; |
|
for (var i = 0; i < highstBit; i++) { |
|
// count position in each dimension |
|
hyperplaneDimension = mapFunction.charAt(i); |
|
if (mapFunction.charAt(i) == "0") {// x dimesnion |
|
hyperplaneCutIndex[0]++; |
|
} else {// y dimension |
|
hyperplaneCutIndex[1]++; |
|
} |
|
index = i; |
|
if (zCodeHigh.charAt(i) != zCodeLow.charAt(i)) { |
|
break; |
|
} |
|
} |
|
// check conditions 1 |
|
if (index >= highstBit - 1) { |
|
return [ { |
|
"zl" : parseInt(zCodeLow, 2), |
|
"zh" : parseInt(zCodeHigh, 2) |
|
} ]; |
|
} |
|
// check condition 2 |
|
var allOnes = true; |
|
var allNulls = true; |
|
for (var i = index; i < highstBit; i++) { |
|
if (zCodeHigh.charAt(i) != "1") { |
|
allOnes = false; |
|
break; |
|
} |
|
if (zCodeLow.charAt(i) != "0") { |
|
allNulls = false; |
|
break; |
|
} |
|
} |
|
if (allOnes && allNulls) { |
|
return [ { |
|
"zl" : parseInt(zCodeLow, 2), |
|
"zh" : parseInt(zCodeHigh, 2) |
|
} ]; |
|
} |
|
// both conditions not fulfilled cut the box along the hyperplane |
|
var queryBoxes = cutBox(hyperplaneDimension, hyperplaneCutIndex, |
|
queryRec, mapFunction); |
|
// recursive call left |
|
var rangesLeft = getRanges(hyperPlanePosition, queryBoxes[0], |
|
mapFunction, highstBit); |
|
var rangesRight = getRanges(hyperPlanePosition, queryBoxes[1], |
|
mapFunction, highstBit); |
|
// merge : last und first check |
|
var lastLeft = rangesLeft[rangesLeft.length - 1].zh; |
|
var firstRight = rangesRight[0].zl; |
|
|
|
if (firstRight - lastLeft == 1) { |
|
var mergeRange = { |
|
"zl" : rangesLeft[rangesLeft.length - 1].zl, |
|
"zh" : rangesRight[0].zh |
|
}; |
|
rangesLeft[rangesLeft.length - 1] = mergeRange; |
|
for (var i = 1; i < rangesRight.length; i++) { |
|
rangesLeft.push(rangesRight[i]); |
|
} |
|
} else { |
|
for (var i = 0; i < rangesRight.length; i++) { |
|
rangesLeft.push(rangesRight[i]); |
|
} |
|
} |
|
return rangesLeft; |
|
} |
|
|
|
/** |
|
* |
|
*/ |
|
function cutBox(hyperplaneDimension, index, queryRec, mapFunction) { |
|
// query rec convert to strings |
|
var lowStrings = mapToBitStrings(queryRec.xLow, queryRec.yLow, |
|
mapFunction); |
|
var highStrings = mapToBitStrings(queryRec.xHigh, queryRec.yHigh, |
|
mapFunction); |
|
// firts box below the hyperpale |
|
var x1Low = lowStrings.xBitString.slice(0); |
|
var y1Low = lowStrings.yBitString.slice(0); |
|
var x1High = highStrings.xBitString.slice(0); |
|
var y1High = highStrings.yBitString.slice(0); |
|
// new coordinates |
|
if (hyperplaneDimension == "0") {// x |
|
// set bit to 0 |
|
x1High = simpleReplaceAndFill(index[0], x1High, "0", "1"); // remain |
|
// part |
|
// 0111111 |
|
} else { |
|
// set bit to 0 |
|
y1High = simpleReplaceAndFill(index[1], y1High, "0", "1"); // remain |
|
// part |
|
// 0111111 |
|
} |
|
// second box above the hyperplane |
|
var x2Low = lowStrings.xBitString.slice(0); |
|
var y2Low = lowStrings.yBitString.slice(0); |
|
var x2High = highStrings.xBitString.slice(0); |
|
var y2High = highStrings.yBitString.slice(0); |
|
if (hyperplaneDimension == "0") {// x |
|
// set bit to 1 |
|
x2Low = simpleReplaceAndFill(index[0], x2Low, "1", "0"); // remain |
|
// part |
|
// 1000000 |
|
} else { |
|
// set bit to 1 |
|
y2Low = simpleReplaceAndFill(index[1], y2Low, "1", "0"); // remain |
|
// part |
|
// 100000 |
|
} |
|
var queryBoxes = [ { |
|
"xLow" : parseInt(x1Low, 2), |
|
"yLow" : parseInt(y1Low, 2), |
|
"xHigh" : parseInt(x1High, 2), |
|
"yHigh" : parseInt(y1High, 2) |
|
}, { |
|
"xLow" : parseInt(x2Low, 2), |
|
"yLow" : parseInt(y2Low, 2), |
|
"xHigh" : parseInt(x2High, 2), |
|
"yHigh" : parseInt(y2High, 2) |
|
} ]; |
|
return queryBoxes; |
|
} |
|
|
|
/** |
|
* string replacement |
|
*/ |
|
function simpleReplaceAt(pos, source, c) { |
|
return source.substr(0, pos) + c + source.substr(pos + 1); |
|
} |
|
/** |
|
* |
|
*/ |
|
function simpleReplaceAndFill(pos, source, c, fillc) { |
|
var prefix = source.substr(0, pos) + c; |
|
var f = ""; |
|
for (var i = pos + 1; i < source.length; i++) { |
|
f = f + fillc; |
|
} |
|
return prefix + f; |
|
} |
|
|
|
/** |
|
* |
|
*/ |
|
function drawRec(queryRec) { |
|
var path = []; |
|
path.push({ |
|
"x" : parseInt(queryRec.xLow, 2), |
|
"y" : parseInt(queryRec.yLow, 2) |
|
}); |
|
path.push({ |
|
"x" : parseInt(queryRec.xLow, 2), |
|
"y" : parseInt(queryRec.yHigh, 2) |
|
}); |
|
path.push({ |
|
"x" : parseInt(queryRec.xHigh, 2), |
|
"y" : parseInt(queryRec.yHigh, 2) |
|
}); |
|
path.push({ |
|
"x" : pasreInt(queryRec.xHigh, 2), |
|
"y" : parseInt(queryRec.yLow, 2) |
|
}); |
|
path.push({ |
|
"x" : parseInt(queryRec.xLow, 2), |
|
"y" : parseInt(queryRec.yLow, 2) |
|
}); |
|
return path; |
|
|
|
} |
|
|
|
/** |
|
* |
|
* compute zvalue array |
|
*/ |
|
function buildPath(mapFunction, resolution) { |
|
var path = []; |
|
var maxPoints = (1 << resolution) * (1 << resolution) - 1; |
|
return buildPathFromRange(0, maxPoints, mapFunction); |
|
} |
|
|
|
/** |
|
* |
|
* compute zvalue array |
|
*/ |
|
function buildPathFromRange(min, max, mapFunction) { |
|
var path = []; |
|
for (var x = min; x <= max; x++) { |
|
var zString = toBitString(x.toString(2), mapFunction); |
|
var coord = getBitXYStringsFromZCode(zString, mapFunction); |
|
path.push({ |
|
"x" : parseInt(coord.xStr, 2) * scale, |
|
"y" : parseInt(coord.yStr, 2) * scale |
|
}); |
|
} |
|
return path; |
|
} |
|
|
|
/** |
|
* window definition |
|
*/ |
|
function brushed() { |
|
var extent = brush.extent(); |
|
var xLow = Math.round(extent[0][0] / scale); |
|
var yLow = Math.round(extent[0][1] / scale); |
|
var xHigh = Math.round(extent[1][0] / scale); |
|
var yHigh = Math.round(extent[1][1] / scale); |
|
// check if within area |
|
if (xLow > maxCellNumber) |
|
xLow = maxCellNumber; |
|
if (yLow > maxCellNumber) |
|
yLow = maxCellNumber; |
|
if (xHigh > maxCellNumber) |
|
xHigh = maxCellNumber; |
|
if (yHigh > maxCellNumber) |
|
yHigh = maxCellNumber; |
|
// get path |
|
var lowZ = parseInt(computeZCode(xLow, yLow, zMapFunction), 2); |
|
var highZ = parseInt(computeZCode(xHigh, yHigh, zMapFunction), 2); |
|
// compute rangess |
|
var queryBox = { |
|
"xLow" : xLow, |
|
"yLow" : yLow, |
|
"xHigh" : xHigh, |
|
"yHigh" : yHigh |
|
}; |
|
var ranges = getRanges(0, queryBox, zMapFunction, |
|
zMapFunction.length - 1); |
|
$(".debugArea").html( |
|
"<p> XLow:" + xLow + " YLow " + yLow + " XHigh " + xHigh |
|
+ " YHigh " + yHigh + " LowZ " + lowZ + " HighZ " |
|
+ highZ + "</p>"); |
|
d3.select(".debugSvg").selectAll("svg").remove(); |
|
var svgArea = d3.select(".debugSvg").append("svg").attr("width", w) |
|
.attr("height", h); |
|
for (var i = 0; i < ranges.length; i++) { |
|
var min = ranges[i].zl; |
|
var max = ranges[i].zh; |
|
var stroke = "blue"; |
|
//if (i % 2 == 0) { |
|
// stroke = "blue"; |
|
//} |
|
svgArea.append("path").attr("d", |
|
line(buildPathFromRange(min, max, zMapFunction))).attr( |
|
"stroke", stroke).attr("stroke-width", 2).attr("fill", |
|
"none"); |
|
} |
|
|
|
} |
|
|
|
var brush = d3.svg.brush().x(d3.scale.identity().domain([ 0, w ])).y( |
|
d3.scale.identity().domain([ 0, h ])).on("brush", brushed) |
|
.extent([ [ 100, 100 ], [ 200, 200 ] ]); |
|
|
|
/** |
|
* main line ge |
|
*/ |
|
// line generator |
|
var line = d3.svg.line().x(function(d) { |
|
return d.x; |
|
}).y(function(d) { |
|
return d.y; |
|
}).interpolate("linear"); |
|
var svg = d3.select(".brushArea").append("svg").attr("width", w).attr( |
|
"height", h); |
|
svg.append("path").attr("d", line(buildPath(zMapFunction, resolution))) |
|
.attr("stroke", "#333").attr("stroke-width", 1).attr("fill", |
|
"none"); |
|
// |
|
svg.append("g").attr("class", "brush").call(brush); |
|
</script> |
|
<!-- <script type="text/javascript" src="js/skript.js"></script> --> |
|
</body> |
|
</html> |