Implemented aligment by senior unit in time axis.#4267
Implemented aligment by senior unit in time axis.#4267etimberg merged 9 commits intochartjs:masterfrom
Conversation
src/scales/scale.time.js
Outdated
| 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; |
There was a problem hiding this comment.
I'd probably call it majorUnit vs seniorUnit
There was a problem hiding this comment.
Wll be changed in next pull request
There was a problem hiding this comment.
Sorry. Changed in this PR.
test/specs/scale.time.tests.js
Outdated
| // 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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Uncommented and changed in next pull request
There was a problem hiding this comment.
Changed in this PR
There was a problem hiding this comment.
Uncommented in last commit
src/scales/scale.time.js
Outdated
| 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; |
src/scales/scale.time.js
Outdated
| 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); |
There was a problem hiding this comment.
what will happen if options.seniorUnit is not set? That would be the default case
There was a problem hiding this comment.
If options.seniorUnit is not set - there will be no alignment by majorUnit.
Added check of options.seniorUnit
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Can we remove options.majorUnit? I don't think the user should be able to change it as it just adds additional complexity
There was a problem hiding this comment.
Yes, you're correct. Thanks for clarifying
test/specs/scale.time.tests.js
Outdated
| 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']); |
There was a problem hiding this comment.
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.
src/scales/scale.time.js
Outdated
| var alignedTick = startTick; | ||
| ticks.push(startTick); | ||
| if (startTick !== alignedTick + startFraction && | ||
| options.majorUnit && |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
I don't think that will make ESLint happy!
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 + startFractioneq.startFraction !== 0majorUnitshould 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) {
// ...
}
src/scales/scale.time.js
Outdated
| stepSize: stepSize, | ||
| majorUnit: majorUnit, | ||
| unit: unit, | ||
| timeOpts: timeOpts, |
There was a problem hiding this comment.
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?
src/scales/scale.time.js
Outdated
| } | ||
|
|
||
| me.displayFormat = timeOpts.displayFormats[unit]; | ||
| me.seniorDisplayFormat = timeOpts.displayFormats[majorUnit]; |
There was a problem hiding this comment.
we should probably call this majorDisplayFormat for consistency
src/scales/scale.time.js
Outdated
| 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); |
There was a problem hiding this comment.
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);
There was a problem hiding this comment.
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; }
There was a problem hiding this comment.
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
src/scales/scale.time.js
Outdated
| 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) { |
There was a problem hiding this comment.
I'm not sure why you need startTick !== alignedTick + startFraction. Is this equivalent to startTick !== majorUnitStart?
There was a problem hiding this comment.
For sure, it's equivalent to if (startFraction && ... (see my previous comment)
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
@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?
There was a problem hiding this comment.
@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
There was a problem hiding this comment.
@benmccann Sorry, my mistake. In example above unit == "hour" and majorUnit == "day"
|
@hurskiy-andriy can you rebase both your PRs? |
|
@benmccann rebase |
…ck 'if' statement formatting. Removed isoWeekday from generateTick ooptions due to redundancy.
76a3d19 to
deee58f
Compare
|
Yes, rebase each branch against master, so that they can be merged. It looks like you've already done that |
|
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 |
src/helpers/helpers.time.js
Outdated
| var cur = moment(niceRange.min); | ||
| var startTick = options.min !== undefined ? options.min : niceRange.min; | ||
| var majorUnitStart = startTick; | ||
| if (options.majorUnit) { |
There was a problem hiding this comment.
options.majorUnit is used quite often in this method, I would cache it (var majorUnit = options.majorUnit) to increase minification.
… first and last ticks to time steps.
test/specs/scale.time.tests.js
Outdated
| 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, |
There was a problem hiding this comment.
don't need a comma at the end of this line
src/helpers/helpers.time.js
Outdated
| var majorUnitStart = startTick; | ||
| var majorUnit = options.majorUnit; | ||
| if (majorUnit) { | ||
| majorUnitStart = moment(startTick).add(1, majorUnit).startOf(majorUnit); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Is alignedTick += startFraction - stepValue equal to alignedTick -= startFraction ? If so, that might be a more straightforward way of writing it
There was a problem hiding this comment.
I startFraction will equal to 0 - we haven't pass condition on line above
There was a problem hiding this comment.
I'm not sure how that's related to my question...
|
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 |
src/helpers/helpers.time.js
Outdated
| if (majorUnit) { | ||
| majorUnitStart = moment(startTick).add(1, majorUnit).startOf(majorUnit); | ||
| } | ||
| var majorUnitStart = majorUnit ? majorUnitStart = moment(startTick).add(1, majorUnit).startOf(majorUnit) : startTick; |
There was a problem hiding this comment.
This line has majorUnitStart = twice. You only need it once:
var majorUnitStart = majorUnit ? moment(startTick).add(1, majorUnit).startOf(majorUnit) : startTick;
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.
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