utilities.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. /* *
  2. *
  3. * (c) 2009-2020 Øystein Moseng
  4. *
  5. * Utility functions for sonification.
  6. *
  7. * License: www.highcharts.com/license
  8. *
  9. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  10. *
  11. * */
  12. 'use strict';
  13. import musicalFrequencies from './musicalFrequencies.js';
  14. import U from '../../Core/Utilities.js';
  15. var clamp = U.clamp;
  16. /* eslint-disable no-invalid-this, valid-jsdoc */
  17. /**
  18. * The SignalHandler class. Stores signal callbacks (event handlers), and
  19. * provides an interface to register them, and emit signals. The word "event" is
  20. * not used to avoid confusion with TimelineEvents.
  21. *
  22. * @requires module:modules/sonification
  23. *
  24. * @private
  25. * @class
  26. * @name Highcharts.SignalHandler
  27. *
  28. * @param {Array<string>} supportedSignals
  29. * List of supported signal names.
  30. */
  31. function SignalHandler(supportedSignals) {
  32. this.init(supportedSignals || []);
  33. }
  34. SignalHandler.prototype.init = function (supportedSignals) {
  35. this.supportedSignals = supportedSignals;
  36. this.signals = {};
  37. };
  38. /**
  39. * Register a set of signal callbacks with this SignalHandler.
  40. * Multiple signal callbacks can be registered for the same signal.
  41. * @private
  42. * @param {Highcharts.Dictionary<(Function|undefined)>} signals
  43. * An object that contains a mapping from the signal name to the callbacks. Only
  44. * supported events are considered.
  45. * @return {void}
  46. */
  47. SignalHandler.prototype.registerSignalCallbacks = function (signals) {
  48. var signalHandler = this;
  49. signalHandler.supportedSignals.forEach(function (supportedSignal) {
  50. var signal = signals[supportedSignal];
  51. if (signal) {
  52. (signalHandler.signals[supportedSignal] =
  53. signalHandler.signals[supportedSignal] || []).push(signal);
  54. }
  55. });
  56. };
  57. /**
  58. * Clear signal callbacks, optionally by name.
  59. * @private
  60. * @param {Array<string>} [signalNames] - A list of signal names to clear. If
  61. * not supplied, all signal callbacks are removed.
  62. * @return {void}
  63. */
  64. SignalHandler.prototype.clearSignalCallbacks = function (signalNames) {
  65. var signalHandler = this;
  66. if (signalNames) {
  67. signalNames.forEach(function (signalName) {
  68. if (signalHandler.signals[signalName]) {
  69. delete signalHandler.signals[signalName];
  70. }
  71. });
  72. }
  73. else {
  74. signalHandler.signals = {};
  75. }
  76. };
  77. /**
  78. * Emit a signal. Does nothing if the signal does not exist, or has no
  79. * registered callbacks.
  80. * @private
  81. * @param {string} signalNames
  82. * Name of signal to emit.
  83. * @param {*} [data]
  84. * Data to pass to the callback.
  85. * @return {*}
  86. */
  87. SignalHandler.prototype.emitSignal = function (signalName, data) {
  88. var retval;
  89. if (this.signals[signalName]) {
  90. this.signals[signalName].forEach(function (handler) {
  91. var result = handler(data);
  92. retval = typeof result !== 'undefined' ? result : retval;
  93. });
  94. }
  95. return retval;
  96. };
  97. var utilities = {
  98. // List of musical frequencies from C0 to C8
  99. musicalFrequencies: musicalFrequencies,
  100. // SignalHandler class
  101. SignalHandler: SignalHandler,
  102. /**
  103. * Get a musical scale by specifying the semitones from 1-12 to include.
  104. * 1: C, 2: C#, 3: D, 4: D#, 5: E, 6: F,
  105. * 7: F#, 8: G, 9: G#, 10: A, 11: Bb, 12: B
  106. * @private
  107. * @param {Array<number>} semitones
  108. * Array of semitones from 1-12 to include in the scale. Duplicate entries
  109. * are ignored.
  110. * @return {Array<number>}
  111. * Array of frequencies from C0 to C8 that are included in this scale.
  112. */
  113. getMusicalScale: function (semitones) {
  114. return musicalFrequencies.filter(function (freq, i) {
  115. var interval = i % 12 + 1;
  116. return semitones.some(function (allowedInterval) {
  117. return allowedInterval === interval;
  118. });
  119. });
  120. },
  121. /**
  122. * Calculate the extreme values in a chart for a data prop.
  123. * @private
  124. * @param {Highcharts.Chart} chart - The chart
  125. * @param {string} prop - The data prop to find extremes for
  126. * @return {Highcharts.RangeObject} Object with min and max properties
  127. */
  128. calculateDataExtremes: function (chart, prop) {
  129. return chart.series.reduce(function (extremes, series) {
  130. // We use cropped points rather than series.data here, to allow
  131. // users to zoom in for better fidelity.
  132. series.points.forEach(function (point) {
  133. var val = typeof point[prop] !== 'undefined' ?
  134. point[prop] : point.options[prop];
  135. extremes.min = Math.min(extremes.min, val);
  136. extremes.max = Math.max(extremes.max, val);
  137. });
  138. return extremes;
  139. }, {
  140. min: Infinity,
  141. max: -Infinity
  142. });
  143. },
  144. /**
  145. * Translate a value on a virtual axis. Creates a new, virtual, axis with a
  146. * min and max, and maps the relative value onto this axis.
  147. * @private
  148. * @param {number} value
  149. * The relative data value to translate.
  150. * @param {Highcharts.RangeObject} DataExtremesObject
  151. * The possible extremes for this value.
  152. * @param {object} limits
  153. * Limits for the virtual axis.
  154. * @param {boolean} [invert]
  155. * Invert the virtual axis.
  156. * @return {number}
  157. * The value mapped to the virtual axis.
  158. */
  159. virtualAxisTranslate: function (value, dataExtremes, limits, invert) {
  160. var lenValueAxis = dataExtremes.max - dataExtremes.min, lenVirtualAxis = Math.abs(limits.max - limits.min), valueDelta = invert ?
  161. dataExtremes.max - value :
  162. value - dataExtremes.min, virtualValueDelta = lenVirtualAxis * valueDelta / lenValueAxis, virtualAxisValue = limits.min + virtualValueDelta;
  163. return lenValueAxis > 0 ?
  164. clamp(virtualAxisValue, limits.min, limits.max) :
  165. limits.min;
  166. }
  167. };
  168. export default utilities;