-
Notifications
You must be signed in to change notification settings - Fork 138
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
Allow empty values on line chart #185
Conversation
…e displayed as empty cells Signed-off-by: Xabier Larrakoetxea <slok69@gmail.com>
Signed-off-by: Xabier Larrakoetxea <slok69@gmail.com>
Pull Request Test Coverage Report for Build 1052
💛 - Coveralls |
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.
Great work and thank you for also updating the changelog and the tests.
for _, v := range values { | ||
if math.IsNaN(v) { |
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 think we might have a corner case now - if all the values are NaN, this function will return min==math.MaxFloat64, max==-1 * math.MaxFloat64.
Wondering what the right thing to do is, but considering that the min and max cannot be determined from NaN values, should we return min==NaN, max==NaN?
We probably should also document the corner case in the doc comment and add a corresponding test case.
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.
You are right, maybe we could return some default values for min and max in this case, for example (-1,1), or (0,1). What do you think?
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.
Thinking about this more - this might not be the right place to do that. This function is fairly generic "numerical" function and has more users than just the LineChart. I do agree with you that the behavior of LineChart should be to set some default min and max if all values are NaN.
How about we keep this function generic and return min==NaN, max==NaN when all values are NaN to indicate to all possible callers that the min and max cannot be determined? And then the LineChart internally can interpret the min==NaN, max==NaN as min==0, max==0. Note the LineChart should handle both min and max equal to zero.
widgets/linechart/linechart_test.go
Outdated
@@ -960,6 +960,53 @@ func TestLineChartDraws(t *testing.T) { | |||
return ft | |||
}, | |||
}, | |||
{ | |||
desc: "more values than capacity, X rescales with NaN values ignored", |
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 are a couple more interesting cases we might want to test:
- all the values are NaN.
- the very first visible value is NaN.
- the very last visible value is NaN.
I would appreciate if you would consider adding the test cases. However I know this is a lot of work, so if you want you can just confirm that the corner cases work and I will be happy to add the missing test coverage after we merge your PR in.
If you do choose to add the tests, we could consider adding them cases with less values in the series (just the smallest amount we need for the test case). That should make them easier to write.
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.
You are right, no problem! I'll add them.
@@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
- The grid.Builder API now allows users to specify options for intermediate | |||
containers, i.e. containers that don't have widgets, but represent rows and | |||
columns. | |||
- Line chart widget now allows `math.NaN` values to represent "no value" (values |
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.
One more request in relation to the CLA - can you please explicitly confirm on this PR that I have your permission to merge this into the master branch in the next release?
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.
@mum4k has permission to merge this PR in master branch in the next release.
Signed-off-by: Xabier Larrakoetxea <slok69@gmail.com>
Signed-off-by: Xabier Larrakoetxea <slok69@gmail.com>
Ready for a new check! I've added 2 things as you noted:
|
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.
Looks great, thank you very much for the contribution.
internal/numbers/numbers.go
Outdated
@@ -109,21 +109,33 @@ func Round(x float64) float64 { | |||
|
|||
// MinMax returns the smallest and the largest value among the provided values. | |||
// Returns (0, 0) if there are no values. | |||
// Ignores NaN values. Allowing NaN values could led in a corner case where 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.
(nit) Possible typo.
"could led" Vs. "could lead"
internal/numbers/numbers_test.go
Outdated
} | ||
|
||
for _, tc := range tests { | ||
t.Run(tc.desc, func(t *testing.T) { | ||
gotMin, gotMax := MinMax(tc.values) | ||
if gotMin != tc.wantMin || gotMax != tc.wantMax { | ||
// Different assertion for NaN cases. | ||
if (math.IsNaN(tc.wantMin) && !math.IsNaN(gotMin)) || |
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 wonder - can we simplify this by using pretty.Compare?
if diff := pretty.Compare(tc.wantMin, gotMin); diff != "" {
t.Errorf("MinMax => unexpected min, diff (-want, +got):\n %s", diff)
}
if diff := pretty.Compare(tc.wantMax, gotMax); diff != "" {
t.Errorf("MinMax => unexpected max, diff (-want, +got):\n %s", diff)
}
I didn't try to use pretty on floats before, but hopefully it does the right thing.
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.
Much better. I wasn't sure if you wanted to use something different than standard conditionals to check floats.
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 think pretty is fine even for simple types assuming it works, we use it in majority of tests in this project already.
widgets/linechart/linechart.go
Outdated
@@ -519,3 +531,22 @@ func (lc *LineChart) maxXValue() int { | |||
} | |||
return maxLen - 1 | |||
} | |||
|
|||
const ( |
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.
(optional) Since these consts are only used within the minMax function below, we could as well define them inside the function. Encapsulating them in might give a better explanation of what they are for.
Or we could as well not use the consts and just go with literal zeroes, up to you.
Signed-off-by: Xabier Larrakoetxea <slok69@gmail.com>
Signed-off-by: Xabier Larrakoetxea <slok69@gmail.com>
Ready for a new review! Sorry for all the time is requiring this PR, thank you. |
Looks good, thank you for all the work on this PR! |
This PR Fixes #184.
It will allow having values on the line chart that will not be displayed if they are missing. To do so it will use the
math.NaN
float64 value.If this is combined with adaptive Y-axis option, the graphs that have empty gaps and high values with small variance will be more accurate regarding the Y axis (because they will not start at 0).
In this example, we can see that we don't have metrics around 10:03-10:05 making the graph render values as 0 and making Y axis start at 0:
Instead of representing the missing metrics as 0, with this change and using
math.NaN
, the Y axis has a better range around the min and max values making the graph more accurate.