[#359,view/flot][m]: much better time series support by using flot time plugin - fixes #359.
* Note you still need to set the type of the field to time for this to work properly (i.e. we do not attempt to guess a column is dates as that is very error-prone).
This commit is contained in:
@@ -29,6 +29,7 @@
|
|||||||
<script language="javascript" type="text/javascript" src="{{page.root}}vendor/flot/excanvas.min.js"></script>
|
<script language="javascript" type="text/javascript" src="{{page.root}}vendor/flot/excanvas.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
<script type="text/javascript" src="{{page.root}}vendor/flot/jquery.flot.js"></script>
|
<script type="text/javascript" src="{{page.root}}vendor/flot/jquery.flot.js"></script>
|
||||||
|
<script type="text/javascript" src="{{page.root}}vendor/flot/jquery.flot.time.js"></script>
|
||||||
<script type="text/javascript" src="{{page.root}}vendor/leaflet/0.4.4/leaflet.js"></script>
|
<script type="text/javascript" src="{{page.root}}vendor/leaflet/0.4.4/leaflet.js"></script>
|
||||||
<script type="text/javascript" src="{{page.root}}vendor/leaflet.markercluster/leaflet.markercluster.js"></script>
|
<script type="text/javascript" src="{{page.root}}vendor/leaflet.markercluster/leaflet.markercluster.js"></script>
|
||||||
<script type="text/javascript" src="{{page.root}}vendor/slickgrid/2.0.1/jquery-ui-1.8.16.custom.min.js"></script>
|
<script type="text/javascript" src="{{page.root}}vendor/slickgrid/2.0.1/jquery-ui-1.8.16.custom.min.js"></script>
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ library and the Recline Flot Graph view:
|
|||||||
<script language="javascript" type="text/javascript" src="vendor/flot/excanvas.min.js"></script>
|
<script language="javascript" type="text/javascript" src="vendor/flot/excanvas.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
<script type="text/javascript" src="vendor/flot/jquery.flot.js"></script>
|
<script type="text/javascript" src="vendor/flot/jquery.flot.js"></script>
|
||||||
|
<script type="text/javascript" src="vendor/flot/jquery.flot.time.js"></script>
|
||||||
<script type="text/javascript" src="src/view.graph.js"></script>
|
<script type="text/javascript" src="src/view.graph.js"></script>
|
||||||
{% endhighlight %}
|
{% endhighlight %}
|
||||||
|
|
||||||
|
|||||||
@@ -155,25 +155,17 @@ my.Flot = Backbone.View.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_xaxisLabel: function (x) {
|
_xaxisLabel: function (x) {
|
||||||
var xfield = this.model.fields.get(this.state.attributes.group);
|
if (this._groupFieldIsDateTime()) {
|
||||||
|
// oddly x comes through as milliseconds *string* (rather than int
|
||||||
// time series
|
// or float) so we have to reparse
|
||||||
var xtype = xfield.get('type');
|
x = new Date(parseFloat(x)).toLocaleDateString();
|
||||||
var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time');
|
} else if (this.xvaluesAreIndex) {
|
||||||
|
|
||||||
if (this.xvaluesAreIndex) {
|
|
||||||
x = parseInt(x, 10);
|
x = parseInt(x, 10);
|
||||||
// HACK: deal with bar graph style cases where x-axis items were strings
|
// HACK: deal with bar graph style cases where x-axis items were strings
|
||||||
// In this case x at this point is the index of the item in the list of
|
// In this case x at this point is the index of the item in the list of
|
||||||
// records not its actual x-axis value
|
// records not its actual x-axis value
|
||||||
x = this.model.records.models[x].get(this.state.attributes.group);
|
x = this.model.records.models[x].get(this.state.attributes.group);
|
||||||
}
|
}
|
||||||
if (isDateTime) {
|
|
||||||
x = new Date(x).toLocaleDateString();
|
|
||||||
}
|
|
||||||
// } else if (isDateTime) {
|
|
||||||
// x = new Date(parseInt(x, 10)).toLocaleDateString();
|
|
||||||
// }
|
|
||||||
|
|
||||||
return x;
|
return x;
|
||||||
},
|
},
|
||||||
@@ -188,25 +180,26 @@ my.Flot = Backbone.View.extend({
|
|||||||
// @param numPoints the number of points that will be plotted
|
// @param numPoints the number of points that will be plotted
|
||||||
getGraphOptions: function(typeId, numPoints) {
|
getGraphOptions: function(typeId, numPoints) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var groupFieldIsDateTime = self._groupFieldIsDateTime();
|
||||||
var tickFormatter = function (x) {
|
|
||||||
// convert x to a string and make sure that it is not too long or the
|
|
||||||
// tick labels will overlap
|
|
||||||
// TODO: find a more accurate way of calculating the size of tick labels
|
|
||||||
var label = self._xaxisLabel(x) || "";
|
|
||||||
|
|
||||||
if (typeof label !== 'string') {
|
|
||||||
label = label.toString();
|
|
||||||
}
|
|
||||||
if (self.state.attributes.graphType !== 'bars' && label.length > 10) {
|
|
||||||
label = label.slice(0, 10) + "...";
|
|
||||||
}
|
|
||||||
|
|
||||||
return label;
|
|
||||||
};
|
|
||||||
|
|
||||||
var xaxis = {};
|
var xaxis = {};
|
||||||
xaxis.tickFormatter = tickFormatter;
|
|
||||||
|
if (!groupFieldIsDateTime) {
|
||||||
|
xaxis.tickFormatter = function (x) {
|
||||||
|
// convert x to a string and make sure that it is not too long or the
|
||||||
|
// tick labels will overlap
|
||||||
|
// TODO: find a more accurate way of calculating the size of tick labels
|
||||||
|
var label = self._xaxisLabel(x) || "";
|
||||||
|
|
||||||
|
if (typeof label !== 'string') {
|
||||||
|
label = label.toString();
|
||||||
|
}
|
||||||
|
if (self.state.attributes.graphType !== 'bars' && label.length > 10) {
|
||||||
|
label = label.slice(0, 10) + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// for labels case we only want ticks at the label intervals
|
// for labels case we only want ticks at the label intervals
|
||||||
// HACK: however we also get this case with Date fields. In that case we
|
// HACK: however we also get this case with Date fields. In that case we
|
||||||
@@ -219,6 +212,8 @@ my.Flot = Backbone.View.extend({
|
|||||||
ticks.push(parseInt(i*increment, 10));
|
ticks.push(parseInt(i*increment, 10));
|
||||||
}
|
}
|
||||||
xaxis.ticks = ticks;
|
xaxis.ticks = ticks;
|
||||||
|
} else if (groupFieldIsDateTime) {
|
||||||
|
xaxis.mode = 'time';
|
||||||
}
|
}
|
||||||
|
|
||||||
var yaxis = {};
|
var yaxis = {};
|
||||||
@@ -300,24 +295,31 @@ my.Flot = Backbone.View.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_groupFieldIsDateTime: function() {
|
||||||
|
var xfield = this.model.fields.get(this.state.attributes.group);
|
||||||
|
var xtype = xfield.get('type');
|
||||||
|
var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time');
|
||||||
|
return isDateTime;
|
||||||
|
},
|
||||||
|
|
||||||
createSeries: function() {
|
createSeries: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
self.xvaluesAreIndex = false;
|
self.xvaluesAreIndex = false;
|
||||||
var series = [];
|
var series = [];
|
||||||
|
var xfield = self.model.fields.get(self.state.attributes.group);
|
||||||
|
var isDateTime = self._groupFieldIsDateTime();
|
||||||
|
|
||||||
_.each(this.state.attributes.series, function(field) {
|
_.each(this.state.attributes.series, function(field) {
|
||||||
var points = [];
|
var points = [];
|
||||||
var fieldLabel = self.model.fields.get(field).get('label');
|
var fieldLabel = self.model.fields.get(field).get('label');
|
||||||
_.each(self.model.records.models, function(doc, index) {
|
_.each(self.model.records.models, function(doc, index) {
|
||||||
var xfield = self.model.fields.get(self.state.attributes.group);
|
|
||||||
var x = doc.getFieldValue(xfield);
|
var x = doc.getFieldValue(xfield);
|
||||||
|
|
||||||
// time series
|
|
||||||
var xtype = xfield.get('type');
|
|
||||||
var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time');
|
|
||||||
|
|
||||||
if (isDateTime) {
|
if (isDateTime) {
|
||||||
self.xvaluesAreIndex = true;
|
var _date = moment(x);
|
||||||
x = index;
|
if (_date.isValid()) {
|
||||||
|
x = _date.toDate().getTime();
|
||||||
|
}
|
||||||
} else if (typeof x === 'string') {
|
} else if (typeof x === 'string') {
|
||||||
x = parseFloat(x);
|
x = parseFloat(x);
|
||||||
if (isNaN(x)) { // assume this is a string label
|
if (isNaN(x)) { // assume this is a string label
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<title>Qunit Tests</title>
|
<title>Qunit Tests</title>
|
||||||
<link rel="stylesheet" href="qunit/qunit.css" type="text/css" media="screen" />
|
<link rel="stylesheet" href="qunit/qunit.css" type="text/css" media="screen" />
|
||||||
<link rel="stylesheet" href="../vendor/timeline/20120520/css/timeline.css" type="text/css" media="screen" />
|
<link rel="stylesheet" href="../vendor/timeline/20120520/css/timeline.css" type="text/css" media="screen" />
|
||||||
|
<link rel="stylesheet" href="../css/flot.css">
|
||||||
<link rel="stylesheet" href="../vendor/leaflet/0.4.4/leaflet.css">
|
<link rel="stylesheet" href="../vendor/leaflet/0.4.4/leaflet.css">
|
||||||
<link rel="stylesheet" href="../css/map.css">
|
<link rel="stylesheet" href="../css/map.css">
|
||||||
|
|
||||||
@@ -14,6 +15,8 @@
|
|||||||
<script type="text/javascript" src="../vendor/moment/2.0.0/moment.js"></script>
|
<script type="text/javascript" src="../vendor/moment/2.0.0/moment.js"></script>
|
||||||
<script type="text/javascript" src="../vendor/mustache/0.5.0-dev/mustache.js"></script>
|
<script type="text/javascript" src="../vendor/mustache/0.5.0-dev/mustache.js"></script>
|
||||||
<script type="text/javascript" src="../vendor/bootstrap/2.0.2/bootstrap.js"></script>
|
<script type="text/javascript" src="../vendor/bootstrap/2.0.2/bootstrap.js"></script>
|
||||||
|
<script type="text/javascript" src="../vendor/flot/jquery.flot.js"></script>
|
||||||
|
<script type="text/javascript" src="../vendor/flot/jquery.flot.time.js"></script>
|
||||||
<script type="text/javascript" src="../vendor/leaflet/0.4.4/leaflet-src.js"></script>
|
<script type="text/javascript" src="../vendor/leaflet/0.4.4/leaflet-src.js"></script>
|
||||||
<script type="text/javascript" src="../vendor/leaflet.markercluster/leaflet.markercluster.js"></script>
|
<script type="text/javascript" src="../vendor/leaflet.markercluster/leaflet.markercluster.js"></script>
|
||||||
<script type="text/javascript" src="../vendor/slickgrid/2.0.1/jquery-ui-1.8.16.custom.min.js"></script>
|
<script type="text/javascript" src="../vendor/slickgrid/2.0.1/jquery-ui-1.8.16.custom.min.js"></script>
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ test('dates in graph view', function () {
|
|||||||
'series': ['y', 'z']
|
'series': ['y', 'z']
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
view.render();
|
||||||
$('.fixtures').append(view.el);
|
$('.fixtures').append(view.el);
|
||||||
|
view.redraw();
|
||||||
|
|
||||||
view.remove();
|
view.remove();
|
||||||
});
|
});
|
||||||
|
|||||||
1111
vendor/flot/jquery.flot.js
vendored
1111
vendor/flot/jquery.flot.js
vendored
File diff suppressed because it is too large
Load Diff
431
vendor/flot/jquery.flot.time.js
vendored
Normal file
431
vendor/flot/jquery.flot.time.js
vendored
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
/* Pretty handling of time axes.
|
||||||
|
|
||||||
|
Copyright (c) 2007-2013 IOLA and Ole Laursen.
|
||||||
|
Licensed under the MIT license.
|
||||||
|
|
||||||
|
Set axis.mode to "time" to enable. See the section "Time series data" in
|
||||||
|
API.txt for details.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function($) {
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
xaxis: {
|
||||||
|
timezone: null, // "browser" for local to the client or timezone for timezone-js
|
||||||
|
timeformat: null, // format string to use
|
||||||
|
twelveHourClock: false, // 12 or 24 time in time mode
|
||||||
|
monthNames: null // list of names of months
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// round to nearby lower multiple of base
|
||||||
|
|
||||||
|
function floorInBase(n, base) {
|
||||||
|
return base * Math.floor(n / base);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a string with the date d formatted according to fmt.
|
||||||
|
// A subset of the Open Group's strftime format is supported.
|
||||||
|
|
||||||
|
function formatDate(d, fmt, monthNames, dayNames) {
|
||||||
|
|
||||||
|
if (typeof d.strftime == "function") {
|
||||||
|
return d.strftime(fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
var leftPad = function(n, pad) {
|
||||||
|
n = "" + n;
|
||||||
|
pad = "" + (pad == null ? "0" : pad);
|
||||||
|
return n.length == 1 ? pad + n : n;
|
||||||
|
};
|
||||||
|
|
||||||
|
var r = [];
|
||||||
|
var escape = false;
|
||||||
|
var hours = d.getHours();
|
||||||
|
var isAM = hours < 12;
|
||||||
|
|
||||||
|
if (monthNames == null) {
|
||||||
|
monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dayNames == null) {
|
||||||
|
dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
|
}
|
||||||
|
|
||||||
|
var hours12;
|
||||||
|
|
||||||
|
if (hours > 12) {
|
||||||
|
hours12 = hours - 12;
|
||||||
|
} else if (hours == 0) {
|
||||||
|
hours12 = 12;
|
||||||
|
} else {
|
||||||
|
hours12 = hours;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < fmt.length; ++i) {
|
||||||
|
|
||||||
|
var c = fmt.charAt(i);
|
||||||
|
|
||||||
|
if (escape) {
|
||||||
|
switch (c) {
|
||||||
|
case 'a': c = "" + dayNames[d.getDay()]; break;
|
||||||
|
case 'b': c = "" + monthNames[d.getMonth()]; break;
|
||||||
|
case 'd': c = leftPad(d.getDate()); break;
|
||||||
|
case 'e': c = leftPad(d.getDate(), " "); break;
|
||||||
|
case 'h': // For back-compat with 0.7; remove in 1.0
|
||||||
|
case 'H': c = leftPad(hours); break;
|
||||||
|
case 'I': c = leftPad(hours12); break;
|
||||||
|
case 'l': c = leftPad(hours12, " "); break;
|
||||||
|
case 'm': c = leftPad(d.getMonth() + 1); break;
|
||||||
|
case 'M': c = leftPad(d.getMinutes()); break;
|
||||||
|
// quarters not in Open Group's strftime specification
|
||||||
|
case 'q':
|
||||||
|
c = "" + (Math.floor(d.getMonth() / 3) + 1); break;
|
||||||
|
case 'S': c = leftPad(d.getSeconds()); break;
|
||||||
|
case 'y': c = leftPad(d.getFullYear() % 100); break;
|
||||||
|
case 'Y': c = "" + d.getFullYear(); break;
|
||||||
|
case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
|
||||||
|
case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
|
||||||
|
case 'w': c = "" + d.getDay(); break;
|
||||||
|
}
|
||||||
|
r.push(c);
|
||||||
|
escape = false;
|
||||||
|
} else {
|
||||||
|
if (c == "%") {
|
||||||
|
escape = true;
|
||||||
|
} else {
|
||||||
|
r.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// To have a consistent view of time-based data independent of which time
|
||||||
|
// zone the client happens to be in we need a date-like object independent
|
||||||
|
// of time zones. This is done through a wrapper that only calls the UTC
|
||||||
|
// versions of the accessor methods.
|
||||||
|
|
||||||
|
function makeUtcWrapper(d) {
|
||||||
|
|
||||||
|
function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
|
||||||
|
sourceObj[sourceMethod] = function() {
|
||||||
|
return targetObj[targetMethod].apply(targetObj, arguments);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
var utc = {
|
||||||
|
date: d
|
||||||
|
};
|
||||||
|
|
||||||
|
// support strftime, if found
|
||||||
|
|
||||||
|
if (d.strftime != undefined) {
|
||||||
|
addProxyMethod(utc, "strftime", d, "strftime");
|
||||||
|
}
|
||||||
|
|
||||||
|
addProxyMethod(utc, "getTime", d, "getTime");
|
||||||
|
addProxyMethod(utc, "setTime", d, "setTime");
|
||||||
|
|
||||||
|
var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"];
|
||||||
|
|
||||||
|
for (var p = 0; p < props.length; p++) {
|
||||||
|
addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
|
||||||
|
addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return utc;
|
||||||
|
};
|
||||||
|
|
||||||
|
// select time zone strategy. This returns a date-like object tied to the
|
||||||
|
// desired timezone
|
||||||
|
|
||||||
|
function dateGenerator(ts, opts) {
|
||||||
|
if (opts.timezone == "browser") {
|
||||||
|
return new Date(ts);
|
||||||
|
} else if (!opts.timezone || opts.timezone == "utc") {
|
||||||
|
return makeUtcWrapper(new Date(ts));
|
||||||
|
} else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") {
|
||||||
|
var d = new timezoneJS.Date();
|
||||||
|
// timezone-js is fickle, so be sure to set the time zone before
|
||||||
|
// setting the time.
|
||||||
|
d.setTimezone(opts.timezone);
|
||||||
|
d.setTime(ts);
|
||||||
|
return d;
|
||||||
|
} else {
|
||||||
|
return makeUtcWrapper(new Date(ts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// map of app. size of time units in milliseconds
|
||||||
|
|
||||||
|
var timeUnitSize = {
|
||||||
|
"second": 1000,
|
||||||
|
"minute": 60 * 1000,
|
||||||
|
"hour": 60 * 60 * 1000,
|
||||||
|
"day": 24 * 60 * 60 * 1000,
|
||||||
|
"month": 30 * 24 * 60 * 60 * 1000,
|
||||||
|
"quarter": 3 * 30 * 24 * 60 * 60 * 1000,
|
||||||
|
"year": 365.2425 * 24 * 60 * 60 * 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
// the allowed tick sizes, after 1 year we use
|
||||||
|
// an integer algorithm
|
||||||
|
|
||||||
|
var baseSpec = [
|
||||||
|
[1, "second"], [2, "second"], [5, "second"], [10, "second"],
|
||||||
|
[30, "second"],
|
||||||
|
[1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
|
||||||
|
[30, "minute"],
|
||||||
|
[1, "hour"], [2, "hour"], [4, "hour"],
|
||||||
|
[8, "hour"], [12, "hour"],
|
||||||
|
[1, "day"], [2, "day"], [3, "day"],
|
||||||
|
[0.25, "month"], [0.5, "month"], [1, "month"],
|
||||||
|
[2, "month"]
|
||||||
|
];
|
||||||
|
|
||||||
|
// we don't know which variant(s) we'll need yet, but generating both is
|
||||||
|
// cheap
|
||||||
|
|
||||||
|
var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
|
||||||
|
[1, "year"]]);
|
||||||
|
var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
|
||||||
|
[1, "year"]]);
|
||||||
|
|
||||||
|
function init(plot) {
|
||||||
|
plot.hooks.processOptions.push(function (plot, options) {
|
||||||
|
$.each(plot.getAxes(), function(axisName, axis) {
|
||||||
|
|
||||||
|
var opts = axis.options;
|
||||||
|
|
||||||
|
if (opts.mode == "time") {
|
||||||
|
axis.tickGenerator = function(axis) {
|
||||||
|
|
||||||
|
var ticks = [];
|
||||||
|
var d = dateGenerator(axis.min, opts);
|
||||||
|
var minSize = 0;
|
||||||
|
|
||||||
|
// make quarter use a possibility if quarters are
|
||||||
|
// mentioned in either of these options
|
||||||
|
|
||||||
|
var spec = (opts.tickSize && opts.tickSize[1] ===
|
||||||
|
"quarter") ||
|
||||||
|
(opts.minTickSize && opts.minTickSize[1] ===
|
||||||
|
"quarter") ? specQuarters : specMonths;
|
||||||
|
|
||||||
|
if (opts.minTickSize != null) {
|
||||||
|
if (typeof opts.tickSize == "number") {
|
||||||
|
minSize = opts.tickSize;
|
||||||
|
} else {
|
||||||
|
minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < spec.length - 1; ++i) {
|
||||||
|
if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]]
|
||||||
|
+ spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
|
||||||
|
&& spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = spec[i][0];
|
||||||
|
var unit = spec[i][1];
|
||||||
|
|
||||||
|
// special-case the possibility of several years
|
||||||
|
|
||||||
|
if (unit == "year") {
|
||||||
|
|
||||||
|
// if given a minTickSize in years, just use it,
|
||||||
|
// ensuring that it's an integer
|
||||||
|
|
||||||
|
if (opts.minTickSize != null && opts.minTickSize[1] == "year") {
|
||||||
|
size = Math.floor(opts.minTickSize[0]);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
|
||||||
|
var norm = (axis.delta / timeUnitSize.year) / magn;
|
||||||
|
|
||||||
|
if (norm < 1.5) {
|
||||||
|
size = 1;
|
||||||
|
} else if (norm < 3) {
|
||||||
|
size = 2;
|
||||||
|
} else if (norm < 7.5) {
|
||||||
|
size = 5;
|
||||||
|
} else {
|
||||||
|
size = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
size *= magn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// minimum size for years is 1
|
||||||
|
|
||||||
|
if (size < 1) {
|
||||||
|
size = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
axis.tickSize = opts.tickSize || [size, unit];
|
||||||
|
var tickSize = axis.tickSize[0];
|
||||||
|
unit = axis.tickSize[1];
|
||||||
|
|
||||||
|
var step = tickSize * timeUnitSize[unit];
|
||||||
|
|
||||||
|
if (unit == "second") {
|
||||||
|
d.setSeconds(floorInBase(d.getSeconds(), tickSize));
|
||||||
|
} else if (unit == "minute") {
|
||||||
|
d.setMinutes(floorInBase(d.getMinutes(), tickSize));
|
||||||
|
} else if (unit == "hour") {
|
||||||
|
d.setHours(floorInBase(d.getHours(), tickSize));
|
||||||
|
} else if (unit == "month") {
|
||||||
|
d.setMonth(floorInBase(d.getMonth(), tickSize));
|
||||||
|
} else if (unit == "quarter") {
|
||||||
|
d.setMonth(3 * floorInBase(d.getMonth() / 3,
|
||||||
|
tickSize));
|
||||||
|
} else if (unit == "year") {
|
||||||
|
d.setFullYear(floorInBase(d.getFullYear(), tickSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset smaller components
|
||||||
|
|
||||||
|
d.setMilliseconds(0);
|
||||||
|
|
||||||
|
if (step >= timeUnitSize.minute) {
|
||||||
|
d.setSeconds(0);
|
||||||
|
}
|
||||||
|
if (step >= timeUnitSize.hour) {
|
||||||
|
d.setMinutes(0);
|
||||||
|
}
|
||||||
|
if (step >= timeUnitSize.day) {
|
||||||
|
d.setHours(0);
|
||||||
|
}
|
||||||
|
if (step >= timeUnitSize.day * 4) {
|
||||||
|
d.setDate(1);
|
||||||
|
}
|
||||||
|
if (step >= timeUnitSize.month * 2) {
|
||||||
|
d.setMonth(floorInBase(d.getMonth(), 3));
|
||||||
|
}
|
||||||
|
if (step >= timeUnitSize.quarter * 2) {
|
||||||
|
d.setMonth(floorInBase(d.getMonth(), 6));
|
||||||
|
}
|
||||||
|
if (step >= timeUnitSize.year) {
|
||||||
|
d.setMonth(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var carry = 0;
|
||||||
|
var v = Number.NaN;
|
||||||
|
var prev;
|
||||||
|
|
||||||
|
do {
|
||||||
|
|
||||||
|
prev = v;
|
||||||
|
v = d.getTime();
|
||||||
|
ticks.push(v);
|
||||||
|
|
||||||
|
if (unit == "month" || unit == "quarter") {
|
||||||
|
if (tickSize < 1) {
|
||||||
|
|
||||||
|
// a bit complicated - we'll divide the
|
||||||
|
// month/quarter up but we need to take
|
||||||
|
// care of fractions so we don't end up in
|
||||||
|
// the middle of a day
|
||||||
|
|
||||||
|
d.setDate(1);
|
||||||
|
var start = d.getTime();
|
||||||
|
d.setMonth(d.getMonth() +
|
||||||
|
(unit == "quarter" ? 3 : 1));
|
||||||
|
var end = d.getTime();
|
||||||
|
d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
|
||||||
|
carry = d.getHours();
|
||||||
|
d.setHours(0);
|
||||||
|
} else {
|
||||||
|
d.setMonth(d.getMonth() +
|
||||||
|
tickSize * (unit == "quarter" ? 3 : 1));
|
||||||
|
}
|
||||||
|
} else if (unit == "year") {
|
||||||
|
d.setFullYear(d.getFullYear() + tickSize);
|
||||||
|
} else {
|
||||||
|
d.setTime(v + step);
|
||||||
|
}
|
||||||
|
} while (v < axis.max && v != prev);
|
||||||
|
|
||||||
|
return ticks;
|
||||||
|
};
|
||||||
|
|
||||||
|
axis.tickFormatter = function (v, axis) {
|
||||||
|
|
||||||
|
var d = dateGenerator(v, axis.options);
|
||||||
|
|
||||||
|
// first check global format
|
||||||
|
|
||||||
|
if (opts.timeformat != null) {
|
||||||
|
return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
// possibly use quarters if quarters are mentioned in
|
||||||
|
// any of these places
|
||||||
|
|
||||||
|
var useQuarters = (axis.options.tickSize &&
|
||||||
|
axis.options.tickSize[1] == "quarter") ||
|
||||||
|
(axis.options.minTickSize &&
|
||||||
|
axis.options.minTickSize[1] == "quarter");
|
||||||
|
|
||||||
|
var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
|
||||||
|
var span = axis.max - axis.min;
|
||||||
|
var suffix = (opts.twelveHourClock) ? " %p" : "";
|
||||||
|
var hourCode = (opts.twelveHourClock) ? "%I" : "%H";
|
||||||
|
var fmt;
|
||||||
|
|
||||||
|
if (t < timeUnitSize.minute) {
|
||||||
|
fmt = hourCode + ":%M:%S" + suffix;
|
||||||
|
} else if (t < timeUnitSize.day) {
|
||||||
|
if (span < 2 * timeUnitSize.day) {
|
||||||
|
fmt = hourCode + ":%M" + suffix;
|
||||||
|
} else {
|
||||||
|
fmt = "%b %d " + hourCode + ":%M" + suffix;
|
||||||
|
}
|
||||||
|
} else if (t < timeUnitSize.month) {
|
||||||
|
fmt = "%b %d";
|
||||||
|
} else if ((useQuarters && t < timeUnitSize.quarter) ||
|
||||||
|
(!useQuarters && t < timeUnitSize.year)) {
|
||||||
|
if (span < timeUnitSize.year) {
|
||||||
|
fmt = "%b";
|
||||||
|
} else {
|
||||||
|
fmt = "%b %Y";
|
||||||
|
}
|
||||||
|
} else if (useQuarters && t < timeUnitSize.year) {
|
||||||
|
if (span < timeUnitSize.year) {
|
||||||
|
fmt = "Q%q";
|
||||||
|
} else {
|
||||||
|
fmt = "Q%q %Y";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt = "%Y";
|
||||||
|
}
|
||||||
|
|
||||||
|
var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);
|
||||||
|
|
||||||
|
return rt;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$.plot.plugins.push({
|
||||||
|
init: init,
|
||||||
|
options: options,
|
||||||
|
name: 'time',
|
||||||
|
version: '1.0'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Time-axis support used to be in Flot core, which exposed the
|
||||||
|
// formatDate function on the plot object. Various plugins depend
|
||||||
|
// on the function, so we need to re-expose it here.
|
||||||
|
|
||||||
|
$.plot.formatDate = formatDate;
|
||||||
|
|
||||||
|
})(jQuery);
|
||||||
Reference in New Issue
Block a user