Skip to content
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

Merged
merged 6 commits into from
Apr 18, 2019
Merged

Conversation

slok
Copy link
Contributor

@slok slok commented Apr 14, 2019

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.

…e displayed as empty cells

Signed-off-by: Xabier Larrakoetxea <slok69@gmail.com>
Signed-off-by: Xabier Larrakoetxea <slok69@gmail.com>
@coveralls
Copy link

coveralls commented Apr 14, 2019

Pull Request Test Coverage Report for Build 1052

  • 26 of 26 (100.0%) changed or added relevant lines in 2 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.02%) to 92.535%

Totals Coverage Status
Change from base Build 1032: 0.02%
Covered Lines: 6644
Relevant Lines: 7180

💛 - Coveralls

Copy link
Owner

@mum4k mum4k left a 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) {
Copy link
Owner

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.

Copy link
Contributor Author

@slok slok Apr 15, 2019

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?

Copy link
Owner

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.

@@ -960,6 +960,53 @@ func TestLineChartDraws(t *testing.T) {
return ft
},
},
{
desc: "more values than capacity, X rescales with NaN values ignored",
Copy link
Owner

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.

Copy link
Contributor Author

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
Copy link
Owner

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?

Copy link
Contributor Author

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>
@slok
Copy link
Contributor Author

slok commented Apr 17, 2019

Ready for a new check!

I've added 2 things as you noted:

  • numbers.MinMax now returns NaN min and max when all values are NaN.
  • LineChart defaults to 0 when the min or max is NaN.

Copy link
Owner

@mum4k mum4k left a 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.

@@ -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
Copy link
Owner

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"

}

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)) ||
Copy link
Owner

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.

Copy link
Contributor Author

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.

Copy link
Owner

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.

@@ -519,3 +531,22 @@ func (lc *LineChart) maxXValue() int {
}
return maxLen - 1
}

const (
Copy link
Owner

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>
@slok
Copy link
Contributor Author

slok commented Apr 18, 2019

Ready for a new review!

Sorry for all the time is requiring this PR, thank you.

@mum4k
Copy link
Owner

mum4k commented Apr 18, 2019

Looks good, thank you for all the work on this PR!

@mum4k mum4k merged commit 544632f into mum4k:devel Apr 18, 2019
@mum4k mum4k mentioned this pull request Apr 29, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants