function MdProgressCircularLink()

in src/components/progressCircular/js/progressCircularDirective.js [99:304]


  function MdProgressCircularLink(scope, element, attrs) {
    var node = element[0];
    var svg = angular.element(node.querySelector('svg'));
    var path = angular.element(node.querySelector('path'));
    var startIndeterminate = $mdProgressCircular.startIndeterminate;
    var endIndeterminate = $mdProgressCircular.endIndeterminate;
    var iterationCount = 0;
    var lastAnimationId = 0;
    var lastDrawFrame;
    var interval;

    $mdTheming(element);
    element.toggleClass(DISABLED_CLASS, attrs.hasOwnProperty('disabled'));

    // If the mode is indeterminate, it doesn't need to
    // wait for the next digest. It can start right away.
    if (scope.mdMode === MODE_INDETERMINATE){
      startIndeterminateAnimation();
    }

    scope.$on('$destroy', function(){
      cleanupIndeterminateAnimation();

      if (lastDrawFrame) {
        cAF(lastDrawFrame);
      }
    });

    scope.$watchGroup(['value', 'mdMode', function() {
      var isDisabled = node.disabled;

      // Sometimes the browser doesn't return a boolean, in
      // which case we should check whether the attribute is
      // present.
      if (isDisabled === true || isDisabled === false){
        return isDisabled;
      }

      return angular.isDefined(element.attr('disabled'));
    }], function(newValues, oldValues) {
      var mode = newValues[1];
      var isDisabled = newValues[2];
      var wasDisabled = oldValues[2];
      var diameter = 0;
      var strokeWidth = 0;

      if (isDisabled !== wasDisabled) {
        element.toggleClass(DISABLED_CLASS, !!isDisabled);
      }

      if (isDisabled) {
        cleanupIndeterminateAnimation();
      } else {
        if (mode !== MODE_DETERMINATE && mode !== MODE_INDETERMINATE) {
          mode = MODE_INDETERMINATE;
          attrs.$set('mdMode', mode);
        }

        if (mode === MODE_INDETERMINATE) {
          if (oldValues[1] === MODE_DETERMINATE) {
            diameter = getSize(scope.mdDiameter);
            strokeWidth = getStroke(diameter);
            path.attr('d', getSvgArc(diameter, strokeWidth, true));
            path.attr('stroke-dasharray', getDashLength(diameter, strokeWidth, 75));
          }
          startIndeterminateAnimation();
        } else {
          var newValue = clamp(newValues[0]);
          var oldValue = clamp(oldValues[0]);

          cleanupIndeterminateAnimation();

          if (oldValues[1] === MODE_INDETERMINATE) {
            diameter = getSize(scope.mdDiameter);
            strokeWidth = getStroke(diameter);
            path.attr('d', getSvgArc(diameter, strokeWidth, false));
            path.attr('stroke-dasharray', getDashLength(diameter, strokeWidth, 100));
          }

          element.attr('aria-valuenow', newValue);
          renderCircle(oldValue, newValue);
        }
      }

    });

    // This is in a separate watch in order to avoid layout, unless
    // the value has actually changed.
    scope.$watch('mdDiameter', function(newValue) {
      var diameter = getSize(newValue);
      var strokeWidth = getStroke(diameter);
      var value = clamp(scope.value);
      var transformOrigin = (diameter / 2) + 'px';
      var dimensions = {
        width: diameter + 'px',
        height: diameter + 'px'
      };

      // The viewBox has to be applied via setAttribute, because it is
      // case-sensitive. If jQuery is included in the page, `.attr` lowercases
      // all attribute names.
      svg[0].setAttribute('viewBox', '0 0 ' + diameter + ' ' + diameter);

      // Usually viewBox sets the dimensions for the SVG, however that doesn't
      // seem to be the case on IE10.
      // Important! The transform origin has to be set from here and it has to
      // be in the format of "Ypx Ypx Ypx", otherwise the rotation wobbles in
      // IE and Edge, because they don't account for the stroke width when
      // rotating. Also "center" doesn't help in this case, it has to be a
      // precise value.
      svg
        .css(dimensions)
        .css('transform-origin', transformOrigin + ' ' + transformOrigin + ' ' + transformOrigin);

      element.css(dimensions);

      path.attr('stroke-width', strokeWidth);
      path.attr('stroke-linecap', 'square');
      if (scope.mdMode == MODE_INDETERMINATE) {
        path.attr('d', getSvgArc(diameter, strokeWidth, true));
        path.attr('stroke-dasharray', getDashLength(diameter, strokeWidth, 75));
        path.attr('stroke-dashoffset', getDashOffset(diameter, strokeWidth, 1, 75));
      } else {
        path.attr('d', getSvgArc(diameter, strokeWidth, false));
        path.attr('stroke-dasharray', getDashLength(diameter, strokeWidth, 100));
        path.attr('stroke-dashoffset', getDashOffset(diameter, strokeWidth, 0, 100));
        renderCircle(value, value);
      }

    });

    function renderCircle(animateFrom, animateTo, easing, duration, iterationCount, maxValue) {
      var id = ++lastAnimationId;
      var startTime = $mdUtil.now();
      var changeInValue = animateTo - animateFrom;
      var diameter = getSize(scope.mdDiameter);
      var strokeWidth = getStroke(diameter);
      var ease = easing || $mdProgressCircular.easeFn;
      var animationDuration = duration || $mdProgressCircular.duration;
      var rotation = -90 * (iterationCount || 0);
      var dashLimit = maxValue || 100;

      // No need to animate it if the values are the same
      if (animateTo === animateFrom) {
        renderFrame(animateTo);
      } else {
        lastDrawFrame = rAF(function animation() {
          var currentTime = $window.Math.max(0, $window.Math.min($mdUtil.now() - startTime, animationDuration));

          renderFrame(ease(currentTime, animateFrom, changeInValue, animationDuration));

          // Do not allow overlapping animations
          if (id === lastAnimationId && currentTime < animationDuration) {
            lastDrawFrame = rAF(animation);
          }
        });
      }

      function renderFrame(value) {
        path.attr('stroke-dashoffset', getDashOffset(diameter, strokeWidth, value, dashLimit));
        path.attr('transform','rotate(' + (rotation) + ' ' + diameter/2 + ' ' + diameter/2 + ')');
      }
    }

    function animateIndeterminate() {
      renderCircle(
        startIndeterminate,
        endIndeterminate,
        $mdProgressCircular.easeFnIndeterminate,
        $mdProgressCircular.durationIndeterminate,
        iterationCount,
        75
      );

      // The %4 technically isn't necessary, but it keeps the rotation
      // under 360, instead of becoming a crazy large number.
      iterationCount = ++iterationCount % 4;

    }

    function startIndeterminateAnimation() {
      if (!interval) {
        // Note that this interval isn't supposed to trigger a digest.
        interval = $interval(
          animateIndeterminate,
          $mdProgressCircular.durationIndeterminate,
          0,
          false
        );

        animateIndeterminate();

        element
          .addClass(INDETERMINATE_CLASS)
          .removeAttr('aria-valuenow');
      }
    }

    function cleanupIndeterminateAnimation() {
      if (interval) {
        $interval.cancel(interval);
        interval = null;
        element.removeClass(INDETERMINATE_CLASS);
      }
    }
  }