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