UNPKG

echarts-nightly

Version:

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

436 lines (432 loc) • 16.8 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 } from '../util/model.js'; import { makeLabelFormatter, getOptionCategoryInterval, shouldShowAllLabels } from './axisHelper.js'; var modelInner = makeInner(); var axisInner = makeInner(); export var AxisTickLabelComputingKind = { estimate: 1, determine: 2 }; export function createAxisLabelsComputingContext(kind) { return { out: { noPxChangeTryDetermine: [] }, kind: kind }; } function tickValuesToNumbers(axis, values) { var nums = zrUtil.map(values, function (val) { return axis.scale.parse(val); }); if (axis.type === 'time' && nums.length > 0) { // Time axis needs duplicate first/last tick (see TimeScale.getTicks()) // The first and last tick/label don't get drawn nums.sort(); nums.unshift(nums[0]); nums.push(nums[nums.length - 1]); } return nums; } export function createAxisLabels(axis, ctx) { var custom = axis.getLabelModel().get('customValues'); if (custom) { var labelFormatter_1 = makeLabelFormatter(axis); var extent_1 = axis.scale.getExtent(); var tickNumbers = tickValuesToNumbers(axis, custom); var ticks_1 = zrUtil.filter(tickNumbers, function (val) { return val >= extent_1[0] && val <= extent_1[1]; }); return { labels: zrUtil.map(ticks_1, function (numval) { var tick = { value: numval }; var index = ticks_1.indexOf(numval); return { formattedLabel: labelFormatter_1(tick, index), rawLabel: axis.scale.getLabel(tick), tickValue: numval, time: undefined, "break": undefined }; }) }; } // Only ordinal scale support tick interval return axis.type === 'category' ? makeCategoryLabels(axis, ctx) : makeRealNumberLabels(axis); } /** * @param tickModel For example, can be axisTick, splitLine, splitArea. */ export function createAxisTicks(axis, tickModel, opt) { var custom = axis.getTickModel().get('customValues'); if (custom) { var extent_2 = axis.scale.getExtent(); var tickNumbers = tickValuesToNumbers(axis, custom); return { ticks: zrUtil.filter(tickNumbers, function (val) { return val >= extent_2[0] && val <= extent_2[1]; }) }; } // Only ordinal scale support tick interval return axis.type === 'category' ? makeCategoryTicks(axis, tickModel) : { ticks: zrUtil.map(axis.scale.getTicks(opt), function (tick) { return tick.value; }) }; } 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 = makeLabelsByCustomizedCategoryInterval(axis, optionLabelInterval); } else { numericLabelInterval = optionLabelInterval === 'auto' ? makeAutoCategoryInterval(axis, ctx) : optionLabelInterval; labels = makeLabelsByNumericCategoryInterval(axis, numericLabelInterval); } 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 = makeLabelsByCustomizedCategoryInterval(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.tickValue; }); } else { tickCategoryInterval = optionTickInterval; ticks = makeLabelsByNumericCategoryInterval(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), tickValue: tick.value, time: tick.time, "break": tick["break"] }; }) }; } // 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 stretegy 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 makeLabelsByNumericCategoryInterval(axis, categoryInterval, onlyTick) { var labelFormatter = makeLabelFormatter(axis); var ordinalScale = axis.scale; var ordinalExtent = ordinalScale.getExtent(); var labelModel = axis.getLabelModel(); var result = []; // TODO: axisType: ordinalTime, pick the tick from each month/day/year/... var step = Math.max((categoryInterval || 0) + 1, 1); var startTick = ordinalExtent[0]; var tickCount = ordinalScale.count(); // Calculate start tick based on zero if possible to keep label consistent // while zooming and moving while interval > 0. Otherwise the selection // of displayable ticks and symbols probably keep changing. // 3 is empirical value. if (startTick !== 0 && step > 1 && tickCount / step > 2) { startTick = Math.round(Math.ceil(startTick / step) * step); } // (1) Only add min max label here but leave overlap checking // to render stage, which also ensure the returned list // suitable for splitLine and splitArea rendering. // (2) Scales except category always contain min max label so // do not need to perform this process. var showAllLabel = shouldShowAllLabels(axis); var includeMinLabel = labelModel.get('showMinLabel') || showAllLabel; var includeMaxLabel = labelModel.get('showMaxLabel') || showAllLabel; if (includeMinLabel && startTick !== ordinalExtent[0]) { addItem(ordinalExtent[0]); } // Optimize: avoid generating large array by `ordinalScale.getTicks()`. var tickValue = startTick; for (; tickValue <= ordinalExtent[1]; tickValue += step) { addItem(tickValue); } if (includeMaxLabel && tickValue - step !== ordinalExtent[1]) { addItem(ordinalExtent[1]); } function addItem(tickValue) { var tickObj = { value: tickValue }; result.push(onlyTick ? tickValue : { formattedLabel: labelFormatter(tickObj), rawLabel: ordinalScale.getLabel(tickObj), tickValue: tickValue, time: undefined, "break": undefined }); } return result; } function makeLabelsByCustomizedCategoryInterval(axis, categoryInterval, onlyTick) { var ordinalScale = axis.scale; var labelFormatter = makeLabelFormatter(axis); var result = []; zrUtil.each(ordinalScale.getTicks(), function (tick) { var rawLabel = ordinalScale.getLabel(tick); var tickValue = tick.value; if (categoryInterval(tick.value, rawLabel)) { result.push(onlyTick ? tickValue : { formattedLabel: labelFormatter(tick), rawLabel: rawLabel, tickValue: tickValue, time: undefined, "break": undefined }); } }); return result; }