decal.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  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. } // E.g., [15, 5, 20, 5] draws only for 15 and 20
  219. if (xId1 % 2 === 0) {
  220. var size = (1 - decalOpt.symbolSize) * 0.5;
  221. var left = x + dashArrayX[xId0][xId1] * size;
  222. var top_1 = y + dashArrayY[yId] * size;
  223. var width = dashArrayX[xId0][xId1] * decalOpt.symbolSize;
  224. var height = dashArrayY[yId] * decalOpt.symbolSize;
  225. var symbolXId = xId1Total / 2 % symbolArray[symbolYId].length;
  226. brushSymbol(left, top_1, width, height, symbolArray[symbolYId][symbolXId]);
  227. }
  228. x += dashArrayX[xId0][xId1];
  229. ++xId1Total;
  230. ++xId1;
  231. if (xId1 === dashArrayX[xId0].length) {
  232. xId1 = 0;
  233. }
  234. }
  235. ++xId0;
  236. if (xId0 === dashArrayX.length) {
  237. xId0 = 0;
  238. }
  239. }
  240. y += dashArrayY[yId];
  241. ++yIdTotal;
  242. ++yId;
  243. if (yId === dashArrayY.length) {
  244. yId = 0;
  245. }
  246. }
  247. function brushSymbol(x, y, width, height, symbolType) {
  248. var scale = isSVG ? 1 : dpr;
  249. var symbol = createSymbol(symbolType, x * scale, y * scale, width * scale, height * scale, decalOpt.color, decalOpt.symbolKeepAspect);
  250. if (isSVG) {
  251. var symbolVNode = zr.painter.renderOneToVNode(symbol);
  252. if (symbolVNode) {
  253. svgRoot.children.push(symbolVNode);
  254. }
  255. } else {
  256. // Paint to canvas for all other renderers.
  257. brushSingle(ctx, symbol);
  258. }
  259. }
  260. }
  261. }
  262. }
  263. /**
  264. * Convert symbol array into normalized array
  265. *
  266. * @param {string | (string | string[])[]} symbol symbol input
  267. * @return {string[][]} normolized symbol array
  268. */
  269. function normalizeSymbolArray(symbol) {
  270. if (!symbol || symbol.length === 0) {
  271. return [['rect']];
  272. }
  273. if (isString(symbol)) {
  274. return [[symbol]];
  275. }
  276. var isAllString = true;
  277. for (var i = 0; i < symbol.length; ++i) {
  278. if (!isString(symbol[i])) {
  279. isAllString = false;
  280. break;
  281. }
  282. }
  283. if (isAllString) {
  284. return normalizeSymbolArray([symbol]);
  285. }
  286. var result = [];
  287. for (var i = 0; i < symbol.length; ++i) {
  288. if (isString(symbol[i])) {
  289. result.push([symbol[i]]);
  290. } else {
  291. result.push(symbol[i]);
  292. }
  293. }
  294. return result;
  295. }
  296. /**
  297. * Convert dash input into dashArray
  298. *
  299. * @param {DecalDashArrayX} dash dash input
  300. * @return {number[][]} normolized dash array
  301. */
  302. function normalizeDashArrayX(dash) {
  303. if (!dash || dash.length === 0) {
  304. return [[0, 0]];
  305. }
  306. if (isNumber(dash)) {
  307. var dashValue = Math.ceil(dash);
  308. return [[dashValue, dashValue]];
  309. }
  310. /**
  311. * [20, 5] should be normalized into [[20, 5]],
  312. * while [20, [5, 10]] should be normalized into [[20, 20], [5, 10]]
  313. */
  314. var isAllNumber = true;
  315. for (var i = 0; i < dash.length; ++i) {
  316. if (!isNumber(dash[i])) {
  317. isAllNumber = false;
  318. break;
  319. }
  320. }
  321. if (isAllNumber) {
  322. return normalizeDashArrayX([dash]);
  323. }
  324. var result = [];
  325. for (var i = 0; i < dash.length; ++i) {
  326. if (isNumber(dash[i])) {
  327. var dashValue = Math.ceil(dash[i]);
  328. result.push([dashValue, dashValue]);
  329. } else {
  330. var dashValue = map(dash[i], function (n) {
  331. return Math.ceil(n);
  332. });
  333. if (dashValue.length % 2 === 1) {
  334. // [4, 2, 1] means |---- - -- |---- - -- |
  335. // so normalize it to be [4, 2, 1, 4, 2, 1]
  336. result.push(dashValue.concat(dashValue));
  337. } else {
  338. result.push(dashValue);
  339. }
  340. }
  341. }
  342. return result;
  343. }
  344. /**
  345. * Convert dash input into dashArray
  346. *
  347. * @param {DecalDashArrayY} dash dash input
  348. * @return {number[]} normolized dash array
  349. */
  350. function normalizeDashArrayY(dash) {
  351. if (!dash || typeof dash === 'object' && dash.length === 0) {
  352. return [0, 0];
  353. }
  354. if (isNumber(dash)) {
  355. var dashValue_1 = Math.ceil(dash);
  356. return [dashValue_1, dashValue_1];
  357. }
  358. var dashValue = map(dash, function (n) {
  359. return Math.ceil(n);
  360. });
  361. return dash.length % 2 ? dashValue.concat(dashValue) : dashValue;
  362. }
  363. /**
  364. * Get block length of each line. A block is the length of dash line and space.
  365. * For example, a line with [4, 1] has a dash line of 4 and a space of 1 after
  366. * that, so the block length of this line is 5.
  367. *
  368. * @param {number[][]} dash dash array of X or Y
  369. * @return {number[]} block length of each line
  370. */
  371. function getLineBlockLengthX(dash) {
  372. return map(dash, function (line) {
  373. return getLineBlockLengthY(line);
  374. });
  375. }
  376. function getLineBlockLengthY(dash) {
  377. var blockLength = 0;
  378. for (var i = 0; i < dash.length; ++i) {
  379. blockLength += dash[i];
  380. }
  381. if (dash.length % 2 === 1) {
  382. // [4, 2, 1] means |---- - -- |---- - -- |
  383. // So total length is (4 + 2 + 1) * 2
  384. return blockLength * 2;
  385. }
  386. return blockLength;
  387. }