blob: cc7785ec04b1a909da6a9d2a977973f955a4da56 [file] [log] [blame]
//
// Copyright 2014 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
//
part of charted.core.scales;
/// TimeScale is a linear scale that operates on time.
class TimeScale extends BaseLinearScale {
static const _scaleSteps = const [
1e3, // 1-second
5e3, // 5-second
15e3, // 15-second
3e4, // 30-second
6e4, // 1-minute
3e5, // 5-minute
9e5, // 15-minute
18e5, // 30-minute
36e5, // 1-hour
108e5, // 3-hour
216e5, // 6-hour
432e5, // 12-hour
864e5, // 1-day
1728e5, // 2-day
6048e5, // 1-week
2592e6, // 1-month
7776e6, // 3-month
31536e6 // 1-year
];
static final _scaleLocalMethods = [
[TimeInterval.second, 1],
[TimeInterval.second, 5],
[TimeInterval.second, 15],
[TimeInterval.second, 30],
[TimeInterval.minute, 1],
[TimeInterval.minute, 5],
[TimeInterval.minute, 15],
[TimeInterval.minute, 30],
[TimeInterval.hour, 1],
[TimeInterval.hour, 3],
[TimeInterval.hour, 6],
[TimeInterval.hour, 12],
[TimeInterval.day, 1],
[TimeInterval.day, 2],
[TimeInterval.week, 1],
[TimeInterval.month, 1],
[TimeInterval.month, 3],
[TimeInterval.year, 1]
];
static final FormatFunction _scaleLocalFormat = new TimeFormat().multi([
[".%L", (DateTime d) => d.millisecond > 0],
[":%S", (DateTime d) => d.second > 0],
["%I:%M", (DateTime d) => d.minute > 0],
["%I %p", (DateTime d) => d.hour > 0],
["%a %d", (DateTime d) => (d.weekday % 7) > 0 && d.day != 1],
["%b %d", (DateTime d) => d.day != 1],
["%B", (DateTime d) => d.month > 1],
["%Y", (d) => true]
]);
TimeScale();
TimeScale._clone(TimeScale source) : super._clone(source);
@override
num scale(Object val) =>
super.scale(val is DateTime ? val.millisecondsSinceEpoch : val);
@override
set domain(Iterable<dynamic> value) {
super.domain = value
.map<num>((d) => d is DateTime ? d.millisecondsSinceEpoch : d as num)
.toList();
}
@override
FormatFunction createTickFormatter([String format]) => _scaleLocalFormat;
@override
TimeScale clone() => new TimeScale._clone(this);
List<dynamic> _getTickMethod(Extent<num> extent, int count) {
num target = (extent.max - extent.min) / count;
int i = ScaleUtils.bisect(_scaleSteps, target);
return i == _scaleSteps.length
? [
TimeInterval.year,
_linearTickRange(
new Extent<num>(extent.min / 31536e6, extent.max / 31536e6))
.step
]
: i == 0
? [new ScaleMilliSeconds(), _linearTickRange(extent).step]
: _scaleLocalMethods[
target / _scaleSteps[i - 1] < _scaleSteps[i] / target
? i - 1
: i];
}
List<num> niceInterval(int ticksCount) {
var extent = ScaleUtils.extent(domain);
var method = _getTickMethod(extent, ticksCount);
TimeInterval interval = method[0];
int skip = method[1];
bool skipped(DateTime date) {
var seconds = date.millisecondsSinceEpoch;
return interval.range(seconds, seconds + 1, skip).length == 0;
}
if (skip > 1) {
domain = ScaleUtils.nice(
domain as List<num>,
new RoundingFunctions((dateMillis) {
var date =
new DateTime.fromMillisecondsSinceEpoch(dateMillis.round());
while (skipped(date = interval.floor(date))) {
date = new DateTime.fromMillisecondsSinceEpoch(
date.millisecondsSinceEpoch - 1);
}
return date.millisecondsSinceEpoch;
}, (dateMillis) {
var date =
new DateTime.fromMillisecondsSinceEpoch(dateMillis.round());
while (skipped(date = interval.ceil(date))) {
date = new DateTime.fromMillisecondsSinceEpoch(
date.millisecondsSinceEpoch + 1);
}
return date.millisecondsSinceEpoch;
}));
} else {
domain = ScaleUtils.nice(
domain as List<num>,
new RoundingFunctions(
(date) => interval.floor(date).millisecondsSinceEpoch,
(date) => interval.ceil(date).millisecondsSinceEpoch));
}
return domain;
}
@override
set nice(bool value) {
assert(value != null);
if (value != null && _nice != value) {
_nice = value;
domain = niceInterval(_ticksCount);
}
}
List<DateTime> ticksInterval(int ticksCount) {
var extent = ScaleUtils.extent(domain);
var method = _getTickMethod(extent, ticksCount);
TimeInterval interval = method[0];
int skip = method[1];
return interval
.range(extent.min, extent.max + 1, skip < 1 ? 1 : skip)
.toList();
}
@override
List<DateTime> get ticks => ticksInterval(ticksCount);
}
class ScaleMilliSeconds implements TimeInterval {
DateTime floor(dynamic val) => _toDateTime(val);
DateTime ceil(dynamic val) => _toDateTime(val);
DateTime round(dynamic val) => _toDateTime(val);
DateTime offset(Object val, int dt) {
return new DateTime.fromMillisecondsSinceEpoch(_toMilliseconds(val) + dt);
}
List<DateTime> range(dynamic t0, dynamic t1, int step) {
int start = _toMilliseconds(t0);
int stop = _toMilliseconds(t1);
return new Range((start / step).ceil() * step, stop, step)
.map((d) => new DateTime.fromMillisecondsSinceEpoch(d as int))
.toList();
}
static DateTime _toDateTime(/* int | DateTime */ dynamic x) {
return x is int
? new DateTime.fromMillisecondsSinceEpoch(x)
: x as DateTime;
}
static int _toMilliseconds(/* int | DateTime */ dynamic val) {
return val is int ? val : (val as DateTime).millisecondsSinceEpoch;
}
}