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];
    }
  });
}
The view model for the {@link Animation} widget.
@alias AnimationViewModel
@constructor

@param {ClockViewModel} clockViewModel The ClockViewModel instance to use.

@see Animation