-
Notifications
You must be signed in to change notification settings - Fork 0
Implement attributes base, offset and width in bar traces (issue 80) #3
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
* Simplified algorithm to identify overlapping bars. * Removed the closure setBarCenter. * Checked that jasmine tests still pass.
* Converted the closure that groups vertical and horizontal bars into a function. * This change will help split setPositions into functions for each barmode.
* Moved code to group bars from setGroupPositions to setPositions.
* Converted closure `barpositions` into function `setOffsetAndWidth`. * This change will help split setPositions into functions for each barmode.
* Don't assume the position axis is `xa` in the calculation of `barDelta` in `bar/hover.js`. * Updated `tests/bar_test.js` to account for the replacement of `t.bar` with `t.bargroupwidth`. * Updated the group case in `tests/bar_test.js` to test the use of `layout.bargroupgap`.
* Refactored the code to set bar positions in overlay mode into function `setGroupPositionsInOverlayMode`.
* Refactored the code for setting bar positions into setGroupPositionsInOverlayMode, setGroupPositionsInGroupMode and setGroupPositionsInStackOrRelativeMode. * Refactored code for stacking bars and computing minDiff into helper class Sieve.
* Renamed setBaseAndSize to stackBars. * Refactor code to normalize bars into function normalizeBars.
* Allow minDtick update if barmode is group and bars overlap.
* Function sieveBars sieves all the bars without updating the bar bases. This step is required before calling normalizeBars. * Function stackBars sieves all the bars and updates the bar bases and tops accordingly.
Making sure that the bar calc / set-position steps have 100% test coverage would be a great start. Unfortunately, we haven't hooked in a test coverage tool in our jasmine runner yet, so this process will be somewhat manual. My apologies. Thanks again for making a PR onto your fork first. 🎉 This should make the commit history cleaner in the main plotly.js repo. I'll give your PR a first pass now. |
// set size | ||
for(i = 0; i < cd.length; i++) { | ||
if(isNumeric(size[i])) { | ||
cd[i].s = size[i] - cd[i].b; |
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.
⛔ We'll have to guard against non-numeric base
items when base
is set to an array.
In brief, in the supply-defaults step, scalar base
inputs are coerce to numbers, but the supply-defaults step is designed to never penetrate array values, so non-numeric array items have to be handled in the calc step.
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 and by the way, half-valid array inputs are an example of test cases that should included in our jasmine suites.
@@ -21,7 +21,8 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { | |||
xa = pointData.xa, | |||
ya = pointData.ya, | |||
barDelta = (hovermode === 'closest') ? | |||
t.barwidth / 2 : t.dbar * (1 - xa._gd._fullLayout.bargap) / 2, | |||
t.barwidth / 2 : | |||
t.bargroupwidth, |
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.
nice simplification. 👍
x0 = xa.c2p(p0, true); | ||
x1 = xa.c2p(p1, true); | ||
y0 = ya.c2p(s0, true); | ||
y1 = ya.c2p(s1, true); |
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.
very clean here. 🍻
dontMergeOverlappingData = !barnorm; | ||
|
||
// update position axis and set bar offsets and widths | ||
calcTraces.forEach(function(calcTrace) { |
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.
let's rewrite this as a for-loop for speed 🐎
"data":[ | ||
{ | ||
"base":6, | ||
"y":[1,2,3,4], |
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.
Oof. That took me a while to understand what's going on here. But, I think you got it right.
base
always refers to the start of the bars and y
always refers to their end positions - even though the bar base can be above the bar y coordinate.
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.
Correct!
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.
wait, doesn't y
normally refer to the bar's size, not its end position?
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.
Yeah, I really think it should be size. y
is the primary data "value" attribute (for vertical bars) and for a bar chart, the bar's value is its size, where it starts or ends is a secondary level of analysis.
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 I think making y
mean size
would be confusing, because I'd expect the values in y
and the ticks in yaxis
to be the same.
I think you're right. The thing that made me think of y
as end positions was that we store the end position in calcdata[i].y
.
valType: 'any', | ||
arrayOk: true, | ||
role: 'info', | ||
description: [ |
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.
To make the fact that these attributes are optional, we usually set the dflt: null
(e.g. contour trace start
, end
, size
).
Note that, null
are ignored upon Lib.nestedProperty(/**/).set()
, so your fullTrace.base === undefined
will still work.
calcTrace = calcTraces[i]; | ||
fullTrace = calcTrace[0].trace; | ||
|
||
if(fullTrace.offset === undefined) included.push(calcTrace); |
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.
nice pattern 👍
Thanks again @n-riesco for making the bar set-position step very nicely readable. Apart from my comment about guarding against non-valid So, before making a PR to the main plotly.js repo:
|
reminds me we should have one test image with horizontal bars and these attributes. |
* Null defaults are used to denote optional properties. * Note that null values are ignored by `Lib.nestedProperty(/**/).set()`, so that the pattern `fullTrace.base === undefined` still works.
* Added note to explain the reason why setGroupPositionsInOverlayMode handles the case barnorm is defined.
* Commit bar tests before implementing the commit that interprets trace data as sizes. This should help ensure the implementation is thorough.
* Updated bar tests that set the base attribute, so that now trace data is interpreted as sizes.
* Update bar_attrs_* mock images to account for trace data being interpreted as sizes. * bar_attrs_group.json: updated y to store sizes instead of bar tops. * bar_attrs_group_norm.json: updated base and y, so that it's clear that this mock tests the use of base and normalisation. * bar_attrs_overlay.json: updated y to store sizes instead of bar tops.
* Converted bar_attrs_group_norm into a horizontal bar plot.
* Updated baseline, because now this mock is a horizontal bar plot.
@etpinard @alexcjohnson I've addressed all the feedback. This PR is ready for review again. |
// | ||
// (note that `setGroupPositionsInOverlayMode` handles the case barnorm | ||
// is defined, because this function is also invoked for traces that | ||
// can't be grouped or stacked) |
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.
thanks!
💃 from my side - thanks! |
}); | ||
}); | ||
|
||
describe('Bar.calc', function() { |
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.
Very nice.
Thanks very much!
@@ -348,6 +358,11 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve) { | |||
// if width is an array, then clone it into t.barwidth. | |||
var newBarwidth = width.slice(0, calcTrace.length); | |||
|
|||
// guard against non-numeric items | |||
for(j = 0; j < newBarwidth.length; j++) { |
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.
@n-riesco is there a way to place this for-loop in a place common to setOffsetAndWidthInGroupMode
and setOffsetAndWidth
?
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.
Good catch! Since offset
is undefined for all the traces in group
mode, both functions can share the same implementation.
vpad = pMaxWidth / 2; | ||
Axes.expand(pa, [pMax + pMaxOffset + vpad], {vpad: vpad}); | ||
} | ||
Axes.expand(pa, [pMin, pMax], {padded: 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.
@n-riesco would you mind sharing some info here about why this patch was needed? Thanks.
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.
Before having offset
and width
, the range of the position axis was computed using the position data x
padded with vpad = minDiff/2
(minDiff
being the maximum size of a bar group).
With offset
and width
, bars can be shifted away from their positions x
and widths can be larger than minDiff
. Here I'm iterating each bar and computing their left and right sides (taking into account offset
and width
). This helps me compute the pMin
and pMax
needed to expand the position axis, so that all bars are fully within the range of the position axis.
I will add a comment in the code to explain this.
@@ -566,7 +566,7 @@ function normalizeBars(gd, sa, sieve) { | |||
|
|||
if(!isNumeric(bar.s)) continue; | |||
|
|||
var scale = Math.abs(sTop / sieve.get(bar.p, bar.b + bar.s)); | |||
var scale = Math.abs(sTop / sieve.get(bar.p, bar.s)); |
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.
glad to see if was that easy to make the switch 👍
"type":"bar" | ||
}, { | ||
"base":[7,6,5,4], | ||
"x":[1,2,3,4], |
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.
very nice mock!
}, { | ||
"base":[0,1,3,2], | ||
"width":1, | ||
"offset":[1.5], |
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.
very nice here, offset[0]
is only applied to the first bar.
Awesome @n-riesco 👍 My two comments #3 (comment) and #3 (comment) are non-blocking. After rebasing the test commits into one for all mock / baselines additions and one for all jasmine updates / additions, PR away to main repo! |
@AdnanBoota we got the message, it's not helpful to post this question more than once. |
@etpinard @alexcjohnson
This PR implements attributes
base
,offset
andwidth
as discussed in plotly#80 .I've tested the PR mostly by playing with mock images, and I'm yet to commit the jasmine tests.
Although I've been careful to maintain the original behaviour of all barmodes, I agree with Étienne, here, that more jasmine tests are needed to avoid any regressions.
@etpinard Are there any cases in particular that you'd like to see tested?