text: define(function (require) {
  
      'use strict';
  
      var zrUtil = require('zrender/core/util');
      var graphic = require('../../util/graphic');
      var formatUtil = require('../../util/format');
      var numberUtil = require('../../util/number');
  
      var MONTH_TEXT = {
          EN: [
                  'Jan', 'Feb', 'Mar',
                  'Apr', 'May', 'Jun',
                  'Jul', 'Aug', 'Sep',
                  'Oct', 'Nov', 'Dec'
              ],
          CN: [
                  '一月', '二月', '三月',
                  '四月', '五月', '六月',
                  '七月', '八月', '九月',
                  '十月', '十一月', '十二月'
              ]
      };
  
      var WEEK_TEXT = {
          EN: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
          CN: ['日', '一', '二', '三', '四', '五', '六']
      };
  
      return require('../../echarts').extendComponentView({
  
          type: 'calendar',
  
          /**
           * top/left line points
           *  @private
           */
          _tlpoints: null,
  
          /**
           * bottom/right line points
           *  @private
           */
          _blpoints: null,
  
          /**
           * first day of month
           *  @private
           */
          _firstDayOfMonth: null,
  
          /**
           * first day point of month
           *  @private
           */
          _firstDayPoints: null,
  
          render: function (calendarModel, ecModel, api) {
  
              var group = this.group;
  
              group.removeAll();
  
              var coordSys = calendarModel.coordinateSystem;
  
              // range info
              var rangeData = coordSys.getHandledRangeInfo();
              var orient = coordSys.getOrient();
  
              this._renderDayRect(calendarModel, rangeData, group);
  
              // _renderLines must be called prior to following function
              this._renderLines(calendarModel, rangeData, orient, group);
  
              this._renderYearText(calendarModel, rangeData, orient, group);
  
              this._renderMonthText(calendarModel, orient, group);
  
              this._renderWeekText(calendarModel, rangeData, orient, group);
          },
  
          // render day rect
          _renderDayRect: function (calendarModel, rangeData, group) {
              var coordSys = calendarModel.coordinateSystem;
              var itemRectStyleModel = calendarModel.getModel('itemStyle.normal').getItemStyle();
              var sw = coordSys.getCellWidth();
              var sh = coordSys.getCellHeight();
  
              for (var i = rangeData.start.time;
                  i <= rangeData.end.time;
                  i = coordSys.getNextNDay(i, 1).time
              ) {
  
                  var point = coordSys.dataToRect([i], true).tl;
  
                  // every rect
                  var rect = new graphic.Rect({
                      shape: {
                          x: point[0],
                          y: point[1],
                          width: sw,
                          height: sh
                      },
                      style: itemRectStyleModel
                  });
  
                  group.add(rect);
              }
  
          },
  
          // render separate line
          _renderLines: function (calendarModel, rangeData, orient, group) {
  
              var self = this;
  
              var coordSys = calendarModel.coordinateSystem;
  
              var lineStyleModel = calendarModel.getModel('splitLine.lineStyle').getLineStyle();
              var show = calendarModel.get('splitLine.show');
  
              var lineWidth = lineStyleModel.lineWidth;
  
              this._tlpoints = [];
              this._blpoints = [];
              this._firstDayOfMonth = [];
              this._firstDayPoints = [];
  
  
              var firstDay = rangeData.start;
  
              for (var i = 0; firstDay.time <= rangeData.end.time; i++) {
                  addPoints(firstDay.formatedDate);
  
                  if (i === 0) {
                      firstDay = coordSys.getDateInfo(rangeData.start.y + '-' + rangeData.start.m);
                  }
  
                  var date = firstDay.date;
                  date.setMonth(date.getMonth() + 1);
                  firstDay = coordSys.getDateInfo(date);
              }
  
              addPoints(coordSys.getNextNDay(rangeData.end.time, 1).formatedDate);
  
              function addPoints(date) {
  
                  self._firstDayOfMonth.push(coordSys.getDateInfo(date));
                  self._firstDayPoints.push(coordSys.dataToRect([date], true).tl);
  
                  var points = self._getLinePointsOfOneWeek(calendarModel, date, orient);
  
                  self._tlpoints.push(points[0]);
                  self._blpoints.push(points[points.length - 1]);
  
                  show && self._drawSplitline(points, lineStyleModel, group);
              }
  
  
              // render top/left line
              show && this._drawSplitline(self._getEdgesPoints(self._tlpoints, lineWidth, orient), lineStyleModel, group);
  
              // render bottom/right line
              show && this._drawSplitline(self._getEdgesPoints(self._blpoints, lineWidth, orient), lineStyleModel, group);
  
          },
  
          // get points at both ends
          _getEdgesPoints: function (points, lineWidth, orient) {
              var rs = [points[0].slice(), points[points.length - 1].slice()];
              var idx = orient === 'horizontal' ? 0 : 1;
  
              // both ends of the line are extend half lineWidth
              rs[0][idx] = rs[0][idx] - lineWidth / 2;
              rs[1][idx] = rs[1][idx] + lineWidth / 2;
  
              return rs;
          },
  
          // render split line
          _drawSplitline: function (points, lineStyleModel, group) {
  
              var poyline = new graphic.Polyline({
                  z2: 20,
                  shape: {
                      points: points
                  },
                  style: lineStyleModel
              });
  
              group.add(poyline);
          },
  
          // render month line of one week points
          _getLinePointsOfOneWeek: function (calendarModel, date, orient) {
  
              var coordSys = calendarModel.coordinateSystem;
              date = coordSys.getDateInfo(date);
  
              var points = [];
  
              for (var i = 0; i < 7; i++) {
  
                  var tmpD = coordSys.getNextNDay(date.time, i);
                  var point = coordSys.dataToRect([tmpD.time], true);
  
                  points[2 * tmpD.day] = point.tl;
                  points[2 * tmpD.day + 1] = point[orient === 'horizontal' ? 'bl' : 'tr'];
              }
  
              return points;
  
          },
  
          _formatterLabel: function (formatter, params) {
  
              if (typeof formatter === 'string' && formatter) {
                  return formatUtil.formatTplSimple(formatter, params);
              }
  
              if (typeof formatter === 'function') {
                  return formatter(params);
              }
  
              return params.nameMap;
  
          },
  
          _yearTextPositionControl: function (point, orient, position, margin) {
  
              point = point.slice();
              var aligns = ['center', 'bottom'];
  
              if (position === 'top') {
                  point[1] -= margin;
              }
              if (position === 'bottom') {
                  point[1] += margin;
                  aligns = ['center', 'top'];
              }
              if (position === 'left') {
                  point[0] -= margin;
              }
              if (position === 'right') {
                  point[0] += margin;
                  aligns = ['center', 'top'];
              }
  
              var rotate = 0;
              if (position === 'left' || position === 'right') {
                  rotate = Math.PI / 2;
              }
  
              return {
                  rotation: rotate,
                  origin: point,
                  style: {
                      x: point[0],
                      y: point[1],
                      textAlign: aligns[0],
                      textVerticalAlign: aligns[1]
                  }
              };
          },
  
          // render year
          _renderYearText: function (calendarModel, rangeData, orient, group) {
              var yearLabel = calendarModel.getModel('yearLabel');
  
              if (!yearLabel.get('show')) {
                  return;
              }
  
              var yearLabelStyleModel = calendarModel.getModel('yearLabel.textStyle');
              var margin = yearLabel.get('margin');
              var pos = yearLabel.get('position');
  
              if (!pos) {
                  pos = orient !== 'horizontal' ? 'top' : 'left';
              }
  
              var points = [this._tlpoints[this._tlpoints.length - 1], this._blpoints[0]];
              var xc = (points[0][0] + points[1][0]) / 2;
              var yc = (points[0][1] + points[1][1]) / 2;
  
              var idx = orient === 'horizontal' ? 0 : 1;
  
  
              var posPoints = {
                  top: [xc, points[idx][1]],
                  bottom: [xc, points[1 - idx][1]],
                  left: [points[1 - idx][0], yc],
                  right: [points[idx][0], yc]
              };
  
              var name = rangeData.start.y;
  
              if (+rangeData.end.y > +rangeData.start.y) {
                  name = name + '-' + rangeData.end.y;
              }
  
              var formatter = yearLabel.get('formatter');
  
              var params = {
                  start: rangeData.start.y,
                  end: rangeData.end.y,
                  nameMap: name
              };
  
              var content = this._formatterLabel(formatter, params);
  
              var yearText = new graphic.Text(
                  zrUtil.merge({
                      z2: 30,
                      style: {
                          text: content,
                          font: yearLabelStyleModel.getFont(),
                          fill: yearLabelStyleModel.getTextColor()
                      }
                  }, this._yearTextPositionControl(posPoints[pos], orient, pos, margin))
              );
  
              group.add(yearText);
          },
  
          _monthTextPositionControl: function (point, isCenter, orient, position, margin) {
              var align = 'left';
              var vAlign = 'top';
              var x = point[0];
              var y = point[1];
  
              if (orient === 'horizontal') {
                  y = y + margin;
  
                  if (isCenter) {
                      align = 'center';
                  }
  
                  if (position === 'start') {
                      vAlign = 'bottom';
                  }
              }
              else {
                  x = x + margin;
  
                  if (isCenter) {
                      vAlign = 'middle';
                  }
  
                  if (position === 'start') {
                      align = 'right';
                  }
              }
  
              return {
                  x: x,
                  y: y,
                  textAlign: align,
                  textVerticalAlign: vAlign
              };
          },
  
          // render month and year text
          _renderMonthText: function (calendarModel, orient, group) {
              var monthLabel = calendarModel.getModel('monthLabel');
  
              if (!monthLabel.get('show')) {
                  return;
              }
  
              var monthLabelStyleModel = calendarModel.getModel('monthLabel.textStyle');
              var nameMap = monthLabel.get('nameMap');
              var margin = monthLabel.get('margin');
              var pos = monthLabel.get('position');
              var align = monthLabel.get('align');
  
              var termPoints = [this._tlpoints, this._blpoints];
  
              if (zrUtil.isString(nameMap)) {
                  nameMap = MONTH_TEXT[nameMap.toUpperCase()] || [];
              }
  
              var idx = pos === 'start' ? 0 : 1;
              var axis = orient === 'horizontal' ? 0 : 1;
              margin = pos === 'start' ? -margin : margin;
              var isCenter = (align === 'center');
  
              for (var i = 0; i < termPoints[idx].length - 1; i++) {
  
                  var tmp = termPoints[idx][i].slice();
                  var firstDay = this._firstDayOfMonth[i];
  
                  if (isCenter) {
                      var firstDayPoints = this._firstDayPoints[i];
                      tmp[axis] = (firstDayPoints[axis] + termPoints[0][i + 1][axis]) / 2;
                  }
  
                  var formatter = monthLabel.get('formatter');
                  var name = nameMap[+firstDay.m - 1];
                  var params = {
                      yyyy: firstDay.y,
                      yy: (firstDay.y + '').slice(2),
                      MM: firstDay.m,
                      M: +firstDay.m,
                      nameMap: name
                  };
  
                  var content = this._formatterLabel(formatter, params);
  
                  var monthText = new graphic.Text({
                      z2: 30,
                      style: zrUtil.extend({
                          text: content,
                          font: monthLabelStyleModel.getFont(),
                          fill: monthLabelStyleModel.getTextColor()
                      }, this._monthTextPositionControl(tmp, isCenter, orient, pos, margin))
                  });
  
                  group.add(monthText);
              }
          },
  
          _weekTextPositionControl: function (point, orient, position, margin, cellSize) {
              var align = 'center';
              var vAlign = 'middle';
              var x = point[0];
              var y = point[1];
              var isStart = position === 'start';
  
              if (orient === 'horizontal') {
                  x = x + margin + (isStart ? 1 : -1) * cellSize[0] / 2;
                  align = isStart ? 'right' : 'left';
              }
              else {
                  y = y + margin + (isStart ? 1 : -1) * cellSize[1] / 2;
                  vAlign = isStart ? 'bottom' : 'top';
              }
  
              return {
                  x: x,
                  y: y,
                  textAlign: align,
                  textVerticalAlign: vAlign
              };
          },
  
          // render weeks
          _renderWeekText: function (calendarModel, rangeData, orient, group) {
              var dayLabel = calendarModel.getModel('dayLabel');
  
              if (!dayLabel.get('show')) {
                  return;
              }
  
              var coordSys = calendarModel.coordinateSystem;
              var dayLabelStyleModel = calendarModel.getModel('dayLabel.textStyle');
              var pos = dayLabel.get('position');
              var nameMap = dayLabel.get('nameMap');
              var margin = dayLabel.get('margin');
              var firstDayOfWeek = coordSys.getFirstDayOfWeek();
  
              if (zrUtil.isString(nameMap)) {
                  nameMap = WEEK_TEXT[nameMap.toUpperCase()] || [];
              }
  
              var start = coordSys.getNextNDay(
                  rangeData.end.time, (7 - rangeData.lweek)
              ).time;
  
              var cellSize = [coordSys.getCellWidth(), coordSys.getCellHeight()];
              margin = numberUtil.parsePercent(margin, cellSize[orient === 'horizontal' ? 0 : 1]);
  
              if (pos === 'start') {
                  start = coordSys.getNextNDay(
                      rangeData.start.time, -(7 + rangeData.fweek)
                  ).time;
                  margin = -margin;
              }
  
              for (var i = 0; i < 7; i++) {
  
                  var tmpD = coordSys.getNextNDay(start, i);
                  var point = coordSys.dataToRect([tmpD.time], true).center;
                  var day = i;
                  day = Math.abs((i + firstDayOfWeek) % 7);
                  var weekText = new graphic.Text({
                      z2: 30,
                      style: zrUtil.extend({
                          text: nameMap[day],
                          font: dayLabelStyleModel.getFont(),
                          fill: dayLabelStyleModel.getTextColor()
                      }, this._weekTextPositionControl(point, orient, pos, margin, cellSize))
                  });
                  group.add(weekText);
              }
          }
      });
  });
  