You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							289 lines
						
					
					
						
							7.9 KiB
						
					
					
				
			
		
		
	
	
							289 lines
						
					
					
						
							7.9 KiB
						
					
					
				
 | 
						|
/* | 
						|
* 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 graphic = require("../../util/graphic"); | 
						|
 | 
						|
var _symbol = require("../../util/symbol"); | 
						|
 | 
						|
var createSymbol = _symbol.createSymbol; | 
						|
 | 
						|
var IncrementalDisplayable = require("zrender/lib/graphic/IncrementalDisplayable"); | 
						|
 | 
						|
/* | 
						|
* 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. | 
						|
*/ | 
						|
 | 
						|
/* global Float32Array */ | 
						|
// TODO Batch by color | 
						|
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(); | 
						|
  } | 
						|
}; | 
						|
 | 
						|
var _default = LargeSymbolDraw; | 
						|
module.exports = _default; |