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) {