Skip to content

Conversation

@degzhaus
Copy link

@degzhaus degzhaus commented Oct 14, 2025

Overview

This PR adds a new quiver trace type to Plotly.js for visualizing 2D vector fields using arrows.

Features

  • Vector positioning: Arrows placed at x, y coordinates with direction/magnitude from u, v components
  • Sizing modes: scaled (normalized), absolute, or raw vector lengths via sizemode/sizeref
  • Arrow anchoring: Position arrows by tail, tip, or center/cm/middle
  • Colorscale support: Color arrows by magnitude or custom scalar field (c array)
  • Styling: Configurable line color, width, dash patterns via line.* attributes
  • Arrowhead sizing: Adjustable arrowhead size via arrowsize
  • Full interactivity: Hover support with customizable hovertemplate, point selection
  • Animation support: Animatable x, y, u, v, and c arrays

API

Plotly.newPlot('div', [{
  type: 'quiver',
  x: [0, 1, 2],
  y: [0, 1, 2],
  u: [1, 0, -1],  // x-component of vectors
  v: [0, 1, 0],   // y-component of vectors
  sizemode: 'scaled',  // 'scaled' | 'absolute' | 'raw'
  sizeref: 0.5,
  anchor: 'tail',  // 'tail' | 'tip' | 'center'
  colorscale: 'Viridis',
  line: { width: 2 }
}]);

Screenshots

Examples taken from plotly.com/python/quiver-plots

Gist with example code

Basic Quiver Plot

Basic quiver plot

With Colorscale

Quiver with Viridis colorscale

Testing

  • ✅ Jasmine unit tests
  • ✅ 9 visual regression test mocks

Files Changed

New files (src/traces/quiver/)

  • index.js - Trace module definition
  • attributes.js - Attribute schema
  • defaults.js - Default value handling
  • calc.js - Data calculation
  • plot.js - SVG rendering
  • style.js - Styling
  • hover.js - Hover behavior
  • select_points.js - Selection support
  • event_data.js - Event data formatting
  • format_labels.js - Label formatting

Modified

  • lib/index.js & lib/index-strict.js - Build integration

Tests

  • test/jasmine/tests/quiver_test.js - Unit tests
  • test/image/mocks/quiver_*.json - 9 mock files
  • test/image/baselines/quiver_*.png - Baseline images

@degzhaus degzhaus force-pushed the degzhaus/add_scatterquiver branch 4 times, most recently from dfd3c81 to 9463b3f Compare October 19, 2025 21:12
@degzhaus
Copy link
Author

degzhaus commented Oct 19, 2025

Hello, I am new to this project and attempting to contribute my first PR.

Unfortunately, I am struggling to get the build to pass. Specifically, the publish-dist-node-v22 step:
https://app.circleci.com/pipelines/github/plotly/plotly.js/12658/workflows/ae5271e7-9971-4db1-a713-b42f0625901f/jobs/282198

I tried running npm run schema and pushing the change here:
9463b3f

But still encountering the issue.

Any insight would be greatly appreciated. Thank you!

Edit: tagging a few recent commiters for visibility, thanks so much! (cc @camdecoster @alexshoe @emilykl)

@emilykl
Copy link
Contributor

emilykl commented Oct 20, 2025

Hi @degzhaus, thank you for working on this contribution!

As it stands currently, the devtools dashboard needs to be running in order for npm run schema to work properly. So you should run npm start and leave it running, and then in another terminal run npm run schema. (@camdecoster is working on a PR to eliminate that step, see #7589)

You can also download plot-schema.json from the "Artifacts" tab in CircleCI and commit it.

@degzhaus degzhaus force-pushed the degzhaus/add_scatterquiver branch 18 times, most recently from b07de7b to a32678d Compare October 22, 2025 03:33
@degzhaus
Copy link
Author

degzhaus commented Oct 22, 2025

Hi @degzhaus, thank you for working on this contribution!

As it stands currently, the devtools dashboard needs to be running in order for npm run schema to work properly. So you should run npm start and leave it running, and then in another terminal run npm run schema. (@camdecoster is working on a PR to eliminate that step, see #7589)

You can also download plot-schema.json from the "Artifacts" tab in CircleCI and commit it.

Thank you so much, @emilykl! That was very helpful and unblocked me.

Unfortunately, I have been struggling with the no-gl-jasmine test in ci:
https://app.circleci.com/pipelines/github/plotly/plotly.js/12700/workflows/8b80de4d-1bad-455d-baef-72bc47c31042/jobs/283463

If you have any insight, I would greatly appreciate it. Thanks again!

Edit: @emilykl i figured it out! Pardon my flailing at last night. So exciting! I have a couple of tests to add and this will be ready for review.

Thanks again for your insights and help!

@degzhaus degzhaus force-pushed the degzhaus/add_scatterquiver branch 2 times, most recently from 64cf31d to 02c37f5 Compare October 22, 2025 15:56
@emilykl
Copy link
Contributor

emilykl commented Oct 22, 2025

@degzhaus Great news, glad you're unblocked! Thank you for your work on this. Quiver is a plot type we've gotten a lot of requests for.

A few high-level comments on the API:

  • This trace type is fairly distinct from existing scatter* trace types; let's go with the name quiver instead.

  • The most similar already-existing trace type is the 3d cone trace, so the attributes of quiver should match the existing API of cone as closely as possible for consistency. For example, rather than the scale property it should use sizemode and sizeref as the cone trace does. (Obviously, some attributes like z and w are not relevant in a 2d context).

  • Any attributes for configuring the size/scale of the arrowhead should match the arrowhead config options for annotations

    • It's also not necessary to include those options in a first version of this trace type, but any attributes that are added should be consistent with what's already in the library
  • There's a few other things I noticed in the attributes as well:

    • The scaleratio attribute doesn't belong; the scale ratio between the axes should be a property of the axes themselves, not the trace. The axis scale ratio can already be configured using the existing axis.scaleratio attribute.
    • It's unclear to me how the angle attribute interacts with u and v, since u and v are already sufficient to fully determine the arrow's angle. The angle attribute should probably be removed.

@degzhaus
Copy link
Author

@degzhaus Great news, glad you're unblocked! Thank you for your work on this. Quiver is a plot type we've gotten a lot of requests for.

A few high-level comments on the API:

  • This trace type is fairly distinct from existing scatter* trace types; let's go with the name quiver instead.

  • The most similar already-existing trace type is the 3d cone trace, so the attributes of quiver should match the existing API of cone as closely as possible for consistency. For example, rather than the scale property it should use sizemode and sizeref as the cone trace does. (Obviously, some attributes like z and w are not relevant in a 2d context).

  • Any attributes for configuring the size/scale of the arrowhead should match the arrowhead config options for annotations

    • It's also not necessary to include those options in a first version of this trace type, but any attributes that are added should be consistent with what's already in the library
  • There's a few other things I noticed in the attributes as well:

    • The scaleratio attribute doesn't belong; the scale ratio between the axes should be a property of the axes themselves, not the trace. The axis scale ratio can already be configured using the existing axis.scaleratio attribute.
    • It's unclear to me how the angle attribute interacts with u and v, since u and v are already sufficient to fully determine the arrow's angle. The angle attribute should probably be removed.

Amazing, thank you so much for taking a look and providing great guidance, Emily! Looking forward to spinning a cycle on this feedback.

@emilykl
Copy link
Contributor

emilykl commented Oct 22, 2025

@degzhaus One more comment — it would be great to include colorscale attributes in quiver so that arrows can be colored according to the magnitude of the vector. These attributes are added to the cone trace here; something similar can be done for the quiver trace. And then obviously those attributes need to be referenced in the plotting code to assign the appropriate color.

@gpdf
Copy link

gpdf commented Dec 18, 2025

Perhaps for future readers' clarity of understanding, @degzhaus , you could edit the description at the top of the PR to say:

scatterquiver quiver

in the two places it appears, so that the as-merged plot type appears there.

@degzhaus degzhaus force-pushed the degzhaus/add_scatterquiver branch from b93b445 to 0252e03 Compare December 19, 2025 01:07
@degzhaus
Copy link
Author

Perhaps for future readers' clarity of understanding, @degzhaus , you could edit the description at the top of the PR to say:

scatterquiver quiver

in the two places it appears, so that the as-merged plot type appears there.

Great catch, thank you so much, @gpdf !

Updated per your recommendation, and did a little review/revision based on changes made during the review process as well.

@degzhaus
Copy link
Author

Hi @degzhaus ! I'm aiming to give this another review by the end of the week. Sorry for the delay in getting back to you — looking forward to it! Thanks for your work so far.

Hey there, @emilykl, hope you're having a wonderful week!

I rebased, cleaned up some code, and added some tests here:
0252e03

Thanks again for everything!

@gpdf
Copy link

gpdf commented Dec 19, 2025

I really appreciate all the effort in polishing, and very much look forward to using this.

@gpdf
Copy link

gpdf commented Jan 16, 2026

@emilykl Where are we on the trajectory toward getting this merged? I'm excited about being able to start using it.

@gpdf
Copy link

gpdf commented Jan 27, 2026

@emilykl Is there anything we can do on our end to support this?

@degzhaus degzhaus force-pushed the degzhaus/add_scatterquiver branch from 0252e03 to 51a2afc Compare January 30, 2026 18:49
@emilykl
Copy link
Contributor

emilykl commented Feb 2, 2026

Hi @degzhaus ! I sincerely apologize for the delay in the next round of review, and a big thank you for your patience. I'll be going through this PR again this afternoon. Thanks again for your work so far and for persevering with this one!

@degzhaus degzhaus force-pushed the degzhaus/add_scatterquiver branch from 51a2afc to 2b2d1c9 Compare February 2, 2026 17:47
@degzhaus
Copy link
Author

degzhaus commented Feb 2, 2026

Hi @degzhaus ! I sincerely apologize for the delay in the next round of review, and a big thank you for your patience. I'll be going through this PR again this afternoon. Thanks again for your work so far and for persevering with this one!

All good, @emilykl ! Thank you so much for the message.

I just rebased and ensured tests are passing, so looking forward to the next steps of the review.

Thanks again to you both, @emilykl @gpdf !

John

@emilykl
Copy link
Contributor

emilykl commented Feb 10, 2026

Hi @degzhaus ! Sorry for the delay. Thanks for your continued work. I remember noticing a bug on a previous iteration where the arrow styling was lost when the graph was panned; that seems to be fixed now. 🎉

However I do see some remaining items that should be addressed:

Bugs

  • The axis ranges don't automatically adjust to include the full length of the arrows. See example here. By default if no axis range is specified in the layout, the entirety of all arrows should be visible. I'd recommend removing the hard-coded xaxis and yaxis range from all the image tests in the PR. Test with multiple traces as well.

  • Colorscale seems broken in the latest version, unless I'm missing something. The arrows in quiver_colorscale.png and quiver_custom-colorscale.png seem to be all the same color.

  • Legends are missing icons. A horizontal line icon would be fine. (A little arrow icon would be kind of cute but not required for a first iteration!)

  • I can't figure out how to test the selected and unselected styling options; selecting arrows with the box styling doesn't seem to change anything. Let me know if I'm missing something. In any case these aren't essential for a first iteration, and no other trace types have them AFAIK, so it's fine to simply remove them. (Wouldn't unselected be the same as the styling set elsewhere, anyway?) Correction: I'm wrong, these do in fact exist for scatter traces! I just took a look and I understand now: unselected styling is applied to unselected points whenever any point is selected. However, I still think it's fine to skip this feature for an initial implementation of the quiver trace.

  • The text property also seems broken in the latest iteration, unless I'm using it incorrectly. Could you add text to one of the image tests?

  • Display of colorbars does not seem to work

API refinements (for consistency with other traces)

Open to discussion on any of these if you have an argument for keeping as-is, or another suggestion.

  • c should become marker.color (similar to scatter traces and many other trace types: color can be either an array of numeric values, in which case colors are assigned according to the colorscale, or a single hex color)
    • Correspondingly, colorScaleAttrs() should be nested under marker as well
  • The line options should be nested under marker.line
    • ...except for line.color, which should become simply marker.color since the markers have only a single color
    • line.shape, line.smoothing, and line.simplify should also be removed as I don't think they are relevant to quiver traces
  • Move arrowheadsize under marker so that it becomes marker.arrowheadsize
    • It would also be nice if arrowheadsize imported and reused the exact attribute definition from annotations if possible
  • No need for duplicate arrowwidth property since we already have line.width; arrowwidth can be removed
  • arrow_scale should be removed as well

I'll leave some comments on the code as well if anything jumps out at me, but those are the big picture items! Feel free to reach out with questions on any of these, and I'll get back to you more quickly next time around. 😉 Thanks again.

Comment on lines +12 to +16
// Coerce x and y data arrays (this ensures proper data structure for category ordering)
var x = coerce('x');
var y = coerce('y');
var u = coerce('u');
var v = coerce('v');
Copy link
Contributor

Choose a reason for hiding this comment

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

@degzhaus Can you look into whether we can/should use the handleXYDefaults() function here, as done for the scatter trace?

Reusing the same helper function would be ideal if it works without too much massaging.

coerce('vhoverformat');

// Colorscale defaults (adds colorscale, showscale, colorbar, etc.)
traceOut._hasColorscale = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should probably call the hasColorscale() function here (although I'm curious how the scatter trace manages without the _hasColorscale field, worth looking into)

@@ -0,0 +1,37 @@
'use strict';

module.exports = function selectPoints(searchInfo, selectionTester) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we plausibly reuse the src/traces/scatter/select.js selectPoints() function instead of duplicating a lot of that code here? The implementations look almost identical, although not quite.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community community contribution feature something new P2 considered for next cycle

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants