Skip to content

Handle weekday strings in rangebreaks #4661

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

Merged
merged 9 commits into from
Mar 19, 2020
40 changes: 38 additions & 2 deletions src/plots/cartesian/axis_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ var handleCategoryOrderDefaults = require('./category_order_defaults');
var handleLineGridDefaults = require('./line_grid_defaults');
var setConvert = require('./set_convert');

var DAY_OF_WEEK = require('./constants').WEEKDAY_PATTERN;

/**
* options: object containing:
*
Expand Down Expand Up @@ -161,6 +163,27 @@ function rangebreaksDefaults(itemIn, itemOut, containerOut) {
itemOut.bounds = itemOut.bounds.slice(0, 2);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you have to do this slice(0, 2) - coerce for info_array already does this.
But if somehow I'm mistaken about that, you'd need to also shorten bnds so you don't lengthen itemOut.bounds again in the loops below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any place where itemOut.bounds.length is changed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

itemOut.bounds[i] = bnds[i] = q;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. I revised the new logic in 550a375.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, you don't need to worry about the length of bnds at all - coerce handles that so bnds (and itemOut.bounds) is either undefined (since it has no default value) or it's an array of length 2 - or less, I just tested this, if you pass in a shorter array coerce isn't going to pad it since the inner values don't have defaults either, but that's OK, it'll just look like undefined for missing values and get filtered out in the type checks.

Can you please take all these length checks out? We put a lot of effort into making coerce do the right thing so the individual supplyDefaults functions could be simple and readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Addressed in 84c2a6f.

}

var dfltPattern = '';
var i, q;
for(i = 0; i < bnds.length; i++) {
q = indexOfDay(bnds[i]);
if(q !== -1) {
dfltPattern = DAY_OF_WEEK;
break;
}
}
var pattern = coerce('pattern', dfltPattern);

if(pattern === DAY_OF_WEEK) {
for(i = 0; i < bnds.length; i++) {
q = indexOfDay(bnds[i]);
if(q !== -1) {
// convert to integers i.e 'Monday' --> 1
itemOut.bounds[i] = bnds[i] = q;
}
}
}

if(containerOut.autorange === false) {
var rng = containerOut.range;

Expand All @@ -175,8 +198,6 @@ function rangebreaksDefaults(itemIn, itemOut, containerOut) {
return;
}
}

coerce('pattern');
} else {
var values = coerce('values');

Expand All @@ -189,3 +210,18 @@ function rangebreaksDefaults(itemIn, itemOut, containerOut) {
}
}
}

var weekSTR = [
'sun',
'mon',
'tue',
'wed',
'thu',
'fri',
'sat'
];

function indexOfDay(v) {
var str = String(v).substr(0, 3).toLowerCase();
return weekSTR.indexOf(str);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be sped up a bit with an object lookup rather than indexOf

var dayStrToNum = {
    sun: 0,
    mon: 1,
    ...
}

return dayStrToNum[str];

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and possibly bail out early if v isn't already a string, rather than converting to a string only to discard it later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good calls! Done in db2c3a3.
Curious to know if there is a good article on this?
At the moment, there are quite a number of places that indexOf !== -1 is applied, so please feel free to open an issue for that.
Thanks.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about an article... you find a lot of people saying "hash lookup is O(1) but indexOf is O(n)" but that's as far as it usually goes.

Here's a jsperf showing the general trends: https://jsperf.com/array-indexof-vs-object-key-lookup2/12 - basically, object lookups hardly scale with the number of keys but indexOf scales linearly. So indexOf can be a nice way to simplify if (a===b || a===c || a===d) -> if ([b,c,d].indexOf(a) !== -1) - but more than a few items and it's better to turn the problem into an object lookup.

Also of course object lookups only support strings... looking forward to when we stop supporting IE and can use Set!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexcjohnson thank you!!

}
8 changes: 5 additions & 3 deletions src/plots/cartesian/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,16 +280,18 @@ module.exports = {
pattern: {
valType: 'enumerated',
values: [DAY_OF_WEEK, HOUR, ''],
dflt: '',
role: 'info',
editType: 'calc',
description: [
'Determines a pattern on the time line that generates breaks.',
'If *' + DAY_OF_WEEK + '* - Sunday-based weekday as a decimal number [0, 6].',
'If *' + DAY_OF_WEEK + '* - days of the week in English e.g. \'Sunday\' or `\sun\`',
'(matching is case-insensitive and considers only the first three characters),',
'as well as Sunday-based integers between 0 and 6.',
'If *' + HOUR + '* - hour (24-hour clock) as decimal numbers between 0 and 24.',
'for more info.',
'Examples:',
'- { pattern: \'' + DAY_OF_WEEK + '\', bounds: [6, 0] }',
'- { pattern: \'' + DAY_OF_WEEK + '\', bounds: [6, 1] }',
' or simply { bounds: [\'sat\', \'mon\'] }',
' breaks from Saturday to Monday (i.e. skips the weekends).',
'- { pattern: \'' + HOUR + '\', bounds: [17, 8] }',
' breaks from 5pm to 8am (i.e. skips non-work hours).'
Expand Down
27 changes: 27 additions & 0 deletions test/jasmine/tests/axes_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1135,6 +1135,33 @@ describe('Test axes', function() {
expect(layoutOut.xaxis2.rangebreaks[0].pattern).toBe('day of week', 'coerced');
expect(layoutOut.xaxis3.rangebreaks[0].pattern).toBe(undefined, 'not coerce, using *values*');
});

it('should auto default rangebreaks.pattern to *day of week* when *bounds* include a weekday string and convert bounds to integer days', function() {
layoutIn = {
xaxis: {type: 'date', rangebreaks: [
{bounds: ['Saturday', 'Monday']}
]},
xaxis2: {type: 'date', rangebreaks: [
{bounds: ['sun', 'thu']},
{bounds: ['mon', 'fri']},
{bounds: ['tue', 'sat']},
{bounds: ['wed', '-1']}
]}
};
layoutOut._subplots.xaxis.push('x2');
supplyLayoutDefaults(layoutIn, layoutOut, fullData);

expect(layoutOut.xaxis.rangebreaks[0].pattern).toBe('day of week', 'complete Capital');
expect(layoutOut.xaxis2.rangebreaks[0].pattern).toBe('day of week', '3-letter case');
expect(layoutOut.xaxis2.rangebreaks[0].bounds[0]).toBe(0, 'convert sun');
expect(layoutOut.xaxis2.rangebreaks[1].bounds[0]).toBe(1, 'convert mon');
expect(layoutOut.xaxis2.rangebreaks[2].bounds[0]).toBe(2, 'convert tue');
expect(layoutOut.xaxis2.rangebreaks[3].bounds[0]).toBe(3, 'convert wed');
expect(layoutOut.xaxis2.rangebreaks[0].bounds[1]).toBe(4, 'convert thu');
expect(layoutOut.xaxis2.rangebreaks[1].bounds[1]).toBe(5, 'convert fri');
expect(layoutOut.xaxis2.rangebreaks[2].bounds[1]).toBe(6, 'convert sat');
expect(layoutOut.xaxis2.rangebreaks[3].bounds[1]).toBe('-1', 'string');
});
});

describe('constraints relayout', function() {
Expand Down