mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
parent
40d144f455
commit
b353cdd90b
3 changed files with 298 additions and 10 deletions
|
@ -20,7 +20,9 @@
|
|||
import d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
import { InvalidLogScaleValues } from '../../../errors';
|
||||
import { timeTicks } from './time_ticks';
|
||||
|
||||
export function VislibAxisScaleProvider() {
|
||||
class AxisScale {
|
||||
|
@ -217,16 +219,7 @@ export function VislibAxisScaleProvider() {
|
|||
this.validateScale(this.scale);
|
||||
|
||||
if (this.axisConfig.isTimeDomain()) {
|
||||
// on a time domain shift it to have the buckets start at nice points in time (e.g. at the start of the day) in UTC
|
||||
// then shift the calculated tick positions back into the real domain to have a nice tick position in the actual
|
||||
// time zone. This is necessary because the d3 time scale doesn't provide a function to get nice time positions in
|
||||
// a configurable time zone directly.
|
||||
const offset = moment(domain[0]).utcOffset();
|
||||
const shiftedDomain = domain.map(val => moment(val).add(offset, 'minute'));
|
||||
this.tickScale = scale.copy().domain(shiftedDomain);
|
||||
this.scale.timezoneCorrectedTicks = (n) => this.tickScale.ticks(n).map((d) => {
|
||||
return moment(d).subtract(offset, 'minute').valueOf();
|
||||
});
|
||||
this.scale.timezoneCorrectedTicks = timeTicks(scale);
|
||||
}
|
||||
|
||||
return this.scale;
|
||||
|
|
47
src/legacy/ui/public/vislib/lib/axis/time_ticks.js
Normal file
47
src/legacy/ui/public/vislib/lib/axis/time_ticks.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
export const timeTicks = scale => {
|
||||
// on a time domain shift it to have the buckets start at nice points in time (e.g. at the start of the day) in UTC
|
||||
// then shift the calculated tick positions back into the real domain to have a nice tick position in the actual
|
||||
// time zone. This is necessary because the d3 time scale doesn't provide a function to get nice time positions in
|
||||
// a configurable time zone directly.
|
||||
const domain = scale.domain();
|
||||
const startOffset = moment(domain[0]).utcOffset();
|
||||
const shiftedDomain = domain.map(val => moment(val).add(startOffset, 'minute'));
|
||||
const tickScale = scale.copy().domain(shiftedDomain);
|
||||
return n => {
|
||||
const ticks = tickScale.ticks(n);
|
||||
const timePerTick = (domain[1] - domain[0]) / ticks.length;
|
||||
const hourTicks = timePerTick < 1000 * 60 * 60 * 12;
|
||||
|
||||
return ticks.map(d => {
|
||||
// To get a nice date for the tick, we have to shift the offset of the current UTC tick. This is
|
||||
// relevant in cases where the domain spans various DSTs.
|
||||
// However if there are multiple ticks per day, this would cause a gap because the ticks are placed
|
||||
// in UTC which doesn't have DST. In this case, always shift by the offset of the beginning of the domain.
|
||||
const currentOffset = moment(d).utcOffset();
|
||||
return moment(d)
|
||||
.subtract(hourTicks ? startOffset : currentOffset, 'minute')
|
||||
.valueOf();
|
||||
});
|
||||
};
|
||||
};
|
248
src/legacy/ui/public/vislib/lib/axis/time_ticks.test.js
Normal file
248
src/legacy/ui/public/vislib/lib/axis/time_ticks.test.js
Normal file
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import d3 from 'd3';
|
||||
import moment from 'moment-timezone';
|
||||
import { timeTicks } from './time_ticks';
|
||||
|
||||
const timezonesToTest = [
|
||||
'Asia/Tokyo',
|
||||
'Europe/Berlin',
|
||||
'UTC',
|
||||
'America/New York',
|
||||
'America/Los_Angeles',
|
||||
];
|
||||
|
||||
describe('timeTicks', () => {
|
||||
let scale;
|
||||
|
||||
beforeEach(() => {
|
||||
scale = d3.time.scale.utc();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
moment.tz.setDefault();
|
||||
});
|
||||
|
||||
timezonesToTest.map(tz => {
|
||||
describe(`standard tests in ${tz}`, () => {
|
||||
beforeEach(() => {
|
||||
moment.tz.setDefault(tz);
|
||||
});
|
||||
|
||||
it('should return nice daily ticks', () => {
|
||||
scale.domain([
|
||||
moment('2019-04-04 00:00:00').valueOf(),
|
||||
moment('2019-04-08 00:00:00').valueOf(),
|
||||
]);
|
||||
const tickFn = timeTicks(scale);
|
||||
const ticks = tickFn(5);
|
||||
|
||||
expect(ticks).toEqual([
|
||||
moment('2019-04-04 00:00:00').valueOf(),
|
||||
moment('2019-04-05 00:00:00').valueOf(),
|
||||
moment('2019-04-06 00:00:00').valueOf(),
|
||||
moment('2019-04-07 00:00:00').valueOf(),
|
||||
moment('2019-04-08 00:00:00').valueOf(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return nice hourly ticks', () => {
|
||||
scale.domain([
|
||||
moment('2019-04-04 00:00:00').valueOf(),
|
||||
moment('2019-04-04 04:00:00').valueOf(),
|
||||
]);
|
||||
const tickFn = timeTicks(scale);
|
||||
const ticks = tickFn(5);
|
||||
|
||||
expect(ticks).toEqual([
|
||||
moment('2019-04-04 00:00:00').valueOf(),
|
||||
moment('2019-04-04 01:00:00').valueOf(),
|
||||
moment('2019-04-04 02:00:00').valueOf(),
|
||||
moment('2019-04-04 03:00:00').valueOf(),
|
||||
moment('2019-04-04 04:00:00').valueOf(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return nice yearly ticks', () => {
|
||||
scale.domain([
|
||||
moment('2010-04-04 00:00:00').valueOf(),
|
||||
moment('2019-04-04 04:00:00').valueOf(),
|
||||
]);
|
||||
const tickFn = timeTicks(scale);
|
||||
const ticks = tickFn(9);
|
||||
|
||||
expect(ticks).toEqual([
|
||||
moment('2011-01-01 00:00:00').valueOf(),
|
||||
moment('2012-01-01 00:00:00').valueOf(),
|
||||
moment('2013-01-01 00:00:00').valueOf(),
|
||||
moment('2014-01-01 00:00:00').valueOf(),
|
||||
moment('2015-01-01 00:00:00').valueOf(),
|
||||
moment('2016-01-01 00:00:00').valueOf(),
|
||||
moment('2017-01-01 00:00:00').valueOf(),
|
||||
moment('2018-01-01 00:00:00').valueOf(),
|
||||
moment('2019-01-01 00:00:00').valueOf(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return nice yearly ticks from leap year to leap year', () => {
|
||||
scale.domain([
|
||||
moment('2016-02-29 00:00:00').valueOf(),
|
||||
moment('2020-04-29 00:00:00').valueOf(),
|
||||
]);
|
||||
|
||||
const tickFn = timeTicks(scale);
|
||||
const ticks = tickFn(4);
|
||||
|
||||
expect(ticks).toEqual([
|
||||
moment('2017-01-01 00:00:00').valueOf(),
|
||||
moment('2018-01-01 00:00:00').valueOf(),
|
||||
moment('2019-01-01 00:00:00').valueOf(),
|
||||
moment('2020-01-01 00:00:00').valueOf(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dst switch', () => {
|
||||
it('should not leave gaps in hourly ticks on dst switch winter to summer time', () => {
|
||||
moment.tz.setDefault('Europe/Berlin');
|
||||
|
||||
scale.domain([
|
||||
moment('2019-03-31 01:00:00').valueOf(),
|
||||
moment('2019-03-31 03:00:00').valueOf(),
|
||||
]);
|
||||
|
||||
const tickFn = timeTicks(scale);
|
||||
const ticks = tickFn(5);
|
||||
|
||||
expect(ticks).toEqual([
|
||||
moment('2019-03-31 01:00:00').valueOf(),
|
||||
moment('2019-03-31 01:15:00').valueOf(),
|
||||
moment('2019-03-31 01:30:00').valueOf(),
|
||||
moment('2019-03-31 01:45:00').valueOf(),
|
||||
moment('2019-03-31 03:00:00').valueOf(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not leave gaps in hourly ticks on dst switch summer to winter time', () => {
|
||||
moment.tz.setDefault('Europe/Berlin');
|
||||
|
||||
scale.domain([
|
||||
moment('2019-10-27 02:00:00').valueOf(),
|
||||
moment('2019-10-27 05:00:00').valueOf(),
|
||||
]);
|
||||
|
||||
const tickFn = timeTicks(scale);
|
||||
const ticks = tickFn(5);
|
||||
|
||||
expect(ticks).toEqual([
|
||||
moment('2019-10-27 02:00:00').valueOf(),
|
||||
// this is the "first" 3 o'clock still in summer time
|
||||
moment('2019-10-27 03:00:00+02:00').valueOf(),
|
||||
moment('2019-10-27 03:00:00').valueOf(),
|
||||
moment('2019-10-27 04:00:00').valueOf(),
|
||||
moment('2019-10-27 05:00:00').valueOf(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should set nice daily ticks on dst switch summer to winter time', () => {
|
||||
moment.tz.setDefault('Europe/Berlin');
|
||||
|
||||
scale.domain([
|
||||
moment('2019-10-25 16:00:00').valueOf(),
|
||||
moment('2019-10-30 08:00:00').valueOf(),
|
||||
]);
|
||||
|
||||
const tickFn = timeTicks(scale);
|
||||
const ticks = tickFn(5);
|
||||
|
||||
expect(ticks).toEqual([
|
||||
moment('2019-10-26 00:00:00').valueOf(),
|
||||
moment('2019-10-27 00:00:00').valueOf(),
|
||||
moment('2019-10-28 00:00:00').valueOf(),
|
||||
moment('2019-10-29 00:00:00').valueOf(),
|
||||
moment('2019-10-30 00:00:00').valueOf(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should set nice daily ticks on dst switch winter to summer time', () => {
|
||||
moment.tz.setDefault('Europe/Berlin');
|
||||
|
||||
scale.domain([
|
||||
moment('2019-03-29 16:00:00').valueOf(),
|
||||
moment('2019-04-03 08:00:00').valueOf(),
|
||||
]);
|
||||
|
||||
const tickFn = timeTicks(scale);
|
||||
const ticks = tickFn(5);
|
||||
|
||||
expect(ticks).toEqual([
|
||||
moment('2019-03-30 00:00:00').valueOf(),
|
||||
moment('2019-03-31 00:00:00').valueOf(),
|
||||
moment('2019-04-01 00:00:00').valueOf(),
|
||||
moment('2019-04-02 00:00:00').valueOf(),
|
||||
moment('2019-04-03 00:00:00').valueOf(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should set nice monthly ticks on two dst switches from winter to winter time', () => {
|
||||
moment.tz.setDefault('Europe/Berlin');
|
||||
|
||||
scale.domain([
|
||||
moment('2019-03-29 00:00:00').valueOf(),
|
||||
moment('2019-11-01 00:00:00').valueOf(),
|
||||
]);
|
||||
|
||||
const tickFn = timeTicks(scale);
|
||||
const ticks = tickFn(8);
|
||||
|
||||
expect(ticks).toEqual([
|
||||
moment('2019-04-01 00:00:00').valueOf(),
|
||||
moment('2019-05-01 00:00:00').valueOf(),
|
||||
moment('2019-06-01 00:00:00').valueOf(),
|
||||
moment('2019-07-01 00:00:00').valueOf(),
|
||||
moment('2019-08-01 00:00:00').valueOf(),
|
||||
moment('2019-09-01 00:00:00').valueOf(),
|
||||
moment('2019-10-01 00:00:00').valueOf(),
|
||||
moment('2019-11-01 00:00:00').valueOf(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should set nice monthly ticks on two dst switches from summer to summer time', () => {
|
||||
moment.tz.setDefault('Europe/Berlin');
|
||||
|
||||
scale.domain([
|
||||
moment('2018-10-26 00:00:00').valueOf(),
|
||||
moment('2019-03-31 20:00:00').valueOf(),
|
||||
]);
|
||||
|
||||
const tickFn = timeTicks(scale);
|
||||
const ticks = tickFn(5);
|
||||
|
||||
expect(ticks).toEqual([
|
||||
moment('2018-11-01 00:00:00').valueOf(),
|
||||
moment('2018-12-01 00:00:00').valueOf(),
|
||||
moment('2019-01-01 00:00:00').valueOf(),
|
||||
moment('2019-02-01 00:00:00').valueOf(),
|
||||
moment('2019-03-01 00:00:00').valueOf(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue