You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by sh...@apache.org on 2020/07/03 15:30:38 UTC

[incubator-echarts] branch label-enhancement updated: feat(label): add maxSurfaceAngle limit in pie

This is an automated email from the ASF dual-hosted git repository.

shenyi pushed a commit to branch label-enhancement
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git


The following commit(s) were added to refs/heads/label-enhancement by this push:
     new c003487  feat(label): add maxSurfaceAngle limit in pie
c003487 is described below

commit c003487f6c0ef96be90d6f24955a2d2d5641ee94
Author: pissang <bm...@gmail.com>
AuthorDate: Fri Jul 3 23:29:03 2020 +0800

    feat(label): add maxSurfaceAngle limit in pie
---
 src/chart/pie/PieSeries.ts    | 19 ++++++++----
 src/chart/pie/labelLayout.ts  | 36 ++++++++++++----------
 src/label/labelGuideHelper.ts | 69 +++++++++++++++++++++++++++++++++++++++++++
 test/pie-label-extreme.html   |  6 ++--
 test/pie-label-mobile.html    | 45 ++++++++++++++++++++++++++--
 5 files changed, 150 insertions(+), 25 deletions(-)

diff --git a/src/chart/pie/PieSeries.ts b/src/chart/pie/PieSeries.ts
index e3e877c..de34310 100644
--- a/src/chart/pie/PieSeries.ts
+++ b/src/chart/pie/PieSeries.ts
@@ -51,18 +51,26 @@ interface PieLabelOption extends Omit<LabelOption, 'rotate' | 'position'> {
     position?: LabelOption['position'] | 'outer' | 'inner' | 'center'
 }
 
+interface PieLabelLineOption extends LabelLineOption {
+    /**
+     * Max angle between labelLine and surface normal.
+     * 0 - 180
+     */
+    maxSurfaceAngle?: number
+}
+
 export interface PieDataItemOption extends
     OptionDataItemObject<OptionDataValueNumeric>,
     SelectableTarget {
 
     itemStyle?: ItemStyleOption
     label?: PieLabelOption
-    labelLine?: LabelLineOption
+    labelLine?: PieLabelLineOption
 
     emphasis?: {
         itemStyle?: ItemStyleOption
         label?: PieLabelOption
-        labelLine?: LabelLineOption
+        labelLine?: PieLabelLineOption
     }
 }
 export interface PieSeriesOption extends
@@ -81,7 +89,7 @@ export interface PieSeriesOption extends
     // TODO: TYPE Color Callback
     itemStyle?: ItemStyleOption
     label?: PieLabelOption
-    labelLine?: LabelLineOption
+    labelLine?: PieLabelLineOption
 
     clockwise?: boolean
     startAngle?: number
@@ -99,7 +107,7 @@ export interface PieSeriesOption extends
     emphasis?: {
         itemStyle?: ItemStyleOption
         label?: PieLabelOption
-        labelLine?: LabelLineOption
+        labelLine?: PieLabelLineOption
     }
 
     animationType?: 'expansion' | 'scale'
@@ -272,7 +280,8 @@ class PieSeriesModel extends SeriesModel<PieSeriesOption> {
             // 引导线两段中的第二段长度
             length2: 15,
             smooth: false,
-            minTurnAngle: 100,
+            minTurnAngle: 90,
+            maxSurfaceAngle: 90,
             lineStyle: {
                 // color: 各异,
                 width: 1,
diff --git a/src/chart/pie/labelLayout.ts b/src/chart/pie/labelLayout.ts
index ed77887..d47f18f 100644
--- a/src/chart/pie/labelLayout.ts
+++ b/src/chart/pie/labelLayout.ts
@@ -26,7 +26,7 @@ import { Sector, Polyline, Point } from '../../util/graphic';
 import ZRText from 'zrender/src/graphic/Text';
 import BoundingRect, {RectLike} from 'zrender/src/core/BoundingRect';
 import { each } from 'zrender/src/core/util';
-import { limitTurnAngle } from '../../label/labelGuideHelper';
+import { limitTurnAngle, limitSurfaceAngle } from '../../label/labelGuideHelper';
 import { shiftLayoutOnY } from '../../label/labelLayoutHelper';
 
 const RADIAN = Math.PI / 180;
@@ -38,6 +38,8 @@ interface LabelLayout {
     len: number
     len2: number
     minTurnAngle: number
+    maxSurfaceAngle: number
+    surfaceNormal: Point
     linePoints: VectorArray[]
     textAlign: HorizontalAlign
     labelDistance: number,
@@ -281,8 +283,8 @@ export default function (
         }
 
         const midAngle = (sectorShape.startAngle + sectorShape.endAngle) / 2;
-        const dx = Math.cos(midAngle);
-        const dy = Math.sin(midAngle);
+        const nx = Math.cos(midAngle);
+        const ny = Math.sin(midAngle);
 
         let textX;
         let textY;
@@ -300,27 +302,27 @@ export default function (
             textAlign = 'center';
         }
         else {
-            const x1 = (isLabelInside ? (sectorShape.r + sectorShape.r0) / 2 * dx : sectorShape.r * dx) + cx;
-            const y1 = (isLabelInside ? (sectorShape.r + sectorShape.r0) / 2 * dy : sectorShape.r * dy) + cy;
+            const x1 = (isLabelInside ? (sectorShape.r + sectorShape.r0) / 2 * nx : sectorShape.r * nx) + cx;
+            const y1 = (isLabelInside ? (sectorShape.r + sectorShape.r0) / 2 * ny : sectorShape.r * ny) + cy;
 
-            textX = x1 + dx * 3;
-            textY = y1 + dy * 3;
+            textX = x1 + nx * 3;
+            textY = y1 + ny * 3;
 
             if (!isLabelInside) {
                 // For roseType
-                const x2 = x1 + dx * (labelLineLen + r - sectorShape.r);
-                const y2 = y1 + dy * (labelLineLen + r - sectorShape.r);
-                const x3 = x2 + ((dx < 0 ? -1 : 1) * labelLineLen2);
+                const x2 = x1 + nx * (labelLineLen + r - sectorShape.r);
+                const y2 = y1 + ny * (labelLineLen + r - sectorShape.r);
+                const x3 = x2 + ((nx < 0 ? -1 : 1) * labelLineLen2);
                 const y3 = y2;
 
                 if (labelAlignTo === 'edge') {
                     // Adjust textX because text align of edge is opposite
-                    textX = dx < 0
+                    textX = nx < 0
                         ? viewLeft + edgeDistance
                         : viewLeft + viewWidth - edgeDistance;
                 }
                 else {
-                    textX = x3 + (dx < 0 ? -labelDistance : labelDistance);
+                    textX = x3 + (nx < 0 ? -labelDistance : labelDistance);
                 }
                 textY = y3;
                 linePoints = [[x1, y1], [x2, y2], [x3, y3]];
@@ -329,8 +331,8 @@ export default function (
             textAlign = isLabelInside
                 ? 'center'
                 : (labelAlignTo === 'edge'
-                    ? (dx > 0 ? 'right' : 'left')
-                    : (dx > 0 ? 'left' : 'right'));
+                    ? (nx > 0 ? 'right' : 'left')
+                    : (nx > 0 ? 'left' : 'right'));
         }
 
         let labelRotate;
@@ -340,7 +342,7 @@ export default function (
         }
         else {
             labelRotate = rotate
-                ? (dx < 0 ? -midAngle + Math.PI : -midAngle)
+                ? (nx < 0 ? -midAngle + Math.PI : -midAngle)
                 : 0;
         }
 
@@ -368,6 +370,8 @@ export default function (
                 len: labelLineLen,
                 len2: labelLineLen2,
                 minTurnAngle: labelLineModel.get('minTurnAngle'),
+                maxSurfaceAngle: labelLineModel.get('maxSurfaceAngle'),
+                surfaceNormal: new Point(nx, ny),
                 linePoints: linePoints,
                 textAlign: textAlign,
                 labelDistance: labelDistance,
@@ -420,6 +424,8 @@ export default function (
             }
             else {
                 limitTurnAngle(linePoints, layout.minTurnAngle);
+                limitSurfaceAngle(linePoints, layout.surfaceNormal, layout.maxSurfaceAngle);
+
                 labelLine.setShape({ points: linePoints });
 
                 // Set the anchor to the midpoint of sector
diff --git a/src/label/labelGuideHelper.ts b/src/label/labelGuideHelper.ts
index f089d30..a501d1c 100644
--- a/src/label/labelGuideHelper.ts
+++ b/src/label/labelGuideHelper.ts
@@ -444,6 +444,10 @@ export function limitTurnAngle(linePoints: number[][], minTurnAngle: number) {
         const t = pt2.x !== pt1.x
             ? (tmpProjPoint.x - pt1.x) / (pt2.x - pt1.x)
             : (tmpProjPoint.y - pt1.y) / (pt2.y - pt1.y);
+        if (isNaN(t)) {
+            return;
+        }
+
         if (t < 0) {
             Point.copy(tmpProjPoint, pt1);
         }
@@ -455,6 +459,71 @@ export function limitTurnAngle(linePoints: number[][], minTurnAngle: number) {
     }
 }
 
+/**
+ * Limit the angle of line and the surface
+ * @param maxSurfaceAngle Radian of minimum turn angle. 0 - 180. 0 is same direction to normal. 180 is opposite
+ */
+export function limitSurfaceAngle(linePoints: vector.VectorArray[], surfaceNormal: Point, maxSurfaceAngle: number) {
+    if (!(maxSurfaceAngle <= 180 && maxSurfaceAngle > 0)) {
+        return;
+    }
+    maxSurfaceAngle = maxSurfaceAngle / 180 * Math.PI;
+
+    pt0.fromArray(linePoints[0]);
+    pt1.fromArray(linePoints[1]);
+    pt2.fromArray(linePoints[2]);
+
+    Point.sub(dir, pt1, pt0);
+    Point.sub(dir2, pt2, pt1);
+
+    const len1 = dir.len();
+    const len2 = dir2.len();
+
+    if (len1 < 1e-3 || len2 < 1e-3) {
+        return;
+    }
+
+    dir.scale(1 / len1);
+    dir2.scale(1 / len2);
+
+    const angleCos = dir.dot(surfaceNormal);
+    const maxSurfaceAngleCos = Math.cos(maxSurfaceAngle);
+
+    if (angleCos < maxSurfaceAngleCos) {
+        // Calculate project point of pt0 on pt1-pt2
+        const d = projectPointToLine(pt1.x, pt1.y, pt2.x, pt2.y, pt0.x, pt0.y, tmpArr, false);
+        tmpProjPoint.fromArray(tmpArr);
+
+        const HALF_PI = Math.PI / 2;
+        const angle2 = Math.acos(dir2.dot(surfaceNormal));
+        const newAngle = HALF_PI + angle2 - maxSurfaceAngle;
+        if (newAngle >= HALF_PI) {
+            // parallel
+            Point.copy(tmpProjPoint, pt2);
+        }
+        else {
+            // Calculate new projected length with limited minTurnAngle and get the new connect point
+            tmpProjPoint.scaleAndAdd(dir2, d / Math.tan(Math.PI / 2 - newAngle));
+            // Limit the new calculated connect point between pt1 and pt2.
+            const t = pt2.x !== pt1.x
+                ? (tmpProjPoint.x - pt1.x) / (pt2.x - pt1.x)
+                : (tmpProjPoint.y - pt1.y) / (pt2.y - pt1.y);
+            if (isNaN(t)) {
+                return;
+            }
+
+            if (t < 0) {
+                Point.copy(tmpProjPoint, pt1);
+            }
+            else if (t > 1) {
+                Point.copy(tmpProjPoint, pt2);
+            }
+        }
+
+        tmpProjPoint.toArray(linePoints[1]);
+    }
+}
+
 
 type LabelLineModel = Model<LabelLineOption>;
 
diff --git a/test/pie-label-extreme.html b/test/pie-label-extreme.html
index fb9a278..7ce7d45 100644
--- a/test/pie-label-extreme.html
+++ b/test/pie-label-extreme.html
@@ -688,7 +688,7 @@ under the License.
                     minTurnAngle: 110
                 },
                 label: {
-                    margin: 25,
+                    edgeDistance: 25,
                     bleedMargin: 10,
                     alignTo: 'none',
                     overflow: 'truncate'
@@ -699,7 +699,7 @@ under the License.
                 chart.setOption({
                     series: {
                         label: Object.assign({}, config.label, {
-                            margin: config.label.margin + '%'
+                            edgeDistance: config.label.edgeDistance + '%'
                         }),
                         labelLine: config.labelLine
                     }
@@ -712,7 +712,7 @@ under the License.
             labelLineFolder.open();
             labelFolder.add(config.label, 'alignTo', ['none', 'edge', 'labelLine']).onChange(update);
             labelFolder.add(config.label, 'overflow', ['truncate', 'break', 'breakAll']).onChange(update);
-            labelFolder.add(config.label, 'margin', 0, 50).onChange(update);
+            labelFolder.add(config.label, 'edgeDistance', 0, 50).onChange(update);
             labelFolder.add(config.label, 'bleedMargin', 0, 500).onChange(update);
             labelLineFolder.add(config.labelLine, 'length', 0, 500).onChange(update);
             labelLineFolder.add(config.labelLine, 'length2', 0, 500).onChange(update);
diff --git a/test/pie-label-mobile.html b/test/pie-label-mobile.html
index 352dbbe..aef18c6 100644
--- a/test/pie-label-mobile.html
+++ b/test/pie-label-mobile.html
@@ -28,6 +28,7 @@ under the License.
         <script src="lib/jquery.min.js"></script>
         <script src="lib/facePrint.js"></script>
         <script src="lib/testHelper.js"></script>
+        <script src="lib/dat.gui.min.js"></script>
         <link rel="stylesheet" href="lib/reset.css" />
     </head>
     <body>
@@ -46,6 +47,9 @@ under the License.
                 background-color: #fff;
                 text-align: left;
             }
+            .dg {
+                text-align: left;
+            }
         </style>
 
 
@@ -73,7 +77,7 @@ under the License.
                     { name: '其它', value: 3.8 }
                 ],
 
-                ////////////////////////////////////////
+                // ////////////////////////////////////////
                 [
                     { name: '银河帝国5:迈向基地', value: 3.8 },
                     { name: '俞军产品方法论', value: 2.3 },
@@ -94,6 +98,8 @@ under the License.
                 ]
             ];
 
+            const charts = [];
+
             datas.forEach(function (data) {
                 const dom = document.createElement('div');
                 dom.classList.add('chart');
@@ -133,7 +139,8 @@ under the License.
                         },
                         labelLine: {
                             length: 15,
-                            length2: 0
+                            length2: 0,
+                            maxSurfaceAngle: 80
                         },
                         labelLayout: function (params) {
                             const isLeft = params.labelRect.x < chart.getWidth() / 2;
@@ -150,7 +157,41 @@ under the License.
                         data: data
                     }]
                 });
+
+                charts.push(chart);
             });
+
+            const gui = new dat.GUI();
+            const config = {
+                labelLine: {
+                    smooth: 0,
+                    maxSurfaceAngle: 80
+                },
+                label: {
+                    edgeDistance: 10
+                }
+            };
+
+            function update() {
+                charts.forEach(function (chart) {
+                    chart.setOption({
+                        series: {
+                            label: Object.assign({}, config.label, {
+                                edgeDistance: config.label.edgeDistance
+                            }),
+                            labelLine: config.labelLine
+                        }
+                    });
+                });
+            }
+
+            const labelFolder = gui.addFolder('label');
+            const labelLineFolder = gui.addFolder('labelLine');
+            labelFolder.open();
+            labelLineFolder.open();
+            labelFolder.add(config.label, 'edgeDistance', 0, 50).onChange(update);
+            labelLineFolder.add(config.labelLine, 'maxSurfaceAngle', 0, 90).onChange(update);
+            labelLineFolder.add(config.labelLine, 'smooth', 0, 1).onChange(update);
         });
         </script>
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org