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

Implemented aligment by senior unit in time axis. #4267

Merged
merged 9 commits into from May 29, 2017

Conversation

hurskiy-andriy
Copy link
Contributor

Time axis ticks are aligned by senior units of determined base units (for example: if units determined as "hour", senior units will be "day"). See #4187
Check samples:
samples/scales/time/combo.html
and
samples/scales/time/line-point-data.html

@@ -315,15 +331,26 @@ module.exports = function(Chart) {

var maxTicks = me.getLabelCapacity(minTimestamp || dataMin);
var unit = timeOpts.unit || determineUnit(timeOpts.minUnit, minTimestamp || dataMin, maxTimestamp || dataMax, maxTicks);
var units = Object.keys(interval);
var seniorUnit = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd probably call it majorUnit vs seniorUnit

Copy link
Member

Choose a reason for hiding this comment

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

Agreed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wll be changed in next pull request

Copy link
Member

Choose a reason for hiding this comment

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

@hurskiy-andriy can you change that in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry. Changed in this PR.

@@ -218,7 +218,7 @@ describe('Time scale tests', function() {

// Counts down because the lines are drawn top to bottom
expect(xScale.ticks[0]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981', 'Nov 21, 1981']); // handle time zone changes
expect(xScale.ticks[1]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981', 'Nov 21, 1981']); // handle time zone changes
// expect(xScale.ticks[1]).toEqualOneOf(['Nov 19, 1981', 'Nov 20, 1981', 'Nov 21, 1981']); // handle time zone changes
Copy link
Contributor

@benmccann benmccann May 17, 2017

Choose a reason for hiding this comment

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

can this be changed so that it passes? e.g. by changing the expected values to 'Dec 1, 1981' or whatever it is that it's expecting now
or if there's not a reasonable way to fix it, it's probably better to delete it rather than leaving commented out code

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Uncommented and changed in next pull request

Copy link
Member

Choose a reason for hiding this comment

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

@hurskiy-andriy same here!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed in this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Uncommented in last commit

@@ -315,15 +331,26 @@ module.exports = function(Chart) {

var maxTicks = me.getLabelCapacity(minTimestamp || dataMin);
var unit = timeOpts.unit || determineUnit(timeOpts.minUnit, minTimestamp || dataMin, maxTimestamp || dataMax, maxTicks);
var units = Object.keys(interval);
var seniorUnit = null;
Copy link
Member

Choose a reason for hiding this comment

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

Agreed

ticks.push(options.min !== undefined ? options.min : niceRange.min);
var cur = moment(niceRange.min);
var startTick = options.min !== undefined ? options.min : niceRange.min;
var seniorUnitStart = moment(startTick).add(1, options.seniorUnit).startOf(options.seniorUnit);
Copy link
Member

Choose a reason for hiding this comment

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

what will happen if options.seniorUnit is not set? That would be the default case

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If options.seniorUnit is not set - there will be no alignment by majorUnit.
Added check of options.seniorUnit

Copy link
Contributor

Choose a reason for hiding this comment

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

We discussed in the feature request #4187 that the alignment always be on. Can you auto-detect what the majorUnit is? You should be able to use determineUnit and interval to do so. I think this would be much simpler for the user

Copy link
Contributor Author

Choose a reason for hiding this comment

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

majorUnit is auto-detected on line #340 and then passes to options on line #353

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we remove options.majorUnit? I don't think the user should be able to change it as it just adds additional complexity

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These options are not user defined options. This is object constructed on line #347 and passed to Chart.Ticks.generators.time call and the inside this call passed to generateTicks function at line #231

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, you're correct. Thanks for clarifying

@@ -231,7 +232,7 @@ describe('Time scale tests', function() {

var scale = createScale(mockData, config);
scale.update(2500, 200);
expect(scale.ticks).toEqual(['Jan 1, 8PM', 'Jan 1, 9PM', 'Jan 1, 10PM', 'Jan 1, 11PM', 'Jan 2, 12AM', 'Jan 2, 1AM', 'Jan 2, 2AM', 'Jan 2, 3AM', 'Jan 2, 4AM', 'Jan 2, 5AM', 'Jan 2, 6AM', 'Jan 2, 7AM', 'Jan 2, 8AM', 'Jan 2, 9AM', 'Jan 2, 10AM', 'Jan 2, 11AM', 'Jan 2, 12PM', 'Jan 2, 1PM', 'Jan 2, 2PM', 'Jan 2, 3PM', 'Jan 2, 4PM', 'Jan 2, 5PM', 'Jan 2, 6PM', 'Jan 2, 7PM', 'Jan 2, 8PM', 'Jan 2, 9PM']);
expect(scale.ticks).toEqual(['8PM', '9PM', '10PM', '11PM', 'Jan 2', '1AM', '2AM', '3AM', '4AM', '5AM', '6AM', '7AM', '8AM', '9AM', '10AM', '11AM', '12PM', '1PM', '2PM', '3PM', '4PM', '5PM', '6PM', '7PM', '8PM', '9PM']);
Copy link
Contributor

Choose a reason for hiding this comment

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

There should either be one PR with both the alignment and formatting changes. Or one PR with only alignment changes and one PR with only formatting changes.

This PR says it's the alignment changes and that #4268 is the formatting changes. But you're making formatting changes here which is really confusing.

var alignedTick = startTick;
ticks.push(startTick);
if (startTick !== alignedTick + startFraction &&
options.majorUnit &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you indent continuation lines +2 tabs? I.e. this line and the next two lines. That way it's clearer what's part of the if and what's inside it

Copy link
Member

Choose a reason for hiding this comment

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

I don't think that will make ESLint happy!

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, well if it makes ESLint fail, then nevermind. @simonbrunel do you have any convention about whether the && should be at the end of the line or the beginning of the next line?

Copy link
Member

Choose a reason for hiding this comment

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

I'm used to && and || at the end but it's not enforced by the linter.

This code should fit in one line and be more readable:

  • startTick !== alignedTick + startFraction eq. startFraction !== 0
  • majorUnit should be cached because it's used many time
  • same for timeOpts
var majorUnit = options.majorUnit;
var timeOpts = options.timeOpts;
//...
if (majorUnit  && startFraction && !timeOpts.round && !timeOpts.isoWeekday) {
    // ...
}

unit: unit,
timeOpts: timeOpts,
Copy link
Contributor

Choose a reason for hiding this comment

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

it's weird that here we pass in timeOpts and on the next line we pass in timeOpts.isoWeekday. we should maybe change this to timeOpts.round for consistency?

me.displayFormat = timeOpts.displayFormats[unit];
me.seniorDisplayFormat = timeOpts.displayFormats[majorUnit];
Copy link
Contributor

Choose a reason for hiding this comment

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

we should probably call this majorDisplayFormat for consistency

var startTick = options.min !== undefined ? options.min : niceRange.min;
var majorUnitStart = startTick;
if (options.majorUnit) {
majorUnitStart = moment(startTick).add(1, options.majorUnit).startOf(options.majorUnit);
Copy link
Contributor

Choose a reason for hiding this comment

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

if startTick is already at a majorUnit then I think you'll end up at the next one. do you really want something like:

majorUnitStart = moment(startTick).startOf(options.majorUnit) == moment(startTick) ? moment(startTick) : moment(startTick).add(1, options.majorUnit).startOf(options.majorUnit);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Assignment startTick to majorUnitStart on line #179 is performed instead of else block of if statement at line #180
In other words we can write:
var majorUnitStart; if (options.majorUnit) { majorUnitStart = moment(startTick).add(1, options.majorUnit).startOf(options.majorUnit); } else { majorUnitStart = startTick; }

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to be rounding up here? I would have guessed we'd round down. In which case we wouldn't add 1 and you could ignore my previous comment

var startFraction = startRange % (interval[options.unit].size * stepSize);
var alignedTick = startTick;
ticks.push(startTick);
if (startTick !== alignedTick + startFraction && options.majorUnit && !options.timeOpts.round && !options.timeOpts.isoWeekday) {
Copy link
Contributor

@benmccann benmccann May 26, 2017

Choose a reason for hiding this comment

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

I'm not sure why you need startTick !== alignedTick + startFraction. Is this equivalent to startTick !== majorUnitStart?

Copy link
Member

Choose a reason for hiding this comment

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

For sure, it's equivalent to if (startFraction && ... (see my previous comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If startTick is not equal to time that aligned by stepSize accordingly to majorUnit we need to find first aligned tick so then we can push ticks with stepSize and there will be ticks aligned to majorUnit.
In other words if we have:
startTick == 20:12;
majorUnit == "hour";
sterSize == "3 hour";
majorUnitStart will equal to "00:00", first aligned tick will equal to "21:00" and startFraction will equal to "48 minutes".
With this value of startFraction we can determine alignedTick (tick which aligned by stepSize accordingly to majorUnit (21:00, 00:00, 03:00 etc..))
When we perform startTick !== alignedTick + startFraction at line #187 we check if we need to push aligned tick (21:00) or startTick (20:12) is already aligned.
So startFraction is not difference between startTick (20:12) and majorUnitStart (00:00) but difference between startTick (20:12) and first aligned tick (21:00)

Copy link
Member

Choose a reason for hiding this comment

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

@hurskiy-andriy if var alignedTick = startTick; (line 185) then:

if (startTick !== alignedTick + startFraction && ... eq.
if (startTick !== startTick + startFraction && ... eq.
if (startFraction !== 0 && ... eq.
if (startFraction && ...

Am I right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

@hurskiy-andriy I'm not following your example. I don't understand why majorUnitStart will be equal to "00:00". Wouldn't it be equal to "21:00"?

majorUnitStart = moment(startTick).add(1, options.majorUnit).startOf(options.majorUnit);
majorUnitStart = (20.12 + 1hr).startOf(hour) = 21.12.startOf(hour) = 21:00

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@benmccann Sorry, my mistake. In example above unit == "hour" and majorUnit == "day"

@benmccann
Copy link
Contributor

@hurskiy-andriy can you rebase both your PRs?

@hurskiy-andriy
Copy link
Contributor Author

hurskiy-andriy commented May 26, 2017

@benmccann rebase master into my branches?

@benmccann
Copy link
Contributor

benmccann commented May 26, 2017

Yes, rebase each branch against master, so that they can be merged. It looks like you've already done that

@etimberg etimberg added this to the Version 2.7 milestone May 27, 2017
@benmccann
Copy link
Contributor

The samples (e.g. samples/scales/time/line-point-data.html and samples/scales/time/combo.html) look much nicer with this change. The one improvement I would suggest is to also align the lefthand and righthand side of the chart just as we do for the points on the interior of the chart. E.g. on samples/scales/time/line-point-data.html it would be nice to make the lefthand side "May 27, 6AM" and the righthand side "Jun 1, 12PM"

Doing so would also fix some existing bugs such as #4293

var cur = moment(niceRange.min);
var startTick = options.min !== undefined ? options.min : niceRange.min;
var majorUnitStart = startTick;
if (options.majorUnit) {
Copy link
Member

Choose a reason for hiding this comment

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

options.majorUnit is used quite often in this method, I would cache it (var majorUnit = options.majorUnit) to increase minification.

@@ -94,7 +94,7 @@ describe('Time scale tests', function() {
displayFormat: false,
minUnit: 'millisecond',
displayFormats: {
millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM
millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
Copy link
Contributor

Choose a reason for hiding this comment

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

don't need a comma at the end of this line

var majorUnitStart = startTick;
var majorUnit = options.majorUnit;
if (majorUnit) {
majorUnitStart = moment(startTick).add(1, majorUnit).startOf(majorUnit);
Copy link
Contributor

Choose a reason for hiding this comment

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

you could combine a few lines by doing:

var majorUnitStart = majorUnit ? moment(startTick).add(1, majorUnit).startOf(majorUnit) : startTick;

var startFraction = startRange % stepValue;
var alignedTick = startTick;
if (startFraction && majorUnit && !options.timeOpts.round && !options.timeOpts.isoWeekday) {
alignedTick += startFraction - stepValue;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is alignedTick += startFraction - stepValue equal to alignedTick -= startFraction ? If so, that might be a more straightforward way of writing it

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 startFraction will equal to 0 - we haven't pass condition on line above

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure how that's related to my question...

@benmccann
Copy link
Contributor

Thanks @hurskiy-andriy This looks great! The samples render so nicely now :-)

I left just a couple very small comments, but otherwise looks good to me

if (majorUnit) {
majorUnitStart = moment(startTick).add(1, majorUnit).startOf(majorUnit);
}
var majorUnitStart = majorUnit ? majorUnitStart = moment(startTick).add(1, majorUnit).startOf(majorUnit) : startTick;
Copy link
Contributor

Choose a reason for hiding this comment

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

This line has majorUnitStart = twice. You only need it once:

var majorUnitStart = majorUnit ? moment(startTick).add(1, majorUnit).startOf(majorUnit) : startTick;

@etimberg etimberg merged commit f7f177f into chartjs:master May 29, 2017
exwm pushed a commit to exwm/Chart.js that referenced this pull request Apr 30, 2021
Implemented alignment by major unit in the time scale. This allows showing the first tick of a larger unit like days in a special way and is part of the basis of the time series scale.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants