You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1035 lines
29 KiB
JavaScript
1035 lines
29 KiB
JavaScript
import ClockRange from "../../Core/ClockRange.js";
|
|
import defined from "../../Core/defined.js";
|
|
import destroyObject from "../../Core/destroyObject.js";
|
|
import DeveloperError from "../../Core/DeveloperError.js";
|
|
import JulianDate from "../../Core/JulianDate.js";
|
|
import getElement from "../getElement.js";
|
|
import TimelineHighlightRange from "./TimelineHighlightRange.js";
|
|
import TimelineTrack from "./TimelineTrack.js";
|
|
|
|
var timelineWheelDelta = 1e12;
|
|
|
|
var timelineMouseMode = {
|
|
none: 0,
|
|
scrub: 1,
|
|
slide: 2,
|
|
zoom: 3,
|
|
touchOnly: 4,
|
|
};
|
|
var timelineTouchMode = {
|
|
none: 0,
|
|
scrub: 1,
|
|
slideZoom: 2,
|
|
singleTap: 3,
|
|
ignore: 4,
|
|
};
|
|
|
|
var timelineTicScales = [
|
|
0.001,
|
|
0.002,
|
|
0.005,
|
|
0.01,
|
|
0.02,
|
|
0.05,
|
|
0.1,
|
|
0.25,
|
|
0.5,
|
|
1.0,
|
|
2.0,
|
|
5.0,
|
|
10.0,
|
|
15.0,
|
|
30.0,
|
|
60.0, // 1min
|
|
120.0, // 2min
|
|
300.0, // 5min
|
|
600.0, // 10min
|
|
900.0, // 15min
|
|
1800.0, // 30min
|
|
3600.0, // 1hr
|
|
7200.0, // 2hr
|
|
14400.0, // 4hr
|
|
21600.0, // 6hr
|
|
43200.0, // 12hr
|
|
86400.0, // 24hr
|
|
172800.0, // 2days
|
|
345600.0, // 4days
|
|
604800.0, // 7days
|
|
1296000.0, // 15days
|
|
2592000.0, // 30days
|
|
5184000.0, // 60days
|
|
7776000.0, // 90days
|
|
15552000.0, // 180days
|
|
31536000.0, // 365days
|
|
63072000.0, // 2years
|
|
126144000.0, // 4years
|
|
157680000.0, // 5years
|
|
315360000.0, // 10years
|
|
630720000.0, // 20years
|
|
1261440000.0, // 40years
|
|
1576800000.0, // 50years
|
|
3153600000.0, // 100years
|
|
6307200000.0, // 200years
|
|
12614400000.0, // 400years
|
|
15768000000.0, // 500years
|
|
31536000000.0, // 1000years
|
|
];
|
|
|
|
var timelineMonthNames = [
|
|
"Jan",
|
|
"Feb",
|
|
"Mar",
|
|
"Apr",
|
|
"May",
|
|
"Jun",
|
|
"Jul",
|
|
"Aug",
|
|
"Sep",
|
|
"Oct",
|
|
"Nov",
|
|
"Dec",
|
|
];
|
|
|
|
/**
|
|
* The Timeline is a widget for displaying and controlling the current scene time.
|
|
* @alias Timeline
|
|
* @constructor
|
|
*
|
|
* @param {Element} container The parent HTML container node for this widget.
|
|
* @param {Clock} clock The clock to use.
|
|
*/
|
|
function Timeline(container, clock) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (!defined(container)) {
|
|
throw new DeveloperError("container is required.");
|
|
}
|
|
if (!defined(clock)) {
|
|
throw new DeveloperError("clock is required.");
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
container = getElement(container);
|
|
|
|
var ownerDocument = container.ownerDocument;
|
|
|
|
/**
|
|
* Gets the parent container.
|
|
* @type {Element}
|
|
*/
|
|
this.container = container;
|
|
|
|
var topDiv = ownerDocument.createElement("div");
|
|
topDiv.className = "cesium-timeline-main";
|
|
container.appendChild(topDiv);
|
|
this._topDiv = topDiv;
|
|
|
|
this._endJulian = undefined;
|
|
this._epochJulian = undefined;
|
|
this._lastXPos = undefined;
|
|
this._scrubElement = undefined;
|
|
this._startJulian = undefined;
|
|
this._timeBarSecondsSpan = undefined;
|
|
this._clock = clock;
|
|
this._scrubJulian = clock.currentTime;
|
|
this._mainTicSpan = -1;
|
|
this._mouseMode = timelineMouseMode.none;
|
|
this._touchMode = timelineTouchMode.none;
|
|
this._touchState = {
|
|
centerX: 0,
|
|
spanX: 0,
|
|
};
|
|
this._mouseX = 0;
|
|
this._timelineDrag = 0;
|
|
this._timelineDragLocation = undefined;
|
|
this._lastHeight = undefined;
|
|
this._lastWidth = undefined;
|
|
|
|
this._topDiv.innerHTML =
|
|
'<div class="cesium-timeline-bar"></div><div class="cesium-timeline-trackContainer">' +
|
|
'<canvas class="cesium-timeline-tracks" width="10" height="1">' +
|
|
'</canvas></div><div class="cesium-timeline-needle"></div><span class="cesium-timeline-ruler"></span>';
|
|
this._timeBarEle = this._topDiv.childNodes[0];
|
|
this._trackContainer = this._topDiv.childNodes[1];
|
|
this._trackListEle = this._topDiv.childNodes[1].childNodes[0];
|
|
this._needleEle = this._topDiv.childNodes[2];
|
|
this._rulerEle = this._topDiv.childNodes[3];
|
|
this._context = this._trackListEle.getContext("2d");
|
|
|
|
this._trackList = [];
|
|
this._highlightRanges = [];
|
|
|
|
this.zoomTo(clock.startTime, clock.stopTime);
|
|
|
|
this._onMouseDown = createMouseDownCallback(this);
|
|
this._onMouseUp = createMouseUpCallback(this);
|
|
this._onMouseMove = createMouseMoveCallback(this);
|
|
this._onMouseWheel = createMouseWheelCallback(this);
|
|
this._onTouchStart = createTouchStartCallback(this);
|
|
this._onTouchMove = createTouchMoveCallback(this);
|
|
this._onTouchEnd = createTouchEndCallback(this);
|
|
|
|
var timeBarEle = this._timeBarEle;
|
|
ownerDocument.addEventListener("mouseup", this._onMouseUp, false);
|
|
ownerDocument.addEventListener("mousemove", this._onMouseMove, false);
|
|
timeBarEle.addEventListener("mousedown", this._onMouseDown, false);
|
|
timeBarEle.addEventListener("DOMMouseScroll", this._onMouseWheel, false); // Mozilla mouse wheel
|
|
timeBarEle.addEventListener("mousewheel", this._onMouseWheel, false);
|
|
timeBarEle.addEventListener("touchstart", this._onTouchStart, false);
|
|
timeBarEle.addEventListener("touchmove", this._onTouchMove, false);
|
|
timeBarEle.addEventListener("touchend", this._onTouchEnd, false);
|
|
timeBarEle.addEventListener("touchcancel", this._onTouchEnd, false);
|
|
|
|
this._topDiv.oncontextmenu = function () {
|
|
return false;
|
|
};
|
|
|
|
clock.onTick.addEventListener(this.updateFromClock, this);
|
|
this.updateFromClock();
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Timeline.prototype.addEventListener = function (type, listener, useCapture) {
|
|
this._topDiv.addEventListener(type, listener, useCapture);
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Timeline.prototype.removeEventListener = function (type, listener, useCapture) {
|
|
this._topDiv.removeEventListener(type, listener, useCapture);
|
|
};
|
|
|
|
/**
|
|
* @returns {Boolean} true if the object has been destroyed, false otherwise.
|
|
*/
|
|
Timeline.prototype.isDestroyed = function () {
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Destroys the widget. Should be called if permanently
|
|
* removing the widget from layout.
|
|
*/
|
|
Timeline.prototype.destroy = function () {
|
|
this._clock.onTick.removeEventListener(this.updateFromClock, this);
|
|
|
|
var doc = this.container.ownerDocument;
|
|
doc.removeEventListener("mouseup", this._onMouseUp, false);
|
|
doc.removeEventListener("mousemove", this._onMouseMove, false);
|
|
|
|
var timeBarEle = this._timeBarEle;
|
|
timeBarEle.removeEventListener("mousedown", this._onMouseDown, false);
|
|
timeBarEle.removeEventListener("DOMMouseScroll", this._onMouseWheel, false); // Mozilla mouse wheel
|
|
timeBarEle.removeEventListener("mousewheel", this._onMouseWheel, false);
|
|
timeBarEle.removeEventListener("touchstart", this._onTouchStart, false);
|
|
timeBarEle.removeEventListener("touchmove", this._onTouchMove, false);
|
|
timeBarEle.removeEventListener("touchend", this._onTouchEnd, false);
|
|
timeBarEle.removeEventListener("touchcancel", this._onTouchEnd, false);
|
|
this.container.removeChild(this._topDiv);
|
|
destroyObject(this);
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Timeline.prototype.addHighlightRange = function (color, heightInPx, base) {
|
|
var newHighlightRange = new TimelineHighlightRange(color, heightInPx, base);
|
|
this._highlightRanges.push(newHighlightRange);
|
|
this.resize();
|
|
return newHighlightRange;
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Timeline.prototype.addTrack = function (
|
|
interval,
|
|
heightInPx,
|
|
color,
|
|
backgroundColor
|
|
) {
|
|
var newTrack = new TimelineTrack(
|
|
interval,
|
|
heightInPx,
|
|
color,
|
|
backgroundColor
|
|
);
|
|
this._trackList.push(newTrack);
|
|
this._lastHeight = undefined;
|
|
this.resize();
|
|
return newTrack;
|
|
};
|
|
|
|
/**
|
|
* Sets the view to the provided times.
|
|
*
|
|
* @param {JulianDate} startTime The start time.
|
|
* @param {JulianDate} stopTime The stop time.
|
|
*/
|
|
Timeline.prototype.zoomTo = function (startTime, stopTime) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (!defined(startTime)) {
|
|
throw new DeveloperError("startTime is required.");
|
|
}
|
|
if (!defined(stopTime)) {
|
|
throw new DeveloperError("stopTime is required");
|
|
}
|
|
if (JulianDate.lessThanOrEquals(stopTime, startTime)) {
|
|
throw new DeveloperError("Start time must come before end time.");
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
this._startJulian = startTime;
|
|
this._endJulian = stopTime;
|
|
this._timeBarSecondsSpan = JulianDate.secondsDifference(stopTime, startTime);
|
|
|
|
// If clock is not unbounded, clamp timeline range to clock.
|
|
if (this._clock && this._clock.clockRange !== ClockRange.UNBOUNDED) {
|
|
var clockStart = this._clock.startTime;
|
|
var clockEnd = this._clock.stopTime;
|
|
var clockSpan = JulianDate.secondsDifference(clockEnd, clockStart);
|
|
var startOffset = JulianDate.secondsDifference(
|
|
clockStart,
|
|
this._startJulian
|
|
);
|
|
var endOffset = JulianDate.secondsDifference(clockEnd, this._endJulian);
|
|
|
|
if (this._timeBarSecondsSpan >= clockSpan) {
|
|
// if new duration longer than clock range duration, clamp to full range.
|
|
this._timeBarSecondsSpan = clockSpan;
|
|
this._startJulian = this._clock.startTime;
|
|
this._endJulian = this._clock.stopTime;
|
|
} else if (startOffset > 0) {
|
|
// if timeline start is before clock start, shift right
|
|
this._endJulian = JulianDate.addSeconds(
|
|
this._endJulian,
|
|
startOffset,
|
|
new JulianDate()
|
|
);
|
|
this._startJulian = clockStart;
|
|
this._timeBarSecondsSpan = JulianDate.secondsDifference(
|
|
this._endJulian,
|
|
this._startJulian
|
|
);
|
|
} else if (endOffset < 0) {
|
|
// if timeline end is after clock end, shift left
|
|
this._startJulian = JulianDate.addSeconds(
|
|
this._startJulian,
|
|
endOffset,
|
|
new JulianDate()
|
|
);
|
|
this._endJulian = clockEnd;
|
|
this._timeBarSecondsSpan = JulianDate.secondsDifference(
|
|
this._endJulian,
|
|
this._startJulian
|
|
);
|
|
}
|
|
}
|
|
|
|
this._makeTics();
|
|
|
|
var evt = document.createEvent("Event");
|
|
evt.initEvent("setzoom", true, true);
|
|
evt.startJulian = this._startJulian;
|
|
evt.endJulian = this._endJulian;
|
|
evt.epochJulian = this._epochJulian;
|
|
evt.totalSpan = this._timeBarSecondsSpan;
|
|
evt.mainTicSpan = this._mainTicSpan;
|
|
this._topDiv.dispatchEvent(evt);
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Timeline.prototype.zoomFrom = function (amount) {
|
|
var centerSec = JulianDate.secondsDifference(
|
|
this._scrubJulian,
|
|
this._startJulian
|
|
);
|
|
if (amount > 1 || centerSec < 0 || centerSec > this._timeBarSecondsSpan) {
|
|
centerSec = this._timeBarSecondsSpan * 0.5;
|
|
} else {
|
|
centerSec += centerSec - this._timeBarSecondsSpan * 0.5;
|
|
}
|
|
var centerSecFlip = this._timeBarSecondsSpan - centerSec;
|
|
this.zoomTo(
|
|
JulianDate.addSeconds(
|
|
this._startJulian,
|
|
centerSec - centerSec * amount,
|
|
new JulianDate()
|
|
),
|
|
JulianDate.addSeconds(
|
|
this._endJulian,
|
|
centerSecFlip * amount - centerSecFlip,
|
|
new JulianDate()
|
|
)
|
|
);
|
|
};
|
|
|
|
function twoDigits(num) {
|
|
return num < 10 ? "0" + num.toString() : num.toString();
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Timeline.prototype.makeLabel = function (time) {
|
|
var gregorian = JulianDate.toGregorianDate(time);
|
|
var millisecond = gregorian.millisecond,
|
|
millisecondString = " UTC";
|
|
if (millisecond > 0 && this._timeBarSecondsSpan < 3600) {
|
|
millisecondString = Math.floor(millisecond).toString();
|
|
while (millisecondString.length < 3) {
|
|
millisecondString = "0" + millisecondString;
|
|
}
|
|
millisecondString = "." + millisecondString;
|
|
}
|
|
|
|
return (
|
|
timelineMonthNames[gregorian.month - 1] +
|
|
" " +
|
|
gregorian.day +
|
|
" " +
|
|
gregorian.year +
|
|
" " +
|
|
twoDigits(gregorian.hour) +
|
|
":" +
|
|
twoDigits(gregorian.minute) +
|
|
":" +
|
|
twoDigits(gregorian.second) +
|
|
millisecondString
|
|
);
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Timeline.prototype.smallestTicInPixels = 7.0;
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Timeline.prototype._makeTics = function () {
|
|
var timeBar = this._timeBarEle;
|
|
|
|
var seconds = JulianDate.secondsDifference(
|
|
this._scrubJulian,
|
|
this._startJulian
|
|
);
|
|
var xPos = Math.round(
|
|
(seconds * this._topDiv.clientWidth) / this._timeBarSecondsSpan
|
|
);
|
|
var scrubX = xPos - 8,
|
|
tic;
|
|
var widget = this;
|
|
|
|
this._needleEle.style.left = xPos.toString() + "px";
|
|
|
|
var tics = "";
|
|
|
|
var minimumDuration = 0.01;
|
|
var maximumDuration = 31536000000.0; // ~1000 years
|
|
var epsilon = 1e-10;
|
|
|
|
// If time step size is known, enter it here...
|
|
var minSize = 0;
|
|
|
|
var duration = this._timeBarSecondsSpan;
|
|
if (duration < minimumDuration) {
|
|
duration = minimumDuration;
|
|
this._timeBarSecondsSpan = minimumDuration;
|
|
this._endJulian = JulianDate.addSeconds(
|
|
this._startJulian,
|
|
minimumDuration,
|
|
new JulianDate()
|
|
);
|
|
} else if (duration > maximumDuration) {
|
|
duration = maximumDuration;
|
|
this._timeBarSecondsSpan = maximumDuration;
|
|
this._endJulian = JulianDate.addSeconds(
|
|
this._startJulian,
|
|
maximumDuration,
|
|
new JulianDate()
|
|
);
|
|
}
|
|
|
|
var timeBarWidth = this._timeBarEle.clientWidth;
|
|
if (timeBarWidth < 10) {
|
|
timeBarWidth = 10;
|
|
}
|
|
var startJulian = this._startJulian;
|
|
|
|
// epsilonTime: a small fraction of one pixel width of the timeline, measured in seconds.
|
|
var epsilonTime = Math.min((duration / timeBarWidth) * 1e-5, 0.4);
|
|
|
|
// epochJulian: a nearby time to be considered "zero seconds", should be a round-ish number by human standards.
|
|
var epochJulian;
|
|
var gregorianDate = JulianDate.toGregorianDate(startJulian);
|
|
if (duration > 315360000) {
|
|
// 3650+ days visible, epoch is start of the first visible century.
|
|
epochJulian = JulianDate.fromDate(
|
|
new Date(Date.UTC(Math.floor(gregorianDate.year / 100) * 100, 0))
|
|
);
|
|
} else if (duration > 31536000) {
|
|
// 365+ days visible, epoch is start of the first visible decade.
|
|
epochJulian = JulianDate.fromDate(
|
|
new Date(Date.UTC(Math.floor(gregorianDate.year / 10) * 10, 0))
|
|
);
|
|
} else if (duration > 86400) {
|
|
// 1+ day(s) visible, epoch is start of the year.
|
|
epochJulian = JulianDate.fromDate(
|
|
new Date(Date.UTC(gregorianDate.year, 0))
|
|
);
|
|
} else {
|
|
// Less than a day on timeline, epoch is midnight of the visible day.
|
|
epochJulian = JulianDate.fromDate(
|
|
new Date(
|
|
Date.UTC(gregorianDate.year, gregorianDate.month, gregorianDate.day)
|
|
)
|
|
);
|
|
}
|
|
|
|
// startTime: Seconds offset of the left side of the timeline from epochJulian.
|
|
var startTime = JulianDate.secondsDifference(
|
|
this._startJulian,
|
|
JulianDate.addSeconds(epochJulian, epsilonTime, new JulianDate())
|
|
);
|
|
// endTime: Seconds offset of the right side of the timeline from epochJulian.
|
|
var endTime = startTime + duration;
|
|
this._epochJulian = epochJulian;
|
|
|
|
function getStartTic(ticScale) {
|
|
return Math.floor(startTime / ticScale) * ticScale;
|
|
}
|
|
|
|
function getNextTic(tic, ticScale) {
|
|
return Math.ceil(tic / ticScale + 0.5) * ticScale;
|
|
}
|
|
|
|
function getAlpha(time) {
|
|
return (time - startTime) / duration;
|
|
}
|
|
|
|
function remainder(x, y) {
|
|
//return x % y;
|
|
return x - y * Math.round(x / y);
|
|
}
|
|
|
|
// Width in pixels of a typical label, plus padding
|
|
this._rulerEle.innerHTML = this.makeLabel(
|
|
JulianDate.addSeconds(this._endJulian, -minimumDuration, new JulianDate())
|
|
);
|
|
var sampleWidth = this._rulerEle.offsetWidth + 20;
|
|
if (sampleWidth < 30) {
|
|
// Workaround an apparent IE bug with measuring the width after going full-screen from inside an iframe.
|
|
sampleWidth = 180;
|
|
}
|
|
|
|
var origMinSize = minSize;
|
|
minSize -= epsilon;
|
|
|
|
var renderState = {
|
|
startTime: startTime,
|
|
startJulian: startJulian,
|
|
epochJulian: epochJulian,
|
|
duration: duration,
|
|
timeBarWidth: timeBarWidth,
|
|
getAlpha: getAlpha,
|
|
};
|
|
this._highlightRanges.forEach(function (highlightRange) {
|
|
tics += highlightRange.render(renderState);
|
|
});
|
|
|
|
// Calculate tic mark label spacing in the TimeBar.
|
|
var mainTic = 0.0,
|
|
subTic = 0.0,
|
|
tinyTic = 0.0;
|
|
// Ideal labeled tic as percentage of zoom interval
|
|
var idealTic = sampleWidth / timeBarWidth;
|
|
if (idealTic > 1.0) {
|
|
// Clamp to width of window, for thin windows.
|
|
idealTic = 1.0;
|
|
}
|
|
// Ideal labeled tic size in seconds
|
|
idealTic *= this._timeBarSecondsSpan;
|
|
var ticIndex = -1,
|
|
smallestIndex = -1;
|
|
|
|
var i,
|
|
ticScaleLen = timelineTicScales.length;
|
|
for (i = 0; i < ticScaleLen; ++i) {
|
|
var sc = timelineTicScales[i];
|
|
++ticIndex;
|
|
mainTic = sc;
|
|
// Find acceptable main tic size not smaller than ideal size.
|
|
if (sc > idealTic && sc > minSize) {
|
|
break;
|
|
}
|
|
if (
|
|
smallestIndex < 0 &&
|
|
timeBarWidth * (sc / this._timeBarSecondsSpan) >= this.smallestTicInPixels
|
|
) {
|
|
smallestIndex = ticIndex;
|
|
}
|
|
}
|
|
if (ticIndex > 0) {
|
|
while (ticIndex > 0) {
|
|
// Compute sub-tic size that evenly divides main tic.
|
|
--ticIndex;
|
|
if (Math.abs(remainder(mainTic, timelineTicScales[ticIndex])) < 0.00001) {
|
|
if (timelineTicScales[ticIndex] >= minSize) {
|
|
subTic = timelineTicScales[ticIndex];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (smallestIndex >= 0) {
|
|
while (smallestIndex < ticIndex) {
|
|
// Compute tiny tic size that evenly divides sub-tic.
|
|
if (
|
|
Math.abs(remainder(subTic, timelineTicScales[smallestIndex])) <
|
|
0.00001 &&
|
|
timelineTicScales[smallestIndex] >= minSize
|
|
) {
|
|
tinyTic = timelineTicScales[smallestIndex];
|
|
break;
|
|
}
|
|
++smallestIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
minSize = origMinSize;
|
|
if (
|
|
minSize > epsilon &&
|
|
tinyTic < 0.00001 &&
|
|
Math.abs(minSize - mainTic) > epsilon
|
|
) {
|
|
tinyTic = minSize;
|
|
if (minSize <= mainTic + epsilon) {
|
|
subTic = 0.0;
|
|
}
|
|
}
|
|
|
|
var lastTextLeft = -999999,
|
|
textWidth;
|
|
if (timeBarWidth * (tinyTic / this._timeBarSecondsSpan) >= 3.0) {
|
|
for (
|
|
tic = getStartTic(tinyTic);
|
|
tic <= endTime;
|
|
tic = getNextTic(tic, tinyTic)
|
|
) {
|
|
tics +=
|
|
'<span class="cesium-timeline-ticTiny" style="left: ' +
|
|
Math.round(timeBarWidth * getAlpha(tic)).toString() +
|
|
'px;"></span>';
|
|
}
|
|
}
|
|
if (timeBarWidth * (subTic / this._timeBarSecondsSpan) >= 3.0) {
|
|
for (
|
|
tic = getStartTic(subTic);
|
|
tic <= endTime;
|
|
tic = getNextTic(tic, subTic)
|
|
) {
|
|
tics +=
|
|
'<span class="cesium-timeline-ticSub" style="left: ' +
|
|
Math.round(timeBarWidth * getAlpha(tic)).toString() +
|
|
'px;"></span>';
|
|
}
|
|
}
|
|
if (timeBarWidth * (mainTic / this._timeBarSecondsSpan) >= 2.0) {
|
|
this._mainTicSpan = mainTic;
|
|
endTime += mainTic;
|
|
tic = getStartTic(mainTic);
|
|
var leapSecond = JulianDate.computeTaiMinusUtc(epochJulian);
|
|
while (tic <= endTime) {
|
|
var ticTime = JulianDate.addSeconds(
|
|
startJulian,
|
|
tic - startTime,
|
|
new JulianDate()
|
|
);
|
|
if (mainTic > 2.1) {
|
|
var ticLeap = JulianDate.computeTaiMinusUtc(ticTime);
|
|
if (Math.abs(ticLeap - leapSecond) > 0.1) {
|
|
tic += ticLeap - leapSecond;
|
|
ticTime = JulianDate.addSeconds(
|
|
startJulian,
|
|
tic - startTime,
|
|
new JulianDate()
|
|
);
|
|
}
|
|
}
|
|
var ticLeft = Math.round(timeBarWidth * getAlpha(tic));
|
|
var ticLabel = this.makeLabel(ticTime);
|
|
this._rulerEle.innerHTML = ticLabel;
|
|
textWidth = this._rulerEle.offsetWidth;
|
|
if (textWidth < 10) {
|
|
// IE iframe fullscreen sampleWidth workaround, continued.
|
|
textWidth = sampleWidth;
|
|
}
|
|
var labelLeft = ticLeft - (textWidth / 2 - 1);
|
|
if (labelLeft > lastTextLeft) {
|
|
lastTextLeft = labelLeft + textWidth + 5;
|
|
tics +=
|
|
'<span class="cesium-timeline-ticMain" style="left: ' +
|
|
ticLeft.toString() +
|
|
'px;"></span>' +
|
|
'<span class="cesium-timeline-ticLabel" style="left: ' +
|
|
labelLeft.toString() +
|
|
'px;">' +
|
|
ticLabel +
|
|
"</span>";
|
|
} else {
|
|
tics +=
|
|
'<span class="cesium-timeline-ticSub" style="left: ' +
|
|
ticLeft.toString() +
|
|
'px;"></span>';
|
|
}
|
|
tic = getNextTic(tic, mainTic);
|
|
}
|
|
} else {
|
|
this._mainTicSpan = -1;
|
|
}
|
|
|
|
tics +=
|
|
'<span class="cesium-timeline-icon16" style="left:' +
|
|
scrubX +
|
|
'px;bottom:0;background-position: 0 0;"></span>';
|
|
timeBar.innerHTML = tics;
|
|
this._scrubElement = timeBar.lastChild;
|
|
|
|
// Clear track canvas.
|
|
this._context.clearRect(
|
|
0,
|
|
0,
|
|
this._trackListEle.width,
|
|
this._trackListEle.height
|
|
);
|
|
|
|
renderState.y = 0;
|
|
this._trackList.forEach(function (track) {
|
|
track.render(widget._context, renderState);
|
|
renderState.y += track.height;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Timeline.prototype.updateFromClock = function () {
|
|
this._scrubJulian = this._clock.currentTime;
|
|
var scrubElement = this._scrubElement;
|
|
if (defined(this._scrubElement)) {
|
|
var seconds = JulianDate.secondsDifference(
|
|
this._scrubJulian,
|
|
this._startJulian
|
|
);
|
|
var xPos = Math.round(
|
|
(seconds * this._topDiv.clientWidth) / this._timeBarSecondsSpan
|
|
);
|
|
|
|
if (this._lastXPos !== xPos) {
|
|
this._lastXPos = xPos;
|
|
|
|
scrubElement.style.left = xPos - 8 + "px";
|
|
this._needleEle.style.left = xPos + "px";
|
|
}
|
|
}
|
|
if (defined(this._timelineDragLocation)) {
|
|
this._setTimeBarTime(
|
|
this._timelineDragLocation,
|
|
(this._timelineDragLocation * this._timeBarSecondsSpan) /
|
|
this._topDiv.clientWidth
|
|
);
|
|
this.zoomTo(
|
|
JulianDate.addSeconds(
|
|
this._startJulian,
|
|
this._timelineDrag,
|
|
new JulianDate()
|
|
),
|
|
JulianDate.addSeconds(
|
|
this._endJulian,
|
|
this._timelineDrag,
|
|
new JulianDate()
|
|
)
|
|
);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Timeline.prototype._setTimeBarTime = function (xPos, seconds) {
|
|
xPos = Math.round(xPos);
|
|
this._scrubJulian = JulianDate.addSeconds(
|
|
this._startJulian,
|
|
seconds,
|
|
new JulianDate()
|
|
);
|
|
if (this._scrubElement) {
|
|
var scrubX = xPos - 8;
|
|
this._scrubElement.style.left = scrubX.toString() + "px";
|
|
this._needleEle.style.left = xPos.toString() + "px";
|
|
}
|
|
|
|
var evt = document.createEvent("Event");
|
|
evt.initEvent("settime", true, true);
|
|
evt.clientX = xPos;
|
|
evt.timeSeconds = seconds;
|
|
evt.timeJulian = this._scrubJulian;
|
|
evt.clock = this._clock;
|
|
this._topDiv.dispatchEvent(evt);
|
|
};
|
|
|
|
function createMouseDownCallback(timeline) {
|
|
return function (e) {
|
|
if (timeline._mouseMode !== timelineMouseMode.touchOnly) {
|
|
if (e.button === 0) {
|
|
timeline._mouseMode = timelineMouseMode.scrub;
|
|
if (timeline._scrubElement) {
|
|
timeline._scrubElement.style.backgroundPosition = "-16px 0";
|
|
}
|
|
timeline._onMouseMove(e);
|
|
} else {
|
|
timeline._mouseX = e.clientX;
|
|
if (e.button === 2) {
|
|
timeline._mouseMode = timelineMouseMode.zoom;
|
|
} else {
|
|
timeline._mouseMode = timelineMouseMode.slide;
|
|
}
|
|
}
|
|
}
|
|
e.preventDefault();
|
|
};
|
|
}
|
|
|
|
function createMouseUpCallback(timeline) {
|
|
return function (e) {
|
|
timeline._mouseMode = timelineMouseMode.none;
|
|
if (timeline._scrubElement) {
|
|
timeline._scrubElement.style.backgroundPosition = "0 0";
|
|
}
|
|
timeline._timelineDrag = 0;
|
|
timeline._timelineDragLocation = undefined;
|
|
};
|
|
}
|
|
|
|
function createMouseMoveCallback(timeline) {
|
|
return function (e) {
|
|
var dx;
|
|
if (timeline._mouseMode === timelineMouseMode.scrub) {
|
|
e.preventDefault();
|
|
var x = e.clientX - timeline._topDiv.getBoundingClientRect().left;
|
|
|
|
if (x < 0) {
|
|
timeline._timelineDragLocation = 0;
|
|
timeline._timelineDrag = -0.01 * timeline._timeBarSecondsSpan;
|
|
} else if (x > timeline._topDiv.clientWidth) {
|
|
timeline._timelineDragLocation = timeline._topDiv.clientWidth;
|
|
timeline._timelineDrag = 0.01 * timeline._timeBarSecondsSpan;
|
|
} else {
|
|
timeline._timelineDragLocation = undefined;
|
|
timeline._setTimeBarTime(
|
|
x,
|
|
(x * timeline._timeBarSecondsSpan) / timeline._topDiv.clientWidth
|
|
);
|
|
}
|
|
} else if (timeline._mouseMode === timelineMouseMode.slide) {
|
|
dx = timeline._mouseX - e.clientX;
|
|
timeline._mouseX = e.clientX;
|
|
if (dx !== 0) {
|
|
var dsec =
|
|
(dx * timeline._timeBarSecondsSpan) / timeline._topDiv.clientWidth;
|
|
timeline.zoomTo(
|
|
JulianDate.addSeconds(timeline._startJulian, dsec, new JulianDate()),
|
|
JulianDate.addSeconds(timeline._endJulian, dsec, new JulianDate())
|
|
);
|
|
}
|
|
} else if (timeline._mouseMode === timelineMouseMode.zoom) {
|
|
dx = timeline._mouseX - e.clientX;
|
|
timeline._mouseX = e.clientX;
|
|
if (dx !== 0) {
|
|
timeline.zoomFrom(Math.pow(1.01, dx));
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
function createMouseWheelCallback(timeline) {
|
|
return function (e) {
|
|
var dy = e.wheelDeltaY || e.wheelDelta || -e.detail;
|
|
timelineWheelDelta = Math.max(
|
|
Math.min(Math.abs(dy), timelineWheelDelta),
|
|
1
|
|
);
|
|
dy /= timelineWheelDelta;
|
|
timeline.zoomFrom(Math.pow(1.05, -dy));
|
|
};
|
|
}
|
|
|
|
function createTouchStartCallback(timeline) {
|
|
return function (e) {
|
|
var len = e.touches.length,
|
|
seconds,
|
|
xPos,
|
|
leftX = timeline._topDiv.getBoundingClientRect().left;
|
|
e.preventDefault();
|
|
timeline._mouseMode = timelineMouseMode.touchOnly;
|
|
if (len === 1) {
|
|
seconds = JulianDate.secondsDifference(
|
|
timeline._scrubJulian,
|
|
timeline._startJulian
|
|
);
|
|
xPos = Math.round(
|
|
(seconds * timeline._topDiv.clientWidth) /
|
|
timeline._timeBarSecondsSpan +
|
|
leftX
|
|
);
|
|
if (Math.abs(e.touches[0].clientX - xPos) < 50) {
|
|
timeline._touchMode = timelineTouchMode.scrub;
|
|
if (timeline._scrubElement) {
|
|
timeline._scrubElement.style.backgroundPosition =
|
|
len === 1 ? "-16px 0" : "0 0";
|
|
}
|
|
} else {
|
|
timeline._touchMode = timelineTouchMode.singleTap;
|
|
timeline._touchState.centerX = e.touches[0].clientX - leftX;
|
|
}
|
|
} else if (len === 2) {
|
|
timeline._touchMode = timelineTouchMode.slideZoom;
|
|
timeline._touchState.centerX =
|
|
(e.touches[0].clientX + e.touches[1].clientX) * 0.5 - leftX;
|
|
timeline._touchState.spanX = Math.abs(
|
|
e.touches[0].clientX - e.touches[1].clientX
|
|
);
|
|
} else {
|
|
timeline._touchMode = timelineTouchMode.ignore;
|
|
}
|
|
};
|
|
}
|
|
|
|
function createTouchEndCallback(timeline) {
|
|
return function (e) {
|
|
var len = e.touches.length,
|
|
leftX = timeline._topDiv.getBoundingClientRect().left;
|
|
if (timeline._touchMode === timelineTouchMode.singleTap) {
|
|
timeline._touchMode = timelineTouchMode.scrub;
|
|
timeline._onTouchMove(e);
|
|
} else if (timeline._touchMode === timelineTouchMode.scrub) {
|
|
timeline._onTouchMove(e);
|
|
}
|
|
timeline._mouseMode = timelineMouseMode.touchOnly;
|
|
if (len !== 1) {
|
|
timeline._touchMode =
|
|
len > 0 ? timelineTouchMode.ignore : timelineTouchMode.none;
|
|
} else if (timeline._touchMode === timelineTouchMode.slideZoom) {
|
|
timeline._touchState.centerX = e.touches[0].clientX - leftX;
|
|
}
|
|
if (timeline._scrubElement) {
|
|
timeline._scrubElement.style.backgroundPosition = "0 0";
|
|
}
|
|
};
|
|
}
|
|
|
|
function createTouchMoveCallback(timeline) {
|
|
return function (e) {
|
|
var dx,
|
|
x,
|
|
len,
|
|
newCenter,
|
|
newSpan,
|
|
newStartTime,
|
|
zoom = 1,
|
|
leftX = timeline._topDiv.getBoundingClientRect().left;
|
|
if (timeline._touchMode === timelineTouchMode.singleTap) {
|
|
timeline._touchMode = timelineTouchMode.slideZoom;
|
|
}
|
|
timeline._mouseMode = timelineMouseMode.touchOnly;
|
|
if (timeline._touchMode === timelineTouchMode.scrub) {
|
|
e.preventDefault();
|
|
if (e.changedTouches.length === 1) {
|
|
x = e.changedTouches[0].clientX - leftX;
|
|
if (x >= 0 && x <= timeline._topDiv.clientWidth) {
|
|
timeline._setTimeBarTime(
|
|
x,
|
|
(x * timeline._timeBarSecondsSpan) / timeline._topDiv.clientWidth
|
|
);
|
|
}
|
|
}
|
|
} else if (timeline._touchMode === timelineTouchMode.slideZoom) {
|
|
len = e.touches.length;
|
|
if (len === 2) {
|
|
newCenter = (e.touches[0].clientX + e.touches[1].clientX) * 0.5 - leftX;
|
|
newSpan = Math.abs(e.touches[0].clientX - e.touches[1].clientX);
|
|
} else if (len === 1) {
|
|
newCenter = e.touches[0].clientX - leftX;
|
|
newSpan = 0;
|
|
}
|
|
|
|
if (defined(newCenter)) {
|
|
if (newSpan > 0 && timeline._touchState.spanX > 0) {
|
|
// Zoom and slide
|
|
zoom = timeline._touchState.spanX / newSpan;
|
|
newStartTime = JulianDate.addSeconds(
|
|
timeline._startJulian,
|
|
(timeline._touchState.centerX * timeline._timeBarSecondsSpan -
|
|
newCenter * timeline._timeBarSecondsSpan * zoom) /
|
|
timeline._topDiv.clientWidth,
|
|
new JulianDate()
|
|
);
|
|
} else {
|
|
// Slide to newCenter
|
|
dx = timeline._touchState.centerX - newCenter;
|
|
newStartTime = JulianDate.addSeconds(
|
|
timeline._startJulian,
|
|
(dx * timeline._timeBarSecondsSpan) / timeline._topDiv.clientWidth,
|
|
new JulianDate()
|
|
);
|
|
}
|
|
|
|
timeline.zoomTo(
|
|
newStartTime,
|
|
JulianDate.addSeconds(
|
|
newStartTime,
|
|
timeline._timeBarSecondsSpan * zoom,
|
|
new JulianDate()
|
|
)
|
|
);
|
|
timeline._touchState.centerX = newCenter;
|
|
timeline._touchState.spanX = newSpan;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Resizes the widget to match the container size.
|
|
*/
|
|
Timeline.prototype.resize = function () {
|
|
var width = this.container.clientWidth;
|
|
var height = this.container.clientHeight;
|
|
|
|
if (width === this._lastWidth && height === this._lastHeight) {
|
|
return;
|
|
}
|
|
|
|
this._trackContainer.style.height = height + "px";
|
|
|
|
var trackListHeight = 1;
|
|
this._trackList.forEach(function (track) {
|
|
trackListHeight += track.height;
|
|
});
|
|
this._trackListEle.style.height = trackListHeight.toString() + "px";
|
|
this._trackListEle.width = this._trackListEle.clientWidth;
|
|
this._trackListEle.height = trackListHeight;
|
|
this._makeTics();
|
|
|
|
this._lastXPos = undefined;
|
|
this._lastWidth = width;
|
|
this._lastHeight = height;
|
|
};
|
|
export default Timeline;
|