123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385 |
- /* *
- *
- * (c) 2009-2019 Øystein Moseng
- *
- * Code for sonifying single points.
- *
- * License: www.highcharts.com/license
- *
- * */
- /**
- * Define the parameter mapping for an instrument.
- *
- * @requires module:modules/sonification
- *
- * @interface Highcharts.PointInstrumentMappingObject
- *//**
- * Define the volume of the instrument. This can be a string with a data
- * property name, e.g. `'y'`, in which case this data property is used to define
- * the volume relative to the `y`-values of the other points. A higher `y` value
- * would then result in a higher volume. This option can also be a fixed number
- * or a function. If it is a function, this function is called in regular
- * intervals while the note is playing. It receives three arguments: The point,
- * the dataExtremes, and the current relative time - where 0 is the beginning of
- * the note and 1 is the end. The function should return the volume of the note
- * as a number between 0 and 1.
- * @name Highcharts.PointInstrumentMappingObject#volume
- * @type {string|number|Function}
- *//**
- * Define the duration of the notes for this instrument. This can be a string
- * with a data property name, e.g. `'y'`, in which case this data property is
- * used to define the duration relative to the `y`-values of the other points. A
- * higher `y` value would then result in a longer duration. This option can also
- * be a fixed number or a function. If it is a function, this function is called
- * once before the note starts playing, and should return the duration in
- * milliseconds. It receives two arguments: The point, and the dataExtremes.
- * @name Highcharts.PointInstrumentMappingObject#duration
- * @type {string|number|Function}
- *//**
- * Define the panning of the instrument. This can be a string with a data
- * property name, e.g. `'x'`, in which case this data property is used to define
- * the panning relative to the `x`-values of the other points. A higher `x`
- * value would then result in a higher panning value (panned further to the
- * right). This option can also be a fixed number or a function. If it is a
- * function, this function is called in regular intervals while the note is
- * playing. It receives three arguments: The point, the dataExtremes, and the
- * current relative time - where 0 is the beginning of the note and 1 is the
- * end. The function should return the panning of the note as a number between
- * -1 and 1.
- * @name Highcharts.PointInstrumentMappingObject#pan
- * @type {string|number|Function|undefined}
- *//**
- * Define the frequency of the instrument. This can be a string with a data
- * property name, e.g. `'y'`, in which case this data property is used to define
- * the frequency relative to the `y`-values of the other points. A higher `y`
- * value would then result in a higher frequency. This option can also be a
- * fixed number or a function. If it is a function, this function is called in
- * regular intervals while the note is playing. It receives three arguments:
- * The point, the dataExtremes, and the current relative time - where 0 is the
- * beginning of the note and 1 is the end. The function should return the
- * frequency of the note as a number (in Hz).
- * @name Highcharts.PointInstrumentMappingObject#frequency
- * @type {string|number|Function}
- */
- /**
- * @requires module:modules/sonification
- *
- * @interface Highcharts.PointInstrumentOptionsObject
- *//**
- * The minimum duration for a note when using a data property for duration. Can
- * be overridden by using either a fixed number or a function for
- * instrumentMapping.duration. Defaults to 20.
- * @name Highcharts.PointInstrumentOptionsObject#minDuration
- * @type {number|undefined}
- *//**
- * The maximum duration for a note when using a data property for duration. Can
- * be overridden by using either a fixed number or a function for
- * instrumentMapping.duration. Defaults to 2000.
- * @name Highcharts.PointInstrumentOptionsObject#maxDuration
- * @type {number|undefined}
- *//**
- * The minimum pan value for a note when using a data property for panning. Can
- * be overridden by using either a fixed number or a function for
- * instrumentMapping.pan. Defaults to -1 (fully left).
- * @name Highcharts.PointInstrumentOptionsObject#minPan
- * @type {number|undefined}
- *//**
- * The maximum pan value for a note when using a data property for panning. Can
- * be overridden by using either a fixed number or a function for
- * instrumentMapping.pan. Defaults to 1 (fully right).
- * @name Highcharts.PointInstrumentOptionsObject#maxPan
- * @type {number|undefined}
- *//**
- * The minimum volume for a note when using a data property for volume. Can be
- * overridden by using either a fixed number or a function for
- * instrumentMapping.volume. Defaults to 0.1.
- * @name Highcharts.PointInstrumentOptionsObject#minVolume
- * @type {number|undefined}
- *//**
- * The maximum volume for a note when using a data property for volume. Can be
- * overridden by using either a fixed number or a function for
- * instrumentMapping.volume. Defaults to 1.
- * @name Highcharts.PointInstrumentOptionsObject#maxVolume
- * @type {number|undefined}
- *//**
- * The minimum frequency for a note when using a data property for frequency.
- * Can be overridden by using either a fixed number or a function for
- * instrumentMapping.frequency. Defaults to 220.
- * @name Highcharts.PointInstrumentOptionsObject#minFrequency
- * @type {number|undefined}
- *//**
- * The maximum frequency for a note when using a data property for frequency.
- * Can be overridden by using either a fixed number or a function for
- * instrumentMapping.frequency. Defaults to 2200.
- * @name Highcharts.PointInstrumentOptionsObject#maxFrequency
- * @type {number|undefined}
- */
- /**
- * An instrument definition for a point, specifying the instrument to play and
- * how to play it.
- *
- * @interface Highcharts.PointInstrumentObject
- *//**
- * An Instrument instance or the name of the instrument in the
- * Highcharts.sonification.instruments map.
- * @name Highcharts.PointInstrumentObject#instrument
- * @type {Highcharts.Instrument|string}
- *//**
- * Mapping of instrument parameters for this instrument.
- * @name Highcharts.PointInstrumentObject#instrumentMapping
- * @type {Highcharts.PointInstrumentMappingObject}
- *//**
- * Options for this instrument.
- * @name Highcharts.PointInstrumentObject#instrumentOptions
- * @type {Highcharts.PointInstrumentOptionsObject|undefined}
- *//**
- * Callback to call when the instrument has stopped playing.
- * @name Highcharts.PointInstrumentObject#onEnd
- * @type {Function|undefined}
- */
- /**
- * Options for sonifying a point.
- * @interface Highcharts.PointSonifyOptionsObject
- *//**
- * The instrument definitions for this point.
- * @name Highcharts.PointSonifyOptionsObject#instruments
- * @type {Array<Highcharts.PointInstrumentObject>}
- *//**
- * Optionally provide the minimum/maximum values for the points. If this is not
- * supplied, it is calculated from the points in the chart on demand. This
- * option is supplied in the following format, as a map of point data properties
- * to objects with min/max values:
- * ```js
- * dataExtremes: {
- * y: {
- * min: 0,
- * max: 100
- * },
- * z: {
- * min: -10,
- * max: 10
- * }
- * // Properties used and not provided are calculated on demand
- * }
- * ```
- * @name Highcharts.PointSonifyOptionsObject#dataExtremes
- * @type {object|undefined}
- *//**
- * Callback called when the sonification has finished.
- * @name Highcharts.PointSonifyOptionsObject#onEnd
- * @type {Function|undefined}
- */
- 'use strict';
- import H from '../../parts/Globals.js';
- import utilities from 'utilities.js';
- // Defaults for the instrument options
- // NOTE: Also change defaults in Highcharts.PointInstrumentOptionsObject if
- // making changes here.
- var defaultInstrumentOptions = {
- minDuration: 20,
- maxDuration: 2000,
- minVolume: 0.1,
- maxVolume: 1,
- minPan: -1,
- maxPan: 1,
- minFrequency: 220,
- maxFrequency: 2200
- };
- /**
- * Sonify a single point.
- *
- * @sample highcharts/sonification/point-basic/
- * Click on points to sonify
- * @sample highcharts/sonification/point-advanced/
- * Sonify bubbles
- *
- * @requires module:modules/sonification
- *
- * @function Highcharts.Point#sonify
- *
- * @param {Highcharts.PointSonifyOptionsObject} options
- * Options for the sonification of the point.
- */
- function pointSonify(options) {
- var point = this,
- chart = point.series.chart,
- dataExtremes = options.dataExtremes || {},
- // Get the value to pass to instrument.play from the mapping value
- // passed in.
- getMappingValue = function (
- value, makeFunction, allowedExtremes, allowedValues
- ) {
- // Fixed number, just use that
- if (typeof value === 'number' || value === undefined) {
- return value;
- }
- // Function. Return new function if we try to use callback,
- // otherwise call it now and return result.
- if (typeof value === 'function') {
- return makeFunction ?
- function (time) {
- return value(point, dataExtremes, time);
- } :
- value(point, dataExtremes);
- }
- // String, this is a data prop.
- if (typeof value === 'string') {
- // Find data extremes if we don't have them
- dataExtremes[value] = dataExtremes[value] ||
- utilities.calculateDataExtremes(
- point.series.chart, value
- );
- // Find the value
- return utilities.virtualAxisTranslate(
- H.pick(point[value], point.options[value]),
- dataExtremes[value],
- allowedExtremes,
- allowedValues
- );
- }
- };
- // Register playing point on chart
- chart.sonification.currentlyPlayingPoint = point;
- // Keep track of instruments playing
- point.sonification = point.sonification || {};
- point.sonification.instrumentsPlaying =
- point.sonification.instrumentsPlaying || {};
- // Register signal handler for the point
- var signalHandler = point.sonification.signalHandler =
- point.sonification.signalHandler ||
- new utilities.SignalHandler(['onEnd']);
- signalHandler.clearSignalCallbacks();
- signalHandler.registerSignalCallbacks({ onEnd: options.onEnd });
- // If we have a null point or invisible point, just return
- if (point.isNull || !point.visible || !point.series.visible) {
- signalHandler.emitSignal('onEnd');
- return;
- }
- // Go through instruments and play them
- options.instruments.forEach(function (instrumentDefinition) {
- var instrument = typeof instrumentDefinition.instrument === 'string' ?
- H.sonification.instruments[instrumentDefinition.instrument] :
- instrumentDefinition.instrument,
- mapping = instrumentDefinition.instrumentMapping || {},
- extremes = H.merge(
- defaultInstrumentOptions,
- instrumentDefinition.instrumentOptions
- ),
- id = instrument.id,
- onEnd = function (cancelled) {
- // Instrument on end
- if (instrumentDefinition.onEnd) {
- instrumentDefinition.onEnd.apply(this, arguments);
- }
- // Remove currently playing point reference on chart
- if (
- chart.sonification &&
- chart.sonification.currentlyPlayingPoint
- ) {
- delete chart.sonification.currentlyPlayingPoint;
- }
- // Remove reference from instruments playing
- if (
- point.sonification && point.sonification.instrumentsPlaying
- ) {
- delete point.sonification.instrumentsPlaying[id];
- // This was the last instrument?
- if (
- !Object.keys(
- point.sonification.instrumentsPlaying
- ).length
- ) {
- signalHandler.emitSignal('onEnd', cancelled);
- }
- }
- };
- // Play the note on the instrument
- if (instrument && instrument.play) {
- point.sonification.instrumentsPlaying[instrument.id] = instrument;
- instrument.play({
- frequency: getMappingValue(
- mapping.frequency,
- true,
- { min: extremes.minFrequency, max: extremes.maxFrequency }
- ),
- duration: getMappingValue(
- mapping.duration,
- false,
- { min: extremes.minDuration, max: extremes.maxDuration }
- ),
- pan: getMappingValue(
- mapping.pan,
- true,
- { min: extremes.minPan, max: extremes.maxPan }
- ),
- volume: getMappingValue(
- mapping.volume,
- true,
- { min: extremes.minVolume, max: extremes.maxVolume }
- ),
- onEnd: onEnd,
- minFrequency: extremes.minFrequency,
- maxFrequency: extremes.maxFrequency
- });
- } else {
- H.error(30);
- }
- });
- }
- /**
- * Cancel sonification of a point. Calls onEnd functions.
- *
- * @requires module:modules/sonification
- *
- * @function Highcharts.Point#cancelSonify
- *
- * @param {boolean} [fadeOut=false]
- * Whether or not to fade out as we stop. If false, the points are
- * cancelled synchronously.
- */
- function pointCancelSonify(fadeOut) {
- var playing = this.sonification && this.sonification.instrumentsPlaying,
- instrIds = playing && Object.keys(playing);
- if (instrIds && instrIds.length) {
- instrIds.forEach(function (instr) {
- playing[instr].stop(!fadeOut, null, 'cancelled');
- });
- this.sonification.instrumentsPlaying = {};
- this.sonification.signalHandler.emitSignal('onEnd', 'cancelled');
- }
- }
- var pointSonifyFunctions = {
- pointSonify: pointSonify,
- pointCancelSonify: pointCancelSonify
- };
- export default pointSonifyFunctions;
|