1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370 |
- /**
- * @license Highcharts JS v9.1.1 (2021-06-04)
- *
- * Sonification module
- *
- * (c) 2012-2021 Øystein Moseng
- *
- * License: www.highcharts.com/license
- */
- 'use strict';
- (function (factory) {
- if (typeof module === 'object' && module.exports) {
- factory['default'] = factory;
- module.exports = factory;
- } else if (typeof define === 'function' && define.amd) {
- define('highcharts/modules/sonification', ['highcharts'], function (Highcharts) {
- factory(Highcharts);
- factory.Highcharts = Highcharts;
- return factory;
- });
- } else {
- factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
- }
- }(function (Highcharts) {
- var _modules = Highcharts ? Highcharts._modules : {};
- function _registerModule(obj, path, args, fn) {
- if (!obj.hasOwnProperty(path)) {
- obj[path] = fn.apply(null, args);
- }
- }
- _registerModule(_modules, 'Extensions/Sonification/Instrument.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Instrument class for sonification module.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var error = U.error,
- merge = U.merge,
- pick = U.pick,
- uniqueKey = U.uniqueKey;
- /**
- * A set of options for the Instrument class.
- *
- * @requires module:modules/sonification
- *
- * @interface Highcharts.InstrumentOptionsObject
- */ /**
- * The type of instrument. Currently only `oscillator` is supported. Defaults
- * to `oscillator`.
- * @name Highcharts.InstrumentOptionsObject#type
- * @type {string|undefined}
- */ /**
- * The unique ID of the instrument. Generated if not supplied.
- * @name Highcharts.InstrumentOptionsObject#id
- * @type {string|undefined}
- */ /**
- * The master volume multiplier to apply to the instrument, regardless of other
- * volume changes. Defaults to 1.
- * @name Highcharts.InstrumentPlayOptionsObject#masterVolume
- * @type {number|undefined}
- */ /**
- * When using functions to determine frequency or other parameters during
- * playback, this options specifies how often to call the callback functions.
- * Number given in milliseconds. Defaults to 20.
- * @name Highcharts.InstrumentOptionsObject#playCallbackInterval
- * @type {number|undefined}
- */ /**
- * A list of allowed frequencies for this instrument. If trying to play a
- * frequency not on this list, the closest frequency will be used. Set to `null`
- * to allow all frequencies to be used. Defaults to `null`.
- * @name Highcharts.InstrumentOptionsObject#allowedFrequencies
- * @type {Array<number>|undefined}
- */ /**
- * Options specific to oscillator instruments.
- * @name Highcharts.InstrumentOptionsObject#oscillator
- * @type {Highcharts.OscillatorOptionsObject|undefined}
- */
- /**
- * Options for playing an instrument.
- *
- * @requires module:modules/sonification
- *
- * @interface Highcharts.InstrumentPlayOptionsObject
- */ /**
- * The frequency of the note to play. Can be a fixed number, or a function. The
- * function receives one argument: the relative time of the note playing (0
- * being the start, and 1 being the end of the note). It should return the
- * frequency number for each point in time. The poll interval of this function
- * is specified by the Instrument.playCallbackInterval option.
- * @name Highcharts.InstrumentPlayOptionsObject#frequency
- * @type {number|Function}
- */ /**
- * The duration of the note in milliseconds.
- * @name Highcharts.InstrumentPlayOptionsObject#duration
- * @type {number}
- */ /**
- * The minimum frequency to allow. If the instrument has a set of allowed
- * frequencies, the closest frequency is used by default. Use this option to
- * stop too low frequencies from being used.
- * @name Highcharts.InstrumentPlayOptionsObject#minFrequency
- * @type {number|undefined}
- */ /**
- * The maximum frequency to allow. If the instrument has a set of allowed
- * frequencies, the closest frequency is used by default. Use this option to
- * stop too high frequencies from being used.
- * @name Highcharts.InstrumentPlayOptionsObject#maxFrequency
- * @type {number|undefined}
- */ /**
- * The volume of the instrument. Can be a fixed number between 0 and 1, or a
- * function. The function receives one argument: the relative time of the note
- * playing (0 being the start, and 1 being the end of the note). It should
- * return the volume for each point in time. The poll interval of this function
- * is specified by the Instrument.playCallbackInterval option. Defaults to 1.
- * @name Highcharts.InstrumentPlayOptionsObject#volume
- * @type {number|Function|undefined}
- */ /**
- * The panning of the instrument. Can be a fixed number between -1 and 1, or a
- * function. The function receives one argument: the relative time of the note
- * playing (0 being the start, and 1 being the end of the note). It should
- * return the panning value for each point in time. The poll interval of this
- * function is specified by the Instrument.playCallbackInterval option.
- * Defaults to 0.
- * @name Highcharts.InstrumentPlayOptionsObject#pan
- * @type {number|Function|undefined}
- */ /**
- * Callback function to be called when the play is completed.
- * @name Highcharts.InstrumentPlayOptionsObject#onEnd
- * @type {Function|undefined}
- */
- /**
- * @requires module:modules/sonification
- *
- * @interface Highcharts.OscillatorOptionsObject
- */ /**
- * The waveform shape to use for oscillator instruments. Defaults to `sine`.
- * @name Highcharts.OscillatorOptionsObject#waveformShape
- * @type {string|undefined}
- */
- // Default options for Instrument constructor
- var defaultOptions = {
- type: 'oscillator',
- playCallbackInterval: 20,
- masterVolume: 1,
- oscillator: {
- waveformShape: 'sine'
- }
- };
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * The Instrument class. Instrument objects represent an instrument capable of
- * playing a certain pitch for a specified duration.
- *
- * @sample highcharts/sonification/instrument/
- * Using Instruments directly
- * @sample highcharts/sonification/instrument-advanced/
- * Using callbacks for instrument parameters
- *
- * @requires module:modules/sonification
- *
- * @class
- * @name Highcharts.Instrument
- *
- * @param {Highcharts.InstrumentOptionsObject} options
- * Options for the instrument instance.
- */
- function Instrument(options) {
- this.init(options);
- }
- Instrument.prototype.init = function (options) {
- if (!this.initAudioContext()) {
- error(29);
- return;
- }
- this.options = merge(defaultOptions, options);
- this.id = this.options.id = options && options.id || uniqueKey();
- this.masterVolume = this.options.masterVolume || 0;
- // Init the audio nodes
- var ctx = H.audioContext;
- // Note: Destination node can be overridden by setting
- // Highcharts.sonification.Instrument.prototype.destinationNode.
- // This allows for inserting an additional chain of nodes after
- // the default processing.
- var destination = this.destinationNode || ctx.destination;
- this.gainNode = ctx.createGain();
- this.setGain(0);
- this.panNode = ctx.createStereoPanner && ctx.createStereoPanner();
- if (this.panNode) {
- this.setPan(0);
- this.gainNode.connect(this.panNode);
- this.panNode.connect(destination);
- }
- else {
- this.gainNode.connect(destination);
- }
- // Oscillator initialization
- if (this.options.type === 'oscillator') {
- this.initOscillator(this.options.oscillator);
- }
- // Init timer list
- this.playCallbackTimers = [];
- };
- /**
- * Return a copy of an instrument. Only one instrument instance can play at a
- * time, so use this to get a new copy of the instrument that can play alongside
- * it. The new instrument copy will receive a new ID unless one is supplied in
- * options.
- *
- * @function Highcharts.Instrument#copy
- *
- * @param {Highcharts.InstrumentOptionsObject} [options]
- * Options to merge in for the copy.
- *
- * @return {Highcharts.Instrument}
- * A new Instrument instance with the same options.
- */
- Instrument.prototype.copy = function (options) {
- return new Instrument(merge(this.options, { id: null }, options));
- };
- /**
- * Init the audio context, if we do not have one.
- * @private
- * @return {boolean} True if successful, false if not.
- */
- Instrument.prototype.initAudioContext = function () {
- var Context = H.win.AudioContext || H.win.webkitAudioContext,
- hasOldContext = !!H.audioContext;
- if (Context) {
- H.audioContext = H.audioContext || new Context();
- if (!hasOldContext &&
- H.audioContext &&
- H.audioContext.state === 'running') {
- H.audioContext.suspend(); // Pause until we need it
- }
- return !!(H.audioContext &&
- H.audioContext.createOscillator &&
- H.audioContext.createGain);
- }
- return false;
- };
- /**
- * Init an oscillator instrument.
- * @private
- * @param {Highcharts.OscillatorOptionsObject} oscillatorOptions
- * The oscillator options passed to Highcharts.Instrument#init.
- * @return {void}
- */
- Instrument.prototype.initOscillator = function (options) {
- var ctx = H.audioContext;
- this.oscillator = ctx.createOscillator();
- this.oscillator.type = options.waveformShape;
- this.oscillator.connect(this.gainNode);
- this.oscillatorStarted = false;
- };
- /**
- * Set pan position.
- * @private
- * @param {number} panValue
- * The pan position to set for the instrument.
- * @return {void}
- */
- Instrument.prototype.setPan = function (panValue) {
- if (this.panNode) {
- this.panNode.pan.setValueAtTime(panValue, H.audioContext.currentTime);
- }
- };
- /**
- * Set gain level. A maximum of 1.2 is allowed before we emit a warning. The
- * actual volume is not set above this level regardless of input. This function
- * also handles the Instrument's master volume.
- * @private
- * @param {number} gainValue
- * The gain level to set for the instrument.
- * @param {number} [rampTime=0]
- * Gradually change the gain level, time given in milliseconds.
- * @return {void}
- */
- Instrument.prototype.setGain = function (gainValue, rampTime) {
- var gainNode = this.gainNode;
- var newVal = gainValue * this.masterVolume;
- if (gainNode) {
- if (newVal > 1.2) {
- console.warn(// eslint-disable-line
- 'Highcharts sonification warning: ' +
- 'Volume of instrument set too high.');
- newVal = 1.2;
- }
- if (rampTime) {
- gainNode.gain.setValueAtTime(gainNode.gain.value, H.audioContext.currentTime);
- gainNode.gain.linearRampToValueAtTime(newVal, H.audioContext.currentTime + rampTime / 1000);
- }
- else {
- gainNode.gain.setValueAtTime(newVal, H.audioContext.currentTime);
- }
- }
- };
- /**
- * Cancel ongoing gain ramps.
- * @private
- * @return {void}
- */
- Instrument.prototype.cancelGainRamp = function () {
- if (this.gainNode) {
- this.gainNode.gain.cancelScheduledValues(0);
- }
- };
- /**
- * Set the master volume multiplier of the instrument after creation.
- * @param {number} volumeMultiplier
- * The gain level to set for the instrument.
- * @return {void}
- */
- Instrument.prototype.setMasterVolume = function (volumeMultiplier) {
- this.masterVolume = volumeMultiplier || 0;
- };
- /**
- * Get the closest valid frequency for this instrument.
- * @private
- * @param {number} frequency - The target frequency.
- * @param {number} [min] - Minimum frequency to return.
- * @param {number} [max] - Maximum frequency to return.
- * @return {number} The closest valid frequency to the input frequency.
- */
- Instrument.prototype.getValidFrequency = function (frequency, min, max) {
- var validFrequencies = this.options.allowedFrequencies,
- maximum = pick(max,
- Infinity),
- minimum = pick(min, -Infinity);
- return !validFrequencies || !validFrequencies.length ?
- // No valid frequencies for this instrument, return the target
- frequency :
- // Use the valid frequencies and return the closest match
- validFrequencies.reduce(function (acc, cur) {
- // Find the closest allowed value
- return Math.abs(cur - frequency) < Math.abs(acc - frequency) &&
- cur < maximum && cur > minimum ?
- cur : acc;
- }, Infinity);
- };
- /**
- * Clear existing play callback timers.
- * @private
- * @return {void}
- */
- Instrument.prototype.clearPlayCallbackTimers = function () {
- this.playCallbackTimers.forEach(function (timer) {
- clearInterval(timer);
- });
- this.playCallbackTimers = [];
- };
- /**
- * Set the current frequency being played by the instrument. The closest valid
- * frequency between the frequency limits is used.
- * @param {number} frequency
- * The frequency to set.
- * @param {Highcharts.Dictionary<number>} [frequencyLimits]
- * Object with maxFrequency and minFrequency
- * @return {void}
- */
- Instrument.prototype.setFrequency = function (frequency, frequencyLimits) {
- var limits = frequencyLimits || {},
- validFrequency = this.getValidFrequency(frequency,
- limits.min,
- limits.max);
- if (this.options.type === 'oscillator') {
- this.oscillatorPlay(validFrequency);
- }
- };
- /**
- * Play oscillator instrument.
- * @private
- * @param {number} frequency - The frequency to play.
- */
- Instrument.prototype.oscillatorPlay = function (frequency) {
- if (!this.oscillatorStarted) {
- this.oscillator.start();
- this.oscillatorStarted = true;
- }
- this.oscillator.frequency.setValueAtTime(frequency, H.audioContext.currentTime);
- };
- /**
- * Prepare instrument before playing. Resumes the audio context and starts the
- * oscillator.
- * @private
- */
- Instrument.prototype.preparePlay = function () {
- this.setGain(0.001);
- if (H.audioContext.state === 'suspended') {
- H.audioContext.resume();
- }
- if (this.oscillator && !this.oscillatorStarted) {
- this.oscillator.start();
- this.oscillatorStarted = true;
- }
- };
- /**
- * Play the instrument according to options.
- *
- * @sample highcharts/sonification/instrument/
- * Using Instruments directly
- * @sample highcharts/sonification/instrument-advanced/
- * Using callbacks for instrument parameters
- *
- * @function Highcharts.Instrument#play
- *
- * @param {Highcharts.InstrumentPlayOptionsObject} options
- * Options for the playback of the instrument.
- *
- * @return {void}
- */
- Instrument.prototype.play = function (options) {
- var instrument = this,
- duration = options.duration || 0,
- // Set a value, or if it is a function, set it continously as a timer.
- // Pass in the value/function to set, the setter function, and any
- // additional data to pass through to the setter function.
- setOrStartTimer = function (value,
- setter,
- setterData) {
- var target = options.duration,
- currentDurationIx = 0,
- callbackInterval = instrument.options.playCallbackInterval;
- if (typeof value === 'function') {
- var timer_1 = setInterval(function () {
- currentDurationIx++;
- var curTime = (currentDurationIx * callbackInterval / target);
- if (curTime >= 1) {
- instrument[setter](value(1), setterData);
- clearInterval(timer_1);
- }
- else {
- instrument[setter](value(curTime), setterData);
- }
- }, callbackInterval);
- instrument.playCallbackTimers.push(timer_1);
- }
- else {
- instrument[setter](value, setterData);
- }
- };
- if (!instrument.id) {
- // No audio support - do nothing
- return;
- }
- // If the AudioContext is suspended we have to resume it before playing
- if (H.audioContext.state === 'suspended' ||
- this.oscillator && !this.oscillatorStarted) {
- instrument.preparePlay();
- // Try again in 10ms
- setTimeout(function () {
- instrument.play(options);
- }, 10);
- return;
- }
- // Clear any existing play timers
- if (instrument.playCallbackTimers.length) {
- instrument.clearPlayCallbackTimers();
- }
- // Clear any gain ramps
- instrument.cancelGainRamp();
- // Clear stop oscillator timer
- if (instrument.stopOscillatorTimeout) {
- clearTimeout(instrument.stopOscillatorTimeout);
- delete instrument.stopOscillatorTimeout;
- }
- // If a note is playing right now, clear the stop timeout, and call the
- // callback.
- if (instrument.stopTimeout) {
- clearTimeout(instrument.stopTimeout);
- delete instrument.stopTimeout;
- if (instrument.stopCallback) {
- // We have a callback for the play we are interrupting. We do not
- // allow this callback to start a new play, because that leads to
- // chaos. We pass in 'cancelled' to indicate that this note did not
- // finish, but still stopped.
- instrument._play = instrument.play;
- instrument.play = function () { };
- instrument.stopCallback('cancelled');
- instrument.play = instrument._play;
- }
- }
- // Stop the note without fadeOut if the duration is too short to hear the
- // note otherwise.
- var immediate = duration < H.sonification.fadeOutDuration + 20;
- // Stop the instrument after the duration of the note
- instrument.stopCallback = options.onEnd;
- var onStop = function () {
- delete instrument.stopTimeout;
- instrument.stop(immediate);
- };
- if (duration) {
- instrument.stopTimeout = setTimeout(onStop, immediate ? duration :
- duration - H.sonification.fadeOutDuration);
- // Play the note
- setOrStartTimer(options.frequency, 'setFrequency', {
- minFrequency: options.minFrequency,
- maxFrequency: options.maxFrequency
- });
- // Set the volume and panning
- setOrStartTimer(pick(options.volume, 1), 'setGain', 4); // Slight ramp
- setOrStartTimer(pick(options.pan, 0), 'setPan');
- }
- else {
- // No note duration, so just stop immediately
- onStop();
- }
- };
- /**
- * Mute an instrument that is playing. If the instrument is not currently
- * playing, this function does nothing.
- *
- * @function Highcharts.Instrument#mute
- */
- Instrument.prototype.mute = function () {
- this.setGain(0.0001, H.sonification.fadeOutDuration * 0.8);
- };
- /**
- * Stop the instrument playing.
- *
- * @function Highcharts.Instrument#stop
- *
- * @param {boolean} immediately
- * Whether to do the stop immediately or fade out.
- *
- * @param {Function} [onStopped]
- * Callback function to be called when the stop is completed.
- *
- * @param {*} [callbackData]
- * Data to send to the onEnd callback functions.
- *
- * @return {void}
- */
- Instrument.prototype.stop = function (immediately, onStopped, callbackData) {
- var instr = this,
- reset = function () {
- // Remove timeout reference
- if (instr.stopOscillatorTimeout) {
- delete instr.stopOscillatorTimeout;
- }
- // The oscillator may have stopped in the meantime here, so allow
- // this function to fail if so.
- try {
- instr.oscillator.stop();
- }
- catch (e) {
- // silent error
- }
- instr.oscillator.disconnect(instr.gainNode);
- // We need a new oscillator in order to restart it
- instr.initOscillator(instr.options.oscillator);
- // Done stopping, call the callback from the stop
- if (onStopped) {
- onStopped(callbackData);
- }
- // Call the callback for the play we finished
- if (instr.stopCallback) {
- instr.stopCallback(callbackData);
- }
- };
- // Clear any existing timers
- if (instr.playCallbackTimers.length) {
- instr.clearPlayCallbackTimers();
- }
- if (instr.stopTimeout) {
- clearTimeout(instr.stopTimeout);
- }
- if (immediately) {
- instr.setGain(0);
- reset();
- }
- else {
- instr.mute();
- // Stop the oscillator after the mute fade-out has finished
- instr.stopOscillatorTimeout =
- setTimeout(reset, H.sonification.fadeOutDuration + 100);
- }
- };
- return Instrument;
- });
- _registerModule(_modules, 'Extensions/Sonification/MusicalFrequencies.js', [], function () {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * List of musical frequencies from C0 to C8.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var frequencies = [
- 16.351597831287414,
- 17.323914436054505,
- 18.354047994837977,
- 19.445436482630058,
- 20.601722307054366,
- 21.826764464562746,
- 23.12465141947715,
- 24.499714748859326,
- 25.956543598746574,
- 27.5,
- 29.13523509488062,
- 30.86770632850775,
- 32.70319566257483,
- 34.64782887210901,
- 36.70809598967594,
- 38.890872965260115,
- 41.20344461410875,
- 43.653528929125486,
- 46.2493028389543,
- 48.999429497718666,
- 51.91308719749314,
- 55,
- 58.27047018976124,
- 61.7354126570155,
- 65.40639132514966,
- 69.29565774421802,
- 73.41619197935188,
- 77.78174593052023,
- 82.4068892282175,
- 87.30705785825097,
- 92.4986056779086,
- 97.99885899543733,
- 103.82617439498628,
- 110,
- 116.54094037952248,
- 123.47082531403103,
- 130.8127826502993,
- 138.59131548843604,
- 146.8323839587038,
- 155.56349186104046,
- 164.81377845643496,
- 174.61411571650194,
- 184.9972113558172,
- 195.99771799087463,
- 207.65234878997256,
- 220,
- 233.08188075904496,
- 246.94165062806206,
- 261.6255653005986,
- 277.1826309768721,
- 293.6647679174076,
- 311.1269837220809,
- 329.6275569128699,
- 349.2282314330039,
- 369.9944227116344,
- 391.99543598174927,
- 415.3046975799451,
- 440,
- 466.1637615180899,
- 493.8833012561241,
- 523.2511306011972,
- 554.3652619537442,
- 587.3295358348151,
- 622.2539674441618,
- 659.2551138257398,
- 698.4564628660078,
- 739.9888454232688,
- 783.9908719634985,
- 830.6093951598903,
- 880,
- 932.3275230361799,
- 987.7666025122483,
- 1046.5022612023945,
- 1108.7305239074883,
- 1174.6590716696303,
- 1244.5079348883237,
- 1318.5102276514797,
- 1396.9129257320155,
- 1479.9776908465376,
- 1567.981743926997,
- 1661.2187903197805,
- 1760,
- 1864.6550460723597,
- 1975.533205024496,
- 2093.004522404789,
- 2217.4610478149766,
- 2349.31814333926,
- 2489.0158697766474,
- 2637.02045530296,
- 2793.825851464031,
- 2959.955381693075,
- 3135.9634878539946,
- 3322.437580639561,
- 3520,
- 3729.3100921447194,
- 3951.066410048992,
- 4186.009044809578 // C8
- ];
- return frequencies;
- });
- _registerModule(_modules, 'Extensions/Sonification/Utilities.js', [_modules['Extensions/Sonification/MusicalFrequencies.js'], _modules['Core/Utilities.js']], function (musicalFrequencies, U) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Utility functions for sonification.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var clamp = U.clamp;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * 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 {Highcharts.Dictionary<(Function|undefined)>} signals
- * An object that contains a mapping from the signal name to the callbacks. Only
- * supported events are considered.
- * @return {void}
- */
- SignalHandler.prototype.registerSignalCallbacks = function (signals) {
- var signalHandler = this;
- signalHandler.supportedSignals.forEach(function (supportedSignal) {
- var signal = signals[supportedSignal];
- if (signal) {
- (signalHandler.signals[supportedSignal] =
- signalHandler.signals[supportedSignal] || []).push(signal);
- }
- });
- };
- /**
- * 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.
- * @return {void}
- */
- 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.
- * @return {*}
- */
- SignalHandler.prototype.emitSignal = function (signalName, data) {
- var retval;
- if (this.signals[signalName]) {
- this.signals[signalName].forEach(function (handler) {
- var result = handler(data);
- retval = typeof 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 {Highcharts.RangeObject} 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 = typeof 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 {Highcharts.RangeObject} DataExtremesObject
- * The possible extremes for this value.
- * @param {object} limits
- * Limits for the virtual axis.
- * @param {boolean} [invert]
- * Invert the virtual axis.
- * @return {number}
- * The value mapped to the virtual axis.
- */
- virtualAxisTranslate: function (value, dataExtremes, limits, invert) {
- var lenValueAxis = dataExtremes.max - dataExtremes.min,
- lenVirtualAxis = Math.abs(limits.max - limits.min),
- valueDelta = invert ?
- dataExtremes.max - value :
- value - dataExtremes.min,
- virtualValueDelta = lenVirtualAxis * valueDelta / lenValueAxis,
- virtualAxisValue = limits.min + virtualValueDelta;
- return lenValueAxis > 0 ?
- clamp(virtualAxisValue, limits.min, limits.max) :
- limits.min;
- }
- };
- return utilities;
- });
- _registerModule(_modules, 'Extensions/Sonification/InstrumentDefinitions.js', [_modules['Extensions/Sonification/Instrument.js'], _modules['Extensions/Sonification/Utilities.js']], function (Instrument, utilities) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Instrument definitions for sonification module.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var instruments = {};
- ['sine', 'square', 'triangle', 'sawtooth'].forEach(function (waveform) {
- // Add basic instruments
- instruments[waveform] = new Instrument({
- oscillator: { waveformShape: waveform }
- });
- // Add musical instruments
- instruments[waveform + 'Musical'] = new Instrument({
- allowedFrequencies: utilities.musicalFrequencies,
- oscillator: { waveformShape: waveform }
- });
- // Add scaled instruments
- instruments[waveform + 'Major'] = new Instrument({
- allowedFrequencies: utilities.getMusicalScale([1, 3, 5, 6, 8, 10, 12]),
- oscillator: { waveformShape: waveform }
- });
- });
- return instruments;
- });
- _registerModule(_modules, 'Extensions/Sonification/Earcon.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Earcons for the sonification module in Highcharts.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var error = U.error,
- merge = U.merge,
- pick = U.pick,
- uniqueKey = U.uniqueKey;
- /**
- * Define an Instrument and the options for playing it.
- *
- * @requires module:modules/sonification
- *
- * @interface Highcharts.EarconInstrument
- */ /**
- * An instrument instance or the name of the instrument in the
- * Highcharts.sonification.instruments map.
- * @name Highcharts.EarconInstrument#instrument
- * @type {string|Highcharts.Instrument}
- */ /**
- * The options to pass to Instrument.play.
- * @name Highcharts.EarconInstrument#playOptions
- * @type {Highcharts.InstrumentPlayOptionsObject}
- */
- /**
- * Options for an Earcon.
- *
- * @requires module:modules/sonification
- *
- * @interface Highcharts.EarconOptionsObject
- */ /**
- * The instruments and their options defining this earcon.
- * @name Highcharts.EarconOptionsObject#instruments
- * @type {Array<Highcharts.EarconInstrument>}
- */ /**
- * The unique ID of the Earcon. Generated if not supplied.
- * @name Highcharts.EarconOptionsObject#id
- * @type {string|undefined}
- */ /**
- * Global panning of all instruments. Overrides all panning on individual
- * instruments. Can be a number between -1 and 1.
- * @name Highcharts.EarconOptionsObject#pan
- * @type {number|undefined}
- */ /**
- * Master volume for all instruments. Volume settings on individual instruments
- * can still be used for relative volume between the instruments. This setting
- * does not affect volumes set by functions in individual instruments. Can be a
- * number between 0 and 1. Defaults to 1.
- * @name Highcharts.EarconOptionsObject#volume
- * @type {number|undefined}
- */ /**
- * Callback function to call when earcon has finished playing.
- * @name Highcharts.EarconOptionsObject#onEnd
- * @type {Function|undefined}
- */
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * The Earcon class. Earcon objects represent a certain sound consisting of
- * one or more instruments playing a predefined sound.
- *
- * @sample highcharts/sonification/earcon/
- * Using earcons directly
- *
- * @requires module:modules/sonification
- *
- * @class
- * @name Highcharts.Earcon
- *
- * @param {Highcharts.EarconOptionsObject} options
- * Options for the Earcon instance.
- */
- function Earcon(options) {
- this.init(options || {});
- }
- Earcon.prototype.init = function (options) {
- this.options = options;
- if (!this.options.id) {
- this.options.id = this.id = uniqueKey();
- }
- this.instrumentsPlaying = {};
- };
- /**
- * Play the earcon, optionally overriding init options.
- *
- * @sample highcharts/sonification/earcon/
- * Using earcons directly
- *
- * @function Highcharts.Earcon#sonify
- *
- * @param {Highcharts.EarconOptionsObject} options
- * Override existing options.
- *
- * @return {void}
- */
- Earcon.prototype.sonify = function (options) {
- var playOptions = merge(this.options,
- options);
- // Find master volume/pan settings
- var masterVolume = pick(playOptions.volume, 1),
- masterPan = playOptions.pan,
- earcon = this,
- playOnEnd = options && options.onEnd,
- masterOnEnd = earcon.options.onEnd;
- // Go through the instruments and play them
- playOptions.instruments.forEach(function (opts) {
- var instrument = typeof opts.instrument === 'string' ?
- H.sonification.instruments[opts.instrument] : opts.instrument,
- instrumentOpts = merge(opts.playOptions),
- instrOnEnd,
- instrumentCopy,
- copyId = '';
- if (instrument && instrument.play) {
- if (opts.playOptions) {
- instrumentOpts.pan = pick(masterPan, instrumentOpts.pan);
- // Handle onEnd
- instrOnEnd = instrumentOpts.onEnd;
- instrumentOpts.onEnd = function () {
- delete earcon.instrumentsPlaying[copyId];
- if (instrOnEnd) {
- instrOnEnd.apply(this, arguments);
- }
- if (!Object.keys(earcon.instrumentsPlaying).length) {
- if (playOnEnd) {
- playOnEnd.apply(this, arguments);
- }
- if (masterOnEnd) {
- masterOnEnd.apply(this, arguments);
- }
- }
- };
- // Play the instrument. Use a copy so we can play multiple at
- // the same time.
- instrumentCopy = instrument.copy();
- instrumentCopy.setMasterVolume(masterVolume);
- copyId = instrumentCopy.id;
- earcon.instrumentsPlaying[copyId] = instrumentCopy;
- instrumentCopy.play(instrumentOpts);
- }
- }
- else {
- error(30);
- }
- });
- };
- /**
- * Cancel any current sonification of the Earcon. Calls onEnd functions.
- *
- * @function Highcharts.Earcon#cancelSonify
- *
- * @param {boolean} [fadeOut=false]
- * Whether or not to fade out as we stop. If false, the earcon is
- * cancelled synchronously.
- *
- * @return {void}
- */
- Earcon.prototype.cancelSonify = function (fadeOut) {
- var playing = this.instrumentsPlaying,
- instrIds = playing && Object.keys(playing);
- if (instrIds && instrIds.length) {
- instrIds.forEach(function (instr) {
- playing[instr].stop(!fadeOut, null, 'cancelled');
- });
- this.instrumentsPlaying = {};
- }
- };
- return Earcon;
- });
- _registerModule(_modules, 'Extensions/Sonification/PointSonify.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js'], _modules['Extensions/Sonification/Utilities.js']], function (H, U, utilities) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Code for sonifying single points.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var error = U.error,
- merge = U.merge,
- pick = U.pick;
- /**
- * 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. Alternatively, `'-y'` can be used,
- * which inverts the polarity, so that a higher `y` value results in a lower
- * 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. Alternatively,
- * `'-y'` can be used, in which case the polarity is inverted, and a higher
- * `y` value would result in a shorter 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). Alternatively, `'-x'` can be used, in which case the polarity is
- * inverted, and a higher `x` value would result in a lower panning value
- * (panned further to the left). 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. Alternatively, `'-y'` can be
- * used, in which case the polarity is inverted, and a higher `y` value would
- * result in a lower 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}
- */
- // 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
- };
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * 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.
- *
- * @return {void}
- */
- function pointSonify(options) {
- var point = this,
- chart = point.series.chart,
- masterVolume = pick(options.masterVolume,
- chart.options.sonification &&
- chart.options.sonification.masterVolume),
- dataExtremes = options.dataExtremes || {},
- // Get the value to pass to instrument.play from the mapping value
- // passed in.
- getMappingValue = function (value,
- makeFunction,
- allowedExtremes) {
- // 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. Potentially with negative polarity.
- if (typeof value === 'string') {
- var hasInvertedPolarity = value.charAt(0) === '-';
- var dataProp = hasInvertedPolarity ? value.slice(1) : value;
- var pointValue = pick(point[dataProp],
- point.options[dataProp]);
- // Find data extremes if we don't have them
- dataExtremes[dataProp] = dataExtremes[dataProp] ||
- utilities.calculateDataExtremes(point.series.chart, dataProp);
- // Find the value
- return utilities.virtualAxisTranslate(pointValue, dataExtremes[dataProp], allowedExtremes, hasInvertedPolarity);
- }
- // Fixed number or something else weird, just use that
- return value;
- };
- // 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 = 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) {
- if (typeof masterVolume !== 'undefined') {
- instrument.setMasterVolume(masterVolume);
- }
- 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 {
- 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.
- *
- * @return {void}
- */
- 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
- };
- return pointSonifyFunctions;
- });
- _registerModule(_modules, 'Extensions/Sonification/ChartSonify.js', [_modules['Core/Globals.js'], _modules['Core/Series/Point.js'], _modules['Core/Utilities.js'], _modules['Extensions/Sonification/Utilities.js']], function (H, Point, U, utilities) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Sonification functions for chart/series.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- /**
- * An Earcon configuration, specifying an Earcon and when to play it.
- *
- * @requires module:modules/sonification
- *
- * @interface Highcharts.EarconConfiguration
- */ /**
- * An Earcon instance.
- * @name Highcharts.EarconConfiguration#earcon
- * @type {Highcharts.Earcon}
- */ /**
- * The ID of the point to play the Earcon on.
- * @name Highcharts.EarconConfiguration#onPoint
- * @type {string|undefined}
- */ /**
- * A function to determine whether or not to play this earcon on a point. The
- * function is called for every point, receiving that point as parameter. It
- * should return either a boolean indicating whether or not to play the earcon,
- * or a new Earcon instance - in which case the new Earcon will be played.
- * @name Highcharts.EarconConfiguration#condition
- * @type {Function|undefined}
- */
- /**
- * Options for sonifying a series.
- *
- * @requires module:modules/sonification
- *
- * @interface Highcharts.SonifySeriesOptionsObject
- */ /**
- * The duration for playing the points. Note that points might continue to play
- * after the duration has passed, but no new points will start playing.
- * @name Highcharts.SonifySeriesOptionsObject#duration
- * @type {number}
- */ /**
- * The axis to use for when to play the points. Can be a string with a data
- * property (e.g. `x`), or a function. If it is a function, this function
- * receives the point as argument, and should return a numeric value. The points
- * with the lowest numeric values are then played first, and the time between
- * points will be proportional to the distance between the numeric values.
- * @name Highcharts.SonifySeriesOptionsObject#pointPlayTime
- * @type {string|Function}
- */ /**
- * The instrument definitions for the points in this series.
- * @name Highcharts.SonifySeriesOptionsObject#instruments
- * @type {Array<Highcharts.PointInstrumentObject>}
- */ /**
- * Earcons to add to the series.
- * @name Highcharts.SonifySeriesOptionsObject#earcons
- * @type {Array<Highcharts.EarconConfiguration>|undefined}
- */ /**
- * Optionally provide the minimum/maximum data values for the points. If this is
- * not supplied, it is calculated from all 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.SonifySeriesOptionsObject#dataExtremes
- * @type {Highcharts.Dictionary<Highcharts.RangeObject>|undefined}
- */ /**
- * Callback before a point is played.
- * @name Highcharts.SonifySeriesOptionsObject#onPointStart
- * @type {Function|undefined}
- */ /**
- * Callback after a point has finished playing.
- * @name Highcharts.SonifySeriesOptionsObject#onPointEnd
- * @type {Function|undefined}
- */ /**
- * Callback after the series has played.
- * @name Highcharts.SonifySeriesOptionsObject#onEnd
- * @type {Function|undefined}
- */
- ''; // detach doclets above
- var find = U.find,
- isArray = U.isArray,
- merge = U.merge,
- pick = U.pick,
- splat = U.splat,
- objectEach = U.objectEach;
- /**
- * Get the relative time value of a point.
- * @private
- * @param {Highcharts.Point} point
- * The point.
- * @param {Function|string} timeProp
- * The time axis data prop or the time function.
- * @return {number}
- * The time value.
- */
- function getPointTimeValue(point, timeProp) {
- return typeof timeProp === 'function' ?
- timeProp(point) :
- pick(point[timeProp], point.options[timeProp]);
- }
- /**
- * Get the time extremes of this series. This is handled outside of the
- * dataExtremes, as we always want to just sonify the visible points, and we
- * always want the extremes to be the extremes of the visible points.
- * @private
- * @param {Highcharts.Series} series
- * The series to compute on.
- * @param {Function|string} timeProp
- * The time axis data prop or the time function.
- * @return {Highcharts.RangeObject}
- * Object with min/max extremes for the time values.
- */
- function getTimeExtremes(series, timeProp) {
- // Compute the extremes from the visible points.
- return series.points.reduce(function (acc, point) {
- var value = getPointTimeValue(point,
- timeProp);
- acc.min = Math.min(acc.min, value);
- acc.max = Math.max(acc.max, value);
- return acc;
- }, {
- min: Infinity,
- max: -Infinity
- });
- }
- /**
- * Calculate value extremes for used instrument data properties on a chart.
- * @private
- * @param {Highcharts.Chart} chart
- * The chart to calculate extremes from.
- * @param {Array<Highcharts.PointInstrumentObject>} [instruments]
- * Additional instrument definitions to inspect for data props used, in
- * addition to the instruments defined in the chart options.
- * @param {Highcharts.Dictionary<Highcharts.RangeObject>} [dataExtremes]
- * Predefined extremes for each data prop.
- * @return {Highcharts.Dictionary<Highcharts.RangeObject>}
- * New extremes with data properties mapped to min/max objects.
- */
- function getExtremesForInstrumentProps(chart, instruments, dataExtremes) {
- var allInstrumentDefinitions = (instruments || []).slice(0);
- var defaultInstrumentDef = (chart.options.sonification &&
- chart.options.sonification.defaultInstrumentOptions);
- var optionDefToInstrDef = function (optionDef) { return ({
- instrumentMapping: optionDef.mapping
- }); };
- if (defaultInstrumentDef) {
- allInstrumentDefinitions.push(optionDefToInstrDef(defaultInstrumentDef));
- }
- chart.series.forEach(function (series) {
- var instrOptions = (series.options.sonification &&
- series.options.sonification.instruments);
- if (instrOptions) {
- allInstrumentDefinitions = allInstrumentDefinitions.concat(instrOptions.map(optionDefToInstrDef));
- }
- });
- return (allInstrumentDefinitions).reduce(function (newExtremes, instrumentDefinition) {
- Object.keys(instrumentDefinition.instrumentMapping || {}).forEach(function (instrumentParameter) {
- var value = instrumentDefinition.instrumentMapping[instrumentParameter];
- if (typeof value === 'string' && !newExtremes[value]) {
- // This instrument parameter is mapped to a data prop.
- // If we don't have predefined data extremes, find them.
- newExtremes[value] = utilities.calculateDataExtremes(chart, value);
- }
- });
- return newExtremes;
- }, merge(dataExtremes));
- }
- /**
- * Get earcons for the point if there are any.
- * @private
- * @param {Highcharts.Point} point
- * The point to find earcons for.
- * @param {Array<Highcharts.EarconConfiguration>} earconDefinitions
- * Earcons to check.
- * @return {Array<Highcharts.Earcon>}
- * Array of earcons to be played with this point.
- */
- function getPointEarcons(point, earconDefinitions) {
- return earconDefinitions.reduce(function (earcons, earconDefinition) {
- var cond,
- earcon = earconDefinition.earcon;
- if (earconDefinition.condition) {
- // We have a condition. This overrides onPoint
- cond = earconDefinition.condition(point);
- if (cond instanceof H.sonification.Earcon) {
- // Condition returned an earcon
- earcons.push(cond);
- }
- else if (cond) {
- // Condition returned true
- earcons.push(earcon);
- }
- }
- else if (earconDefinition.onPoint &&
- point.id === earconDefinition.onPoint) {
- // We have earcon onPoint
- earcons.push(earcon);
- }
- return earcons;
- }, []);
- }
- /**
- * Utility function to get a new list of instrument options where all the
- * instrument references are copies.
- * @private
- * @param {Array<Highcharts.PointInstrumentObject>} instruments
- * The instrument options.
- * @return {Array<Highcharts.PointInstrumentObject>}
- * Array of copied instrument options.
- */
- function makeInstrumentCopies(instruments) {
- return instruments.map(function (instrumentDef) {
- var instrument = instrumentDef.instrument,
- copy = (typeof instrument === 'string' ?
- H.sonification.instruments[instrument] :
- instrument).copy();
- return merge(instrumentDef, { instrument: copy });
- });
- }
- /**
- * Utility function to apply a master volume to a list of instrument
- * options.
- * @private
- * @param {Array<Highcharts.PointInstrumentObject>} instruments
- * The instrument options. Only options with Instrument object instances
- * will be affected.
- * @param {number} masterVolume
- * The master volume multiplier to apply to the instruments.
- * @return {Array<Highcharts.PointInstrumentObject>}
- * Array of instrument options.
- */
- function applyMasterVolumeToInstruments(instruments, masterVolume) {
- instruments.forEach(function (instrOpts) {
- var instr = instrOpts.instrument;
- if (typeof instr !== 'string') {
- instr.setMasterVolume(masterVolume);
- }
- });
- return instruments;
- }
- /**
- * Utility function to find the duration of the final note in a series.
- * @private
- * @param {Highcharts.Series} series The data series to calculate on.
- * @param {Array<Highcharts.PointInstrumentObject>} instruments The instrument options for this series.
- * @param {Highcharts.Dictionary<Highcharts.RangeObject>} dataExtremes Value extremes for the data series props.
- * @return {number} The duration of the final note in milliseconds.
- */
- function getFinalNoteDuration(series, instruments, dataExtremes) {
- var finalPoint = series.points[series.points.length - 1];
- return instruments.reduce(function (duration, instrument) {
- var mapping = instrument.instrumentMapping.duration;
- var instrumentDuration;
- if (typeof mapping === 'string') {
- instrumentDuration = 0; // Ignore, no easy way to map this
- }
- else if (typeof mapping === 'function') {
- instrumentDuration = mapping(finalPoint, dataExtremes);
- }
- else {
- instrumentDuration = mapping;
- }
- return Math.max(duration, instrumentDuration);
- }, 0);
- }
- /**
- * Create a TimelinePath from a series. Takes the same options as seriesSonify.
- * To intuitively allow multiple series to play simultaneously we make copies of
- * the instruments for each series.
- * @private
- * @param {Highcharts.Series} series
- * The series to build from.
- * @param {Highcharts.SonifySeriesOptionsObject} options
- * The options for building the TimelinePath.
- * @return {Highcharts.TimelinePath}
- * A timeline path with events.
- */
- function buildTimelinePathFromSeries(series, options) {
- // options.timeExtremes is internal and used so that the calculations from
- // chart.sonify can be reused.
- var timeExtremes = options.timeExtremes || getTimeExtremes(series,
- options.pointPlayTime),
- // Compute any data extremes that aren't defined yet
- dataExtremes = getExtremesForInstrumentProps(series.chart,
- options.instruments,
- options.dataExtremes),
- minimumSeriesDurationMs = 10,
- // Get the duration of the final note
- finalNoteDuration = getFinalNoteDuration(series,
- options.instruments,
- dataExtremes),
- // Get time offset for a point, relative to duration
- pointToTime = function (point) {
- return utilities.virtualAxisTranslate(getPointTimeValue(point,
- options.pointPlayTime),
- timeExtremes, { min: 0,
- max: Math.max(options.duration - finalNoteDuration,
- minimumSeriesDurationMs) });
- }, masterVolume = pick(options.masterVolume, 1),
- // Make copies of the instruments used for this series, to allow
- // multiple series with the same instrument to play together
- instrumentCopies = makeInstrumentCopies(options.instruments), instruments = applyMasterVolumeToInstruments(instrumentCopies, masterVolume),
- // Go through the points, convert to events, optionally add Earcons
- timelineEvents = series.points.reduce(function (events, point) {
- var earcons = getPointEarcons(point,
- options.earcons || []),
- time = pointToTime(point);
- return events.concat(
- // Event object for point
- new H.sonification.TimelineEvent({
- eventObject: point,
- time: time,
- id: point.id,
- playOptions: {
- instruments: instruments,
- dataExtremes: dataExtremes,
- masterVolume: masterVolume
- }
- }),
- // Earcons
- earcons.map(function (earcon) {
- return new H.sonification.TimelineEvent({
- eventObject: earcon,
- time: time,
- playOptions: {
- volume: masterVolume
- }
- });
- }));
- }, []);
- // Build the timeline path
- return new H.sonification.TimelinePath({
- events: timelineEvents,
- onStart: function () {
- if (options.onStart) {
- options.onStart(series);
- }
- },
- onEventStart: function (event) {
- var eventObject = event.options && event.options.eventObject;
- if (eventObject instanceof Point) {
- // Check for hidden series
- if (!eventObject.series.visible &&
- !eventObject.series.chart.series.some(function (series) {
- return series.visible;
- })) {
- // We have no visible series, stop the path.
- event.timelinePath.timeline.pause();
- event.timelinePath.timeline.resetCursor();
- return false;
- }
- // Emit onPointStart
- if (options.onPointStart) {
- options.onPointStart(event, eventObject);
- }
- }
- },
- onEventEnd: function (eventData) {
- var eventObject = eventData.event && eventData.event.options &&
- eventData.event.options.eventObject;
- if (eventObject instanceof Point && options.onPointEnd) {
- options.onPointEnd(eventData.event, eventObject);
- }
- },
- onEnd: function () {
- if (options.onEnd) {
- options.onEnd(series);
- }
- },
- targetDuration: options.duration
- });
- }
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * Sonify a series.
- *
- * @sample highcharts/sonification/series-basic/
- * Click on series to sonify
- * @sample highcharts/sonification/series-earcon/
- * Series with earcon
- * @sample highcharts/sonification/point-play-time/
- * Play y-axis by time
- * @sample highcharts/sonification/earcon-on-point/
- * Earcon set on point
- *
- * @requires module:modules/sonification
- *
- * @function Highcharts.Series#sonify
- *
- * @param {Highcharts.SonifySeriesOptionsObject} [options]
- * The options for sonifying this series. If not provided,
- * uses options set on chart and series.
- *
- * @return {void}
- */
- function seriesSonify(options) {
- var mergedOptions = getSeriesSonifyOptions(this,
- options);
- var timelinePath = buildTimelinePathFromSeries(this,
- mergedOptions);
- var chartSonification = this.chart.sonification;
- // Only one timeline can play at a time. If we want multiple series playing
- // at the same time, use chart.sonify.
- if (chartSonification.timeline) {
- chartSonification.timeline.pause();
- }
- // Store reference to duration
- chartSonification.duration = mergedOptions.duration;
- // Create new timeline for this series, and play it.
- chartSonification.timeline = new H.sonification.Timeline({
- paths: [timelinePath]
- });
- chartSonification.timeline.play();
- }
- /**
- * Utility function to assemble options for creating a TimelinePath from a
- * series when sonifying an entire chart.
- * @private
- * @param {Highcharts.Series} series
- * The series to return options for.
- * @param {Highcharts.RangeObject} dataExtremes
- * Pre-calculated data extremes for the chart.
- * @param {Highcharts.SonificationOptions} chartSonifyOptions
- * Options passed in to chart.sonify.
- * @return {Partial<Highcharts.SonifySeriesOptionsObject>}
- * Options for buildTimelinePathFromSeries.
- */
- function buildChartSonifySeriesOptions(series, dataExtremes, chartSonifyOptions) {
- var additionalSeriesOptions = chartSonifyOptions.seriesOptions || {};
- var pointPlayTime = (series.chart.options.sonification &&
- series.chart.options.sonification.defaultInstrumentOptions &&
- series.chart.options.sonification.defaultInstrumentOptions.mapping &&
- series.chart.options.sonification.defaultInstrumentOptions.mapping.pointPlayTime ||
- 'x');
- var configOptions = chartOptionsToSonifySeriesOptions(series);
- return merge(
- // Options from chart configuration
- configOptions,
- // Options passed in
- {
- // Calculated dataExtremes for chart
- dataExtremes: dataExtremes,
- // We need to get timeExtremes for each series. We pass this
- // in when building the TimelinePath objects to avoid
- // calculating twice.
- timeExtremes: getTimeExtremes(series, pointPlayTime),
- // Some options we just pass on
- instruments: chartSonifyOptions.instruments || configOptions.instruments,
- onStart: chartSonifyOptions.onSeriesStart || configOptions.onStart,
- onEnd: chartSonifyOptions.onSeriesEnd || configOptions.onEnd,
- earcons: chartSonifyOptions.earcons || configOptions.earcons,
- masterVolume: pick(chartSonifyOptions.masterVolume, configOptions.masterVolume)
- },
- // Merge in the specific series options by ID if any are passed in
- isArray(additionalSeriesOptions) ? (find(additionalSeriesOptions, function (optEntry) {
- return optEntry.id === pick(series.id, series.options.id);
- }) || {}) : additionalSeriesOptions, {
- // Forced options
- pointPlayTime: pointPlayTime
- });
- }
- /**
- * Utility function to normalize the ordering of timeline paths when sonifying
- * a chart.
- * @private
- * @param {string|Array<string|Highcharts.Earcon|Array<string|Highcharts.Earcon>>} orderOptions -
- * Order options for the sonification.
- * @param {Highcharts.Chart} chart - The chart we are sonifying.
- * @param {Function} seriesOptionsCallback
- * A function that takes a series as argument, and returns the series options
- * for that series to be used with buildTimelinePathFromSeries.
- * @return {Array<object|Array<object|Highcharts.TimelinePath>>} If order is
- * sequential, we return an array of objects to create series paths from. If
- * order is simultaneous we return an array of an array with the same. If there
- * is a custom order, we return an array of arrays of either objects (for
- * series) or TimelinePaths (for earcons and delays).
- */
- function buildPathOrder(orderOptions, chart, seriesOptionsCallback) {
- var order;
- if (orderOptions === 'sequential' || orderOptions === 'simultaneous') {
- // Just add the series from the chart
- order = chart.series.reduce(function (seriesList, series) {
- if (series.visible &&
- (series.options.sonification &&
- series.options.sonification.enabled) !== false) {
- seriesList.push({
- series: series,
- seriesOptions: seriesOptionsCallback(series)
- });
- }
- return seriesList;
- }, []);
- // If order is simultaneous, group all series together
- if (orderOptions === 'simultaneous') {
- order = [order];
- }
- }
- else {
- // We have a specific order, and potentially custom items - like
- // earcons or silent waits.
- order = orderOptions.reduce(function (orderList, orderDef) {
- // Return set of items to play simultaneously. Could be only one.
- var simulItems = splat(orderDef).reduce(function (items,
- item) {
- var itemObject;
- // Is this item a series ID?
- if (typeof item === 'string') {
- var series = chart.get(item);
- if (series.visible) {
- itemObject = {
- series: series,
- seriesOptions: seriesOptionsCallback(series)
- };
- }
- // Is it an earcon? If so, just create the path.
- }
- else if (item instanceof H.sonification.Earcon) {
- // Path with a single event
- itemObject = new H.sonification.TimelinePath({
- events: [new H.sonification.TimelineEvent({
- eventObject: item
- })]
- });
- }
- // Is this item a silent wait? If so, just create the path.
- if (item.silentWait) {
- itemObject = new H.sonification.TimelinePath({
- silentWait: item.silentWait
- });
- }
- // Add to items to play simultaneously
- if (itemObject) {
- items.push(itemObject);
- }
- return items;
- }, []);
- // Add to order list
- if (simulItems.length) {
- orderList.push(simulItems);
- }
- return orderList;
- }, []);
- }
- return order;
- }
- /**
- * Utility function to add a silent wait after all series.
- * @private
- * @param {Array<object|Array<object|TimelinePath>>} order
- * The order of items.
- * @param {number} wait
- * The wait in milliseconds to add.
- * @return {Array<object|Array<object|TimelinePath>>}
- * The order with waits inserted.
- */
- function addAfterSeriesWaits(order, wait) {
- if (!wait) {
- return order;
- }
- return order.reduce(function (newOrder, orderDef, i) {
- var simultaneousPaths = splat(orderDef);
- newOrder.push(simultaneousPaths);
- // Go through the simultaneous paths and see if there is a series there
- if (i < order.length - 1 && // Do not add wait after last series
- simultaneousPaths.some(function (item) {
- return item.series;
- })) {
- // We have a series, meaning we should add a wait after these
- // paths have finished.
- newOrder.push(new H.sonification.TimelinePath({
- silentWait: wait
- }));
- }
- return newOrder;
- }, []);
- }
- /**
- * Utility function to find the total amout of wait time in the TimelinePaths.
- * @private
- * @param {Array<object|Array<object|TimelinePath>>} order - The order of
- * TimelinePaths/items.
- * @return {number} The total time in ms spent on wait paths between playing.
- */
- function getWaitTime(order) {
- return order.reduce(function (waitTime, orderDef) {
- var def = splat(orderDef);
- return waitTime + (def.length === 1 &&
- def[0].options &&
- def[0].options.silentWait || 0);
- }, 0);
- }
- /**
- * Utility function to ensure simultaneous paths have start/end events at the
- * same time, to sync them.
- * @private
- * @param {Array<Highcharts.TimelinePath>} paths - The paths to sync.
- */
- function syncSimultaneousPaths(paths) {
- // Find the extremes for these paths
- var extremes = paths.reduce(function (extremes,
- path) {
- var events = path.events;
- if (events && events.length) {
- extremes.min = Math.min(events[0].time, extremes.min);
- extremes.max = Math.max(events[events.length - 1].time, extremes.max);
- }
- return extremes;
- }, {
- min: Infinity,
- max: -Infinity
- });
- // Go through the paths and add events to make them fit the same timespan
- paths.forEach(function (path) {
- var events = path.events,
- hasEvents = events && events.length,
- eventsToAdd = [];
- if (!(hasEvents && events[0].time <= extremes.min)) {
- eventsToAdd.push(new H.sonification.TimelineEvent({
- time: extremes.min
- }));
- }
- if (!(hasEvents && events[events.length - 1].time >= extremes.max)) {
- eventsToAdd.push(new H.sonification.TimelineEvent({
- time: extremes.max
- }));
- }
- if (eventsToAdd.length) {
- path.addTimelineEvents(eventsToAdd);
- }
- });
- }
- /**
- * Utility function to find the total duration span for all simul path sets
- * that include series.
- * @private
- * @param {Array<object|Array<object|Highcharts.TimelinePath>>} order - The
- * order of TimelinePaths/items.
- * @return {number} The total time value span difference for all series.
- */
- function getSimulPathDurationTotal(order) {
- return order.reduce(function (durationTotal, orderDef) {
- return durationTotal + splat(orderDef).reduce(function (maxPathDuration, item) {
- var timeExtremes = (item.series &&
- item.seriesOptions &&
- item.seriesOptions.timeExtremes);
- return timeExtremes ?
- Math.max(maxPathDuration, timeExtremes.max - timeExtremes.min) : maxPathDuration;
- }, 0);
- }, 0);
- }
- /**
- * Function to calculate the duration in ms for a series.
- * @private
- * @param {number} seriesValueDuration - The duration of the series in value
- * difference.
- * @param {number} totalValueDuration - The total duration of all (non
- * simultaneous) series in value difference.
- * @param {number} totalDurationMs - The desired total duration for all series
- * in milliseconds.
- * @return {number} The duration for the series in milliseconds.
- */
- function getSeriesDurationMs(seriesValueDuration, totalValueDuration, totalDurationMs) {
- // A series spanning the whole chart would get the full duration.
- return utilities.virtualAxisTranslate(seriesValueDuration, { min: 0, max: totalValueDuration }, { min: 0, max: totalDurationMs });
- }
- /**
- * Convert series building objects into paths and return a new list of
- * TimelinePaths.
- * @private
- * @param {Array<object|Array<object|Highcharts.TimelinePath>>} order - The
- * order list.
- * @param {number} duration - Total duration to aim for in milliseconds.
- * @return {Array<Array<Highcharts.TimelinePath>>} Array of TimelinePath objects
- * to play.
- */
- function buildPathsFromOrder(order, duration) {
- // Find time used for waits (custom or after series), and subtract it from
- // available duration.
- var totalAvailableDurationMs = Math.max(duration - getWaitTime(order), 0),
- // Add up simultaneous path durations to find total value span duration
- // of everything
- totalUsedDuration = getSimulPathDurationTotal(order);
- // Go through the order list and convert the items
- return order.reduce(function (allPaths, orderDef) {
- var simultaneousPaths = splat(orderDef).reduce(function (simulPaths,
- item) {
- if (item instanceof H.sonification.TimelinePath) {
- // This item is already a path object
- simulPaths.push(item);
- }
- else if (item.series) {
- // We have a series.
- // We need to set the duration of the series
- item.seriesOptions.duration =
- item.seriesOptions.duration || getSeriesDurationMs(item.seriesOptions.timeExtremes.max -
- item.seriesOptions.timeExtremes.min, totalUsedDuration, totalAvailableDurationMs);
- // Add the path
- simulPaths.push(buildTimelinePathFromSeries(item.series, item.seriesOptions));
- }
- return simulPaths;
- }, []);
- // Add in the simultaneous paths
- allPaths.push(simultaneousPaths);
- return allPaths;
- }, []);
- }
- /**
- * @private
- * @param {Highcharts.Series} series The series to get options for.
- * @param {Highcharts.SonifySeriesOptionsObject} options
- * Options to merge with user options on series/chart and default options.
- * @returns {Array<Highcharts.PointInstrumentObject>} The merged options.
- */
- function getSeriesInstrumentOptions(series, options) {
- if (options && options.instruments) {
- return options.instruments;
- }
- var defaultInstrOpts = (series.chart.options.sonification &&
- series.chart.options.sonification.defaultInstrumentOptions ||
- {});
- var seriesInstrOpts = (series.options.sonification &&
- series.options.sonification.instruments ||
- [{}]);
- var removeNullsFromObject = function (obj) {
- objectEach(obj,
- function (val,
- key) {
- if (val === null) {
- delete obj[key];
- }
- });
- };
- // Convert series options to PointInstrumentObjects and merge with
- // default options
- return (seriesInstrOpts).map(function (optionSet) {
- // Allow setting option to null to use default
- removeNullsFromObject(optionSet.mapping || {});
- removeNullsFromObject(optionSet);
- return {
- instrument: optionSet.instrument || defaultInstrOpts.instrument,
- instrumentOptions: merge(defaultInstrOpts, optionSet, {
- // Instrument options are lifted to root in the API options
- // object, so merge all in order to avoid missing any. But
- // remove the following which are not instrumentOptions:
- mapping: void 0,
- instrument: void 0
- }),
- instrumentMapping: merge(defaultInstrOpts.mapping, optionSet.mapping)
- };
- });
- }
- /**
- * Utility function to translate between options set in chart configuration and
- * a SonifySeriesOptionsObject.
- * @private
- * @param {Highcharts.Series} series The series to get options for.
- * @returns {Highcharts.SonifySeriesOptionsObject} Options for chart/series.sonify()
- */
- function chartOptionsToSonifySeriesOptions(series) {
- var seriesOpts = series.options.sonification || {};
- var chartOpts = series.chart.options.sonification || {};
- var chartEvents = chartOpts.events || {};
- var seriesEvents = seriesOpts.events || {};
- return {
- onEnd: seriesEvents.onSeriesEnd || chartEvents.onSeriesEnd,
- onStart: seriesEvents.onSeriesStart || chartEvents.onSeriesStart,
- onPointEnd: seriesEvents.onPointEnd || chartEvents.onPointEnd,
- onPointStart: seriesEvents.onPointStart || chartEvents.onPointStart,
- pointPlayTime: (chartOpts.defaultInstrumentOptions &&
- chartOpts.defaultInstrumentOptions.mapping &&
- chartOpts.defaultInstrumentOptions.mapping.pointPlayTime),
- masterVolume: chartOpts.masterVolume,
- instruments: getSeriesInstrumentOptions(series),
- earcons: seriesOpts.earcons || chartOpts.earcons
- };
- }
- /**
- * @private
- * @param {Highcharts.Series} series The series to get options for.
- * @param {Highcharts.SonifySeriesOptionsObject} options
- * Options to merge with user options on series/chart and default options.
- * @returns {Highcharts.SonifySeriesOptionsObject} The merged options.
- */
- function getSeriesSonifyOptions(series, options) {
- var chartOpts = series.chart.options.sonification;
- var seriesOpts = series.options.sonification;
- return merge({
- duration: ((seriesOpts && seriesOpts.duration) ||
- (chartOpts && chartOpts.duration))
- }, chartOptionsToSonifySeriesOptions(series), options);
- }
- /**
- * @private
- * @param {Highcharts.Chart} chart The chart to get options for.
- * @param {Highcharts.SonificationOptions} options
- * Options to merge with user options on chart and default options.
- * @returns {Highcharts.SonificationOptions} The merged options.
- */
- function getChartSonifyOptions(chart, options) {
- var chartOpts = chart.options.sonification || {};
- return merge({
- duration: chartOpts.duration,
- afterSeriesWait: chartOpts.afterSeriesWait,
- pointPlayTime: (chartOpts.defaultInstrumentOptions &&
- chartOpts.defaultInstrumentOptions.mapping &&
- chartOpts.defaultInstrumentOptions.mapping.pointPlayTime),
- order: chartOpts.order,
- onSeriesStart: (chartOpts.events && chartOpts.events.onSeriesStart),
- onSeriesEnd: (chartOpts.events && chartOpts.events.onSeriesEnd),
- onEnd: (chartOpts.events && chartOpts.events.onEnd)
- }, options);
- }
- /**
- * Options for sonifying a chart.
- *
- * @requires module:modules/sonification
- *
- * @interface Highcharts.SonificationOptions
- */ /**
- * Duration for sonifying the entire chart. The duration is distributed across
- * the different series intelligently, but does not take earcons into account.
- * It is also possible to set the duration explicitly per series, using
- * `seriesOptions`. Note that points may continue to play after the duration has
- * passed, but no new points will start playing.
- * @name Highcharts.SonificationOptions#duration
- * @type {number}
- */ /**
- * Define the order to play the series in. This can be given as a string, or an
- * array specifying a custom ordering. If given as a string, valid values are
- * `sequential` - where each series is played in order - or `simultaneous`,
- * where all series are played at once. For custom ordering, supply an array as
- * the order. Each element in the array can be either a string with a series ID,
- * an Earcon object, or an object with a numeric `silentWait` property
- * designating a number of milliseconds to wait before continuing. Each element
- * of the array will be played in order. To play elements simultaneously, group
- * the elements in an array.
- * @name Highcharts.SonificationOptions#order
- * @type {string|Array<string|Highcharts.Earcon|Array<string|Highcharts.Earcon>>}
- */ /**
- * The axis to use for when to play the points. Can be a string with a data
- * property (e.g. `x`), or a function. If it is a function, this function
- * receives the point as argument, and should return a numeric value. The points
- * with the lowest numeric values are then played first, and the time between
- * points will be proportional to the distance between the numeric values. This
- * option can not be overridden per series.
- * @name Highcharts.SonificationOptions#pointPlayTime
- * @type {string|Function}
- */ /**
- * Milliseconds of silent waiting to add between series. Note that waiting time
- * is considered part of the sonify duration.
- * @name Highcharts.SonificationOptions#afterSeriesWait
- * @type {number|undefined}
- */ /**
- * Options as given to `series.sonify` to override options per series. If the
- * option is supplied as an array of options objects, the `id` property of the
- * object should correspond to the series' id. If the option is supplied as a
- * single object, the options apply to all series.
- * @name Highcharts.SonificationOptions#seriesOptions
- * @type {Object|Array<object>|undefined}
- */ /**
- * The instrument definitions for the points in this chart.
- * @name Highcharts.SonificationOptions#instruments
- * @type {Array<Highcharts.PointInstrumentObject>|undefined}
- */ /**
- * Earcons to add to the chart. Note that earcons can also be added per series
- * using `seriesOptions`.
- * @name Highcharts.SonificationOptions#earcons
- * @type {Array<Highcharts.EarconConfiguration>|undefined}
- */ /**
- * Optionally provide the minimum/maximum data values for the points. If this is
- * not supplied, it is calculated from all 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.SonificationOptions#dataExtremes
- * @type {Highcharts.Dictionary<Highcharts.RangeObject>|undefined}
- */ /**
- * Callback before a series is played.
- * @name Highcharts.SonificationOptions#onSeriesStart
- * @type {Function|undefined}
- */ /**
- * Callback after a series has finished playing.
- * @name Highcharts.SonificationOptions#onSeriesEnd
- * @type {Function|undefined}
- */ /**
- * Callback after the chart has played.
- * @name Highcharts.SonificationOptions#onEnd
- * @type {Function|undefined}
- */
- /**
- * Sonify a chart.
- *
- * @sample highcharts/sonification/chart-sequential/
- * Sonify a basic chart
- * @sample highcharts/sonification/chart-simultaneous/
- * Sonify series simultaneously
- * @sample highcharts/sonification/chart-custom-order/
- * Custom defined order of series
- * @sample highcharts/sonification/chart-earcon/
- * Earcons on chart
- * @sample highcharts/sonification/chart-events/
- * Sonification events on chart
- *
- * @requires module:modules/sonification
- *
- * @function Highcharts.Chart#sonify
- *
- * @param {Highcharts.SonificationOptions} [options]
- * The options for sonifying this chart. If not provided,
- * uses options set on chart and series.
- *
- * @return {void}
- */
- function chartSonify(options) {
- var opts = getChartSonifyOptions(this,
- options);
- // Only one timeline can play at a time.
- if (this.sonification.timeline) {
- this.sonification.timeline.pause();
- }
- // Store reference to duration
- this.sonification.duration = opts.duration;
- // Calculate data extremes for the props used
- var dataExtremes = getExtremesForInstrumentProps(this,
- opts.instruments,
- opts.dataExtremes);
- // Figure out ordering of series and custom paths
- var order = buildPathOrder(opts.order,
- this,
- function (series) {
- return buildChartSonifySeriesOptions(series,
- dataExtremes,
- opts);
- });
- // Add waits after simultaneous paths with series in them.
- order = addAfterSeriesWaits(order, opts.afterSeriesWait || 0);
- // We now have a list of either TimelinePath objects or series that need to
- // be converted to TimelinePath objects. Convert everything to paths.
- var paths = buildPathsFromOrder(order,
- opts.duration);
- // Sync simultaneous paths
- paths.forEach(function (simultaneousPaths) {
- syncSimultaneousPaths(simultaneousPaths);
- });
- // We have a set of paths. Create the timeline, and play it.
- this.sonification.timeline = new H.sonification.Timeline({
- paths: paths,
- onEnd: opts.onEnd
- });
- this.sonification.timeline.play();
- }
- /**
- * Get a list of the points currently under cursor.
- *
- * @requires module:modules/sonification
- *
- * @function Highcharts.Chart#getCurrentSonifyPoints
- *
- * @return {Array<Highcharts.Point>}
- * The points currently under the cursor.
- */
- function getCurrentPoints() {
- var cursorObj;
- if (this.sonification.timeline) {
- cursorObj = this.sonification.timeline.getCursor(); // Cursor per pathID
- return Object.keys(cursorObj).map(function (path) {
- // Get the event objects under cursor for each path
- return cursorObj[path].eventObject;
- }).filter(function (eventObj) {
- // Return the events that are points
- return eventObj instanceof Point;
- });
- }
- return [];
- }
- /**
- * Set the cursor to a point or set of points in different series.
- *
- * @requires module:modules/sonification
- *
- * @function Highcharts.Chart#setSonifyCursor
- *
- * @param {Highcharts.Point|Array<Highcharts.Point>} points
- * The point or points to set the cursor to. If setting multiple points
- * under the cursor, the points have to be in different series that are
- * being played simultaneously.
- */
- function setCursor(points) {
- var timeline = this.sonification.timeline;
- if (timeline) {
- splat(points).forEach(function (point) {
- // We created the events with the ID of the points, which makes
- // this easy. Just call setCursor for each ID.
- timeline.setCursor(point.id);
- });
- }
- }
- /**
- * Pause the running sonification.
- *
- * @requires module:modules/sonification
- *
- * @function Highcharts.Chart#pauseSonify
- *
- * @param {boolean} [fadeOut=true]
- * Fade out as we pause to avoid clicks.
- *
- * @return {void}
- */
- function pause(fadeOut) {
- if (this.sonification.timeline) {
- this.sonification.timeline.pause(pick(fadeOut, true));
- }
- else if (this.sonification.currentlyPlayingPoint) {
- this.sonification.currentlyPlayingPoint.cancelSonify(fadeOut);
- }
- }
- /**
- * Resume the currently running sonification. Requires series.sonify or
- * chart.sonify to have been played at some point earlier.
- *
- * @requires module:modules/sonification
- *
- * @function Highcharts.Chart#resumeSonify
- *
- * @param {Function} onEnd
- * Callback to call when play finished.
- *
- * @return {void}
- */
- function resume(onEnd) {
- if (this.sonification.timeline) {
- this.sonification.timeline.play(onEnd);
- }
- }
- /**
- * Play backwards from cursor. Requires series.sonify or chart.sonify to have
- * been played at some point earlier.
- *
- * @requires module:modules/sonification
- *
- * @function Highcharts.Chart#rewindSonify
- *
- * @param {Function} onEnd
- * Callback to call when play finished.
- *
- * @return {void}
- */
- function rewind(onEnd) {
- if (this.sonification.timeline) {
- this.sonification.timeline.rewind(onEnd);
- }
- }
- /**
- * Cancel current sonification and reset cursor.
- *
- * @requires module:modules/sonification
- *
- * @function Highcharts.Chart#cancelSonify
- *
- * @param {boolean} [fadeOut=true]
- * Fade out as we pause to avoid clicks.
- *
- * @return {void}
- */
- function cancel(fadeOut) {
- this.pauseSonify(fadeOut);
- this.resetSonifyCursor();
- }
- /**
- * Reset cursor to start. Requires series.sonify or chart.sonify to have been
- * played at some point earlier.
- *
- * @requires module:modules/sonification
- *
- * @function Highcharts.Chart#resetSonifyCursor
- *
- * @return {void}
- */
- function resetCursor() {
- if (this.sonification.timeline) {
- this.sonification.timeline.resetCursor();
- }
- }
- /**
- * Reset cursor to end. Requires series.sonify or chart.sonify to have been
- * played at some point earlier.
- *
- * @requires module:modules/sonification
- *
- * @function Highcharts.Chart#resetSonifyCursorEnd
- *
- * @return {void}
- */
- function resetCursorEnd() {
- if (this.sonification.timeline) {
- this.sonification.timeline.resetCursorEnd();
- }
- }
- // Export functions
- var chartSonifyFunctions = {
- chartSonify: chartSonify,
- seriesSonify: seriesSonify,
- pause: pause,
- resume: resume,
- rewind: rewind,
- cancel: cancel,
- getCurrentPoints: getCurrentPoints,
- setCursor: setCursor,
- resetCursor: resetCursor,
- resetCursorEnd: resetCursorEnd
- };
- return chartSonifyFunctions;
- });
- _registerModule(_modules, 'Extensions/Sonification/Timeline.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js'], _modules['Extensions/Sonification/Utilities.js']], function (H, U, utilities) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * TimelineEvent class definition.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var merge = U.merge,
- splat = U.splat,
- uniqueKey = U.uniqueKey;
- /**
- * A set of options for the TimelineEvent class.
- *
- * @requires module:modules/sonification
- *
- * @private
- * @interface Highcharts.TimelineEventOptionsObject
- */ /**
- * The object we want to sonify when playing the TimelineEvent. Can be any
- * object that implements the `sonify` and `cancelSonify` functions. If this is
- * not supplied, the TimelineEvent is considered a silent event, and the onEnd
- * event is immediately called.
- * @name Highcharts.TimelineEventOptionsObject#eventObject
- * @type {*}
- */ /**
- * Options to pass on to the eventObject when playing it.
- * @name Highcharts.TimelineEventOptionsObject#playOptions
- * @type {object|undefined}
- */ /**
- * The time at which we want this event to play (in milliseconds offset). This
- * is not used for the TimelineEvent.play function, but rather intended as a
- * property to decide when to call TimelineEvent.play. Defaults to 0.
- * @name Highcharts.TimelineEventOptionsObject#time
- * @type {number|undefined}
- */ /**
- * Unique ID for the event. Generated automatically if not supplied.
- * @name Highcharts.TimelineEventOptionsObject#id
- * @type {string|undefined}
- */ /**
- * Callback called when the play has finished.
- * @name Highcharts.TimelineEventOptionsObject#onEnd
- * @type {Function|undefined}
- */
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * The TimelineEvent class. Represents a sound event on a timeline.
- *
- * @requires module:modules/sonification
- *
- * @private
- * @class
- * @name Highcharts.TimelineEvent
- *
- * @param {Highcharts.TimelineEventOptionsObject} options
- * Options for the TimelineEvent.
- */
- function TimelineEvent(options) {
- this.init(options || {});
- }
- TimelineEvent.prototype.init = function (options) {
- this.options = options;
- this.time = options.time || 0;
- this.id = this.options.id = options.id || uniqueKey();
- };
- /**
- * Play the event. Does not take the TimelineEvent.time option into account,
- * and plays the event immediately.
- *
- * @function Highcharts.TimelineEvent#play
- *
- * @param {Highcharts.TimelineEventOptionsObject} [options]
- * Options to pass in to the eventObject when playing it.
- *
- * @return {void}
- */
- TimelineEvent.prototype.play = function (options) {
- var eventObject = this.options.eventObject,
- masterOnEnd = this.options.onEnd,
- playOnEnd = options && options.onEnd,
- playOptionsOnEnd = this.options.playOptions &&
- this.options.playOptions.onEnd,
- playOptions = merge(this.options.playOptions,
- options);
- if (eventObject && eventObject.sonify) {
- // If we have multiple onEnds defined, use all
- playOptions.onEnd = masterOnEnd || playOnEnd || playOptionsOnEnd ?
- function () {
- var args = arguments;
- [masterOnEnd, playOnEnd, playOptionsOnEnd].forEach(function (onEnd) {
- if (onEnd) {
- onEnd.apply(this, args);
- }
- });
- } : void 0;
- eventObject.sonify(playOptions);
- }
- else {
- if (playOnEnd) {
- playOnEnd();
- }
- if (masterOnEnd) {
- masterOnEnd();
- }
- }
- };
- /**
- * Cancel the sonification of this event. Does nothing if the event is not
- * currently sonifying.
- *
- * @function Highcharts.TimelineEvent#cancel
- *
- * @param {boolean} [fadeOut=false]
- * Whether or not to fade out as we stop. If false, the event is
- * cancelled synchronously.
- */
- TimelineEvent.prototype.cancel = function (fadeOut) {
- this.options.eventObject.cancelSonify(fadeOut);
- };
- /**
- * A set of options for the TimelinePath class.
- *
- * @requires module:modules/
- *
- * @private
- * @interface Highcharts.TimelinePathOptionsObject
- */ /**
- * List of TimelineEvents to play on this track.
- * @name Highcharts.TimelinePathOptionsObject#events
- * @type {Array<Highcharts.TimelineEvent>}
- */ /**
- * If this option is supplied, this path ignores all events and just waits for
- * the specified number of milliseconds before calling onEnd.
- * @name Highcharts.TimelinePathOptionsObject#silentWait
- * @type {number|undefined}
- */ /**
- * Unique ID for this timeline path. Automatically generated if not supplied.
- * @name Highcharts.TimelinePathOptionsObject#id
- * @type {string|undefined}
- */ /**
- * Callback called before the path starts playing.
- * @name Highcharts.TimelinePathOptionsObject#onStart
- * @type {Function|undefined}
- */ /**
- * Callback function to call before an event plays.
- * @name Highcharts.TimelinePathOptionsObject#onEventStart
- * @type {Function|undefined}
- */ /**
- * Callback function to call after an event has stopped playing.
- * @name Highcharts.TimelinePathOptionsObject#onEventEnd
- * @type {Function|undefined}
- */ /**
- * Callback called when the whole path is finished.
- * @name Highcharts.TimelinePathOptionsObject#onEnd
- * @type {Function|undefined}
- */
- /**
- * The TimelinePath class. Represents a track on a timeline with a list of
- * sound events to play at certain times relative to each other.
- *
- * @requires module:modules/sonification
- *
- * @private
- * @class
- * @name Highcharts.TimelinePath
- *
- * @param {Highcharts.TimelinePathOptionsObject} options
- * Options for the TimelinePath.
- */
- function TimelinePath(options) {
- this.init(options);
- }
- TimelinePath.prototype.init = function (options) {
- this.options = options;
- this.id = this.options.id = options.id || uniqueKey();
- this.cursor = 0;
- this.eventsPlaying = {};
- // Handle silent wait, otherwise use events from options
- this.events = options.silentWait ?
- [
- new TimelineEvent({ time: 0 }),
- new TimelineEvent({ time: options.silentWait })
- ] :
- this.options.events;
- // Reference optionally provided by the user that indicates the intended
- // duration of the path. Unused by TimelinePath itself.
- this.targetDuration = options.targetDuration || options.silentWait;
- // We need to sort our events by time
- this.sortEvents();
- // Get map from event ID to index
- this.updateEventIdMap();
- // Signal events to fire
- this.signalHandler = new utilities.SignalHandler(['playOnEnd', 'masterOnEnd', 'onStart', 'onEventStart', 'onEventEnd']);
- this.signalHandler.registerSignalCallbacks(merge(options, { masterOnEnd: options.onEnd }));
- };
- /**
- * Sort the internal event list by time.
- * @private
- */
- TimelinePath.prototype.sortEvents = function () {
- this.events = this.events.sort(function (a, b) {
- return a.time - b.time;
- });
- };
- /**
- * Update the internal eventId to index map.
- * @private
- */
- TimelinePath.prototype.updateEventIdMap = function () {
- this.eventIdMap = this.events.reduce(function (acc, cur, i) {
- acc[cur.id] = i;
- return acc;
- }, {});
- };
- /**
- * Add events to the path. Should not be done while the path is playing.
- * The new events are inserted according to their time property.
- * @private
- * @param {Array<Highcharts.TimelineEvent>} newEvents - The new timeline events
- * to add.
- */
- TimelinePath.prototype.addTimelineEvents = function (newEvents) {
- this.events = this.events.concat(newEvents);
- this.sortEvents(); // Sort events by time
- this.updateEventIdMap(); // Update the event ID to index map
- };
- /**
- * Get the current TimelineEvent under the cursor.
- * @private
- * @return {Highcharts.TimelineEvent} The current timeline event.
- */
- TimelinePath.prototype.getCursor = function () {
- return this.events[this.cursor];
- };
- /**
- * Set the current TimelineEvent under the cursor.
- * @private
- * @param {string} eventId
- * The ID of the timeline event to set as current.
- * @return {boolean}
- * True if there is an event with this ID in the path. False otherwise.
- */
- TimelinePath.prototype.setCursor = function (eventId) {
- var ix = this.eventIdMap[eventId];
- if (typeof ix !== 'undefined') {
- this.cursor = ix;
- return true;
- }
- return false;
- };
- /**
- * Play the timeline from the current cursor.
- * @private
- * @param {Function} onEnd
- * Callback to call when play finished. Does not override other onEnd callbacks.
- * @return {void}
- */
- TimelinePath.prototype.play = function (onEnd) {
- this.pause();
- this.signalHandler.emitSignal('onStart');
- this.signalHandler.clearSignalCallbacks(['playOnEnd']);
- this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });
- this.playEvents(1);
- };
- /**
- * Play the timeline backwards from the current cursor.
- * @private
- * @param {Function} onEnd
- * Callback to call when play finished. Does not override other onEnd callbacks.
- * @return {void}
- */
- TimelinePath.prototype.rewind = function (onEnd) {
- this.pause();
- this.signalHandler.emitSignal('onStart');
- this.signalHandler.clearSignalCallbacks(['playOnEnd']);
- this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });
- this.playEvents(-1);
- };
- /**
- * Reset the cursor to the beginning.
- * @private
- */
- TimelinePath.prototype.resetCursor = function () {
- this.cursor = 0;
- };
- /**
- * Reset the cursor to the end.
- * @private
- */
- TimelinePath.prototype.resetCursorEnd = function () {
- this.cursor = this.events.length - 1;
- };
- /**
- * Cancel current playing. Leaves the cursor intact.
- * @private
- * @param {boolean} [fadeOut=false] - Whether or not to fade out as we stop. If
- * false, the path is cancelled synchronously.
- */
- TimelinePath.prototype.pause = function (fadeOut) {
- var timelinePath = this;
- // Cancel next scheduled play
- clearTimeout(timelinePath.nextScheduledPlay);
- // Cancel currently playing events
- Object.keys(timelinePath.eventsPlaying).forEach(function (id) {
- if (timelinePath.eventsPlaying[id]) {
- timelinePath.eventsPlaying[id].cancel(fadeOut);
- }
- });
- timelinePath.eventsPlaying = {};
- };
- /**
- * Play the events, starting from current cursor, and going in specified
- * direction.
- * @private
- * @param {number} direction
- * The direction to play, 1 for forwards and -1 for backwards.
- * @return {void}
- */
- TimelinePath.prototype.playEvents = function (direction) {
- var timelinePath = this,
- curEvent = timelinePath.events[this.cursor],
- nextEvent = timelinePath.events[this.cursor + direction],
- timeDiff,
- onEnd = function (signalData) {
- timelinePath.signalHandler.emitSignal('masterOnEnd',
- signalData);
- timelinePath.signalHandler.emitSignal('playOnEnd', signalData);
- };
- // Store reference to path on event
- curEvent.timelinePath = timelinePath;
- // Emit event, cancel if returns false
- if (timelinePath.signalHandler.emitSignal('onEventStart', curEvent) === false) {
- onEnd({
- event: curEvent,
- cancelled: true
- });
- return;
- }
- // Play the current event
- timelinePath.eventsPlaying[curEvent.id] = curEvent;
- curEvent.play({
- onEnd: function (cancelled) {
- var signalData = {
- event: curEvent,
- cancelled: !!cancelled
- };
- // Keep track of currently playing events for cancelling
- delete timelinePath.eventsPlaying[curEvent.id];
- // Handle onEventEnd
- timelinePath.signalHandler.emitSignal('onEventEnd', signalData);
- // Reached end of path?
- if (!nextEvent) {
- onEnd(signalData);
- }
- }
- });
- // Schedule next
- if (nextEvent) {
- timeDiff = Math.abs(nextEvent.time - curEvent.time);
- if (timeDiff < 1) {
- // Play immediately
- timelinePath.cursor += direction;
- timelinePath.playEvents(direction);
- }
- else {
- // Schedule after the difference in ms
- this.nextScheduledPlay = setTimeout(function () {
- timelinePath.cursor += direction;
- timelinePath.playEvents(direction);
- }, timeDiff);
- }
- }
- };
- /* ************************************************************************** *
- * TIMELINE *
- * ************************************************************************** */
- /**
- * A set of options for the Timeline class.
- *
- * @requires module:modules/sonification
- *
- * @private
- * @interface Highcharts.TimelineOptionsObject
- */ /**
- * List of TimelinePaths to play. Multiple paths can be grouped together and
- * played simultaneously by supplying an array of paths in place of a single
- * path.
- * @name Highcharts.TimelineOptionsObject#paths
- * @type {Array<(Highcharts.TimelinePath|Array<Highcharts.TimelinePath>)>}
- */ /**
- * Callback function to call before a path plays.
- * @name Highcharts.TimelineOptionsObject#onPathStart
- * @type {Function|undefined}
- */ /**
- * Callback function to call after a path has stopped playing.
- * @name Highcharts.TimelineOptionsObject#onPathEnd
- * @type {Function|undefined}
- */ /**
- * Callback called when the whole path is finished.
- * @name Highcharts.TimelineOptionsObject#onEnd
- * @type {Function|undefined}
- */
- /**
- * The Timeline class. Represents a sonification timeline with a list of
- * timeline paths with events to play at certain times relative to each other.
- *
- * @requires module:modules/sonification
- *
- * @private
- * @class
- * @name Highcharts.Timeline
- *
- * @param {Highcharts.TimelineOptionsObject} options
- * Options for the Timeline.
- */
- function Timeline(options) {
- this.init(options || {});
- }
- Timeline.prototype.init = function (options) {
- this.options = options;
- this.cursor = 0;
- this.paths = options.paths || [];
- this.pathsPlaying = {};
- this.signalHandler = new utilities.SignalHandler(['playOnEnd', 'masterOnEnd', 'onPathStart', 'onPathEnd']);
- this.signalHandler.registerSignalCallbacks(merge(options, { masterOnEnd: options.onEnd }));
- };
- /**
- * Play the timeline forwards from cursor.
- * @private
- * @param {Function} [onEnd]
- * Callback to call when play finished. Does not override other onEnd callbacks.
- * @return {void}
- */
- Timeline.prototype.play = function (onEnd) {
- this.pause();
- this.signalHandler.clearSignalCallbacks(['playOnEnd']);
- this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });
- this.playPaths(1);
- };
- /**
- * Play the timeline backwards from cursor.
- * @private
- * @param {Function} onEnd
- * Callback to call when play finished. Does not override other onEnd callbacks.
- * @return {void}
- */
- Timeline.prototype.rewind = function (onEnd) {
- this.pause();
- this.signalHandler.clearSignalCallbacks(['playOnEnd']);
- this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });
- this.playPaths(-1);
- };
- /**
- * Play the timeline in the specified direction.
- * @private
- * @param {number} direction
- * Direction to play in. 1 for forwards, -1 for backwards.
- * @return {void}
- */
- Timeline.prototype.playPaths = function (direction) {
- var timeline = this;
- var signalHandler = timeline.signalHandler;
- if (!timeline.paths.length) {
- var emptySignal = {
- cancelled: false
- };
- signalHandler.emitSignal('playOnEnd', emptySignal);
- signalHandler.emitSignal('masterOnEnd', emptySignal);
- return;
- }
- var curPaths = splat(this.paths[this.cursor]),
- nextPaths = this.paths[this.cursor + direction],
- pathsEnded = 0,
- // Play a path
- playPath = function (path) {
- // Emit signal and set playing state
- signalHandler.emitSignal('onPathStart',
- path);
- timeline.pathsPlaying[path.id] = path;
- // Do the play
- path[direction > 0 ? 'play' : 'rewind'](function (callbackData) {
- // Play ended callback
- // Data to pass to signal callbacks
- var cancelled = callbackData && callbackData.cancelled,
- signalData = {
- path: path,
- cancelled: cancelled
- };
- // Clear state and send signal
- delete timeline.pathsPlaying[path.id];
- signalHandler.emitSignal('onPathEnd', signalData);
- // Handle next paths
- pathsEnded++;
- if (pathsEnded >= curPaths.length) {
- // We finished all of the current paths for cursor.
- if (nextPaths && !cancelled) {
- // We have more paths, move cursor along
- timeline.cursor += direction;
- // Reset upcoming path cursors before playing
- splat(nextPaths).forEach(function (nextPath) {
- nextPath[direction > 0 ? 'resetCursor' : 'resetCursorEnd']();
- });
- // Play next
- timeline.playPaths(direction);
- }
- else {
- // If it is the last path in this direction, call onEnd
- signalHandler.emitSignal('playOnEnd', signalData);
- signalHandler.emitSignal('masterOnEnd', signalData);
- }
- }
- });
- };
- // Go through the paths under cursor and play them
- curPaths.forEach(function (path) {
- if (path) {
- // Store reference to timeline
- path.timeline = timeline;
- // Leave a timeout to let notes fade out before next play
- setTimeout(function () {
- playPath(path);
- }, H.sonification.fadeOutDuration);
- }
- });
- };
- /**
- * Stop the playing of the timeline. Cancels all current sounds, but does not
- * affect the cursor.
- * @private
- * @param {boolean} [fadeOut=false]
- * Whether or not to fade out as we stop. If false, the timeline is cancelled
- * synchronously.
- * @return {void}
- */
- Timeline.prototype.pause = function (fadeOut) {
- var timeline = this;
- // Cancel currently playing events
- Object.keys(timeline.pathsPlaying).forEach(function (id) {
- if (timeline.pathsPlaying[id]) {
- timeline.pathsPlaying[id].pause(fadeOut);
- }
- });
- timeline.pathsPlaying = {};
- };
- /**
- * Reset the cursor to the beginning of the timeline.
- * @private
- * @return {void}
- */
- Timeline.prototype.resetCursor = function () {
- this.paths.forEach(function (paths) {
- splat(paths).forEach(function (path) {
- path.resetCursor();
- });
- });
- this.cursor = 0;
- };
- /**
- * Reset the cursor to the end of the timeline.
- * @private
- * @return {void}
- */
- Timeline.prototype.resetCursorEnd = function () {
- this.paths.forEach(function (paths) {
- splat(paths).forEach(function (path) {
- path.resetCursorEnd();
- });
- });
- this.cursor = this.paths.length - 1;
- };
- /**
- * Set the current TimelineEvent under the cursor. If multiple paths are being
- * played at the same time, this function only affects a single path (the one
- * that contains the eventId that is passed in).
- * @private
- * @param {string} eventId
- * The ID of the timeline event to set as current.
- * @return {boolean}
- * True if the cursor was set, false if no TimelineEvent was found for this ID.
- */
- Timeline.prototype.setCursor = function (eventId) {
- return this.paths.some(function (paths) {
- return splat(paths).some(function (path) {
- return path.setCursor(eventId);
- });
- });
- };
- /**
- * Get the current TimelineEvents under the cursors. This function will return
- * the event under the cursor for each currently playing path, as an object
- * where the path ID is mapped to the TimelineEvent under that path's cursor.
- * @private
- * @return {Highcharts.Dictionary<Highcharts.TimelineEvent>}
- * The TimelineEvents under each path's cursors.
- */
- Timeline.prototype.getCursor = function () {
- return this.getCurrentPlayingPaths().reduce(function (acc, cur) {
- acc[cur.id] = cur.getCursor();
- return acc;
- }, {});
- };
- /**
- * Check if timeline is reset or at start.
- * @private
- * @return {boolean}
- * True if timeline is at the beginning.
- */
- Timeline.prototype.atStart = function () {
- if (this.cursor) {
- return false;
- }
- return !splat(this.paths[0]).some(function (path) {
- return path.cursor;
- });
- };
- /**
- * Get the current TimelinePaths being played.
- * @private
- * @return {Array<Highcharts.TimelinePath>}
- * The TimelinePaths currently being played.
- */
- Timeline.prototype.getCurrentPlayingPaths = function () {
- if (!this.paths.length) {
- return [];
- }
- return splat(this.paths[this.cursor]);
- };
- // Export the classes
- var timelineClasses = {
- TimelineEvent: TimelineEvent,
- TimelinePath: TimelinePath,
- Timeline: Timeline
- };
- return timelineClasses;
- });
- _registerModule(_modules, 'Extensions/Sonification/Options.js', [], function () {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Default options for sonification.
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- // Experimental, disabled by default, not exposed in API
- var options = {
- sonification: {
- enabled: false,
- duration: 2500,
- afterSeriesWait: 700,
- masterVolume: 1,
- order: 'sequential',
- defaultInstrumentOptions: {
- instrument: 'sineMusical',
- // Start at G4 note, end at C6
- minFrequency: 392,
- maxFrequency: 1046,
- mapping: {
- pointPlayTime: 'x',
- duration: 200,
- frequency: 'y'
- }
- }
- }
- };
- return options;
- });
- _registerModule(_modules, 'Extensions/Sonification/Sonification.js', [_modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Series/Point.js'], _modules['Core/Series/Series.js'], _modules['Core/Utilities.js'], _modules['Extensions/Sonification/Instrument.js'], _modules['Extensions/Sonification/InstrumentDefinitions.js'], _modules['Extensions/Sonification/Earcon.js'], _modules['Extensions/Sonification/PointSonify.js'], _modules['Extensions/Sonification/ChartSonify.js'], _modules['Extensions/Sonification/Utilities.js'], _modules['Extensions/Sonification/Timeline.js'], _modules['Extensions/Sonification/Options.js']], function (Chart, H, D, Point, Series, U, Instrument, instruments, Earcon, pointSonifyFunctions, chartSonifyFunctions, utilities, TimelineClasses, sonificationOptions) {
- /* *
- *
- * (c) 2009-2021 Øystein Moseng
- *
- * Sonification module for Highcharts
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- var defaultOptions = D.defaultOptions;
- var addEvent = U.addEvent,
- extend = U.extend,
- merge = U.merge;
- // Expose on the Highcharts object
- /**
- * Global classes and objects related to sonification.
- *
- * @requires module:modules/sonification
- *
- * @name Highcharts.sonification
- * @type {Highcharts.SonificationObject}
- */
- /**
- * Global classes and objects related to sonification.
- *
- * @requires module:modules/sonification
- *
- * @interface Highcharts.SonificationObject
- */ /**
- * Note fade-out-time in milliseconds. Most notes are faded out quickly by
- * default if there is time. This is to avoid abrupt stops which will cause
- * perceived clicks.
- * @name Highcharts.SonificationObject#fadeOutDuration
- * @type {number}
- */ /**
- * Utility functions.
- * @name Highcharts.SonificationObject#utilities
- * @private
- * @type {object}
- */ /**
- * The Instrument class.
- * @name Highcharts.SonificationObject#Instrument
- * @type {Function}
- */ /**
- * Predefined instruments, given as an object with a map between the instrument
- * name and the Highcharts.Instrument object.
- * @name Highcharts.SonificationObject#instruments
- * @type {Object}
- */ /**
- * The Earcon class.
- * @name Highcharts.SonificationObject#Earcon
- * @type {Function}
- */ /**
- * The TimelineEvent class.
- * @private
- * @name Highcharts.SonificationObject#TimelineEvent
- * @type {Function}
- */ /**
- * The TimelinePath class.
- * @private
- * @name Highcharts.SonificationObject#TimelinePath
- * @type {Function}
- */ /**
- * The Timeline class.
- * @private
- * @name Highcharts.SonificationObject#Timeline
- * @type {Function}
- */
- H.sonification = {
- fadeOutDuration: 20,
- // Classes and functions
- utilities: utilities,
- Instrument: Instrument,
- instruments: instruments,
- Earcon: Earcon,
- TimelineEvent: TimelineClasses.TimelineEvent,
- TimelinePath: TimelineClasses.TimelinePath,
- Timeline: TimelineClasses.Timeline
- };
- // Add default options
- merge(true, defaultOptions, sonificationOptions);
- // Chart specific
- Point.prototype.sonify = pointSonifyFunctions.pointSonify;
- Point.prototype.cancelSonify = pointSonifyFunctions.pointCancelSonify;
- Series.prototype.sonify = chartSonifyFunctions.seriesSonify;
- extend(Chart.prototype, {
- sonify: chartSonifyFunctions.chartSonify,
- pauseSonify: chartSonifyFunctions.pause,
- resumeSonify: chartSonifyFunctions.resume,
- rewindSonify: chartSonifyFunctions.rewind,
- cancelSonify: chartSonifyFunctions.cancel,
- getCurrentSonifyPoints: chartSonifyFunctions.getCurrentPoints,
- setSonifyCursor: chartSonifyFunctions.setCursor,
- resetSonifyCursor: chartSonifyFunctions.resetCursor,
- resetSonifyCursorEnd: chartSonifyFunctions.resetCursorEnd
- });
- /* eslint-disable no-invalid-this */
- // Prepare charts for sonification on init
- addEvent(Chart, 'init', function () {
- this.sonification = {};
- });
- // Update with chart/series/point updates
- addEvent(Chart, 'update', function (e) {
- var newOptions = e.options.sonification;
- if (newOptions) {
- merge(true, this.options.sonification, newOptions);
- }
- });
- });
- _registerModule(_modules, 'masters/modules/sonification.src.js', [], function () {
- });
- }));
|