phetsims/vector-addition

make vector heads larger

Closed this issue · 32 comments

@arouinfar and @kathy-phet asked if we could make the vector heads larger when vector magnitude is > 1. This would make them easier to interact with.

A few problems that would need to be addressed:

  • Common code ArrowNode (used for solid vectors) has support for scaling the height of the vector head, but not the width. This looks a bit odd to me (see below) but might be a worthwhile tradeoff.

screenshot_1600

  • Custom node DashedVectorNode is used for (dashed) component vectors, and has no support for modifying the vector head.

  • This will involve revisiting the spacing of component vectors in "projection" mode, which we recently polished in #225.

  • This will involve revisiting all vector icons (checkboxes, radio buttons, toolboxes,...)

@arouinfar please advise on how to proceed.

Note to self... The larger arrows in the screenshot above are with these options, as defined in VectorAdditionConstants:

  const VECTOR_ARROW_OPTIONS = {
    headWidth: 13,
    headHeight: 16,
    tailWidth: 3.5,
    stroke: null,
    isHeadDynamic: true,
    fractionalHeadHeight: 0.5
  };

If you want to proceed with this, here are time estimates for the remaining tasks. This assumes that the current behavior of ArrowNode is acceptable with respect to how it scales the vector head (see screenshot in #240 (comment)).

  • 1-2 hours to make DashedVectorNode behave like ArrowNode. That is, give it isHeadDynamic and fractionalHeadHeight options.
    (See ArrowNode, ArrowShape, and DashedVectorNode.)

  • ½ hour to decide exactly what dimensions to use for the vector heads. This will involve a couple of back-and-forth iterations, or (preferably) realtime iteration on Zoom.
    (See VECTOR_ARROW_OPTIONS.VECTOR_ARROW_OPTIONS.)

  • ½ hour to re-tweak the offsets and spacings for "projection" component vectors.
    (See VectorSet and LabGraph)

  • ½ hour to review and tweak all vector icons. If we make the head bigger, I would make some of them longer -- e.g. the icons in the toolboxes, and the Sum checkbox icons.
    (See VectorAdditionIconFactory.)

  • ¼ hours to verify and possibly tweak vector pointer areas.
    (See VectorAdditionConstants.VECTOR_TOUCH_AREA_DILATION and VECTOR_MOUSE_AREA_DILATION.)

@pixelzoom thanks for the investigation and time estimates. This is a rather involved changed, and bit complex for an 11th hour polish. While it would be nice, I don't see it as critical.

@kathy-phet what would you recommend?

I think larger vector heads would have a slightly better visual aesthetic, and perhaps make them look a little more "inviting".

But if everyone is generally OK with how the vectors look, and this is being considered solely to make the head easier to interact with, then we could also consider adjusting pointer areas. Screenshot below shows the current pointer areas (red=touchArea, blue=mouseArea), and the head pointer areas are in front of the tail. Note that if we dilate the pointer areas much further, you won't be able to translate a vector with small magnitude, because you won't be able to grab its tail (already an issue with unit vectors).

screenshot_1609

My understanding of the suggestion was that it wasn't just to make them easier to interact with, but to make sure it was easy to see the head from the back of the room by making it bigger so the head of the vector is bigger (has a wider base) and more contrast with the tail of the vector. @arouinfar - am I interpreting what you mentioned as Mike's suggestion correctly?

That said, I am OK with marking this as deferred (keeping it as a possible future polish) but moving forward with publication.

... to make sure it was easy to see the head from the back of the room ...

Perhaps I misunderstood the purpose.

... by making it bigger so the base of the vector is wider and more contrast with the line of the vector.

@kathy-phet please clarify - what do you mean by "base" and "line" of a vector. We have been speaking here in terms of head and tail.

... marking this as deferred (keeping it as a possible future polish) but moving forward with publication.

If projection visuals has been verified to be a problem... Since this sim has no hard deadline, I'd be in favor of addressing this now.

I edited my entry. @arouinfar spoke directly with Mike, so she will have the first-hand feedback. From the projector testing, a larger head would help when you are in the back of the room. But it does have all of the consequences you mention above.

The motivation for the larger vector heads is to improve the back-of-the-lecture-hall experience. At that distance, the heads become somewhat harder to distinguish from the body of the vector. Increasing the vector head by 15% improves things quite a bit. I do not see this as a general usability issue, though a larger vector head may indeed look more grabbable.

The * indicates vectors with a 15% larger head.

Here's what it looks like in context. The blue vectors have the larger head, and the orange ones do not. @kathy-phet and I tested this image on the projector in the lecture hall, and it was a nice improvement.
image

If projection visuals has been verified to be a problem... Since this sim has no hard deadline, I'd be in favor of addressing this now.

@pixelzoom if you're up for it, then we can proceed.

I'm up for it, will proceed. Better to do it now, since I know exactly what and where needs to be changed. If we wait until later, it will take some future developer (myself included) at least 50% longer than I estimated.

1.0.0-dev.48 was published immediately before proceeding with this work.

Summary of above commits:

  • added headWidth,headHeight, and tailWidth query parameters, which affect all vectors (including icons and screenshots)
  • increased head size by ~15%. headWidth increased from 10 to 12. headHeight increased from 12 to 14. tailWidth is unchanged at 3.5.
  • increased the length of vectors in the toolboxes by 15%
  • increased the length of Sum, 'c', and 'f' checkbox vector icons to match the base vectors checkbox
  • offsets for projection component vectors are now computed based on headWidth, instead of set empirically, so that behavior stays the same regardless of headWidth
  • added support for dynamic head height to DashedArrowNode
  • enabled dynamic head height for ArrowNode and DashedArrowNode. When the head height is >= ½ the vector magnitude, the head height will scale. (The ½ value is adjustable.)

All of the above went smoothly. And then I hit a major problem. The pointer area for the head is determined by an invisible triangle that is placed over the head. (See const headNode in VectorNode.js.) That triangle will somehow need to scale as the ArrowNode head scales. I'm not sure how I'm going to address this. What a headache (pun intended).

As I mentioned above... There's an invisible triangle that's place over the head, and it's what you drag to rotate/scale a vector -- let's call it "the invisible head". It's in front of the visible vector, which is what you drag to translate.

I figured out how to scale the invisible head to match the visible head. But now I'm not sure that we want to do this. It also scales the pointer areas, and makes it more difficult to grab small vectors. For example, here's a vector where the head is not scaled (I've turned off the pointer areas for the tail in these screenshots):

screenshot_1615

If I do nothing, the visible head scales, but the invisible head does not scale, so the pointer areas remain the same. The downside here is that you can't possibly grab the tail to to translate a vector with a small tail. (This has always been an issue, btw.)

screenshot_1616

If I scale the invisible head to match the visible head, then its pointer areas also scale. It's possible, but not practical, to drag the tail (there's a tiny bit of the tail extending past the left).

screenshot_1614

So 2 questions for @arouinfar:

(1) Do we want to scale the invisible head, thus scaling the pointer areas?

(2) Do we need to address the fact that a small vector can't be translated? I'm not sure how we could address this.

Note to self, here's the patch for VectorNode to implement scaling of the invisible head:

// When the vector changes, transform the head.
const vectorComponentsListener = vectorComponents => {

  const viewComponents = this.modelViewTransformProperty.value.modelToViewDelta( vector.vectorComponents );

  // Scale the head the same way that ArrowNode's head scales.
  // See https://github.com/phetsims/vector-addition/issues/240
  if ( options.arrowOptions.isHeadDynamic ) {
    assert && assert( options.arrowOptions.headHeight, `arrowOptions.headHeight is required` );
    assert && assert( options.arrowOptions.fractionalHeadHeight, `arrowOptions.fractionalHeadHeight is required` );

    const viewMagnitude = viewComponents.magnitude;
    const headHeight = options.arrowOptions.headHeight;
    const maxHeadHeight = options.arrowOptions.fractionalHeadHeight * viewMagnitude;
    if ( headHeight > maxHeadHeight ) {
      headNode.setScaleMagnitude( maxHeadHeight / headHeight );
    }
    else {
      headNode.setScaleMagnitude( 1 );
    }
  }

  headNode.translation = viewComponents;
  headNode.rotation = -vectorComponents.angle;
};

I would be nervous about scaling the head because then it will become hard to pick up.

Do you mean "nervous about scaling the head's pointer areas"? Because the head is definitely going to scale, that's what we decided.

Can you put the translation touch area in front of the head touch area where they overlap?

No. The translation drag area is the complete vector arrow, including the head. This is how you're able to translate a sum vector (which can't be scaled or rotated via direct manipulation) by dragging anywhere on the sum vector (including its head). So if we move the translation drag area to the front for short vectors, it will cover the rotate/scale drag area, and then you'd never be able to scale or rotate a short vector -- it would be stuck being short forever.

Here's another idea...

Here's what the head's pointer areas currently look like for a small vector, which makes it impossible to grab the tail to translate:

screenshot_1616

We could shift the pointer areas, like this:

screenshot_1617

Or we could shift and scale the pointer areas, like this:

screenshot_1618

I prefer the latter (shift and scale). In the former (shift only), I suspect that you might accidentally grab the vector head when vectors/labels overlap.

In both cases, the complete tail is outside of the head's pointer areas, making it possible to grab the tail and thus translate the vector.

Note to self... Hardcoded pointer areas for the "shift and scale" screenshot immediately above. This would need to be generalized, with computed translation and scale.

const headHeight = VectorAdditionConstants.VECTOR_ARROW_OPTIONS.headHeight;
headNode.touchArea = headShape.getOffsetShape( VectorAdditionConstants.VECTOR_HEAD_TOUCH_AREA_DILATION )
  .transformed( Matrix3.scale( 0.5, 1 ) )
  .transformed( Matrix3.translation( 0.25 * headHeight, 0 ) );
headNode.mouseArea = headShape.getOffsetShape( VectorAdditionConstants.VECTOR_HEAD_MOUSE_AREA_DILATION )
  .transformed( Matrix3.scale( 0.5, 1 ) )
  .transformed( Matrix3.translation( 0.25 * headHeight, 0 ) );

I like the shift and scale, too. Maybe a little bigger? I would want to make sure it works with the swipe to pick up mechanism, since it will have a smaller area. You might want to use that "shift" a bit on all the regular vector heads too, so we don't hit an issue when the vector is 2 units long?

@pixelzoom I also like the shift-and-scale approach. I'm not sure there's room to dilate the head much more than shown in #240 (comment), otherwise we may be likely to grab neighboring vectors. This will likely be a tradeoff, and things will likely be fine on iPad, but tricky to grab on a phone -- I'm okay with that.

@kathy-phet said:

You might want to use that "shift" a bit on all the regular vector heads too, so we don't hit an issue when the vector is 2 units long?

This would raise the degree of difficulty and result in even more changes.

@arouinfar @kathy-phet Could you please try a vector that is 2 units long, and verify whether there is a problem? Here's what it currently looks like, and I have no problem (with mouse or touch) with grabbing the head vs tail. The latest dev version is 1.0.0-dev.49.

screenshot_1619

Before I do anything else on modifying the pointer areas, we should finalize the vector head size. @arouinfar and @kathy-phet please review the current values for headWidth and headHeight and let me know if they need to be adjusted.

From #240 (comment):

  • added headWidth,headHeight, and tailWidth query parameters, which affect all vectors (including icons and screenshots)
  • increased head size by ~15%. headWidth increased from 10 to 12. headHeight increased from 12 to 14. tailWidth is unchanged at 3.5.

Discussed with @arouinfar on Zoom, summary:

  • head size is solidified (headWidth:12, headHeight:14, as in 1.0.0-dev.49)
  • reduced the length of the Sum, 'c', and 'f' vector icons to 35
  • new strategy for pointer areas, described below

New strategy for pointer areas, see diagram below, dashed line is touch area.

  • for vectors with magnitude > 3, no change
  • for vectors with magnitude <= 3, head pointer area will not overlap tail
  • for vectors whose headHeight is scaled down, pointer area height will also scale down
  • the value '3' is easily changed

screenshot_1620

This seems good to me. Scaled headHeight is only for vectors that are 1 in length, correct?
I don't really understand this comment, but maybe its just a side discussion:
reduced the length of the Sum, 'c', and 'f' vector icons to 35

Scaled headHeight is only for vectors that are 1 in length, correct?

No. Head height scaling occurs for any vector with headHeight > N * magnitude. headHeight is currently 14 (view units), which works out to ~0.97 model units. N is currently 0.5. So a vector whose components are <1,1> has a magnitude of 1.4, and it's head height will be scaled to 0.5 * 1.4 = 0.7.

I don't really understand this comment, but maybe its just a side discussion:
reduced the length of the Sum, 'c', and 'f' vector icons to 35

The sizes of the checkbox icons were increased while doing this work, as noted in #240 (comment). @arouinfar wanted them shorter, and I made it so.

First attempt at dynamic pointer areas in the commit above. I'm not too happy with this. It creates a ton of Shapes while you're interacting with a vector. And the pointer area for the "head scaled" case is too large.

magnitude > 3
screenshot_1621

magnitude <= 3, head unscaled
screenshot_1622

head scaled
screenshot_1623

Second attempt pushed in above commit. I like this approach better. There are 3 sets of pointer area shapes, corresponding to the 3 cases -- call them "large", "medium", and "small". The shapes are allocated once, so it's efficient. And the pointer areas sizes seem to be reasonable, not too large so that we don't accidentally grab them. I can translate, scale, and rotate (with or without touch snag) a unit vector on my iPad6.

magnitude > 3
screenshot_1624

magnitude <= 3, head unscaled
screenshot_1625

head scaled
screenshot_1626

I'm now at 10+ hours for this issue, way beyond my estimate of 3.75. And I feel like I'm chasing something that is not possible. It is now possible to translate, scale, and rotate a unit vector on a phone. But no matter how much we continue to tweak this, it will never be easy, and it will never be a good UX. On my iPhone 4s, it's a horrible UX, even with long vectors. My index finger covers half the graph, so to expect to be able to grab a unit vector's tail with any kind of precision is an unreasonable expectation. That said...

@arouinfar please review master or 1.0.0-dev.52. (EDIT: Changed to 1.0.0-dev.52)

This is all working beautifully on my phone and I have very few misfires. I'm remote today, so I don't have access to the iPad, but I can only imagine how nice the UX is now.

Very nicely done @pixelzoom.

Glad you like it, definitely worth the effort. Especially satisfying that we addressed the inability to translate unit vectors, which was a problem on all platforms.