123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659 |
- import binarySearch from "../../Core/binarySearch.js";
- import ClockRange from "../../Core/ClockRange.js";
- import ClockStep from "../../Core/ClockStep.js";
- import defined from "../../Core/defined.js";
- import DeveloperError from "../../Core/DeveloperError.js";
- import JulianDate from "../../Core/JulianDate.js";
- import knockout from "../../ThirdParty/knockout.js";
- import createCommand from "../createCommand.js";
- import ToggleButtonViewModel from "../ToggleButtonViewModel.js";
- const monthNames = [
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec",
- ];
- const realtimeShuttleRingAngle = 15;
- const maxShuttleRingAngle = 105;
- function numberComparator(left, right) {
- return left - right;
- }
- function getTypicalMultiplierIndex(multiplier, shuttleRingTicks) {
- const index = binarySearch(shuttleRingTicks, multiplier, numberComparator);
- return index < 0 ? ~index : index;
- }
- function angleToMultiplier(angle, shuttleRingTicks) {
- //Use a linear scale for -1 to 1 between -15 < angle < 15 degrees
- if (Math.abs(angle) <= realtimeShuttleRingAngle) {
- return angle / realtimeShuttleRingAngle;
- }
- const minp = realtimeShuttleRingAngle;
- const maxp = maxShuttleRingAngle;
- let maxv;
- const minv = 0;
- let scale;
- if (angle > 0) {
- maxv = Math.log(shuttleRingTicks[shuttleRingTicks.length - 1]);
- scale = (maxv - minv) / (maxp - minp);
- return Math.exp(minv + scale * (angle - minp));
- }
- maxv = Math.log(-shuttleRingTicks[0]);
- scale = (maxv - minv) / (maxp - minp);
- return -Math.exp(minv + scale * (Math.abs(angle) - minp));
- }
- function multiplierToAngle(multiplier, shuttleRingTicks, clockViewModel) {
- if (clockViewModel.clockStep === ClockStep.SYSTEM_CLOCK) {
- return realtimeShuttleRingAngle;
- }
- if (Math.abs(multiplier) <= 1) {
- return multiplier * realtimeShuttleRingAngle;
- }
- const fastedMultipler = shuttleRingTicks[shuttleRingTicks.length - 1];
- if (multiplier > fastedMultipler) {
- multiplier = fastedMultipler;
- } else if (multiplier < -fastedMultipler) {
- multiplier = -fastedMultipler;
- }
- const minp = realtimeShuttleRingAngle;
- const maxp = maxShuttleRingAngle;
- let maxv;
- const minv = 0;
- let scale;
- if (multiplier > 0) {
- maxv = Math.log(fastedMultipler);
- scale = (maxv - minv) / (maxp - minp);
- return (Math.log(multiplier) - minv) / scale + minp;
- }
- maxv = Math.log(-shuttleRingTicks[0]);
- scale = (maxv - minv) / (maxp - minp);
- return -((Math.log(Math.abs(multiplier)) - minv) / scale + minp);
- }
- /**
- * The view model for the {@link Animation} widget.
- * @alias AnimationViewModel
- * @constructor
- *
- * @param {ClockViewModel} clockViewModel The ClockViewModel instance to use.
- *
- * @see Animation
- */
- function AnimationViewModel(clockViewModel) {
- //>>includeStart('debug', pragmas.debug);
- if (!defined(clockViewModel)) {
- throw new DeveloperError("clockViewModel is required.");
- }
- //>>includeEnd('debug');
- const that = this;
- this._clockViewModel = clockViewModel;
- this._allShuttleRingTicks = [];
- this._dateFormatter = AnimationViewModel.defaultDateFormatter;
- this._timeFormatter = AnimationViewModel.defaultTimeFormatter;
- /**
- * Gets or sets whether the shuttle ring is currently being dragged. This property is observable.
- * @type {Boolean}
- * @default false
- */
- this.shuttleRingDragging = false;
- /**
- * Gets or sets whether dragging the shuttle ring should cause the multiplier
- * to snap to the defined tick values rather than interpolating between them.
- * This property is observable.
- * @type {Boolean}
- * @default false
- */
- this.snapToTicks = false;
- knockout.track(this, [
- "_allShuttleRingTicks",
- "_dateFormatter",
- "_timeFormatter",
- "shuttleRingDragging",
- "snapToTicks",
- ]);
- this._sortedFilteredPositiveTicks = [];
- this.setShuttleRingTicks(AnimationViewModel.defaultTicks);
- /**
- * Gets the string representation of the current time. This property is observable.
- * @type {String}
- */
- this.timeLabel = undefined;
- knockout.defineProperty(this, "timeLabel", function () {
- return that._timeFormatter(that._clockViewModel.currentTime, that);
- });
- /**
- * Gets the string representation of the current date. This property is observable.
- * @type {String}
- */
- this.dateLabel = undefined;
- knockout.defineProperty(this, "dateLabel", function () {
- return that._dateFormatter(that._clockViewModel.currentTime, that);
- });
- /**
- * Gets the string representation of the current multiplier. This property is observable.
- * @type {String}
- */
- this.multiplierLabel = undefined;
- knockout.defineProperty(this, "multiplierLabel", function () {
- const clockViewModel = that._clockViewModel;
- if (clockViewModel.clockStep === ClockStep.SYSTEM_CLOCK) {
- return "Today";
- }
- const multiplier = clockViewModel.multiplier;
- //If it's a whole number, just return it.
- if (multiplier % 1 === 0) {
- return `${multiplier.toFixed(0)}x`;
- }
- //Convert to decimal string and remove any trailing zeroes
- return `${multiplier.toFixed(3).replace(/0{0,3}$/, "")}x`;
- });
- /**
- * Gets or sets the current shuttle ring angle. This property is observable.
- * @type {Number}
- */
- this.shuttleRingAngle = undefined;
- knockout.defineProperty(this, "shuttleRingAngle", {
- get: function () {
- return multiplierToAngle(
- clockViewModel.multiplier,
- that._allShuttleRingTicks,
- clockViewModel
- );
- },
- set: function (angle) {
- angle = Math.max(
- Math.min(angle, maxShuttleRingAngle),
- -maxShuttleRingAngle
- );
- const ticks = that._allShuttleRingTicks;
- const clockViewModel = that._clockViewModel;
- clockViewModel.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
- //If we are at the max angle, simply return the max value in either direction.
- if (Math.abs(angle) === maxShuttleRingAngle) {
- clockViewModel.multiplier =
- angle > 0 ? ticks[ticks.length - 1] : ticks[0];
- return;
- }
- let multiplier = angleToMultiplier(angle, ticks);
- if (that.snapToTicks) {
- multiplier = ticks[getTypicalMultiplierIndex(multiplier, ticks)];
- } else if (multiplier !== 0) {
- const positiveMultiplier = Math.abs(multiplier);
- if (positiveMultiplier > 100) {
- const numDigits = positiveMultiplier.toFixed(0).length - 2;
- const divisor = Math.pow(10, numDigits);
- multiplier = (Math.round(multiplier / divisor) * divisor) | 0;
- } else if (positiveMultiplier > realtimeShuttleRingAngle) {
- multiplier = Math.round(multiplier);
- } else if (positiveMultiplier > 1) {
- multiplier = +multiplier.toFixed(1);
- } else if (positiveMultiplier > 0) {
- multiplier = +multiplier.toFixed(2);
- }
- }
- clockViewModel.multiplier = multiplier;
- },
- });
- this._canAnimate = undefined;
- knockout.defineProperty(this, "_canAnimate", function () {
- const clockViewModel = that._clockViewModel;
- const clockRange = clockViewModel.clockRange;
- if (that.shuttleRingDragging || clockRange === ClockRange.UNBOUNDED) {
- return true;
- }
- const multiplier = clockViewModel.multiplier;
- const currentTime = clockViewModel.currentTime;
- const startTime = clockViewModel.startTime;
- let result = false;
- if (clockRange === ClockRange.LOOP_STOP) {
- result =
- JulianDate.greaterThan(currentTime, startTime) ||
- (currentTime.equals(startTime) && multiplier > 0);
- } else {
- const stopTime = clockViewModel.stopTime;
- result =
- (JulianDate.greaterThan(currentTime, startTime) &&
- JulianDate.lessThan(currentTime, stopTime)) || //
- (currentTime.equals(startTime) && multiplier > 0) || //
- (currentTime.equals(stopTime) && multiplier < 0);
- }
- if (!result) {
- clockViewModel.shouldAnimate = false;
- }
- return result;
- });
- this._isSystemTimeAvailable = undefined;
- knockout.defineProperty(this, "_isSystemTimeAvailable", function () {
- const clockViewModel = that._clockViewModel;
- const clockRange = clockViewModel.clockRange;
- if (clockRange === ClockRange.UNBOUNDED) {
- return true;
- }
- const systemTime = clockViewModel.systemTime;
- return (
- JulianDate.greaterThanOrEquals(systemTime, clockViewModel.startTime) &&
- JulianDate.lessThanOrEquals(systemTime, clockViewModel.stopTime)
- );
- });
- this._isAnimating = undefined;
- knockout.defineProperty(this, "_isAnimating", function () {
- return (
- that._clockViewModel.shouldAnimate &&
- (that._canAnimate || that.shuttleRingDragging)
- );
- });
- const pauseCommand = createCommand(function () {
- const clockViewModel = that._clockViewModel;
- if (clockViewModel.shouldAnimate) {
- clockViewModel.shouldAnimate = false;
- } else if (that._canAnimate) {
- clockViewModel.shouldAnimate = true;
- }
- });
- this._pauseViewModel = new ToggleButtonViewModel(pauseCommand, {
- toggled: knockout.computed(function () {
- return !that._isAnimating;
- }),
- tooltip: "Pause",
- });
- const playReverseCommand = createCommand(function () {
- const clockViewModel = that._clockViewModel;
- const multiplier = clockViewModel.multiplier;
- if (multiplier > 0) {
- clockViewModel.multiplier = -multiplier;
- }
- clockViewModel.shouldAnimate = true;
- });
- this._playReverseViewModel = new ToggleButtonViewModel(playReverseCommand, {
- toggled: knockout.computed(function () {
- return that._isAnimating && clockViewModel.multiplier < 0;
- }),
- tooltip: "Play Reverse",
- });
- const playForwardCommand = createCommand(function () {
- const clockViewModel = that._clockViewModel;
- const multiplier = clockViewModel.multiplier;
- if (multiplier < 0) {
- clockViewModel.multiplier = -multiplier;
- }
- clockViewModel.shouldAnimate = true;
- });
- this._playForwardViewModel = new ToggleButtonViewModel(playForwardCommand, {
- toggled: knockout.computed(function () {
- return (
- that._isAnimating &&
- clockViewModel.multiplier > 0 &&
- clockViewModel.clockStep !== ClockStep.SYSTEM_CLOCK
- );
- }),
- tooltip: "Play Forward",
- });
- const playRealtimeCommand = createCommand(function () {
- that._clockViewModel.clockStep = ClockStep.SYSTEM_CLOCK;
- }, knockout.getObservable(this, "_isSystemTimeAvailable"));
- this._playRealtimeViewModel = new ToggleButtonViewModel(playRealtimeCommand, {
- toggled: knockout.computed(function () {
- return clockViewModel.clockStep === ClockStep.SYSTEM_CLOCK;
- }),
- tooltip: knockout.computed(function () {
- return that._isSystemTimeAvailable
- ? "Today (real-time)"
- : "Current time not in range";
- }),
- });
- this._slower = createCommand(function () {
- const clockViewModel = that._clockViewModel;
- const shuttleRingTicks = that._allShuttleRingTicks;
- const multiplier = clockViewModel.multiplier;
- const index = getTypicalMultiplierIndex(multiplier, shuttleRingTicks) - 1;
- if (index >= 0) {
- clockViewModel.multiplier = shuttleRingTicks[index];
- }
- });
- this._faster = createCommand(function () {
- const clockViewModel = that._clockViewModel;
- const shuttleRingTicks = that._allShuttleRingTicks;
- const multiplier = clockViewModel.multiplier;
- const index = getTypicalMultiplierIndex(multiplier, shuttleRingTicks) + 1;
- if (index < shuttleRingTicks.length) {
- clockViewModel.multiplier = shuttleRingTicks[index];
- }
- });
- }
- /**
- * Gets or sets the default date formatter used by new instances.
- *
- * @member
- * @type {AnimationViewModel.DateFormatter}
- */
- AnimationViewModel.defaultDateFormatter = function (date, viewModel) {
- const gregorianDate = JulianDate.toGregorianDate(date);
- return `${monthNames[gregorianDate.month - 1]} ${gregorianDate.day} ${
- gregorianDate.year
- }`;
- };
- /**
- * Gets or sets the default array of known clock multipliers associated with new instances of the shuttle ring.
- * @type {Number[]}
- */
- AnimationViewModel.defaultTicks = [
- //
- 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,
- 120.0,
- 300.0,
- 600.0,
- 900.0,
- 1800.0,
- 3600.0,
- 7200.0,
- 14400.0, //
- 21600.0,
- 43200.0,
- 86400.0,
- 172800.0,
- 345600.0,
- 604800.0,
- ];
- /**
- * Gets or sets the default time formatter used by new instances.
- *
- * @member
- * @type {AnimationViewModel.TimeFormatter}
- */
- AnimationViewModel.defaultTimeFormatter = function (date, viewModel) {
- const gregorianDate = JulianDate.toGregorianDate(date);
- const millisecond = Math.round(gregorianDate.millisecond);
- if (Math.abs(viewModel._clockViewModel.multiplier) < 1) {
- return `${gregorianDate.hour
- .toString()
- .padStart(2, "0")}:${gregorianDate.minute
- .toString()
- .padStart(2, "0")}:${gregorianDate.second
- .toString()
- .padStart(2, "0")}.${millisecond.toString().padStart(3, "0")}`;
- }
- return `${gregorianDate.hour
- .toString()
- .padStart(2, "0")}:${gregorianDate.minute
- .toString()
- .padStart(2, "0")}:${gregorianDate.second.toString().padStart(2, "0")} UTC`;
- };
- /**
- * Gets a copy of the array of positive known clock multipliers to associate with the shuttle ring.
- *
- * @returns {Number[]} The array of known clock multipliers associated with the shuttle ring.
- */
- AnimationViewModel.prototype.getShuttleRingTicks = function () {
- return this._sortedFilteredPositiveTicks.slice(0);
- };
- /**
- * Sets the array of positive known clock multipliers to associate with the shuttle ring.
- * These values will have negative equivalents created for them and sets both the minimum
- * and maximum range of values for the shuttle ring as well as the values that are snapped
- * to when a single click is made. The values need not be in order, as they will be sorted
- * automatically, and duplicate values will be removed.
- *
- * @param {Number[]} positiveTicks The list of known positive clock multipliers to associate with the shuttle ring.
- */
- AnimationViewModel.prototype.setShuttleRingTicks = function (positiveTicks) {
- //>>includeStart('debug', pragmas.debug);
- if (!defined(positiveTicks)) {
- throw new DeveloperError("positiveTicks is required.");
- }
- //>>includeEnd('debug');
- let i;
- let len;
- let tick;
- const hash = {};
- const sortedFilteredPositiveTicks = this._sortedFilteredPositiveTicks;
- sortedFilteredPositiveTicks.length = 0;
- for (i = 0, len = positiveTicks.length; i < len; ++i) {
- tick = positiveTicks[i];
- //filter duplicates
- if (!hash.hasOwnProperty(tick)) {
- hash[tick] = true;
- sortedFilteredPositiveTicks.push(tick);
- }
- }
- sortedFilteredPositiveTicks.sort(numberComparator);
- const allTicks = [];
- for (len = sortedFilteredPositiveTicks.length, i = len - 1; i >= 0; --i) {
- tick = sortedFilteredPositiveTicks[i];
- if (tick !== 0) {
- allTicks.push(-tick);
- }
- }
- Array.prototype.push.apply(allTicks, sortedFilteredPositiveTicks);
- this._allShuttleRingTicks = allTicks;
- };
- Object.defineProperties(AnimationViewModel.prototype, {
- /**
- * Gets a command that decreases the speed of animation.
- * @memberof AnimationViewModel.prototype
- * @type {Command}
- */
- slower: {
- get: function () {
- return this._slower;
- },
- },
- /**
- * Gets a command that increases the speed of animation.
- * @memberof AnimationViewModel.prototype
- * @type {Command}
- */
- faster: {
- get: function () {
- return this._faster;
- },
- },
- /**
- * Gets the clock view model.
- * @memberof AnimationViewModel.prototype
- *
- * @type {ClockViewModel}
- */
- clockViewModel: {
- get: function () {
- return this._clockViewModel;
- },
- },
- /**
- * Gets the pause toggle button view model.
- * @memberof AnimationViewModel.prototype
- *
- * @type {ToggleButtonViewModel}
- */
- pauseViewModel: {
- get: function () {
- return this._pauseViewModel;
- },
- },
- /**
- * Gets the reverse toggle button view model.
- * @memberof AnimationViewModel.prototype
- *
- * @type {ToggleButtonViewModel}
- */
- playReverseViewModel: {
- get: function () {
- return this._playReverseViewModel;
- },
- },
- /**
- * Gets the play toggle button view model.
- * @memberof AnimationViewModel.prototype
- *
- * @type {ToggleButtonViewModel}
- */
- playForwardViewModel: {
- get: function () {
- return this._playForwardViewModel;
- },
- },
- /**
- * Gets the realtime toggle button view model.
- * @memberof AnimationViewModel.prototype
- *
- * @type {ToggleButtonViewModel}
- */
- playRealtimeViewModel: {
- get: function () {
- return this._playRealtimeViewModel;
- },
- },
- /**
- * Gets or sets the function which formats a date for display.
- * @memberof AnimationViewModel.prototype
- *
- * @type {AnimationViewModel.DateFormatter}
- * @default AnimationViewModel.defaultDateFormatter
- */
- dateFormatter: {
- //TODO:@exception {DeveloperError} dateFormatter must be a function.
- get: function () {
- return this._dateFormatter;
- },
- set: function (dateFormatter) {
- //>>includeStart('debug', pragmas.debug);
- if (typeof dateFormatter !== "function") {
- throw new DeveloperError("dateFormatter must be a function");
- }
- //>>includeEnd('debug');
- this._dateFormatter = dateFormatter;
- },
- },
- /**
- * Gets or sets the function which formats a time for display.
- * @memberof AnimationViewModel.prototype
- *
- * @type {AnimationViewModel.TimeFormatter}
- * @default AnimationViewModel.defaultTimeFormatter
- */
- timeFormatter: {
- //TODO:@exception {DeveloperError} timeFormatter must be a function.
- get: function () {
- return this._timeFormatter;
- },
- set: function (timeFormatter) {
- //>>includeStart('debug', pragmas.debug);
- if (typeof timeFormatter !== "function") {
- throw new DeveloperError("timeFormatter must be a function");
- }
- //>>includeEnd('debug');
- this._timeFormatter = timeFormatter;
- },
- },
- });
- //Currently exposed for tests.
- AnimationViewModel._maxShuttleRingAngle = maxShuttleRingAngle;
- AnimationViewModel._realtimeShuttleRingAngle = realtimeShuttleRingAngle;
- /**
- * A function that formats a date for display.
- * @callback AnimationViewModel.DateFormatter
- *
- * @param {JulianDate} date The date to be formatted
- * @param {AnimationViewModel} viewModel The AnimationViewModel instance requesting formatting.
- * @returns {String} The string representation of the calendar date portion of the provided date.
- */
- /**
- * A function that formats a time for display.
- * @callback AnimationViewModel.TimeFormatter
- *
- * @param {JulianDate} date The date to be formatted
- * @param {AnimationViewModel} viewModel The AnimationViewModel instance requesting formatting.
- * @returns {String} The string representation of the time portion of the provided date.
- */
- export default AnimationViewModel;
|