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

Support any number of points in SelectionTool #1430

Closed

Conversation

PeterC-DLS
Copy link
Contributor

This adds a capability to define selections that need different number of points:

  1. Can specify minimum number of points
  2. Can specify maximum number of points, can be -1 for no limit
  3. When number of points is at least minimum
    - set status boolean
    - end selection on enter/return key press
    - pointer down on same position sets last point (within maxMovement)

Also make SVG selections compatible using new type, add SvgPolyline and use in new story

@axelboc
Copy link
Contributor

axelboc commented Apr 26, 2023

Thanks @PeterC-DLS, this feature is very welcome! However, this is not the right implementation approach, in my opinion.

SelectionTool implements a click-and-drag interaction with a start point and end point. As such, it is meant mainly for drawing selection areas (i.e. to select elements, zoom, etc.) and simple shapes (lines, rectangles, circles, etc.) Drawing polylines and irregular polygons is a whole other interaction and therefore would benefit from being implemented as a dedicated component.

It would have been great to discuss the naming of the component and the user needs it fulfils in an issue ahead of time. 😅 For instance, I'm not sure I get the need for being able to limit the number of points, and how this setting affects the overall interaction. I'm also not sure how drawing polylines vs polygons should be handled, both in terms of user freedom (i.e. should the user be able to do both at any time, or should this be controlled by a prop?) and interaction (e.g. ending the shape by clicking on the last point for a polyline or the first point for a polygon, or always the last point and auto-closing, or double-clicking, or pressing Enter, or ...)

As a starting point, could you please maybe describe the situations that require your users to draw polylines and polygons?

@PeterC-DLS
Copy link
Contributor Author

The story with polylines is solely for demonstration. A real example of using unlimited number of points to fit an ellipse manually by selecting points on its outline in a diffraction pattern. As the user selects more points, the fit may improve.

@PeterC-DLS
Copy link
Contributor Author

Another easy example is allowing a user to define a tilted ellipse by three points: its centre, major semi-axis, then minor semi-axes.

@axelboc
Copy link
Contributor

axelboc commented May 9, 2023

A real example of using unlimited number of points is to fit an ellipse manually by selecting points on its outline in a diffraction pattern.

Makes sense. So the interaction comes down to creating the outline of a polygon one point at a time.

Closing the polygon

The main decision I see is how to close the polygon when you're done drawing it. In Inkscape, this is done by clicking on the first point of the path:

Peek 2023-05-09 14-55

If you double click instead (which is similar to clicking again on the last point of the path), you make a polyline, not a polygon.

Personally, I find clicking on the first point to be more natural. Of course, this doesn't necessarily mean adding the starting point twice in the resulting array.

Closing the polygon with the Enter key is a great shortcut for power users, with which I'm fully on board.

Affordance

A couple of other things we can learn from Inkscape:

  • The first point has to look clickable and react on hover (and be sufficiently big to be easy to hover/click on).
  • The line between the last point and the mouse is displayed in a different colour to indicate that it is not yet part of the polyline/polygon.

Concerning the second point, we could go a step further and always render the polygon closed as if the user were about to close it, and connect the mouse position to the polygon with two additional, more subtle lines (one to the starting point and one to the last-added point).

Validation

I'm not sure about this. I'd be more incline to let consumers implement their own validation through a validate callback than to provide a minPoints prop or the like. However, I'm not sure when this validate callback should be called (on every pointermove, when adding a point, when hovering the first point to close the polygon, ...), and how the validity state should be shown to users.

I suggest we wait for a more concrete validation use case. For now, we can allow closing the polygon (and show the corresponding affordance mechanism) once the user has added at least 3 points.

Cancellation

When it comes to cancelling the creation of the polygon, I'm on board with using Escape like in SelectionTool. However, I think this should be doable with the mouse as well somehow, for discoverability (i.e. in SelectionTool, you simply release the pointer). What do you think of using the right click to remove the last-added point? By right-clicking multiple times, users could then go back to the first point and right-click again to effectively cancel the interaction.

Implementation

With all this in mind, here is what I'm thinking for a first implementation:

  • A dedicated component called PolygonTool with a children render prop to let the consumer customise the appearance of the polygon being drawn.
  • The render prop receives the points currently in the polygon, plus the position of the mouse.
  • An SvgPolygon component to render the polygon as it is being drawn (the lines to the mouse can be rendered with SvgLine).
  • A generic Points model (Vector3[]), separate from Rect ([Vector3, Vector3]), which can be used to store polygons (without repeating the starting point).
  • PolygonTool calls the useInteraction hook to register a new custom interaction.
  • PolygonTool accepts the following props:
    • the two common interaction props (CommonInteractionProps): modifierKey and disabled;
    • an id prop to allow consumers to customise the ID of the interaction registered with useInteraction (defaulting to 'Path');
    • four event callbacks: onStart, onChange, onEnd, onClose.
  • PolygonTool implements the interaction as follows:
    • the first point is created with a pointerdown event, after calling shouldInteract() (it is up to the consumer to ensure that other interactions such as panning don't conflict with polygon creation interaction) => onStart;
    • more points are added to the polygon also with pointerdown events => onChange;
    • as soon as the polygon has at least three points, the first point of the polygon appears as a subtle disc, which, when hovered, becomes more prominent somehow (and the cursor changes to pointer);
    • the polygon is closed with a pointerdown event on that disc => 1) onEnd with a closed parameter set to true, then onClose with the final points;
    • right-clicking anywhere on the canvas while a polygon is being drawn removes the latest point from the polygon (and doesn't bring up the context menu) => onChange;
    • if right-clicking removes the first-and-only-remaining point of the polygon, onEnd gets called with parameter closed set to false.

Another easy example is allowing a user to define a tilted ellipse by three points: its centre, major semi-axis, then minor semi-axes.

This is a very interesting use case. The adjective "tilted" is crucial, as it explains why the ellipse cannot be drawn with the SelectionTool. I guess this is the justification for being able to specify a maximum number of points for the polygon/polyline interaction in your implementation?

Clearly, here, clicking on the first point to close the polygon makes no sense. This makes me think that we shouldn't try to solve the two use cases with the same component. Perhaps we can abstract a custom hook from PolygonTool to share the basic interaction of clicking on the canvas to add points to an array.

@PeterC-DLS
Copy link
Contributor Author

I think the polygon case is special in the sense that the shape is closed so "last" point is automatically the first. In an earlier version of my PR, I used a SVG polygon element where in this case minPoints would be 2 and you would need to click twice on the last point or press Enter to finish the selection. But I wanted to test and demonstrate the single point case so switched to polyline.

This adds a capability to define selections that need different number of
points:
  1. Can specify minimum number of points
  2. Can specify maximum number of points, can be -1 for no limit
  3. When number of points is at least minimum
   - set status boolean
   - end selection on enter/return key press
   - pointer down on same position sets last point (within maxMovement)

Also make SVG selections compatible using new type, add SvgPolyline and
use in new story
@axelboc
Copy link
Contributor

axelboc commented Jul 26, 2023

Closing this, sorry. To reiterate, modifying SelectionTool is definitely not the right approach in terms of maintainability and scalability to additional selection/drawing use cases.

You will have a much easier time implementing your own interaction component directly in your project (rather than in your fork of H5Web) so that H5Web's SelectionTool can stay relatively simple and dedicated to drawing simple two-point selection boxes.

@axelboc axelboc closed this Jul 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants