Painter.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. var _core = require("./core");
  2. var createElement = _core.createElement;
  3. var util = require("../core/util");
  4. var logError = require("../core/log");
  5. var Path = require("../graphic/Path");
  6. var ZImage = require("../graphic/Image");
  7. var ZText = require("../graphic/Text");
  8. var arrayDiff = require("../core/arrayDiff2");
  9. var GradientManager = require("./helper/GradientManager");
  10. var ClippathManager = require("./helper/ClippathManager");
  11. var ShadowManager = require("./helper/ShadowManager");
  12. var _graphic = require("./graphic");
  13. var svgPath = _graphic.path;
  14. var svgImage = _graphic.image;
  15. var svgText = _graphic.text;
  16. /**
  17. * SVG Painter
  18. * @module zrender/svg/Painter
  19. */
  20. function parseInt10(val) {
  21. return parseInt(val, 10);
  22. }
  23. function getSvgProxy(el) {
  24. if (el instanceof Path) {
  25. return svgPath;
  26. } else if (el instanceof ZImage) {
  27. return svgImage;
  28. } else if (el instanceof ZText) {
  29. return svgText;
  30. } else {
  31. return svgPath;
  32. }
  33. }
  34. function checkParentAvailable(parent, child) {
  35. return child && parent && child.parentNode !== parent;
  36. }
  37. function insertAfter(parent, child, prevSibling) {
  38. if (checkParentAvailable(parent, child) && prevSibling) {
  39. var nextSibling = prevSibling.nextSibling;
  40. nextSibling ? parent.insertBefore(child, nextSibling) : parent.appendChild(child);
  41. }
  42. }
  43. function prepend(parent, child) {
  44. if (checkParentAvailable(parent, child)) {
  45. var firstChild = parent.firstChild;
  46. firstChild ? parent.insertBefore(child, firstChild) : parent.appendChild(child);
  47. }
  48. } // function append(parent, child) {
  49. // if (checkParentAvailable(parent, child)) {
  50. // parent.appendChild(child);
  51. // }
  52. // }
  53. function remove(parent, child) {
  54. if (child && parent && child.parentNode === parent) {
  55. parent.removeChild(child);
  56. }
  57. }
  58. function getTextSvgElement(displayable) {
  59. return displayable.__textSvgEl;
  60. }
  61. function getSvgElement(displayable) {
  62. return displayable.__svgEl;
  63. }
  64. /**
  65. * @alias module:zrender/svg/Painter
  66. * @constructor
  67. * @param {HTMLElement} root 绘图容器
  68. * @param {module:zrender/Storage} storage
  69. * @param {Object} opts
  70. */
  71. var SVGPainter = function (root, storage, opts, zrId) {
  72. this.root = root;
  73. this.storage = storage;
  74. this._opts = opts = util.extend({}, opts || {});
  75. var svgDom = createElement('svg');
  76. svgDom.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  77. svgDom.setAttribute('version', '1.1');
  78. svgDom.setAttribute('baseProfile', 'full');
  79. svgDom.style.cssText = 'user-select:none;position:absolute;left:0;top:0;';
  80. var bgRoot = createElement('g');
  81. svgDom.appendChild(bgRoot);
  82. var svgRoot = createElement('g');
  83. svgDom.appendChild(svgRoot);
  84. this.gradientManager = new GradientManager(zrId, svgRoot);
  85. this.clipPathManager = new ClippathManager(zrId, svgRoot);
  86. this.shadowManager = new ShadowManager(zrId, svgRoot);
  87. var viewport = document.createElement('div');
  88. viewport.style.cssText = 'overflow:hidden;position:relative';
  89. this._svgDom = svgDom;
  90. this._svgRoot = svgRoot;
  91. this._backgroundRoot = bgRoot;
  92. this._viewport = viewport;
  93. root.appendChild(viewport);
  94. viewport.appendChild(svgDom);
  95. this.resize(opts.width, opts.height);
  96. this._visibleList = [];
  97. };
  98. SVGPainter.prototype = {
  99. constructor: SVGPainter,
  100. getType: function () {
  101. return 'svg';
  102. },
  103. getViewportRoot: function () {
  104. return this._viewport;
  105. },
  106. getSvgDom: function () {
  107. return this._svgDom;
  108. },
  109. getSvgRoot: function () {
  110. return this._svgRoot;
  111. },
  112. getViewportRootOffset: function () {
  113. var viewportRoot = this.getViewportRoot();
  114. if (viewportRoot) {
  115. return {
  116. offsetLeft: viewportRoot.offsetLeft || 0,
  117. offsetTop: viewportRoot.offsetTop || 0
  118. };
  119. }
  120. },
  121. refresh: function () {
  122. var list = this.storage.getDisplayList(true);
  123. this._paintList(list);
  124. },
  125. setBackgroundColor: function (backgroundColor) {
  126. // TODO gradient
  127. // Insert a bg rect instead of setting background to viewport.
  128. // Otherwise, the exported SVG don't have background.
  129. if (this._backgroundRoot && this._backgroundNode) {
  130. this._backgroundRoot.removeChild(this._backgroundNode);
  131. }
  132. var bgNode = createElement('rect');
  133. bgNode.setAttribute('width', this.getWidth());
  134. bgNode.setAttribute('height', this.getHeight());
  135. bgNode.setAttribute('x', 0);
  136. bgNode.setAttribute('y', 0);
  137. bgNode.setAttribute('id', 0);
  138. bgNode.style.fill = backgroundColor;
  139. this._backgroundRoot.appendChild(bgNode);
  140. this._backgroundNode = bgNode;
  141. },
  142. _paintList: function (list) {
  143. this.gradientManager.markAllUnused();
  144. this.clipPathManager.markAllUnused();
  145. this.shadowManager.markAllUnused();
  146. var svgRoot = this._svgRoot;
  147. var visibleList = this._visibleList;
  148. var listLen = list.length;
  149. var newVisibleList = [];
  150. var i;
  151. for (i = 0; i < listLen; i++) {
  152. var displayable = list[i];
  153. var svgProxy = getSvgProxy(displayable);
  154. var svgElement = getSvgElement(displayable) || getTextSvgElement(displayable);
  155. if (!displayable.invisible) {
  156. if (displayable.__dirty) {
  157. svgProxy && svgProxy.brush(displayable); // Update clipPath
  158. this.clipPathManager.update(displayable); // Update gradient and shadow
  159. if (displayable.style) {
  160. this.gradientManager.update(displayable.style.fill);
  161. this.gradientManager.update(displayable.style.stroke);
  162. this.shadowManager.update(svgElement, displayable);
  163. }
  164. displayable.__dirty = false;
  165. }
  166. newVisibleList.push(displayable);
  167. }
  168. }
  169. var diff = arrayDiff(visibleList, newVisibleList);
  170. var prevSvgElement; // First do remove, in case element moved to the head and do remove
  171. // after add
  172. for (i = 0; i < diff.length; i++) {
  173. var item = diff[i];
  174. if (item.removed) {
  175. for (var k = 0; k < item.count; k++) {
  176. var displayable = visibleList[item.indices[k]];
  177. var svgElement = getSvgElement(displayable);
  178. var textSvgElement = getTextSvgElement(displayable);
  179. remove(svgRoot, svgElement);
  180. remove(svgRoot, textSvgElement);
  181. }
  182. }
  183. }
  184. for (i = 0; i < diff.length; i++) {
  185. var item = diff[i];
  186. if (item.added) {
  187. for (var k = 0; k < item.count; k++) {
  188. var displayable = newVisibleList[item.indices[k]];
  189. var svgElement = getSvgElement(displayable);
  190. var textSvgElement = getTextSvgElement(displayable);
  191. prevSvgElement ? insertAfter(svgRoot, svgElement, prevSvgElement) : prepend(svgRoot, svgElement);
  192. if (svgElement) {
  193. insertAfter(svgRoot, textSvgElement, svgElement);
  194. } else if (prevSvgElement) {
  195. insertAfter(svgRoot, textSvgElement, prevSvgElement);
  196. } else {
  197. prepend(svgRoot, textSvgElement);
  198. } // Insert text
  199. insertAfter(svgRoot, textSvgElement, svgElement);
  200. prevSvgElement = textSvgElement || svgElement || prevSvgElement; // zrender.Text only create textSvgElement.
  201. this.gradientManager.addWithoutUpdate(svgElement || textSvgElement, displayable);
  202. this.shadowManager.addWithoutUpdate(svgElement || textSvgElement, displayable);
  203. this.clipPathManager.markUsed(displayable);
  204. }
  205. } else if (!item.removed) {
  206. for (var k = 0; k < item.count; k++) {
  207. var displayable = newVisibleList[item.indices[k]];
  208. var svgElement = getSvgElement(displayable);
  209. var textSvgElement = getTextSvgElement(displayable);
  210. var svgElement = getSvgElement(displayable);
  211. var textSvgElement = getTextSvgElement(displayable);
  212. this.gradientManager.markUsed(displayable);
  213. this.gradientManager.addWithoutUpdate(svgElement || textSvgElement, displayable);
  214. this.shadowManager.markUsed(displayable);
  215. this.shadowManager.addWithoutUpdate(svgElement || textSvgElement, displayable);
  216. this.clipPathManager.markUsed(displayable);
  217. if (textSvgElement) {
  218. // Insert text.
  219. insertAfter(svgRoot, textSvgElement, svgElement);
  220. }
  221. prevSvgElement = svgElement || textSvgElement || prevSvgElement;
  222. }
  223. }
  224. }
  225. this.gradientManager.removeUnused();
  226. this.clipPathManager.removeUnused();
  227. this.shadowManager.removeUnused();
  228. this._visibleList = newVisibleList;
  229. },
  230. _getDefs: function (isForceCreating) {
  231. var svgRoot = this._svgDom;
  232. var defs = svgRoot.getElementsByTagName('defs');
  233. if (defs.length === 0) {
  234. // Not exist
  235. if (isForceCreating) {
  236. var defs = svgRoot.insertBefore(createElement('defs'), // Create new tag
  237. svgRoot.firstChild // Insert in the front of svg
  238. );
  239. if (!defs.contains) {
  240. // IE doesn't support contains method
  241. defs.contains = function (el) {
  242. var children = defs.children;
  243. if (!children) {
  244. return false;
  245. }
  246. for (var i = children.length - 1; i >= 0; --i) {
  247. if (children[i] === el) {
  248. return true;
  249. }
  250. }
  251. return false;
  252. };
  253. }
  254. return defs;
  255. } else {
  256. return null;
  257. }
  258. } else {
  259. return defs[0];
  260. }
  261. },
  262. resize: function (width, height) {
  263. var viewport = this._viewport; // FIXME Why ?
  264. viewport.style.display = 'none'; // Save input w/h
  265. var opts = this._opts;
  266. width != null && (opts.width = width);
  267. height != null && (opts.height = height);
  268. width = this._getSize(0);
  269. height = this._getSize(1);
  270. viewport.style.display = '';
  271. if (this._width !== width || this._height !== height) {
  272. this._width = width;
  273. this._height = height;
  274. var viewportStyle = viewport.style;
  275. viewportStyle.width = width + 'px';
  276. viewportStyle.height = height + 'px';
  277. var svgRoot = this._svgDom; // Set width by 'svgRoot.width = width' is invalid
  278. svgRoot.setAttribute('width', width);
  279. svgRoot.setAttribute('height', height);
  280. }
  281. if (this._backgroundNode) {
  282. this._backgroundNode.setAttribute('width', width);
  283. this._backgroundNode.setAttribute('height', height);
  284. }
  285. },
  286. /**
  287. * 获取绘图区域宽度
  288. */
  289. getWidth: function () {
  290. return this._width;
  291. },
  292. /**
  293. * 获取绘图区域高度
  294. */
  295. getHeight: function () {
  296. return this._height;
  297. },
  298. _getSize: function (whIdx) {
  299. var opts = this._opts;
  300. var wh = ['width', 'height'][whIdx];
  301. var cwh = ['clientWidth', 'clientHeight'][whIdx];
  302. var plt = ['paddingLeft', 'paddingTop'][whIdx];
  303. var prb = ['paddingRight', 'paddingBottom'][whIdx];
  304. if (opts[wh] != null && opts[wh] !== 'auto') {
  305. return parseFloat(opts[wh]);
  306. }
  307. var root = this.root; // IE8 does not support getComputedStyle, but it use VML.
  308. var stl = document.defaultView.getComputedStyle(root);
  309. return (root[cwh] || parseInt10(stl[wh]) || parseInt10(root.style[wh])) - (parseInt10(stl[plt]) || 0) - (parseInt10(stl[prb]) || 0) | 0;
  310. },
  311. dispose: function () {
  312. this.root.innerHTML = '';
  313. this._svgRoot = this._backgroundRoot = this._svgDom = this._backgroundNode = this._viewport = this.storage = null;
  314. },
  315. clear: function () {
  316. if (this._viewport) {
  317. this.root.removeChild(this._viewport);
  318. }
  319. },
  320. toDataURL: function () {
  321. this.refresh();
  322. var html = encodeURIComponent(this._svgDom.outerHTML.replace(/></g, '>\n\r<'));
  323. return 'data:image/svg+xml;charset=UTF-8,' + html;
  324. }
  325. }; // Not supported methods
  326. function createMethodNotSupport(method) {
  327. return function () {
  328. logError('In SVG mode painter not support method "' + method + '"');
  329. };
  330. } // Unsuppoted methods
  331. util.each(['getLayer', 'insertLayer', 'eachLayer', 'eachBuiltinLayer', 'eachOtherLayer', 'getLayers', 'modLayer', 'delLayer', 'clearLayer', 'pathToImage'], function (name) {
  332. SVGPainter.prototype[name] = createMethodNotSupport(name);
  333. });
  334. var _default = SVGPainter;
  335. module.exports = _default;