123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026 |
- /* *
- *
- * (c) 2009-2019 Øystein Moseng
- *
- * Sonification functions for chart/series.
- *
- * License: www.highcharts.com/license
- *
- * */
- /**
- * 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 {object|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}
- */
- 'use strict';
- import H from '../../parts/Globals.js';
- import utilities from 'utilities.js';
- /**
- * 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) :
- H.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 {object} 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.
- * @private
- * @param {Highcharts.Chart} chart - The chart to calculate extremes from.
- * @param {Array<Highcharts.PointInstrumentObject>} instruments - The instrument
- * definitions used.
- * @param {object} [dataExtremes] - Predefined extremes for each data prop.
- * @return {object} New extremes with data properties mapped to min/max objects.
- */
- function getExtremesForInstrumentProps(chart, instruments, dataExtremes) {
- return (
- instruments || []
- ).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;
- }, H.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 H.merge(instrumentDef, { instrument: copy });
- });
- }
- /**
- * 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 {object} 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, options.dataExtremes
- ),
- // Get time offset for a point, relative to duration
- pointToTime = function (point) {
- return utilities.virtualAxisTranslate(
- getPointTimeValue(point, options.pointPlayTime),
- timeExtremes,
- { min: 0, max: options.duration }
- );
- },
- // Compute any data extremes that aren't defined yet
- dataExtremes = getExtremesForInstrumentProps(
- series.chart, options.instruments, options.dataExtremes
- ),
- // Make copies of the instruments used for this series, to allow
- // multiple series with the same instrument to play together
- instruments = makeInstrumentCopies(options.instruments),
- // 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
- }
- }),
- // Earcons
- earcons.map(function (earcon) {
- return new H.sonification.TimelineEvent({
- eventObject: earcon,
- time: time
- });
- })
- );
- }, []);
- // 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 H.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 H.Point && options.onPointEnd) {
- options.onPointEnd(eventData.event, eventObject);
- }
- },
- onEnd: function () {
- if (options.onEnd) {
- options.onEnd(series);
- }
- }
- });
- }
- /**
- * 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.
- */
- function seriesSonify(options) {
- var timelinePath = buildTimelinePathFromSeries(this, options),
- 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();
- }
- // 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 {object} dataExtremes - Pre-calculated data extremes for the chart.
- * @param {object} chartSonifyOptions - Options passed in to chart.sonify.
- * @return {object} Options for buildTimelinePathFromSeries.
- */
- function buildSeriesOptions(series, dataExtremes, chartSonifyOptions) {
- var seriesOptions = chartSonifyOptions.seriesOptions || {};
- return H.merge(
- {
- // 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, chartSonifyOptions.pointPlayTime
- ),
- // Some options we just pass on
- instruments: chartSonifyOptions.instruments,
- onStart: chartSonifyOptions.onSeriesStart,
- onEnd: chartSonifyOptions.onSeriesEnd,
- earcons: chartSonifyOptions.earcons
- },
- // Merge in the specific series options by ID
- H.isArray(seriesOptions) ? (
- H.find(seriesOptions, function (optEntry) {
- return optEntry.id === H.pick(series.id, series.options.id);
- }) || {}
- ) : seriesOptions,
- {
- // Forced options
- pointPlayTime: chartSonifyOptions.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) {
- 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 = H.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 = H.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 = H.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 + H.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 = H.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;
- }, []);
- }
- /**
- * Options for sonifying a chart.
- *
- * @requires module:modules/sonification
- *
- * @interface Highcharts.SonifyChartOptionsObject
- *//**
- * 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.SonifyChartOptionsObject#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.SonifyChartOptionsObject#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.SonifyChartOptionsObject#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.SonifyChartOptionsObject#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.SonifyChartOptionsObject#seriesOptions
- * @type {Object|Array<object>|undefined}
- *//**
- * The instrument definitions for the points in this chart.
- * @name Highcharts.SonifyChartOptionsObject#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.SonifyChartOptionsObject#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.SonifyChartOptionsObject#dataExtremes
- * @type {object|undefined}
- *//**
- * Callback before a series is played.
- * @name Highcharts.SonifyChartOptionsObject#onSeriesStart
- * @type {Function|undefined}
- *//**
- * Callback after a series has finished playing.
- * @name Highcharts.SonifyChartOptionsObject#onSeriesEnd
- * @type {Function|undefined}
- *//**
- * Callback after the chart has played.
- * @name Highcharts.SonifyChartOptionsObject#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.SonifyChartOptionsObject} options
- * The options for sonifying this chart.
- */
- function chartSonify(options) {
- // Only one timeline can play at a time.
- if (this.sonification.timeline) {
- this.sonification.timeline.pause();
- }
- // Calculate data extremes for the props used
- var dataExtremes = getExtremesForInstrumentProps(
- this, options.instruments, options.dataExtremes
- );
- // Figure out ordering of series and custom paths
- var order = buildPathOrder(options.order, this, function (series) {
- return buildSeriesOptions(series, dataExtremes, options);
- });
- // Add waits after simultaneous paths with series in them.
- order = addAfterSeriesWaits(order, options.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, options.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: options.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 H.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) {
- H.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.
- */
- function pause(fadeOut) {
- if (this.sonification.timeline) {
- this.sonification.timeline.pause(H.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.
- */
- 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.
- */
- 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.
- */
- 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
- */
- 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
- */
- 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
- };
- export default chartSonifyFunctions;
|