pattern-fill.src.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. /**
  2. * @license Highcharts JS v7.0.2 (2019-01-17)
  3. * Module for adding patterns and images as point fills.
  4. *
  5. * (c) 2010-2019 Highsoft AS
  6. * Author: Torstein Hønsi, Øystein Moseng
  7. *
  8. * License: www.highcharts.com/license
  9. */
  10. 'use strict';
  11. (function (factory) {
  12. if (typeof module === 'object' && module.exports) {
  13. factory['default'] = factory;
  14. module.exports = factory;
  15. } else if (typeof define === 'function' && define.amd) {
  16. define(function () {
  17. return factory;
  18. });
  19. } else {
  20. factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
  21. }
  22. }(function (Highcharts) {
  23. (function (H) {
  24. /* *
  25. * Module for using patterns or images as point fills.
  26. *
  27. * (c) 2010-2019 Highsoft AS
  28. * Author: Torstein Hønsi, Øystein Moseng
  29. *
  30. * License: www.highcharts.com/license
  31. */
  32. /**
  33. * Pattern options
  34. *
  35. * @interface Highcharts.PatternOptionsObject
  36. *//**
  37. * URL to an image to use as the pattern.
  38. * @name Highcharts.PatternOptionsObject#image
  39. * @type {string}
  40. *//**
  41. * Width of the pattern. For images this is automatically set to the width of
  42. * the element bounding box if not supplied. For non-image patterns the default
  43. * is 32px. Note that automatic resizing of image patterns to fill a bounding
  44. * box dynamically is only supported for patterns with an automatically
  45. * calculated ID.
  46. * @name Highcharts.PatternOptionsObject#with
  47. * @type {number}
  48. *//**
  49. * Analogous to pattern.width.
  50. * @name Highcharts.PatternOptionsObject#height
  51. * @type {number}
  52. *//**
  53. * For automatically calculated width and height on images, it is possible to
  54. * set an aspect ratio. The image will be zoomed to fill the bounding box,
  55. * maintaining the aspect ratio defined.
  56. * @name Highcharts.PatternOptionsObject#aspectRatio
  57. * @type {number}
  58. *//**
  59. * Horizontal offset of the pattern. Defaults to 0.
  60. * @name Highcharts.PatternOptionsObject#x
  61. * @type {number|undefined}
  62. *//**
  63. * Vertical offset of the pattern. Defaults to 0.
  64. * @name Highcharts.PatternOptionsObject#y
  65. * @type {number|undefined}
  66. *//**
  67. * Either an SVG path as string, or an object. As an object, supply the path
  68. * string in the `path.d` property. Other supported properties are standard SVG
  69. * attributes like `path.stroke` and `path.fill`. If a path is supplied for the
  70. * pattern, the `image` property is ignored.
  71. * @name Highcharts.PatternOptionsObject#path
  72. * @type {string|Highcharts.SVGAttributes}
  73. *//**
  74. * Pattern color, used as default path stroke.
  75. * @name Highcharts.PatternOptionsObject#color
  76. * @type {Highcharts.ColorString}
  77. *//**
  78. * Opacity of the pattern as a float value from 0 to 1.
  79. * @name Highcharts.PatternOptionsObject#opacity
  80. * @type {number}
  81. *//**
  82. * ID to assign to the pattern. This is automatically computed if not added, and
  83. * identical patterns are reused. To refer to an existing pattern for a
  84. * Highcharts color, use `color: "url(#pattern-id)"`.
  85. * @name Highcharts.PatternOptionsObject#id
  86. * @type {string}
  87. */
  88. /**
  89. * Holds a pattern definition.
  90. *
  91. * @sample highcharts/series/pattern-fill-area/
  92. * Define a custom path pattern
  93. * @sample highcharts/series/pattern-fill-pie/
  94. * Default patterns and a custom image pattern
  95. * @sample maps/demo/pattern-fill-map/
  96. * Custom images on map
  97. *
  98. * @example
  99. * // Pattern used as a color option
  100. * color: {
  101. * pattern: {
  102. * path: {
  103. * d: 'M 3 3 L 8 3 L 8 8 Z',
  104. * fill: '#102045'
  105. * },
  106. * width: 12,
  107. * height: 12,
  108. * color: '#907000',
  109. * opacity: 0.5
  110. * }
  111. * }
  112. *
  113. * @interface Highcharts.PatternObject
  114. *//**
  115. * Pattern options
  116. * @name Highcharts.PatternObject#pattern
  117. * @type {Highcharts.PatternOptionsObject}
  118. *//**
  119. * Animation options for the image pattern loading.
  120. * @name Highcharts.PatternObject#animation
  121. * @type {boolean|Highcharts.AnimationOptionsObject|undefined}
  122. */
  123. var addEvent = H.addEvent,
  124. wrap = H.wrap,
  125. merge = H.merge,
  126. pick = H.pick;
  127. /**
  128. * Utility function to compute a hash value from an object. Modified Java
  129. * String.hashCode implementation in JS. Use the preSeed parameter to add an
  130. * additional seeding step.
  131. *
  132. * @private
  133. * @function hashFromObject
  134. *
  135. * @param {object} obj
  136. * The javascript object to compute the hash from.
  137. *
  138. * @param {boolean} [preSeed=false]
  139. * Add an optional preSeed stage.
  140. *
  141. * @return {string}
  142. * The computed hash.
  143. */
  144. function hashFromObject(obj, preSeed) {
  145. var str = JSON.stringify(obj),
  146. strLen = str.length || 0,
  147. hash = 0,
  148. i = 0,
  149. char,
  150. seedStep;
  151. if (preSeed) {
  152. seedStep = Math.max(Math.floor(strLen / 500), 1);
  153. for (var a = 0; a < strLen; a += seedStep) {
  154. hash += str.charCodeAt(a);
  155. }
  156. hash = hash & hash;
  157. }
  158. for (; i < strLen; ++i) {
  159. char = str.charCodeAt(i);
  160. hash = ((hash << 5) - hash) + char;
  161. hash = hash & hash;
  162. }
  163. return hash.toString(16).replace('-', '1');
  164. }
  165. /**
  166. * Set dimensions on pattern from point. This function will set internal
  167. * pattern._width/_height properties if width and height are not both already
  168. * set. We only do this on image patterns. The _width/_height properties are set
  169. * to the size of the bounding box of the point, optionally taking aspect ratio
  170. * into account. If only one of width or height are supplied as options, the
  171. * undefined option is calculated as above.
  172. *
  173. * @private
  174. * @function Highcharts.Point#calculatePatternDimensions
  175. *
  176. * @param {Highcharts.PatternObject} pattern
  177. * The pattern to set dimensions on.
  178. */
  179. H.Point.prototype.calculatePatternDimensions = function (pattern) {
  180. if (pattern.width && pattern.height) {
  181. return;
  182. }
  183. var bBox = this.graphic && (
  184. this.graphic.getBBox &&
  185. this.graphic.getBBox(true) ||
  186. this.graphic.element &&
  187. this.graphic.element.getBBox()
  188. ) || {},
  189. shapeArgs = this.shapeArgs;
  190. // Prefer using shapeArgs, as it is animation agnostic
  191. if (shapeArgs) {
  192. bBox.width = shapeArgs.width || bBox.width;
  193. bBox.height = shapeArgs.height || bBox.height;
  194. bBox.x = shapeArgs.x || bBox.x;
  195. bBox.y = shapeArgs.y || bBox.y;
  196. }
  197. // For images we stretch to bounding box
  198. if (pattern.image) {
  199. // If we do not have a bounding box at this point, simply add a defer
  200. // key and pick this up in the fillSetter handler, where the bounding
  201. // box should exist.
  202. if (!bBox.width || !bBox.height) {
  203. pattern._width = 'defer';
  204. pattern._height = 'defer';
  205. return;
  206. }
  207. // Handle aspect ratio filling
  208. if (pattern.aspectRatio) {
  209. bBox.aspectRatio = bBox.width / bBox.height;
  210. if (pattern.aspectRatio > bBox.aspectRatio) {
  211. // Height of bBox will determine width
  212. bBox.aspectWidth = bBox.height * pattern.aspectRatio;
  213. } else {
  214. // Width of bBox will determine height
  215. bBox.aspectHeight = bBox.width / pattern.aspectRatio;
  216. }
  217. }
  218. // We set the width/height on internal properties to differentiate
  219. // between the options set by a user and by this function.
  220. pattern._width = pattern.width ||
  221. Math.ceil(bBox.aspectWidth || bBox.width);
  222. pattern._height = pattern.height ||
  223. Math.ceil(bBox.aspectHeight || bBox.height);
  224. }
  225. // Set x/y accordingly, centering if using aspect ratio, otherwise adjusting
  226. // so bounding box corner is 0,0 of pattern.
  227. if (!pattern.width) {
  228. pattern._x = pattern.x || 0;
  229. pattern._x += bBox.x - Math.round(
  230. bBox.aspectWidth ?
  231. Math.abs(bBox.aspectWidth - bBox.width) / 2 :
  232. 0
  233. );
  234. }
  235. if (!pattern.height) {
  236. pattern._y = pattern.y || 0;
  237. pattern._y += bBox.y - Math.round(
  238. bBox.aspectHeight ?
  239. Math.abs(bBox.aspectHeight - bBox.height) / 2 :
  240. 0
  241. );
  242. }
  243. };
  244. /**
  245. * Add a pattern to the renderer.
  246. *
  247. * @private
  248. * @function Highcharts.SVGRenderer#addPattern
  249. *
  250. * @param {Highcharts.PatternObject} options
  251. * The pattern options.
  252. *
  253. * @return {Highcharts.SVGElement|undefined}
  254. * The added pattern. Undefined if the pattern already exists.
  255. */
  256. H.SVGRenderer.prototype.addPattern = function (options, animation) {
  257. var pattern,
  258. animate = H.pick(animation, true),
  259. animationOptions = H.animObject(animate),
  260. path,
  261. defaultSize = 32,
  262. width = options.width || options._width || defaultSize,
  263. height = options.height || options._height || defaultSize,
  264. color = options.color || '#343434',
  265. id = options.id,
  266. ren = this,
  267. rect = function (fill) {
  268. ren.rect(0, 0, width, height)
  269. .attr({
  270. fill: fill
  271. })
  272. .add(pattern);
  273. },
  274. attribs;
  275. if (!id) {
  276. this.idCounter = this.idCounter || 0;
  277. id = 'highcharts-pattern-' + this.idCounter;
  278. ++this.idCounter;
  279. }
  280. // Do nothing if ID already exists
  281. this.defIds = this.defIds || [];
  282. if (this.defIds.indexOf(id) > -1) {
  283. return;
  284. }
  285. // Store ID in list to avoid duplicates
  286. this.defIds.push(id);
  287. // Create pattern element
  288. pattern = this.createElement('pattern').attr({
  289. id: id,
  290. patternUnits: 'userSpaceOnUse',
  291. width: width,
  292. height: height,
  293. x: options._x || options.x || 0,
  294. y: options._y || options.y || 0
  295. }).add(this.defs);
  296. // Set id on the SVGRenderer object
  297. pattern.id = id;
  298. // Use an SVG path for the pattern
  299. if (options.path) {
  300. path = options.path;
  301. // The background
  302. if (path.fill) {
  303. rect(path.fill);
  304. }
  305. // The pattern
  306. attribs = {
  307. 'd': path.d || path
  308. };
  309. if (!this.styledMode) {
  310. attribs.stroke = path.stroke || color;
  311. attribs['stroke-width'] = path.strokeWidth || 2;
  312. }
  313. this.createElement('path').attr(attribs).add(pattern);
  314. pattern.color = color;
  315. // Image pattern
  316. } else if (options.image) {
  317. if (animate) {
  318. this.image(
  319. options.image, 0, 0, width, height, function () {
  320. // Onload
  321. this.animate({
  322. opacity: pick(options.opacity, 1)
  323. }, animationOptions);
  324. H.removeEvent(this.element, 'load');
  325. }
  326. ).attr({ opacity: 0 }).add(pattern);
  327. } else {
  328. this.image(options.image, 0, 0, width, height).add(pattern);
  329. }
  330. }
  331. // For non-animated patterns, set opacity now
  332. if (!(options.image && animate) && options.opacity !== undefined) {
  333. [].forEach.call(pattern.element.childNodes, function (child) {
  334. child.setAttribute('opacity', options.opacity);
  335. });
  336. }
  337. // Store for future reference
  338. this.patternElements = this.patternElements || {};
  339. this.patternElements[id] = pattern;
  340. return pattern;
  341. };
  342. // Make sure we have a series color
  343. wrap(H.Series.prototype, 'getColor', function (proceed) {
  344. var oldColor = this.options.color;
  345. // Temporarely remove color options to get defaults
  346. if (oldColor && oldColor.pattern && !oldColor.pattern.color) {
  347. delete this.options.color;
  348. // Get default
  349. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  350. // Replace with old, but add default color
  351. oldColor.pattern.color = this.color;
  352. this.color = this.options.color = oldColor;
  353. } else {
  354. // We have a color, no need to do anything special
  355. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  356. }
  357. });
  358. // Calculate pattern dimensions on points that have their own pattern.
  359. addEvent(H.Series, 'render', function () {
  360. var isResizing = this.chart.isResizing;
  361. if (this.isDirtyData || isResizing || !this.chart.hasRendered) {
  362. (this.points || []).forEach(function (point) {
  363. var colorOptions = point.options && point.options.color;
  364. if (colorOptions && colorOptions.pattern) {
  365. // For most points we want to recalculate the dimensions on
  366. // render, where we have the shape args and bbox. But if we
  367. // are resizing and don't have the shape args, defer it, since
  368. // the bounding box is still not resized.
  369. if (
  370. isResizing &&
  371. !(
  372. point.shapeArgs &&
  373. point.shapeArgs.width &&
  374. point.shapeArgs.height
  375. )
  376. ) {
  377. colorOptions.pattern._width = 'defer';
  378. colorOptions.pattern._height = 'defer';
  379. } else {
  380. point.calculatePatternDimensions(colorOptions.pattern);
  381. }
  382. }
  383. });
  384. }
  385. });
  386. // Merge series color options to points
  387. addEvent(H.Point, 'afterInit', function () {
  388. var point = this,
  389. colorOptions = point.options.color;
  390. // Only do this if we have defined a specific color on this point. Otherwise
  391. // we will end up trying to re-add the series color for each point.
  392. if (colorOptions && colorOptions.pattern) {
  393. // Move path definition to object, allows for merge with series path
  394. // definition
  395. if (typeof colorOptions.pattern.path === 'string') {
  396. colorOptions.pattern.path = {
  397. d: colorOptions.pattern.path
  398. };
  399. }
  400. // Merge with series options
  401. point.color = point.options.color = merge(
  402. point.series.options.color, colorOptions
  403. );
  404. }
  405. });
  406. // Add functionality to SVG renderer to handle patterns as complex colors
  407. H.addEvent(H.SVGRenderer, 'complexColor', function (args) {
  408. var color = args.args[0],
  409. prop = args.args[1],
  410. element = args.args[2],
  411. pattern = color.pattern,
  412. value = '#343434',
  413. forceHashId;
  414. // Skip and call default if there is no pattern
  415. if (!pattern) {
  416. return true;
  417. }
  418. // We have a pattern.
  419. if (
  420. pattern.image ||
  421. typeof pattern.path === 'string' ||
  422. pattern.path && pattern.path.d
  423. ) {
  424. // Real pattern. Add it and set the color value to be a reference.
  425. // Force Hash-based IDs for legend items, as they are drawn before
  426. // point render, meaning they are drawn before autocalculated image
  427. // width/heights. We don't want them to highjack the width/height for
  428. // this ID if it is defined by users.
  429. forceHashId = element.parentNode &&
  430. element.parentNode.getAttribute('class');
  431. forceHashId = forceHashId &&
  432. forceHashId.indexOf('highcharts-legend') > -1;
  433. // If we don't have a width/height yet, handle it. Try faking a point
  434. // and running the algorithm again.
  435. if (pattern._width === 'defer' || pattern._height === 'defer') {
  436. H.Point.prototype.calculatePatternDimensions.call(
  437. { graphic: { element: element } }, pattern
  438. );
  439. }
  440. // If we don't have an explicit ID, compute a hash from the
  441. // definition and use that as the ID. This ensures that points with
  442. // the same pattern definition reuse existing pattern elements by
  443. // default. We combine two hashes, the second with an additional
  444. // preSeed algorithm, to minimize collision probability.
  445. if (forceHashId || !pattern.id) {
  446. // Make a copy so we don't accidentally edit options when setting ID
  447. pattern = merge({}, pattern);
  448. pattern.id = 'highcharts-pattern-' + hashFromObject(pattern) +
  449. hashFromObject(pattern, true);
  450. }
  451. // Add it. This function does nothing if an element with this ID
  452. // already exists.
  453. this.addPattern(pattern, !this.forExport && H.pick(
  454. pattern.animation,
  455. this.globalAnimation,
  456. { duration: 100 }
  457. ));
  458. value = 'url(' + this.url + '#' + pattern.id + ')';
  459. } else {
  460. // Not a full pattern definition, just add color
  461. value = pattern.color || value;
  462. }
  463. // Set the fill/stroke prop on the element
  464. element.setAttribute(prop, value);
  465. // Allow the color to be concatenated into tooltips formatters etc.
  466. color.toString = function () {
  467. return value;
  468. };
  469. // Skip default handler
  470. return false;
  471. });
  472. // When animation is used, we have to recalculate pattern dimensions after
  473. // resize, as the bounding boxes are not available until then.
  474. H.addEvent(H.Chart, 'endResize', function () {
  475. if (
  476. (this.renderer.defIds || []).filter(function (id) {
  477. return id && id.indexOf && id.indexOf('highcharts-pattern-') === 0;
  478. }).length
  479. ) {
  480. // We have non-default patterns to fix. Find them by looping through
  481. // all points.
  482. this.series.forEach(function (series) {
  483. series.points.forEach(function (point) {
  484. var colorOptions = point.options && point.options.color;
  485. if (colorOptions && colorOptions.pattern) {
  486. colorOptions.pattern._width = 'defer';
  487. colorOptions.pattern._height = 'defer';
  488. }
  489. });
  490. });
  491. // Redraw without animation
  492. this.redraw(false);
  493. }
  494. });
  495. // Add a garbage collector to delete old patterns with autogenerated hashes that
  496. // are no longer being referenced.
  497. H.addEvent(H.Chart, 'redraw', function () {
  498. var usedIds = [],
  499. renderer = this.renderer,
  500. // Get the autocomputed patterns - these are the ones we might delete
  501. patterns = (renderer.defIds || []).filter(function (pattern) {
  502. return pattern.indexOf &&
  503. pattern.indexOf('highcharts-pattern-') === 0;
  504. });
  505. if (patterns.length) {
  506. // Look through the DOM for usage of the patterns. This can be points,
  507. // series, tooltips etc.
  508. [].forEach.call(
  509. this.renderTo.querySelectorAll(
  510. '[color^="url(#"], [fill^="url(#"], [stroke^="url(#"]'
  511. ),
  512. function (node) {
  513. var id = node.getAttribute('fill') ||
  514. node.getAttribute('color') ||
  515. node.getAttribute('stroke');
  516. if (id) {
  517. usedIds.push(
  518. id
  519. .substring(id.indexOf('url(#') + 5)
  520. .replace(')', '')
  521. );
  522. }
  523. }
  524. );
  525. // Loop through the patterns that exist and see if they are used
  526. patterns.forEach(function (id) {
  527. if (usedIds.indexOf(id) === -1) {
  528. // Remove id from used id list
  529. H.erase(renderer.defIds, id);
  530. // Remove pattern element
  531. if (renderer.patternElements[id]) {
  532. renderer.patternElements[id].destroy();
  533. delete renderer.patternElements[id];
  534. }
  535. }
  536. });
  537. }
  538. });
  539. // Add the predefined patterns
  540. H.Chart.prototype.callbacks.push(function (chart) {
  541. var colors = H.getOptions().colors;
  542. [
  543. 'M 0 0 L 10 10 M 9 -1 L 11 1 M -1 9 L 1 11',
  544. 'M 0 10 L 10 0 M -1 1 L 1 -1 M 9 11 L 11 9',
  545. 'M 3 0 L 3 10 M 8 0 L 8 10',
  546. 'M 0 3 L 10 3 M 0 8 L 10 8',
  547. 'M 0 3 L 5 3 L 5 0 M 5 10 L 5 7 L 10 7',
  548. 'M 3 3 L 8 3 L 8 8 L 3 8 Z',
  549. 'M 5 5 m -4 0 a 4 4 0 1 1 8 0 a 4 4 0 1 1 -8 0',
  550. 'M 10 3 L 5 3 L 5 0 M 5 10 L 5 7 L 0 7',
  551. 'M 2 5 L 5 2 L 8 5 L 5 8 Z',
  552. 'M 0 0 L 5 10 L 10 0'
  553. ].forEach(function (pattern, i) {
  554. chart.renderer.addPattern({
  555. id: 'highcharts-default-pattern-' + i,
  556. path: pattern,
  557. color: colors[i],
  558. width: 10,
  559. height: 10
  560. });
  561. });
  562. });
  563. }(Highcharts));
  564. return (function () {
  565. }());
  566. }));