diff --git a/doc/doc-en.html b/doc/doc-en.html index 763ec64bf5e4b5c2acd920014f714a837f3a391b..bfb7ec644aa7b3faec01b9a32bee02be866cf651 100644 --- a/doc/doc-en.html +++ b/doc/doc-en.html @@ -2317,6 +2317,12 @@ require(['echarts'], function (ec){ value This option works when axis.type === 'log'. If set to false, negative value is supported. It is self-adapting by default, which means that if all of the data is negative, logPositive will be set to false, otherwise true. + + {number} logLabelBase + null + value + This option works when axis.type === 'log'. If specified, axisLabel is drawn as exponent style. For example, when logLabelBase = 4, axisLabel is drawn like 4², 4³. If not specified, axisLabel is drawn like 1,000,000 as usual. + {Object} axisLine varying @@ -3722,7 +3728,7 @@ indicator : [ {Object} itemStyle {} - see + see itemStyle @@ -3771,7 +3777,7 @@ indicator : [ {Object} itemStyle {} - see + see itemStyle diff --git a/doc/doc.html b/doc/doc.html index dd18077c8d2a5b42fd6b40420a9488e193e3b64b..ca9cebc02ef2afdb911f551f0e1c3fb4609f3641 100644 --- a/doc/doc.html +++ b/doc/doc.html @@ -2331,6 +2331,12 @@ require(['echarts'], function (ec){ 数值型,时间型 分割段数,不指定时根据min、max算法调整 + + {number} logLabelBase + null + value + axis.type === 'log'时生效。指定时,axisLabel显示为指数形式,如指定为4时,axisLabel可显示为4²、4³。不指定时,显示为普通形式,如 1,000,000 + {Object} logPositive null diff --git a/doc/example/line10.html b/doc/example/line10.html new file mode 100644 index 0000000000000000000000000000000000000000..e828f2031cec135840eac2b8e343bc54d66724f3 --- /dev/null +++ b/doc/example/line10.html @@ -0,0 +1,141 @@ + + + + + + + + + ECharts · Example + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+ + 切换主题 + +     + axisLabel普通形式显示 +     + 负值log数轴 + + +
+
+
+ +
+ + + + + + + + + + diff --git a/doc/example/line11.html b/doc/example/line11.html new file mode 100644 index 0000000000000000000000000000000000000000..c2bb8719f986c3f0ad04bc2a65ead44254c527bb --- /dev/null +++ b/doc/example/line11.html @@ -0,0 +1,133 @@ + + + + + + + + + ECharts · Example + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+ + 切换主题 + +     + axisLabel普通形式显示 +     + axisLabel指数形式显示 + + +
+
+
+ +
+ + + + + + + + + + diff --git a/doc/example/line9.html b/doc/example/line9.html index d0455055398cc12c76f28e0dce1d97b5f0c4e5da..3c0db409ad04431f4c32a03a981ddb8397a287a7 100644 --- a/doc/example/line9.html +++ b/doc/example/line9.html @@ -109,6 +109,10 @@ option = { 切换主题 +     + axisLabel指数形式显示 +     + 负值log数轴 diff --git a/src/component/valueAxis.js b/src/component/valueAxis.js index f75d41d0bec4454d7e90e8502a2d628fe2044c27..b0a6c5b0347f4876278f603b40c749c7b4997eef 100644 --- a/src/component/valueAxis.js +++ b/src/component/valueAxis.js @@ -514,13 +514,15 @@ define(function (require) { } // console.log(this._min,this._max,'vvvvv111111',this.option.type) + // log情况暂时禁用boundaryGap。 + var boundaryGap = this.option.type !== 'log' ? this.option.boundaryGap : [0, 0]; var gap = Math.abs(this._max - this._min); this._min = isNaN(this.option.min - 0) - ? (this._min - Math.abs(gap * this.option.boundaryGap[0])) + ? (this._min - Math.abs(gap * boundaryGap[0])) : (this.option.min - 0); // 指定min忽略boundaryGay[0] this._max = isNaN(this.option.max - 0) - ? (this._max + Math.abs(gap * this.option.boundaryGap[1])) + ? (this._max + Math.abs(gap * boundaryGap[1])) : (this.option.max - 0); // 指定max忽略boundaryGay[1] if (this._min === this._max) { if (this._max === 0) { @@ -750,7 +752,11 @@ define(function (require) { } this._valueList.push(ecDate.getNewDate(this._max)); - this._reformLabelData(formatter); + this._reformLabelData((function (formatterStr) { + return function (value) { + return ecDate.format(formatterStr, value); + }; + })(formatter)); }, _customerValue: function () { @@ -767,53 +773,52 @@ define(function (require) { _reformLogValue: function() { // log数轴本质就是缩放,相当于默认this.option.scale === true,所以不修正_min和_max到0。 - - var stepOpt = require('../util/smartLogSteps')({ + var thisOption = this.option; + var result = require('../util/smartLogSteps')({ dataMin: this._min, dataMax: this._max, - logPositive: this.option.logPositive, - splitNumber: this.option.splitNumber + logPositive: thisOption.logPositive, + logLabelBase: thisOption.logLabelBase, + splitNumber: thisOption.splitNumber }); - this._min = stepOpt.dataMin; - this._max = stepOpt.dataMax; - this._valueList = stepOpt.tickList; + this._min = result.dataMin; + this._max = result.dataMax; + this._valueList = result.tickList; // {value2Coord: {Function}, coord2Value: {Function}} - this._dataMappingMethods = stepOpt.dataMappingMethods; + this._dataMappingMethods = result.dataMappingMethods; - this._reformLabelData(); + this._reformLabelData(result.labelFormatter); }, - _reformLabelData: function (timeFormatter) { + _reformLabelData: function (innerFormatter) { this._valueLabel = []; var formatter = this.option.axisLabel.formatter; if (formatter) { for (var i = 0, l = this._valueList.length; i < l; i++) { if (typeof formatter === 'function') { this._valueLabel.push( - timeFormatter - ? formatter.call(this.myChart, this._valueList[i], timeFormatter) + innerFormatter + ? formatter.call(this.myChart, this._valueList[i], innerFormatter) : formatter.call(this.myChart, this._valueList[i]) ); } else if (typeof formatter === 'string') { this._valueLabel.push( - timeFormatter + innerFormatter ? ecDate.format(formatter, this._valueList[i]) : formatter.replace('{value}',this._valueList[i]) ); } } } - else if (timeFormatter) { - for (var i = 0, l = this._valueList.length; i < l; i++) { - this._valueLabel.push(ecDate.format(timeFormatter, this._valueList[i])); - } - } else { - // 每三位默认加,格式化 for (var i = 0, l = this._valueList.length; i < l; i++) { - this._valueLabel.push(this.numAddCommas(this._valueList[i])); + this._valueLabel.push( + innerFormatter + ? innerFormatter(this._valueList[i]) + : this.numAddCommas(this._valueList[i]) // 每三位默认加,格式化 + ); } } }, diff --git a/src/util/smartLogSteps.js b/src/util/smartLogSteps.js index 70577ce128e8b98016788152a23e8ca5ba332cb5..c875c9b814faec6ea555d056ade79926976df2de 100644 --- a/src/util/smartLogSteps.js +++ b/src/util/smartLogSteps.js @@ -8,7 +8,7 @@ define(function(require) { // Reference - var zrUtil = require('zrender/tool/util'); + var number = require('./number'); var Mt = Math; var mathLog = Mt.log; var mathPow = Mt.pow; @@ -26,20 +26,40 @@ define(function(require) { var EPSILON = 1e-9; var DEFAULT_SPLIT_NUMBER = 5; var MIN_BASE_10_SPLIT_NUMBER = 2; + var SUPERSCRIPTS = { + '0': '⁰', + '1': '¹', + '2': '²', + '3': '³', + '4': '⁴', + '5': '⁵', + '6': '⁶', + '7': '⁷', + '8': '⁸', + '9': '⁹', + '-': '⁻' + }; // Static variable var logPositive; + var logLabelBase; + var logLabelMode; // enumeration: + // 'plain' (i.e. axis labels are shown like 10000) + // 'exponent' (i.e. axis labels are shown like 10²) + var lnBase; var custOpts; var splitNumber; var logMappingOffset; var absMin; var absMax; - var dataMappingMethods; var tickList; /** * Test cases: * [2, 4, 8, 16, 32, 64, 128] + * [0.01, 0.1, 10, 100, 1000] logLabelBase: 3 + * [0.01, 0.1, 10, 100, 1000] logLabelBase: -12 + * [-2, -4, -8, -16, -32, -64, -128] logLabelBase: 3 * [2, 4, 8, 16, '-', 64, 128] * [2, 4, 8, 16, 32, 64] * [2, 4, 8, 16, 32] @@ -67,6 +87,8 @@ define(function(require) { * [-0.00001, -0.00001, -0.00001] * ['-', '-'] * ['-', 10] + * logarithmic axis in scatter (try dataZoom) + * logarithmic axis width dataZoom component (try xAxis and yAxis) */ /** @@ -77,6 +99,8 @@ define(function(require) { * @param {number} opts.dataMin data Minimum * @param {number} opts.dataMax data Maximum * @param {number=} opts.logPositive Logarithmic sign. If not specified, it will be auto-detected. + * @param {number=} opts.logLabelBase Logaithmic base in axis label. + * If not specified, it will be set to 10 (and use 2 for detail) * @param {number=} opts.splitNumber Number of sections perfered. * @return {Object} { * dataMin: New min, @@ -93,8 +117,6 @@ define(function(require) { reformSetting(); makeTicksList(); - dataMappingMethods = zrUtil.merge({}, makeDataMappingMethods(logPositive, logMappingOffset)); - return [ makeResult(), clearStaticVariables() @@ -105,8 +127,8 @@ define(function(require) { * All of static variables must be clear here. */ function clearStaticVariables() { - dataMappingMethods = logPositive = custOpts = logMappingOffset = - absMin = absMax = splitNumber = tickList = null; + logPositive = custOpts = logMappingOffset = lnBase = + absMin = absMax = splitNumber = tickList = logLabelBase = logLabelMode = null; } /** @@ -114,9 +136,27 @@ define(function(require) { * Reform min and max of data. */ function reformSetting() { + // Settings of log label base + logLabelBase = custOpts.logLabelBase; + if (logLabelBase == null) { + logLabelMode = 'plain'; + logLabelBase = 10; + lnBase = LN10; + } + else { + logLabelBase = +logLabelBase; + if (logLabelBase < 1) { // log base less than 1 is not supported. + logLabelBase = 10; + } + logLabelMode = 'exponent'; + lnBase = mathLog(logLabelBase); + } + + // Settings of split number splitNumber = custOpts.splitNumber; splitNumber == null && (splitNumber = DEFAULT_SPLIT_NUMBER); + // Setting of data min and max var dataMin = parseFloat(custOpts.dataMin); var dataMax = parseFloat(custOpts.dataMax); @@ -133,6 +173,7 @@ define(function(require) { dataMax = [dataMin, dataMin = dataMax][0]; // Exchange min, max. } + // Settings of log positive logPositive = custOpts.logPositive; // If not specified, determine sign by data. if (logPositive == null) { @@ -142,7 +183,7 @@ define(function(require) { logPositive = dataMax > 0 || dataMin === 0; } - // Set absMin and absMax, which must be greater than 0. + // Settings of absMin and absMax, which must be greater than 0. absMin = logPositive ? dataMin : -dataMax; absMax = logPositive ? dataMax : -dataMin; // FIXME @@ -160,72 +201,81 @@ define(function(require) { tickList = []; // Estimate max exponent and min exponent - var maxDataLog10 = fixAccurate(mathLog(absMax) / LN10); - var minDataLog10 = fixAccurate(mathLog(absMin) / LN10); - var maxExpon10 = mathCeil(maxDataLog10); - var minExpon10 = mathFloor(minDataLog10); - var spanExpon10 = maxExpon10 - minExpon10; - var spanDataLog10 = maxDataLog10 - minDataLog10; - - !( - spanExpon10 <= MIN_BASE_10_SPLIT_NUMBER - && splitNumber > MIN_BASE_10_SPLIT_NUMBER - ) - ? base10Analysis() : detailAnalysis(); - + var maxDataLog = fixAccurate(mathLog(absMax) / lnBase); + var minDataLog = fixAccurate(mathLog(absMin) / lnBase); + var maxExpon = mathCeil(maxDataLog); + var minExpon = mathFloor(minDataLog); + var spanExpon = maxExpon - minExpon; + var spanDataLog = maxDataLog - minDataLog; + + if (logLabelMode === 'exponent') { + baseAnalysis(); + } + else { // logLabelMode === 'plain', we will self-adapter + !( + spanExpon <= MIN_BASE_10_SPLIT_NUMBER + && splitNumber > MIN_BASE_10_SPLIT_NUMBER + ) + ? baseAnalysis() : detailAnalysis(); + } - // In this situation, only plot base-10 ticks. + // In this situation, only draw base-10 ticks. // Base-10 ticks: 10^h (i.e. 0.01, 0.1, 1, 10, 100, ...) - function base10Analysis() { - if (spanExpon10 < splitNumber) { - splitNumber = spanExpon10; + function baseAnalysis() { + if (spanExpon < splitNumber) { + splitNumber = spanExpon; } // Suppose: - // spanExpon10 > splitNumber - // stepExpon10 := floor(spanExpon10 / splitNumber) - // splitNumberFloat := spanExpon10 / stepExpon10 + // spanExpon > splitNumber + // stepExpon := floor(spanExpon / splitNumber) + // splitNumberFloat := spanExpon / stepExpon // There are tow expressions which are identically-true: // splitNumberFloat - splitNumber <= 1 - // stepExpon10 * ceil(splitNumberFloat) - spanExpon10 <= stepExpon10 + // stepExpon * ceil(splitNumberFloat) - spanExpon <= stepExpon // So we can calculate as follows: - var stepExpon10 = mathFloor(fixAccurate(spanExpon10 / splitNumber)); + var stepExpon = mathFloor(fixAccurate(spanExpon / splitNumber)); // Put the plot in the middle of the min, max. - var splitNumberAdjust = mathCeil(fixAccurate(spanExpon10 / stepExpon10)); - var spanExpon10Adjust = stepExpon10 * splitNumberAdjust; - var halfDiff10 = (spanExpon10Adjust - spanDataLog10) / 2; - var minExpon10Adjust = mathFloor(fixAccurate(minDataLog10 - halfDiff10)); + var splitNumberAdjust = mathCeil(fixAccurate(spanExpon / stepExpon)); + var spanExponAdjust = stepExpon * splitNumberAdjust; + var halfDiff = (spanExponAdjust - spanDataLog) / 2; + var minExponAdjust = mathFloor(fixAccurate(minDataLog - halfDiff)); + + if (aroundZero(minExponAdjust - minDataLog)) { + minExponAdjust -= 1; + } // Build logMapping offset - logMappingOffset = -minExpon10Adjust * LN10; + logMappingOffset = -minExponAdjust * lnBase; // Build tickList - for (var n = minExpon10Adjust; n - stepExpon10 < maxDataLog10; n += stepExpon10) { - tickList.push(mathPow(10, n)); + for (var n = minExponAdjust; n - stepExpon <= maxDataLog; n += stepExpon) { + tickList.push(mathPow(logLabelBase, n)); } } // In this situation, base-2|10 ticks are used to make detailed split. - // Base-2|10 ticks: 10^h * 2^k (i.e. 0.1, 0.2, 0.4, 0.8, 1, 2, 4, 8, 10, 20, 40, 80), + // Base-2|10 ticks: 10^h * 2^k (i.e. 0.1, 0.2, 0.4, 1, 2, 4, 10, 20, 40), // where k in [0, 1, 2]. // Because LN2 * 3 < LN10 and LN2 * 4 > LN10, k should be less than 3. - // And when k === 3, the tick is too close to that of k === 0, which looks weird. So we dont use 3. + // And when k === 3, the tick is too close to that of k === 0, which looks weird. + // So we do not use 3. function detailAnalysis() { // Find max exponent and min exponent. // Calculate base on 3-hexadecimal (0, 1, 2, 10, 11, 12, 20). - var minDecimal = toDecimalFrom4Hex(minExpon10, 0); + var minDecimal = toDecimalFrom4Hex(minExpon, 0); var endDecimal = minDecimal + 2; while ( minDecimal < endDecimal - && toH(minDecimal + 1) + toK(minDecimal + 1) * LN2D10 < minDataLog10 + && toH(minDecimal + 1) + toK(minDecimal + 1) * LN2D10 < minDataLog ) { minDecimal++; } - var maxDecimal = toDecimalFrom4Hex(maxExpon10, 0); + var maxDecimal = toDecimalFrom4Hex(maxExpon, 0); var endDecimal = maxDecimal - 2; // maxDecimal is greater than 4 while ( maxDecimal > endDecimal - && toH(maxDecimal - 1) + toK(maxDecimal - 1) * LN2D10 > maxDataLog10 + && toH(maxDecimal - 1) + toK(maxDecimal - 1) * LN2D10 > maxDataLog ) { maxDecimal--; } @@ -238,8 +288,6 @@ define(function(require) { var h = toH(i); var k = toK(i); tickList.push(mathPow(10, h) * mathPow(2, k)); - // FIXME - // 小数的显示 } } @@ -266,11 +314,13 @@ define(function(require) { function makeResult() { var resultTickList = []; for (var i = 0, len = tickList.length; i < len; i++) { - resultTickList[i] = formatNumber((logPositive ? 1 : -1) * tickList[i]); + resultTickList[i] = (logPositive ? 1 : -1) * tickList[i]; } !logPositive && resultTickList.reverse(); + var dataMappingMethods = makeDataMappingMethods(); var value2Coord = dataMappingMethods.value2Coord; + var newDataMin = value2Coord(resultTickList[0]); var newDataMax = value2Coord(resultTickList[resultTickList.length - 1]); @@ -280,21 +330,54 @@ define(function(require) { } return { - // FIXME - // tickList.length 为0的情况 dataMin: newDataMin, dataMax: newDataMax, tickList: resultTickList, logPositive: logPositive, - dataMappingMethods: zrUtil.merge({}, dataMappingMethods) + labelFormatter: makeLabelFormatter(), + dataMappingMethods: dataMappingMethods }; } + /** + * Make axis label formatter. + */ + function makeLabelFormatter() { + if (logLabelMode === 'exponent') { // For label style like 3⁴. + // Static variables should be fixed in the scope of the methods. + var myLogLabelBase = logLabelBase; + var myLnBase = lnBase; + + return function (value) { + if (!isFinite(parseFloat(value))) { + return ''; + } + var sign = ''; + if (value < 0) { + value = -value; + sign = '-'; + } + return sign + myLogLabelBase + makeSuperscriptExponent(mathLog(value) / myLnBase); + }; + } + else { + return function (value) { // Normal style like 0.001, 10,000,0 + if (!isFinite(parseFloat(value))) { + return ''; + } + return number.addCommas(formatNumber(value)); + }; + } + } + /** * Make calculate methods. - * logPositive and logMappingOffset should be fixed in the scope of the methods. */ - function makeDataMappingMethods(logPositive, logMappingOffset) { + function makeDataMappingMethods() { + // Static variables should be fixed in the scope of the methods. + var myLogPositive = logPositive; + var myLogMappingOffset = logMappingOffset; + return { value2Coord: function (x) { if (x == null || isNaN(x) || !isFinite(x)) { @@ -304,16 +387,16 @@ define(function(require) { if (!isFinite(x)) { x = EPSILON; } - else if (logPositive && x < EPSILON) { + else if (myLogPositive && x < EPSILON) { // FIXME // It is suppose to be ignore, but not be set to EPSILON. See comments above. x = EPSILON; } - else if (!logPositive && x > -EPSILON) { + else if (!myLogPositive && x > -EPSILON) { x = -EPSILON; } x = mathAbs(x); - return (logPositive ? 1 : -1) * (mathLog(x) + logMappingOffset); + return (myLogPositive ? 1 : -1) * (mathLog(x) + myLogMappingOffset); }, coord2Value: function (x) { if (x == null || isNaN(x) || !isFinite(x)) { @@ -323,9 +406,9 @@ define(function(require) { if (!isFinite(x)) { x = EPSILON; } - return logPositive - ? mathPow(LOG_BASE, x - logMappingOffset) - : -mathPow(LOG_BASE, -x + logMappingOffset); + return myLogPositive + ? mathPow(LOG_BASE, x - myLogMappingOffset) + : -mathPow(LOG_BASE, -x + myLogMappingOffset); } }; } @@ -344,11 +427,29 @@ define(function(require) { * @return {string} */ function formatNumber(num) { - // FIXME - // I think we should not do this here, but in valueAxis.js - // So refector is desired. return Number(num).toFixed(15).replace(/\.?0*$/, ''); } + /** + * Make superscript exponent + */ + function makeSuperscriptExponent(exponent) { + exponent = formatNumber(Math.round(exponent)); // Do not support float superscript. + // (because I can not find superscript style of '.') + var result = []; + for (var i = 0, len = exponent.length; i < len; i++) { + var cha = exponent.charAt(i); + result.push(SUPERSCRIPTS[cha] || ''); + } + return result.join(''); + } + + /** + * Decide whether near zero + */ + function aroundZero(val) { + return val > -EPSILON && val < EPSILON; + } + return smartLogSteps; });