utilities.js
5.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/* *
*
* (c) 2009-2019 Øystein Moseng
*
* Utility functions for sonification.
*
* License: www.highcharts.com/license
*
* */
'use strict';
import musicalFrequencies from 'musicalFrequencies.js';
/**
* 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<string>} 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<string>} [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<number>} semitones - Array of semitones from 1-12 to
* include in the scale. Duplicate entries are ignored.
* @return {Array<number>} 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;