| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659 | 
/** Licensed to the Apache Software Foundation (ASF) under one* or more contributor license agreements.  See the NOTICE file* distributed with this work for additional information* regarding copyright ownership.  The ASF licenses this file* to you under the Apache License, Version 2.0 (the* "License"); you may not use this file except in compliance* with the License.  You may obtain a copy of the License at**   http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing,* software distributed under the License is distributed on an* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY* KIND, either express or implied.  See the License for the* specific language governing permissions and limitations* under the License.*/var zrUtil = require("zrender/lib/core/util");var BoundingRect = require("zrender/lib/core/BoundingRect");var matrix = require("zrender/lib/core/matrix");var graphic = require("../../util/graphic");var layout = require("../../util/layout");var TimelineView = require("./TimelineView");var TimelineAxis = require("./TimelineAxis");var _symbol = require("../../util/symbol");var createSymbol = _symbol.createSymbol;var axisHelper = require("../../coord/axisHelper");var numberUtil = require("../../util/number");var _format = require("../../util/format");var encodeHTML = _format.encodeHTML;/** Licensed to the Apache Software Foundation (ASF) under one* or more contributor license agreements.  See the NOTICE file* distributed with this work for additional information* regarding copyright ownership.  The ASF licenses this file* to you under the Apache License, Version 2.0 (the* "License"); you may not use this file except in compliance* with the License.  You may obtain a copy of the License at**   http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing,* software distributed under the License is distributed on an* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY* KIND, either express or implied.  See the License for the* specific language governing permissions and limitations* under the License.*/var bind = zrUtil.bind;var each = zrUtil.each;var PI = Math.PI;var _default = TimelineView.extend({  type: 'timeline.slider',  init: function (ecModel, api) {    this.api = api;    /**     * @private     * @type {module:echarts/component/timeline/TimelineAxis}     */    this._axis;    /**     * @private     * @type {module:zrender/core/BoundingRect}     */    this._viewRect;    /**     * @type {number}     */    this._timer;    /**     * @type {module:zrender/Element}     */    this._currentPointer;    /**     * @type {module:zrender/container/Group}     */    this._mainGroup;    /**     * @type {module:zrender/container/Group}     */    this._labelGroup;  },  /**   * @override   */  render: function (timelineModel, ecModel, api, payload) {    this.model = timelineModel;    this.api = api;    this.ecModel = ecModel;    this.group.removeAll();    if (timelineModel.get('show', true)) {      var layoutInfo = this._layout(timelineModel, api);      var mainGroup = this._createGroup('mainGroup');      var labelGroup = this._createGroup('labelGroup');      /**       * @private       * @type {module:echarts/component/timeline/TimelineAxis}       */      var axis = this._axis = this._createAxis(layoutInfo, timelineModel);      timelineModel.formatTooltip = function (dataIndex) {        return encodeHTML(axis.scale.getLabel(dataIndex));      };      each(['AxisLine', 'AxisTick', 'Control', 'CurrentPointer'], function (name) {        this['_render' + name](layoutInfo, mainGroup, axis, timelineModel);      }, this);      this._renderAxisLabel(layoutInfo, labelGroup, axis, timelineModel);      this._position(layoutInfo, timelineModel);    }    this._doPlayStop();  },  /**   * @override   */  remove: function () {    this._clearTimer();    this.group.removeAll();  },  /**   * @override   */  dispose: function () {    this._clearTimer();  },  _layout: function (timelineModel, api) {    var labelPosOpt = timelineModel.get('label.position');    var orient = timelineModel.get('orient');    var viewRect = getViewRect(timelineModel, api); // Auto label offset.    if (labelPosOpt == null || labelPosOpt === 'auto') {      labelPosOpt = orient === 'horizontal' ? viewRect.y + viewRect.height / 2 < api.getHeight() / 2 ? '-' : '+' : viewRect.x + viewRect.width / 2 < api.getWidth() / 2 ? '+' : '-';    } else if (isNaN(labelPosOpt)) {      labelPosOpt = {        horizontal: {          top: '-',          bottom: '+'        },        vertical: {          left: '-',          right: '+'        }      }[orient][labelPosOpt];    }    var labelAlignMap = {      horizontal: 'center',      vertical: labelPosOpt >= 0 || labelPosOpt === '+' ? 'left' : 'right'    };    var labelBaselineMap = {      horizontal: labelPosOpt >= 0 || labelPosOpt === '+' ? 'top' : 'bottom',      vertical: 'middle'    };    var rotationMap = {      horizontal: 0,      vertical: PI / 2    }; // Position    var mainLength = orient === 'vertical' ? viewRect.height : viewRect.width;    var controlModel = timelineModel.getModel('controlStyle');    var showControl = controlModel.get('show', true);    var controlSize = showControl ? controlModel.get('itemSize') : 0;    var controlGap = showControl ? controlModel.get('itemGap') : 0;    var sizePlusGap = controlSize + controlGap; // Special label rotate.    var labelRotation = timelineModel.get('label.rotate') || 0;    labelRotation = labelRotation * PI / 180; // To radian.    var playPosition;    var prevBtnPosition;    var nextBtnPosition;    var axisExtent;    var controlPosition = controlModel.get('position', true);    var showPlayBtn = showControl && controlModel.get('showPlayBtn', true);    var showPrevBtn = showControl && controlModel.get('showPrevBtn', true);    var showNextBtn = showControl && controlModel.get('showNextBtn', true);    var xLeft = 0;    var xRight = mainLength; // position[0] means left, position[1] means middle.    if (controlPosition === 'left' || controlPosition === 'bottom') {      showPlayBtn && (playPosition = [0, 0], xLeft += sizePlusGap);      showPrevBtn && (prevBtnPosition = [xLeft, 0], xLeft += sizePlusGap);      showNextBtn && (nextBtnPosition = [xRight - controlSize, 0], xRight -= sizePlusGap);    } else {      // 'top' 'right'      showPlayBtn && (playPosition = [xRight - controlSize, 0], xRight -= sizePlusGap);      showPrevBtn && (prevBtnPosition = [0, 0], xLeft += sizePlusGap);      showNextBtn && (nextBtnPosition = [xRight - controlSize, 0], xRight -= sizePlusGap);    }    axisExtent = [xLeft, xRight];    if (timelineModel.get('inverse')) {      axisExtent.reverse();    }    return {      viewRect: viewRect,      mainLength: mainLength,      orient: orient,      rotation: rotationMap[orient],      labelRotation: labelRotation,      labelPosOpt: labelPosOpt,      labelAlign: timelineModel.get('label.align') || labelAlignMap[orient],      labelBaseline: timelineModel.get('label.verticalAlign') || timelineModel.get('label.baseline') || labelBaselineMap[orient],      // Based on mainGroup.      playPosition: playPosition,      prevBtnPosition: prevBtnPosition,      nextBtnPosition: nextBtnPosition,      axisExtent: axisExtent,      controlSize: controlSize,      controlGap: controlGap    };  },  _position: function (layoutInfo, timelineModel) {    // Position is be called finally, because bounding rect is needed for    // adapt content to fill viewRect (auto adapt offset).    // Timeline may be not all in the viewRect when 'offset' is specified    // as a number, because it is more appropriate that label aligns at    // 'offset' but not the other edge defined by viewRect.    var mainGroup = this._mainGroup;    var labelGroup = this._labelGroup;    var viewRect = layoutInfo.viewRect;    if (layoutInfo.orient === 'vertical') {      // transform to horizontal, inverse rotate by left-top point.      var m = matrix.create();      var rotateOriginX = viewRect.x;      var rotateOriginY = viewRect.y + viewRect.height;      matrix.translate(m, m, [-rotateOriginX, -rotateOriginY]);      matrix.rotate(m, m, -PI / 2);      matrix.translate(m, m, [rotateOriginX, rotateOriginY]);      viewRect = viewRect.clone();      viewRect.applyTransform(m);    }    var viewBound = getBound(viewRect);    var mainBound = getBound(mainGroup.getBoundingRect());    var labelBound = getBound(labelGroup.getBoundingRect());    var mainPosition = mainGroup.position;    var labelsPosition = labelGroup.position;    labelsPosition[0] = mainPosition[0] = viewBound[0][0];    var labelPosOpt = layoutInfo.labelPosOpt;    if (isNaN(labelPosOpt)) {      // '+' or '-'      var mainBoundIdx = labelPosOpt === '+' ? 0 : 1;      toBound(mainPosition, mainBound, viewBound, 1, mainBoundIdx);      toBound(labelsPosition, labelBound, viewBound, 1, 1 - mainBoundIdx);    } else {      var mainBoundIdx = labelPosOpt >= 0 ? 0 : 1;      toBound(mainPosition, mainBound, viewBound, 1, mainBoundIdx);      labelsPosition[1] = mainPosition[1] + labelPosOpt;    }    mainGroup.attr('position', mainPosition);    labelGroup.attr('position', labelsPosition);    mainGroup.rotation = labelGroup.rotation = layoutInfo.rotation;    setOrigin(mainGroup);    setOrigin(labelGroup);    function setOrigin(targetGroup) {      var pos = targetGroup.position;      targetGroup.origin = [viewBound[0][0] - pos[0], viewBound[1][0] - pos[1]];    }    function getBound(rect) {      // [[xmin, xmax], [ymin, ymax]]      return [[rect.x, rect.x + rect.width], [rect.y, rect.y + rect.height]];    }    function toBound(fromPos, from, to, dimIdx, boundIdx) {      fromPos[dimIdx] += to[dimIdx][boundIdx] - from[dimIdx][boundIdx];    }  },  _createAxis: function (layoutInfo, timelineModel) {    var data = timelineModel.getData();    var axisType = timelineModel.get('axisType');    var scale = axisHelper.createScaleByModel(timelineModel, axisType); // Customize scale. The `tickValue` is `dataIndex`.    scale.getTicks = function () {      return data.mapArray(['value'], function (value) {        return value;      });    };    var dataExtent = data.getDataExtent('value');    scale.setExtent(dataExtent[0], dataExtent[1]);    scale.niceTicks();    var axis = new TimelineAxis('value', scale, layoutInfo.axisExtent, axisType);    axis.model = timelineModel;    return axis;  },  _createGroup: function (name) {    var newGroup = this['_' + name] = new graphic.Group();    this.group.add(newGroup);    return newGroup;  },  _renderAxisLine: function (layoutInfo, group, axis, timelineModel) {    var axisExtent = axis.getExtent();    if (!timelineModel.get('lineStyle.show')) {      return;    }    group.add(new graphic.Line({      shape: {        x1: axisExtent[0],        y1: 0,        x2: axisExtent[1],        y2: 0      },      style: zrUtil.extend({        lineCap: 'round'      }, timelineModel.getModel('lineStyle').getLineStyle()),      silent: true,      z2: 1    }));  },  /**   * @private   */  _renderAxisTick: function (layoutInfo, group, axis, timelineModel) {    var data = timelineModel.getData(); // Show all ticks, despite ignoring strategy.    var ticks = axis.scale.getTicks(); // The value is dataIndex, see the costomized scale.    each(ticks, function (value) {      var tickCoord = axis.dataToCoord(value);      var itemModel = data.getItemModel(value);      var itemStyleModel = itemModel.getModel('itemStyle');      var hoverStyleModel = itemModel.getModel('emphasis.itemStyle');      var symbolOpt = {        position: [tickCoord, 0],        onclick: bind(this._changeTimeline, this, value)      };      var el = giveSymbol(itemModel, itemStyleModel, group, symbolOpt);      graphic.setHoverStyle(el, hoverStyleModel.getItemStyle());      if (itemModel.get('tooltip')) {        el.dataIndex = value;        el.dataModel = timelineModel;      } else {        el.dataIndex = el.dataModel = null;      }    }, this);  },  /**   * @private   */  _renderAxisLabel: function (layoutInfo, group, axis, timelineModel) {    var labelModel = axis.getLabelModel();    if (!labelModel.get('show')) {      return;    }    var data = timelineModel.getData();    var labels = axis.getViewLabels();    each(labels, function (labelItem) {      // The tickValue is dataIndex, see the costomized scale.      var dataIndex = labelItem.tickValue;      var itemModel = data.getItemModel(dataIndex);      var normalLabelModel = itemModel.getModel('label');      var hoverLabelModel = itemModel.getModel('emphasis.label');      var tickCoord = axis.dataToCoord(labelItem.tickValue);      var textEl = new graphic.Text({        position: [tickCoord, 0],        rotation: layoutInfo.labelRotation - layoutInfo.rotation,        onclick: bind(this._changeTimeline, this, dataIndex),        silent: false      });      graphic.setTextStyle(textEl.style, normalLabelModel, {        text: labelItem.formattedLabel,        textAlign: layoutInfo.labelAlign,        textVerticalAlign: layoutInfo.labelBaseline      });      group.add(textEl);      graphic.setHoverStyle(textEl, graphic.setTextStyle({}, hoverLabelModel));    }, this);  },  /**   * @private   */  _renderControl: function (layoutInfo, group, axis, timelineModel) {    var controlSize = layoutInfo.controlSize;    var rotation = layoutInfo.rotation;    var itemStyle = timelineModel.getModel('controlStyle').getItemStyle();    var hoverStyle = timelineModel.getModel('emphasis.controlStyle').getItemStyle();    var rect = [0, -controlSize / 2, controlSize, controlSize];    var playState = timelineModel.getPlayState();    var inverse = timelineModel.get('inverse', true);    makeBtn(layoutInfo.nextBtnPosition, 'controlStyle.nextIcon', bind(this._changeTimeline, this, inverse ? '-' : '+'));    makeBtn(layoutInfo.prevBtnPosition, 'controlStyle.prevIcon', bind(this._changeTimeline, this, inverse ? '+' : '-'));    makeBtn(layoutInfo.playPosition, 'controlStyle.' + (playState ? 'stopIcon' : 'playIcon'), bind(this._handlePlayClick, this, !playState), true);    function makeBtn(position, iconPath, onclick, willRotate) {      if (!position) {        return;      }      var opt = {        position: position,        origin: [controlSize / 2, 0],        rotation: willRotate ? -rotation : 0,        rectHover: true,        style: itemStyle,        onclick: onclick      };      var btn = makeIcon(timelineModel, iconPath, rect, opt);      group.add(btn);      graphic.setHoverStyle(btn, hoverStyle);    }  },  _renderCurrentPointer: function (layoutInfo, group, axis, timelineModel) {    var data = timelineModel.getData();    var currentIndex = timelineModel.getCurrentIndex();    var pointerModel = data.getItemModel(currentIndex).getModel('checkpointStyle');    var me = this;    var callback = {      onCreate: function (pointer) {        pointer.draggable = true;        pointer.drift = bind(me._handlePointerDrag, me);        pointer.ondragend = bind(me._handlePointerDragend, me);        pointerMoveTo(pointer, currentIndex, axis, timelineModel, true);      },      onUpdate: function (pointer) {        pointerMoveTo(pointer, currentIndex, axis, timelineModel);      }    }; // Reuse when exists, for animation and drag.    this._currentPointer = giveSymbol(pointerModel, pointerModel, this._mainGroup, {}, this._currentPointer, callback);  },  _handlePlayClick: function (nextState) {    this._clearTimer();    this.api.dispatchAction({      type: 'timelinePlayChange',      playState: nextState,      from: this.uid    });  },  _handlePointerDrag: function (dx, dy, e) {    this._clearTimer();    this._pointerChangeTimeline([e.offsetX, e.offsetY]);  },  _handlePointerDragend: function (e) {    this._pointerChangeTimeline([e.offsetX, e.offsetY], true);  },  _pointerChangeTimeline: function (mousePos, trigger) {    var toCoord = this._toAxisCoord(mousePos)[0];    var axis = this._axis;    var axisExtent = numberUtil.asc(axis.getExtent().slice());    toCoord > axisExtent[1] && (toCoord = axisExtent[1]);    toCoord < axisExtent[0] && (toCoord = axisExtent[0]);    this._currentPointer.position[0] = toCoord;    this._currentPointer.dirty();    var targetDataIndex = this._findNearestTick(toCoord);    var timelineModel = this.model;    if (trigger || targetDataIndex !== timelineModel.getCurrentIndex() && timelineModel.get('realtime')) {      this._changeTimeline(targetDataIndex);    }  },  _doPlayStop: function () {    this._clearTimer();    if (this.model.getPlayState()) {      this._timer = setTimeout(bind(handleFrame, this), this.model.get('playInterval'));    }    function handleFrame() {      // Do not cache      var timelineModel = this.model;      this._changeTimeline(timelineModel.getCurrentIndex() + (timelineModel.get('rewind', true) ? -1 : 1));    }  },  _toAxisCoord: function (vertex) {    var trans = this._mainGroup.getLocalTransform();    return graphic.applyTransform(vertex, trans, true);  },  _findNearestTick: function (axisCoord) {    var data = this.model.getData();    var dist = Infinity;    var targetDataIndex;    var axis = this._axis;    data.each(['value'], function (value, dataIndex) {      var coord = axis.dataToCoord(value);      var d = Math.abs(coord - axisCoord);      if (d < dist) {        dist = d;        targetDataIndex = dataIndex;      }    });    return targetDataIndex;  },  _clearTimer: function () {    if (this._timer) {      clearTimeout(this._timer);      this._timer = null;    }  },  _changeTimeline: function (nextIndex) {    var currentIndex = this.model.getCurrentIndex();    if (nextIndex === '+') {      nextIndex = currentIndex + 1;    } else if (nextIndex === '-') {      nextIndex = currentIndex - 1;    }    this.api.dispatchAction({      type: 'timelineChange',      currentIndex: nextIndex,      from: this.uid    });  }});function getViewRect(model, api) {  return layout.getLayoutRect(model.getBoxLayoutParams(), {    width: api.getWidth(),    height: api.getHeight()  }, model.get('padding'));}function makeIcon(timelineModel, objPath, rect, opts) {  var style = opts.style;  var icon = graphic.createIcon(timelineModel.get(objPath), opts || {}, new BoundingRect(rect[0], rect[1], rect[2], rect[3])); // TODO createIcon won't use style in opt.  if (style) {    icon.setStyle(style);  }  return icon;}/** * Create symbol or update symbol * opt: basic position and event handlers */function giveSymbol(hostModel, itemStyleModel, group, opt, symbol, callback) {  var color = itemStyleModel.get('color');  if (!symbol) {    var symbolType = hostModel.get('symbol');    symbol = createSymbol(symbolType, -1, -1, 2, 2, color);    symbol.setStyle('strokeNoScale', true);    group.add(symbol);    callback && callback.onCreate(symbol);  } else {    symbol.setColor(color);    group.add(symbol); // Group may be new, also need to add.    callback && callback.onUpdate(symbol);  } // Style  var itemStyle = itemStyleModel.getItemStyle(['color', 'symbol', 'symbolSize']);  symbol.setStyle(itemStyle); // Transform and events.  opt = zrUtil.merge({    rectHover: true,    z2: 100  }, opt, true);  var symbolSize = hostModel.get('symbolSize');  symbolSize = symbolSize instanceof Array ? symbolSize.slice() : [+symbolSize, +symbolSize];  symbolSize[0] /= 2;  symbolSize[1] /= 2;  opt.scale = symbolSize;  var symbolOffset = hostModel.get('symbolOffset');  if (symbolOffset) {    var pos = opt.position = opt.position || [0, 0];    pos[0] += numberUtil.parsePercent(symbolOffset[0], symbolSize[0]);    pos[1] += numberUtil.parsePercent(symbolOffset[1], symbolSize[1]);  }  var symbolRotate = hostModel.get('symbolRotate');  opt.rotation = (symbolRotate || 0) * Math.PI / 180 || 0;  symbol.attr(opt); // FIXME  // (1) When symbol.style.strokeNoScale is true and updateTransform is not performed,  // getBoundingRect will return wrong result.  // (This is supposed to be resolved in zrender, but it is a little difficult to  // leverage performance and auto updateTransform)  // (2) All of ancesters of symbol do not scale, so we can just updateTransform symbol.  symbol.updateTransform();  return symbol;}function pointerMoveTo(pointer, dataIndex, axis, timelineModel, noAnimation) {  if (pointer.dragging) {    return;  }  var pointerModel = timelineModel.getModel('checkpointStyle');  var toCoord = axis.dataToCoord(timelineModel.getData().get(['value'], dataIndex));  if (noAnimation || !pointerModel.get('animation', true)) {    pointer.attr({      position: [toCoord, 0]    });  } else {    pointer.stopAnimation(true);    pointer.animateTo({      position: [toCoord, 0]    }, pointerModel.get('animationDuration', true), pointerModel.get('animationEasing', true));  }}module.exports = _default;
 |