text: define(function (require) {
  
      'use strict';
  
      var zrUtil = require('zrender/core/util');
  
      var pathTool = require('zrender/tool/path');
      var round = Math.round;
      var Path = require('zrender/graphic/Path');
      var colorTool = require('zrender/tool/color');
      var matrix = require('zrender/core/matrix');
      var vector = require('zrender/core/vector');
      var Gradient = require('zrender/graphic/Gradient');
  
      var graphic = {};
  
      graphic.Group = require('zrender/container/Group');
  
      graphic.Image = require('zrender/graphic/Image');
  
      graphic.Text = require('zrender/graphic/Text');
  
      graphic.Circle = require('zrender/graphic/shape/Circle');
  
      graphic.Sector = require('zrender/graphic/shape/Sector');
  
      graphic.Ring = require('zrender/graphic/shape/Ring');
  
      graphic.Polygon = require('zrender/graphic/shape/Polygon');
  
      graphic.Polyline = require('zrender/graphic/shape/Polyline');
  
      graphic.Rect = require('zrender/graphic/shape/Rect');
  
      graphic.Line = require('zrender/graphic/shape/Line');
  
      graphic.BezierCurve = require('zrender/graphic/shape/BezierCurve');
  
      graphic.Arc = require('zrender/graphic/shape/Arc');
  
      graphic.CompoundPath = require('zrender/graphic/CompoundPath');
  
      graphic.LinearGradient = require('zrender/graphic/LinearGradient');
  
      graphic.RadialGradient = require('zrender/graphic/RadialGradient');
  
      graphic.BoundingRect = require('zrender/core/BoundingRect');
  
      /**
       * Extend shape with parameters
       */
      graphic.extendShape = function (opts) {
          return Path.extend(opts);
      };
  
      /**
       * Extend path
       */
      graphic.extendPath = function (pathData, opts) {
          return pathTool.extendFromString(pathData, opts);
      };
  
      /**
       * Create a path element from path data string
       * @param {string} pathData
       * @param {Object} opts
       * @param {module:zrender/core/BoundingRect} rect
       * @param {string} [layout=cover] 'center' or 'cover'
       */
      graphic.makePath = function (pathData, opts, rect, layout) {
          var path = pathTool.createFromString(pathData, opts);
          var boundingRect = path.getBoundingRect();
          if (rect) {
              var aspect = boundingRect.width / boundingRect.height;
  
              if (layout === 'center') {
                  // Set rect to center, keep width / height ratio.
                  var width = rect.height * aspect;
                  var height;
                  if (width <= rect.width) {
                      height = rect.height;
                  }
                  else {
                      width = rect.width;
                      height = width / aspect;
                  }
                  var cx = rect.x + rect.width / 2;
                  var cy = rect.y + rect.height / 2;
  
                  rect.x = cx - width / 2;
                  rect.y = cy - height / 2;
                  rect.width = width;
                  rect.height = height;
              }
  
              this.resizePath(path, rect);
          }
          return path;
      };
  
      graphic.mergePath = pathTool.mergePath,
  
          /**
           * Resize a path to fit the rect
           * @param {module:zrender/graphic/Path} path
           * @param {Object} rect
           */
          graphic.resizePath = function (path, rect) {
              if (!path.applyTransform) {
                  return;
              }
  
              var pathRect = path.getBoundingRect();
  
              var m = pathRect.calculateTransform(rect);
  
              path.applyTransform(m);
          };
  
      /**
       * Sub pixel optimize line for canvas
       *
       * @param {Object} param
       * @param {Object} [param.shape]
       * @param {number} [param.shape.x1]
       * @param {number} [param.shape.y1]
       * @param {number} [param.shape.x2]
       * @param {number} [param.shape.y2]
       * @param {Object} [param.style]
       * @param {number} [param.style.lineWidth]
       * @return {Object} Modified param
       */
      graphic.subPixelOptimizeLine = function (param) {
          var subPixelOptimize = graphic.subPixelOptimize;
          var shape = param.shape;
          var lineWidth = param.style.lineWidth;
  
          if (round(shape.x1 * 2) === round(shape.x2 * 2)) {
              shape.x1 = shape.x2 = subPixelOptimize(shape.x1, lineWidth, true);
          }
          if (round(shape.y1 * 2) === round(shape.y2 * 2)) {
              shape.y1 = shape.y2 = subPixelOptimize(shape.y1, lineWidth, true);
          }
          return param;
      };
  
      /**
       * Sub pixel optimize rect for canvas
       *
       * @param {Object} param
       * @param {Object} [param.shape]
       * @param {number} [param.shape.x]
       * @param {number} [param.shape.y]
       * @param {number} [param.shape.width]
       * @param {number} [param.shape.height]
       * @param {Object} [param.style]
       * @param {number} [param.style.lineWidth]
       * @return {Object} Modified param
       */
      graphic.subPixelOptimizeRect = function (param) {
          var subPixelOptimize = graphic.subPixelOptimize;
          var shape = param.shape;
          var lineWidth = param.style.lineWidth;
          var originX = shape.x;
          var originY = shape.y;
          var originWidth = shape.width;
          var originHeight = shape.height;
          shape.x = subPixelOptimize(shape.x, lineWidth, true);
          shape.y = subPixelOptimize(shape.y, lineWidth, true);
          shape.width = Math.max(
              subPixelOptimize(originX + originWidth, lineWidth, false) - shape.x,
              originWidth === 0 ? 0 : 1
          );
          shape.height = Math.max(
              subPixelOptimize(originY + originHeight, lineWidth, false) - shape.y,
              originHeight === 0 ? 0 : 1
          );
          return param;
      };
  
      /**
       * Sub pixel optimize for canvas
       *
       * @param {number} position Coordinate, such as x, y
       * @param {number} lineWidth Should be nonnegative integer.
       * @param {boolean=} positiveOrNegative Default false (negative).
       * @return {number} Optimized position.
       */
      graphic.subPixelOptimize = function (position, lineWidth, positiveOrNegative) {
          // Assure that (position + lineWidth / 2) is near integer edge,
          // otherwise line will be fuzzy in canvas.
          var doubledPosition = round(position * 2);
          return (doubledPosition + round(lineWidth)) % 2 === 0
              ? doubledPosition / 2
              : (doubledPosition + (positiveOrNegative ? 1 : -1)) / 2;
      };
  
      function hasFillOrStroke(fillOrStroke) {
          return fillOrStroke != null && fillOrStroke != 'none';
      }
  
      function liftColor(color) {
          return color instanceof Gradient ? color : colorTool.lift(color, -0.1);
      }
  
      /**
       * @private
       */
      function cacheElementStl(el) {
          if (el.__hoverStlDirty) {
              var stroke = el.style.stroke;
              var fill = el.style.fill;
  
              // Create hoverStyle on mouseover
              var hoverStyle = el.__hoverStl;
              hoverStyle.fill = hoverStyle.fill
                  || (hasFillOrStroke(fill) ? liftColor(fill) : null);
              hoverStyle.stroke = hoverStyle.stroke
                  || (hasFillOrStroke(stroke) ? liftColor(stroke) : null);
  
              var normalStyle = {};
              for (var name in hoverStyle) {
                  if (hoverStyle.hasOwnProperty(name)) {
                      normalStyle[name] = el.style[name];
                  }
              }
  
              el.__normalStl = normalStyle;
  
              el.__hoverStlDirty = false;
          }
      }
  
      /**
       * @private
       */
      function doSingleEnterHover(el) {
          if (el.__isHover) {
              return;
          }
  
          cacheElementStl(el);
  
          el.setStyle(el.__hoverStl);
          el.z2 += 1;
  
          el.__isHover = true;
      }
  
      /**
       * @inner
       */
      function doSingleLeaveHover(el) {
          if (!el.__isHover) {
              return;
          }
  
          var normalStl = el.__normalStl;
          normalStl && el.setStyle(normalStl);
          el.z2 -= 1;
  
          el.__isHover = false;
      }
  
      /**
       * @inner
       */
      function doEnterHover(el) {
          el.type === 'group'
              ? el.traverse(function (child) {
                  if (child.type !== 'group') {
                      doSingleEnterHover(child);
                  }
              })
              : doSingleEnterHover(el);
      }
  
      function doLeaveHover(el) {
          el.type === 'group'
              ? el.traverse(function (child) {
                  if (child.type !== 'group') {
                      doSingleLeaveHover(child);
                  }
              })
              : doSingleLeaveHover(el);
      }
  
      /**
       * @inner
       */
      function setElementHoverStl(el, hoverStl) {
          // If element has sepcified hoverStyle, then use it instead of given hoverStyle
          // Often used when item group has a label element and it's hoverStyle is different
          el.__hoverStl = el.hoverStyle || hoverStl || {};
          el.__hoverStlDirty = true;
  
          if (el.__isHover) {
              cacheElementStl(el);
          }
      }
  
      /**
       * @inner
       */
      function onElementMouseOver() {
          // Only if element is not in emphasis status
          !this.__isEmphasis && doEnterHover(this);
      }
  
      /**
       * @inner
       */
      function onElementMouseOut() {
          // Only if element is not in emphasis status
          !this.__isEmphasis && doLeaveHover(this);
      }
  
      /**
       * @inner
       */
      function enterEmphasis() {
          this.__isEmphasis = true;
          doEnterHover(this);
      }
  
      /**
       * @inner
       */
      function leaveEmphasis() {
          this.__isEmphasis = false;
          doLeaveHover(this);
      }
  
      /**
       * Set hover style of element
       * @param {module:zrender/Element} el
       * @param {Object} [hoverStyle]
       */
      graphic.setHoverStyle = function (el, hoverStyle) {
          el.type === 'group'
              ? el.traverse(function (child) {
                  if (child.type !== 'group') {
                      setElementHoverStl(child, hoverStyle);
                  }
              })
              : setElementHoverStl(el, hoverStyle);
          // Remove previous bound handlers
          el.on('mouseover', onElementMouseOver)
              .on('mouseout', onElementMouseOut);
  
          // Emphasis, normal can be triggered manually
          el.on('emphasis', enterEmphasis)
              .on('normal', leaveEmphasis);
      };
  
      /**
       * Set text option in the style
       * @param {Object} textStyle
       * @param {module:echarts/model/Model} labelModel
       * @param {string} color
       */
      graphic.setText = function (textStyle, labelModel, color) {
          var labelPosition = labelModel.getShallow('position') || 'inside';
          var labelColor = labelPosition.indexOf('inside') >= 0 ? 'white' : color;
          var textStyleModel = labelModel.getModel('textStyle');
          zrUtil.extend(textStyle, {
              textDistance: labelModel.getShallow('distance') || 5,
              textFont: textStyleModel.getFont(),
              textPosition: labelPosition,
              textFill: textStyleModel.getTextColor() || labelColor
          });
      };
  
      function animateOrSetProps(isUpdate, el, props, animatableModel, dataIndex, cb) {
          if (typeof dataIndex === 'function') {
              cb = dataIndex;
              dataIndex = null;
          }
  
          var postfix = isUpdate ? 'Update' : '';
          var duration = animatableModel
              && animatableModel.getShallow('animationDuration' + postfix);
          var animationEasing = animatableModel
              && animatableModel.getShallow('animationEasing' + postfix);
          var animationDelay = animatableModel
              && animatableModel.getShallow('animationDelay' + postfix);
          if (typeof animationDelay === 'function') {
              animationDelay = animationDelay(dataIndex);
          }
  
          animatableModel && animatableModel.getShallow('animation')
              ? el.animateTo(props, duration, animationDelay || 0, animationEasing, cb)
              : (el.attr(props), cb && cb());
      }
  
      /**
       * Update graphic element properties with or without animation according to the configuration in series
       * @param {module:zrender/Element} el
       * @param {Object} props
       * @param {module:echarts/model/Model} [animatableModel]
       * @param {number} [dataIndex]
       * @param {Function} [cb]
       * @example
       *     graphic.updateProps(el, {
       *         position: [100, 100]
       *     }, seriesModel, dataIndex, function () { console.log('Animation done!'); });
       *     // Or
       *     graphic.updateProps(el, {
       *         position: [100, 100]
       *     }, seriesModel, function () { console.log('Animation done!'); });
       */
      graphic.updateProps = zrUtil.curry(animateOrSetProps, true);
  
      /**
       * Init graphic element properties with or without animation according to the configuration in series
       * @param {module:zrender/Element} el
       * @param {Object} props
       * @param {module:echarts/model/Model} [animatableModel]
       * @param {Function} cb
       */
      graphic.initProps = zrUtil.curry(animateOrSetProps, false);
  
      /**
       * Get transform matrix of target (param target),
       * in coordinate of its ancestor (param ancestor)
       *
       * @param {module:zrender/mixin/Transformable} target
       * @param {module:zrender/mixin/Transformable} [ancestor]
       */
      graphic.getTransform = function (target, ancestor) {
          var mat = matrix.identity([]);
  
          while (target && target !== ancestor) {
              matrix.mul(mat, target.getLocalTransform(), mat);
              target = target.parent;
          }
  
          return mat;
      };
  
      /**
       * Apply transform to an vertex.
       * @param {Array.<number>} vertex [x, y]
       * @param {Array.<number>} transform Transform matrix: like [1, 0, 0, 1, 0, 0]
       * @param {boolean=} invert Whether use invert matrix.
       * @return {Array.<number>} [x, y]
       */
      graphic.applyTransform = function (vertex, transform, invert) {
          if (invert) {
              transform = matrix.invert([], transform);
          }
          return vector.applyTransform([], vertex, transform);
      };
  
      /**
       * @param {string} direction 'left' 'right' 'top' 'bottom'
       * @param {Array.<number>} transform Transform matrix: like [1, 0, 0, 1, 0, 0]
       * @param {boolean=} invert Whether use invert matrix.
       * @return {string} Transformed direction. 'left' 'right' 'top' 'bottom'
       */
      graphic.transformDirection = function (direction, transform, invert) {
  
          // Pick a base, ensure that transform result will not be (0, 0).
          var hBase = (transform[4] === 0 || transform[5] === 0 || transform[0] === 0)
              ? 1 : Math.abs(2 * transform[4] / transform[0]);
          var vBase = (transform[4] === 0 || transform[5] === 0 || transform[2] === 0)
              ? 1 : Math.abs(2 * transform[4] / transform[2]);
  
          var vertex = [
              direction === 'left' ? -hBase : direction === 'right' ? hBase : 0,
              direction === 'top' ? -vBase : direction === 'bottom' ? vBase : 0
          ];
  
          vertex = graphic.applyTransform(vertex, transform, invert);
  
          return Math.abs(vertex[0]) > Math.abs(vertex[1])
              ? (vertex[0] > 0 ? 'right' : 'left')
              : (vertex[1] > 0 ? 'bottom' : 'top');
      };
  
      return graphic;
  });
  