This is an automated email from the ASF dual-hosted git repository. sushuang pushed a commit to branch gh-pages in repository https://gitbox.apache.org/repos/asf/echarts-examples.git
commit d6249eb75f57c0a0cb8279fa5d468421e1521132 Author: 100pah <[email protected]> AuthorDate: Tue Aug 12 06:29:20 2025 +0800 Add axis break examples. --- public/examples/ts/bar-breaks-brush.ts | 233 +++++++++++++++++++ public/examples/ts/bar-breaks-simple.ts | 160 +++++++++++++ ...aday-stock-breaks-1.ts => intraday-breaks-1.ts} | 35 ++- ...aday-stock-breaks-2.ts => intraday-breaks-2.ts} | 9 +- public/examples/ts/line-fisheye-lens.ts | 250 +++++++++++++++++++++ public/examples/ts/matrix-grid-layout.js | 2 +- public/examples/ts/matrix-sparkline.js | 8 +- 7 files changed, 681 insertions(+), 16 deletions(-) diff --git a/public/examples/ts/bar-breaks-brush.ts b/public/examples/ts/bar-breaks-brush.ts new file mode 100644 index 00000000..f01502ee --- /dev/null +++ b/public/examples/ts/bar-breaks-brush.ts @@ -0,0 +1,233 @@ +/* +title: Bar Chart with Axis Breaks (Brush-enabled) +titleCN: 断轴上的柱状图(可刷选) +category: bar +difficulty: 8 +*/ + +var GRID_TOP = 120; +var GRID_BOTTOM = 80; +var Y_DATA_ROUND_PRECISION = 0; + +type AxisBreakItem = NonNullable<echarts.YAXisComponentOption['breaks']>[number]; + +var _currentAxisBreaks: AxisBreakItem[] = [{ + start: 5000, + end: 100000, + gap: '2%' +}]; + +option = { + title: { + text: 'Bar Chart with Axis Break (Brush-enabled)', + subtext: 'Brush to create a new axis break\n(Click on the break area to reset)', + left: 'center', + textStyle: { + fontSize: 20 + }, + subtextStyle: { + color: '#175ce5', + fontSize: 15, + fontWeight: 'bold' + } + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' + } + }, + legend: {}, + grid: { + top: GRID_TOP, + bottom: GRID_BOTTOM + }, + xAxis: [{ + type: 'category', + data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + }], + yAxis: [{ + type: 'value', + breaks: _currentAxisBreaks, + breakArea: { + itemStyle: { + opacity: 1 + }, + zigzagMaxSpan: 15, + zigzagAmplitude: 2, + zigzagZ: 200, + } + }], + series: [ + { + name: 'Data A', + type: 'bar', + emphasis: { + focus: 'series' + }, + data: [1500, 2032, 2001, 3154, 2190, 4330, 2410] + }, + { + name: 'Data B', + type: 'bar', + emphasis: { + focus: 'series' + }, + data: [1200, 1320, 1010, 1340, 900, 2300, 2100] + }, + { + name: 'Data C', + type: 'bar', + emphasis: { + focus: 'series' + }, + data: [103200, 100320, 103010, 102340, 103900, 103300, 103200] + }, + { + name: 'Data D', + type: 'bar', + data: [106212, 102118, 102643, 104631, 106679, 100130, 107022], + emphasis: { + focus: 'series' + }, + } + ] +}; + +/** + * This is some interaction logic with axis break: + * - Brush to create a axis break. + * + * You can ignore this part if you do not need it. + */ +function initAxisBreakInteraction() { + + var _brushingEl: echarts.graphic.Rect | null = null; + + myChart.getZr().on('mousedown', function (params) { + _brushingEl = new echarts.graphic.Rect({ + shape: {x: 0, y: params.offsetY}, + style: {stroke: 'none', fill: '#ccc'}, + ignore: true + }); + myChart.getZr().add(_brushingEl); + }); + + myChart.getZr().on('mousemove', function (params) { + if (!_brushingEl) { + return; + } + + var initY = _brushingEl.shape.y; + var currPoint = [params.offsetX, params.offsetY]; + _brushingEl.setShape('width', myChart.getWidth()); + _brushingEl.setShape('height', currPoint[1] - initY); + _brushingEl.ignore = false; + }); + + document.addEventListener('mouseup', function (params) { + if (!_brushingEl) { + return; + } + + var initX = _brushingEl.shape.x; + var initY = _brushingEl.shape.y; + var currPoint = [params.offsetX, params.offsetY]; + var pixelSpan = Math.abs(currPoint[1] - initY); + if (pixelSpan > 2) { + updateAxisBreak(myChart, [initX, initY], currPoint); + } + + myChart.getZr().remove(_brushingEl); + _brushingEl = null; + }); + + myChart.on('axisbreakchanged', function (params) { + // Remove expanded axis breaks from _currentAxisBreaks. + var changedBreaks = (params as echarts.AxisBreakChangedEvent).breaks || []; + for (var i = 0; i < changedBreaks.length; i++) { + var changedBreakItem = changedBreaks[i]; + if (changedBreakItem.isExpanded) { + for (var j = _currentAxisBreaks.length - 1; j >= 0; j--) { + if (_currentAxisBreaks[j].start === changedBreakItem.start + && _currentAxisBreaks[j].end === changedBreakItem.end + ) { + _currentAxisBreaks.splice(j, 1); + } + } + } + } + }); + + function updateAxisBreak( + myChart: echarts.ECharts, + initXY: number[], + currPoint: number[] + ): void { + + var dataXY0 = myChart.convertFromPixel({gridIndex: 0}, initXY); + var dataXY1 = myChart.convertFromPixel({gridIndex: 0}, currPoint); + var dataRange = [roundYValue(dataXY0[1]), roundYValue(dataXY1[1])]; + if (dataRange[0] > dataRange[1]) { + dataRange.reverse(); + } + + var newBreak: AxisBreakItem = { + start: dataRange[0], + end: dataRange[1] + }; + + insertAndMergeNewBreakWithExistingBreaks(newBreak); + + var gapPercentStr = ( + Math.abs( + myChart.convertToPixel({yAxisIndex: 0}, newBreak.start) + - myChart.convertToPixel({yAxisIndex: 0}, newBreak.end) + ) / getYAxisPixelSpan(myChart) * 100 + ) + '%'; + + function makeOption(gapPercentStr: string): echarts.EChartsOption { + newBreak.gap = gapPercentStr; + return { + yAxis: { + breaks: _currentAxisBreaks + } + }; + } + + // This is to make a transition animation effect - firstly create axis break + // on the brushed area, then collapse it to a small gap. + myChart.setOption(makeOption(gapPercentStr)); + setTimeout(() => { + myChart.setOption(makeOption('2%')); + }, 0); + } + + // Insert and merge new break with existing breaks if intersecting. + function insertAndMergeNewBreakWithExistingBreaks(newBreak: AxisBreakItem) { + for (var i = _currentAxisBreaks.length - 1; i >= 0; i--) { + var existingBreak = _currentAxisBreaks[i]; + + if (existingBreak.start <= newBreak.end && existingBreak.end >= newBreak.start) { + newBreak.start = Math.min(existingBreak.start as number, newBreak.start as number); + newBreak.end = Math.max(existingBreak.end as number, newBreak.end as number); + _currentAxisBreaks.splice(i, 1); // Remove the existing break. + } + } + _currentAxisBreaks.push(newBreak); + } + + function getYAxisPixelSpan(myChart: echarts.ECharts): number { + return myChart.getHeight() - GRID_BOTTOM - GRID_TOP; + } + + function roundYValue(val: number): number { + return +(+val).toFixed(Y_DATA_ROUND_PRECISION); + } + +} // End of initAxisBreakInteraction + +setTimeout(initAxisBreakInteraction, 0); + +export {}; + diff --git a/public/examples/ts/bar-breaks-simple.ts b/public/examples/ts/bar-breaks-simple.ts new file mode 100644 index 00000000..5204efd6 --- /dev/null +++ b/public/examples/ts/bar-breaks-simple.ts @@ -0,0 +1,160 @@ +/* +title: Bar Chart with Axis Breaks +titleCN: 断轴上的柱状图 +category: bar +difficulty: 3 +*/ + +var _currentAxisBreaks = [{ + start: 5000, + end: 100000, + gap: '1.5%' +}, { + // `start` and `end` are also used as the identifier for a certain axis break. + start: 105000, + end: 3100000, + gap: '1.5%' +}]; + + +option = { + title: { + text: 'Bar Chart with Axis Breaks', + subtext: 'Click the break area to expand it', + left: 'center', + textStyle: { + fontSize: 20 + }, + subtextStyle: { + color: '#175ce5', + fontSize: 15, + fontWeight: 'bold' + } + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' + } + }, + legend: {}, + grid: { + top: 120, + }, + xAxis: [{ + type: 'category', + data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + }], + yAxis: [{ + type: 'value', + breaks: _currentAxisBreaks, + breakArea: { + itemStyle: { + opacity: 1 + }, + zigzagZ: 200, + } + }], + series: [ + { + name: 'Data A', + type: 'bar', + emphasis: { + focus: 'series' + }, + data: [1500, 2032, 2001, 3154, 2190, 4330, 2410] + }, + { + name: 'Data B', + type: 'bar', + emphasis: { + focus: 'series' + }, + data: [1200, 1320, 1010, 1340, 900, 2300, 2100] + }, + { + name: 'Data C', + type: 'bar', + emphasis: { + focus: 'series' + }, + data: [103200, 100320, 103010, 102340, 103900, 103300, 103200] + }, + { + name: 'Data D', + type: 'bar', + data: [3106212, 3102118, 3102643, 3104631, 3106679, 3100130, 3107022], + emphasis: { + focus: 'series' + }, + } + ] +}; + +/** + * This is some interaction logic with axis break: + * - Click to expand and reset button. + * + * You can ignore this part if you do not need it. + */ +function initAxisBreakInteraction() { + + myChart.on('axisbreakchanged', function (params) { + updateCollapseButton(params as echarts.AxisBreakChangedEvent); + }); + + myChart.on('click', function (params) { + if (params.name === 'collapseAxisBreakBtn') { + collapseAxisBreak(); + } + }); + + function updateCollapseButton(params: echarts.AxisBreakChangedEvent) { + // If there is any axis break expanded, we need to show the collapse button. + var needReset = false; + for (let i = 0; i < params.breaks.length; i++) { + const changedBreakItem = params.breaks[i]; + if (changedBreakItem.isExpanded) { + needReset = true; + break; + } + } + myChart.setOption({ + // Draw the collapse button. + graphic: [{ + elements: [{ + type: 'rect', + ignore: !needReset, + name: 'collapseAxisBreakBtn', + top: 5, + left: 5, + shape: {r: 3, width: 140, height: 24}, + style: {fill: '#eee', stroke: '#999', lineWidth: 1}, + textContent: { + type: 'text', + style: { + text: 'Collapse Axis Breaks', + fontSize: 13, + fontWeight: 'bold' + } + }, + textConfig: {position: 'inside'} + }] + }] + }); + } + + function collapseAxisBreak() { + myChart.dispatchAction({ + type: 'collapseAxisBreak', + yAxisIndex: 0, + breaks: _currentAxisBreaks + }); + } + +} // End of initAxisBreakInteraction + +setTimeout(initAxisBreakInteraction, 0); + +export {}; + diff --git a/public/examples/ts/intraday-stock-breaks-1.ts b/public/examples/ts/intraday-breaks-1.ts similarity index 80% rename from public/examples/ts/intraday-stock-breaks-1.ts rename to public/examples/ts/intraday-breaks-1.ts index a43dc6f7..92ccc104 100644 --- a/public/examples/ts/intraday-stock-breaks-1.ts +++ b/public/examples/ts/intraday-breaks-1.ts @@ -1,6 +1,6 @@ /* -title: Intraday Stock Chart with Breaks -titleCN: 盘中股票走势图 +title: Intraday Chart with Breaks +titleCN: 日内走势图 category: candlestick, line difficulty: 4 since: 6.0.0 @@ -14,14 +14,18 @@ var DATA_ZOOM_MIN_VALUE_SPAN = 3600 * 1000; var _data = generateData(); option = { + // Choose axis ticks based on UTC time. useUTC: true, title: { - text: 'Intraday Stock Chart with Breaks', + text: 'Intraday Chart with Breaks', left: 'center' }, tooltip: { show: true, - trigger: 'axis' + trigger: 'axis', + axisPointer: { + type: 'cross' + } }, grid: { top: '25%', @@ -36,9 +40,15 @@ option = { showMaxLabel: true, formatter(timestamp, _, opt) { if (opt.break) { - return formatTime(timestamp, '{HH}:{mm}\n{dd}d', true); + // The third parameter is `useUTC: true`. + return formatTime(timestamp, '{HH}:{mm}\n{weak|{dd}d}', true); } return formatTime(timestamp, '{HH}:{mm}', true); + }, + rich: { + weak: { + color: '#999' + } } }, breaks: _data.breaks, @@ -47,14 +57,15 @@ option = { zigzagAmplitude: 0, zigzagZ: 200, itemStyle: { - opacity: 1, + borderColor: 'none', + opacity: 0 } } } ], yAxis: { type: 'value', - min: 'dataMin' + min: 'dataMin', }, dataZoom: [ { @@ -93,7 +104,7 @@ function generateData() { roundTime(time, 'day', true); todayCloseTime.setTime(time.getTime()); time.setUTCHours(9, 30); // Open time - todayCloseTime.setUTCHours(15, 0); // Close time + todayCloseTime.setUTCHours(16, 0); // Close time } var valBreak = false; @@ -101,8 +112,8 @@ function generateData() { var delta; if (valBreak) { delta = - Math.floor((Math.random() - 0.5 * Math.sin(val / 1000)) * 20 * 5000) / - 100; + Math.floor((Math.random() - 0.5 * Math.sin(val / 1000)) * 20 * 100) / + 10; valBreak = false; } else { delta = @@ -116,6 +127,10 @@ function generateData() { time.setMinutes(time.getMinutes() + 1); if (time.getTime() > todayCloseTime.getTime()) { + + // Use `NaN` to break the line. + seriesData.push([time.getTime(), NaN]); + var breakStart = todayCloseTime.getTime(); time.setUTCDate(time.getUTCDate() + 1); updateDayTime(time, todayCloseTime); diff --git a/public/examples/ts/intraday-stock-breaks-2.ts b/public/examples/ts/intraday-breaks-2.ts similarity index 91% rename from public/examples/ts/intraday-stock-breaks-2.ts rename to public/examples/ts/intraday-breaks-2.ts index 8f45d7ae..335dd0f5 100644 --- a/public/examples/ts/intraday-stock-breaks-2.ts +++ b/public/examples/ts/intraday-breaks-2.ts @@ -1,6 +1,6 @@ /* -title: Intraday Stock Chart with Breaks (II) -titleCN: 盘中股票走势图 (II) +title: Intraday Chart with Breaks (II) +titleCN: 日内走势图 (II) category: candlestick, line difficulty: 4 noExplore: true @@ -12,8 +12,10 @@ var formatTime = echarts.time.format; var _data = generateData1(); option = { + // Choose axis ticks based on UTC time. + useUTC: true, title: { - text: 'Intraday Stock Chart with Breaks (II)', + text: 'Intraday Chart with Breaks', left: 'center' }, tooltip: { @@ -29,6 +31,7 @@ option = { showMaxLabel: true, formatter: (value, index, extra) => { if (!extra || !extra.break) { + // The third parameter is `useUTC: true`. return formatTime(value, '{HH}:{mm}', true); } // Only render the label on break start, but not on break end. diff --git a/public/examples/ts/line-fisheye-lens.ts b/public/examples/ts/line-fisheye-lens.ts new file mode 100644 index 00000000..c49a19d6 --- /dev/null +++ b/public/examples/ts/line-fisheye-lens.ts @@ -0,0 +1,250 @@ +/* +title: Fisheye Lens on Line Chart +titleCN: 折线图鱼眼放大 +category: line +difficulty: 8 +*/ + +var GRID_TOP = 120; +var GRID_BOTTOM = 80; +var GRID_LEFT = 60; +var GRID_RIGHT = 60; +var Y_DATA_ROUND_PRECISION = 0; + +var _breakAreaStyle = { + expandOnClick: false, + zigzagZ: 200, + zigzagAmplitude: 0, + itemStyle: { + borderColor: '#777', + opacity: 0 + } +}; + +option = { + title: { + text: 'Fisheye Lens on Line Chart', + subtext: 'Brush to magnify the details', + left: 'center', + textStyle: { + fontSize: 20 + }, + subtextStyle: { + color: '#175ce5', + fontSize: 15, + fontWeight: 'bold' + } + }, + tooltip: { + trigger: 'axis', + }, + legend: {}, + grid: { + top: GRID_TOP, + bottom: GRID_BOTTOM, + left: GRID_LEFT, + right: GRID_RIGHT + }, + xAxis: [{ + splitLine: { + show: false + }, + breakArea: _breakAreaStyle + }], + yAxis: [{ + axisTick: { + show: true + }, + breakArea: _breakAreaStyle + }], + series: [{ + type: 'line', + name: 'Data A', + symbol: 'circle', + showSymbol: false, + symbolSize: 5, + data: generateSeriesData() + }] +}; + +/** + * This is some interaction logic with axis break: + * - Brush to fisheye-magnify an area. + * + * You can ignore this part if you do not need it. + */ +function initAxisBreakInteraction() { + + var _brushingEl: echarts.graphic.Rect | null = null; + + myChart.on('click', function (params) { + if (params.name === 'clearAxisBreakBtn') { + var option = { + xAxis: {breaks: []}, + yAxis: {breaks: []} + }; + addClearButtonUpdateOption(option, false); + myChart.setOption(option); + } + }); + + function addClearButtonUpdateOption(option: echarts.EChartsOption, show: boolean) { + option.graphic = [{ + elements: [{ + type: 'rect', + ignore: !show, + name: 'clearAxisBreakBtn', + top: 5, + left: 5, + shape: {r: 3, width: 70, height: 30}, + style: {fill: '#eee', stroke: '#999', lineWidth: 1}, + textContent: { + type: 'text', + style: { + text: 'Reset', + fontSize: 15, + fontWeight: 'bold' + } + }, + textConfig: {position: 'inside'} + }] + }]; + } + + myChart.getZr().on('mousedown', function (params) { + _brushingEl = new echarts.graphic.Rect({ + shape: {x: params.offsetX, y: params.offsetY}, + style: {stroke: 'none', fill: '#ccc'}, + ignore: true + }); + myChart.getZr().add(_brushingEl); + }); + + myChart.getZr().on('mousemove', function (params) { + if (!_brushingEl) { + return; + } + var initX = _brushingEl.shape.x; + var initY = _brushingEl.shape.y; + var currPoint = [params.offsetX, params.offsetY]; + _brushingEl.setShape('width', currPoint[0] - initX); + _brushingEl.setShape('height', currPoint[1] - initY); + _brushingEl.ignore = false; + }); + + document.addEventListener('mouseup', function (params) { + if (!_brushingEl) { + return; + } + + var initX = _brushingEl.shape.x; + var initY = _brushingEl.shape.y; + var currPoint = [params.offsetX, params.offsetY]; + var xPixelSpan = Math.abs(currPoint[0] - initX); + var yPixelSpan = Math.abs(currPoint[1] - initY); + if (xPixelSpan > 2 && yPixelSpan > 2) { + updateAxisBreak(myChart, [initX, initY], currPoint, xPixelSpan, yPixelSpan); + } + + myChart.getZr().remove(_brushingEl); + _brushingEl = null; + }); + + function updateAxisBreak( + myChart: echarts.ECharts, + initXY: number[], + currPoint: number[], + xPixelSpan: number, + yPixelSpan: number + ): void { + + var dataXY0 = myChart.convertFromPixel({gridIndex: 0}, initXY); + var dataXY1 = myChart.convertFromPixel({gridIndex: 0}, currPoint); + + function makeDataRange(v0: number, v1: number): number[] { + var dataRange = [roundXYValue(v0), roundXYValue(v1)]; + if (dataRange[0] > dataRange[1]) { + dataRange.reverse(); + } + return dataRange; + } + + var xDataRange = makeDataRange(dataXY0[0], dataXY1[0]); + var yDataRange = makeDataRange(dataXY0[1], dataXY1[1]); + + var xySpan = getXYAxisPixelSpan(myChart); + var xGapPercentStr = (xPixelSpan / xySpan[0] * 100) + '%'; + var yGapPercentStr = (yPixelSpan / xySpan[1] * 100) + '%'; + + function makeOption(xGapPercentStr: string, yGapPercentStr: string): echarts.EChartsOption { + return { + xAxis: { + breaks: [{ + start: xDataRange[0], + end: xDataRange[1], + gap: xGapPercentStr, + }] + }, + yAxis: { + breaks: [{ + start: yDataRange[0], + end: yDataRange[1], + gap: yGapPercentStr, + }] + } + }; + } + + // This is to make a transition animation effect - firstly create axis break + // on the brushed area, then collapse it to a small gap. + myChart.setOption(makeOption(xGapPercentStr, yGapPercentStr)); + setTimeout(() => { + var option = makeOption('80%', '80%'); + addClearButtonUpdateOption(option, true); + myChart.setOption(option); + }, 0); + } + + function getXYAxisPixelSpan(myChart: echarts.ECharts): number[] { + return [ + myChart.getWidth() - GRID_LEFT - GRID_RIGHT, + myChart.getHeight() - GRID_BOTTOM - GRID_TOP + ]; + } + + +} // End of initAxisBreakInteraction + +function roundXYValue(val: number): number { + return +(+val).toFixed(Y_DATA_ROUND_PRECISION); +} + +function generateSeriesData() { + function makeRandom(lastYVal: number, range: number[], factor: number): number { + lastYVal = lastYVal - range[0]; + var delta = (Math.random() - 0.5 * Math.sin(lastYVal / factor)) * (range[1] - range[0]) * 0.8; + return roundXYValue(lastYVal + delta + range[0]); + } + var seriesData: [number, number][] = []; + var DATA_COUNT = 1000; + var reset1 = true; + var reset2 = true; + let yVal = 0; + for (var idx = 0; idx < DATA_COUNT; idx++) { + if (idx < DATA_COUNT / 4) { + yVal = makeRandom(yVal, [100, 10000], 50000); + } else if (idx < (2 * DATA_COUNT) / 3) { + if (reset1) {yVal = 110010; reset1 = false;} + yVal = makeRandom(yVal, [100000, 105000], 50000); + } else { + if (reset2) {yVal = 300100; reset2 = false;} + yVal = makeRandom(yVal, [300000, 305000], 20000); + } + seriesData.push([idx, yVal]); + } + return seriesData; +} + +setTimeout(initAxisBreakInteraction, 0); + +export {}; diff --git a/public/examples/ts/matrix-grid-layout.js b/public/examples/ts/matrix-grid-layout.js index bf6bd64e..6d2aa05f 100644 --- a/public/examples/ts/matrix-grid-layout.js +++ b/public/examples/ts/matrix-grid-layout.js @@ -58,7 +58,7 @@ const _sectionDefinitionMap = { option: { title: [{ coordinateSystem: 'matrix', - text: 'Resize the Canvas to Check the Responsiveness.', + text: 'Resize the Canvas to Check the Responsiveness', left: 'center', top: 10, }], diff --git a/public/examples/ts/matrix-sparkline.js b/public/examples/ts/matrix-sparkline.js index d4ac5ef1..4cfc9807 100644 --- a/public/examples/ts/matrix-sparkline.js +++ b/public/examples/ts/matrix-sparkline.js @@ -71,7 +71,7 @@ option = { tooltip: { trigger: 'axis' }, - dataZoom: { + dataZoom: [{ type: 'slider', xAxisIndex: 'all', left: '10%', @@ -79,7 +79,11 @@ option = { bottom: 30, height: 30, throttle: 120 - }, + }, { + type: 'inside', + xAxisIndex: 'all', + throttle: 120 + }], grid: [], xAxis: [], yAxis: [], --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
