Responsive.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. /**
  2. * (c) 2010-2019 Torstein Honsi
  3. *
  4. * License: www.highcharts.com/license
  5. */
  6. /**
  7. * A callback function to gain complete control on when the responsive rule
  8. * applies.
  9. *
  10. * @callback Highcharts.ResponsiveCallbackFunction
  11. *
  12. * @return {boolean}
  13. * Return `true` if it applies.
  14. */
  15. 'use strict';
  16. import H from './Globals.js';
  17. import './Chart.js';
  18. import './Utilities.js';
  19. var Chart = H.Chart,
  20. isArray = H.isArray,
  21. isObject = H.isObject,
  22. pick = H.pick,
  23. splat = H.splat;
  24. /**
  25. * Allows setting a set of rules to apply for different screen or chart
  26. * sizes. Each rule specifies additional chart options.
  27. *
  28. * @sample {highstock} stock/demo/responsive/
  29. * Stock chart
  30. * @sample highcharts/responsive/axis/
  31. * Axis
  32. * @sample highcharts/responsive/legend/
  33. * Legend
  34. * @sample highcharts/responsive/classname/
  35. * Class name
  36. *
  37. * @since 5.0.0
  38. * @apioption responsive
  39. */
  40. /**
  41. * A set of rules for responsive settings. The rules are executed from
  42. * the top down.
  43. *
  44. * @sample {highcharts} highcharts/responsive/axis/
  45. * Axis changes
  46. * @sample {highstock} highcharts/responsive/axis/
  47. * Axis changes
  48. * @sample {highmaps} highcharts/responsive/axis/
  49. * Axis changes
  50. *
  51. * @type {Array<*>}
  52. * @since 5.0.0
  53. * @apioption responsive.rules
  54. */
  55. /**
  56. * A full set of chart options to apply as overrides to the general
  57. * chart options. The chart options are applied when the given rule
  58. * is active.
  59. *
  60. * A special case is configuration objects that take arrays, for example
  61. * [xAxis](#xAxis), [yAxis](#yAxis) or [series](#series). For these
  62. * collections, an `id` option is used to map the new option set to
  63. * an existing object. If an existing object of the same id is not found,
  64. * the item of the same indexupdated. So for example, setting `chartOptions`
  65. * with two series items without an `id`, will cause the existing chart's
  66. * two series to be updated with respective options.
  67. *
  68. * @sample {highstock} stock/demo/responsive/
  69. * Stock chart
  70. * @sample highcharts/responsive/axis/
  71. * Axis
  72. * @sample highcharts/responsive/legend/
  73. * Legend
  74. * @sample highcharts/responsive/classname/
  75. * Class name
  76. *
  77. * @type {Highcharts.Options}
  78. * @since 5.0.0
  79. * @apioption responsive.rules.chartOptions
  80. */
  81. /**
  82. * Under which conditions the rule applies.
  83. *
  84. * @since 5.0.0
  85. * @apioption responsive.rules.condition
  86. */
  87. /**
  88. * A callback function to gain complete control on when the responsive
  89. * rule applies. Return `true` if it applies. This opens for checking
  90. * against other metrics than the chart size, or example the document
  91. * size or other elements.
  92. *
  93. * @type {Highcharts.ResponsiveCallbackFunction}
  94. * @since 5.0.0
  95. * @context Highcharts.Chart
  96. * @apioption responsive.rules.condition.callback
  97. */
  98. /**
  99. * The responsive rule applies if the chart height is less than this.
  100. *
  101. * @type {number}
  102. * @since 5.0.0
  103. * @apioption responsive.rules.condition.maxHeight
  104. */
  105. /**
  106. * The responsive rule applies if the chart width is less than this.
  107. *
  108. * @sample highcharts/responsive/axis/
  109. * Max width is 500
  110. *
  111. * @type {number}
  112. * @since 5.0.0
  113. * @apioption responsive.rules.condition.maxWidth
  114. */
  115. /**
  116. * The responsive rule applies if the chart height is greater than this.
  117. *
  118. * @type {number}
  119. * @default 0
  120. * @since 5.0.0
  121. * @apioption responsive.rules.condition.minHeight
  122. */
  123. /**
  124. * The responsive rule applies if the chart width is greater than this.
  125. *
  126. * @type {number}
  127. * @default 0
  128. * @since 5.0.0
  129. * @apioption responsive.rules.condition.minWidth
  130. */
  131. /**
  132. * Update the chart based on the current chart/document size and options for
  133. * responsiveness.
  134. *
  135. * @private
  136. * @function Highcharts.Chart#setResponsive
  137. *
  138. * @param {boolean} [redraw=true]
  139. * @param {Array} [reset=false]
  140. * Reset by un-applying all rules. Chart.update resets all rules before
  141. * applying updated options.
  142. */
  143. Chart.prototype.setResponsive = function (redraw, reset) {
  144. var options = this.options.responsive,
  145. ruleIds = [],
  146. currentResponsive = this.currentResponsive,
  147. currentRuleIds,
  148. undoOptions;
  149. if (!reset && options && options.rules) {
  150. options.rules.forEach(function (rule) {
  151. if (rule._id === undefined) {
  152. rule._id = H.uniqueKey();
  153. }
  154. this.matchResponsiveRule(rule, ruleIds, redraw);
  155. }, this);
  156. }
  157. // Merge matching rules
  158. var mergedOptions = H.merge.apply(0, ruleIds.map(function (ruleId) {
  159. return H.find(options.rules, function (rule) {
  160. return rule._id === ruleId;
  161. }).chartOptions;
  162. }));
  163. mergedOptions.isResponsiveOptions = true;
  164. // Stringified key for the rules that currently apply.
  165. ruleIds = ruleIds.toString() || undefined;
  166. currentRuleIds = currentResponsive && currentResponsive.ruleIds;
  167. // Changes in what rules apply
  168. if (ruleIds !== currentRuleIds) {
  169. // Undo previous rules. Before we apply a new set of rules, we need to
  170. // roll back completely to base options (#6291).
  171. if (currentResponsive) {
  172. this.update(currentResponsive.undoOptions, redraw);
  173. }
  174. if (ruleIds) {
  175. // Get undo-options for matching rules
  176. undoOptions = this.currentOptions(mergedOptions);
  177. undoOptions.isResponsiveOptions = true;
  178. this.currentResponsive = {
  179. ruleIds: ruleIds,
  180. mergedOptions: mergedOptions,
  181. undoOptions: undoOptions
  182. };
  183. this.update(mergedOptions, redraw);
  184. } else {
  185. this.currentResponsive = undefined;
  186. }
  187. }
  188. };
  189. /**
  190. * Handle a single responsiveness rule.
  191. *
  192. * @private
  193. * @function Highcharts.Chart#matchResponsiveRule
  194. *
  195. * @param {Highcharts.ResponsiveRulesConditionOptions} rule
  196. *
  197. * @param {Array<number>} matches
  198. */
  199. Chart.prototype.matchResponsiveRule = function (rule, matches) {
  200. var condition = rule.condition,
  201. fn = condition.callback || function () {
  202. return (
  203. this.chartWidth <= pick(condition.maxWidth, Number.MAX_VALUE) &&
  204. this.chartHeight <=
  205. pick(condition.maxHeight, Number.MAX_VALUE) &&
  206. this.chartWidth >= pick(condition.minWidth, 0) &&
  207. this.chartHeight >= pick(condition.minHeight, 0)
  208. );
  209. };
  210. if (fn.call(this)) {
  211. matches.push(rule._id);
  212. }
  213. };
  214. /**
  215. * Get the current values for a given set of options. Used before we update
  216. * the chart with a new responsiveness rule.
  217. * TODO: Restore axis options (by id?)
  218. *
  219. * @private
  220. * @function Highcharts.Chart#currentOptions
  221. *
  222. * @param {Highcharts.Options} options
  223. *
  224. * @return {Highcharts.Options}
  225. */
  226. Chart.prototype.currentOptions = function (options) {
  227. var ret = {};
  228. /**
  229. * Recurse over a set of options and its current values,
  230. * and store the current values in the ret object.
  231. */
  232. function getCurrent(options, curr, ret, depth) {
  233. var i;
  234. H.objectEach(options, function (val, key) {
  235. if (!depth && ['series', 'xAxis', 'yAxis'].indexOf(key) > -1) {
  236. val = splat(val);
  237. ret[key] = [];
  238. // Iterate over collections like series, xAxis or yAxis and map
  239. // the items by index.
  240. for (i = 0; i < val.length; i++) {
  241. if (curr[key][i]) { // Item exists in current data (#6347)
  242. ret[key][i] = {};
  243. getCurrent(
  244. val[i],
  245. curr[key][i],
  246. ret[key][i],
  247. depth + 1
  248. );
  249. }
  250. }
  251. } else if (isObject(val)) {
  252. ret[key] = isArray(val) ? [] : {};
  253. getCurrent(val, curr[key] || {}, ret[key], depth + 1);
  254. } else {
  255. ret[key] = curr[key] || null;
  256. }
  257. });
  258. }
  259. getCurrent(options, this.options, ret, 0);
  260. return ret;
  261. };