From 0ad5b62a7afbe0bb5c681d863a8c0e1d777aa6a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 15 Jun 2018 17:44:33 -0400 Subject: [PATCH 01/20] rename *circle keys ... in preparation fo polygon grids to avoid confusion --- src/plots/polar/polar.js | 27 +++++++++++++-------------- src/traces/scatterpolar/plot.js | 2 +- test/jasmine/tests/polar_test.js | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 5b8264194a3..075403e9a1c 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -51,10 +51,10 @@ function Polar(gd, id) { var fullLayout = gd._fullLayout; var clipIdBase = 'clip' + fullLayout._uid + id; - this.clipIds.circle = clipIdBase + '-circle'; - this.clipPaths.circle = fullLayout._clips.append('clipPath') - .attr('id', this.clipIds.circle); - this.clipPaths.circle.append('path'); + this.clipIds.forTraces = clipIdBase + '-for-traces'; + this.clipPaths.forTraces = fullLayout._clips.append('clipPath') + .attr('id', this.clipIds.forTraces); + this.clipPaths.forTraces.append('path'); this.framework = fullLayout._polarlayer.append('g') .attr('class', id); @@ -130,7 +130,7 @@ proto.updateLayers = function(fullLayout, polarLayout) { sel.append('g').classed('maplayer', true); break; case 'plotbg': - layers.bgcircle = sel.append('path'); + layers.bg = sel.append('path'); break; case 'radial-grid': sel.style('fill', 'none'); @@ -235,19 +235,18 @@ proto.updateLayout = function(fullLayout, polarLayout) { xaxis.isPtWithinRange = function(d) { return _this.isPtWithinSector(d); }; yaxis.isPtWithinRange = function() { return true; }; + _this.clipPaths.forTraces.select('path') + .attr('d', pathSectorClosed(radius, sector)) + .attr('transform', strTranslate(cxx, cyy)); + layers.frontplot .attr('transform', strTranslate(xOffset2, yOffset2)) - .call(Drawing.setClipUrl, _this._hasClipOnAxisFalse ? null : _this.clipIds.circle); + .call(Drawing.setClipUrl, _this._hasClipOnAxisFalse ? null : _this.clipIds.forTraces); - layers.bgcircle.attr({ - d: pathSectorClosed(radius, sector), - transform: strTranslate(cx, cy) - }) - .call(Color.fill, polarLayout.bgcolor); - - _this.clipPaths.circle.select('path') + layers.bg .attr('d', pathSectorClosed(radius, sector)) - .attr('transform', strTranslate(cxx, cyy)); + .attr('transform', strTranslate(cx, cy)) + .call(Color.fill, polarLayout.bgcolor); // remove crispEdges - all the off-square angles in polar plots // make these counterproductive. diff --git a/src/traces/scatterpolar/plot.js b/src/traces/scatterpolar/plot.js index 5ad3a019bef..e0018ddfc85 100644 --- a/src/traces/scatterpolar/plot.js +++ b/src/traces/scatterpolar/plot.js @@ -18,7 +18,7 @@ module.exports = function plot(gd, subplot, moduleCalcData) { xaxis: subplot.xaxis, yaxis: subplot.yaxis, plot: subplot.framework, - layerClipId: subplot._hasClipOnAxisFalse ? subplot.clipIds.circle : null + layerClipId: subplot._hasClipOnAxisFalse ? subplot.clipIds.forTraces : null }; var radialAxis = subplot.radialAxis; diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index 32ca0d61b6b..aa299331709 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -522,7 +522,7 @@ describe('Test relayout on polar subplots:', function() { var clipCnt = 0; d3.selectAll('clipPath').each(function() { - if(/polar-circle$/.test(this.id)) clipCnt++; + if(/polar-for-traces/.test(this.id)) clipCnt++; }); expect(clipCnt).toBe(exp.clip, '# clip paths'); } From 7e16134fc9d3468f56f182f4e599138965af87b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 15 Jun 2018 17:49:10 -0400 Subject: [PATCH 02/20] split radial ax autorange into own subroutine ... in preperation for polygon grids where angular axis tick must be computed earlier on. --- src/plots/polar/polar.js | 129 +++++++++++++++++++-------------------- 1 file changed, 62 insertions(+), 67 deletions(-) diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 075403e9a1c..993b286a715 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -207,9 +207,48 @@ proto.updateLayout = function(fullLayout, polarLayout) { var cxx = _this.cxx = cx - xOffset2; var cyy = _this.cyy = cy - yOffset2; + var mockOpts = { + // to get _boundingBox computation right when showticklabels is false + anchor: 'free', + position: 0, + // dummy truthy value to make Axes.doTicksSingle draw the grid + _counteraxis: true, + // don't use automargins routine for labels + automargin: false + }; + + _this.radialAxis = Lib.extendFlat({}, polarLayout.radialaxis, mockOpts, { + _axislayer: layers['radial-axis'], + _gridlayer: layers['radial-grid'], + // make this an 'x' axis to make positioning (especially rotation) easier + _id: 'x', + _pos: 0, + // convert to 'x' axis equivalent + side: { + counterclockwise: 'top', + clockwise: 'bottom' + }[polarLayout.radialaxis.side], + // spans length 1 radius + domain: [0, radius / gs.w] + }); + + _this.angularAxis = Lib.extendFlat({}, polarLayout.angularaxis, mockOpts, { + _axislayer: layers['angular-axis'], + _gridlayer: layers['angular-grid'], + // angular axes need *special* logic + _id: 'angular', + _pos: 0, + side: 'right', + // to get auto nticks right + domain: [0, Math.PI], + // don't pass through autorange logic + autorange: false + }); + + _this.doAutoRange(fullLayout, polarLayout); + _this.updateAngularAxis(fullLayout, polarLayout); _this.updateRadialAxis(fullLayout, polarLayout); _this.updateRadialAxisTitle(fullLayout, polarLayout); - _this.updateAngularAxis(fullLayout, polarLayout); var radialRange = _this.radialAxis.range; var rSpan = radialRange[1] - radialRange[0]; @@ -253,6 +292,17 @@ proto.updateLayout = function(fullLayout, polarLayout) { _this.framework.selectAll('.crisp').classed('crisp', 0); }; +proto.doAutoRange = function(fullLayout, polarLayout) { + var radialLayout = polarLayout.radialaxis; + var ax = this.radialAxis; + + setScale(ax, radialLayout, fullLayout); + doAutoRange(ax); + + radialLayout.range = ax.range.slice(); + radialLayout._input.range = ax.range.slice(); +}; + proto.updateRadialAxis = function(fullLayout, polarLayout) { var _this = this; var gd = _this.gd; @@ -260,42 +310,12 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) { var radius = _this.radius; var cx = _this.cx; var cy = _this.cy; - var gs = fullLayout._size; var radialLayout = polarLayout.radialaxis; var sector = polarLayout.sector; var a0 = wrap360(sector[0]); + var ax = _this.radialAxis; _this.fillViewInitialKey('radialaxis.angle', radialLayout.angle); - - var ax = _this.radialAxis = Lib.extendFlat({}, radialLayout, { - _axislayer: layers['radial-axis'], - _gridlayer: layers['radial-grid'], - - // make this an 'x' axis to make positioning (especially rotation) easier - _id: 'x', - _pos: 0, - - // convert to 'x' axis equivalent - side: {counterclockwise: 'top', clockwise: 'bottom'}[radialLayout.side], - - // spans length 1 radius - domain: [0, radius / gs.w], - - // to get _boundingBox computation right when showticklabels is false - anchor: 'free', - position: 0, - - // dummy truthy value to make Axes.doTicksSingle draw the grid - _counteraxis: true, - - // don't use automargins routine for labels - automargin: false - }); - - setScale(ax, radialLayout, fullLayout); - doAutoRange(ax); - radialLayout.range = ax.range.slice(); - radialLayout._input.range = ax.range.slice(); _this.fillViewInitialKey('radialaxis.range', ax.range.slice()); // rotate auto tick labels by 180 if in quadrant II and III to make them @@ -393,34 +413,20 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { var angularLayout = polarLayout.angularaxis; var sector = polarLayout.sector; var sectorInRad = sector.map(deg2rad); + var ax = _this.angularAxis; _this.fillViewInitialKey('angularaxis.rotation', angularLayout.rotation); - var ax = _this.angularAxis = Lib.extendFlat({}, angularLayout, { - _axislayer: layers['angular-axis'], - _gridlayer: layers['angular-grid'], - - // angular axes need *special* logic - _id: 'angular', - _pos: 0, - side: 'right', - - // to get auto nticks right - domain: [0, Math.PI], - - // to get _boundingBox computation right when showticklabels is false - anchor: 'free', - position: 0, - - // dummy truthy value to make Axes.doTicksSingle draw the grid - _counteraxis: true, - - // don't use automargins routine for labels - automargin: false, + // wrapper around c2rad from setConvertAngular + // note that linear ranges are always set in degrees for Axes.doTicksSingle + function c2rad(d) { + return ax.c2rad(d.x, 'degrees'); + } - // don't pass through autorange logic - autorange: false - }); + // (x,y) at max radius + function rad2xy(rad) { + return [radius * Math.cos(rad), radius * Math.sin(rad)]; + } // Set the angular range in degrees to make auto-tick computation cleaner, // changing rotation/direction should not affect the angular tick labels. @@ -458,17 +464,6 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { setScale(ax, angularLayout, fullLayout); - // wrapper around c2rad from setConvertAngular - // note that linear ranges are always set in degrees for Axes.doTicksSingle - function c2rad(d) { - return ax.c2rad(d.x, 'degrees'); - } - - // (x,y) at max radius - function rad2xy(rad) { - return [radius * Math.cos(rad), radius * Math.sin(rad)]; - } - ax._transfn = function(d) { var rad = c2rad(d); var xy = rad2xy(rad); From 33b05eb882df3250f3615394d5bfdd9a8e242a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 15 Jun 2018 17:52:29 -0400 Subject: [PATCH 03/20] split out angular part of polar.isPtWithinSector into own fn - which speeds up the category angular tick filter, and will help with polygon grids --- src/plots/polar/polar.js | 43 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 993b286a715..066308a54c1 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -453,13 +453,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { angularLayout._categories.length; ax.range = [0, period]; - - ax._tickFilter = function(d) { - return _this.isPtWithinSector({ - r: _this.radialAxis.range[1], - rad: ax.c2rad(d.x) - }); - }; + ax._tickFilter = function(d) { return isAngleInSector(c2rad(d), sector); }; } setScale(ax, angularLayout, fullLayout); @@ -1003,17 +997,15 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { proto.isPtWithinSector = function(d) { var sector = this.sector; + + if(!isAngleInSector(d.rad, sector)) { + return false; + } + var radialAxis = this.radialAxis; var radialRange = radialAxis.range; var r = radialAxis.c2r(d.r); - var s0 = wrap360(sector[0]); - var s1 = wrap360(sector[1]); - if(s0 > s1) s1 += 360; - - var deg = wrap360(rad2deg(d.rad)); - var nextTurnDeg = deg + 360; - var r0, r1; if(radialRange[1] >= radialRange[0]) { r0 = radialRange[0]; @@ -1023,13 +1015,8 @@ proto.isPtWithinSector = function(d) { r1 = radialRange[0]; } - return ( - (r >= r0 && r <= r1) && - (isFullCircle(sector) || - (deg >= s0 && deg <= s1) || - (nextTurnDeg >= s0 && nextTurnDeg <= s1) - ) - ); + + return r >= r0 && r <= r1; }; proto.fillViewInitialKey = function(key, val) { @@ -1110,6 +1097,20 @@ function computeSectorBBox(sector) { return [x0, y0, x1, y1]; } +function isAngleInSector(rad, sector) { + if(isFullCircle(sector)) return true; + + var s0 = wrap360(sector[0]); + var s1 = wrap360(sector[1]); + if(s0 > s1) s1 += 360; + + var deg = wrap360(rad2deg(rad)); + var nextTurnDeg = deg + 360; + + return (deg >= s0 && deg <= s1) || + (nextTurnDeg >= s0 && nextTurnDeg <= s1); +} + function pathSector(r, sector) { if(isFullCircle(sector)) { return Drawing.symbolFuncs[0](r); From c3f5b1baaa2f8ad8a909b44ac9603ab3f09bd7f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 15 Jun 2018 17:59:08 -0400 Subject: [PATCH 04/20] first cut `polar.usepolygons` - this adds polygons grid (and angular ax line) support to categorical angular axes - had to add 'polygon mode' to pathSector() and isPtWithinSector() - polygon vertices match the angular ticks - snaps radialaxis.angle to vertex angles - needed special care for sector and on angular drag --- src/plots/polar/layout_attributes.js | 18 ++- src/plots/polar/layout_defaults.js | 4 + src/plots/polar/polar.js | 225 ++++++++++++++++++++++++--- 3 files changed, 217 insertions(+), 30 deletions(-) diff --git a/src/plots/polar/layout_attributes.js b/src/plots/polar/layout_attributes.js index ba4d8fe25a6..18b0129a0c7 100644 --- a/src/plots/polar/layout_attributes.js +++ b/src/plots/polar/layout_attributes.js @@ -122,11 +122,6 @@ var radialAxisAttrs = { // span: {}, // hole: 1 - // maybe should add a boolean to enable square grid lines - // and square axis lines - // (most common in radar-like charts) - // e.g. squareline/squaregrid or showline/showgrid: 'square' (on-top of true) - editType: 'calc' }; @@ -272,6 +267,19 @@ module.exports = { radialaxis: radialAxisAttrs, angularaxis: angularAxisAttrs, + usepolygons: { + valType: 'boolean', + role: 'style', + editType: 'plot', + dflt: false, + description: [ + '...', + 'Has an effect only when the angular axis has `type` *category*.', + 'Note that `radialaxis.angle` is snapped to the angle of the closest', + 'vertex (so that radial axis scale is the same as the data scale).' + ].join(' ') + }, + // TODO maybe? // annotations: diff --git a/src/plots/polar/layout_defaults.js b/src/plots/polar/layout_defaults.js index ec383eafe69..ddfff41ac7e 100644 --- a/src/plots/polar/layout_defaults.js +++ b/src/plots/polar/layout_defaults.js @@ -177,6 +177,10 @@ function handleDefaults(contIn, contOut, coerce, opts) { axOut._input = axIn; } + + if(contOut.angularaxis.type === 'category') { + coerce('usepolygons'); + } } function handleAxisTypeDefaults(axIn, axOut, coerce, subplotData, dataAttr) { diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 066308a54c1..d6b3ea3b8c0 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -25,6 +25,7 @@ var Titles = require('../../components/titles'); var prepSelect = require('../cartesian/select').prepSelect; var clearSelect = require('../cartesian/select').clearSelect; var setCursor = require('../../lib/setcursor'); +var polygonTester = require('../../lib/polygon').tester; var MID_SHIFT = require('../../constants/alignment').MID_SHIFT; @@ -42,6 +43,8 @@ function Polar(gd, id) { this.gd = gd; this._hasClipOnAxisFalse = null; + this.vangles = null; + this.radialAxisAngle = null; this.traceHash = {}; this.layers = {}; this.clipPaths = {}; @@ -246,7 +249,9 @@ proto.updateLayout = function(fullLayout, polarLayout) { }); _this.doAutoRange(fullLayout, polarLayout); + // N.B. this sets _this.vangles _this.updateAngularAxis(fullLayout, polarLayout); + // N.B. this sets _this.radialAxisAngle _this.updateRadialAxis(fullLayout, polarLayout); _this.updateRadialAxisTitle(fullLayout, polarLayout); @@ -334,7 +339,7 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) { // set special grid path function ax._gridpath = function(d) { var r = ax.r2p(d.x); - return pathSector(r, sector); + return pathSector(r, sector, _this.vangles); }; var newTickLayout = strTickLayout(radialLayout); @@ -345,8 +350,15 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) { Axes.doTicksSingle(gd, ax, true); + // stash 'actual' radial axis angle for drag handlers (in degrees) + var angle = _this.radialAxisAngle = _this.vangles ? + rad2deg(snapToVertexAngle(deg2rad(radialLayout.angle), _this.vangles)) : + radialLayout.angle; + + var trans = strTranslate(cx, cy) + strRotate(-angle); + updateElement(layers['radial-axis'], radialLayout.showticklabels || radialLayout.ticks, { - transform: strTranslate(cx, cy) + strRotate(-radialLayout.angle) + transform: trans }); // move all grid paths to about circle center, @@ -361,7 +373,7 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) { y1: 0, x2: radius, y2: 0, - transform: strTranslate(cx, cy) + strRotate(-radialLayout.angle) + transform: trans }) .attr('stroke-width', radialLayout.linewidth) .call(Color.stroke, radialLayout.linecolor); @@ -376,7 +388,7 @@ proto.updateRadialAxisTitle = function(fullLayout, polarLayout, _angle) { var radialLayout = polarLayout.radialaxis; var titleClass = _this.id + 'title'; - var angle = _angle !== undefined ? _angle : radialLayout.angle; + var angle = _angle !== undefined ? _angle : _this.radialAxisAngle; var angleRad = deg2rad(angle); var cosa = Math.cos(angleRad); var sina = Math.sin(angleRad); @@ -520,8 +532,12 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { Axes.doTicksSingle(gd, ax, true); + // angle of polygon vertices in radians (null means circles) + // TODO what to do when ax.period > ax._categories ?? + _this.vangles = polarLayout.usepolygons ? ax._vals.map(c2rad) : null; + updateElement(layers['angular-line'].select('path'), angularLayout.showline, { - d: pathSectorClosed(radius, sector), + d: pathSectorClosed(radius, sector, _this.vangles), transform: strTranslate(cx, cy) }) .attr('stroke-width', angularLayout.linewidth) @@ -549,11 +565,12 @@ proto.updateMainDrag = function(fullLayout, polarLayout) { var cxx = _this.cxx; var cyy = _this.cyy; var sector = polarLayout.sector; + var vangles = _this.vangles; var mainDrag = dragBox.makeDragger(layers, 'path', 'maindrag', 'crosshair'); d3.select(mainDrag) - .attr('d', pathSectorClosed(radius, sector)) + .attr('d', pathSectorClosed(radius, sector, vangles)) .attr('transform', strTranslate(cx, cy)); var dragOpts = { @@ -614,7 +631,7 @@ proto.updateMainDrag = function(fullLayout, polarLayout) { function zoomPrep() { r0 = null; r1 = null; - path0 = pathSectorClosed(radius, sector); + path0 = pathSectorClosed(radius, sector, vangles); dimmed = false; var polarLayoutNow = gd._fullLayout[_this.id]; @@ -656,7 +673,7 @@ proto.updateMainDrag = function(fullLayout, polarLayout) { a1 = [a0, a0 = a1][0]; // swap a0 and a1 } - path1 = path0 + pathSectorClosed(r1, sector) + pathSectorClosed(r0, sector); + path1 = path0 + pathSectorClosed(r1, sector, vangles) + pathSectorClosed(r0, sector, vangles); cpath = pathCorner(r0, a0) + pathCorner(r1, a1); } else { r0 = null; @@ -750,7 +767,7 @@ proto.updateRadialDrag = function(fullLayout, polarLayout) { var cy = _this.cy; var radialAxis = _this.radialAxis; var radialLayout = polarLayout.radialaxis; - var angle0 = deg2rad(radialLayout.angle); + var angle0 = deg2rad(_this.radialAxisAngle); var range0 = radialAxis.range.slice(); var drange = range0[1] - range0[0]; var bl = constants.radialDragBoxSize; @@ -801,7 +818,9 @@ proto.updateRadialDrag = function(fullLayout, polarLayout) { var x1 = tx + dx; var y1 = ty + dy; - angle1 = rad2deg(Math.atan2(cy - y1, x1 - cx)); + angle1 = Math.atan2(cy - y1, x1 - cx); + if(_this.vangles) angle1 = snapToVertexAngle(angle1, _this.vangles); + angle1 = rad2deg(angle1); var transform = strTranslate(cx, cy) + strRotate(-angle1); layers['radial-axis'].attr('transform', transform); @@ -904,6 +923,8 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { var x0, y0; // angular axis angle rotation at drag start (0), move (1) var rot0, rot1; + // induced radial axis rotation (only used on polygon grids) + var rrot1; // copy of polar sector value at drag start var sector0; // angle about circle center at drag start @@ -920,9 +941,22 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { strTranslate(_this.xOffset2, _this.yOffset2) + strRotate([-da, cxx, cyy]) ); - _this.clipPaths.circle.select('path').attr('transform', - strTranslate(cxx, cyy) + strRotate(da) - ); + if(_this.vangles) { + rrot1 = _this.radialAxisAngle + da; + + var trans = strTranslate(cx, cy) + strRotate(-da); + var trans2 = strTranslate(cx, cy) + strRotate(-rrot1); + + layers.bg.attr('transform', trans); + layers['radial-grid'].attr('transform', trans); + layers['angular-line'].select('path').attr('transform', trans); + layers['radial-axis'].attr('transform', trans2); + layers['radial-line'].select('line').attr('transform', trans2); + } else { + _this.clipPaths.forTraces.select('path').attr('transform', + strTranslate(cxx, cyy) + strRotate(da) + ); + } // 'un-rotate' marker and text points scatterPoints.each(function() { @@ -971,8 +1005,14 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { function doneFn() { scatterTextPoints.select('text').attr('transform', null); + var updateObj = {}; updateObj[_this.id + '.angularaxis.rotation'] = rot1; + + if(_this.vangles) { + updateObj[_this.id + '.radialaxis.angle'] = rrot1; + } + Registry.call('relayout', gd, updateObj); } @@ -992,6 +1032,12 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { clearSelect(fullLayout._zoomlayer); }; + // I don't what we should do in this case, skip we now + if(_this.vangles && !isFullCircle(sector)) { + dragOpts.prepFn = Lib.noop; + setCursor(d3.select(angularDrag), null); + } + dragElement.init(dragOpts); }; @@ -1002,6 +1048,7 @@ proto.isPtWithinSector = function(d) { return false; } + var vangles = this.vangles; var radialAxis = this.radialAxis; var radialRange = radialAxis.range; var r = radialAxis.c2r(d.r); @@ -1015,6 +1062,12 @@ proto.isPtWithinSector = function(d) { r1 = radialRange[0]; } + if(vangles) { + var polygonIn = polygonTester(makePolygon(r0, sector, vangles)); + var polygonOut = polygonTester(makePolygon(r1, sector, vangles)); + var xy = [r * Math.cos(d.rad), r * Math.sin(d.rad)]; + return polygonOut.contains(xy) && !polygonIn.contains(xy); + } return r >= r0 && r <= r1; }; @@ -1111,25 +1164,147 @@ function isAngleInSector(rad, sector) { (nextTurnDeg >= s0 && nextTurnDeg <= s1); } -function pathSector(r, sector) { +function snapToVertexAngle(a, vangles) { + function angleDeltaAbs(va) { + return Math.abs(angleDelta(a, va)); + } + + var ind = findIndexOfMin(vangles, angleDeltaAbs); + return vangles[ind]; +} + +// taken from https://stackoverflow.com/a/2007279 +function angleDelta(a, b) { + var d = b - a; + return Math.atan2(Math.sin(d), Math.cos(d)); +} + +function findIndexOfMin(arr, fn) { + fn = fn || Lib.identity; + + var min = Infinity; + var ind; + + for(var i = 0; i < arr.length; i++) { + var v = fn(arr[i]); + if(v < min) { + min = v; + ind = i; + } + } + return ind; +} + +// find intersection of v0 <-> v1 edge with (r,s) sector radius +// in (x,y) coordinates +function findIntersectionXY(v0, v1, s, r) { + var as = Math.sin(s) / Math.cos(s); + var av = (Math.sin(v1) - Math.sin(v0)) / (Math.cos(v1) - Math.cos(v0)); + var bv = r * (Math.sin(v1) - av * Math.cos(v1)); + var x = bv / (as - av); + var y = as * x; + return [x, y]; +} + +function makePolygon(r, sector, vangles) { + var len = vangles.length; + var vertices, i; + + function a2xy(a) { + return [r * Math.cos(a), r * Math.sin(a)]; + } + + function cycleIndex(i) { + return i < 0 ? len + i : + i < len ? i : i - len; + } + if(isFullCircle(sector)) { - return Drawing.symbolFuncs[0](r); + vertices = new Array(len + 1); + for(i = 0; i < len; i++) { + vertices[i] = a2xy(vangles[i]); + } + vertices[i] = vertices[0].slice(); + } else { + vertices = []; + + // vangles should be always ordered, make them + // always turn counterclockwise here + var _vangles; + if(angleDelta(vangles[0], vangles[1]) > 0) { + _vangles = vangles; + } else { + _vangles = vangles.slice().reverse(); + } + + var s0 = deg2rad(sector[0]); + var s1 = deg2rad(sector[1]); + + // find index in sector closest to sector[0], + // use it to find intersection of v[i0] <-> v[i0-1] edge with sector radius + var i0 = findIndexOfMin(_vangles, function(v) { + return isAngleInSector(v, sector) ? Math.abs(angleDelta(v, s0)) : Infinity; + }); + var xy0 = findIntersectionXY(_vangles[i0], _vangles[cycleIndex(i0 - 1)], s0, r); + vertices.push(xy0); + + // fill in in-sector vertices + var n = 0; + for(i = i0; n < 1000; i++, n++) { + var va = _vangles[cycleIndex(i)]; + if(!isAngleInSector(va, sector)) break; + vertices.push(a2xy(va)); + } + + // find index in sector closest to sector[1], + // use it to find intersection of v[iN] <-> v[iN+1] edge with sector radius + var iN = findIndexOfMin(_vangles, function(v) { + return isAngleInSector(v, sector) ? Math.abs(angleDelta(v, s1)) : Infinity; + }); + var xyN = findIntersectionXY(_vangles[iN], _vangles[cycleIndex(iN + 1)], s1, r); + vertices.push(xyN); + + vertices.push([0, 0]); + vertices.push(vertices[0].slice()); } - var xs = r * Math.cos(deg2rad(sector[0])); - var ys = -r * Math.sin(deg2rad(sector[0])); - var xe = r * Math.cos(deg2rad(sector[1])); - var ye = -r * Math.sin(deg2rad(sector[1])); + return vertices; +} - var arc = Math.abs(sector[1] - sector[0]); - var flags = arc <= 180 ? [0, 0, 0] : [0, 1, 0]; +function invertY(pts0) { + var len = pts0.length; + var pts1 = new Array(len); + for(var i = 0; i < len; i++) { + var pt = pts0[i]; + pts1[i] = [pt[0], -pt[1]]; + } + return pts1; +} + +function pathSector(r, sector, vangles) { + var d; + + if(vangles) { + d = 'M' + invertY(makePolygon(r, sector, vangles)).join('L'); + } else if(isFullCircle(sector)) { + d = Drawing.symbolFuncs[0](r); + } else { + var arc = Math.abs(sector[1] - sector[0]); + var flags = arc <= 180 ? [0, 0, 0] : [0, 1, 0]; + var xs = r * Math.cos(deg2rad(sector[0])); + var ys = -r * Math.sin(deg2rad(sector[0])); + var xe = r * Math.cos(deg2rad(sector[1])); + var ye = -r * Math.sin(deg2rad(sector[1])); + + d = 'M' + [xs, ys] + + 'A' + [r, r] + ' ' + flags + ' ' + [xe, ye]; + } - return 'M' + [xs, ys] + - 'A' + [r, r] + ' ' + flags + ' ' + [xe, ye]; + return d; } -function pathSectorClosed(r, sector) { - return pathSector(r, sector) + +function pathSectorClosed(r, sector, nside) { + return pathSector(r, sector, nside) + (isFullCircle(sector) ? '' : 'L0,0Z'); } From 24276b3074e6a52f29fa3ae5c0e9de65916a8c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 15 Jun 2018 17:59:18 -0400 Subject: [PATCH 05/20] add polar polygon grid mock --- test/image/baselines/polar_polygon-grids.png | Bin 0 -> 84193 bytes test/image/mocks/polar_polygon-grids.json | 103 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 test/image/baselines/polar_polygon-grids.png create mode 100644 test/image/mocks/polar_polygon-grids.json diff --git a/test/image/baselines/polar_polygon-grids.png b/test/image/baselines/polar_polygon-grids.png new file mode 100644 index 0000000000000000000000000000000000000000..4c06a410fccfa56d17cb7450b6bb7aed0efb9a9b GIT binary patch literal 84193 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