/* * * * (c) 2009-2019 Øystein Moseng * * Utility functions for sonification. * * License: www.highcharts.com/license * * */ 'use strict'; import musicalFrequencies from './musicalFrequencies'; /** * The SignalHandler class. Stores signal callbacks (event handlers), and * provides an interface to register them, and emit signals. The word "event" is * not used to avoid confusion with TimelineEvents. * * @requires module:modules/sonification * * @private * @class * @name Highcharts.SignalHandler * * @param {Array} supportedSignals * List of supported signal names. */ function SignalHandler(supportedSignals) { this.init(supportedSignals || []); } SignalHandler.prototype.init = function (supportedSignals) { this.supportedSignals = supportedSignals; this.signals = {}; }; /** * Register a set of signal callbacks with this SignalHandler. * Multiple signal callbacks can be registered for the same signal. * @private * @param {object} signals - An object that contains a mapping from the signal * name to the callbacks. Only supported events are considered. */ SignalHandler.prototype.registerSignalCallbacks = function (signals) { var signalHandler = this; signalHandler.supportedSignals.forEach(function (supportedSignal) { if (signals[supportedSignal]) { ( signalHandler.signals[supportedSignal] = signalHandler.signals[supportedSignal] || [] ).push( signals[supportedSignal] ); } }); }; /** * Clear signal callbacks, optionally by name. * @private * @param {Array} [signalNames] - A list of signal names to clear. If * not supplied, all signal callbacks are removed. */ SignalHandler.prototype.clearSignalCallbacks = function (signalNames) { var signalHandler = this; if (signalNames) { signalNames.forEach(function (signalName) { if (signalHandler.signals[signalName]) { delete signalHandler.signals[signalName]; } }); } else { signalHandler.signals = {}; } }; /** * Emit a signal. Does nothing if the signal does not exist, or has no * registered callbacks. * @private * @param {string} signalNames - Name of signal to emit. * @param {*} data - Data to pass to the callback. */ SignalHandler.prototype.emitSignal = function (signalName, data) { var retval; if (this.signals[signalName]) { this.signals[signalName].forEach(function (handler) { var result = handler(data); retval = result !== undefined ? result : retval; }); } return retval; }; var utilities = { // List of musical frequencies from C0 to C8 musicalFrequencies: musicalFrequencies, // SignalHandler class SignalHandler: SignalHandler, /** * Get a musical scale by specifying the semitones from 1-12 to include. * 1: C, 2: C#, 3: D, 4: D#, 5: E, 6: F, * 7: F#, 8: G, 9: G#, 10: A, 11: Bb, 12: B * @private * @param {Array} semitones - Array of semitones from 1-12 to * include in the scale. Duplicate entries are ignored. * @return {Array} Array of frequencies from C0 to C8 that are * included in this scale. */ getMusicalScale: function (semitones) { return musicalFrequencies.filter(function (freq, i) { var interval = i % 12 + 1; return semitones.some(function (allowedInterval) { return allowedInterval === interval; }); }); }, /** * Calculate the extreme values in a chart for a data prop. * @private * @param {Highcharts.Chart} chart - The chart * @param {string} prop - The data prop to find extremes for * @return {object} Object with min and max properties */ calculateDataExtremes: function (chart, prop) { return chart.series.reduce(function (extremes, series) { // We use cropped points rather than series.data here, to allow // users to zoom in for better fidelity. series.points.forEach(function (point) { var val = point[prop] !== undefined ? point[prop] : point.options[prop]; extremes.min = Math.min(extremes.min, val); extremes.max = Math.max(extremes.max, val); }); return extremes; }, { min: Infinity, max: -Infinity }); }, /** * Translate a value on a virtual axis. Creates a new, virtual, axis with a * min and max, and maps the relative value onto this axis. * @private * @param {number} value - The relative data value to translate. * @param {object} dataExtremes - The possible extremes for this value. * @param {object} limits - Limits for the virtual axis. * @return {number} The value mapped to the virtual axis. */ virtualAxisTranslate: function (value, dataExtremes, limits) { var lenValueAxis = dataExtremes.max - dataExtremes.min, lenVirtualAxis = limits.max - limits.min, virtualAxisValue = limits.min + lenVirtualAxis * (value - dataExtremes.min) / lenValueAxis; return lenValueAxis > 0 ? Math.max(Math.min(virtualAxisValue, limits.max), limits.min) : limits.min; } }; export default utilities;