utilities.js 5.3 KB

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