diff --git a/src/plots/polar/constants.js b/src/plots/polar/constants.js index ca0c0734803..71b196f422c 100644 --- a/src/plots/polar/constants.js +++ b/src/plots/polar/constants.js @@ -22,10 +22,10 @@ module.exports = { 'angular-grid', 'radial-grid', 'frontplot', - 'angular-axis', - 'radial-axis', 'angular-line', - 'radial-line' + 'radial-line', + 'angular-axis', + 'radial-axis' ], radialDragBoxSize: 50, diff --git a/src/plots/polar/layout_attributes.js b/src/plots/polar/layout_attributes.js index ab2c880daeb..d7fa1d6c31f 100644 --- a/src/plots/polar/layout_attributes.js +++ b/src/plots/polar/layout_attributes.js @@ -112,16 +112,6 @@ var radialAxisAttrs = { hoverformat: axesAttrs.hoverformat, - // More attributes: - - // We'll need some attribute that determines the span - // to draw donut-like charts - // e.g. https://github.com/matplotlib/matplotlib/issues/4217 - // - // maybe something like 'span' or 'hole' (like pie, but pie set it in data coords?) - // span: {}, - // hole: 1 - editType: 'calc' }; @@ -256,6 +246,17 @@ module.exports = { 'with *0* corresponding to rightmost limit of the polar subplot.' ].join(' ') }, + hole: { + valType: 'number', + min: 0, + max: 1, + dflt: 0, + editType: 'plot', + role: 'info', + description: [ + 'Sets the fraction of the radius to cut out of the polar subplot.' + ].join(' ') + }, bgcolor: { valType: 'color', diff --git a/src/plots/polar/layout_defaults.js b/src/plots/polar/layout_defaults.js index f8bb7a9103b..462916536ac 100644 --- a/src/plots/polar/layout_defaults.js +++ b/src/plots/polar/layout_defaults.js @@ -30,6 +30,7 @@ function handleDefaults(contIn, contOut, coerce, opts) { opts.bgColor = Color.combine(bgColor, opts.paper_bgcolor); var sector = coerce('sector'); + coerce('hole'); // could optimize, subplotData is not always needed! var subplotData = getSubplotData(opts.fullData, constants.name, opts.id); diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index e7df279c845..6c78215d339 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -90,7 +90,7 @@ proto.plot = function(polarCalcData, fullLayout) { _this.updateLayers(fullLayout, polarLayout); _this.updateLayout(fullLayout, polarLayout); Plots.generalUpdatePerTraceModule(_this.gd, _this, polarCalcData, polarLayout); - _this.updateFx(fullLayout); + _this.updateFx(fullLayout, polarLayout); }; proto.updateLayers = function(fullLayout, polarLayout) { @@ -105,17 +105,17 @@ proto.updateLayers = function(fullLayout, polarLayout) { var isAngularAxisBelowTraces = angularLayout.layer === 'below traces'; var isRadialAxisBelowTraces = radialLayout.layer === 'below traces'; - if(isAngularAxisBelowTraces) layerData.push('angular-axis'); - if(isRadialAxisBelowTraces) layerData.push('radial-axis'); if(isAngularAxisBelowTraces) layerData.push('angular-line'); if(isRadialAxisBelowTraces) layerData.push('radial-line'); + if(isAngularAxisBelowTraces) layerData.push('angular-axis'); + if(isRadialAxisBelowTraces) layerData.push('radial-axis'); layerData.push('frontplot'); - if(!isAngularAxisBelowTraces) layerData.push('angular-axis'); - if(!isRadialAxisBelowTraces) layerData.push('radial-axis'); if(!isAngularAxisBelowTraces) layerData.push('angular-line'); if(!isRadialAxisBelowTraces) layerData.push('radial-line'); + if(!isAngularAxisBelowTraces) layerData.push('angular-axis'); + if(!isRadialAxisBelowTraces) layerData.push('radial-axis'); var join = _this.framework.selectAll('.polarsublayer') .data(layerData, String); @@ -127,9 +127,9 @@ proto.updateLayers = function(fullLayout, polarLayout) { switch(d) { case 'frontplot': - sel.append('g').classed('scatterlayer', true); // TODO add option to place in 'backplot' layer?? sel.append('g').classed('barlayer', true); + sel.append('g').classed('scatterlayer', true); break; case 'backplot': sel.append('g').classed('maplayer', true); @@ -235,6 +235,8 @@ proto.updateLayout = function(fullLayout, polarLayout) { var yOffset2 = _this.yOffset2 = gs.t + gs.h * (1 - yDomain2[1]); // circle radius in px var radius = _this.radius = xLength2 / dxSectorBBox; + // 'inner' radius in px (when polar.hole is set) + var innerRadius = _this.innerRadius = polarLayout.hole * radius; // circle center position in px var cx = _this.cx = xOffset2 - radius * sectorBBox[0]; var cy = _this.cy = yOffset2 + radius * sectorBBox[3]; @@ -253,7 +255,7 @@ proto.updateLayout = function(fullLayout, polarLayout) { clockwise: 'bottom' }[radialLayout.side], // spans length 1 radius - domain: [0, radius / gs.w] + domain: [innerRadius / gs.w, radius / gs.w] }); _this.angularAxis = _this.mockAxis(fullLayout, polarLayout, angularLayout, { @@ -283,7 +285,7 @@ proto.updateLayout = function(fullLayout, polarLayout) { domain: yDomain2 }); - var dPath = _this.pathSector(); + var dPath = _this.pathSubplot(); _this.clipPaths.forTraces.select('path') .attr('d', dPath) @@ -334,9 +336,9 @@ proto.mockCartesianAxis = function(fullLayout, polarLayout, opts) { ax.setRange = function() { var sectorBBox = _this.sectorBBox; - var rl = _this.radialAxis._rl; - var drl = rl[1] - rl[0]; var ind = bboxIndices[axId]; + var rl = _this.radialAxis._rl; + var drl = (rl[1] - rl[0]) / (1 - polarLayout.hole); ax.range = [sectorBBox[ind[0]] * drl, sectorBBox[ind[1]] * drl]; }; @@ -372,6 +374,7 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) { var gd = _this.gd; var layers = _this.layers; var radius = _this.radius; + var innerRadius = _this.innerRadius; var cx = _this.cx; var cy = _this.cy; var radialLayout = polarLayout.radialaxis; @@ -393,12 +396,12 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) { // easier to set rotate angle with custom translate function ax._transfn = function(d) { - return 'translate(' + ax.l2p(d.x) + ',0)'; + return 'translate(' + (ax.l2p(d.x) + innerRadius) + ',0)'; }; // set special grid path function ax._gridpath = function(d) { - return _this.pathArc(ax.r2p(d.x)); + return _this.pathArc(ax.r2p(d.x) + innerRadius); }; var newTickLayout = strTickLayout(radialLayout); @@ -429,7 +432,7 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) { .selectAll('path').attr('transform', null); updateElement(layers['radial-line'].select('line'), radialLayout.showline, { - x1: 0, + x1: innerRadius, y1: 0, x2: radius, y2: 0, @@ -480,6 +483,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { var gd = _this.gd; var layers = _this.layers; var radius = _this.radius; + var innerRadius = _this.innerRadius; var cx = _this.cx; var cy = _this.cy; var angularLayout = polarLayout.angularaxis; @@ -492,11 +496,6 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { // '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)]; - } - // run rad2deg on tick0 and ditck for thetaunit: 'radians' axes if(ax.type === 'linear' && ax.thetaunit === 'radians') { ax.tick0 = rad2deg(ax.tick0); @@ -513,13 +512,17 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { } ax._transfn = function(d) { + var sel = d3.select(this); + var hasElement = sel && sel.node(); + + // don't translate grid lines + if(hasElement && sel.classed('angularaxisgrid')) return ''; + var rad = t2g(d); - var xy = rad2xy(rad); - var out = strTranslate(cx + xy[0], cy - xy[1]); + var out = strTranslate(cx + radius * Math.cos(rad), cy - radius * Math.sin(rad)); - // must also rotate ticks, but don't rotate labels and grid lines - var sel = d3.select(this); - if(sel && sel.node() && sel.classed('ticks')) { + // must also rotate ticks, but don't rotate labels + if(hasElement && sel.classed('ticks')) { out += strRotate(-rad2deg(rad)); } @@ -528,8 +531,10 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { ax._gridpath = function(d) { var rad = t2g(d); - var xy = rad2xy(rad); - return 'M0,0L' + (-xy[0]) + ',' + xy[1]; + var cosRad = Math.cos(rad); + var sinRad = Math.sin(rad); + return 'M' + [cx + innerRadius * cosRad, cy - innerRadius * sinRad] + + 'L' + [cx + radius * cosRad, cy - radius * sinRad]; }; var offset4fontsize = (angularLayout.ticks !== 'outside' ? 0.7 : 0.5); @@ -591,18 +596,22 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { } _this.vangles = vangles; + // TODO maybe two arcs is better here? + // maybe split style attributes between inner and outer angular axes? + updateElement(layers['angular-line'].select('path'), angularLayout.showline, { - d: _this.pathSector(), + d: _this.pathSubplot(), transform: strTranslate(cx, cy) }) .attr('stroke-width', angularLayout.linewidth) .call(Color.stroke, angularLayout.linecolor); }; -proto.updateFx = function(fullLayout) { +proto.updateFx = function(fullLayout, polarLayout) { if(!this.gd._context.staticPlot) { this.updateAngularDrag(fullLayout); - this.updateRadialDrag(fullLayout); + this.updateRadialDrag(fullLayout, polarLayout, 0); + this.updateRadialDrag(fullLayout, polarLayout, 1); this.updateMainDrag(fullLayout); } }; @@ -615,12 +624,14 @@ proto.updateMainDrag = function(fullLayout) { var MINZOOM = constants.MINZOOM; var OFFEDGE = constants.OFFEDGE; var radius = _this.radius; + var innerRadius = _this.innerRadius; var cx = _this.cx; var cy = _this.cy; var cxx = _this.cxx; var cyy = _this.cyy; var sectorInRad = _this.sectorInRad; var vangles = _this.vangles; + var radialAxis = _this.radialAxis; var clampTiny = helpers.clampTiny; var findXYatLength = helpers.findXYatLength; var findEnclosingVertexAngles = helpers.findEnclosingVertexAngles; @@ -630,7 +641,7 @@ proto.updateMainDrag = function(fullLayout) { var mainDrag = dragBox.makeDragger(layers, 'path', 'maindrag', 'crosshair'); d3.select(mainDrag) - .attr('d', _this.pathSector()) + .attr('d', _this.pathSubplot()) .attr('transform', strTranslate(cx, cy)); var dragOpts = { @@ -729,7 +740,7 @@ proto.updateMainDrag = function(fullLayout) { function zoomPrep() { r0 = null; r1 = null; - path0 = _this.pathSector(); + path0 = _this.pathSubplot(); dimmed = false; var polarLayoutNow = gd._fullLayout[_this.id]; @@ -744,7 +755,7 @@ proto.updateMainDrag = function(fullLayout) { // N.B. this sets scoped 'r0' and 'r1' // return true if 'valid' zoom distance, false otherwise function clampAndSetR0R1(rr0, rr1) { - rr1 = Math.min(rr1, radius); + rr1 = Math.max(Math.min(rr1, radius), innerRadius); // starting or ending drag near center (outer edge), // clamps radial distance at origin (at r=radius) @@ -833,16 +844,13 @@ proto.updateMainDrag = function(fullLayout) { dragBox.showDoubleClickNotifier(gd); - var radialAxis = _this.radialAxis; var rl = radialAxis._rl; - var drl = rl[1] - rl[0]; - var updateObj = {}; - updateObj[_this.id + '.radialaxis.range'] = [ - rl[0] + r0 * drl / radius, - rl[0] + r1 * drl / radius + var m = (rl[1] - rl[0]) / (1 - innerRadius / radius) / radius; + var newRng = [ + rl[0] + (r0 - innerRadius) * m, + rl[0] + (r1 - innerRadius) * m ]; - - Registry.call('relayout', gd, updateObj); + Registry.call('relayout', gd, _this.id + '.radialaxis.range', newRng); } function zoomClick(numClicks, evt) { @@ -917,38 +925,52 @@ proto.updateMainDrag = function(fullLayout) { dragElement.init(dragOpts); }; -proto.updateRadialDrag = function(fullLayout) { +proto.updateRadialDrag = function(fullLayout, polarLayout, rngIndex) { var _this = this; var gd = _this.gd; var layers = _this.layers; var radius = _this.radius; + var innerRadius = _this.innerRadius; var cx = _this.cx; var cy = _this.cy; var radialAxis = _this.radialAxis; + var bl = constants.radialDragBoxSize; + var bl2 = bl / 2; if(!radialAxis.visible) return; var angle0 = deg2rad(_this.radialAxisAngle); - var rl0 = radialAxis._rl[0]; - var rl1 = radialAxis._rl[1]; - var drl = rl1 - rl0; + var rl = radialAxis._rl; + var rl0 = rl[0]; + var rl1 = rl[1]; + var rbase = rl[rngIndex]; + var m = 0.75 * (rl[1] - rl[0]) / (1 - polarLayout.hole) / radius; + + var tx, ty, className; + if(rngIndex) { + tx = cx + (radius + bl2) * Math.cos(angle0); + ty = cy - (radius + bl2) * Math.sin(angle0); + className = 'radialdrag'; + } else { + // the 'inner' box can get called: + // - when polar.hole>0 + // - when polar.sector isn't a full circle + // otherwise it is hidden behind the main drag. + tx = cx + (innerRadius - bl2) * Math.cos(angle0); + ty = cy - (innerRadius - bl2) * Math.sin(angle0); + className = 'radialdrag-inner'; + } - var bl = constants.radialDragBoxSize; - var bl2 = bl / 2; - var radialDrag = dragBox.makeRectDragger(layers, 'radialdrag', 'crosshair', -bl2, -bl2, bl, bl); + var radialDrag = dragBox.makeRectDragger(layers, className, 'crosshair', -bl2, -bl2, bl, bl); var dragOpts = {element: radialDrag, gd: gd}; - var tx = cx + (radius + bl2) * Math.cos(angle0); - var ty = cy - (radius + bl2) * Math.sin(angle0); - - d3.select(radialDrag) - .attr('transform', strTranslate(tx, ty)); + d3.select(radialDrag).attr('transform', strTranslate(tx, ty)); // move function (either rotate or re-range flavor) var moveFn2; // rotate angle on done var angle1; - // re-range range[1] on done - var rng1; + // re-range range[1] (or range[0]) on done + var rprime; function moveFn(dx, dy) { if(moveFn2) { @@ -969,12 +991,15 @@ proto.updateRadialDrag = function(fullLayout) { function doneFn() { if(angle1 !== null) { Registry.call('relayout', gd, _this.id + '.radialaxis.angle', angle1); - } else if(rng1 !== null) { - Registry.call('relayout', gd, _this.id + '.radialaxis.range[1]', rng1); + } else if(rprime !== null) { + Registry.call('relayout', gd, _this.id + '.radialaxis.range[' + rngIndex + ']', rprime); } } function rotateMove(dx, dy) { + // disable for inner drag boxes + if(rngIndex === 0) return; + var x1 = tx + dx; var y1 = ty + dy; @@ -994,14 +1019,17 @@ proto.updateRadialDrag = function(fullLayout) { function rerangeMove(dx, dy) { // project (dx, dy) unto unit radial axis vector var dr = Lib.dot([dx, -dy], [Math.cos(angle0), Math.sin(angle0)]); - rng1 = rl1 - drl * dr / radius * 0.75; + rprime = rbase - m * dr; - // make sure new range[1] does not change the range[0] -> range[1] sign - if((drl > 0) !== (rng1 > rl0)) return; + // make sure rprime does not change the range[0] -> range[1] sign + if((m > 0) !== (rngIndex ? rprime > rl0 : rprime < rl1)) { + rprime = null; + return; + } // update radial range -> update c2g -> update _m,_b - radialAxis.range[1] = rng1; - radialAxis._rl[1] = rng1; + radialAxis.range[rngIndex] = rprime; + radialAxis._rl[rngIndex] = rprime; radialAxis.setGeometry(); radialAxis.setScale(); @@ -1029,7 +1057,7 @@ proto.updateRadialDrag = function(fullLayout) { dragOpts.prepFn = function() { moveFn2 = null; angle1 = null; - rng1 = null; + rprime = null; dragOpts.moveFn = moveFn; dragOpts.doneFn = doneFn; @@ -1204,7 +1232,6 @@ proto.isPtInside = function(d) { }; proto.pathArc = function(r) { - r = r || this.radius; var sectorInRad = this.sectorInRad; var vangles = this.vangles; var fn = vangles ? helpers.pathPolygon : Lib.pathArc; @@ -1212,7 +1239,6 @@ proto.pathArc = function(r) { }; proto.pathSector = function(r) { - r = r || this.radius; var sectorInRad = this.sectorInRad; var vangles = this.vangles; var fn = vangles ? helpers.pathPolygon : Lib.pathSector; @@ -1226,6 +1252,12 @@ proto.pathAnnulus = function(r0, r1) { return fn(r0, r1, sectorInRad[0], sectorInRad[1], vangles); }; +proto.pathSubplot = function() { + var r0 = this.innerRadius; + var r1 = this.radius; + return r0 ? this.pathAnnulus(r0, r1) : this.pathSector(r1); +}; + proto.fillViewInitialKey = function(key, val) { if(!(key in this.viewInitial)) { this.viewInitial[key] = val; diff --git a/src/plots/polar/set_convert.js b/src/plots/polar/set_convert.js index 7eb8af9b852..684a8224cc8 100644 --- a/src/plots/polar/set_convert.js +++ b/src/plots/polar/set_convert.js @@ -34,7 +34,7 @@ var rad2deg = Lib.rad2deg; * * Radial axis coordinate systems: * - d, c and l: same as for cartesian axes - * - g: like calcdata but translated about `radialaxis.range[0]` + * - g: like calcdata but translated about `radialaxis.range[0]` & `polar.hole` * * Angular axis coordinate systems: * - d: data, in whatever form it's provided @@ -61,25 +61,29 @@ module.exports = function setConvert(ax, polarLayout, fullLayout) { }; function setConvertRadial(ax, polarLayout) { + var subplot = polarLayout._subplot; + ax.setGeometry = function() { var rl0 = ax._rl[0]; var rl1 = ax._rl[1]; + var b = subplot.innerRadius; + var m = (subplot.radius - b) / (rl1 - rl0); + var b2 = b / m; + var rFilter = rl0 > rl1 ? function(v) { return v <= 0; } : function(v) { return v >= 0; }; ax.c2g = function(v) { var r = ax.c2l(v) - rl0; - return rFilter(r) ? r : 0; + return (rFilter(r) ? r : 0) + b2; }; ax.g2c = function(v) { - return ax.l2c(v + rl0); + return ax.l2c(v + rl0 - b2); }; - var m = polarLayout._subplot.radius / (rl1 - rl0); - ax.g2p = function(v) { return v * m; }; ax.c2p = function(v) { return ax.g2p(ax.c2g(v)); }; }; diff --git a/test/image/baselines/glpolar_scatter.png b/test/image/baselines/glpolar_scatter.png index d278a1bd29e..d631ae1d118 100644 Binary files a/test/image/baselines/glpolar_scatter.png and b/test/image/baselines/glpolar_scatter.png differ diff --git a/test/image/baselines/glpolar_style.png b/test/image/baselines/glpolar_style.png index 31c2b18b375..8e27ce4e66c 100644 Binary files a/test/image/baselines/glpolar_style.png and b/test/image/baselines/glpolar_style.png differ diff --git a/test/image/baselines/glpolar_subplots.png b/test/image/baselines/glpolar_subplots.png index ed807c1ab15..7fd76151d3f 100644 Binary files a/test/image/baselines/glpolar_subplots.png and b/test/image/baselines/glpolar_subplots.png differ diff --git a/test/image/baselines/polar_bar-overlay.png b/test/image/baselines/polar_bar-overlay.png index 54e29302826..c10f093cd84 100644 Binary files a/test/image/baselines/polar_bar-overlay.png and b/test/image/baselines/polar_bar-overlay.png differ diff --git a/test/image/baselines/polar_bar-stacked.png b/test/image/baselines/polar_bar-stacked.png index c232d2b3c0a..e317800d5fb 100644 Binary files a/test/image/baselines/polar_bar-stacked.png and b/test/image/baselines/polar_bar-stacked.png differ diff --git a/test/image/baselines/polar_bar-width-base-offset.png b/test/image/baselines/polar_bar-width-base-offset.png index 760c06c857c..eac19c97c6c 100644 Binary files a/test/image/baselines/polar_bar-width-base-offset.png and b/test/image/baselines/polar_bar-width-base-offset.png differ diff --git a/test/image/baselines/polar_blank.png b/test/image/baselines/polar_blank.png index d5c9b75dd63..ac57f2fcf44 100644 Binary files a/test/image/baselines/polar_blank.png and b/test/image/baselines/polar_blank.png differ diff --git a/test/image/baselines/polar_categories.png b/test/image/baselines/polar_categories.png index 36e92602dc3..67e45a62d91 100644 Binary files a/test/image/baselines/polar_categories.png and b/test/image/baselines/polar_categories.png differ diff --git a/test/image/baselines/polar_dates.png b/test/image/baselines/polar_dates.png index 6127f9afd71..b102e5ea70c 100644 Binary files a/test/image/baselines/polar_dates.png and b/test/image/baselines/polar_dates.png differ diff --git a/test/image/baselines/polar_direction.png b/test/image/baselines/polar_direction.png index 967aead4abc..4b57bf4a130 100644 Binary files a/test/image/baselines/polar_direction.png and b/test/image/baselines/polar_direction.png differ diff --git a/test/image/baselines/polar_fills.png b/test/image/baselines/polar_fills.png index efd3df8448b..fd34fb02040 100644 Binary files a/test/image/baselines/polar_fills.png and b/test/image/baselines/polar_fills.png differ diff --git a/test/image/baselines/polar_funky-bars.png b/test/image/baselines/polar_funky-bars.png index 721da8c124b..f986930a388 100644 Binary files a/test/image/baselines/polar_funky-bars.png and b/test/image/baselines/polar_funky-bars.png differ diff --git a/test/image/baselines/polar_hole.png b/test/image/baselines/polar_hole.png new file mode 100644 index 00000000000..b701371fe93 Binary files /dev/null and b/test/image/baselines/polar_hole.png differ diff --git a/test/image/baselines/polar_line.png b/test/image/baselines/polar_line.png index 8b5849a3303..bd00514df84 100644 Binary files a/test/image/baselines/polar_line.png and b/test/image/baselines/polar_line.png differ diff --git a/test/image/baselines/polar_polygon-bars.png b/test/image/baselines/polar_polygon-bars.png index a6dabee586c..892e5dd4ac5 100644 Binary files a/test/image/baselines/polar_polygon-bars.png and b/test/image/baselines/polar_polygon-bars.png differ diff --git a/test/image/baselines/polar_polygon-grids.png b/test/image/baselines/polar_polygon-grids.png index 8a6e41ffdef..dea0454b4d3 100644 Binary files a/test/image/baselines/polar_polygon-grids.png and b/test/image/baselines/polar_polygon-grids.png differ diff --git a/test/image/baselines/polar_r0dr-theta0dtheta.png b/test/image/baselines/polar_r0dr-theta0dtheta.png index e5b78152117..b11d35e2c42 100644 Binary files a/test/image/baselines/polar_r0dr-theta0dtheta.png and b/test/image/baselines/polar_r0dr-theta0dtheta.png differ diff --git a/test/image/baselines/polar_radial-range.png b/test/image/baselines/polar_radial-range.png index e79a88f3b3d..8e6e570dc3e 100644 Binary files a/test/image/baselines/polar_radial-range.png and b/test/image/baselines/polar_radial-range.png differ diff --git a/test/image/baselines/polar_scatter.png b/test/image/baselines/polar_scatter.png index 2a94c6851c4..c5fffaf3d30 100644 Binary files a/test/image/baselines/polar_scatter.png and b/test/image/baselines/polar_scatter.png differ diff --git a/test/image/baselines/polar_sector.png b/test/image/baselines/polar_sector.png index 78e5585892a..d81ffb8f7c9 100644 Binary files a/test/image/baselines/polar_sector.png and b/test/image/baselines/polar_sector.png differ diff --git a/test/image/baselines/polar_subplots.png b/test/image/baselines/polar_subplots.png index 140e0aac811..e441914242d 100644 Binary files a/test/image/baselines/polar_subplots.png and b/test/image/baselines/polar_subplots.png differ diff --git a/test/image/baselines/polar_ticks.png b/test/image/baselines/polar_ticks.png index 2cb629b084a..6a4eb5b642b 100644 Binary files a/test/image/baselines/polar_ticks.png and b/test/image/baselines/polar_ticks.png differ diff --git a/test/image/baselines/polar_transforms.png b/test/image/baselines/polar_transforms.png index c7c0a5eb218..b120caaafdc 100644 Binary files a/test/image/baselines/polar_transforms.png and b/test/image/baselines/polar_transforms.png differ diff --git a/test/image/baselines/polar_wind-rose.png b/test/image/baselines/polar_wind-rose.png index d1050119a3f..951dfe8f833 100644 Binary files a/test/image/baselines/polar_wind-rose.png and b/test/image/baselines/polar_wind-rose.png differ diff --git a/test/image/mocks/polar_hole.json b/test/image/mocks/polar_hole.json new file mode 100644 index 00000000000..4eb7d7dd016 --- /dev/null +++ b/test/image/mocks/polar_hole.json @@ -0,0 +1,101 @@ +{ + "data": [ + { + "type": "barpolar", + "r": [10, 12, 15] + }, + { + "type": "scatterpolar", + "r": [10, 12, 15], + "theta0": 90 + }, + + { + "type": "scatterpolar", + "subplot": "polar2", + "r": [100, 50, 200], + "marker": {"size": 20} + }, + { + "type": "barpolar", + "subplot": "polar2", + "r": [100, 50, 200] + }, + + { + "type": "barpolar", + "subplot": "polar3", + "theta": ["a", "b", "c", "d", "b", "f", "a", "a"] + }, + { + "type": "scatterpolar", + "subplot": "polar3", + "theta": ["a", "b", "c", "d", "b", "f", "a", "a"] + }, + + { + "type": "barpolar", + "subplot": "polar4", + "r": [10, 12, 15] + }, + { + "type": "scatterpolar", + "subplot": "polar4", + "r": [10, 12, 5], + "theta": [0, 90, -90], + "cliponaxis": true, + "marker": {"size": 20} + }, + + { + "type": "barpolar", + "subplot": "polar5", + "r": [10, 12, 15] + }, + { + "type": "scatterpolar", + "subplot": "polar5", + "r": [10, 12, 15] + } + ], + "layout": { + "width": 600, + "height": 800, + "margin": {"l": 40, "r": 40, "b": 40, "t": 40, "pad": 0}, + "grid": { + "rows": 3, + "columns": 2, + "ygap": 0.2 + }, + "polar": { + "hole": 0.1, + "domain": {"row": 0, "column": 0} + }, + "polar2": { + "hole": 0.4, + "domain": {"row": 0, "column": 1}, + "angularaxis": {"layer": "below traces"}, + "radialaxis": {"type": "log", "range": [1.65, 2.35]} + }, + "polar3": { + "hole": 0.2, + "gridshape": "linear", + "angularaxis": {"direction": "clockwise"}, + "domain": {"row": 1, "column": 0} + }, + "polar4": { + "hole": 0.4, + "domain": {"row": 1, "column": 1}, + "sector": [0, 180], + "angularaxis": {"direction": "clockwise"}, + "radialaxis": {"angle": 90, "side": "counterclockwise", "range": [5, 15]} + }, + "polar5": { + "hole": 0.5, + "domain": {"row": 2, "column": 0}, + "radialaxis": {"range": [20, 0], "tickfont": {"color": "red", "size": 20}}, + "angularaxis": {"linewidth": 3} + }, + "showlegend": false + } +} diff --git a/test/jasmine/tests/barpolar_test.js b/test/jasmine/tests/barpolar_test.js index a6aa3ef7f5f..b5314a8f330 100644 --- a/test/jasmine/tests/barpolar_test.js +++ b/test/jasmine/tests/barpolar_test.js @@ -274,6 +274,24 @@ describe('Test barpolar hover:', function() { extraText: 'r: 12
θ: 120°', color: '#1f77b4' } + }, { + desc: 'works on a subplot with hole>0', + traces: [{ + r: [1, 2, 3], + theta: [0, 90, 180] + }], + layout: { + polar: {hole: 0.2} + }, + xval: 1, + yval: 0, + exp: { + index: 0, + x: 290.67, + y: 200, + extraText: 'r: 1
θ: 0°', + color: '#1f77b4' + } }, { desc: 'on overlapping bars of same size, the narrower wins', traces: [{ diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index c7ffc6a685c..f241d155088 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -236,19 +236,19 @@ describe('Test relayout on polar subplots:', function() { .then(function() { _assert([ 'draglayer', 'plotbg', 'backplot', 'angular-grid', 'radial-grid', - 'radial-axis', 'radial-line', + 'radial-line', 'radial-axis', 'frontplot', - 'angular-axis', 'angular-line' + 'angular-line', 'angular-axis' ]); return Plotly.relayout(gd, 'polar.angularaxis.layer', 'below traces'); }) .then(function() { _assert([ 'draglayer', 'plotbg', 'backplot', 'angular-grid', 'radial-grid', - 'angular-axis', - 'radial-axis', 'angular-line', 'radial-line', + 'angular-axis', + 'radial-axis', 'frontplot' ]); return Plotly.relayout(gd, 'polar.radialaxis.layer', 'above traces'); @@ -256,9 +256,9 @@ describe('Test relayout on polar subplots:', function() { .then(function() { _assert([ 'draglayer', 'plotbg', 'backplot', 'angular-grid', 'radial-grid', - 'angular-axis', 'angular-line', + 'angular-line', 'angular-axis', 'frontplot', - 'radial-axis', 'radial-line' + 'radial-line', 'radial-axis' ]); return Plotly.relayout(gd, 'polar.angularaxis.layer', null); }) @@ -924,6 +924,13 @@ describe('Test polar interactions:', function() { expect(eventCnts.plotly_relayout) .toBe(relayoutNumber, 'no new relayout events after *not far enough* cases'); }) + .then(_reset) + .then(function() { return Plotly.relayout(gd, 'polar.hole', 0.2); }) + .then(function() { relayoutNumber++; }) + .then(function() { return _drag([mid[0] + 30, mid[0] - 30], [50, -50]); }) + .then(function() { + _assertDrag([1.15, 7.70], 'with polar.hole>0, from quadrant #1 move top-right'); + }) .catch(failTest) .then(done); }); @@ -1012,6 +1019,45 @@ describe('Test polar interactions:', function() { .then(done); }); + it('should response to drag interactions on inner radial drag area', function(done) { + var fig = Lib.extendDeep({}, require('@mocks/polar_scatter.json')); + fig.layout.polar.hole = 0.2; + // to avoid dragging on hover labels + fig.layout.hovermode = false; + // adjust margins so that middle of plot area is at 300x300 + // with its middle at [200,200] + fig.layout.width = 400; + fig.layout.height = 400; + fig.layout.margin = {l: 50, t: 50, b: 50, r: 50}; + + var dragPos0 = [200, 200]; + + // use 'special' drag method - as we need two mousemove events + // to activate the radial drag mode + function _drag(p0, dp) { + var node = d3.select('.polar > .draglayer > .radialdrag-inner').node(); + return drag(node, dp[0], dp[1], null, p0[0], p0[1], 2); + } + + function _assert(rng, msg) { + expect(gd._fullLayout.polar.radialaxis.range) + .toBeCloseToArray(rng, 1, msg + ' - range'); + } + + _plot(fig) + .then(function() { return _drag(dragPos0, [-50, 0]); }) + .then(function() { + _assert([3.55, 11.36], 'move inward'); + }) + .then(function() { return Plotly.relayout(gd, 'polar.radialaxis.autorange', true); }) + .then(function() { return _drag(dragPos0, [50, 0]); }) + .then(function() { + _assert([-3.55, 11.36], 'move outward'); + }) + .catch(failTest) + .then(done); + }); + it('should response to drag interactions on angular drag area', function(done) { var fig = Lib.extendDeep({}, require('@mocks/polar_scatter.json')); diff --git a/test/jasmine/tests/scatterpolar_test.js b/test/jasmine/tests/scatterpolar_test.js index 14d2ea0a954..27584eb48db 100644 --- a/test/jasmine/tests/scatterpolar_test.js +++ b/test/jasmine/tests/scatterpolar_test.js @@ -150,6 +150,14 @@ describe('Test scatterpolar hover:', function() { pos: [465, 90], nums: 'r: 4\nθ: d', name: 'angular cate...' + }, { + desc: 'on a subplot with hole>0', + patch: function(fig) { + fig.layout.polar.hole = 0.2; + return fig; + }, + nums: 'r: 1.108937\nθ: 115.4969°', + name: 'Trial 3' }] .forEach(function(specs) { it('should generate correct hover labels ' + specs.desc, function(done) {