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

Polar 2.0 #2200

Merged
merged 73 commits into from
Jan 16, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
58d53e8
move (old) polar code to legacy/ folder
etpinard Nov 20, 2017
853581e
introduce scatterpolar attributes
etpinard Dec 11, 2017
3c8ae5b
introduce polar subplot attributes
etpinard Dec 11, 2017
ca8081b
introduce angles helper module
etpinard Dec 11, 2017
46fe066
first cut polar subplot defaults and draw routines
etpinard Dec 11, 2017
0826ee7
first cut scatterpolar calc/defaults/plot/style/hover
etpinard Dec 11, 2017
9770f2c
misc things to get scatterpolar traces to show up
etpinard Dec 11, 2017
0b85af2
init categories stashes for polar axes
etpinard Dec 11, 2017
cbf19c1
allow ticksuffix dflt to be passed via option
etpinard Dec 11, 2017
86204a6
get polar axis updates to work
etpinard Dec 11, 2017
0027f53
add legacy deprecation warnings
etpinard Dec 11, 2017
4cdc301
remove obsolete check for polar in registry
etpinard Dec 11, 2017
a5a719c
get snapshot redraw function right for (new) polar
etpinard Dec 11, 2017
da720aa
improve scatterpolar hover
etpinard Dec 11, 2017
04a886b
use subplot ref in scatterternary hover pointData
etpinard Dec 11, 2017
22cf4ba
expose a few things out of cartesian dragbox for :recycle:
etpinard Dec 11, 2017
122dd0b
uber wip polar main drag interaction
etpinard Dec 11, 2017
689414c
add radial axis drag interactions
etpinard Dec 11, 2017
543b615
first cut polar mocks!
etpinard Dec 11, 2017
54b3f1c
Merge branch 'master' into polar
etpinard Dec 15, 2017
17503cb
make scatter{ternary,polar,carpet} Scatter.style reuse straight-up
etpinard Dec 14, 2017
7b0f718
add test for commit 86204a6 (ternary ax tickfont relayouts)
etpinard Dec 14, 2017
fbea869
:palm_tree: domain attributes
etpinard Dec 14, 2017
1e769f0
make non-zero radialaxis.range[0] work
etpinard Dec 14, 2017
f53495e
make radial/angular axis visible: false cases work
etpinard Dec 14, 2017
c5a3a72
1st pass isPtWithinSector algo
etpinard Dec 15, 2017
61e155c
warn -> log for polar deprecation
etpinard Dec 15, 2017
258600d
:hocho: special polygon logic from scatterpolar/plot
etpinard Dec 18, 2017
0a41da5
implement {angular,radial}axis.layer
etpinard Dec 18, 2017
f3a2379
:palm_tree: make filter-visible work for calcdata containers
etpinard Dec 19, 2017
3f5e77b
first cut radial axis drag intractions
etpinard Dec 19, 2017
65b717d
get `polar.sector` working on category angular axes
etpinard Dec 19, 2017
13568a3
fixup isPtWithinSector
etpinard Dec 20, 2017
797fec1
add support for 'log' radial axes
etpinard Dec 20, 2017
0a9278c
fixup axis autotype logic for visible: false traces
etpinard Dec 22, 2017
07a3af7
make sure that angular setting relayout call propagate to tick labels
etpinard Dec 22, 2017
a263c1c
solid zoombox try + some pan work and radial drag fixups
etpinard Dec 22, 2017
765d2b7
pretty solid zoom/pan interactions
etpinard Dec 23, 2017
909d134
make polar ticks relayout more robust
etpinard Dec 27, 2017
3e1e4dd
:palm_tree: calcMarkerSize logic
etpinard Dec 28, 2017
a96131d
fix hoveron fill hover label placement edge case
etpinard Dec 28, 2017
b35d7ec
update ticks when panning sectors using new wrap180 routine
etpinard Dec 28, 2017
5751de5
make marker.size bump radial autorange results
etpinard Dec 28, 2017
86067a6
:palm_tree: cartesian/ternary/polar line/grid default logic
etpinard Dec 29, 2017
5422da9
coerce 'cliponaxis' only when markers and/or text are present
etpinard Dec 29, 2017
b4af46f
clear ternary axis titles when ternary subplots are removed
etpinard Jan 3, 2018
20b17e5
set radialaxis.rangemode dflt to 'tozero' + add support for -ve r ranges
etpinard Jan 4, 2018
cb78d19
implement radialaxis.title
etpinard Jan 4, 2018
25b4e7b
even more robust relayout
etpinard Jan 4, 2018
1e81039
large commit of attribute name & dflt changes
etpinard Jan 4, 2018
e5d238d
remove crispEdges for all polar subplot paths
etpinard Jan 4, 2018
0146c2f
Merge branch 'master' into polar
etpinard Jan 4, 2018
c94423b
adapt polar to clean-subplots improvements
etpinard Jan 4, 2018
4d5a1bd
fixups for domain attrs
etpinard Jan 10, 2018
d4c7d2c
default *rotation* to 90 for clockwise *direction*
etpinard Jan 10, 2018
8b4c2b5
do not implement 'date' angular axes (for now)
etpinard Jan 10, 2018
5357501
clear polar radial range viewInitial during log <-> linear relayout
etpinard Jan 10, 2018
9a71ba3
set limit of 100 for num2frac denominator
etpinard Jan 10, 2018
f714449
extend line & fills to polar center when pts < radialaxis.range[0]
etpinard Jan 10, 2018
cde7369
fixup radial axis drag rotate logic
etpinard Jan 10, 2018
9c4c9bf
Merge branch 'master' into polar
etpinard Jan 11, 2018
b365374
more strict check for polar subplot existence
etpinard Jan 11, 2018
a72ce55
adapt polar main drag to dragElement updates (+ tests)
etpinard Jan 11, 2018
473643c
AJ-proof latest additions
etpinard Jan 12, 2018
c2da23e
replace dragmode: 'pan' with annulus-shaped dragger
etpinard Jan 11, 2018
974740b
improve zoom dragbox behavior
etpinard Jan 11, 2018
e8aec86
un-rotate markers and text pts during angular drag
etpinard Jan 12, 2018
e8865bc
add scatterpolar hover label tests
etpinard Dec 22, 2017
8a33b89
add scatterpolar select test
etpinard Jan 12, 2018
c7a7f59
add polar drag interaction tests
etpinard Jan 15, 2018
7f7193d
a few small tweaks
etpinard Jan 15, 2018
816be51
misc. polar fixup after AJ's review:
etpinard Jan 16, 2018
18a3a0d
add clampFn option to dragElement & use it for radial drag box
etpinard Jan 16, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 116 additions & 9 deletions src/plots/cartesian/axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,10 @@ axes.calcTicks = function calcTicks(ax) {
minPx = ax._id.charAt(0) === 'y' ? 40 : 80;
nt = Lib.constrain(ax._length / minPx, 4, 9) + 1;
}

// radial axes span half their domain,
// multiply nticks value by two to get correct number of auto ticks.
if(ax._name === 'radialaxis') nt *= 2;
}

// add a couple of extra digits for filling in ticks when we
Expand Down Expand Up @@ -819,6 +823,13 @@ axes.calcTicks = function calcTicks(ax) {
vals.push(x);
}

// If same angle over a full circle, the last tick vals is a duplicate.
//
// TODO must do something similar for angular date axes.
if(ax._id === 'angular' && Math.abs(rng[1] - rng[0]) === 360) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had to hack a few things deep into axes.js to get the angular axes looking the way I wanted. I hope @alexcjohnson won't mind.

Copy link
Collaborator

Choose a reason for hiding this comment

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

What if you use something else as the period of a linear angular axis? Also do we need to do something more flexible to handle nearly duplicate values so we don't get overlaps?

vals.pop();
}

// save the last tick as well as first, so we can
// show the exponent only on the last one
ax._tmax = vals[vals.length - 1];
Expand Down Expand Up @@ -886,7 +897,11 @@ var roundBase10 = [2, 5, 10],
// approx. tick positions for log axes, showing all (1) and just 1, 2, 5 (2)
// these don't have to be exact, just close enough to round to the right value
roundLog1 = [-0.046, 0, 0.301, 0.477, 0.602, 0.699, 0.778, 0.845, 0.903, 0.954, 1],
roundLog2 = [-0.301, 0, 0.301, 0.699, 1];
roundLog2 = [-0.301, 0, 0.301, 0.699, 1],
// TODO
// maybe [1, 2, 5, 10, 15, 30, 45, 90, 180] would give better results
// on thin polar sectors?
roundAngles = [15, 30, 45, 90, 180];

function roundDTick(roughDTick, base, roundingSet) {
return base * Lib.roundUp(roughDTick / base, roundingSet);
Expand All @@ -911,6 +926,10 @@ function roundDTick(roughDTick, base, roundingSet) {
axes.autoTicks = function(ax, roughDTick) {
var base;

function getBase(v) {
return Math.pow(v, Math.floor(Math.log(roughDTick) / Math.LN10));
}

if(ax.type === 'date') {
ax.tick0 = Lib.dateTick0(ax.calendar);
// the criteria below are all based on the rough spacing we calculate
Expand All @@ -919,7 +938,7 @@ axes.autoTicks = function(ax, roughDTick) {

if(roughX2 > ONEAVGYEAR) {
roughDTick /= ONEAVGYEAR;
base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10));
base = getBase(10);
ax.dtick = 'M' + (12 * roundDTick(roughDTick, base, roundBase10));
}
else if(roughX2 > ONEAVGMONTH) {
Expand All @@ -944,7 +963,7 @@ axes.autoTicks = function(ax, roughDTick) {
}
else {
// milliseconds
base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10));
base = getBase(10);
ax.dtick = roundDTick(roughDTick, base, roundBase10);
}
}
Expand All @@ -963,7 +982,7 @@ axes.autoTicks = function(ax, roughDTick) {
// ticks on a linear scale, labeled fully
roughDTick = Math.abs(Math.pow(10, rng[1]) -
Math.pow(10, rng[0])) / nt;
base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10));
base = getBase(10);
ax.dtick = 'L' + roundDTick(roughDTick, base, roundBase10);
}
else {
Expand All @@ -977,10 +996,15 @@ axes.autoTicks = function(ax, roughDTick) {
ax.tick0 = 0;
ax.dtick = Math.ceil(Math.max(roughDTick, 1));
}
else if(ax._id === 'angular') {
ax.tick0 = 0;
base = getBase(1);
Copy link
Collaborator

Choose a reason for hiding this comment

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

what am I missing? Isn't getBase(1) just 1^something === 1?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch -> 7f7193d

ax.dtick = roundDTick(roughDTick, base, roundAngles);
}
else {
// auto ticks always start at 0
ax.tick0 = 0;
base = Math.pow(10, Math.floor(Math.log(roughDTick) / Math.LN10));
base = getBase(10);
ax.dtick = roundDTick(roughDTick, base, roundBase10);
}

Expand Down Expand Up @@ -1208,6 +1232,7 @@ axes.tickText = function(ax, x, hover) {
if(ax.type === 'date') formatDate(ax, out, hover, extraPrecision);
else if(ax.type === 'log') formatLog(ax, out, hover, extraPrecision, hideexp);
else if(ax.type === 'category') formatCategory(ax, out);
else if(ax._id === 'angular') formatAngle(ax, out, hover, extraPrecision, hideexp);
else formatLinear(ax, out, hover, extraPrecision, hideexp);

// add prefix and suffix
Expand Down Expand Up @@ -1402,6 +1427,66 @@ function formatLinear(ax, out, hover, extraPrecision, hideexp) {
out.text = numFormat(out.x, ax, hideexp, extraPrecision);
}

function formatAngle(ax, out, hover, extraPrecision, hideexp) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah nice, here it is 🎉 I guess even though it's currently only used for polar, there is something nice about having this in axes.js (granted that perhaps all of this should go somewhere else outside cartesian at some point) because I could imagine us wanting to support fractions and/or multiples of π on cartesian axes at some point.

But yeah, previous comments hold: we need a cutoff to switch to decimal, and then perhaps hover can use π too!

if(ax.thetaunit === 'radians' && !hover) {
var isNeg = out.x < 0;
var num = out.x / 180;

if(num === 0) {
out.text = '0';
} else {
var frac = num2frac(num);

if(frac[1] === 1) {
if(frac[0] === 1) out.text = 'π';
else out.text = frac[0] + 'π';
} else {
out.text = [
'<sup>', frac[0], '</sup>',
'⁄',
'<sub>', frac[1], '</sub>',
'π'
].join('');
}
}

if(isNeg) out.text = MINUS_SIGN + out.text;
} else {
out.text = numFormat(out.x, ax, hideexp, extraPrecision);
}
}

// inspired by
// https://github.com/yisibl/num2fraction/blob/master/index.js
function num2frac(num) {
function almostEq(a, b) {
return Math.abs(a - b) <= 1e-6;
}

function findGCD(a, b) {
return almostEq(b, 0) ? a : findGCD(b, a % b);
}

function findPrecision(n) {
var e = 1;
while(!almostEq(Math.round(n * e) / e, n)) {
e *= 10;
}
return e;
}

var precision = findPrecision(num);
var number = num * precision;
var gcd = Math.abs(findGCD(number, precision));

return [
// numerator
Math.round(number / gcd),
// denominator
Math.round(precision / gcd)
];
}

// format a number (tick value) according to the axis settings
// new, more reliable procedure than d3.round or similar:
// add half the rounding increment, then stringify and truncate
Expand Down Expand Up @@ -1846,7 +1931,7 @@ axes.doTicks = function(gd, axid, skipTitle) {
// positioning arguments for x vs y axes
if(axLetter === 'x') {
sides = ['bottom', 'top'];
transfn = function(d) {
transfn = ax._transfn || function(d) {
return 'translate(' + ax.l2p(d.x) + ',0)';
};
tickpathfn = function(shift, len) {
Expand All @@ -1859,7 +1944,7 @@ axes.doTicks = function(gd, axid, skipTitle) {
}
else if(axLetter === 'y') {
sides = ['left', 'right'];
transfn = function(d) {
transfn = ax._transfn || function(d) {
return 'translate(0,' + ax.l2p(d.x) + ')';
};
tickpathfn = function(shift, len) {
Expand All @@ -1870,6 +1955,13 @@ axes.doTicks = function(gd, axid, skipTitle) {
else return 'M' + shift + ',0h' + len;
};
}
else if(axid === 'angular') {
sides = ['left', 'right'];
transfn = ax._transfn;
tickpathfn = function(shift, len) {
return 'M' + shift + ',0h' + len;
};
}
else {
Lib.warn('Unrecognized doTicks axis:', axid);
return;
Expand All @@ -1893,6 +1985,11 @@ axes.doTicks = function(gd, axid, skipTitle) {
}
var valsClipped = vals.filter(clipEnds);

// don't clip angular values
if(ax._id === 'angular') {
valsClipped = vals;
}

function drawTicks(container, tickpath) {
var ticks = container.selectAll('path.' + tcls)
.data(ax.ticks === 'inside' ? valsClipped : vals, datafn);
Expand Down Expand Up @@ -1941,7 +2038,7 @@ axes.doTicks = function(gd, axid, skipTitle) {
return (angle * flipit < 0) ? 'end' : 'start';
};
}
else {
else if(axLetter === 'y') {
flipit = (axside === 'right') ? 1 : -1;
labely = function(d) {
return d.dy + d.fontSize * MID_SHIFT - labelShift * flipit;
Expand All @@ -1957,6 +2054,16 @@ axes.doTicks = function(gd, axid, skipTitle) {
return axside === 'right' ? 'start' : 'end';
};
}
else if(axid === 'angular') {
ax._labelShift = labelShift;
ax._labelStandoff = labelStandoff;
ax._pad = pad;

labelx = ax._labelx;
labely = ax._labely;
labelanchor = ax._labelanchor;
}

var maxFontSize = 0,
autoangle = 0,
labelsReady = [];
Expand Down Expand Up @@ -1996,7 +2103,7 @@ axes.doTicks = function(gd, axid, skipTitle) {

function positionLabels(s, angle) {
s.each(function(d) {
var anchor = labelanchor(angle);
var anchor = labelanchor(angle, d);
var thisLabel = d3.select(this),
mathjaxGroup = thisLabel.select('.text-math-group'),
transform = transfn(d) +
Expand Down
28 changes: 28 additions & 0 deletions src/plots/polar/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Copyright 2012-2017, 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';

module.exports = {
attr: 'subplot',
name: 'polar',

axisNames: ['angularaxis', 'radialaxis'],
axisName2dataArray: {angularaxis: 'theta', radialaxis: 'r'},

// TODO should radial axis be above frontplot by default?
layerNames: [
'draglayer',
'plotbg',
'backplot',
'grids',
'axes',
'lines',
'frontplot'
],
};
82 changes: 82 additions & 0 deletions src/plots/polar/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Copyright 2012-2017, 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 Plots = require('../../plots/plots');
var counterRegex = require('../../lib').counterRegex;

var createPolar = require('./polar');
var constants = require('./constants');

var attr = constants.attr;
var name = constants.name;
var counter = counterRegex(name);

var attributes = {};
attributes[attr] = {
valType: 'subplotid',
role: 'info',
dflt: name,
editType: 'calc',
description: [
'Sets a reference between this trace\'s data coordinates and',
'a polar subplot.',
'If *polar* (the default value), the data refer to `layout.polar`.',
'If *polar2*, the data refer to `layout.polar2`, and so on.'
].join(' ')
};

function plot(gd) {
var fullLayout = gd._fullLayout;
var calcData = gd.calcdata;
var subplotIds = Plots.getSubplotIds(fullLayout, name);

for(var i = 0; i < subplotIds.length; i++) {
var id = subplotIds[i];
var subplotCalcData = Plots.getSubplotCalcData(calcData, name, id);
var subplot = fullLayout[id]._subplot;

if(!subplot) {
subplot = createPolar(gd, id);
fullLayout[id]._subplot = subplot;
}

subplot.plot(subplotCalcData, fullLayout, gd._promises);
}
}

function clean(newFullData, newFullLayout, oldFullData, oldFullLayout) {
var oldIds = Plots.getSubplotIds(oldFullLayout, name);

for(var i = 0; i < oldIds.length; i++) {
var id = oldIds[i];
var oldSubplot = oldFullLayout[id]._subplot;

if(!newFullLayout[id] && !!oldSubplot) {
oldSubplot.framework.remove();

for(var k in oldSubplot.clipPaths) {
oldSubplot.clipPaths[k].remove();
}
}
}
}

module.exports = {
attr: attr,
name: name,
idRoot: name,
idRegex: counter,
attrRegex: counter,
attributes: attributes,
layoutAttributes: require('./layout_attributes'),
supplyLayoutDefaults: require('./layout_defaults'),
plot: plot,
clean: clean
};
Loading