You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by ov...@apache.org on 2021/06/18 08:17:02 UTC
[echarts] 01/01: feat(label): sector label position #14616
This is an automated email from the ASF dual-hosted git repository.
ovilia pushed a commit to branch feat-sector-label
in repository https://gitbox.apache.org/repos/asf/echarts.git
commit d15dcf67abc496afa29630d0e2b245b6c752b0f9
Author: Ovilia <zw...@gmail.com>
AuthorDate: Fri Jun 18 16:15:48 2021 +0800
feat(label): sector label position #14616
---
src/chart/bar/BarView.ts | 9 +-
src/chart/gauge/GaugeView.ts | 2 +-
src/label/calculateTextPosition.ts | 200 +++++++++++++++++++++++++++++++++++++
src/util/shape/sausage.ts | 6 +-
test/bar-polar-label.html | 126 +++++++++++++++++++++++
5 files changed, 335 insertions(+), 8 deletions(-)
diff --git a/src/chart/bar/BarView.ts b/src/chart/bar/BarView.ts
index 470c6f6..39bbf10 100644
--- a/src/chart/bar/BarView.ts
+++ b/src/chart/bar/BarView.ts
@@ -32,7 +32,7 @@ import { enableHoverEmphasis, setStatesStylesFromModel } from '../../util/states
import { setLabelStyle, getLabelStatesModels, setLabelValueAnimation } from '../../label/labelStyle';
import {throttle} from '../../util/throttle';
import {createClipPath} from '../helper/createClipPathFromCoordSys';
-import Sausage from '../../util/shape/sausage';
+import {SausageShape, Sausage} from '../../util/shape/sausage';
import ChartView from '../../view/Chart';
import List, {DefaultDataVisual} from '../../data/List';
import GlobalModel from '../../model/Global';
@@ -60,6 +60,7 @@ import CartesianAxisModel from '../../coord/cartesian/AxisModel';
import {LayoutRect} from '../../util/layout';
import {EventCallback} from 'zrender/src/core/Eventful';
import { warn } from '../../util/log';
+import {createSectorCalculateTextPosition} from '../../label/calculateTextPosition';
const _eventPos = [0, 0];
@@ -761,6 +762,8 @@ const elementCreator: {
sector.name = 'item';
+ sector.calculateTextPosition = createSectorCalculateTextPosition();
+
// Animation
if (animationModel) {
const sectorShape = sector.shape;
@@ -911,7 +914,7 @@ function updateStyle(
const cursorStyle = itemModel.getShallow('cursor');
cursorStyle && (el as Path).attr('cursor', cursorStyle);
- if (!isPolar) {
+ // if (!isPolar) {
const labelPositionOutside = isHorizontal
? ((layout as RectLayout).height > 0 ? 'bottom' as const : 'top' as const)
: ((layout as RectLayout).width > 0 ? 'left' as const : 'right' as const);
@@ -937,7 +940,7 @@ function updateStyle(
seriesModel.getRawValue(dataIndex) as ParsedValue,
(value: number) => getDefaultInterpolatedLabel(data, value)
);
- }
+ // }
const emphasisModel = itemModel.getModel(['emphasis']);
enableHoverEmphasis(el, emphasisModel.get('focus'), emphasisModel.get('blurScope'));
diff --git a/src/chart/gauge/GaugeView.ts b/src/chart/gauge/GaugeView.ts
index b005b53..398f231 100644
--- a/src/chart/gauge/GaugeView.ts
+++ b/src/chart/gauge/GaugeView.ts
@@ -28,7 +28,7 @@ import GlobalModel from '../../model/Global';
import ExtensionAPI from '../../core/ExtensionAPI';
import { ColorString, ECElement } from '../../util/types';
import List from '../../data/List';
-import Sausage from '../../util/shape/sausage';
+import {Sausage} from '../../util/shape/sausage';
import {createSymbol} from '../../util/symbol';
import ZRImage from 'zrender/src/graphic/Image';
import {extend} from 'zrender/src/core/util';
diff --git a/src/label/calculateTextPosition.ts b/src/label/calculateTextPosition.ts
new file mode 100644
index 0000000..a7d02b1
--- /dev/null
+++ b/src/label/calculateTextPosition.ts
@@ -0,0 +1,200 @@
+import {RectLike, Sector} from 'zrender';
+import {calculateTextPosition, TextPositionCalculationResult} from 'zrender/src/contain/text';
+import {BuiltinTextPosition, TextAlign, TextVerticalAlign} from 'zrender/src/core/types';
+import {ElementCalculateTextPosition, ElementTextConfig} from 'zrender/src/Element';
+
+export type SectorTextPosition = BuiltinTextPosition
+ | 'start' | 'end'
+ | 'startTop' | 'insideStartTop' | 'insideStart' | 'insideStartBottom' | 'startBottom'
+ | 'middleTop' | 'insideMiddleTop' | 'middle' | 'insideMiddleBottom' | 'middleBottom'
+ | 'endTop' | 'insideEndTop' | 'insideEnd' | 'insideEndBottom' | 'endBottom';
+
+export type SectorLike = {
+ cx: number
+ cy: number
+ r0: number
+ r: number
+ startAngle: number
+ endAngle: number
+ clockwise: boolean
+};
+
+export function createSectorCalculateTextPosition(): ElementCalculateTextPosition {
+ return function (
+ this: Sector,
+ out: TextPositionCalculationResult,
+ opts: {
+ position?: SectorTextPosition | (number | string)[]
+ distance?: number // Default 5
+ global?: boolean
+ },
+ boundingRect: RectLike
+ ) {
+ const textPosition = opts.position;
+
+ if (!textPosition || textPosition instanceof Array) {
+ return calculateTextPosition(
+ out,
+ opts as ElementTextConfig,
+ boundingRect
+ );
+ }
+
+ const distance = opts.distance != null ? opts.distance : 5;
+ const sector = this.shape;
+ const cx = sector.cx;
+ const cy = sector.cy;
+ const r = sector.r;
+ const r0 = sector.r0;
+ const middleR = (r + r0) / 2;
+ const startAngle = sector.clockwise ? sector.startAngle : sector.endAngle;
+ const endAngle = sector.clockwise ? sector.endAngle : sector.startAngle;
+ const middleAngle = (startAngle + endAngle) / 2;
+
+ // base position: top-left
+ let x = cx + r * Math.cos(startAngle);
+ let y = cy + r * Math.sin(startAngle);
+
+ let textAlign: TextAlign = 'left';
+ let textVerticalAlign: TextVerticalAlign = 'top';
+
+ switch (textPosition) {
+ case 'start':
+ x = cx + middleR * Math.cos(startAngle);
+ y = cy + middleR * Math.sin(startAngle);
+ textAlign = fixAlign('right', startAngle);
+ textVerticalAlign = fixVerticalAlign('middle', startAngle);
+ break;
+ case 'end':
+ x = cx + middleR * Math.cos(endAngle);
+ y = cy + middleR * Math.sin(endAngle);
+ textAlign = fixAlign('left', endAngle);
+ textVerticalAlign = fixVerticalAlign('middle', endAngle);
+ break;
+
+ case 'startTop':
+ x = cx + (r + distance) * Math.cos(startAngle);
+ y = cy + (r + distance) * Math.sin(startAngle);
+ textAlign = fixAlign('left', startAngle);
+ textVerticalAlign = fixVerticalAlign('bottom', startAngle);
+ break;
+ case 'insideStartTop':
+ x = cx + (r - distance) * Math.cos(startAngle);
+ y = cy + (r - distance) * Math.sin(startAngle);
+ textAlign = fixAlign('left', startAngle);
+ textVerticalAlign = fixVerticalAlign('top', startAngle);
+ break;
+ case 'insideStart':
+ x = cx + middleR * Math.cos(startAngle);
+ y = cy + middleR * Math.sin(startAngle);
+ textAlign = fixAlign('left', startAngle);
+ textVerticalAlign = fixVerticalAlign('middle', startAngle);
+ break;
+ case 'insideStartBottom':
+ x = cx + (r0 + distance) * Math.cos(startAngle);
+ y = cy + (r0 + distance) * Math.sin(startAngle);
+ textAlign = fixAlign('left', startAngle);
+ textVerticalAlign = fixVerticalAlign('bottom', startAngle);
+ break;
+ case 'startBottom':
+ x = cx + (r0 - distance) * Math.cos(startAngle);
+ y = cy + (r0 - distance) * Math.sin(startAngle);
+ textAlign = fixAlign('left', startAngle);
+ textVerticalAlign = fixVerticalAlign('top', startAngle);
+ break;
+
+ case 'middleTop':
+ x = cx + (r + distance) * Math.cos(middleAngle);
+ y = cy + (r + distance) * Math.sin(middleAngle);
+ textAlign = fixAlign('center', middleAngle);
+ textVerticalAlign = fixVerticalAlign('bottom', middleAngle);
+ break;
+ case 'insideMiddleTop':
+ x = cx + (r - distance) * Math.cos(middleAngle);
+ y = cy + (r - distance) * Math.sin(middleAngle);
+ textAlign = fixAlign('center', middleAngle);
+ textVerticalAlign = fixVerticalAlign('top', middleAngle);
+ break;
+ case 'middle':
+ x = cx + middleR * Math.cos(middleAngle);
+ y = cy + middleR * Math.sin(middleAngle);
+ textAlign = fixAlign('center', middleAngle);
+ textVerticalAlign = fixVerticalAlign('middle', middleAngle);
+ break;
+ case 'insideMiddleBottom':
+ x = cx + (r0 + distance) * Math.cos(middleAngle);
+ y = cy + (r0 + distance) * Math.sin(middleAngle);
+ textAlign = fixAlign('center', middleAngle);
+ textVerticalAlign = fixVerticalAlign('bottom', middleAngle);
+ break;
+ case 'middleBottom':
+ x = cx + (r0 - distance) * Math.cos(middleAngle);
+ y = cy + (r0 - distance) * Math.sin(middleAngle);
+ textAlign = fixAlign('center', middleAngle);
+ textVerticalAlign = fixVerticalAlign('top', middleAngle);
+ break;
+
+ case 'endTop':
+ x = cx + (r + distance) * Math.cos(endAngle);
+ y = cy + (r + distance) * Math.sin(endAngle);
+ textAlign = fixAlign('right', endAngle);
+ textVerticalAlign = fixVerticalAlign('bottom', endAngle);
+ case 'insideEndTop':
+ x = cx + (r - distance) * Math.cos(endAngle);
+ y = cy + (r - distance) * Math.sin(endAngle);
+ textAlign = fixAlign('right', endAngle);
+ textVerticalAlign = fixVerticalAlign('top', endAngle);
+ break;
+ case 'insideEnd':
+ x = cx + middleR * Math.cos(endAngle);
+ y = cy + middleR * Math.sin(endAngle);
+ textAlign = fixAlign('right', endAngle);
+ textVerticalAlign = fixVerticalAlign('middle', endAngle);
+ break;
+ case 'insideEndBottom':
+ x = cx + (r0 + distance) * Math.cos(endAngle);
+ y = cy + (r0 + distance) * Math.sin(endAngle);
+ textAlign = fixAlign('right', endAngle);
+ textVerticalAlign = fixVerticalAlign('bottom', endAngle);
+ break;
+ case 'endBottom':
+ x = cx + (r0 - distance) * Math.cos(endAngle);
+ y = cy + (r0 - distance) * Math.sin(endAngle);
+ textAlign = fixAlign('right', endAngle);
+ textVerticalAlign = fixVerticalAlign('top', endAngle);
+ break;
+ default:
+ return calculateTextPosition(
+ out,
+ opts as ElementTextConfig,
+ boundingRect
+ );
+ }
+
+ out = out || {} as TextPositionCalculationResult;
+ out.x = x;
+ out.y = y;
+ out.align = textAlign;
+ out.verticalAlign = textVerticalAlign;
+
+ return out;
+ };
+}
+
+function fixAlign(align: TextAlign, angle: number) {
+ if (angle > Math.PI / 2) {
+ return align === 'left'
+ ? 'right'
+ : (align === 'right' ? 'left' : align);
+ }
+ return align;
+}
+
+function fixVerticalAlign(align: TextVerticalAlign, angle: number) {
+ if (angle > 0 && angle <= Math.PI) {
+ return align === 'top'
+ ? 'bottom'
+ : (align === 'bottom' ? 'top' : align);
+ }
+ return align;
+}
diff --git a/src/util/shape/sausage.ts b/src/util/shape/sausage.ts
index b0a843e..a30d2cb 100644
--- a/src/util/shape/sausage.ts
+++ b/src/util/shape/sausage.ts
@@ -24,7 +24,7 @@ import { PathProps } from 'zrender/src/graphic/Path';
* Sausage: similar to sector, but have half circle on both sides
*/
-class SausageShape {
+export class SausageShape {
cx = 0;
cy = 0;
r0 = 0;
@@ -38,7 +38,7 @@ interface SausagePathProps extends PathProps {
shape?: SausageShape
}
-class SausagePath extends Path<SausagePathProps> {
+export class Sausage extends Path<SausagePathProps> {
type = 'sausage';
@@ -97,5 +97,3 @@ class SausagePath extends Path<SausagePathProps> {
ctx.closePath();
}
}
-
-export default SausagePath;
\ No newline at end of file
diff --git a/test/bar-polar-label.html b/test/bar-polar-label.html
new file mode 100644
index 0000000..bcb2569
--- /dev/null
+++ b/test/bar-polar-label.html
@@ -0,0 +1,126 @@
+
+<!--
+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.
+-->
+
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script src="lib/simpleRequire.js"></script>
+ <script src="lib/config.js"></script>
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ </head>
+ <body>
+ <style>
+ #main0, #main1 {
+ width: 100%;
+ height: 1800px;
+ margin: 0;
+ }
+ </style>
+ <h3>Polar bar series label positions</h3>
+ <div id="main0"></div>
+ <div id="main1"></div>
+ <script>
+
+ require(['echarts'], function (echarts) {
+ function setChart(chartIndex) {
+ var positions = [
+ 'start', 'end',
+ 'startTop', 'insideStartTop', 'insideStart', 'insideStartBottom', 'startBottom',
+ 'middleTop', 'insideMiddleTop', 'middle', 'inside', 'insideMiddleBottom', 'middleBottom',
+ 'endTop', 'insideEndTop', 'insideEnd', 'insideEndBottom', 'endBottom'
+ ];
+
+ var chart = echarts.init(document.getElementById('main' + chartIndex));
+ var rows = 5;
+ var cols = 4;
+
+ var angleAxis = [];
+ var radiusAxis = [];
+ var polar = [];
+ var series = [];
+ var title = [];
+ var cnt = positions.length;
+ for (var i = 0; i < rows; ++i) {
+ for (var j = 0; j < cols; ++j) {
+ var id = i * cols + j;
+ if (id >= cnt) {
+ break;
+ }
+ angleAxis.push({
+ max: 4,
+ polarIndex: id,
+ startAngle: 75
+ });
+ radiusAxis.push({
+ polarIndex: id,
+ type: 'category',
+ data: ['a', 'b', 'c', 'd'],
+ startAngle: 75
+ });
+ polar.push({
+ center: [
+ 100 / cols * (j + 0.5) + '%',
+ 100 / rows * (i + 0.5) + '%'
+ ],
+ radius: [
+ chartIndex ? 10: 20,
+ 100 / rows * 0.8 + '%'
+ ]
+ });
+ series.push({
+ type: 'bar',
+ data: [0.5, 1.2, 2.4, 3.6],
+ coordinateSystem: 'polar',
+ polarIndex: id,
+ label: {
+ show: true,
+ position: positions[id],
+ formatter: '{b}: {c}',
+ borderColor: '#0ff',
+ borderWidth: 2
+ }
+ });
+ title.push({
+ text: positions[id],
+ left: 100 / cols * (j + 0.5) + '%',
+ top: 100 / rows * (i + 0.92) + '%',
+ textAlign: 'center'
+ });
+ }
+ }
+
+ chart.setOption({
+ title: title,
+ angleAxis: chartIndex ? angleAxis : radiusAxis,
+ radiusAxis: chartIndex ? radiusAxis : angleAxis,
+ polar: polar,
+ tooltip: {},
+ series: series,
+ backgroundColor: '#fff',
+ animation: 0
+ });
+ }
+
+ setChart(0);
+ setChart(1);
+ });
+ </script>
+ </body>
+</html>
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org