| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777 |
- /* *
- *
- * (c) 2010-2020 Torstein Honsi
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- 'use strict';
- import Axis from './Axis.js';
- import H from '../Globals.js';
- import U from '../Utilities.js';
- var addEvent = U.addEvent, css = U.css, defined = U.defined, pick = U.pick, timeUnits = U.timeUnits;
- import '../Chart/Chart.js';
- // Has a dependency on Navigator due to the use of Axis.toFixedRange
- import '../Navigator.js';
- import '../Series/Series.js';
- var Chart = H.Chart, Series = H.Series;
- /**
- * Extends the axis with ordinal support.
- * @private
- */
- var OrdinalAxis;
- (function (OrdinalAxis) {
- /* *
- *
- * Classes
- *
- * */
- /**
- * @private
- */
- var Composition = /** @class */ (function () {
- /* *
- *
- * Constructors
- *
- * */
- /**
- * @private
- */
- function Composition(axis) {
- this.index = {};
- this.axis = axis;
- }
- /* *
- *
- * Functions
- *
- * */
- /**
- * Calculate the ordinal positions before tick positions are calculated.
- *
- * @private
- */
- Composition.prototype.beforeSetTickPositions = function () {
- var axis = this.axis, ordinal = axis.ordinal, len, ordinalPositions = [], uniqueOrdinalPositions, useOrdinal = false, dist, extremes = axis.getExtremes(), min = extremes.min, max = extremes.max, minIndex, maxIndex, slope, hasBreaks = axis.isXAxis && !!axis.options.breaks, isOrdinal = axis.options.ordinal, overscrollPointsRange = Number.MAX_VALUE, ignoreHiddenSeries = axis.chart.options.chart.ignoreHiddenSeries, i, hasBoostedSeries;
- // Apply the ordinal logic
- if (isOrdinal || hasBreaks) { // #4167 YAxis is never ordinal ?
- axis.series.forEach(function (series, i) {
- uniqueOrdinalPositions = [];
- if ((!ignoreHiddenSeries || series.visible !== false) &&
- (series.takeOrdinalPosition !== false || hasBreaks)) {
- // concatenate the processed X data into the existing
- // positions, or the empty array
- ordinalPositions = ordinalPositions.concat(series.processedXData);
- len = ordinalPositions.length;
- // remove duplicates (#1588)
- ordinalPositions.sort(function (a, b) {
- // without a custom function it is sorted as strings
- return a - b;
- });
- overscrollPointsRange = Math.min(overscrollPointsRange, pick(
- // Check for a single-point series:
- series.closestPointRange, overscrollPointsRange));
- if (len) {
- i = 0;
- while (i < len - 1) {
- if (ordinalPositions[i] !== ordinalPositions[i + 1]) {
- uniqueOrdinalPositions.push(ordinalPositions[i + 1]);
- }
- i++;
- }
- // Check first item:
- if (uniqueOrdinalPositions[0] !== ordinalPositions[0]) {
- uniqueOrdinalPositions.unshift(ordinalPositions[0]);
- }
- ordinalPositions = uniqueOrdinalPositions;
- }
- }
- if (series.isSeriesBoosting) {
- hasBoostedSeries = true;
- }
- });
- if (hasBoostedSeries) {
- ordinalPositions.length = 0;
- }
- // cache the length
- len = ordinalPositions.length;
- // Check if we really need the overhead of mapping axis data
- // against the ordinal positions. If the series consist of
- // evenly spaced data any way, we don't need any ordinal logic.
- if (len > 2) { // two points have equal distance by default
- dist = ordinalPositions[1] - ordinalPositions[0];
- i = len - 1;
- while (i-- && !useOrdinal) {
- if (ordinalPositions[i + 1] - ordinalPositions[i] !== dist) {
- useOrdinal = true;
- }
- }
- // When zooming in on a week, prevent axis padding for
- // weekends even though the data within the week is evenly
- // spaced.
- if (!axis.options.keepOrdinalPadding &&
- (ordinalPositions[0] - min > dist ||
- max - ordinalPositions[ordinalPositions.length - 1] >
- dist)) {
- useOrdinal = true;
- }
- }
- else if (axis.options.overscroll) {
- if (len === 2) {
- // Exactly two points, distance for overscroll is fixed:
- overscrollPointsRange =
- ordinalPositions[1] - ordinalPositions[0];
- }
- else if (len === 1) {
- // We have just one point, closest distance is unknown.
- // Assume then it is last point and overscrolled range:
- overscrollPointsRange = axis.options.overscroll;
- ordinalPositions = [
- ordinalPositions[0],
- ordinalPositions[0] + overscrollPointsRange
- ];
- }
- else {
- // In case of zooming in on overscrolled range, stick to
- // the old range:
- overscrollPointsRange = ordinal.overscrollPointsRange;
- }
- }
- // Record the slope and offset to compute the linear values from
- // the array index. Since the ordinal positions may exceed the
- // current range, get the start and end positions within it
- // (#719, #665b)
- if (useOrdinal) {
- if (axis.options.overscroll) {
- ordinal.overscrollPointsRange = overscrollPointsRange;
- ordinalPositions = ordinalPositions.concat(ordinal.getOverscrollPositions());
- }
- // Register
- ordinal.positions = ordinalPositions;
- // This relies on the ordinalPositions being set. Use
- // Math.max and Math.min to prevent padding on either sides
- // of the data.
- minIndex = axis.ordinal2lin(// #5979
- Math.max(min, ordinalPositions[0]), true);
- maxIndex = Math.max(axis.ordinal2lin(Math.min(max, ordinalPositions[ordinalPositions.length - 1]), true), 1); // #3339
- // Set the slope and offset of the values compared to the
- // indices in the ordinal positions
- ordinal.slope = slope = (max - min) / (maxIndex - minIndex);
- ordinal.offset = min - (minIndex * slope);
- }
- else {
- ordinal.overscrollPointsRange = pick(axis.closestPointRange, ordinal.overscrollPointsRange);
- ordinal.positions = axis.ordinal.slope = ordinal.offset =
- void 0;
- }
- }
- axis.isOrdinal = isOrdinal && useOrdinal; // #3818, #4196, #4926
- ordinal.groupIntervalFactor = null; // reset for next run
- };
- /**
- * Get the ordinal positions for the entire data set. This is necessary
- * in chart panning because we need to find out what points or data
- * groups are available outside the visible range. When a panning
- * operation starts, if an index for the given grouping does not exists,
- * it is created and cached. This index is deleted on updated data, so
- * it will be regenerated the next time a panning operation starts.
- *
- * @private
- */
- Composition.prototype.getExtendedPositions = function () {
- var ordinal = this, axis = ordinal.axis, axisProto = axis.constructor.prototype, chart = axis.chart, grouping = axis.series[0].currentDataGrouping, ordinalIndex = ordinal.index, key = grouping ?
- grouping.count + grouping.unitName :
- 'raw', overscroll = axis.options.overscroll, extremes = axis.getExtremes(), fakeAxis, fakeSeries;
- // If this is the first time, or the ordinal index is deleted by
- // updatedData,
- // create it.
- if (!ordinalIndex) {
- ordinalIndex = ordinal.index = {};
- }
- if (!ordinalIndex[key]) {
- // Create a fake axis object where the extended ordinal
- // positions are emulated
- fakeAxis = {
- series: [],
- chart: chart,
- getExtremes: function () {
- return {
- min: extremes.dataMin,
- max: extremes.dataMax + overscroll
- };
- },
- options: {
- ordinal: true
- },
- ordinal: {},
- ordinal2lin: axisProto.ordinal2lin,
- val2lin: axisProto.val2lin // #2590
- };
- fakeAxis.ordinal.axis = fakeAxis;
- // Add the fake series to hold the full data, then apply
- // processData to it
- axis.series.forEach(function (series) {
- fakeSeries = {
- xAxis: fakeAxis,
- xData: series.xData.slice(),
- chart: chart,
- destroyGroupedData: H.noop,
- getProcessedData: H.Series.prototype.getProcessedData
- };
- fakeSeries.xData = fakeSeries.xData.concat(ordinal.getOverscrollPositions());
- fakeSeries.options = {
- dataGrouping: grouping ? {
- enabled: true,
- forced: true,
- // doesn't matter which, use the fastest
- approximation: 'open',
- units: [[
- grouping.unitName,
- [grouping.count]
- ]]
- } : {
- enabled: false
- }
- };
- series.processData.apply(fakeSeries);
- fakeAxis.series.push(fakeSeries);
- });
- // Run beforeSetTickPositions to compute the ordinalPositions
- axis.ordinal.beforeSetTickPositions.apply({ axis: fakeAxis });
- // Cache it
- ordinalIndex[key] = fakeAxis.ordinal.positions;
- }
- return ordinalIndex[key];
- };
- /**
- * Find the factor to estimate how wide the plot area would have been if
- * ordinal gaps were included. This value is used to compute an imagined
- * plot width in order to establish the data grouping interval.
- *
- * A real world case is the intraday-candlestick example. Without this
- * logic, it would show the correct data grouping when viewing a range
- * within each day, but once moving the range to include the gap between
- * two days, the interval would include the cut-away night hours and the
- * data grouping would be wrong. So the below method tries to compensate
- * by identifying the most common point interval, in this case days.
- *
- * An opposite case is presented in issue #718. We have a long array of
- * daily data, then one point is appended one hour after the last point.
- * We expect the data grouping not to change.
- *
- * In the future, if we find cases where this estimation doesn't work
- * optimally, we might need to add a second pass to the data grouping
- * logic, where we do another run with a greater interval if the number
- * of data groups is more than a certain fraction of the desired group
- * count.
- *
- * @private
- */
- Composition.prototype.getGroupIntervalFactor = function (xMin, xMax, series) {
- var ordinal = this, axis = ordinal.axis, i, processedXData = series.processedXData, len = processedXData.length, distances = [], median, groupIntervalFactor = ordinal.groupIntervalFactor;
- // Only do this computation for the first series, let the other
- // inherit it (#2416)
- if (!groupIntervalFactor) {
- // Register all the distances in an array
- for (i = 0; i < len - 1; i++) {
- distances[i] =
- processedXData[i + 1] - processedXData[i];
- }
- // Sort them and find the median
- distances.sort(function (a, b) {
- return a - b;
- });
- median = distances[Math.floor(len / 2)];
- // Compensate for series that don't extend through the entire
- // axis extent. #1675.
- xMin = Math.max(xMin, processedXData[0]);
- xMax = Math.min(xMax, processedXData[len - 1]);
- ordinal.groupIntervalFactor = groupIntervalFactor =
- (len * median) / (xMax - xMin);
- }
- // Return the factor needed for data grouping
- return groupIntervalFactor;
- };
- /**
- * Get ticks for an ordinal axis within a range where points don't
- * exist. It is required when overscroll is enabled. We can't base on
- * points, because we may not have any, so we use approximated
- * pointRange and generate these ticks between Axis.dataMax,
- * Axis.dataMax + Axis.overscroll evenly spaced. Used in panning and
- * navigator scrolling.
- *
- * @private
- */
- Composition.prototype.getOverscrollPositions = function () {
- var ordinal = this, axis = ordinal.axis, extraRange = axis.options.overscroll, distance = ordinal.overscrollPointsRange, positions = [], max = axis.dataMax;
- if (defined(distance)) {
- // Max + pointRange because we need to scroll to the last
- positions.push(max);
- while (max <= axis.dataMax + extraRange) {
- max += distance;
- positions.push(max);
- }
- }
- return positions;
- };
- /**
- * Make the tick intervals closer because the ordinal gaps make the
- * ticks spread out or cluster.
- *
- * @private
- */
- Composition.prototype.postProcessTickInterval = function (tickInterval) {
- // Problem: https://jsfiddle.net/highcharts/FQm4E/1/
- // This is a case where this algorithm doesn't work optimally. In
- // this case, the tick labels are spread out per week, but all the
- // gaps reside within weeks. So we have a situation where the labels
- // are courser than the ordinal gaps, and thus the tick interval
- // should not be altered.
- var ordinal = this, axis = ordinal.axis, ordinalSlope = ordinal.slope, ret;
- if (ordinalSlope) {
- if (!axis.options.breaks) {
- ret = tickInterval / (ordinalSlope / axis.closestPointRange);
- }
- else {
- ret = axis.closestPointRange || tickInterval; // #7275
- }
- }
- else {
- ret = tickInterval;
- }
- return ret;
- };
- return Composition;
- }());
- OrdinalAxis.Composition = Composition;
- /* *
- *
- * Functions
- *
- * */
- /**
- * Extends the axis with ordinal support.
- *
- * @private
- *
- * @param AxisClass
- * Axis class to extend.
- *
- * @param ChartClass
- * Chart class to use.
- *
- * @param SeriesClass
- * Series class to use.
- */
- function compose(AxisClass, ChartClass, SeriesClass) {
- AxisClass.keepProps.push('ordinal');
- var axisProto = AxisClass.prototype;
- /**
- * In an ordinal axis, there might be areas with dense consentrations of
- * points, then large gaps between some. Creating equally distributed
- * ticks over this entire range may lead to a huge number of ticks that
- * will later be removed. So instead, break the positions up in
- * segments, find the tick positions for each segment then concatenize
- * them. This method is used from both data grouping logic and X axis
- * tick position logic.
- *
- * @private
- */
- AxisClass.prototype.getTimeTicks = function (normalizedInterval, min, max, startOfWeek, positions, closestDistance, findHigherRanks) {
- if (positions === void 0) { positions = []; }
- if (closestDistance === void 0) { closestDistance = 0; }
- var start = 0, end, segmentPositions, higherRanks = {}, hasCrossedHigherRank, info, posLength, outsideMax, groupPositions = [], lastGroupPosition = -Number.MAX_VALUE, tickPixelIntervalOption = this.options.tickPixelInterval, time = this.chart.time,
- // Record all the start positions of a segment, to use when
- // deciding what's a gap in the data.
- segmentStarts = [];
- // The positions are not always defined, for example for ordinal
- // positions when data has regular interval (#1557, #2090)
- if ((!this.options.ordinal && !this.options.breaks) ||
- !positions ||
- positions.length < 3 ||
- typeof min === 'undefined') {
- return time.getTimeTicks.apply(time, arguments);
- }
- // Analyze the positions array to split it into segments on gaps
- // larger than 5 times the closest distance. The closest distance is
- // already found at this point, so we reuse that instead of
- // computing it again.
- posLength = positions.length;
- for (end = 0; end < posLength; end++) {
- outsideMax = end && positions[end - 1] > max;
- if (positions[end] < min) { // Set the last position before min
- start = end;
- }
- if (end === posLength - 1 ||
- positions[end + 1] - positions[end] > closestDistance * 5 ||
- outsideMax) {
- // For each segment, calculate the tick positions from the
- // getTimeTicks utility function. The interval will be the
- // same regardless of how long the segment is.
- if (positions[end] > lastGroupPosition) { // #1475
- segmentPositions = time.getTimeTicks(normalizedInterval, positions[start], positions[end], startOfWeek);
- // Prevent duplicate groups, for example for multiple
- // segments within one larger time frame (#1475)
- while (segmentPositions.length &&
- segmentPositions[0] <= lastGroupPosition) {
- segmentPositions.shift();
- }
- if (segmentPositions.length) {
- lastGroupPosition =
- segmentPositions[segmentPositions.length - 1];
- }
- segmentStarts.push(groupPositions.length);
- groupPositions = groupPositions.concat(segmentPositions);
- }
- // Set start of next segment
- start = end + 1;
- }
- if (outsideMax) {
- break;
- }
- }
- // Get the grouping info from the last of the segments. The info is
- // the same for all segments.
- info = segmentPositions.info;
- // Optionally identify ticks with higher rank, for example when the
- // ticks have crossed midnight.
- if (findHigherRanks && info.unitRange <= timeUnits.hour) {
- end = groupPositions.length - 1;
- // Compare points two by two
- for (start = 1; start < end; start++) {
- if (time.dateFormat('%d', groupPositions[start]) !==
- time.dateFormat('%d', groupPositions[start - 1])) {
- higherRanks[groupPositions[start]] = 'day';
- hasCrossedHigherRank = true;
- }
- }
- // If the complete array has crossed midnight, we want to mark
- // the first positions also as higher rank
- if (hasCrossedHigherRank) {
- higherRanks[groupPositions[0]] = 'day';
- }
- info.higherRanks = higherRanks;
- }
- // Save the info
- info.segmentStarts = segmentStarts;
- groupPositions.info = info;
- // Don't show ticks within a gap in the ordinal axis, where the
- // space between two points is greater than a portion of the tick
- // pixel interval
- if (findHigherRanks && defined(tickPixelIntervalOption)) {
- var length = groupPositions.length, i = length, itemToRemove, translated, translatedArr = [], lastTranslated, medianDistance, distance, distances = [];
- // Find median pixel distance in order to keep a reasonably even
- // distance between ticks (#748)
- while (i--) {
- translated = this.translate(groupPositions[i]);
- if (lastTranslated) {
- distances[i] = lastTranslated - translated;
- }
- translatedArr[i] = lastTranslated = translated;
- }
- distances.sort();
- medianDistance = distances[Math.floor(distances.length / 2)];
- if (medianDistance < tickPixelIntervalOption * 0.6) {
- medianDistance = null;
- }
- // Now loop over again and remove ticks where needed
- i = groupPositions[length - 1] > max ? length - 1 : length; // #817
- lastTranslated = void 0;
- while (i--) {
- translated = translatedArr[i];
- distance = Math.abs(lastTranslated - translated);
- // #4175 - when axis is reversed, the distance, is negative
- // but tickPixelIntervalOption positive, so we need to
- // compare the same values
- // Remove ticks that are closer than 0.6 times the pixel
- // interval from the one to the right, but not if it is
- // close to the median distance (#748).
- if (lastTranslated &&
- distance < tickPixelIntervalOption * 0.8 &&
- (medianDistance === null || distance < medianDistance * 0.8)) {
- // Is this a higher ranked position with a normal
- // position to the right?
- if (higherRanks[groupPositions[i]] &&
- !higherRanks[groupPositions[i + 1]]) {
- // Yes: remove the lower ranked neighbour to the
- // right
- itemToRemove = i + 1;
- lastTranslated = translated; // #709
- }
- else {
- // No: remove this one
- itemToRemove = i;
- }
- groupPositions.splice(itemToRemove, 1);
- }
- else {
- lastTranslated = translated;
- }
- }
- }
- return groupPositions;
- };
- /**
- * Translate from linear (internal) to axis value.
- *
- * @private
- * @function Highcharts.Axis#lin2val
- *
- * @param {number} val
- * The linear abstracted value.
- *
- * @param {boolean} [fromIndex]
- * Translate from an index in the ordinal positions rather than a
- * value.
- *
- * @return {number}
- */
- axisProto.lin2val = function (val, fromIndex) {
- var axis = this, ordinal = axis.ordinal, ordinalPositions = ordinal.positions, ret;
- // the visible range contains only equally spaced values
- if (!ordinalPositions) {
- ret = val;
- }
- else {
- var ordinalSlope = ordinal.slope, ordinalOffset = ordinal.offset, i = ordinalPositions.length - 1, linearEquivalentLeft, linearEquivalentRight, distance;
- // Handle the case where we translate from the index directly,
- // used only when panning an ordinal axis
- if (fromIndex) {
- if (val < 0) { // out of range, in effect panning to the left
- val = ordinalPositions[0];
- }
- else if (val > i) { // out of range, panning to the right
- val = ordinalPositions[i];
- }
- else { // split it up
- i = Math.floor(val);
- distance = val - i; // the decimal
- }
- // Loop down along the ordinal positions. When the linear
- // equivalent of i matches an ordinal position, interpolate
- // between the left and right values.
- }
- else {
- while (i--) {
- linearEquivalentLeft =
- (ordinalSlope * i) + ordinalOffset;
- if (val >= linearEquivalentLeft) {
- linearEquivalentRight =
- (ordinalSlope *
- (i + 1)) +
- ordinalOffset;
- // something between 0 and 1
- distance = (val - linearEquivalentLeft) /
- (linearEquivalentRight - linearEquivalentLeft);
- break;
- }
- }
- }
- // If the index is within the range of the ordinal positions,
- // return the associated or interpolated value. If not, just
- // return the value.
- return (typeof distance !== 'undefined' &&
- typeof ordinalPositions[i] !== 'undefined' ?
- ordinalPositions[i] + (distance ?
- distance *
- (ordinalPositions[i + 1] - ordinalPositions[i]) :
- 0) :
- val);
- }
- return ret;
- };
- /**
- * Translate from a linear axis value to the corresponding ordinal axis
- * position. If there are no gaps in the ordinal axis this will be the
- * same. The translated value is the value that the point would have if
- * the axis were linear, using the same min and max.
- *
- * @private
- * @function Highcharts.Axis#val2lin
- *
- * @param {number} val
- * The axis value.
- *
- * @param {boolean} [toIndex]
- * Whether to return the index in the ordinalPositions or the new value.
- *
- * @return {number}
- */
- axisProto.val2lin = function (val, toIndex) {
- var axis = this, ordinal = axis.ordinal, ordinalPositions = ordinal.positions, ret;
- if (!ordinalPositions) {
- ret = val;
- }
- else {
- var ordinalLength = ordinalPositions.length, i, distance, ordinalIndex;
- // first look for an exact match in the ordinalpositions array
- i = ordinalLength;
- while (i--) {
- if (ordinalPositions[i] === val) {
- ordinalIndex = i;
- break;
- }
- }
- // if that failed, find the intermediate position between the
- // two nearest values
- i = ordinalLength - 1;
- while (i--) {
- if (val > ordinalPositions[i] || i === 0) { // interpolate
- // something between 0 and 1
- distance = (val - ordinalPositions[i]) /
- (ordinalPositions[i + 1] - ordinalPositions[i]);
- ordinalIndex = i + distance;
- break;
- }
- }
- ret = toIndex ?
- ordinalIndex :
- ordinal.slope *
- (ordinalIndex || 0) +
- ordinal.offset;
- }
- return ret;
- };
- // Record this to prevent overwriting by broken-axis module (#5979)
- axisProto.ordinal2lin = axisProto.val2lin;
- /* eslint-disable no-invalid-this */
- addEvent(AxisClass, 'afterInit', function () {
- var axis = this;
- if (!axis.ordinal) {
- axis.ordinal = new OrdinalAxis.Composition(axis);
- }
- });
- addEvent(AxisClass, 'foundExtremes', function () {
- var axis = this;
- if (axis.isXAxis &&
- defined(axis.options.overscroll) &&
- axis.max === axis.dataMax &&
- (
- // Panning is an execption. We don't want to apply
- // overscroll when panning over the dataMax
- !axis.chart.mouseIsDown ||
- axis.isInternal) && (
- // Scrollbar buttons are the other execption:
- !axis.eventArgs ||
- axis.eventArgs && axis.eventArgs.trigger !== 'navigator')) {
- axis.max += axis.options.overscroll;
- // Live data and buttons require translation for the min:
- if (!axis.isInternal && defined(axis.userMin)) {
- axis.min += axis.options.overscroll;
- }
- }
- });
- // For ordinal axis, that loads data async, redraw axis after data is
- // loaded. If we don't do that, axis will have the same extremes as
- // previously, but ordinal positions won't be calculated. See #10290
- addEvent(AxisClass, 'afterSetScale', function () {
- var axis = this;
- if (axis.horiz && !axis.isDirty) {
- axis.isDirty = axis.isOrdinal &&
- axis.chart.navigator &&
- !axis.chart.navigator.adaptToUpdatedData;
- }
- });
- addEvent(AxisClass, 'initialAxisTranslation', function () {
- var axis = this;
- if (axis.ordinal) {
- axis.ordinal.beforeSetTickPositions();
- axis.tickInterval = axis.ordinal.postProcessTickInterval(axis.tickInterval);
- }
- });
- // Extending the Chart.pan method for ordinal axes
- addEvent(ChartClass, 'pan', function (e) {
- var chart = this, xAxis = chart.xAxis[0], overscroll = xAxis.options.overscroll, chartX = e.originalEvent.chartX, panning = chart.options.chart &&
- chart.options.chart.panning, runBase = false;
- if (panning &&
- panning.type !== 'y' &&
- xAxis.options.ordinal &&
- xAxis.series.length) {
- var mouseDownX = chart.mouseDownX, extremes = xAxis.getExtremes(), dataMax = extremes.dataMax, min = extremes.min, max = extremes.max, trimmedRange, hoverPoints = chart.hoverPoints, closestPointRange = (xAxis.closestPointRange ||
- (xAxis.ordinal && xAxis.ordinal.overscrollPointsRange)), pointPixelWidth = (xAxis.translationSlope *
- (xAxis.ordinal.slope || closestPointRange)),
- // how many ordinal units did we move?
- movedUnits = (mouseDownX - chartX) / pointPixelWidth,
- // get index of all the chart's points
- extendedAxis = { ordinal: { positions: xAxis.ordinal.getExtendedPositions() } }, ordinalPositions, searchAxisLeft, lin2val = xAxis.lin2val, val2lin = xAxis.val2lin, searchAxisRight;
- // we have an ordinal axis, but the data is equally spaced
- if (!extendedAxis.ordinal.positions) {
- runBase = true;
- }
- else if (Math.abs(movedUnits) > 1) {
- // Remove active points for shared tooltip
- if (hoverPoints) {
- hoverPoints.forEach(function (point) {
- point.setState();
- });
- }
- if (movedUnits < 0) {
- searchAxisLeft = extendedAxis;
- searchAxisRight = xAxis.ordinal.positions ? xAxis : extendedAxis;
- }
- else {
- searchAxisLeft = xAxis.ordinal.positions ? xAxis : extendedAxis;
- searchAxisRight = extendedAxis;
- }
- // In grouped data series, the last ordinal position
- // represents the grouped data, which is to the left of the
- // real data max. If we don't compensate for this, we will
- // be allowed to pan grouped data series passed the right of
- // the plot area.
- ordinalPositions = searchAxisRight.ordinal.positions;
- if (dataMax >
- ordinalPositions[ordinalPositions.length - 1]) {
- ordinalPositions.push(dataMax);
- }
- // Get the new min and max values by getting the ordinal
- // index for the current extreme, then add the moved units
- // and translate back to values. This happens on the
- // extended ordinal positions if the new position is out of
- // range, else it happens on the current x axis which is
- // smaller and faster.
- chart.fixedRange = max - min;
- trimmedRange = xAxis.navigatorAxis.toFixedRange(null, null, lin2val.apply(searchAxisLeft, [
- val2lin.apply(searchAxisLeft, [min, true]) + movedUnits,
- true // translate from index
- ]), lin2val.apply(searchAxisRight, [
- val2lin.apply(searchAxisRight, [max, true]) + movedUnits,
- true // translate from index
- ]));
- // Apply it if it is within the available data range
- if (trimmedRange.min >= Math.min(extremes.dataMin, min) &&
- trimmedRange.max <= Math.max(dataMax, max) + overscroll) {
- xAxis.setExtremes(trimmedRange.min, trimmedRange.max, true, false, { trigger: 'pan' });
- }
- chart.mouseDownX = chartX; // set new reference for next run
- css(chart.container, { cursor: 'move' });
- }
- }
- else {
- runBase = true;
- }
- // revert to the linear chart.pan version
- if (runBase || (panning && /y/.test(panning.type))) {
- if (overscroll) {
- xAxis.max = xAxis.dataMax + overscroll;
- }
- }
- else {
- e.preventDefault();
- }
- });
- addEvent(SeriesClass, 'updatedData', function () {
- var xAxis = this.xAxis;
- // Destroy the extended ordinal index on updated data
- if (xAxis && xAxis.options.ordinal) {
- delete xAxis.ordinal.index;
- }
- });
- /* eslint-enable no-invalid-this */
- }
- OrdinalAxis.compose = compose;
- })(OrdinalAxis || (OrdinalAxis = {}));
- OrdinalAxis.compose(Axis, Chart, Series); // @todo move to StockChart, remove from master
- export default OrdinalAxis;
|