/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import { NumericAxisBaseOptionCommon } from './axisCommonTypes'; import { getPrecisionSafe, round } from '../util/number'; import IntervalScale from '../scale/Interval'; import { getScaleExtent } from './axisHelper'; import { AxisBaseModel } from './AxisBaseModel'; import LogScale from '../scale/Log'; import { warn } from '../util/log'; import { increaseInterval, isValueNice } from '../scale/helper'; const mathLog = Math.log; export function alignScaleTicks( scale: IntervalScale | LogScale, axisModel: AxisBaseModel>, alignToScale: IntervalScale | LogScale ) { const intervalScaleProto = IntervalScale.prototype; // NOTE: There is a precondition for log scale here: // In log scale we store _interval and _extent of exponent value. // So if we use the method of InternalScale to set/get these data. // It process the exponent value, which is linear and what we want here. const alignToTicks = intervalScaleProto.getTicks.call(alignToScale); const alignToNicedTicks = intervalScaleProto.getTicks.call(alignToScale, true); const alignToSplitNumber = alignToTicks.length - 1; const alignToInterval = intervalScaleProto.getInterval.call(alignToScale); const scaleExtent = getScaleExtent(scale, axisModel); let rawExtent = scaleExtent.extent; const isMinFixed = scaleExtent.fixMin; const isMaxFixed = scaleExtent.fixMax; if (scale.type === 'log') { const logBase = mathLog((scale as LogScale).base); rawExtent = [mathLog(rawExtent[0]) / logBase, mathLog(rawExtent[1]) / logBase]; } scale.setExtent(rawExtent[0], rawExtent[1]); scale.calcNiceExtent({ splitNumber: alignToSplitNumber, fixMin: isMinFixed, fixMax: isMaxFixed }); const extent = intervalScaleProto.getExtent.call(scale); // Need to update the rawExtent. // Because value in rawExtent may be not parsed. e.g. 'dataMin', 'dataMax' if (isMinFixed) { rawExtent[0] = extent[0]; } if (isMaxFixed) { rawExtent[1] = extent[1]; } let interval = intervalScaleProto.getInterval.call(scale); let min: number = rawExtent[0]; let max: number = rawExtent[1]; if (isMinFixed && isMaxFixed) { // User set min, max, divide to get new interval interval = (max - min) / alignToSplitNumber; } else if (isMinFixed) { max = rawExtent[0] + interval * alignToSplitNumber; // User set min, expand extent on the other side while (max < rawExtent[1] && isFinite(max) && isFinite(rawExtent[1])) { interval = increaseInterval(interval); max = rawExtent[0] + interval * alignToSplitNumber; } } else if (isMaxFixed) { // User set max, expand extent on the other side min = rawExtent[1] - interval * alignToSplitNumber; while (min > rawExtent[0] && isFinite(min) && isFinite(rawExtent[0])) { interval = increaseInterval(interval); min = rawExtent[1] - interval * alignToSplitNumber; } } else { const nicedSplitNumber = scale.getTicks().length - 1; if (nicedSplitNumber > alignToSplitNumber) { interval = increaseInterval(interval); } const range = interval * alignToSplitNumber; max = Math.ceil(rawExtent[1] / interval) * interval; min = round(max - range); // Not change the result that crossing zero. if (min < 0 && rawExtent[0] >= 0) { min = 0; max = round(range); } else if (max > 0 && rawExtent[1] <= 0) { max = 0; min = -round(range); } } // Adjust min, max based on the extent of alignTo. When min or max is set in alignTo scale const t0 = (alignToTicks[0].value - alignToNicedTicks[0].value) / alignToInterval; const t1 = (alignToTicks[alignToSplitNumber].value - alignToNicedTicks[alignToSplitNumber].value) / alignToInterval; // NOTE: Must in setExtent -> setInterval -> setNiceExtent order. intervalScaleProto.setExtent.call(scale, min + interval * t0, max + interval * t1); intervalScaleProto.setInterval.call(scale, interval); if (t0 || t1) { intervalScaleProto.setNiceExtent.call(scale, min + interval, max - interval); } if (__DEV__) { const ticks = intervalScaleProto.getTicks.call(scale); if (ticks[1] && (!isValueNice(interval) || getPrecisionSafe(ticks[1].value) > getPrecisionSafe(interval))) { warn( // eslint-disable-next-line `The ticks may be not readable when set min: ${axisModel.get('min')}, max: ${axisModel.get('max')} and alignTicks: true` ); } } }