decal.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  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';
  41. import LRU from 'zrender/lib/core/LRU';
  42. import { defaults, createCanvas, map, isArray } from 'zrender/lib/core/util';
  43. import { getLeastCommonMultiple } from './number';
  44. import { createSymbol } from './symbol';
  45. import { brushSingle } from 'zrender/lib/canvas/graphic';
  46. var decalMap = new WeakMap();
  47. var decalCache = new LRU(100);
  48. var decalKeys = ['symbol', 'symbolSize', 'symbolKeepAspect', 'color', 'backgroundColor', 'dashArrayX', 'dashArrayY', 'maxTileWidth', 'maxTileHeight'];
  49. /**
  50. * Create or update pattern image from decal options
  51. *
  52. * @param {InnerDecalObject | 'none'} decalObject decal options, 'none' if no decal
  53. * @return {Pattern} pattern with generated image, null if no decal
  54. */
  55. export function createOrUpdatePatternFromDecal(decalObject, api) {
  56. if (decalObject === 'none') {
  57. return null;
  58. }
  59. var dpr = api.getDevicePixelRatio();
  60. var zr = api.getZr();
  61. var isSVG = zr.painter.type === 'svg';
  62. if (decalObject.dirty) {
  63. decalMap["delete"](decalObject);
  64. }
  65. var oldPattern = decalMap.get(decalObject);
  66. if (oldPattern) {
  67. return oldPattern;
  68. }
  69. var decalOpt = defaults(decalObject, {
  70. symbol: 'rect',
  71. symbolSize: 1,
  72. symbolKeepAspect: true,
  73. color: 'rgba(0, 0, 0, 0.2)',
  74. backgroundColor: null,
  75. dashArrayX: 5,
  76. dashArrayY: 5,
  77. rotation: 0,
  78. maxTileWidth: 512,
  79. maxTileHeight: 512
  80. });
  81. if (decalOpt.backgroundColor === 'none') {
  82. decalOpt.backgroundColor = null;
  83. }
  84. var pattern = {
  85. repeat: 'repeat'
  86. };
  87. setPatternnSource(pattern);
  88. pattern.rotation = decalOpt.rotation;
  89. pattern.scaleX = pattern.scaleY = isSVG ? 1 : 1 / dpr;
  90. decalMap.set(decalObject, pattern);
  91. decalObject.dirty = false;
  92. return pattern;
  93. function setPatternnSource(pattern) {
  94. var keys = [dpr];
  95. var isValidKey = true;
  96. for (var i = 0; i < decalKeys.length; ++i) {
  97. var value = decalOpt[decalKeys[i]];
  98. var valueType = typeof value;
  99. if (value != null && !isArray(value) && valueType !== 'string' && valueType !== 'number' && valueType !== '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 && createCanvas();
  119. var svgRoot = isSVG && zr.painter.createSVGElement('g');
  120. var pSize = getPatternSize();
  121. var ctx;
  122. if (canvas) {
  123. canvas.width = pSize.width * dpr;
  124. canvas.height = pSize.height * dpr;
  125. ctx = canvas.getContext('2d');
  126. }
  127. brushDecal();
  128. if (isValidKey) {
  129. decalCache.put(cacheKey, canvas || svgRoot);
  130. }
  131. pattern.image = canvas;
  132. pattern.svgElement = svgRoot;
  133. pattern.svgWidth = pSize.width;
  134. pattern.svgHeight = pSize.height;
  135. /**
  136. * Get minumum length that can make a repeatable pattern.
  137. *
  138. * @return {Object} pattern width and height
  139. */
  140. function getPatternSize() {
  141. /**
  142. * For example, if dash is [[3, 2], [2, 1]] for X, it looks like
  143. * |--- --- --- --- --- ...
  144. * |-- -- -- -- -- -- -- -- ...
  145. * |--- --- --- --- --- ...
  146. * |-- -- -- -- -- -- -- -- ...
  147. * So the minumum length of X is 15,
  148. * which is the least common multiple of `3 + 2` and `2 + 1`
  149. * |--- --- --- |--- --- ...
  150. * |-- -- -- -- -- |-- -- -- ...
  151. */
  152. var width = 1;
  153. for (var i = 0, xlen = lineBlockLengthsX.length; i < xlen; ++i) {
  154. width = getLeastCommonMultiple(width, lineBlockLengthsX[i]);
  155. }
  156. var symbolRepeats = 1;
  157. for (var i = 0, xlen = symbolArray.length; i < xlen; ++i) {
  158. symbolRepeats = getLeastCommonMultiple(symbolRepeats, symbolArray[i].length);
  159. }
  160. width *= symbolRepeats;
  161. var height = lineBlockLengthY * lineBlockLengthsX.length * symbolArray.length;
  162. if (process.env.NODE_ENV !== 'production') {
  163. var warn = function (attrName) {
  164. /* eslint-disable-next-line */
  165. 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.");
  166. };
  167. if (width > decalOpt.maxTileWidth) {
  168. warn('maxTileWidth');
  169. }
  170. if (height > decalOpt.maxTileHeight) {
  171. warn('maxTileHeight');
  172. }
  173. }
  174. return {
  175. width: Math.max(1, Math.min(width, decalOpt.maxTileWidth)),
  176. height: Math.max(1, Math.min(height, decalOpt.maxTileHeight))
  177. };
  178. }
  179. function brushDecal() {
  180. if (ctx) {
  181. ctx.clearRect(0, 0, canvas.width, canvas.height);
  182. if (decalOpt.backgroundColor) {
  183. ctx.fillStyle = decalOpt.backgroundColor;
  184. ctx.fillRect(0, 0, canvas.width, canvas.height);
  185. }
  186. }
  187. var ySum = 0;
  188. for (var i = 0; i < dashArrayY.length; ++i) {
  189. ySum += dashArrayY[i];
  190. }
  191. if (ySum <= 0) {
  192. // dashArrayY is 0, draw nothing
  193. return;
  194. }
  195. var y = -lineBlockLengthY;
  196. var yId = 0;
  197. var yIdTotal = 0;
  198. var xId0 = 0;
  199. while (y < pSize.height) {
  200. if (yId % 2 === 0) {
  201. var symbolYId = yIdTotal / 2 % symbolArray.length;
  202. var x = 0;
  203. var xId1 = 0;
  204. var xId1Total = 0;
  205. while (x < pSize.width * 2) {
  206. var xSum = 0;
  207. for (var i = 0; i < dashArrayX[xId0].length; ++i) {
  208. xSum += dashArrayX[xId0][i];
  209. }
  210. if (xSum <= 0) {
  211. // Skip empty line
  212. break;
  213. } // E.g., [15, 5, 20, 5] draws only for 15 and 20
  214. if (xId1 % 2 === 0) {
  215. var size = (1 - decalOpt.symbolSize) * 0.5;
  216. var left = x + dashArrayX[xId0][xId1] * size;
  217. var top_1 = y + dashArrayY[yId] * size;
  218. var width = dashArrayX[xId0][xId1] * decalOpt.symbolSize;
  219. var height = dashArrayY[yId] * decalOpt.symbolSize;
  220. var symbolXId = xId1Total / 2 % symbolArray[symbolYId].length;
  221. brushSymbol(left, top_1, width, height, symbolArray[symbolYId][symbolXId]);
  222. }
  223. x += dashArrayX[xId0][xId1];
  224. ++xId1Total;
  225. ++xId1;
  226. if (xId1 === dashArrayX[xId0].length) {
  227. xId1 = 0;
  228. }
  229. }
  230. ++xId0;
  231. if (xId0 === dashArrayX.length) {
  232. xId0 = 0;
  233. }
  234. }
  235. y += dashArrayY[yId];
  236. ++yIdTotal;
  237. ++yId;
  238. if (yId === dashArrayY.length) {
  239. yId = 0;
  240. }
  241. }
  242. function brushSymbol(x, y, width, height, symbolType) {
  243. var scale = isSVG ? 1 : dpr;
  244. var symbol = createSymbol(symbolType, x * scale, y * scale, width * scale, height * scale, decalOpt.color, decalOpt.symbolKeepAspect);
  245. if (isSVG) {
  246. svgRoot.appendChild(zr.painter.paintOne(symbol));
  247. } else {
  248. // Paint to canvas for all other renderers.
  249. brushSingle(ctx, symbol);
  250. }
  251. }
  252. }
  253. }
  254. }
  255. /**
  256. * Convert symbol array into normalized array
  257. *
  258. * @param {string | (string | string[])[]} symbol symbol input
  259. * @return {string[][]} normolized symbol array
  260. */
  261. function normalizeSymbolArray(symbol) {
  262. if (!symbol || symbol.length === 0) {
  263. return [['rect']];
  264. }
  265. if (typeof symbol === 'string') {
  266. return [[symbol]];
  267. }
  268. var isAllString = true;
  269. for (var i = 0; i < symbol.length; ++i) {
  270. if (typeof symbol[i] !== 'string') {
  271. isAllString = false;
  272. break;
  273. }
  274. }
  275. if (isAllString) {
  276. return normalizeSymbolArray([symbol]);
  277. }
  278. var result = [];
  279. for (var i = 0; i < symbol.length; ++i) {
  280. if (typeof symbol[i] === 'string') {
  281. result.push([symbol[i]]);
  282. } else {
  283. result.push(symbol[i]);
  284. }
  285. }
  286. return result;
  287. }
  288. /**
  289. * Convert dash input into dashArray
  290. *
  291. * @param {DecalDashArrayX} dash dash input
  292. * @return {number[][]} normolized dash array
  293. */
  294. function normalizeDashArrayX(dash) {
  295. if (!dash || dash.length === 0) {
  296. return [[0, 0]];
  297. }
  298. if (typeof dash === 'number') {
  299. var dashValue = Math.ceil(dash);
  300. return [[dashValue, dashValue]];
  301. }
  302. /**
  303. * [20, 5] should be normalized into [[20, 5]],
  304. * while [20, [5, 10]] should be normalized into [[20, 20], [5, 10]]
  305. */
  306. var isAllNumber = true;
  307. for (var i = 0; i < dash.length; ++i) {
  308. if (typeof dash[i] !== 'number') {
  309. isAllNumber = false;
  310. break;
  311. }
  312. }
  313. if (isAllNumber) {
  314. return normalizeDashArrayX([dash]);
  315. }
  316. var result = [];
  317. for (var i = 0; i < dash.length; ++i) {
  318. if (typeof dash[i] === 'number') {
  319. var dashValue = Math.ceil(dash[i]);
  320. result.push([dashValue, dashValue]);
  321. } else {
  322. var dashValue = map(dash[i], function (n) {
  323. return Math.ceil(n);
  324. });
  325. if (dashValue.length % 2 === 1) {
  326. // [4, 2, 1] means |---- - -- |---- - -- |
  327. // so normalize it to be [4, 2, 1, 4, 2, 1]
  328. result.push(dashValue.concat(dashValue));
  329. } else {
  330. result.push(dashValue);
  331. }
  332. }
  333. }
  334. return result;
  335. }
  336. /**
  337. * Convert dash input into dashArray
  338. *
  339. * @param {DecalDashArrayY} dash dash input
  340. * @return {number[]} normolized dash array
  341. */
  342. function normalizeDashArrayY(dash) {
  343. if (!dash || typeof dash === 'object' && dash.length === 0) {
  344. return [0, 0];
  345. }
  346. if (typeof dash === 'number') {
  347. var dashValue_1 = Math.ceil(dash);
  348. return [dashValue_1, dashValue_1];
  349. }
  350. var dashValue = map(dash, function (n) {
  351. return Math.ceil(n);
  352. });
  353. return dash.length % 2 ? dashValue.concat(dashValue) : dashValue;
  354. }
  355. /**
  356. * Get block length of each line. A block is the length of dash line and space.
  357. * For example, a line with [4, 1] has a dash line of 4 and a space of 1 after
  358. * that, so the block length of this line is 5.
  359. *
  360. * @param {number[][]} dash dash arrary of X or Y
  361. * @return {number[]} block length of each line
  362. */
  363. function getLineBlockLengthX(dash) {
  364. return map(dash, function (line) {
  365. return getLineBlockLengthY(line);
  366. });
  367. }
  368. function getLineBlockLengthY(dash) {
  369. var blockLength = 0;
  370. for (var i = 0; i < dash.length; ++i) {
  371. blockLength += dash[i];
  372. }
  373. if (dash.length % 2 === 1) {
  374. // [4, 2, 1] means |---- - -- |---- - -- |
  375. // So total length is (4 + 2 + 1) * 2
  376. return blockLength * 2;
  377. }
  378. return blockLength;
  379. }