LargeSymbolDraw.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. var graphic = require("../../util/graphic");
  20. var _symbol = require("../../util/symbol");
  21. var createSymbol = _symbol.createSymbol;
  22. var IncrementalDisplayable = require("zrender/lib/graphic/IncrementalDisplayable");
  23. /*
  24. * Licensed to the Apache Software Foundation (ASF) under one
  25. * or more contributor license agreements. See the NOTICE file
  26. * distributed with this work for additional information
  27. * regarding copyright ownership. The ASF licenses this file
  28. * to you under the Apache License, Version 2.0 (the
  29. * "License"); you may not use this file except in compliance
  30. * with the License. You may obtain a copy of the License at
  31. *
  32. * http://www.apache.org/licenses/LICENSE-2.0
  33. *
  34. * Unless required by applicable law or agreed to in writing,
  35. * software distributed under the License is distributed on an
  36. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  37. * KIND, either express or implied. See the License for the
  38. * specific language governing permissions and limitations
  39. * under the License.
  40. */
  41. /* global Float32Array */
  42. // TODO Batch by color
  43. var BOOST_SIZE_THRESHOLD = 4;
  44. var LargeSymbolPath = graphic.extendShape({
  45. shape: {
  46. points: null
  47. },
  48. symbolProxy: null,
  49. softClipShape: null,
  50. buildPath: function (path, shape) {
  51. var points = shape.points;
  52. var size = shape.size;
  53. var symbolProxy = this.symbolProxy;
  54. var symbolProxyShape = symbolProxy.shape;
  55. var ctx = path.getContext ? path.getContext() : path;
  56. var canBoost = ctx && size[0] < BOOST_SIZE_THRESHOLD; // Do draw in afterBrush.
  57. if (canBoost) {
  58. return;
  59. }
  60. for (var i = 0; i < points.length;) {
  61. var x = points[i++];
  62. var y = points[i++];
  63. if (isNaN(x) || isNaN(y)) {
  64. continue;
  65. }
  66. if (this.softClipShape && !this.softClipShape.contain(x, y)) {
  67. continue;
  68. }
  69. symbolProxyShape.x = x - size[0] / 2;
  70. symbolProxyShape.y = y - size[1] / 2;
  71. symbolProxyShape.width = size[0];
  72. symbolProxyShape.height = size[1];
  73. symbolProxy.buildPath(path, symbolProxyShape, true);
  74. }
  75. },
  76. afterBrush: function (ctx) {
  77. var shape = this.shape;
  78. var points = shape.points;
  79. var size = shape.size;
  80. var canBoost = size[0] < BOOST_SIZE_THRESHOLD;
  81. if (!canBoost) {
  82. return;
  83. }
  84. this.setTransform(ctx); // PENDING If style or other canvas status changed?
  85. for (var i = 0; i < points.length;) {
  86. var x = points[i++];
  87. var y = points[i++];
  88. if (isNaN(x) || isNaN(y)) {
  89. continue;
  90. }
  91. if (this.softClipShape && !this.softClipShape.contain(x, y)) {
  92. continue;
  93. } // fillRect is faster than building a rect path and draw.
  94. // And it support light globalCompositeOperation.
  95. ctx.fillRect(x - size[0] / 2, y - size[1] / 2, size[0], size[1]);
  96. }
  97. this.restoreTransform(ctx);
  98. },
  99. findDataIndex: function (x, y) {
  100. // TODO ???
  101. // Consider transform
  102. var shape = this.shape;
  103. var points = shape.points;
  104. var size = shape.size;
  105. var w = Math.max(size[0], 4);
  106. var h = Math.max(size[1], 4); // Not consider transform
  107. // Treat each element as a rect
  108. // top down traverse
  109. for (var idx = points.length / 2 - 1; idx >= 0; idx--) {
  110. var i = idx * 2;
  111. var x0 = points[i] - w / 2;
  112. var y0 = points[i + 1] - h / 2;
  113. if (x >= x0 && y >= y0 && x <= x0 + w && y <= y0 + h) {
  114. return idx;
  115. }
  116. }
  117. return -1;
  118. }
  119. });
  120. function LargeSymbolDraw() {
  121. this.group = new graphic.Group();
  122. }
  123. var largeSymbolProto = LargeSymbolDraw.prototype;
  124. largeSymbolProto.isPersistent = function () {
  125. return !this._incremental;
  126. };
  127. /**
  128. * Update symbols draw by new data
  129. * @param {module:echarts/data/List} data
  130. * @param {Object} opt
  131. * @param {Object} [opt.clipShape]
  132. */
  133. largeSymbolProto.updateData = function (data, opt) {
  134. this.group.removeAll();
  135. var symbolEl = new LargeSymbolPath({
  136. rectHover: true,
  137. cursor: 'default'
  138. });
  139. symbolEl.setShape({
  140. points: data.getLayout('symbolPoints')
  141. });
  142. this._setCommon(symbolEl, data, false, opt);
  143. this.group.add(symbolEl);
  144. this._incremental = null;
  145. };
  146. largeSymbolProto.updateLayout = function (data) {
  147. if (this._incremental) {
  148. return;
  149. }
  150. var points = data.getLayout('symbolPoints');
  151. this.group.eachChild(function (child) {
  152. if (child.startIndex != null) {
  153. var len = (child.endIndex - child.startIndex) * 2;
  154. var byteOffset = child.startIndex * 4 * 2;
  155. points = new Float32Array(points.buffer, byteOffset, len);
  156. }
  157. child.setShape('points', points);
  158. });
  159. };
  160. largeSymbolProto.incrementalPrepareUpdate = function (data) {
  161. this.group.removeAll();
  162. this._clearIncremental(); // Only use incremental displayables when data amount is larger than 2 million.
  163. // PENDING Incremental data?
  164. if (data.count() > 2e6) {
  165. if (!this._incremental) {
  166. this._incremental = new IncrementalDisplayable({
  167. silent: true
  168. });
  169. }
  170. this.group.add(this._incremental);
  171. } else {
  172. this._incremental = null;
  173. }
  174. };
  175. largeSymbolProto.incrementalUpdate = function (taskParams, data, opt) {
  176. var symbolEl;
  177. if (this._incremental) {
  178. symbolEl = new LargeSymbolPath();
  179. this._incremental.addDisplayable(symbolEl, true);
  180. } else {
  181. symbolEl = new LargeSymbolPath({
  182. rectHover: true,
  183. cursor: 'default',
  184. startIndex: taskParams.start,
  185. endIndex: taskParams.end
  186. });
  187. symbolEl.incremental = true;
  188. this.group.add(symbolEl);
  189. }
  190. symbolEl.setShape({
  191. points: data.getLayout('symbolPoints')
  192. });
  193. this._setCommon(symbolEl, data, !!this._incremental, opt);
  194. };
  195. largeSymbolProto._setCommon = function (symbolEl, data, isIncremental, opt) {
  196. var hostModel = data.hostModel;
  197. opt = opt || {}; // TODO
  198. // if (data.hasItemVisual.symbolSize) {
  199. // // TODO typed array?
  200. // symbolEl.setShape('sizes', data.mapArray(
  201. // function (idx) {
  202. // var size = data.getItemVisual(idx, 'symbolSize');
  203. // return (size instanceof Array) ? size : [size, size];
  204. // }
  205. // ));
  206. // }
  207. // else {
  208. var size = data.getVisual('symbolSize');
  209. symbolEl.setShape('size', size instanceof Array ? size : [size, size]); // }
  210. symbolEl.softClipShape = opt.clipShape || null; // Create symbolProxy to build path for each data
  211. symbolEl.symbolProxy = createSymbol(data.getVisual('symbol'), 0, 0, 0, 0); // Use symbolProxy setColor method
  212. symbolEl.setColor = symbolEl.symbolProxy.setColor;
  213. var extrudeShadow = symbolEl.shape.size[0] < BOOST_SIZE_THRESHOLD;
  214. symbolEl.useStyle( // Draw shadow when doing fillRect is extremely slow.
  215. hostModel.getModel('itemStyle').getItemStyle(extrudeShadow ? ['color', 'shadowBlur', 'shadowColor'] : ['color']));
  216. var visualColor = data.getVisual('color');
  217. if (visualColor) {
  218. symbolEl.setColor(visualColor);
  219. }
  220. if (!isIncremental) {
  221. // Enable tooltip
  222. // PENDING May have performance issue when path is extremely large
  223. symbolEl.seriesIndex = hostModel.seriesIndex;
  224. symbolEl.on('mousemove', function (e) {
  225. symbolEl.dataIndex = null;
  226. var dataIndex = symbolEl.findDataIndex(e.offsetX, e.offsetY);
  227. if (dataIndex >= 0) {
  228. // Provide dataIndex for tooltip
  229. symbolEl.dataIndex = dataIndex + (symbolEl.startIndex || 0);
  230. }
  231. });
  232. }
  233. };
  234. largeSymbolProto.remove = function () {
  235. this._clearIncremental();
  236. this._incremental = null;
  237. this.group.removeAll();
  238. };
  239. largeSymbolProto._clearIncremental = function () {
  240. var incremental = this._incremental;
  241. if (incremental) {
  242. incremental.clearDisplaybles();
  243. }
  244. };
  245. var _default = LargeSymbolDraw;
  246. module.exports = _default;