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

vector + symbol #649

Merged
merged 15 commits into from
Jan 7, 2022
Merged

vector + symbol #649

merged 15 commits into from
Jan 7, 2022

Conversation

mbostock
Copy link
Member

@mbostock mbostock commented Jan 4, 2022

Adds a new Plot.vector mark, similar to Plot.dot, for drawing little arrows. In addition to x and y, it exposes length (magnitude) and rotate (direction, in degrees) channels. The anchor option (start, middle, or end) controls where the arrow is anchored to x and y; it defaults to start.

Screen Shot 2022-01-04 at 10 09 59 AM

Plot.plot({
  inset: 12,
  height: 600,
  marks: [
    Plot.vector(
      (T => d3.cross(T, T))(d3.ticks(0, 2 * Math.PI, 20)),
      {
        anchor: "middle",
        length: ([x, y]) => (x + y) * 2 + 2,
        rotate: ([x, y]) => (Math.sin(x) - Math.sin(y)) * 60
      }
    )
  ]
})

TODO

  • Add documentation for Plot.vector.
  • Add documentation for Plot.dot’s new symbol option.
  • Bind Plot.vector’s length channel to a length scale.

Fixes #407.

@mbostock mbostock requested a review from Fil January 4, 2022 18:12
@mbostock
Copy link
Member Author

mbostock commented Jan 4, 2022

(I really wanted to use radians here, but I wanted to be consistent with Plot.text’s rotate option.)

@mbostock
Copy link
Member Author

mbostock commented Jan 4, 2022

Maybe anchor should default to middle instead?

@mbostock
Copy link
Member Author

mbostock commented Jan 4, 2022

I tried implementing vx and vy channels as alternatives to length and rotate, but it’s awkward because vx and vy would likely need to be in pixel space, rather than data space as x and y are. So I’m going to punt on that idea.

@Fil
Copy link
Contributor

Fil commented Jan 5, 2022

Yes I imagine vx, vy to be working in data space; maybe it needs a "time resolution" factor t, so if x and y are in kilometers, and vx, vy in km/h, the actual length of a vector represents t hours of driving?

@mbostock
Copy link
Member Author

mbostock commented Jan 5, 2022

I’m not planning on implementing vx and vy channels for now.

@Fil
Copy link
Contributor

Fil commented Jan 5, 2022

A few thoughts as I'm playing with this branch:

Should we filter on positive lengths? Or do we keep it generalized to negative lengths being "reversed"?

Do we want a way to personalize the arrows' shapes? (In #39 there is some demand for arrowheads symbols, and in https://observablehq.com/@fil/plot-arrow I've experimented with a variable arrowhead angle and length.)

If we wanted to generalize, Plot.vector could be an instance of Plot.symbol (#41), with an arrow symbol and a linear size. In that case, we wouldn't compute the rotated path's d, but only a scaled path, and we would leave the rotation to a svg transform.

(Also, it needs a bit of documentation.)

@mbostock
Copy link
Member Author

mbostock commented Jan 5, 2022

Should we filter on positive lengths? Or do we keep it generalized to negative lengths being "reversed"?

I originally filtered on positive lengths, but I think it’s preferable to support inverted vectors for negative lengths. You can always filter if you don’t want this behavior.

Do we want a way to personalize the arrows' shapes? … If we wanted to generalize, Plot.vector could be an instance of Plot.symbol

I think we want this eventually, but I don’t think it’s a blocker for now. I also think there is a difference between Plot.symbol having an r channel with default sqrt scale (or area channel with linear scale) versus Plot.vector’s length channel. Having magnitude and direction feels most natural for a vector field, but for a symbol I would expect an areal encoding (and rarely need rotate).

In that case, we wouldn't compute the rotated path's d, but only a scaled path, and we would leave the rotation to a svg transform.

I originally implemented Plot.vector with a transform attribute such that the d attribute always pointed up with the given length. But I suspected that the performance would be better to bake everything into the path string, or at least, it felt more concise to do it in one place. But, I’m happy to change it. I don’t see it as being necessary, though, since it’s just an implementation detail of the mark.

I could certainly take a crack at Plot.symbol as a PR to this PR so that we can feel confident that we’re designing holistically.

(Also, it needs a bit of documentation.)

Yep!

* symbol

* optimize rendering

* maybeSymbolChannel

* move default symbol to Plot.Dot

* symbol legend

* symbol legend paint options

* color+symbol legend

* automatic symbol hints

* fix for orthogonal color and symbol

* preattentive symbols
@mbostock
Copy link
Member Author

mbostock commented Jan 7, 2022

This is ready for review, @Fil. 🙏

Copy link
Contributor

@Fil Fil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

two small remarks in the README


If the **x** channel is not specified, vectors will be horizontally centered in the plot (or facet). Likewise if the **y** channel is not specified, vectors will vertically centered in the plot (or facet). Typically either *x*, *y*, or both are specified.

The **rotate** and **length** options can be specified as either channels or constants. When specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. The length defaults to 12 pixels, and the rotate defaults to 0 degrees (pointing up↑). Vectors with a negative length will be drawn inverted. Positive angles proceed clockwise from noon.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The **rotate** and **length** options can be specified as either channels or constants. When specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. The length defaults to 12 pixels, and the rotate defaults to 0 degrees (pointing up↑). Vectors with a negative length will be drawn inverted. Positive angles proceed clockwise from noon.
The **rotate** and **length** options can be specified as either channels or constants. When specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. The length defaults to 12 pixels, and the rotate defaults to 0 degrees (pointing up↑). Vectors with a negative length will be drawn inverted. Vectors of length 0 will show as a single pixel; use NaN to remove. Positive angles proceed clockwise from noon.

(not sure about this wording)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I don’t think saying a single pixel is correct… since it depends on the strokeWidth? Or possibly you see nothing? In any case, probably better to elaborate in the Marks section above, near here:

Plot.dot will not generate circles with null, undefined or negative radius, or null or undefined coordinates.

mbostock and others added 3 commits January 7, 2022 14:22
Co-authored-by: Philippe Rivière <fil@rezo.net>
@mbostock mbostock changed the title vector vector + symbol Jan 7, 2022
@mbostock mbostock merged commit 7f39a77 into main Jan 7, 2022
@mbostock mbostock deleted the mbostock/vector branch January 7, 2022 23:03
@mbostock mbostock mentioned this pull request Jan 8, 2022
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.

Vector field plots : x, y, size, color and orientation channels
2 participants