diff --git a/axes.js b/axes.js index fa5914f..0258993 100644 --- a/axes.js +++ b/axes.js @@ -36,6 +36,7 @@ function Axes(gl) { this.tickFont = [ 'sans-serif', 'sans-serif', 'sans-serif' ] this.tickSize = [ 12, 12, 12 ] this.tickAngle = [ 0, 0, 0 ] + this._tickAlign = [ 'auto', 'auto', 'auto' ] this.tickColor = [ [0,0,0,1], [0,0,0,1], [0,0,0,1] ] this.tickPad = [ 10, 10, 10 ] @@ -48,7 +49,8 @@ function Axes(gl) { this.labelEnable = [ true, true, true ] this.labelFont = 'sans-serif' this.labelSize = [ 20, 20, 20 ] - this.labelAngle = [ 0, 0, 0 ] + this._labelAngle = [ 0, 0, 0 ] + this._labelAlign = [ 'auto', 'auto', 'auto' ] this.labelColor = [ [0,0,0,1], [0,0,0,1], [0,0,0,1] ] this.labelPad = [ 10, 10, 10 ] @@ -457,13 +459,48 @@ proto.draw = function(params) { projection, this.pixelRatio) - var alignOpt = [0,0,1] - // Note: the 3rd member is the integer option - // from {-1, 0, 1, 2, 3, ..., n} + var alignOpt // options in shader are from this list {-1, 0, 1, 2, 3, ..., n} + // -1: backward compatible + // 0: raw data + // 1: auto align, free angles + // 2: auto align, horizontal or vertical + //3-n: auto align, round to n directions e.g. 12 -> round to angles with 30-degree steps + + var upwardsTolerance + var hv_ratio = 0.5 // can have an effect on the ratio between horizontals and verticals when using option 2 + + var alignDir + + function alignTo(i) { + alignDir = [0,0,0]; + alignDir[i] = 1 + } + + function solveTickAlignments(i, minor, major) { + + var i1 = (i + 1) % 3 + var i2 = (i + 2) % 3 + + var A = minor[i1] + var B = minor[i2] + var C = major[i1] + var D = major[i2] + + if ((A > 0) && (D > 0)) { alignTo(i1); return; } + else if ((A > 0) && (D < 0)) { alignTo(i1); return; } + else if ((A < 0) && (D > 0)) { alignTo(i1); return; } + else if ((A < 0) && (D < 0)) { alignTo(i1); return; } + else if ((B > 0) && (C > 0)) { alignTo(i2); return; } + else if ((B > 0) && (C < 0)) { alignTo(i2); return; } + else if ((B < 0) && (C > 0)) { alignTo(i2); return; } + else if ((B < 0) && (C < 0)) { alignTo(i2); return; } + } for(var i=0; i<3; ++i) { var minor = lineOffset[i].primalMinor + var major = lineOffset[i].mirrorMinor + var offset = copyVec3(PRIMAL_OFFSET, lineOffset[i].primalOffset) for(var j=0; j<3; ++j) { @@ -475,12 +512,25 @@ proto.draw = function(params) { var axis = [0,0,0] axis[i] = 1 - var alignDir = [0,0,0] - alignDir[i] = 1 - //Draw tick text if(this.tickEnable[i]) { + upwardsTolerance = 0.0 // using a value e.g. 0.25 * Math.PI could allow downwards ticks (45 degrees) + + if(this.tickAngle[i] === -3600) { + this.tickAngle[i] = 0 + this._tickAlign[i] = 'auto' + } else { + this._tickAlign[i] = -1 + } + + alignOpt = [this._tickAlign[i], upwardsTolerance, hv_ratio] + if(alignOpt[0] === 'auto') alignOpt[0] = 1 + else alignOpt[0] = parseInt('' + alignOpt[0]) + + alignDir = [0,0,0] + solveTickAlignments(i, minor, major) + //Add tick padding for(var j=0; j<3; ++j) { offset[j] += pixelScaleF * minor[j] * this.tickPad[j] / model[5*j] @@ -490,7 +540,7 @@ proto.draw = function(params) { this._text.drawTicks( i, this.tickSize[i], - this.tickAngle[i] + 0.5 * Math.PI, + this.tickAngle[i], offset, this.tickColor[i], axis, @@ -501,22 +551,27 @@ proto.draw = function(params) { //Draw labels if(this.labelEnable[i]) { + upwardsTolerance = 0 // no tolerance for titles + alignOpt = [this._labelAlign[i], upwardsTolerance, hv_ratio] + if(alignOpt[0] === 'auto') alignOpt[0] = 1 + else alignOpt[0] = parseInt('' + alignOpt[0]) + + var alignDir = [0,0,0] + if(this.labels[i].length > 4) { // for large label axis enable alignDir to axis + alignTo(i) + } + //Add label padding for(var j=0; j<3; ++j) { offset[j] += pixelScaleF * minor[j] * this.labelPad[j] / model[5*j] } offset[i] += 0.5 * (bounds[0][i] + bounds[1][i]) - var alignDir = [0,0,0] - if(this.labels[i].length > 4) { // for large label axis enable alignDir to axis - alignDir[i] = 1 - } - //Draw axis this._text.drawLabel( i, this.labelSize[i], - this.labelAngle[i], + this._labelAngle[i], offset, this.labelColor[i], [0,0,0], diff --git a/lib/shaders/textVert.glsl b/lib/shaders/textVert.glsl index 5bf1be7..aac259c 100644 --- a/lib/shaders/textVert.glsl +++ b/lib/shaders/textVert.glsl @@ -15,14 +15,18 @@ const float TWO_PI = 2.0 * PI; const float HALF_PI = 0.5 * PI; const float ONE_AND_HALF_PI = 1.5 * PI; +int option = int(floor(alignOpt.x + 0.001)); +float up_tolerance = alignOpt.y; +float hv_ratio = alignOpt.z; + float positive_angle(float a) { if (a < 0.0) return a + TWO_PI; return a; } -float look_upwards(float a) { +float look_upwards(float a, float tolerance) { float b = positive_angle(a); - if ((b > HALF_PI) && (b < ONE_AND_HALF_PI)) return b - PI; + if ((b > HALF_PI + tolerance) && (b <= ONE_AND_HALF_PI + tolerance)) return b - PI; return b; } @@ -48,11 +52,9 @@ float look_round_n_directions(float a, int n) { float b = positive_angle(a); float div = TWO_PI / float(n); float c = roundTo(b, div); - return look_upwards(c); + return look_upwards(c, up_tolerance); } -int option = int(floor(alignOpt.z + 0.001)); - float applyAlignOption(float rawAngle) { if (option == -1) { @@ -63,35 +65,33 @@ float applyAlignOption(float rawAngle) { return rawAngle; } else if (option == 1) { // option 1: use free angle, but flip when reversed - return look_upwards(rawAngle); + return look_upwards(rawAngle, up_tolerance); } else if (option == 2) { // option 2: horizontal or vertical - return look_horizontal_or_vertical(rawAngle, 0.8); // 0.8 here means: increase the chance of getting horizontal labels + return look_horizontal_or_vertical(rawAngle, hv_ratio); } // option 3-n: round to n directions return look_round_n_directions(rawAngle, option); } +bool enableAlign = (alignDir.x != 0.0) || (alignDir.y != 0.0) || (alignDir.z != 0.0); + void main() { //Compute world offset float axisDistance = position.z; vec3 dataPosition = axisDistance * axis + offset; - float clipAngle = 0.0; - - if ((alignDir.x != 0.0) || - (alignDir.y != 0.0) || - (alignDir.z != 0.0)) { + float clipAngle = angle; // i.e. user defined attributes for each tick - vec3 REF = dataPosition; + if (enableAlign) { + vec3 startPoint = project(dataPosition); + vec3 endPoint = project(dataPosition + alignDir); - vec3 startPoint = project(REF); - vec3 endPoint = project(REF + alignDir); + if (endPoint.z < 0.0) endPoint = project(dataPosition - alignDir); - clipAngle = applyAlignOption( - angle + // i.e. user defined attributes for each tick + clipAngle += applyAlignOption( atan( (endPoint.y - startPoint.y) * resolution.y, (endPoint.x - startPoint.x) * resolution.x