UNPKG

echarts

Version:

Apache ECharts is a powerful, interactive charting and data visualization library for browser

386 lines (382 loc) 14.9 kB
/* * 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. */ /** * AUTO-GENERATED FILE. DO NOT MODIFY. */ /* * 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 * as zrUtil from 'zrender/lib/core/util.js'; import * as textContain from 'zrender/lib/contain/text.js'; import { makeInner, removeDuplicates, removeDuplicatesGetKeyFromItemItself } from '../util/model.js'; import { makeLabelFormatter, getOptionCategoryInterval } from './axisHelper.js'; import { asc } from '../util/number.js'; import { ordinalScaleCreateTicks } from '../scale/helper.js'; var modelInner = makeInner(); var axisInner = makeInner(); export var AxisTickLabelComputingKind = { estimate: 1, determine: 2 }; export function createAxisLabelsComputingContext(kind) { return { out: { noPxChangeTryDetermine: [] }, kind: kind }; } /** * CAUTION: Do not modify the result. */ export function createAxisLabels(axis, ctx) { var custom = axis.getLabelModel().get('customValues'); if (custom) { var scale_1 = axis.scale; return { labels: zrUtil.map(parseTickLabelCustomValues(custom, scale_1), function (tick, index) { return { formattedLabel: makeLabelFormatter(axis)(tick, index), rawLabel: scale_1.getLabel(tick), tick: tick }; }) }; } // Only ordinal scale support tick interval return axis.type === 'category' ? makeCategoryLabels(axis, ctx) : makeRealNumberLabels(axis); } /** * CAUTION: Do not modify the result. * * @param tickModel For example, can be axisTick, splitLine, splitArea. */ export function createAxisTicks(axis, tickModel, opt) { var scale = axis.scale; var custom = axis.getTickModel().get('customValues'); if (custom) { return { ticks: parseTickLabelCustomValues(custom, scale) }; } // Only ordinal scale support tick interval return axis.type === 'category' ? makeCategoryTicks(axis, tickModel) : { ticks: scale.getTicks(opt) }; } function parseTickLabelCustomValues(customValues, scale) { var extent = scale.getExtent(); var tickNumbers = []; zrUtil.each(customValues, function (val) { val = scale.parse(val); if (val >= extent[0] && val <= extent[1]) { tickNumbers.push(val); } }); removeDuplicates(tickNumbers, removeDuplicatesGetKeyFromItemItself, null); asc(tickNumbers); return zrUtil.map(tickNumbers, function (tickVal) { return { value: tickVal }; }); } function makeCategoryLabels(axis, ctx) { var labelModel = axis.getLabelModel(); var result = makeCategoryLabelsActually(axis, labelModel, ctx); return !labelModel.get('show') || axis.scale.isBlank() ? { labels: [] } : result; } function makeCategoryLabelsActually(axis, labelModel, ctx) { var labelsCache = ensureCategoryLabelCache(axis); var optionLabelInterval = getOptionCategoryInterval(labelModel); var isEstimate = ctx.kind === AxisTickLabelComputingKind.estimate; // In AxisTickLabelComputingKind.estimate, the result likely varies during a single // pass of ec main process,due to the change of axisExtent, and will not be shared with // splitLine. Therefore no cache is used. if (!isEstimate) { // PENDING: check necessary? var result_1 = axisCacheGet(labelsCache, optionLabelInterval); if (result_1) { return result_1; } } var labels; var numericLabelInterval; if (zrUtil.isFunction(optionLabelInterval)) { labels = makeTicksLabelsByCategoryIntervalNumOrCb(axis, optionLabelInterval, false); } else { numericLabelInterval = optionLabelInterval === 'auto' ? makeAutoCategoryInterval(axis, ctx) : optionLabelInterval; labels = makeTicksLabelsByCategoryIntervalNumOrCb(axis, numericLabelInterval, false); } var result = { labels: labels, labelCategoryInterval: numericLabelInterval }; if (!isEstimate) { axisCacheSet(labelsCache, optionLabelInterval, result); } else { ctx.out.noPxChangeTryDetermine.push(function () { axisCacheSet(labelsCache, optionLabelInterval, result); return true; }); } return result; } function makeCategoryTicks(axis, tickModel) { var ticksCache = ensureCategoryTickCache(axis); var optionTickInterval = getOptionCategoryInterval(tickModel); var result = axisCacheGet(ticksCache, optionTickInterval); if (result) { return result; } var ticks; var tickCategoryInterval; // Optimize for the case that large category data and no label displayed, // we should not return all ticks. if (!tickModel.get('show') || axis.scale.isBlank()) { ticks = []; } if (zrUtil.isFunction(optionTickInterval)) { ticks = makeTicksLabelsByCategoryIntervalNumOrCb(axis, optionTickInterval, true); } // Always use label interval by default despite label show. Consider this // scenario, Use multiple grid with the xAxis sync, and only one xAxis shows // labels. `splitLine` and `axisTick` should be consistent in this case. else if (optionTickInterval === 'auto') { var labelsResult = makeCategoryLabelsActually(axis, axis.getLabelModel(), createAxisLabelsComputingContext(AxisTickLabelComputingKind.determine)); tickCategoryInterval = labelsResult.labelCategoryInterval; ticks = zrUtil.map(labelsResult.labels, function (labelItem) { return labelItem.tick; }); } else { tickCategoryInterval = optionTickInterval; ticks = makeTicksLabelsByCategoryIntervalNumOrCb(axis, tickCategoryInterval, true); } // Cache to avoid calling interval function repeatedly. return axisCacheSet(ticksCache, optionTickInterval, { ticks: ticks, tickCategoryInterval: tickCategoryInterval }); } function makeRealNumberLabels(axis) { var ticks = axis.scale.getTicks(); var labelFormatter = makeLabelFormatter(axis); return { labels: zrUtil.map(ticks, function (tick, idx) { return { formattedLabel: labelFormatter(tick, idx), rawLabel: axis.scale.getLabel(tick), tick: tick }; }) }; } // Large category data calculation is performance sensitive, and ticks and label probably will // be fetched multiple times (e.g. shared by splitLine and axisTick). So we cache the result. // axis is created each time during a ec process, so we do not need to clear cache. var ensureCategoryTickCache = initAxisCacheMethod('axisTick'); var ensureCategoryLabelCache = initAxisCacheMethod('axisLabel'); /** * PENDING: refactor to JS Map? Because key can be a function or more complicated object, and * cache size always is small, and currently no JS Map object key polyfill, we use a simple * array cache instead of plain object hash. */ function initAxisCacheMethod(prop) { return function ensureCache(axis) { return axisInner(axis)[prop] || (axisInner(axis)[prop] = { list: [] }); }; } function axisCacheGet(cache, key) { for (var i = 0; i < cache.list.length; i++) { if (cache.list[i].key === key) { return cache.list[i].value; } } } function axisCacheSet(cache, key, value) { cache.list.push({ key: key, value: value }); return value; } function makeAutoCategoryInterval(axis, ctx) { if (ctx.kind === AxisTickLabelComputingKind.estimate) { // Currently axisTick is not involved in estimate kind, and the result likely varies during a // single pass of ec main process, due to the change of axisExtent. Therefore no cache is used. var result_2 = axis.calculateCategoryInterval(ctx); ctx.out.noPxChangeTryDetermine.push(function () { axisInner(axis).autoInterval = result_2; return true; }); return result_2; } // Both tick and label uses this result, cacah it to avoid recompute. var result = axisInner(axis).autoInterval; return result != null ? result : axisInner(axis).autoInterval = axis.calculateCategoryInterval(ctx); } /** * Calculate interval for category axis ticks and labels. * Use a strategy to try to avoid overlapping. * To get precise result, at least one of `getRotate` and `isHorizontal` * should be implemented in axis. */ export function calculateCategoryInterval(axis, ctx) { var kind = ctx.kind; var params = fetchAutoCategoryIntervalCalculationParams(axis); var labelFormatter = makeLabelFormatter(axis); var rotation = (params.axisRotate - params.labelRotate) / 180 * Math.PI; var ordinalScale = axis.scale; var ordinalExtent = ordinalScale.getExtent(); // Providing this method is for optimization: // avoid generating a long array by `getTicks` // in large category data case. var tickCount = ordinalScale.count(); if (ordinalExtent[1] - ordinalExtent[0] < 1) { return 0; } var step = 1; // Simple optimization. Arbitrary value. var maxCount = 40; if (tickCount > maxCount) { step = Math.max(1, Math.floor(tickCount / maxCount)); } var tickValue = ordinalExtent[0]; var unitSpan = axis.dataToCoord(tickValue + 1) - axis.dataToCoord(tickValue); var unitW = Math.abs(unitSpan * Math.cos(rotation)); var unitH = Math.abs(unitSpan * Math.sin(rotation)); var maxW = 0; var maxH = 0; // Caution: Performance sensitive for large category data. // Consider dataZoom, we should make appropriate step to avoid O(n) loop. for (; tickValue <= ordinalExtent[1]; tickValue += step) { var width = 0; var height = 0; // Not precise, do not consider align and vertical align // and each distance from axis line yet. var rect = textContain.getBoundingRect(labelFormatter({ value: tickValue }), params.font, 'center', 'top'); // Magic number width = rect.width * 1.3; height = rect.height * 1.3; // Min size, void long loop. maxW = Math.max(maxW, width, 7); maxH = Math.max(maxH, height, 7); } var dw = maxW / unitW; var dh = maxH / unitH; // 0/0 is NaN, 1/0 is Infinity. isNaN(dw) && (dw = Infinity); isNaN(dh) && (dh = Infinity); var interval = Math.max(0, Math.floor(Math.min(dw, dh))); if (kind === AxisTickLabelComputingKind.estimate) { // In estimate kind, the inteval likely varies, thus do not erase the cache. ctx.out.noPxChangeTryDetermine.push(zrUtil.bind(calculateCategoryIntervalTryDetermine, null, axis, interval, tickCount)); return interval; } var lastInterval = calculateCategoryIntervalDealCache(axis, interval, tickCount); return lastInterval != null ? lastInterval : interval; } function calculateCategoryIntervalTryDetermine(axis, interval, tickCount) { return calculateCategoryIntervalDealCache(axis, interval, tickCount) == null; } // Return the lastInterval if need to use it, otherwise return NullUndefined and save cache. function calculateCategoryIntervalDealCache(axis, interval, tickCount) { var cache = modelInner(axis.model); var axisExtent = axis.getExtent(); var lastAutoInterval = cache.lastAutoInterval; var lastTickCount = cache.lastTickCount; // Use cache to keep interval stable while moving zoom window, // otherwise the calculated interval might jitter when the zoom // window size is close to the interval-changing size. // For example, if all of the axis labels are `a, b, c, d, e, f, g`. // The jitter will cause that sometimes the displayed labels are // `a, d, g` (interval: 2) sometimes `a, c, e`(interval: 1). if (lastAutoInterval != null && lastTickCount != null && Math.abs(lastAutoInterval - interval) <= 1 && Math.abs(lastTickCount - tickCount) <= 1 // Always choose the bigger one, otherwise the critical // point is not the same when zooming in or zooming out. && lastAutoInterval > interval // If the axis change is caused by chart resize, the cache should not // be used. Otherwise some hidden labels might not be shown again. && cache.axisExtent0 === axisExtent[0] && cache.axisExtent1 === axisExtent[1]) { return lastAutoInterval; } // Only update cache if cache not used, otherwise the // changing of interval is too insensitive. else { cache.lastTickCount = tickCount; cache.lastAutoInterval = interval; cache.axisExtent0 = axisExtent[0]; cache.axisExtent1 = axisExtent[1]; } } function fetchAutoCategoryIntervalCalculationParams(axis) { var labelModel = axis.getLabelModel(); return { axisRotate: axis.getRotate ? axis.getRotate() : axis.isHorizontal && !axis.isHorizontal() ? 90 : 0, labelRotate: labelModel.get('rotate') || 0, font: labelModel.getFont() }; } function makeTicksLabelsByCategoryIntervalNumOrCb(axis, categoryInterval, onlyTick) { var labelFormatter = makeLabelFormatter(axis); var ordinalScale = axis.scale; var result = []; var categoryIntervalIsCb = zrUtil.isFunction(categoryInterval); ordinalScaleCreateTicks(ordinalScale, categoryIntervalIsCb ? 0 : categoryInterval, function (tickObj, isExtentBoundary) { var tickLabel = ordinalScale.getLabel(tickObj); if (categoryIntervalIsCb) { // When interval is function, a falsy return means ignore the tick. // It is time consuming for large category data. var isOnInterval = !!categoryInterval(tickObj.value, tickLabel); tickObj.offInterval = !isOnInterval; // axis extent min max labels should be always included and the display strategy // is adopted uniformly later in `AxisBuilder`. if (!isOnInterval && !isExtentBoundary) { return; } } result.push(onlyTick ? tickObj : { formattedLabel: labelFormatter(tickObj), rawLabel: tickLabel, tick: tickObj }); }); return result; }