diff --git a/src/lib/angles.js b/src/lib/angles.js index ccfd1c83ffd..b5dd27f28f9 100644 --- a/src/lib/angles.js +++ b/src/lib/angles.js @@ -27,3 +27,8 @@ exports.wrap180 = function(deg) { if(Math.abs(deg) > 180) deg -= Math.round(deg / 360) * 360; return deg; }; + +exports.isFullCircle = function(sector) { + var arc = Math.abs(sector[1] - sector[0]); + return arc === 360; +}; diff --git a/src/lib/index.js b/src/lib/index.js index 9df929ce3e4..abe8e1a8fa8 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -87,6 +87,7 @@ lib.deg2rad = anglesModule.deg2rad; lib.rad2deg = anglesModule.rad2deg; lib.wrap360 = anglesModule.wrap360; lib.wrap180 = anglesModule.wrap180; +lib.isFullCircle = anglesModule.isFullCircle; var geom2dModule = require('./geometry2d'); lib.segmentsIntersect = geom2dModule.segmentsIntersect; diff --git a/src/plots/polar/helpers.js b/src/plots/polar/helpers.js deleted file mode 100644 index 6a85594d1a5..00000000000 --- a/src/plots/polar/helpers.js +++ /dev/null @@ -1,61 +0,0 @@ -/** -* Copyright 2012-2018, Plotly, Inc. -* All rights reserved. -* -* This source code is licensed under the MIT license found in the -* LICENSE file in the root directory of this source tree. -*/ - -'use strict'; - -var Lib = require('../../lib'); - -exports.setConvertAngular = function setConvertAngular(ax) { - var dir = {clockwise: -1, counterclockwise: 1}[ax.direction]; - var rot = Lib.deg2rad(ax.rotation); - var _c2rad; - var _rad2c; - - function getTotalNumberOfCategories() { - return ax.period ? - Math.max(ax.period, ax._categories.length) : - ax._categories.length; - } - - if(ax.type === 'linear') { - _c2rad = function(v, unit) { - if(unit === 'degrees') return Lib.deg2rad(v); - return v; - }; - _rad2c = function(v, unit) { - if(unit === 'degrees') return Lib.rad2deg(v); - return v; - }; - } - else if(ax.type === 'category') { - _c2rad = function(v) { - var tot = getTotalNumberOfCategories(); - return v * 2 * Math.PI / tot; - }; - _rad2c = function(v) { - var tot = getTotalNumberOfCategories(); - return v * tot / Math.PI / 2; - }; - } - - function transformRad(v) { return dir * v + rot; } - function unTransformRad(v) { return (v - rot) / dir; } - - // use the shift 'sector' to get right tick labels for non-default - // angularaxis 'rotation' and/or 'direction' - ax.unTransformRad = unTransformRad; - - // this version is used on hover - ax._c2rad = _c2rad; - - ax.c2rad = function(v, unit) { return transformRad(_c2rad(v, unit)); }; - ax.rad2c = function(v, unit) { return _rad2c(unTransformRad(v), unit); }; - - ax.c2deg = function(v, unit) { return Lib.rad2deg(ax.c2rad(v, unit)); }; - ax.deg2c = function(v, unit) { return ax.rad2c(Lib.deg2rad(v), unit); }; -}; diff --git a/src/plots/polar/layout_defaults.js b/src/plots/polar/layout_defaults.js index 850301deef2..1b89e221c7f 100644 --- a/src/plots/polar/layout_defaults.js +++ b/src/plots/polar/layout_defaults.js @@ -19,10 +19,9 @@ var handleTickLabelDefaults = require('../cartesian/tick_label_defaults'); var handleCategoryOrderDefaults = require('../cartesian/category_order_defaults'); var handleLineGridDefaults = require('../cartesian/line_grid_defaults'); var autoType = require('../cartesian/axis_autotype'); -var setConvert = require('../cartesian/set_convert'); -var setConvertAngular = require('./helpers').setConvertAngular; var layoutAttributes = require('./layout_attributes'); +var setConvert = require('./set_convert'); var constants = require('./constants'); var axisNames = constants.axisNames; @@ -66,7 +65,7 @@ function handleDefaults(contIn, contOut, coerce, opts) { }); var visible = coerceAxis('visible'); - setConvert(axOut, layoutOut); + setConvert(axOut, contOut, layoutOut); var dfltColor; var dfltFontColor; @@ -140,8 +139,6 @@ function handleDefaults(contIn, contOut, coerce, opts) { var direction = coerceAxis('direction'); coerceAxis('rotation', {counterclockwise: 0, clockwise: 90}[direction]); - - setConvertAngular(axOut); break; } diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 97af7bd7d25..01f838c978a 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -17,6 +17,7 @@ var Color = require('../../components/color'); var Drawing = require('../../components/drawing'); var Plots = require('../plots'); var setConvertCartesian = require('../cartesian/set_convert'); +var setConvertPolar = require('./set_convert'); var doAutoRange = require('../cartesian/autorange').doAutoRange; var doTicksSingle = require('../cartesian/axes').doTicksSingle; var dragBox = require('../cartesian/dragbox'); @@ -29,15 +30,14 @@ var setCursor = require('../../lib/setcursor'); var polygonTester = require('../../lib/polygon').tester; var MID_SHIFT = require('../../constants/alignment').MID_SHIFT; +var constants = require('./constants'); var _ = Lib._; var deg2rad = Lib.deg2rad; var rad2deg = Lib.rad2deg; var wrap360 = Lib.wrap360; var wrap180 = Lib.wrap180; - -var setConvertAngular = require('./helpers').setConvertAngular; -var constants = require('./constants'); +var isFullCircle = Lib.isFullCircle; function Polar(gd, id) { this.id = id; @@ -161,6 +161,9 @@ proto.updateLayout = function(fullLayout, polarLayout) { var layers = _this.layers; var gs = fullLayout._size; + // axis attributes + var radialLayout = polarLayout.radialaxis; + var angularLayout = polarLayout.angularaxis; // layout domains var xDomain = polarLayout.domain.x; var yDomain = polarLayout.domain.y; @@ -211,37 +214,25 @@ 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, { + _this.radialAxis = _this.mockAxis(fullLayout, polarLayout, 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' - }[polarLayout.radialaxis.side], + }[radialLayout.side], // spans length 1 radius domain: [0, radius / gs.w] }); - _this.angularAxis = Lib.extendFlat({}, polarLayout.angularaxis, mockOpts, { + _this.angularAxis = _this.mockAxis(fullLayout, polarLayout, 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], @@ -298,6 +289,23 @@ proto.updateLayout = function(fullLayout, polarLayout) { _this.framework.selectAll('.crisp').classed('crisp', 0); }; +proto.mockAxis = function(fullLayout, polarLayout, axLayout, opts) { + var commonOpts = { + // to get _boundingBox computation right when showticklabels is false + anchor: 'free', + position: 0, + _pos: 0, + // dummy truthy value to make doTicksSingle draw the grid + _counteraxis: true, + // don't use automargins routine for labels + automargin: false + }; + + var ax = Lib.extendFlat(commonOpts, axLayout, opts); + setConvertPolar(ax, polarLayout, fullLayout); + return ax; +}; + proto.doAutoRange = function(fullLayout, polarLayout) { var gd = this.gd; var radialAxis = this.radialAxis; @@ -326,6 +334,8 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) { _this.fillViewInitialKey('radialaxis.angle', radialLayout.angle); _this.fillViewInitialKey('radialaxis.range', ax.range.slice()); + ax.setGeometry(); + // rotate auto tick labels by 180 if in quadrant II and III to make them // readable from left-to-right // @@ -428,52 +438,35 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { var cy = _this.cy; var angularLayout = polarLayout.angularaxis; var sector = polarLayout.sector; - var sectorInRad = sector.map(deg2rad); var ax = _this.angularAxis; _this.fillViewInitialKey('angularaxis.rotation', angularLayout.rotation); - // 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'); - } + ax.setGeometry(); + + // 't'ick to 'g'eometric radians is used all over the place here + var t2g = function(d) { return ax.t2g(d.x); }; // (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. - if(ax.type === 'linear') { - if(isFullCircle(sector)) { - ax.range = sector.slice(); - } else { - ax.range = sectorInRad.map(ax.unTransformRad).map(rad2deg); - } - - // run rad2deg on tick0 and ditck for thetaunit: 'radians' axes - if(ax.thetaunit === 'radians') { - ax.tick0 = rad2deg(ax.tick0); - ax.dtick = rad2deg(ax.dtick); - } - + // run rad2deg on tick0 and ditck for thetaunit: 'radians' axes + if(ax.type === 'linear' && ax.thetaunit === 'radians') { + ax.tick0 = rad2deg(ax.tick0); + ax.dtick = rad2deg(ax.dtick); } + // Use tickval filter for category axes instead of tweaking // the range w.r.t sector, so that sectors that cross 360 can // show all their ticks. - else if(ax.type === 'category') { - var period = angularLayout.period ? - Math.max(angularLayout.period, angularLayout._categories.length) : - angularLayout._categories.length; - - ax.range = [0, period]; - ax._tickFilter = function(d) { return isAngleInSector(c2rad(d), sector); }; + if(ax.type === 'category') { + ax._tickFilter = function(d) { return isAngleInSector(t2g(d), sector); }; } ax._transfn = function(d) { - var rad = c2rad(d); + var rad = t2g(d); var xy = rad2xy(rad); var out = strTranslate(cx + xy[0], cy - xy[1]); @@ -487,7 +480,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { }; ax._gridpath = function(d) { - var rad = c2rad(d); + var rad = t2g(d); var xy = rad2xy(rad); return 'M0,0L' + (-xy[0]) + ',' + xy[1]; }; @@ -495,7 +488,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { var offset4fontsize = (angularLayout.ticks !== 'outside' ? 0.7 : 0.5); ax._labelx = function(d) { - var rad = c2rad(d); + var rad = t2g(d); var labelStandoff = ax._labelStandoff; var pad = ax._pad; @@ -508,7 +501,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { }; ax._labely = function(d) { - var rad = c2rad(d); + var rad = t2g(d); var labelStandoff = ax._labelStandoff; var labelShift = ax._labelShift; var pad = ax._pad; @@ -520,7 +513,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { }; ax._labelanchor = function(angle, d) { - var rad = c2rad(d); + var rad = t2g(d); return signSin(rad) === 0 ? (signCos(rad) > 0 ? 'start' : 'end') : 'middle'; @@ -535,11 +528,11 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { ax.setScale(); doTicksSingle(gd, ax, true); - // angle of polygon vertices in radians (null means circles) + // angle of polygon vertices in geometric radians (null means circles) // TODO what to do when ax.period > ax._categories ?? var vangles; if(polarLayout.gridshape === 'linear') { - vangles = ax._vals.map(c2rad); + vangles = ax._vals.map(t2g); // ax._vals should be always ordered, make them // always turn counterclockwise for convenience here @@ -1014,6 +1007,7 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { var gd = _this.gd; var layers = _this.layers; var radius = _this.radius; + var angularAxis = _this.angularAxis; var cx = _this.cx; var cy = _this.cy; var cxx = _this.cxx; @@ -1054,8 +1048,6 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { 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 var a0; @@ -1104,22 +1096,14 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { sel.attr('transform', strRotate([da, tx.attr('x'), tx.attr('y')]) + strTranslate(xy.x, xy.y)); }); - var angularAxis = _this.angularAxis; + // update rotation -> range -> _m,_b angularAxis.rotation = wrap180(rot1); + angularAxis.setGeometry(); + angularAxis.setScale(); - if(angularAxis.type === 'linear' && !isFullCircle(sector)) { - angularAxis.range = sector0 - .map(deg2rad) - .map(angularAxis.unTransformRad) - .map(rad2deg); - } - - setConvertAngular(angularAxis); doTicksSingle(gd, angularAxis, true); if(_this._hasClipOnAxisFalse && !isFullCircle(sector)) { - // mutate sector to trick isPtWithinSector - _this.sector = [sector0[0] - da, sector0[1] - da]; scatterTraces.call(Drawing.hideOutsideRangePoints, _this); } @@ -1148,7 +1132,6 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { dragOpts.prepFn = function(evt, startX, startY) { var polarLayoutNow = fullLayout[_this.id]; - sector0 = polarLayoutNow.sector.slice(); rot0 = polarLayoutNow.angularaxis.rotation; var bbox = angularDrag.getBoundingClientRect(); @@ -1173,8 +1156,9 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { proto.isPtWithinSector = function(d) { var sector = this.sector; + var thetag = this.angularAxis.c2g(d.theta); - if(!isAngleInSector(d.rad, sector)) { + if(!isAngleInSector(thetag, sector)) { return false; } @@ -1195,7 +1179,7 @@ proto.isPtWithinSector = function(d) { 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)]; + var xy = [r * Math.cos(thetag), r * Math.sin(thetag)]; return polygonOut.contains(xy) && !polygonIn.contains(xy); } @@ -1539,10 +1523,6 @@ function pathAnnulus(r0, r1, sector) { } } -function isFullCircle(sector) { - var arc = Math.abs(sector[1] - sector[0]); - return arc === 360; -} function updateElement(sel, showAttr, attrs) { if(showAttr) { diff --git a/src/plots/polar/set_convert.js b/src/plots/polar/set_convert.js new file mode 100644 index 00000000000..ac930aa9de2 --- /dev/null +++ b/src/plots/polar/set_convert.js @@ -0,0 +1,169 @@ +/** +* Copyright 2012-2018, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var Lib = require('../../lib'); +var setConvertCartesian = require('../cartesian/set_convert'); + +var deg2rad = Lib.deg2rad; +var rad2deg = Lib.rad2deg; +var isFullCircle = Lib.isFullCircle; + +/** + * setConvert for polar axes! + * + * @param {object} ax + * axis in question (works for both radial and angular axes) + * @param {object} polarLayout + * full polar layout of the subplot associated with 'ax' + * @param {object} fullLayout + * full layout + * + * Here, reuse some of the Cartesian setConvert logic, + * but we must extend some of it, as both radial and angular axes + * don't have domains and angular axes don't have _true_ ranges. + * + * Moreover, we introduce two new coordinate systems: + * - 'g' for geometric coordinates and + * - 't' for angular ticks + * + * Radial axis coordinate systems flow: + * - d2c (in calc) just like for cartesian axes + * - c2g (in plot) translates calcdata about `radialaxis.range[0]` + * + * Angular axis coordinate systems flow: + * + for linear axes: + * - d2c (in calc) angles -> 'data' radians + * - c2g (in plot) 'data' -> 'geometric' radians (fn of ax rotation & direction) + * - t2g (in updateAngularAxis) 'tick' value (in degrees) -> 'geometric' radians + * + for category axes: + * - d2c (in calc) just like for cartesian axes + * - c2g (in plot) category indices -> 'geometric' radians + * - t2g (in updateAngularAxis) 'tick' value (as category indices) -> 'geometric' radians + * + * Then, 'g'eometric data is ready to be converted to (x,y). + */ +module.exports = function setConvert(ax, polarLayout, fullLayout) { + setConvertCartesian(ax, fullLayout); + + switch(ax._id) { + case 'x': + case 'radialaxis': + setConvertRadial(ax); + break; + case 'angular': + case 'angularaxis': + setConvertAngular(ax, polarLayout); + break; + } +}; + +function setConvertRadial(ax) { + ax.setGeometry = function() { + var rng = ax.range; + + var rFilter = rng[0] > rng[1] ? + function(v) { return v <= 0; } : + function(v) { return v >= 0; }; + + ax.c2g = function(v) { + var r = ax.c2r(v) - rng[0]; + return rFilter(r) ? r : 0; + }; + + ax.g2c = function(v) { + return ax.r2c(v + rng[0]); + }; + }; +} + +function toRadians(v, unit) { + return unit === 'degrees' ? deg2rad(v) : v; +} + +function fromRadians(v, unit) { + return unit === 'degrees' ? rad2deg(v) : v; +} + +function setConvertAngular(ax, polarLayout) { + var axType = ax.type; + + if(axType === 'linear') { + var _d2c = ax.d2c; + var _c2d = ax.c2d; + + ax.d2c = function(v, unit) { return toRadians(_d2c(v), unit); }; + ax.c2d = function(v, unit) { return _c2d(fromRadians(v, unit)); }; + } + + // override makeCalcdata to handle thetaunit and special theta0/dtheta logic + ax.makeCalcdata = function(trace, coord) { + var arrayIn = trace[coord]; + var len = trace._length; + var arrayOut, i; + + var _d2c = function(v) { return ax.d2c(v, trace.thetaunit); }; + + if(arrayIn) { + if(Lib.isTypedArray(arrayIn) && axType === 'linear') { + if(len === arrayIn.length) { + return arrayIn; + } else if(arrayIn.subarray) { + return arrayIn.subarray(0, len); + } + } + + arrayOut = new Array(len); + for(i = 0; i < len; i++) { + arrayOut[i] = _d2c(arrayIn[i]); + } + } + return arrayOut; + }; + + // N.B. we mock the axis 'range' here + ax.setGeometry = function() { + var sector = polarLayout.sector; + var dir = {clockwise: -1, counterclockwise: 1}[ax.direction]; + var rot = deg2rad(ax.rotation); + + ax.rad2g = function(v) { return dir * v + rot; }; + ax.g2rad = function(v) { return (v - rot) / dir; }; + + switch(axType) { + case 'linear': + ax.c2rad = ax.rad2c = Lib.identity; + ax.t2rad = deg2rad; + ax.rad2r = rad2deg; + + // Set the angular range in degrees to make auto-tick computation cleaner, + // changing rotation/direction should not affect the angular tick value. + ax.range = isFullCircle(sector) ? + sector.slice() : + sector.map(deg2rad).map(ax.g2rad).map(rad2deg); + break; + + case 'category': + var catLen = ax._categories.length; + var _period = ax._period = ax.period ? Math.max(ax.period, catLen) : catLen; + + ax.c2rad = ax.t2rad = function(v) { return v * 2 * Math.PI / _period; }; + ax.rad2c = ax.rad2t = function(v) { return v * _period / Math.PI / 2; }; + + ax.range = [0, _period]; + break; + } + + ax.c2g = function(v) { return ax.rad2g(ax.c2rad(v)); }; + ax.g2c = function(v) { return ax.rad2c(ax.g2rad(v)); }; + + ax.t2g = function(v) { return ax.rad2g(ax.t2rad(v)); }; + ax.g2t = function(v) { return ax.rad2t(ax.g2rad(v)); }; + }; +} diff --git a/src/traces/scatterpolar/calc.js b/src/traces/scatterpolar/calc.js index f2f10dd7f2d..69d1cd55600 100644 --- a/src/traces/scatterpolar/calc.js +++ b/src/traces/scatterpolar/calc.js @@ -29,10 +29,6 @@ module.exports = function calc(gd, trace) { var len = trace._length; var cd = new Array(len); - function c2rad(v) { - return angularAxis.c2rad(v, trace.thetaunit); - } - for(var i = 0; i < len; i++) { var r = rArray[i]; var theta = thetaArray[i]; @@ -41,7 +37,6 @@ module.exports = function calc(gd, trace) { if(isNumeric(r) && isNumeric(theta)) { cdi.r = r; cdi.theta = theta; - cdi.rad = c2rad(theta); } else { cdi.r = BADNUM; } diff --git a/src/traces/scatterpolar/hover.js b/src/traces/scatterpolar/hover.js index 8202724a332..6eaad2950c0 100644 --- a/src/traces/scatterpolar/hover.js +++ b/src/traces/scatterpolar/hover.js @@ -46,23 +46,21 @@ function makeHoverPointText(cdi, trace, subplot) { radialAxis._hovertitle = 'r'; angularAxis._hovertitle = 'θ'; - var rad = angularAxis._c2rad(cdi.theta, trace.thetaunit); - - // show theta value in unit of angular axis - var theta; - if(angularAxis.type === 'linear' && trace.thetaunit !== angularAxis.thetaunit) { - theta = angularAxis.thetaunit === 'degrees' ? Lib.rad2deg(rad) : rad; - } else { - theta = cdi.theta; - } - function textPart(ax, val) { text.push(ax._hovertitle + ': ' + Axes.tickText(ax, val, 'hover').text); } if(parts.indexOf('all') !== -1) parts = ['r', 'theta']; - if(parts.indexOf('r') !== -1) textPart(radialAxis, radialAxis.c2r(cdi.r)); - if(parts.indexOf('theta') !== -1) textPart(angularAxis, theta); + if(parts.indexOf('r') !== -1) { + textPart(radialAxis, radialAxis.c2r(cdi.r)); + } + if(parts.indexOf('theta') !== -1) { + var theta = cdi.theta; + textPart( + angularAxis, + angularAxis.thetaunit === 'degrees' ? Lib.rad2deg(theta) : theta + ); + } return text.join('
'); } diff --git a/src/traces/scatterpolar/plot.js b/src/traces/scatterpolar/plot.js index e0018ddfc85..cde1dd613be 100644 --- a/src/traces/scatterpolar/plot.js +++ b/src/traces/scatterpolar/plot.js @@ -12,7 +12,7 @@ var scatterPlot = require('../scatter/plot'); var BADNUM = require('../../constants/numerical').BADNUM; module.exports = function plot(gd, subplot, moduleCalcData) { - var i, j; + var mlayer = subplot.layers.frontplot.select('g.scatterlayer'); var plotinfo = { xaxis: subplot.xaxis, @@ -22,45 +22,27 @@ module.exports = function plot(gd, subplot, moduleCalcData) { }; var radialAxis = subplot.radialAxis; - var radialRange = radialAxis.range; - var rFilter; - - if(radialRange[0] > radialRange[1]) { - rFilter = function(v) { return v <= 0; }; - } else { - rFilter = function(v) { return v >= 0; }; - } - - // map (r, theta) first to a 'geometric' r and then to (x,y) - // on-par with what scatterPlot expects. - - for(i = 0; i < moduleCalcData.length; i++) { - for(j = 0; j < moduleCalcData[i].length; j++) { - var cdi = moduleCalcData[i][j]; - var r = cdi.r; - - if(r !== BADNUM) { - // convert to 'r' data to fit with mocked polar x/y axis - // which are always `type: 'linear'` - var rr = radialAxis.c2r(r) - radialRange[0]; - if(rFilter(rr)) { - var rad = cdi.rad; - cdi.x = rr * Math.cos(rad); - cdi.y = rr * Math.sin(rad); - continue; - } else { - // flag for scatter/line_points.js - // to extend line (and fills) into center - cdi.intoCenter = [subplot.cxx, subplot.cyy]; - } + var angularAxis = subplot.angularAxis; + + // convert: + // 'c' (r,theta) -> 'geometric' (r,theta) -> (x,y) + for(var i = 0; i < moduleCalcData.length; i++) { + var cdi = moduleCalcData[i]; + + for(var j = 0; j < cdi.length; j++) { + var cd = cdi[j]; + var r = cd.r; + + if(r === BADNUM) { + cd.x = cd.y = BADNUM; + } else { + var rg = radialAxis.c2g(r); + var thetag = angularAxis.c2g(cd.theta); + cd.x = rg * Math.cos(thetag); + cd.y = rg * Math.sin(thetag); } - - cdi.x = BADNUM; - cdi.y = BADNUM; } } - var scatterLayer = subplot.layers.frontplot.select('g.scatterlayer'); - - scatterPlot(gd, plotinfo, moduleCalcData, scatterLayer); + scatterPlot(gd, plotinfo, moduleCalcData, mlayer); }; diff --git a/src/traces/scatterpolargl/index.js b/src/traces/scatterpolargl/index.js index b5a16ed6937..d6a24114b55 100644 --- a/src/traces/scatterpolargl/index.js +++ b/src/traces/scatterpolargl/index.js @@ -44,7 +44,6 @@ function calc(container, trace) { function plot(container, subplot, cdata) { var radialAxis = subplot.radialAxis; var angularAxis = subplot.angularAxis; - var rRange = radialAxis.range; var scene = ScatterGl.sceneUpdate(container, subplot); scene.clear(); @@ -56,42 +55,38 @@ function plot(container, subplot, cdata) { var stash = cd.t; var rArray = stash.r; var thetaArray = stash.theta; - var i, r, rr, theta, rad; + var i; var subRArray = rArray.slice(); var subThetaArray = thetaArray.slice(); // filter out by range for(i = 0; i < rArray.length; i++) { - r = rArray[i], theta = thetaArray[i]; - rad = angularAxis.c2rad(theta, trace.thetaunit); - - if(!subplot.isPtWithinSector({r: r, rad: rad})) { + if(!subplot.isPtWithinSector({r: rArray[i], theta: thetaArray[i]})) { subRArray[i] = NaN; subThetaArray[i] = NaN; } } var count = rArray.length; - var positions = new Array(count * 2), x = Array(count), y = Array(count); - - function c2rad(v) { - return angularAxis.c2rad(v, trace.thetaunit); - } + var positions = new Array(count * 2); + var x = Array(count); + var y = Array(count); for(i = 0; i < count; i++) { - r = subRArray[i]; - theta = subThetaArray[i]; - - if(isNumeric(r) && isNumeric(theta) && r >= 0) { - rr = radialAxis.c2r(r) - rRange[0]; - rad = c2rad(theta); - - x[i] = positions[i * 2] = rr * Math.cos(rad); - y[i] = positions[i * 2 + 1] = rr * Math.sin(rad); + var r = subRArray[i]; + var xx, yy; + + if(isNumeric(r)) { + var rg = radialAxis.c2g(r); + var thetag = angularAxis.c2g(subThetaArray[i], trace.thetaunit); + xx = rg * Math.cos(thetag); + yy = rg * Math.sin(thetag); } else { - x[i] = y[i] = positions[i * 2] = positions[i * 2 + 1] = NaN; + xx = yy = NaN; } + x[i] = positions[i * 2] = xx; + y[i] = positions[i * 2 + 1] = yy; } var options = ScatterGl.sceneOptions(container, subplot, trace, positions); @@ -156,14 +151,12 @@ function hoverPoints(pointData, xval, yval, hovermode) { } var subplot = pointData.subplot; - var angularAxis = subplot.angularAxis; var cdi = newPointData.cd[newPointData.index]; var trace = newPointData.trace; // augment pointData with r/theta param cdi.r = rArray[newPointData.index]; cdi.theta = thetaArray[newPointData.index]; - cdi.rad = angularAxis.c2rad(cdi.theta, trace.thetaunit); if(!subplot.isPtWithinSector(cdi)) return;