-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
add 'width' to box and violin trace #3109
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
Conversation
|
Welcome to plotly.js @Kully 🎉 Thanks for the PR! Lets call this parameter just
I suspect in fact all that matters as you have it now is the last trace (of the right type on the same subplot). That's because you're taking |
src/traces/box/attributes.js
Outdated
| valType: 'number', | ||
| min: 0, | ||
| role: 'info', | ||
| dflt: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might just set the dflt to 0 and have that mean "auto". That's a convention we use various other places. Then you can (still) use truthiness test if(trace.width) to tell if the user has specified an explicit width.
BTW this is in box/attributes and you have explicitly disabled this for boxes below but I'm sure you'll enable it in a revision 😁
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(also this is in box/attributes but the description below says violins...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a convention we use various other places.
Hmm. Bar width has dflt: null which I think is more appropriate
plotly.js/src/traces/bar/attributes.js
Lines 155 to 165 in 7ae6fd6
| width: { | |
| valType: 'number', | |
| dflt: null, | |
| min: 0, | |
| arrayOk: true, | |
| role: 'info', | |
| editType: 'calc', | |
| description: [ | |
| 'Sets the bar width (in position axis units).' | |
| ].join(' ') | |
| }, |
Which "other places" are you referring to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of nticks, nbins(x|y), also looks like we do that for maxdisplayed.
null is certainly more meaningful on its own than 0, and it's necessary if 0 is a valid value distinct from "auto" - like bar.offset where 0 means "center the bar at the data value" and "auto" means "if the bars are grouped, offset them all so they end up side-by-side".
It's just a little funny though with valType: 'number' - it means null looks invalid, so if you try and set it we'll determine it to be invalid and fall back on the default, which I guess is fine as long as null is the default, but it would still fail Plotly.validate. Also you can't set it at all via restyle/relayout, because null there means "unset" - again, not immediately a problem as long as this is the default. BUT... what if you have a template that overrides the default? Seems to me then you would be unable to revert to auto with any setting at all of the user attribute, right?
valType: 'angle' adds a special value 'auto', which might be the best of both worlds? Its meaning is clear (and distinct from 0), and it's not intercepted by plotlyjs like null is. Perhaps we should allow valType: 'number' to specify allowAuto: 'true', or extras: ['auto']?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BUT... what if you have a template that overrides the default? Seems to me then you would be unable to revert to auto with any setting at all of the user attribute, right?
Interesting point. This is somewhat related to #2834, I'll comment over there.
src/traces/violin/defaults.js
Outdated
| coerce('scalegroup', traceOut.name); | ||
| coerce('scalemode'); | ||
| coerce('side'); | ||
| coerce('vwidth'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect we should disable scalegroup and scalemode if there's a width... otherwise it would be very confusing what should happen. That would look like:
var width = coerce('width');
if(!width) {
coerce('scalegroup', traceOut.name);
coerce('scalemode');
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scalegroup and scalecount seem to not do anything when there is width
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly, that's why we don't want to coerce them - every attribute that makes it to traceOut should do something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh sorry, I marked this resolved when I saw that you added the conditional coerce block... but I guess you removed it again in a later commit. We do want the conditional, per my previous comment.
|
@Kully re: tests - I think all we need for this feature is one or two image tests - covering both box and violin, with at least two different explicit widths and one automatic width on the same subplot. |
src/traces/box/cross_trace_calc.js
Outdated
| vpadminus: dPos + pad[0] * padfactor, | ||
| vpadplus: dPos + pad[1] * padfactor | ||
| vpadminus: max_half_width + pad[0] * padfactor, | ||
| vpadplus: max_half_width + pad[1] * padfactor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah good catch - unfortunately the TODO concern above this gets more acute with custom widths. If you show two traces, one off to the left with a very narrow width and a wide one to the right, the one on the left will be padded as much as the one on the right, giving an inappropriately large autorange.
It looks to me as though we could pretty easily fix this by calculating extremes per trace. That would go below in the trace loop and might be as simple as:
var thisDPos = calcTrace[0].t.dPos = (calcTrace[0].trace.width / 2) || dPos;
var positions = calcTrace.map(function(di) { return di.pos });
var extremes = Axes.findExtremes(posAxis, positions, {
vpadminus: thisDPos + pad[0] * padfactor,
vpadminus: thisDPos + pad[1] * padfactor
});
calcTrace[0].trace._extremes[posAxis._id] = extremes;That wouldn't address the original TODO - which I guess is about a per-trace pad / padfactor depending on trace.pointpos, I'd have to look to see if there's an easy solution to that, there may be... but if you don't feel like digging into that feel free to just move the TODO along with this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not that much improvement, but there is some. Notice the vertical distance between the -0.4 and the -5 label. We need a better algo for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right - because we don't take trace.side into account - if it's 'positive' we should set vpadminus: 0, if 'negative' we should set vpadplus: 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
@alexcjohnson image test is done. anything else that needs updating/adding before merging this guy? |
src/traces/box/cross_trace_calc.js
Outdated
|
|
||
| // Find maximum trace width | ||
| // we baseline this at dPos | ||
| var max_half_width = dPos; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Camel case please 🐫
src/traces/box/attributes.js
Outdated
| 'or sample points or both?' | ||
| ].join(' ') | ||
| } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚡ that ,
Not sure why our linting didn't pick this up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this as I placed the width property at the end of this list the first time around. I then moved to the middle to a more appropriate spot but forgot to ⚡️ the ,
src/traces/box/cross_trace_calc.js
Outdated
| calcTrace = calcdata[boxList[i]]; | ||
|
|
||
| if(calcTrace[0].trace.width) { | ||
| if(calcTrace[0].trace.width / 2 > max_half_width) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. What if trace.width is smaller than the minimum-diff-between-distinct-values? Does this still work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It actually does work. See this line where the width is set.
These lines are affecting the axis autoscale of the plotted points. But if a violin has trace.width = 0.00001 (much less than the min-diff as you mention) then that trace will shrink to the whatever the width is set to but the "camera" will not zoom in anymore. The view of all the traces is always calibrated to the violin/box with the maximum width set, which doesn't loose any desired layout that you could want with violins or boxes, if you decide to use width at all.
| } else { | ||
| traceOut.scalegroup = ''; | ||
| traceOut.scalemode = 'width'; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does anything break if we 🔪 the else entirely? Unnecessary attributes should simply not be included in traceOut at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, the violins are not drawn properly: the outlines are filled with black ink
|
Hmm. Looks like Is this desired behavior? Or should we do like |
- do not fill in fullLayout._violinScaleGroupStats for traces with set 'width' - use maxKDE key instead of maxWidth to avoid confusing
I implemented this along with fixing #3109 (comment) and improving the attribute descriptions in -> |
💯 good 👀 you're absolutely correct. |
Nice stuff! |
I gave this a quick scan - if you are fixing the coercing issue with these two variables, I'm good with that. Looks good to merge in. |
Joyplots etpinard
| Plotly.plot(gd, fig).then(function() { | ||
| mouseEvent('mousemove', 300, 250); | ||
| assertViolinHoverLine([299.35, 250, 250, 250]); | ||
| assertViolinHoverLine([178.67823028564453, 250, 80, 250]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Kully do you know why this test had to be modified?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't too sure to be honest. I ran the test a few times but there were only two values the assert was saying was valid, iirc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably it changed because autorange can shrink depending on side now, so the violin moved.
|
I think I have solved the points getting cut off issue except for when What I see left is to:
Does this seem like the right direction to you guys? |
|
Now in #3234 |





re: plotly/documentation#1090
inspiration for joyplots found in community discussion here: https://community.plot.ly/t/ridgeline-joy-plots-in-dash-automatic/12602
First PR for js, whooho 🎉
This is my first shot at adding a
widthparameter for violin and box (I am calling the paramvwidthfor now). So far it works okay. The only thing I am noticing is thatdataobject have to be set to the same value in order for there to be any update to the plotRunning this script
I get:
and tweaking
to
I get the following:
And changing the snippet to
where all the
vwidthsare 0.4, we get what we should be getting.I am contemplating switching
vwidthto a layout attribute in the same category asviolingap. I would appreciate some input before continuing.cc @etpinard