-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
add 'width' to box and violin trace #3109
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
@@ -29,6 +29,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout | |||
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
@@ -90,18 +90,33 @@ function setPositionOffset(traceType, gd, boxList, posAxis, pad) { | |||
var groupgap = fullLayout[traceType + 'groupgap']; | |||
var padfactor = (1 - gap) * (1 - groupgap) * dPos / fullLayout[numKey]; | |||
|
|||
// 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
@@ -255,5 +268,6 @@ module.exports = { | |||
'Do the hover effects highlight individual boxes ', | |||
'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
@@ -533,7 +550,7 @@ describe('Test violin hover:', function() { | |||
|
|||
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
width
parameter for violin and box (I am calling the paramvwidth
for now). So far it works okay. The only thing I am noticing is thatdata
object 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
vwidths
are 0.4, we get what we should be getting.I am contemplating switching
vwidth
to a layout attribute in the same category asviolingap
. I would appreciate some input before continuing.cc @etpinard