Skip to content

Fixed size circle shape #2193

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

Closed
chriddyp opened this issue Dec 5, 2017 · 19 comments
Closed

Fixed size circle shape #2193

chriddyp opened this issue Dec 5, 2017 · 19 comments
Labels
feature something new
Milestone

Comments

@chriddyp
Copy link
Member

chriddyp commented Dec 5, 2017

I'm helping out a Dash community user who could use interactive shape circles (https://community.plot.ly/t/moving-the-location-of-a-graph-point-interactively/7161/1)

I'd like a shape circle that has a fixed size in points but is positioned relative to data - something like a marker point that I can drag around but not necessarily resize.

Right now, circle shapes change size when you drag them around:
circle shape

This is somewhat related to #2038

@chriddyp chriddyp added this to the Dash milestone Dec 5, 2017
@alexcjohnson
Copy link
Collaborator

Cool idea, and there are a lot of variants of this that could be useful. Even for (fully or partially) paper-referenced shapes, it might be nice to be able to size them in px, like if you want to draw your own arrow pointing to a particular axis value, or you have a logo or something that you want a fixed size even if the plot resizes.

Seems to me the most flexible way to do this would be adding attributes like:

xmode: 'scaled' | 'pixel' // scaled (dflt) -> x0/x1 are data vals or plot units, anchorx is ignored
                          // pixel -> x0/x1 are pixels relative to anchorx
anchorx: (any) // data val or plot fraction, depending on `xref`
ymode, anchory: same for y

Not sure about the naming, but that would leave the flexibility to do things like make a shape off to the side of a data point, like (x|y)shift for annotations, not just centered.

@rmoestl
Copy link
Contributor

rmoestl commented Mar 19, 2018

I think it is worth noting that the API suggested by @alexcjohnson will support two different uses cases:

  1. Scenario A: to render a fixed size shape positioned relative to data (blue rectangle below) as suggested by @chriddyp. E.g. useful to annotate a specific data point.
  2. Scenario B: to render fixed size shapes relative to the plotting area (red rectangle). E.g. useful to place an icon of some sort at the top right corner of a plot.

Despite it increases the API surface, I can clearly see the benefit of having an anchor. When annotating a specific data point you certainly would want the control to render it 3px left the data point, for example.

Regarding the naming, I find anchorx quite good. Might xsizing be more clear than xmode? But I also saw "mode" as parts of an attribute name already. So xmode might be more consistent within plotly.js...

One question, though: Should we take the dimensions of a fixed size shape into account when calculating the range of an axis?
It is clear to factor in the position of a fixed size shape, but when it comes to the dimensions, it could become hairy, right? It could easily be the case that a fixed size shape overflows the plotting area (the green rectangle). To prevent this, we would need to know how "much data" is worth one pixel at the time of calculating the axis range.

2193_fixed-size-shape-scenarios

@etpinard
Copy link
Contributor

This reminds me of how we specify layout images. Attributes sizex, sizey, xanchor, yanchor and sizing could be used in scatter markers too.

@rmoestl
Copy link
Contributor

rmoestl commented Mar 21, 2018

Thanks @etpinard for pointing me into that direction. Here are my thoughts.

API

images layout attributes xanchor and yanchor are about an image's alignment – a different meaning than what we have in the initial proposal for fixed size shapes. Also, image sizex and sizey can't have pixel values.

So because of that, I stuck with the semantics of @alexcjohnson's initial proposal for the time being. Although with slightly different naming. Here's what the API currently looks like in my local branch.

// Shape positioned relative to data
shapes: [{
    type: 'rect',
    xref: 'x',
    yref: 'y',
    xsizemode: 'pixel',
    ysizemode: 'pixel',
    xanchor: '3',
    yanchor: '0',
    x0: 3,
    x1: 25,
    y0: 0,
    y1: 25
}]
// Shape positioned relative to plot area, in this case top right corner
shapes: [{
    type: 'rect',
    xref: 'paper',
    yref: 'paper',
    xsizemode: 'pixel',
    ysizemode: 'pixel',
    xanchor: '1',
    yanchor: '1',
    x0: 3,
    x1: 25,
    y0: 0,
    y1: 25
}]

Any feedback on that?
By the way, one can size by pixel horizontally and relative to data vertically and vice versa.

Autorange

When calculating an axis range the xanchor and yanchor coordinates are taken into account. However, it still needs to be decided whether to factor in the size and position of the shape as well. I lean towards not factoring that in. What do you think?

@etpinard etpinard added the feature something new label Mar 21, 2018
@etpinard
Copy link
Contributor

Any feedback on that?

👍 from me. Nice!

When calculating an axis range the xanchor and yanchor coordinates are taken into account. However, it still needs to be decided whether to factor in the size and position of the shape as well. I lean towards not factoring that in. What do you think?

In calc_autorange.js, we already take into account the shapes' bounds on auto-ranged graphs. It shouldn't be too hard to extend that to pixel size modes using the ax.p2c (for pixel-to-calculated data) convert method.

@alexcjohnson
Copy link
Collaborator

Yes, please do include the whole shape in autorange. But don't use ax.p2c to do it - that assumes the axis scaling (ie range & length) is already known, and a) that's what we're trying to calculate with Axes.expand, and b) we'd like this calculation to not need to be redone when the plot changes size, margins, etc.

Fortunately we DO have this built into Axes.expand, in the form of ppad (pixel padding) and its asymmetric variants ppadplus and ppadminus -
https://github.com/plotly/plotly.js/blob/master/src/plots/cartesian/autorange.js#L228-L230

@rmoestl
Copy link
Contributor

rmoestl commented Mar 28, 2018

Thanks @alexcjohnson, I've now implemented the correct auto-ranging for lines, rectangles and circles.

However, I wonder what we should do about path shapes. We haven't really discussed that in this thread except @alexcjohnson mentioned arrows in his initial reply. Should we allow pixel sized path shapes as well? It would certainly be a cool feature. But I'm not sure if enough people would use it so that the effort is worth it.

@alexcjohnson
Copy link
Collaborator

Yes, it'll be more effort to get path working with pixel sizing - but I think we should do it. We don't want any more exceptions in the API than we really need, and even if very few people use it, we'll be able to make some extra-cool demos with this feature.

@chriddyp
Copy link
Member Author

Yes, it'll be more effort to get path working with pixel sizing - but I think we should do it
we'll be able to make some extra-cool demos with this feature

Yeah, that's pretty interesting. I wonder if this will allow the user to draw an entire line plot and then drag around the vertices, which is what the Dash community member originally asked about in https://community.plot.ly/t/moving-the-location-of-a-graph-point-interactively/7161:

I want to give the user the ability to drag a point on a line graph either up or down, thereby changing the underlying y-axis data (what if analysis). This would then change other charts on the dashboard.
Is this type of interactivity possible through Dash (clicking on and moving a point)?

@alexcjohnson
Copy link
Collaborator

We don't support editing vertices of path shapes by GUI currently, and while that could be interesting, I feel like dragging data points themselves would be a more valuable feature to add first. Not in this issue though, we can make another issue if there's sufficient interest.

@rmoestl
Copy link
Contributor

rmoestl commented Mar 28, 2018

Question about how the coordinate system for specifying fixed size shapes will be expected by the users: The positive x direction will point to the right.

But what about the positive y direction? Will it point to the bottom (as in SVG) or to the top (like the plot's coordinate system)? First, I leaned towards 'to the top'. But I suppose most users are coders, so they might be familiar with positive y-axis pointing 'to the bottom'.

@alexcjohnson
Copy link
Collaborator

It's true, this is a weird point that requires a lot of minus signs in our code, but I think positive y should be toward the top, no matter whether it's data (unless you explicitly flip the y axis), plot fraction, or pixels.

There's one exception to this, that I know of anyway, and that's annotations[i].ay, which is to the bottom. I think elsewhere "to the top" is consistent enough that we should make anything new be "to the top", and in v2.0 we should flip ay to match (or even before, if we couple the fix with giving this attribute a nicer name, then we can do it in a backward-compatible way).

@etpinard
Copy link
Contributor

but I think positive y should be toward the top, no matter whether it's data

+1

@rmoestl
Copy link
Contributor

rmoestl commented Apr 4, 2018

To expand an axis with a fixed size path shape, I need to extract the x and y coordinates of the path's d attribute (in order to set the ppadminus and ppadplus of Axes.expand correctly).

There's somewhat similar code in

function convertPath(pathIn, x2p, y2p) {

I could base my code on that. But before doing so, I wanted to ask if there is already a function in the Plotly.js codebase that parses the d path attribute.

@alexcjohnson
Copy link
Collaborator

That's probably the only place we parse a d path attribute. See if you can generalize that code into a form you can reuse, and feel free to move it to another file that both shapes/draw and your addition (in shapes/calc_autorange?) can import from.

@rmoestl
Copy link
Contributor

rmoestl commented Apr 5, 2018

For a pixel-sized shape (rect, line, circle), should x1/y1 be the width/height of the shape relative to x0/y0 or should x1/y1 be relative to xanchor/yanchor? In the latter case width/height would be the distance between x0 and x1/y0 and y1.

I'm asking because right now I've implemented the former case with x1/y1 being the width/height. Unfortunately I overlooked @alexcjohnson initial spec saying

x0/x1 are pixels relative to anchorx

That describes the latter case. I see pros and cons for both approaches.

x1/y1 being the width/height

  • Pros
    • easier for user to specify a square, a circle, the length of a line
  • Cons
    • inconsistent to existing shapes API
    • a bit harder to implement, therefore less maintainable code

Pros and cons are the reverse of the above for x1/y1 being absolute (anchor is 0/0).

I should have implemented the initial spec in the first place, sorry. Anyways, there's now the chance to decide if x1/y1 being the width/height is the better way.

@rmoestl
Copy link
Contributor

rmoestl commented Apr 5, 2018

In today's check-in meeting we've decided the following: x1 and y1 are going to be absolute coordinates with xanchor and yanchor being the origin. There are certain situations where own way of specifying the dimension would be favorable over the other and vice versa. The absolute approach chosen is better for the most likely case: centering a shape (e.g. a circle) on a data point. And in addition to that, it is more consistent with the existing shapes API.

For the question 'When being moved, should the anchor or the x and y coordinates of a fixed size shape be changed?' it was concluded that the anchor should. This approach is resembling common UI patterns implemented in apps such as Google Slides.

rmoestl added a commit that referenced this issue Apr 6, 2018
- Reason: prepare writing fixed size shape tests.
rmoestl added a commit that referenced this issue Apr 6, 2018
- Shapes (line, rect, circle as well as path) can now be
  sized by pixel while being positioned relative to data.
- New shape attributes `xsizemode`, `xanchor`, `ysizemode`
  and `yanchor` introduced and semantics of `x0`, `x1`,
  `y0` and `y1` extended.
- Shapes can be sized by pixel on one axis and sized by
  data on the other.
- Fixed size shapes can be moved.
- Fixed size lines, rectangles and circles can be resized,
  paths not.
- Fixed size shapes are fully taken into account when an
  axis is in auto-range mode.
rmoestl added a commit that referenced this issue Apr 10, 2018
- Note: not an exhaustive mock with all variations but rather a set of
  shapes covering the newly introduced capabilities, also useful
  for manually testing and debugging things.
rmoestl added a commit that referenced this issue Apr 11, 2018
- Bug revealed during review of #2193
- Bug: if a shape is not or not completely on screen at time of drag
  element setup, obtained bounding box would not be correct leading
  to the calculation of a wrong cursor once the shape scrolls into the
  viewport.
@etpinard
Copy link
Contributor

@rmoestl did #2532 close this issue?

@rmoestl
Copy link
Contributor

rmoestl commented Apr 12, 2018

@etpinard yes!

@rmoestl rmoestl closed this as completed Apr 12, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature something new
Projects
None yet
Development

No branches or pull requests

4 participants