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

Better polar setConvert + a few misc polar touch ups #2895

Merged
merged 18 commits into from
Aug 15, 2018

Conversation

etpinard
Copy link
Contributor

@etpinard etpinard commented Aug 10, 2018

This is a preliminary PR for barpolar.

I dove back hard into the polar code in the past 10 days and I noticed a few hacks that were worth cleaning up before adding barpolar.

Commit 3d8eef2 is the big one. In brief, it locks down what 'c'alc means for angular coordinates and adds a new coordinate system (I called it 'g'eometric) that depends on things that interactions can update (i.e. radialaxis.range and angularaxis.rotation). I invite you to read the setConvertPolar docstring as well as commit 91064ca for more info. It's still not perfect: ideally we should try to factor things in Axes.doTicks to no longer rely on 2 radial and 2 angular axis objects per polar subplots, but this feels like a step in the right direction.

After that, this PR also:

cc @alexcjohnson

- 🔪 one obsolete comment
- use trace.orientation instead using same x/y logic as in
  supplyDefaults.
- introduce 'g'eomtric coordinate system, that handles
  angular rotation / directrion and radial range shifts
- add 't'icks coord system for angular axes tick vals
- add setGeometry (which sets the c2g converters)
- make 'c' linear theta coordinates be in radians,
  eliminating trace.thetaunit deps after 'calc'
... which is on-par with fullLayout.polar.angularaxis
- where the dtheta default is the ax.period divided by the trace
  length (given by the length of the 'other' coordinate).
@etpinard etpinard added bug something broken feature something new status: reviewable labels Aug 10, 2018
@etpinard etpinard added this to the v1.40.0 milestone Aug 10, 2018

var xa = Axes.getFromId(gd, trace.xaxis || 'x'),
ya = Axes.getFromId(gd, trace.yaxis || 'y'),
orientation = trace.orientation || ((trace.x && !trace.y) ? 'h' : 'v'),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice catch!

* + 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
Copy link
Collaborator

Choose a reason for hiding this comment

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

This would be easier for me to understand if instead of describing t2g etc, just describe t and g (and c and d if they're different from cartesian) for the linear and category cases)

Copy link
Collaborator

Choose a reason for hiding this comment

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

(like

* d: data, in whatever form it's provided
* c: calcdata: turned into numbers, but not linearized
* l: linearized - same as c except for log axes (and other nonlinear
* mappings later?) this is used when we need to know if it's
* *possible* to show some data on this axis, without caring about
* the current range
* p: pixel value - mapped to the screen with current size and zoom
* r: ranges, tick0, and annotation positions match one of the above
* but are handled differently for different types:
* - linear and date: data format (d)
* - category: calcdata format (c), and will stay that way because
* the data format has no continuous mapping
* - log: linearized (l) format
* TODO: in v2.0 we plan to change it to data format. At that point
* shapes will work the same way as ranges, tick0, and annotations
* so they can use this conversion too.
)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

latest attempt in -> 4c11c84

Copy link
Collaborator

Choose a reason for hiding this comment

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

Much clearer - thanks!

var rot = deg2rad(ax.rotation);

ax.rad2g = function(v) { return dir * v + rot; };
ax.g2rad = function(v) { return (v - rot) / dir; };
Copy link
Collaborator

Choose a reason for hiding this comment

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

As discussed in slack, seems like we can 🔪 ax.rad2* and ax.*2rad entirely now, so we don't need to think about the difference between rad and g to understand all this.

Copy link
Contributor Author

@etpinard etpinard Aug 13, 2018

Choose a reason for hiding this comment

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

done in f5bceda

- ?2rad and rad2? methods are now gone
- ax._period is gone
* - this.yaxis
* setup so that polar traces can reuse plot methods of Cartesian traces
* which mostly rely on 2pixel methods (e.g ax.c2p)
*/
Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks! 📚

@@ -257,8 +257,6 @@ proto.updateLayout = function(fullLayout, polarLayout) {
_this.angularAxis = _this.mockAxis(fullLayout, polarLayout, angularLayout, {
_axislayer: layers['angular-axis'],
_gridlayer: layers['angular-grid'],
// angular axes need *special* logic
_id: 'angular',
Copy link
Collaborator

Choose a reason for hiding this comment

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

for reference, here's where _id gets set to angularaxis

axOut._id = axOut._name = axName;

@@ -554,7 +554,7 @@ axes.calcTicks = function calcTicks(ax) {

// 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) {
if(ax._id === 'angularaxis' && Math.abs(rng[1] - rng[0]) === 360) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This gets a lot of reuse... perhaps function isAngular(axid) { return axid === 'angularaxis'; }?

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 call -> 0c517d1

@@ -1682,7 +1682,7 @@ axes.doTicksSingle = function(gd, arg, skipTitle) {
var valsClipped = vals.filter(clipEnds);

// don't clip angular values
if(ax._id === 'angular') {
if(ax._id === 'angularaxis') {
Copy link
Collaborator

Choose a reason for hiding this comment

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

not new in this PR, but I think you have an axid here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

went one step further in 0c517d1

'By default, the `dtheta` step equals the subplot\'s period divided',
'by the length of the `r` coordinates.'
].join(' ')
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

One of the most underrated features, IMHO. Thanks for adding it!

@@ -302,6 +302,8 @@ function sceneUpdate(gd, subplot) {
}
if(scene.scatter2d) {
clearViewport(scene.scatter2d, vp);
} else if(scene.line2d) {
clearViewport(scene.line2d, vp);
Copy link
Collaborator

Choose a reason for hiding this comment

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

The first arg to clearViewport is just used to find the regl._gl context... and so I guess the reason select2d is off by itself is that select gets its own context separate from the main data context?

Might be clearer then to refactor this as something like

var anyComponent = scene.scatter2d || scene.line2d || (scene.glText || [])[0];
if(anyComponent) clearViewport(anyComponent, vp);

Which would also make it easier to add error2d (can that ever be the only thing drawn?) and fill2d to the lookup list, looks plausible that these also have bugs similar to #2888.

BTW, looking at what clearViewport does - ie clears a rectangle from the context - what happens if two polar subplots have overlapping bounding boxes? Like if you made a hexagonal packing of polar subplots or you do something funny with sectors, like you make one large pac-man subplot eating a smaller one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

what happens if two polar subplots have overlapping bounding boxes?

This happens -> #2797

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Might be clearer then to refactor this as something like

Absolutely -> c3044e0

add error2d (can that ever be the only thing drawn?)

Traces with error bar will always have either an active scene.scatter2d or scene.line2d.

BTW, looking at what clearViewport does - ie clears a rectangle from the context -

We should take a look at how much of a perf gain is to clear only a fraction of the canvas on pan/select. If that's negligible, then I'm thinking we should be keeping track of a graph-wide regl-draw-queue instead, always clear the whole canvas and draw all regl parts on pan/select. This should make this a lot more manageable. cc @dy

Copy link
Collaborator

Choose a reason for hiding this comment

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

Traces with error bar will always have either an active scene.scatter2d or scene.line2d.

Even if mode: 'none'? That works fine for scatter, and sometimes tries to work with scattergl #2900

fill works with mode: 'none' too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Bulletproofed in 75786e4

@@ -993,11 +993,13 @@ proto.updateRadialDrag = function(fullLayout, polarLayout) {
var moduleCalcDataVisible = Lib.filterVisible(moduleCalcData);
var _module = moduleCalcData[0][0].trace._module;
var polarLayoutNow = gd._fullLayout[_this.id];
var isGL = Registry.traceIs(k, 'gl');

if(isGL && _this._scene) _this._scene.clear();
Copy link
Collaborator

Choose a reason for hiding this comment

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

is it possible to not have _this._scene at this point if there's a gl trace?

BTW this would be clearer with traceType instead of k.

I don't see a problem at the moment because there's only one gl type that can be here, but for future reference seems like if we ever add another we'll have to make sure only the first one clears the scene.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for tip. Improvements in -> b81885d

@@ -1136,7 +1143,9 @@ describe('Test polar interactions:', function() {
fig.layout.margin = {l: 50, t: 50, b: 50, r: 50};

if(s.patch) s.patch(fig);
nTraces = fig.data.length;
nTraces = fig.data.reduce(function(acc, trace) {
return (trace.type === 'scatterpolargl') ? ++acc : acc;
Copy link
Collaborator

Choose a reason for hiding this comment

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

hmm, OK. I always find prefix ++ confusing, and perhaps because it encourages stuff like that reduce also often comes out confusing to me. I realize it's better perf this way, but that's irrelevant to a test.
fig.data.filter(function(trace) { return trace.type === 'scatterpolargl'; }).length?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you're right, filter is better: b4700ca

@alexcjohnson
Copy link
Collaborator

Nice work! 💪 💃

@etpinard etpinard merged commit 665a9c4 into master Aug 15, 2018
@etpinard etpinard deleted the polar-better-set-convert branch August 15, 2018 14:18
@etpinard etpinard mentioned this pull request Sep 6, 2018
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug something broken feature something new
Projects
None yet
Development

Successfully merging this pull request may close these issues.

scatterpolargl in 'lines' mode smears on axis rotation
2 participants