spatialnetworkslab / florence

This is a read-only mirror of the Gitlab repository: https://gitlab.com/spatialnetworkslab/florence. A grammar of graphics, built on Svelte's template syntax.
https://florence.spatialnetworkslab.org
MIT License
8 stars 1 forks source link

[Interactivity] Selection #107

Open luucvanderzee opened 4 years ago

luucvanderzee commented 4 years ago

To support selection, I propose the following syntax:

<script>
  import { Graphic, Section, Point, Rectangle } from '@snlab/florence'

  const pointData = [ ... ]
  const selectedPoints = {}
  let section
  let selectRectangle

  const initSelection = event => {
    section.resetSelection()

    const { x, y } = event.screenCoordinates 
    selectRectangle = { x1: x, x2: x, y1: y, y2: y }
  }

  const updateSelection = event => {
    const { x, y } = event.screenCoordinates 
    selectRectangle.x2 = x
    selectRectangle.y2 = y

    section.updateSelection(selectRectangle)
  }
</script>

<Graphic width={500} height={500}>

  <Section
    bind:this={section}
    onMousedown={initSelection}
    onDrag={updateSelection}
  >

    {#each pointData as point, i (i)}
      <Point
        x={point.x}
        y={point.y}
        onSelect={() => { selectedPoints[i] = true }}
        onDeselect={() => { delete selectionPoints[i] }}
        fill={selectedPoints[i] ? 'red' : 'black' }
      />
    {/each}

  </Section>

  {#if selectRectangle !== undefined}
    <Rectangle {...selectRectangle} fill="green" opacity={0.2} />
  {/if}

</Graphic>

The advantages of this method over the old vue-gg syntax are:

  1. The user has more control over when the selection fires and when it is 'reset'. This makes it easier in the future to implement things like brushing.
  2. In this example I am using a selection-rectangle, but any sort of polygon would work with the same syntax.
  3. Selections can be triggered programmatically- so you can just call section.updateSelection with your own rectangle or polygon. This way you can, for example, create a map with points and polygons and easily light up all points that are in a polygon without using any of the mouse interactions.
  4. The initSelection and updateSelection functions could be abstracted away and made customizable with helpers functions similar to createZoomHandler and createPanHandler.

The downside is that this is maybe a bit more boilerplate-y.

Some more ideas:

  1. In vue-gg, the callbacks given to onSelect and onDeselect were fired when the Mark's centroid would be within the selection rectangle/polygon. However, in some cases it might be preferable to fire when the selection rectangle/polygon intersects the mark instead. I suggest we add a prop on the mark/layer saying selectionFires="intersectsPolygon" or something like that, where the default would be selectionFires="centroidInPolygon".
  2. We have to think about supporting this stuff for polar coordinates too. The implementation described here would be using screen coordinates, which would work in a polar coordinate system, but it would also be cool to be able to drag clockwise or counterclockwise in the polarcoordinate system and have a pie-slice-shaped selection polygon (if that makes any sense). I am not sure yet how we should implement that but I feel like there should be a way.

Thoughts?

atepoorthuis commented 4 years ago

3x yes! This is a quite neat and clean. I agree with pretty much all your points here (and love the extensibility to 'lasso' type selection - and you can easily extend too to enable snapping for example in the handler function).

On when a selection is an actual selection: I agree and this links potentially to other interaction too where we might want to do 'click with buffer' type of things. In other words, it'd be nice if we can make the spatial relation between the exact point of the interaction and the Mark a bit more flexible.

johsi-k commented 4 years ago

As I was adding selection to the area mark it occurred to me that firing on centroid detection might not be the most intuitive approach. It could also be problematic in the case of concave shapes where the centroid might end up outside the shape boundary.

When it comes to how much of the shape should be contained within the selector, most CAD programs use an all-or-nothing approach and fire only if the selector encompasses the entire shape. Firing on intersection is distinguished by direction:

See for example https://youtu.be/OLsstz6AvlI?t=65