-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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 polygon grids #2739
Polar polygon grids #2739
Conversation
... in preparation fo polygon grids to avoid confusion
... in preperation for polygon grids where angular axis tick must be computed earlier on.
- which speeds up the category angular tick filter, and will help with polygon grids
- this adds polygons grid (and angular ax line) support to categorical angular axes - had to add 'polygon mode' to pathSector() and isPtWithinSector() - polygon vertices match the angular ticks - snaps radialaxis.angle to vertex angles - needed special care for sector and on angular drag
Would something like |
It would work, but
|
throwing some ideas out there:
|
Yes. Rotational symmetry is already gone.
This isn’t already the case for category axes?
Would be weird if there are little gaps between the angular and radial drag areas, so probably yes. |
Ok. So should the cursor follow the handle, or should it just snap to the closest polygon edge? |
Mmm, it could actually be a bit weird if the handles move around everywhere the cursor goes, especially as they slip over the corners and would have to bend in arbitrary places. And there would be something nice about leaving the handles in the center of the polygon edge, where we know there's no data, only connecting lines. So yeah, I guess snap to the nearest edge center, sound reasonable? That said, currently (circular mode) the starting handle is fixed and only the ending handle moves around with the cursor, I wonder if it wouldn't look cleaner to have the ending handle stay at the same angle as the starting handle no matter what? That's how we do it in cartesian with one axis Regardless (and this may already be implied but just to be clear) I think it's important that the radial value implied by any given cursor location is given by the polygon it's on, NOT its distance from the center point. |
Going with |
I like it. @alexcjohnson What do you think? |
Is this what you had in mind @alexcjohnson ? |
About Plotly.newPlot(gd, [{
x: ['a', 'b', 0.5],
y: [1, 2, 3]
}, {
type: 'scatterpolar',
r: [1, 2, 3],
theta: ['a', 'b', 0.5]
}], {
xaxis: {
type: 'category',
domain: [0, 0.5]
},
polar: {
domain: {x: [0.5, 1]},
angularaxis: {type: 'category'}
},
annotations: [{
xref: 'x', x: 1.5,
yref: 'y', y: 2,
showarrow: false,
text: 'text at (1.5, 2)'
}]
}) gives: But we should keep this in mind when we'll add polar data-ref'ed annotations. |
and what if the cursor is on a vertex? |
break the symmetry however you like - but I don't think it's worth making bent handles at vertices, in fact I'd argue it's better to use the edge centers anyway because we know we're not obscuring any data points there. |
Wow, I totally misread that comment of yours. @alexcjohnson here's my latest attempt ⬇️ |
... where handles are placed on the polygon under the cursor halfway between the closest vertices
@alexcjohnson latest attempt |
See: plotly.js/src/plots/polar/polar.js Lines 1145 to 1149 in 40040a5
|
- please note, the bounding box of a polygon inside a circle isn't the same as the bounding box of the circle
I didn't expect adding almost 1000 lines for this thing, but oh well here it is ✅ |
type: 'scatterpolar', | ||
// octogons have nice angles | ||
r: [1, 2, 3, 2, 3, 1, 2, 1, 2], | ||
theta: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, not for this PR of course (and would be useful in circular mode as well), but this makes me realize we should have a mode in scatterpolar
to join the endpoints together so you don't need to repeat the initial point when the data are meant to be periodic. trace.connectends
perhaps, to mirror trace.connectgaps
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call. Added to -> #2255
@@ -532,8 +532,24 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { | |||
|
|||
Axes.doTicksSingle(gd, ax, true); | |||
|
|||
// angle of polygon vertices in radians (null means circles) | |||
// TODO what to do when ax.period > ax._categories ?? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the "angular period > _categories" subplot in your mock looks good - does that mean this TODO is done?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That mock looks good, but I'm not sure it's correct. I still don't know what the ax.period
> ax._categories
case can or should represent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... and there's an item about this in #2255
src/plots/polar/polar.js
Outdated
// find intersection of 'v0' <-> 'v1' edge with a 'radial' line | ||
// (i.e. a line that starts from the origin at angle 'a') | ||
// given an (xp,yp) pair on the 'v0' <-> 'v1' line | ||
// (N.B. 'v0' and 'v1' are angles in radians) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whew, I think I understand the goal of this function: so you have two rays at angles v0
and v1
, and given points on each ray at equal radius and a segment connecting them, first find the radius (or the segment) for which that segment intersects [xp, yp]
. Then, find the point on a ray at angle a
that intersects this segment. Did I get that right?
If a
always matches either v0
or v1
this looks fine, but the else
clause looks like it will give the wrong answer if a
is anything else (since the norm of the result won't be r
then).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whew, I think I understand the goal of this function: so you have two rays at angles v0 and v1, and given points on each ray at equal radius and a segment connecting them, first find the radius (or the segment) for which that segment intersects [xp, yp]. Then, find the point on a ray at angle a that intersects this segment. Did I get that right?
Yeah, you got it right except that a priori we don't know the points on each ray. We only know their angles.
If a always matches either v0 or v1 this looks fine, but the else clause looks like it will give the wrong answer if a is anything else (since the norm of the result won't be r then).
Good eye. Thanks!
When computing the the zoombox coordinates, a
matches v0
so no bugs there. When computing the (clipped) polygon coordinates (when polar.sector
isn't [0,360]) a
is either polar.sector[0]
or polar.sector[1]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At lot of little things weren't perfect for non-[0,360] polar sectors. Thanks for bringing this up. 7d405a0 fixes this.
src/plots/polar/polar.js
Outdated
// solve g(xstar) = h(xstar) | ||
var m = dsin / dcos; | ||
var b = yp - m * xp; | ||
var mr = Math.sin(a) / Math.cos(a); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Math.tan(a)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, this isn't so 😆 , Math.tan
isn't bounded. I didn't noticed anything wrong before because:
Math.tan(Math.PI / 2)
// => 16331239353195370
(in Chrome 67 at least).
Oh well, 7d405a0 made things more robust.
src/plots/polar/polar.js
Outdated
return function(index) { | ||
return index < 0 ? len + index : | ||
index < len ? index : index - len; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, so nice. Thanks for tip. Sub'ed in -> 1ca6508
@etpinard beautiful, your trigonometry teacher would be very proud 🥇 |
Very nice. 💃 stands (or continues dancing?), lets do it! |
This PR adds an attribute to polar 2.0 charts that allows users to draw polar radial grid lines and the angular axis line as polygons when the angular axis has
type: 'category'
. in brief, this allows users to create poorman's radar charts (i.e. radar charts with the same radial scale for all variables).This new attribute is tentatively named
polar.usepolygons
. I'm not particularly happy about the name. I think we should make this apolar.
attribute as it affects the look of both radial axis grid lines and the angular axis line. I'm open to suggestions.Adding this feature turned out to a little more difficult than I thought. Quite a bit of "polygon-only" needed to be added in c3f5b1b. Getting this to work for sectors for tricky (though there's probably a simpler algo out there). We need to snap
radialaxis.angle
to one of the vertex angles to make sure radial axis scale remain the same as the data scale. Angular drag needed some special treatment. Here's what it looks like:TODO:
theta
values to string or "integer" to not show "out-of-scale" valuescc @alexcjohnson @chriddyp