Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add polar.hole #2977

Merged
merged 13 commits into from
Sep 10, 2018
3 changes: 3 additions & 0 deletions src/plots/polar/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ module.exports = {
'frontplot',
'angular-axis',
'radial-axis',
// TODO hmm, putting angular-line above radial-axis
// can sometimes hide radial ticks and label
// this happens especially for the 'inner' angular axis
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved
'angular-line',
'radial-line'
],
Expand Down
21 changes: 11 additions & 10 deletions src/plots/polar/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
};

Expand Down Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions src/plots/polar/layout_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
67 changes: 41 additions & 26 deletions src/plots/polar/polar.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,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);
Expand Down Expand Up @@ -234,6 +234,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];
Expand All @@ -252,7 +254,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, {
Expand Down Expand Up @@ -282,7 +284,7 @@ proto.updateLayout = function(fullLayout, polarLayout) {
domain: yDomain2
});

var dPath = _this.pathSector();
var dPath = _this.pathSubplot();

_this.clipPaths.forTraces.select('path')
.attr('d', dPath)
Expand Down Expand Up @@ -333,9 +335,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];
};

Expand Down Expand Up @@ -371,6 +373,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;
Expand All @@ -392,12 +395,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);
Expand Down Expand Up @@ -428,7 +431,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,
Expand Down Expand Up @@ -479,6 +482,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;
Expand All @@ -491,11 +495,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);
Expand All @@ -512,13 +511,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));
}

Expand All @@ -527,8 +530,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);
Expand Down Expand Up @@ -590,8 +595,11 @@ 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?
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved

updateElement(layers['angular-line'].select('path'), angularLayout.showline, {
d: _this.pathSector(),
d: _this.pathSubplot(),
transform: strTranslate(cx, cy)
})
.attr('stroke-width', angularLayout.linewidth)
Expand All @@ -614,6 +622,7 @@ 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;
Expand All @@ -629,7 +638,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 = {
Expand Down Expand Up @@ -727,7 +736,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];
Expand All @@ -742,7 +751,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)
Expand Down Expand Up @@ -929,6 +938,8 @@ proto.updateRadialDrag = function(fullLayout) {
var tx = cx + (radius + bl2) * Math.cos(angle0);
var ty = cy - (radius + bl2) * Math.sin(angle0);

// TODO add 'inner' drag box when innerRadius > 0 !!
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not quite sure what this means, but if it's something like the clamped inner edge still jumps to be a circle at the center then yeah, we'd better avoid that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO refers to the radial drag box. Currently we have 1 radial drag box per polar subplot out beyond the subplot radius. We don't drag one at the other end of the radial axis as it would conflict with the "main" drag over the subplot.

peek 2018-09-07 12-22

But, when polar.hole > 0 we could add an inner radial drag box that doesn't conflict with the main drag. This inner radial drag box would update radialaxis.range[0].

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right! Yes, we should add that. Would be confusing if there's room for one but it's not there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

peek 2018-09-07 16-41

I'm really liking this effect.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in d466146


d3.select(radialDrag)
.attr('transform', strTranslate(tx, ty));

Expand Down Expand Up @@ -1193,15 +1204,13 @@ 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;
return fn(r, sectorInRad[0], sectorInRad[1], vangles);
};

proto.pathSector = function(r) {
r = r || this.radius;
var sectorInRad = this.sectorInRad;
var vangles = this.vangles;
var fn = vangles ? helpers.pathPolygon : Lib.pathSector;
Expand All @@ -1215,6 +1224,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;
Expand Down
12 changes: 8 additions & 4 deletions src/plots/polar/set_convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)); };
};
Expand Down
Binary file added test/image/baselines/polar_hole.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/polar_polygon-bars.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading