blob: 02a5ad45958fe3b7d8c33a3d2b63557749059f0e [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.locale;
class TimeScale extends LinearScale {
static List _scaleSteps = [
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 List _scaleLocalMethods = [
[chartTime.Time.second, 1],
[chartTime.Time.second, 5],
[chartTime.Time.second, 15],
[chartTime.Time.second, 30],
[chartTime.Time.minute, 1],
[chartTime.Time.minute, 5],
[chartTime.Time.minute, 15],
[chartTime.Time.minute, 30],
[chartTime.Time.hour, 1],
[chartTime.Time.hour, 3],
[chartTime.Time.hour, 6],
[chartTime.Time.hour, 12],
[chartTime.Time.day, 1],
[chartTime.Time.day, 2],
[chartTime.Time.week, 1],
[chartTime.Time.month, 1],
[chartTime.Time.month, 3],
[chartTime.Time.year, 1]
];
static TimeFormatFunction _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([List domain = LinearScale.defaultDomainRange,
List range = LinearScale.defaultDomainRange,
interpolators.Interpolator interpolator = interpolators.interpolateNumber,
bool clamp = false]) : super(domain, range, interpolator, clamp);
DateTime _timeScaleDate(num t) {
return new DateTime.fromMillisecondsSinceEpoch(t);
}
List _tickMethod(Extent extent, int count) {
var span = extent.max - extent.min,
target = span / count,
i = ScaleUtil.bisect(_scaleSteps, target);
return i == _scaleSteps.length ?
[chartTime.Time.year, linearTickRange(
[extent.min / 31536e6, extent.max / 31536e6], count)[2]] :
i == 0 ? [new ScaleMilliSeconds(),
linearTickRange([extent.min, extent.max], count)[2]] :
_scaleLocalMethods[target / _scaleSteps[i - 1] <
_scaleSteps[i] / target ? i - 1 : i];
}
/**
* Given a value x as DateTime or TimeStamp, returns the corresponding value
* in the output range.
*/
apply(x){
return super.apply(x is DateTime ? x.millisecondsSinceEpoch: x);
}
/**
* Returns the value in the input domain x for the corresponding value in the
* output range y. This represents the inverse mapping from range to domain.
* If elements in range are not number, the invert function returns null.
*/
invert(y) {
return super.invert(y);
}
/** Sets the domain of the scale. */
set domain(List newDomain) {
assert(newDomain.length > 1);
super.domain = newDomain.map(
(d) => d is DateTime ? d.millisecondsSinceEpoch : d).toList();
}
Function tickFormat(int ticks, [String format = null]) {
return _scaleLocalFormat;
}
/**
* Returns an exact copy of this time scale. Changes to this scale will not
* affect the returned scale, and vice versa.
**/
TimeScale copy() => new TimeScale(domain, range, interpolator, clamp);
List niceInterval(var interval, [int skip = 1]) {
var extent = _scaleDomainExtent();
var method = interval == null ? _tickMethod(extent, 10) :
interval is int ? _tickMethod(extent, interval) : null;
if (method != null) {
interval = method[0];
skip = method[1];
}
bool skipped(var date) {
if (date is DateTime) date = date.millisecondsSinceEpoch;
return (interval as chartTime.Interval)
.range(date, date + 1, skip).length == 0;
}
if (skip > 1) {
domain = scaleNice(domain,
(date) {
while (skipped(date = (interval as chartTime.Interval).floor(date))) {
date = _timeScaleDate(date.millisecondsSinceEpoch - 1);
}
return date.millisecondsSinceEpoch;
},
(date) {
while (skipped(date = (interval as chartTime.Interval).ceil(date))) {
date = _timeScaleDate(date.millisecondsSinceEpoch + 1);
}
return date.millisecondsSinceEpoch;
}
);
} else {
domain = scaleNice(domain,
(date) => interval.floor(date).millisecondsSinceEpoch,
(date) => interval.ceil(date).millisecondsSinceEpoch
);
}
return domain;
}
void nice([int ticks]) {
domain = niceInterval(ticks);
}
Extent _scaleDomainExtent() {
var extent = scaleExtent(domain);
return new Extent(extent[0], extent[1]);
}
List ticksInterval(var interval, [int skip = 1]) {
var extent = _scaleDomainExtent();
var method = interval == null ? _tickMethod(extent, 10) :
interval is int ? _tickMethod(extent, interval) :
[interval, skip];
if (method != null) {
interval = method[0];
skip = method[1];
}
return interval.range(extent.min, extent.max + 1, skip < 1 ? 1 : skip);
}
List ticks([int ticks = 10]) {
return ticksInterval(ticks);
}
}
class ScaleMilliSeconds extends chartTime.Interval {
DateTime floor(var date) =>
date is num ? new DateTime.fromMillisecondsSinceEpoch(date) : date;
DateTime ceil(var date) =>
date is num ? new DateTime.fromMillisecondsSinceEpoch(date) : date;
List range(var t0, var t1, int step) {
int start = t0 is DateTime ? t0.millisecondsSinceEpoch : t0,
stop = t1 is DateTime ? t1.millisecondsSinceEpoch : t1;
return new Range((start / step).ceil() * step, stop, step).map(
(d) => new DateTime.fromMillisecondsSinceEpoch(d)).toList();
}
}