UNPKG

@amcharts/amcharts4

Version:
1,252 lines (1,250 loc) 85.4 kB
/** * Value Axis module */ import { __extends } from "tslib"; /** * ============================================================================ * IMPORTS * ============================================================================ * @hidden */ import { Axis, AxisDataItem } from "./Axis"; import { AxisRendererY } from "./AxisRendererY"; import { MultiDisposer } from "../../core/utils/Disposer"; import { registry } from "../../core/Registry"; import { ValueAxisBreak } from "./ValueAxisBreak"; import * as $math from "../../core/utils/Math"; import * as $iter from "../../core/utils/Iterator"; import * as $object from "../../core/utils/Object"; import * as $type from "../../core/utils/Type"; import * as $utils from "../../core/utils/Utils"; /** * ============================================================================ * DATA ITEM * ============================================================================ * @hidden */ /** * Defines a [[DataItem]] for [[ValueAxis]]. * * @see {@link DataItem} */ var ValueAxisDataItem = /** @class */ (function (_super) { __extends(ValueAxisDataItem, _super); /** * Constructor */ function ValueAxisDataItem() { var _this = _super.call(this) || this; _this.className = "ValueAxisDataItem"; _this.values.value = {}; _this.values.endValue = {}; _this.applyTheme(); return _this; } Object.defineProperty(ValueAxisDataItem.prototype, "value", { /** * @return Value */ get: function () { return this.values["value"].value; }, /** * A data point's numeric value. * * @param value Value */ set: function (value) { this.setValue("value", value); }, enumerable: true, configurable: true }); Object.defineProperty(ValueAxisDataItem.prototype, "endValue", { /** * @return Value */ get: function () { return this.values["endValue"].value; }, /** * Data point's numeric end value. * * @param value End value */ set: function (value) { this.setValue("endValue", value); }, enumerable: true, configurable: true }); return ValueAxisDataItem; }(AxisDataItem)); export { ValueAxisDataItem }; /** * ============================================================================ * MAIN CLASS * ============================================================================ * @hidden */ /** * Used to create a value axis for the chart. * * ```TypeScript * // Create the axis * let valueAxis = chart.yAxes.push(new am4charts.ValueAxis()); * * // Set settings * valueAxis.title.text = "Monthly Sales"; * ``` * ```JavaScript * // Create the axis * var valueAxis = chart.yAxes.push(new am4charts.ValueAxis()); * * // Set settings * valueAxis.title.text = "Monthly Sales"; * ``` * ```JSON * "yAxes": [{ * "type": "ValueAxis", * "title": { * "text": "Monthly Sales" * } * }] * ``` * * @see {@link IValueAxisEvents} for a list of available Events * @see {@link IValueAxisAdapters} for a list of available Adapters * @important */ var ValueAxis = /** @class */ (function (_super) { __extends(ValueAxis, _super); /** * Constructor */ function ValueAxis() { var _this = // Init _super.call(this) || this; /** * [_stepDecimalPlaces description] * * @todo Description */ _this._stepDecimalPlaces = 0; _this._prevStepDecimalPlaces = 0; _this._adjustLabelPrecision = true; /** * Base value for the axis. */ _this._baseValue = 0; /** * Adjusted start in case we have breaks. * * @todo Description */ _this._adjustedStart = 0; /** * Adjusted end in case we have breaks. * * @todo Description */ _this._adjustedEnd = 1; _this._extremesChanged = false; _this._deltaMinMax = 1; _this._dsc = false; /** * As calculating totals is expensive operation and not often needed, we * don't do it by default. * * In case you use `totalPercent` or `total` in your charts, this must be set * to `true`. * * @default false * @see {@link https://www.amcharts.com/docs/v4/chart-types/xy-chart/#100_stacks} For using `calculateTotals` for 100% stacked series. * @see {@link https://www.amcharts.com/docs/v4/concepts/formatters/formatting-strings/#Placeholders_for_numeric_values} For using `calculateTotals` in labels. */ _this.calculateTotals = false; _this.className = "ValueAxis"; // Set field name _this.axisFieldName = "value"; // Set defaults _this.setPropertyValue("maxZoomFactor", 1000); _this.setPropertyValue("extraMin", 0); _this.setPropertyValue("extraMax", 0); _this.setPropertyValue("strictMinMax", false); _this.setPropertyValue("maxPrecision", Number.MAX_VALUE); _this.setPropertyValue("adjustLabelPrecision", true); _this.setPropertyValue("extraTooltipPrecision", 0); _this.keepSelection = false; _this.includeRangesInMinMax = false; // Apply theme _this.applyTheme(); return _this; } /** * Holds reference to a function that accepts a DataItem as parameter. * * It can either return a fill opacity for a fill, or manipulate data item * directly, to create various highlighting scenarios. */ ValueAxis.prototype.fillRule = function (dataItem) { var value = dataItem.value; var axis = dataItem.component; if (!dataItem.axisFill.disabled) { // rounding in left to solve floating point number if ($math.round(value / axis.step / 2, 5) == Math.round(value / axis.step / 2)) { dataItem.axisFill.__disabled = true; } else { dataItem.axisFill.__disabled = false; } } }; /** * Returns a new/empty [[DataItem]] of the type appropriate for this object. * * @see {@link DataItem} * @return Data Item */ ValueAxis.prototype.createDataItem = function () { return new ValueAxisDataItem(); }; /** * Returns a new/empty [[AxisBreak]] of the appropriate type. * * @return Axis break */ ValueAxis.prototype.createAxisBreak = function () { return new ValueAxisBreak(); }; /** * [dataChangeUpdate description] * * This is a placeholder to override for extending classes. * * @ignore Exclude from docs * @todo Description */ ValueAxis.prototype.dataChangeUpdate = function () { this.clearCache(); if (!this.keepSelection) { if (this._start != 0 || this._end != 1) { this._start = 0; this._end = 1; this.dispatchImmediately("startendchanged"); } } else { if (this._start != 0) { this.dispatchImmediately("startchanged"); } if (this._end != 1) { this.dispatchImmediately("endchanged"); } if (this._start != 0 || this._end != 1) { this.dispatchImmediately("startendchanged"); } } this._maxZoomed = this._maxDefined; this._minZoomed = this._minDefined; this._maxAdjusted = this._maxDefined; this._minAdjusted = this._minDefined; }; /** * Processes data items of the related Series. * * @ignore Exclude from docs */ ValueAxis.prototype.processSeriesDataItems = function () { // @todo: add some boolean (maybe autodedect) if we need these calculations or not. this place uses a lot of cpu if (this.calculateTotals) { var series = this.series.getIndex(0); var startIndex = series.startIndex; if (series.dataItems.length > 0) { if (startIndex > 0) { startIndex--; } var endIndex = series.endIndex; if (endIndex < series.dataItems.length) { endIndex++; } var _loop_1 = function (i) { // This has to be `var` in order to avoid garbage collection var total = {}; var sum = {}; this_1.series.each(function (series) { if (!series.excludeFromTotal) { var dataItem_1 = series.dataItems.getIndex(i); if (dataItem_1) { $object.each(dataItem_1.values, function (key) { var value = dataItem_1.values[key].workingValue; // can not use getWorkingValue here! if ($type.isNumber(value)) { if (!$type.isNumber(total[key])) { total[key] = Math.abs(value); } else { total[key] += Math.abs(value); } if (!$type.isNumber(sum[key])) { sum[key] = value; } else { sum[key] += value; } } }); } } }); this_1.series.each(function (series) { if (!series.excludeFromTotal) { var dataItem_2 = series.dataItems.getIndex(i); if (dataItem_2) { $object.each(dataItem_2.values, function (key) { var value = dataItem_2.values[key].workingValue; // can not use getWorkingValue here! if ($type.isNumber(value)) { dataItem_2.setCalculatedValue(key, total[key], "total"); dataItem_2.setCalculatedValue(key, 100 * value / total[key], "totalPercent"); dataItem_2.setCalculatedValue(key, sum[key], "sum"); } }); } } }); }; var this_1 = this; // This has to be `var` in order to avoid garbage collection for (var i = startIndex; i < endIndex; ++i) { _loop_1(i); } } } }; /** * Validates the whole axis. Causes it to redraw. * * @ignore Exclude from docs * @todo Description (review) */ ValueAxis.prototype.validate = function () { if (this.axisLength <= 0) { return; } _super.prototype.validate.call(this); this.getMinMax(); if (!$type.isNumber(this._minAdjusted)) { this.dataItems.each(function (dataItem) { dataItem.value = null; }); } this.fixAxisBreaks(); this.calculateZoom(); this.validateAxisElements(); this.validateAxisRanges(); this.validateBreaks(); this.hideUnusedDataItems(); this.renderer.invalidateLayout(); // hide too close //this.hideTooCloseDataItems(); }; /** * Calculates all positions, related to axis as per current zoom. * * @ignore Exclude from docs */ ValueAxis.prototype.calculateZoom = function () { if ($type.isNumber(this.min) && $type.isNumber(this.max)) { var min = this.positionToValue(this.start); var max = this.positionToValue(this.end); var differece = this.adjustDifference(min, max); var minMaxStep = this.adjustMinMax(min, max, differece, this._gridCount, true); var stepDecimalPlaces = $utils.decimalPlaces(minMaxStep.step); this._stepDecimalPlaces = stepDecimalPlaces; min = $math.round(min, stepDecimalPlaces); max = $math.round(max, stepDecimalPlaces); minMaxStep = this.adjustMinMax(min, max, differece, this._gridCount, true); var step = minMaxStep.step; if (this.syncWithAxis) { var calculated = this.getCache(min + "-" + max); if ($type.isNumber(calculated)) { step = calculated; } } else { min = minMaxStep.min; max = minMaxStep.max; } if (this._minZoomed != min || this._maxZoomed != max || this._step != step || this._dsc) { this._dsc = false; this._minZoomed = min; this._maxZoomed = max; this._step = step; this.dispatchImmediately("selectionextremeschanged"); } } }; ValueAxis.prototype.fixSmallStep = function (step) { if (1 + step == 1) { step *= 2; return this.fixSmallStep(step); } return step; }; /** * Validates Axis elements. * * @ignore Exclude from docs * @todo Description */ ValueAxis.prototype.validateAxisElements = function () { var _this = this; if ($type.isNumber(this.max) && $type.isNumber(this.min)) { // first regular items var value_1 = this.minZoomed - this._step * 2; if (!this.logarithmic) { value_1 = Math.floor(value_1 / this._step) * this._step; } else { var differencePower = Math.log(this.max) * Math.LOG10E - Math.log(this.min) * Math.LOG10E; if (differencePower > 1) { value_1 = Math.pow(10, Math.log(this.min) * Math.LOG10E); } else { value_1 = Math.floor(this.minZoomed / this._step) * this._step; if (value_1 == 0) { value_1 = this.minZoomed; } } } var maxZoomed = this._maxZoomed + this._step; this.resetIterators(); var dataItemsIterator_1 = this._dataItemsIterator; if (this._step == 0) { return; } this._step = this.fixSmallStep(this._step); var i = 0; var precisionChanged = this._prevStepDecimalPlaces != this._stepDecimalPlaces; this._prevStepDecimalPlaces = this._stepDecimalPlaces; while (value_1 <= maxZoomed) { var axisBreak = this.isInBreak(value_1); if (!axisBreak) { var dataItem = dataItemsIterator_1.find(function (x) { return x.value === value_1; }); if (dataItem.__disabled) { dataItem.__disabled = false; } //this.processDataItem(dataItem); this.appendDataItem(dataItem); dataItem.axisBreak = undefined; if (dataItem.value != value_1 || precisionChanged) { dataItem.value = value_1; dataItem.text = this.formatLabel(value_1); if (dataItem.label && dataItem.label.invalid) { dataItem.label.validate(); } if (dataItem.value >= this.min && dataItem.value <= this.max) { if (dataItem.label) { if ((this.axisLetter == "Y" && dataItem.label.measuredWidth > this.ghostLabel.measuredWidth) || (this.axisLetter == "X" && dataItem.label.measuredHeight > this.ghostLabel.measuredHeight)) { this.ghostLabel.text = dataItem.label.currentText; this.ghostLabel.validate(); } } } } this.validateDataElement(dataItem); } i++; var oldValue = value_1; if (!this.logarithmic) { value_1 += this._step; } else { var differencePower = Math.log(this.max) * Math.LOG10E - Math.log(this.min) * Math.LOG10E; if (differencePower > 1) { value_1 = Math.pow(10, Math.log(this.min) * Math.LOG10E + i); } else { value_1 += this._step; } } var stepPower = Math.pow(10, Math.floor(Math.log(Math.abs(this._step)) * Math.LOG10E)); if (stepPower < 1) { // exponent is less then 1 too. Count decimals of exponent var decCount = Math.round(Math.abs(Math.log(Math.abs(stepPower)) * Math.LOG10E)) + 2; decCount = Math.min(13, decCount); // round value to avoid floating point issues value_1 = $math.round(value_1, decCount); // ceil causes problems: https://codepen.io/team/amcharts/pen/XWMjZwy?editors=1010 if (oldValue == value_1) { value_1 = maxZoomed; break; } } } var axisBreaks = this._axisBreaks; if (axisBreaks) { // breaks later var renderer_1 = this.renderer; $iter.each(axisBreaks.iterator(), function (axisBreak) { if (axisBreak.breakSize > 0) { // only add grid if gap is bigger then minGridDistance if ($math.getDistance(axisBreak.startPoint, axisBreak.endPoint) > renderer_1.minGridDistance) { var breakValue_1 = axisBreak.adjustedMin; while (breakValue_1 <= axisBreak.adjustedMax) { if (breakValue_1 >= axisBreak.adjustedStartValue && breakValue_1 <= axisBreak.adjustedEndValue) { var dataItem = dataItemsIterator_1.find(function (x) { return x.value === breakValue_1; }); if (dataItem.__disabled) { dataItem.__disabled = false; } //this.processDataItem(dataItem); _this.appendDataItem(dataItem); dataItem.axisBreak = axisBreak; if (dataItem.value != breakValue_1) { dataItem.value = breakValue_1; dataItem.text = _this.formatLabel(breakValue_1); if (dataItem.label && dataItem.label.invalid) { dataItem.label.validate(); } } _this.validateDataElement(dataItem); } breakValue_1 += axisBreak.adjustedStep; } } } }); } } }; /** * Validates axis data item. * * @ignore Exclude from docs * @todo Description * @param dataItem Data item */ ValueAxis.prototype.validateDataElement = function (dataItem) { _super.prototype.validateDataElement.call(this, dataItem); //dataItem.__disabled = false; dataItem.itemIndex = this._axisItemCount; this._axisItemCount++; var renderer = this.renderer; var value = dataItem.value; var endValue = dataItem.endValue; var position = this.valueToPosition(value); dataItem.position = position; var endPosition = position; var fillEndPosition = this.valueToPosition(value + this._step); if ($type.isNumber(endValue)) { endPosition = this.valueToPosition(endValue); fillEndPosition = endPosition; } // this point is needed to calculate distance to satisfy minGridDistance dataItem.point = renderer.positionToPoint(position); var tick = dataItem.tick; if (tick && !tick.disabled) { renderer.updateTickElement(tick, position, endPosition); } var grid = dataItem.grid; if (grid && !grid.disabled) { renderer.updateGridElement(grid, position, endPosition); } var label = dataItem.label; if (label && !label.disabled) { renderer.updateLabelElement(label, position, endPosition); } var fill = dataItem.axisFill; if (fill && !fill.disabled) { renderer.updateFillElement(fill, position, fillEndPosition); if (!dataItem.isRange) { this.fillRule(dataItem); } } if (dataItem.bullet) { renderer.updateBullet(dataItem.bullet, position, endPosition); } var mask = dataItem.mask; if (mask) { renderer.updateFillElement(mask, position, fillEndPosition); } }; /** * Formats the value according to axis' own [[NumberFormatter]]. * * @param value Source value * @return Formatted value */ ValueAxis.prototype.formatLabel = function (value) { if (this.adjustLabelPrecision && value != 0) { return this.numberFormatter.format(value, undefined, this._stepDecimalPlaces); } else { return this.numberFormatter.format(value); } }; Object.defineProperty(ValueAxis.prototype, "basePoint", { /** * Coordinates of the actual axis start. * * @ignore Exclude from docs * @return Base point */ get: function () { var baseValue = this.baseValue; var position = this.valueToPosition(baseValue); var basePoint = this.renderer.positionToPoint(position); return basePoint; }, enumerable: true, configurable: true }); Object.defineProperty(ValueAxis.prototype, "baseValue", { /** * @return base value */ get: function () { var baseValue = this._baseValue; if (this.logarithmic) { baseValue = this.min; } if (!this._adapterO) { return baseValue; } else { return this._adapterO.apply("baseValue", baseValue); } }, /** * A base value. * * This is a threshold value that will divide "positive" and "negative" * value ranges. * * Other scale-related functionality also depend on base value. E.g. stacks, * value-dependent coloring, etc. * * @param value Base value */ set: function (value) { this._baseValue = value; this.invalidateLayout(); this.invalidateSeries(); }, enumerable: true, configurable: true }); /** * Converts a numeric value to relative position on axis * * An alias to `valueToPosition()`. * * @param value Value * @return Position */ ValueAxis.prototype.anyToPosition = function (value) { return this.valueToPosition(value); }; /** * Converts a numeric value to orientation point (x, y, angle) on axis * * @param value Value * @return Orientation point */ ValueAxis.prototype.valueToPoint = function (value) { var position = this.valueToPosition(value); var point = this.renderer.positionToPoint(position); var angle = this.renderer.positionToAngle(position); return { x: point.x, y: point.y, angle: angle }; }; /** * Converts a numeric value to orientation (x, y, angle) point on axis * * @param value Value * @return Orientation point */ ValueAxis.prototype.anyToPoint = function (value) { return this.valueToPoint(value); }; /** * Converts a numeric value to relative position on axis. * * @param value Value * @return relative position */ ValueAxis.prototype.valueToPosition = function (value) { if ($type.isNumber(value)) { // todo: think if possible to take previous value and do not go through all previous breaks var min_1 = this.min; var max_1 = this.max; if ($type.isNumber(min_1) && $type.isNumber(max_1)) { var difference = this._difference; var axisBreaks = this._axisBreaks; if (axisBreaks && axisBreaks.length > 0) { $iter.eachContinue(axisBreaks.iterator(), function (axisBreak) { var startValue = axisBreak.adjustedStartValue; var endValue = axisBreak.adjustedEndValue; if ($type.isNumber(startValue) && $type.isNumber(endValue)) { if (value < startValue) { return false; } if ($math.intersect({ start: startValue, end: endValue }, { start: min_1, end: max_1 })) { // todo: check this once and set some flag in axisBreak startValue = Math.max(startValue, min_1); endValue = Math.min(endValue, max_1); var breakSize = axisBreak.breakSize; // value to the right of break end if (value > endValue) { min_1 += (endValue - startValue) * (1 - breakSize); // todo: maybe this can be done differently? } // value to the left of break start else if (value < startValue) { } // value within break else { value = startValue + (value - startValue) * breakSize; } } } return true; }); } var position = void 0; if (!this.logarithmic) { position = (value - min_1) / difference; } else { var treatZeroAs = this.treatZeroAs; if ($type.isNumber(treatZeroAs)) { if (value <= treatZeroAs) { value = treatZeroAs; } } position = (Math.log(value) * Math.LOG10E - Math.log(this.min) * Math.LOG10E) / ((Math.log(this.max) * Math.LOG10E - Math.log(this.min) * Math.LOG10E)); } //position = $math.round(position, 10); return position; } } return 0; }; /** * When fontSize of fontFamily changes we need to hard-invalidate all Labels of this container to position them properly. */ ValueAxis.prototype.invalidateLabels = function () { _super.prototype.invalidateLabels.call(this); if (this.dataItems) { this.dataItems.each(function (dataItem) { dataItem.value = undefined; }); this.invalidate(); } }; /** * Converts an relative position to a corresponding value within * axis' scale. * * @param position Position (px) * @return Value */ ValueAxis.prototype.positionToValue = function (position) { var min = this.min; var max = this.max; if ($type.isNumber(min) && $type.isNumber(max)) { var difference_1 = max - min; //no need to adjust! var value_2 = null; var axisBreaks = this._axisBreaks; if (axisBreaks) { // in case we have some axis breaks if (axisBreaks.length > 0) { $iter.eachContinue(axisBreaks.iterator(), function (axisBreak) { var breakStartPosition = axisBreak.startPosition; var breakEndPosition = axisBreak.endPosition; var breakStartValue = axisBreak.adjustedStartValue; var breakEndValue = axisBreak.adjustedEndValue; if ($type.isNumber(breakStartValue) && $type.isNumber(breakEndValue)) { if (breakStartValue > max) { return false; } if ($math.intersect({ start: breakStartValue, end: breakEndValue }, { start: min, end: max })) { breakStartValue = $math.max(breakStartValue, min); breakEndValue = $math.min(breakEndValue, max); var breakSize = axisBreak.breakSize; difference_1 -= (breakEndValue - breakStartValue) * (1 - breakSize); // position to the right of break end if (position > breakEndPosition) { min += (breakEndValue - breakStartValue) * (1 - breakSize); } // position to the left of break start else if (position < breakStartPosition) { } // value within break else { var breakPosition = (position - breakStartPosition) / (breakEndPosition - breakStartPosition); value_2 = breakStartValue + breakPosition * (breakEndValue - breakStartValue); return false; } } return true; } }); } } if (!$type.isNumber(value_2)) { if (this.logarithmic) { value_2 = Math.pow(Math.E, (position * ((Math.log(this.max) * Math.LOG10E - Math.log(this.min) * Math.LOG10E)) + Math.log(this.min) * Math.LOG10E) / Math.LOG10E); } else { value_2 = position * difference_1 + min; } } return value_2; } //} }; /** * Converts an X coordinate to a relative value in axis' scale. * * @param x X (px) * @return Value */ ValueAxis.prototype.xToValue = function (x) { return this.positionToValue(this.pointToPosition({ x: x, y: 0 })); }; /** * Converts an Y coordinate to a relative value in axis' scale. * * @param y Y (px) * @return Value */ ValueAxis.prototype.yToValue = function (y) { return this.positionToValue(this.pointToPosition({ x: 0, y: y })); }; /** * Converts pixel coordinates to a relative position. (0-1) * * @param point Coorinates (px) * @return Position (0-1) */ ValueAxis.prototype.pointToPosition = function (point) { if (this.renderer instanceof AxisRendererY) { return 1 - this.renderer.pointToPosition(point); } else { return this.renderer.pointToPosition(point); } }; /** * @ignore */ ValueAxis.prototype.animateMinMax = function (min, max) { return this.animate([{ property: "_minAdjusted", from: this._minAdjusted, to: min }, { property: "_maxAdjusted", from: this._maxAdjusted, to: max }], this.rangeChangeDuration, this.rangeChangeEasing); }; /** * Calculates smallest and biggest value for the axis scale. * @ignore * @todo Description (review) */ ValueAxis.prototype.getMinMax = function () { var _this = this; this.updateGridCount(); var min = Number.POSITIVE_INFINITY; var max = Number.NEGATIVE_INFINITY; // only if min and max are not set from outside, we go through min and max influencers if (!$type.isNumber(this._minDefined) || !$type.isNumber(this._maxDefined)) { this.series.each(function (series) { if (!series.ignoreMinMax) { // check min var seriesMin = series.min(_this); if ($type.isNumber(seriesMin) && (seriesMin < min)) { min = seriesMin; } // check max var seriesMax = series.max(_this); if ($type.isNumber(seriesMax) && (seriesMax > max)) { max = seriesMax; } } }); if (this.includeRangesInMinMax) { this.axisRanges.each(function (range) { if (!range.ignoreMinMax) { var minValue = $math.min(range.value, range.endValue); var maxValue = $math.max(range.value, range.endValue); if (minValue < min || !$type.isNumber(min)) { min = minValue; } if (maxValue > max || !$type.isNumber(max)) { max = maxValue; } } }); } } if (this.logarithmic) { var treatZeroAs = this.treatZeroAs; if ($type.isNumber(treatZeroAs)) { if (min <= 0) { min = treatZeroAs; } } if (min <= 0) { this.raiseCriticalError(new Error("Logarithmic value axis can not have values <= 0."), true); } } if (min == 0 && max == 0) { max = 0.9; min = -0.9; } // if defined from outside if ($type.isNumber(this._minDefined)) { min = this._minDefined; } if ($type.isNumber(this._maxDefined)) { max = this._maxDefined; } if (this._adapterO) { min = this._adapterO.apply("min", min); } if (this._adapterO) { max = this._adapterO.apply("max", max); } if (!$type.isNumber(min) || !$type.isNumber(max)) { return; } this._minReal = min; this._maxReal = max; if (min == Number.POSITIVE_INFINITY) { min = undefined; } if (max == Number.NEGATIVE_INFINITY) { max = undefined; } var dif = this.adjustDifference(min, max); // previously it was max-min, but not worked well min = this.fixMin(min); max = this.fixMax(max); // this happens if starLocation and endLocation are 0.5 and DateAxis has only one date if (max - min <= 1 / Math.pow(10, 15)) { if (max - min != 0) { this._deltaMinMax = (max - min) / 2; } else { // the number by which we need to raise 10 to get difference var exponent = Math.log(Math.abs(max)) * Math.LOG10E; // here we find a number which is power of 10 and has the same count of numbers as difference has var power = Math.pow(10, Math.floor(exponent)); // reduce this number by 10 times power = power / 10; this._deltaMinMax = power; } min -= this._deltaMinMax; max += this._deltaMinMax; } min -= (max - min) * this.extraMin; max += (max - min) * this.extraMax; var strict = this.strictMinMax; if ($type.isNumber(this._maxDefined)) { strict = true; } var minMaxStep = this.adjustMinMax(min, max, dif, this._gridCount, strict); min = minMaxStep.min; max = minMaxStep.max; dif = max - min; //new // do it for the second time (importat!) minMaxStep = this.adjustMinMax(min, max, max - min, this._gridCount, true); min = minMaxStep.min; max = minMaxStep.max; // return min max if strict if (this.strictMinMax) { if ($type.isNumber(this._minDefined)) { min = this._minDefined; } else { min = this._minReal; } if ($type.isNumber(this._maxDefined)) { max = this._maxDefined; } else { max = this._maxReal; } if (max - min <= 0.00000001) { min -= this._deltaMinMax; max += this._deltaMinMax; } min -= (max - min) * this.extraMin; max += (max - min) * this.extraMax; } if (this._adapterO) { min = this._adapterO.apply("min", min); } if (this._adapterO) { max = this._adapterO.apply("max", max); } this._step = minMaxStep.step; if (!$type.isNumber(min) && !$type.isNumber(max)) { this.start = 0; this.end = 1; this.renderer.labels.each(function (label) { label.dataItem.text = ""; }); } // checking isNumber is good when all series are hidden if ((this._minAdjusted != min || this._maxAdjusted != max) && $type.isNumber(min) && $type.isNumber(max)) { var animation = this._minMaxAnimation; if (this._extremesChanged && $type.isNumber(this._minAdjusted) && $type.isNumber(this._maxAdjusted) && this.inited) { if ((animation && !animation.isFinished()) && this._finalMax == max && this._finalMin == min) { return; } else { this._finalMin = min; this._finalMax = max; animation = this.animateMinMax(min, max); if (animation && !animation.isFinished()) { animation.events.on("animationprogress", this.validateDataItems, this); animation.events.on("animationended", function () { //this.validateDataItems(); _this.series.each(function (series) { series.validate(); }); _this.validateDataItems(); _this.handleSelectionExtremesChange(); }); this._minMaxAnimation = animation; } else { this.series.each(function (series) { series.invalidate(); }); } this.validateDataItems(); this.dispatchImmediately("extremeschanged"); this.handleSelectionExtremesChange(); } } else { if ((animation && !animation.isFinished()) && this._finalMax == max && this._finalMin == min) { return; } else { this._minAdjusted = min; this._maxAdjusted = max; this._finalMin = min; this._finalMax = max; this.invalidateDataItems(); this.dispatchImmediately("extremeschanged"); this._saveMinMax(min, max); } } } this._extremesChanged = false; this._difference = this.adjustDifference(min, max); }; /** * Adjusts the minimum value. * * This is a placeholder method for extending classes to override. * * For numeric values this does nothing, however for more complex types, like * dates, it may be necessary to adjust. * * @param value Value * @return Adjusted value */ ValueAxis.prototype.fixMin = function (value) { return value; }; /** * Adjusts the maximum value. * * This is a placeholder method for extending classes to override. * * For numeric values this does nothing, however for more complex types, like * dates, it may be necessary to adjust. * * @param value Value * @return Adjusted value */ ValueAxis.prototype.fixMax = function (value) { return value; }; /** * Adjusts actual min and max scale values so that the axis starts and ends * at "nice" values, unless `strictMinMax` is set. * * The `difference` can be something else than `max - min`, because of the * axis breaks. * * @ignore Exclude from docs * @todo Description * @param min [description] * @param max [description] * @param difference [description] * @param gridCount [description] * @param strictMode [description] * @return [description] */ ValueAxis.prototype.adjustMinMax = function (min, max, difference, gridCount, strictMode) { // will fail if 0 if (gridCount <= 1) { gridCount = 1; } gridCount = Math.round(gridCount); var initialMin = min; var initialMax = max; // in case min and max is the same, use max if (difference === 0) { difference = Math.abs(max); } // the number by which we need to raise 10 to get difference var exponent = Math.log(Math.abs(difference)) * Math.LOG10E; // here we find a number which is power of 10 and has the same count of numbers as difference has var power = Math.pow(10, Math.floor(exponent)); // reduce this number by 10 times power = power / 10; var extra = power; if (strictMode) { extra = 0; } if (!this.logarithmic) { // round down min if (strictMode) { min = Math.floor(min / power) * power; // round up max max = Math.ceil(max / power) * power; } else { min = Math.ceil(min / power) * power - extra; // round up max max = Math.floor(max / power) * power + extra; } // don't let min go below 0 if real min is >= 0 if (min < 0 && initialMin >= 0) { min = 0; } // don't let max go above 0 if real max is <= 0 if (max > 0 && initialMax <= 0) { max = 0; } } else { if (min <= 0) { //throw Error("Logarithmic value axis can not have values <= 0."); min = this.baseValue; } // @todo: think of a better way or to restrict zooming when no series are selected if (min == Infinity) { min = 1; } if (max == -Infinity) { max = 10; } if (this.strictMinMax) { if (this._minDefined > 0) { min = this._minDefined; } else { min = min; } if (this._maxDefined > 0) { max = max; } } else { min = Math.pow(10, Math.floor(Math.log(Math.abs(min)) * Math.LOG10E)); max = Math.pow(10, Math.ceil(Math.log(Math.abs(max)) * Math.LOG10E)); } } // repeat diff, exponent and power again with rounded values //difference = this.adjustDifference(min, max); /* if(min > initialMin){ min = initialMin; } if(max < initialMax){ max = initialMax; } */ exponent = Math.log(Math.abs(difference)) * Math.LOG10E; power = Math.pow(10, Math.floor(exponent)); power = power / 10; // approximate difference between two grid lines var step = Math.ceil((difference / gridCount) / power) * power; var stepPower = Math.pow(10, Math.floor(Math.log(Math.abs(step)) * Math.LOG10E)); // TODO: in v3 I had fixStepE here, ommiting it for a while, need to think about other solution // the step should divide by 2, 5, and 10. var stepDivisor = Math.ceil(step / stepPower); // number 0 - 10 if (stepDivisor > 5) { stepDivisor = 10; } else if (stepDivisor <= 5 && stepDivisor > 2) { stepDivisor = 5; } // now get real step step = Math.ceil(step / (stepPower * stepDivisor)) * stepPower * stepDivisor; if (this.maxPrecision < Number.MAX_VALUE && step != $math.ceil(step, this.maxPrecision)) { step = $math.ceil(step, this.maxPrecision); } var decCount = 0; // in case numbers are smaller than 1 if (stepPower < 1) { // exponent is less then 1 too. Count decimals of exponent decCount = Math.round(Math.abs(Math.log(Math.abs(stepPower)) * Math.LOG10E)) + 1; // round step step = $math.round(step, decCount); } if (!this.logarithmic) { // final min and max var minCount = Math.floor(min / step); min = $math.round(step * minCount, decCount); var maxCount = void 0; if (!strictMode) { maxCount = Math.ceil(max / step); } else { maxCount = Math.floor(max / step); } if (maxCount == minCount) { maxCount++; } max = $math.round(step * maxCount, decCount); if (max < initialMax) { max = max + step; } if (min > initialMin) { min = min - step; } } return { min: min, max: max, step: step }; }; Object.defineProperty(ValueAxis.prototype, "min", { /** * @return Min value */ get: function () { var min = this._minAdjusted; if (!$type.isNumber(min)) { min = this._minDefined; } return min; }, /** * A minimum value for the axis scale. * * This value might be auto-adjusted by the Axis in order to accomodate the * grid nicely, i.e. plot area is divided by grid in nice equal cells. * * The above might be overridden by `strictMinMax` which will force exact * user-defined min and max values to be used for scale. * * @param value Min value */ set: function (value) { if (this._minDefined != value) { this._minDefined = value; this.invalidate(); } }, enumerable: true, configurable: true }); Objec