text: // TODO Batch by color
  
  import * as graphic from '../../util/graphic';
  import {createSymbol} from '../../util/symbol';
  import IncrementalDisplayable from 'zrender/src/graphic/IncrementalDisplayable';
  
  var BOOST_SIZE_THRESHOLD = 4;
  
  var LargeSymbolPath = graphic.extendShape({
  
      shape: {
          points: null
      },
  
      symbolProxy: null,
  
      buildPath: function (path, shape) {
          var points = shape.points;
          var size = shape.size;
  
          var symbolProxy = this.symbolProxy;
          var symbolProxyShape = symbolProxy.shape;
          var ctx = path.getContext ? path.getContext() : path;
          var canBoost = ctx && size[0] < BOOST_SIZE_THRESHOLD;
  
          // Do draw in afterBrush.
          if (canBoost) {
              return;
          }
  
          for (var i = 0; i < points.length;) {
              var x = points[i++];
              var y = points[i++];
  
              if (isNaN(x) || isNaN(y)) {
                  continue;
              }
  
              symbolProxyShape.x = x - size[0] / 2;
              symbolProxyShape.y = y - size[1] / 2;
              symbolProxyShape.width = size[0];
              symbolProxyShape.height = size[1];
  
              symbolProxy.buildPath(path, symbolProxyShape, true);
          }
      },
  
      afterBrush: function (ctx) {
          var shape = this.shape;
          var points = shape.points;
          var size = shape.size;
          var canBoost = size[0] < BOOST_SIZE_THRESHOLD;
  
          if (!canBoost) {
              return;
          }
  
          this.setTransform(ctx);
          // PENDING If style or other canvas status changed?
          for (var i = 0; i < points.length;) {
              var x = points[i++];
              var y = points[i++];
              if (isNaN(x) || isNaN(y)) {
                  continue;
              }
              // fillRect is faster than building a rect path and draw.
              // And it support light globalCompositeOperation.
              ctx.fillRect(
                  x - size[0] / 2, y - size[1] / 2,
                  size[0], size[1]
              );
          }
  
          this.restoreTransform(ctx);
      },
  
      findDataIndex: function (x, y) {
          // TODO ???
          // Consider transform
  
          var shape = this.shape;
          var points = shape.points;
          var size = shape.size;
  
          var w = Math.max(size[0], 4);
          var h = Math.max(size[1], 4);
  
          // Not consider transform
          // Treat each element as a rect
          // top down traverse
          for (var idx = points.length / 2 - 1; idx >= 0; idx--) {
              var i = idx * 2;
              var x0 = points[i] - w / 2;
              var y0 = points[i + 1] - h / 2;
              if (x >= x0 && y >= y0 && x <= x0 + w && y <= y0 + h) {
                  return idx;
              }
          }
  
          return -1;
      }
  });
  
  function LargeSymbolDraw() {
      this.group = new graphic.Group();
  }
  
  var largeSymbolProto = LargeSymbolDraw.prototype;
  
  largeSymbolProto.isPersistent = function () {
      return !this._incremental;
  };
  
  /**
   * Update symbols draw by new data
   * @param {module:echarts/data/List} data
   */
  largeSymbolProto.updateData = function (data) {
      this.group.removeAll();
      var symbolEl = new LargeSymbolPath({
          rectHover: true,
          cursor: 'default'
      });
  
      symbolEl.setShape({
          points: data.getLayout('symbolPoints')
      });
      this._setCommon(symbolEl, data);
      this.group.add(symbolEl);
  
      this._incremental = null;
  };
  
  largeSymbolProto.updateLayout = function (data) {
      if (this._incremental) {
          return;
      }
  
      var points = data.getLayout('symbolPoints');
      this.group.eachChild(function (child) {
          if (child.startIndex != null) {
              var len = (child.endIndex - child.startIndex) * 2;
              var byteOffset = child.startIndex * 4 * 2;
              points = new Float32Array(points.buffer, byteOffset, len);
          }
          child.setShape('points', points);
      });
  };
  
  largeSymbolProto.incrementalPrepareUpdate = function (data) {
      this.group.removeAll();
  
      this._clearIncremental();
      // Only use incremental displayables when data amount is larger than 2 million.
      // PENDING Incremental data?
      if (data.count() > 2e6) {
          if (!this._incremental) {
              this._incremental = new IncrementalDisplayable({
                  silent: true
              });
          }
          this.group.add(this._incremental);
      }
      else {
          this._incremental = null;
      }
  };
  
  largeSymbolProto.incrementalUpdate = function (taskParams, data) {
      var symbolEl;
      if (this._incremental) {
          symbolEl = new LargeSymbolPath();
          this._incremental.addDisplayable(symbolEl, true);
      }
      else {
          symbolEl = new LargeSymbolPath({
              rectHover: true,
              cursor: 'default',
              startIndex: taskParams.start,
              endIndex: taskParams.end
          });
          symbolEl.incremental = true;
          this.group.add(symbolEl);
      }
  
      symbolEl.setShape({
          points: data.getLayout('symbolPoints')
      });
      this._setCommon(symbolEl, data, !!this._incremental);
  };
  
  largeSymbolProto._setCommon = function (symbolEl, data, isIncremental) {
      var hostModel = data.hostModel;
  
      // TODO
      // if (data.hasItemVisual.symbolSize) {
      //     // TODO typed array?
      //     symbolEl.setShape('sizes', data.mapArray(
      //         function (idx) {
      //             var size = data.getItemVisual(idx, 'symbolSize');
      //             return (size instanceof Array) ? size : [size, size];
      //         }
      //     ));
      // }
      // else {
      var size = data.getVisual('symbolSize');
      symbolEl.setShape('size', (size instanceof Array) ? size : [size, size]);
      // }
  
      // Create symbolProxy to build path for each data
      symbolEl.symbolProxy = createSymbol(
          data.getVisual('symbol'), 0, 0, 0, 0
      );
      // Use symbolProxy setColor method
      symbolEl.setColor = symbolEl.symbolProxy.setColor;
  
      var extrudeShadow = symbolEl.shape.size[0] < BOOST_SIZE_THRESHOLD;
      symbolEl.useStyle(
          // Draw shadow when doing fillRect is extremely slow.
          hostModel.getModel('itemStyle').getItemStyle(extrudeShadow ? ['color', 'shadowBlur', 'shadowColor'] : ['color'])
      );
  
      var visualColor = data.getVisual('color');
      if (visualColor) {
          symbolEl.setColor(visualColor);
      }
  
      if (!isIncremental) {
          // Enable tooltip
          // PENDING May have performance issue when path is extremely large
          symbolEl.seriesIndex = hostModel.seriesIndex;
          symbolEl.on('mousemove', function (e) {
              symbolEl.dataIndex = null;
              var dataIndex = symbolEl.findDataIndex(e.offsetX, e.offsetY);
              if (dataIndex >= 0) {
                  // Provide dataIndex for tooltip
                  symbolEl.dataIndex = dataIndex + (symbolEl.startIndex || 0);
              }
          });
      }
  };
  
  largeSymbolProto.remove = function () {
      this._clearIncremental();
      this._incremental = null;
      this.group.removeAll();
  };
  
  largeSymbolProto._clearIncremental = function () {
      var incremental = this._incremental;
      if (incremental) {
          incremental.clearDisplaybles();
      }
  };
  
  export default LargeSymbolDraw;