decal.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  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. /**
  20. * AUTO-GENERATED FILE. DO NOT MODIFY.
  21. */
  22. /*
  23. * Licensed to the Apache Software Foundation (ASF) under one
  24. * or more contributor license agreements. See the NOTICE file
  25. * distributed with this work for additional information
  26. * regarding copyright ownership. The ASF licenses this file
  27. * to you under the Apache License, Version 2.0 (the
  28. * "License"); you may not use this file except in compliance
  29. * with the License. You may obtain a copy of the License at
  30. *
  31. * http://www.apache.org/licenses/LICENSE-2.0
  32. *
  33. * Unless required by applicable law or agreed to in writing,
  34. * software distributed under the License is distributed on an
  35. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  36. * KIND, either express or implied. See the License for the
  37. * specific language governing permissions and limitations
  38. * under the License.
  39. */
  40. import WeakMap from 'zrender/lib/core/WeakMap.js';
  41. import LRU from 'zrender/lib/core/LRU.js';
  42. import { defaults, map, isArray, isString, isNumber } from 'zrender/lib/core/util.js';
  43. import { getLeastCommonMultiple } from './number.js';
  44. import { createSymbol } from './symbol.js';
  45. import { brushSingle } from 'zrender/lib/canvas/graphic.js';
  46. import { platformApi } from 'zrender/lib/core/platform.js';
  47. var decalMap = new WeakMap();
  48. var decalCache = new LRU(100);
  49. var decalKeys = ['symbol', 'symbolSize', 'symbolKeepAspect', 'color', 'backgroundColor', 'dashArrayX', 'dashArrayY', 'maxTileWidth', 'maxTileHeight'];
  50. /**
  51. * Create or update pattern image from decal options
  52. *
  53. * @param {InnerDecalObject | 'none'} decalObject decal options, 'none' if no decal
  54. * @return {Pattern} pattern with generated image, null if no decal
  55. */
  56. export function createOrUpdatePatternFromDecal(decalObject, api) {
  57. if (decalObject === 'none') {
  58. return null;
  59. }
  60. var dpr = api.getDevicePixelRatio();
  61. var zr = api.getZr();
  62. var isSVG = zr.painter.type === 'svg';
  63. if (decalObject.dirty) {
  64. decalMap["delete"](decalObject);
  65. }
  66. var oldPattern = decalMap.get(decalObject);
  67. if (oldPattern) {
  68. return oldPattern;
  69. }
  70. var decalOpt = defaults(decalObject, {
  71. symbol: 'rect',
  72. symbolSize: 1,
  73. symbolKeepAspect: true,
  74. color: 'rgba(0, 0, 0, 0.2)',
  75. backgroundColor: null,
  76. dashArrayX: 5,
  77. dashArrayY: 5,
  78. rotation: 0,
  79. maxTileWidth: 512,
  80. maxTileHeight: 512
  81. });
  82. if (decalOpt.backgroundColor === 'none') {
  83. decalOpt.backgroundColor = null;
  84. }
  85. var pattern = {
  86. repeat: 'repeat'
  87. };
  88. setPatternnSource(pattern);
  89. pattern.rotation = decalOpt.rotation;
  90. pattern.scaleX = pattern.scaleY = isSVG ? 1 : 1 / dpr;
  91. decalMap.set(decalObject, pattern);
  92. decalObject.dirty = false;
  93. return pattern;
  94. function setPatternnSource(pattern) {
  95. var keys = [dpr];
  96. var isValidKey = true;
  97. for (var i = 0; i < decalKeys.length; ++i) {
  98. var value = decalOpt[decalKeys[i]];
  99. if (value != null && !isArray(value) && !isString(value) && !isNumber(value) && typeof value !== 'boolean') {
  100. isValidKey = false;
  101. break;
  102. }
  103. keys.push(value);
  104. }
  105. var cacheKey;
  106. if (isValidKey) {
  107. cacheKey = keys.join(',') + (isSVG ? '-svg' : '');
  108. var cache = decalCache.get(cacheKey);
  109. if (cache) {
  110. isSVG ? pattern.svgElement = cache : pattern.image = cache;
  111. }
  112. }
  113. var dashArrayX = normalizeDashArrayX(decalOpt.dashArrayX);
  114. var dashArrayY = normalizeDashArrayY(decalOpt.dashArrayY);
  115. var symbolArray = normalizeSymbolArray(decalOpt.symbol);
  116. var lineBlockLengthsX = getLineBlockLengthX(dashArrayX);
  117. var lineBlockLengthY = getLineBlockLengthY(dashArrayY);
  118. var canvas = !isSVG && platformApi.createCanvas();
  119. var svgRoot = isSVG && {
  120. tag: 'g',
  121. attrs: {},
  122. key: 'dcl',
  123. children: []
  124. };
  125. var pSize = getPatternSize();
  126. var ctx;
  127. if (canvas) {
  128. canvas.width = pSize.width * dpr;
  129. canvas.height = pSize.height * dpr;
  130. ctx = canvas.getContext('2d');
  131. }
  132. brushDecal();
  133. if (isValidKey) {
  134. decalCache.put(cacheKey, canvas || svgRoot);
  135. }
  136. pattern.image = canvas;
  137. pattern.svgElement = svgRoot;
  138. pattern.svgWidth = pSize.width;
  139. pattern.svgHeight = pSize.height;
  140. /**
  141. * Get minimum length that can make a repeatable pattern.
  142. *
  143. * @return {Object} pattern width and height
  144. */
  145. function getPatternSize() {
  146. /**
  147. * For example, if dash is [[3, 2], [2, 1]] for X, it looks like
  148. * |--- --- --- --- --- ...
  149. * |-- -- -- -- -- -- -- -- ...
  150. * |--- --- --- --- --- ...
  151. * |-- -- -- -- -- -- -- -- ...
  152. * So the minimum length of X is 15,
  153. * which is the least common multiple of `3 + 2` and `2 + 1`
  154. * |--- --- --- |--- --- ...
  155. * |-- -- -- -- -- |-- -- -- ...
  156. */
  157. var width = 1;
  158. for (var i = 0, xlen = lineBlockLengthsX.length; i < xlen; ++i) {
  159. width = getLeastCommonMultiple(width, lineBlockLengthsX[i]);
  160. }
  161. var symbolRepeats = 1;
  162. for (var i = 0, xlen = symbolArray.length; i < xlen; ++i) {
  163. symbolRepeats = getLeastCommonMultiple(symbolRepeats, symbolArray[i].length);
  164. }
  165. width *= symbolRepeats;
  166. var height = lineBlockLengthY * lineBlockLengthsX.length * symbolArray.length;
  167. if (process.env.NODE_ENV !== 'production') {
  168. var warn = function (attrName) {
  169. /* eslint-disable-next-line */
  170. console.warn("Calculated decal size is greater than " + attrName + " due to decal option settings so " + attrName + " is used for the decal size. Please consider changing the decal option to make a smaller decal or set " + attrName + " to be larger to avoid incontinuity.");
  171. };
  172. if (width > decalOpt.maxTileWidth) {
  173. warn('maxTileWidth');
  174. }
  175. if (height > decalOpt.maxTileHeight) {
  176. warn('maxTileHeight');
  177. }
  178. }
  179. return {
  180. width: Math.max(1, Math.min(width, decalOpt.maxTileWidth)),
  181. height: Math.max(1, Math.min(height, decalOpt.maxTileHeight))
  182. };
  183. }
  184. function brushDecal() {
  185. if (ctx) {
  186. ctx.clearRect(0, 0, canvas.width, canvas.height);
  187. if (decalOpt.backgroundColor) {
  188. ctx.fillStyle = decalOpt.backgroundColor;
  189. ctx.fillRect(0, 0, canvas.width, canvas.height);
  190. }
  191. }
  192. var ySum = 0;
  193. for (var i = 0; i < dashArrayY.length; ++i) {
  194. ySum += dashArrayY[i];
  195. }
  196. if (ySum <= 0) {
  197. // dashArrayY is 0, draw nothing
  198. return;
  199. }
  200. var y = -lineBlockLengthY;
  201. var yId = 0;
  202. var yIdTotal = 0;
  203. var xId0 = 0;
  204. while (y < pSize.height) {
  205. if (yId % 2 === 0) {
  206. var symbolYId = yIdTotal / 2 % symbolArray.length;
  207. var x = 0;
  208. var xId1 = 0;
  209. var xId1Total = 0;
  210. while (x < pSize.width * 2) {
  211. var xSum = 0;
  212. for (var i = 0; i < dashArrayX[xId0].length; ++i) {
  213. xSum += dashArrayX[xId0][i];
  214. }
  215. if (xSum <= 0) {
  216. // Skip empty line
  217. break;
  218. }
  219. // E.g., [15, 5, 20, 5] draws only for 15 and 20
  220. if (xId1 % 2 === 0) {
  221. var size = (1 - decalOpt.symbolSize) * 0.5;
  222. var left = x + dashArrayX[xId0][xId1] * size;
  223. var top_1 = y + dashArrayY[yId] * size;
  224. var width = dashArrayX[xId0][xId1] * decalOpt.symbolSize;
  225. var height = dashArrayY[yId] * decalOpt.symbolSize;
  226. var symbolXId = xId1Total / 2 % symbolArray[symbolYId].length;
  227. brushSymbol(left, top_1, width, height, symbolArray[symbolYId][symbolXId]);
  228. }
  229. x += dashArrayX[xId0][xId1];
  230. ++xId1Total;
  231. ++xId1;
  232. if (xId1 === dashArrayX[xId0].length) {
  233. xId1 = 0;
  234. }
  235. }
  236. ++xId0;
  237. if (xId0 === dashArrayX.length) {
  238. xId0 = 0;
  239. }
  240. }
  241. y += dashArrayY[yId];
  242. ++yIdTotal;
  243. ++yId;
  244. if (yId === dashArrayY.length) {
  245. yId = 0;
  246. }
  247. }
  248. function brushSymbol(x, y, width, height, symbolType) {
  249. var scale = isSVG ? 1 : dpr;
  250. var symbol = createSymbol(symbolType, x * scale, y * scale, width * scale, height * scale, decalOpt.color, decalOpt.symbolKeepAspect);
  251. if (isSVG) {
  252. var symbolVNode = zr.painter.renderOneToVNode(symbol);
  253. if (symbolVNode) {
  254. svgRoot.children.push(symbolVNode);
  255. }
  256. } else {
  257. // Paint to canvas for all other renderers.
  258. brushSingle(ctx, symbol);
  259. }
  260. }
  261. }
  262. }
  263. }
  264. /**
  265. * Convert symbol array into normalized array
  266. *
  267. * @param {string | (string | string[])[]} symbol symbol input
  268. * @return {string[][]} normolized symbol array
  269. */
  270. function normalizeSymbolArray(symbol) {
  271. if (!symbol || symbol.length === 0) {
  272. return [['rect']];
  273. }
  274. if (isString(symbol)) {
  275. return [[symbol]];
  276. }
  277. var isAllString = true;
  278. for (var i = 0; i < symbol.length; ++i) {
  279. if (!isString(symbol[i])) {
  280. isAllString = false;
  281. break;
  282. }
  283. }
  284. if (isAllString) {
  285. return normalizeSymbolArray([symbol]);
  286. }
  287. var result = [];
  288. for (var i = 0; i < symbol.length; ++i) {
  289. if (isString(symbol[i])) {
  290. result.push([symbol[i]]);
  291. } else {
  292. result.push(symbol[i]);
  293. }
  294. }
  295. return result;
  296. }
  297. /**
  298. * Convert dash input into dashArray
  299. *
  300. * @param {DecalDashArrayX} dash dash input
  301. * @return {number[][]} normolized dash array
  302. */
  303. function normalizeDashArrayX(dash) {
  304. if (!dash || dash.length === 0) {
  305. return [[0, 0]];
  306. }
  307. if (isNumber(dash)) {
  308. var dashValue = Math.ceil(dash);
  309. return [[dashValue, dashValue]];
  310. }
  311. /**
  312. * [20, 5] should be normalized into [[20, 5]],
  313. * while [20, [5, 10]] should be normalized into [[20, 20], [5, 10]]
  314. */
  315. var isAllNumber = true;
  316. for (var i = 0; i < dash.length; ++i) {
  317. if (!isNumber(dash[i])) {
  318. isAllNumber = false;
  319. break;
  320. }
  321. }
  322. if (isAllNumber) {
  323. return normalizeDashArrayX([dash]);
  324. }
  325. var result = [];
  326. for (var i = 0; i < dash.length; ++i) {
  327. if (isNumber(dash[i])) {
  328. var dashValue = Math.ceil(dash[i]);
  329. result.push([dashValue, dashValue]);
  330. } else {
  331. var dashValue = map(dash[i], function (n) {
  332. return Math.ceil(n);
  333. });
  334. if (dashValue.length % 2 === 1) {
  335. // [4, 2, 1] means |---- - -- |---- - -- |
  336. // so normalize it to be [4, 2, 1, 4, 2, 1]
  337. result.push(dashValue.concat(dashValue));
  338. } else {
  339. result.push(dashValue);
  340. }
  341. }
  342. }
  343. return result;
  344. }
  345. /**
  346. * Convert dash input into dashArray
  347. *
  348. * @param {DecalDashArrayY} dash dash input
  349. * @return {number[]} normolized dash array
  350. */
  351. function normalizeDashArrayY(dash) {
  352. if (!dash || typeof dash === 'object' && dash.length === 0) {
  353. return [0, 0];
  354. }
  355. if (isNumber(dash)) {
  356. var dashValue_1 = Math.ceil(dash);
  357. return [dashValue_1, dashValue_1];
  358. }
  359. var dashValue = map(dash, function (n) {
  360. return Math.ceil(n);
  361. });
  362. return dash.length % 2 ? dashValue.concat(dashValue) : dashValue;
  363. }
  364. /**
  365. * Get block length of each line. A block is the length of dash line and space.
  366. * For example, a line with [4, 1] has a dash line of 4 and a space of 1 after
  367. * that, so the block length of this line is 5.
  368. *
  369. * @param {number[][]} dash dash array of X or Y
  370. * @return {number[]} block length of each line
  371. */
  372. function getLineBlockLengthX(dash) {
  373. return map(dash, function (line) {
  374. return getLineBlockLengthY(line);
  375. });
  376. }
  377. function getLineBlockLengthY(dash) {
  378. var blockLength = 0;
  379. for (var i = 0; i < dash.length; ++i) {
  380. blockLength += dash[i];
  381. }
  382. if (dash.length % 2 === 1) {
  383. // [4, 2, 1] means |---- - -- |---- - -- |
  384. // So total length is (4 + 2 + 1) * 2
  385. return blockLength * 2;
  386. }
  387. return blockLength;
  388. }