Chart/api/zoom.ts

  1. /**
  2. * Copyright (c) 2017 ~ present NAVER Corp.
  3. * billboard.js project is licensed under the MIT license
  4. */
  5. import {zoomIdentity as d3ZoomIdentity, zoomTransform as d3ZoomTransform} from "d3-zoom";
  6. import type {TDomainRange} from "../../ChartInternal/data/IData";
  7. import {extend, getMinMax, isDefined, isObject, parseDate} from "../../module/util";
  8. /**
  9. * Zoom by giving x domain range.
  10. * - **ℹ️ NOTE:**
  11. * - For `wheel` type zoom, the minimum zoom range will be set as the given domain range. To get the initial state, [.unzoom()](#unzoom) should be called.
  12. * - To be used [zoom.enabled](Options.html#.zoom) option should be set as `truthy`.
  13. * - When x axis type is `category`, domain range should be specified as index numbers.
  14. * - Due to the limitations of floating point precision, domain value may not be exact returning approximately values.
  15. * @function zoom
  16. * @instance
  17. * @memberof Chart
  18. * @param {Array} domainValue If domain range is given, the chart will be zoomed to the given domain. If no argument is given, the current zoomed domain will be returned.
  19. * @returns {Array} domain value in array
  20. * @example
  21. * // Zoom to specified domain range
  22. * chart.zoom([10, 20]);
  23. *
  24. * // For timeseries x axis, the domain value can be string, but the format should match with the 'data.xFormat' option.
  25. * chart.zoom(["2021-02-03", "2021-02-08"]);
  26. *
  27. * // For category x axis, the domain value should be index number.
  28. * chart.zoom([0, 3]);
  29. *
  30. * // Get the current zoomed domain range
  31. * // Domain value may not be exact returning approximately values.
  32. * chart.zoom();
  33. */
  34. // NOTE: declared funciton assigning to variable to prevent duplicated method generation in JSDoc.
  35. const zoom = function<T = TDomainRange>(domainValue?: T): T | undefined {
  36. const $$ = this.internal;
  37. const {axis, config, org, scale, state} = $$;
  38. const isCategorized = axis.isCategorized();
  39. let domain;
  40. if (config.zoom_enabled) {
  41. domain = domainValue;
  42. if (Array.isArray(domain)) {
  43. if (axis.isTimeSeries()) {
  44. domain = domain.map(x => parseDate.bind($$)(x));
  45. }
  46. const isWithinRange = $$.withinRange(
  47. domain,
  48. $$.getZoomDomain("zoom", true),
  49. $$.getZoomDomain("zoom")
  50. );
  51. if (isWithinRange) {
  52. state.domain = domain;
  53. domain = $$.getZoomDomainValue(domain);
  54. // hide any possible tooltip show before the zoom
  55. $$.api.tooltip.hide();
  56. if (config.subchart_show) {
  57. const x = scale.zoom || scale.x;
  58. $$.brush.getSelection().call($$.brush.move, domain.map(x));
  59. // resultDomain = domain;
  60. } else {
  61. // in case of 'config.zoom_rescale=true', use org.xScale
  62. const x = isCategorized ? scale.x.orgScale() : (org.xScale || scale.x);
  63. $$.updateCurrentZoomTransform(x, domain);
  64. }
  65. $$.setZoomResetButton();
  66. }
  67. } else {
  68. domain = $$.zoom.getDomain();
  69. }
  70. }
  71. return state.domain ?? domain;
  72. };
  73. extend(zoom, {
  74. /**
  75. * Enable and disable zooming.
  76. * @function zoom․enable
  77. * @instance
  78. * @memberof Chart
  79. * @param {string|boolean} enabled Possible string values are "wheel" or "drag". If enabled is true, "wheel" will be used. If false is given, zooming will be disabled.<br>When set to false, the current zooming status will be reset.
  80. * @example
  81. * // Enable zooming using the mouse wheel
  82. * chart.zoom.enable(true);
  83. * // Or
  84. * chart.zoom.enable("wheel");
  85. *
  86. * // Enable zooming by dragging
  87. * chart.zoom.enable("drag");
  88. *
  89. * // Disable zooming
  90. * chart.zoom.enable(false);
  91. */
  92. enable(enabled: boolean | "wheel" | "drag" | any): void {
  93. const $$ = this.internal;
  94. const {config} = $$;
  95. if (/^(drag|wheel)$/.test(enabled)) {
  96. config.zoom_type = enabled;
  97. }
  98. config.zoom_enabled = !!enabled;
  99. if (!$$.zoom) {
  100. $$.initZoom();
  101. $$.bindZoomEvent();
  102. } else if (enabled === false) {
  103. $$.bindZoomEvent(false);
  104. }
  105. $$.updateAndRedraw();
  106. },
  107. /**
  108. * Set or get x Axis maximum zoom range value
  109. * @function zoom․max
  110. * @instance
  111. * @memberof Chart
  112. * @param {number} [max] maximum value to set for zoom
  113. * @returns {number} zoom max value
  114. * @example
  115. * // Set maximum range value
  116. * chart.zoom.max(20);
  117. */
  118. max(max?: number): number {
  119. const $$ = this.internal;
  120. const {config, org: {xDomain}} = $$;
  121. if (max === 0 || max) {
  122. config.zoom_x_max = getMinMax("max", [xDomain[1], max]);
  123. }
  124. return config.zoom_x_max;
  125. },
  126. /**
  127. * Set or get x Axis minimum zoom range value
  128. * @function zoom․min
  129. * @instance
  130. * @memberof Chart
  131. * @param {number} [min] minimum value to set for zoom
  132. * @returns {number} zoom min value
  133. * @example
  134. * // Set minimum range value
  135. * chart.zoom.min(-1);
  136. */
  137. min(min?: number): number {
  138. const $$ = this.internal;
  139. const {config, org: {xDomain}} = $$;
  140. if (min === 0 || min) {
  141. config.zoom_x_min = getMinMax("min", [xDomain[0], min]);
  142. }
  143. return config.zoom_x_min;
  144. },
  145. /**
  146. * Set zoom range
  147. * @function zoom․range
  148. * @instance
  149. * @memberof Chart
  150. * @param {object} [range] zoom range
  151. * @returns {object} zoom range value
  152. * {
  153. * min: 0,
  154. * max: 100
  155. * }
  156. * @example
  157. * chart.zoom.range({
  158. * min: 10,
  159. * max: 100
  160. * });
  161. */
  162. range(range): {min: (number | undefined)[], max: (number | undefined)[]} {
  163. const zoom = this.zoom;
  164. if (isObject(range)) {
  165. const {min, max} = range;
  166. isDefined(min) && zoom.min(min);
  167. isDefined(max) && zoom.max(max);
  168. }
  169. return {
  170. min: zoom.min(),
  171. max: zoom.max()
  172. };
  173. }
  174. });
  175. export default {
  176. zoom,
  177. /**
  178. * Unzoom zoomed area
  179. * - **NOTE:** Calling .unzoom() will not trigger zoom events.
  180. * @function unzoom
  181. * @instance
  182. * @memberof Chart
  183. * @example
  184. * chart.unzoom();
  185. */
  186. unzoom(): void {
  187. const $$ = this.internal;
  188. const {config, $el: {eventRect, zoomResetBtn}, scale: {zoom}, state} = $$;
  189. if (zoom) {
  190. config.subchart_show ?
  191. $$.brush.getSelection().call($$.brush.move, null) :
  192. $$.zoom.updateTransformScale(d3ZoomIdentity);
  193. $$.updateZoom(true);
  194. zoomResetBtn?.style("display", "none");
  195. // reset transform
  196. if (d3ZoomTransform(eventRect.node()) !== d3ZoomIdentity) {
  197. $$.zoom.transform(eventRect, d3ZoomIdentity);
  198. }
  199. state.domain = undefined;
  200. }
  201. }
  202. };