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/06/01 05:18:41 UTC

[incubator-echarts] 02/02: feat(pie): improve the shape of label layout

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

commit 5accd257d0e77f069c37e826927be7870e0f96f3
Author: pissang <bm...@gmail.com>
AuthorDate: Mon Jun 1 13:17:51 2020 +0800

    feat(pie): improve the shape of label layout
---
 src/chart/pie/labelLayout.ts | 130 ++++++++++++++++++++++++-------------------
 1 file changed, 74 insertions(+), 56 deletions(-)

diff --git a/src/chart/pie/labelLayout.ts b/src/chart/pie/labelLayout.ts
index 90c53e5..ec057ec 100644
--- a/src/chart/pie/labelLayout.ts
+++ b/src/chart/pie/labelLayout.ts
@@ -25,7 +25,6 @@ import { HorizontalAlign, VerticalAlign, ZRRectLike, ZRTextAlign } from '../../u
 import { Sector, Polyline } from '../../util/graphic';
 import ZRText from 'zrender/src/graphic/Text';
 import { RectLike } from 'zrender/src/core/BoundingRect';
-import Displayable from 'zrender/src/graphic/Displayable';
 import { each } from 'zrender/src/core/util';
 
 const RADIAN = Math.PI / 180;
@@ -40,7 +39,6 @@ interface LabelLayout {
     len2: number
     linePoints: VectorArray[]
     textAlign: HorizontalAlign
-    verticalAlign: VerticalAlign,
     rotation: number,
     labelDistance: number,
     labelAlignTo: PieSeriesOption['label']['alignTo'],
@@ -65,13 +63,17 @@ function adjustSingleSide(
         return a.y - b.y;
     });
 
+    let adjusted = false;
+
     function shiftDown(start: number, end: number, delta: number, dir: number) {
         for (let j = start; j < end; j++) {
-            if (list[j].y + delta > viewTop + viewHeight) {
+            if (list[j].y + delta + list[j].textRect.height / 2 > viewTop + viewHeight) {
                 break;
             }
 
             list[j].y += delta;
+            adjusted = true;
+
             if (j > start
                 && j + 1 < end
                 && list[j + 1].y > list[j].y + list[j].textRect.height
@@ -86,11 +88,13 @@ function adjustSingleSide(
 
     function shiftUp(end: number, delta: number) {
         for (let j = end; j >= 0; j--) {
-            if (list[j].y - delta < viewTop) {
+            if (list[j].y - delta - list[j].textRect.height / 2 < viewTop) {
                 break;
             }
 
             list[j].y -= delta;
+            adjusted = true;
+
             if (j > 0
                 && list[j].y > list[j - 1].y + list[j - 1].textRect.height
             ) {
@@ -99,52 +103,61 @@ function adjustSingleSide(
         }
     }
 
-    function changeX(
-        list: LabelLayout[], isDownList: boolean,
-        cx: number, cy: number, r: number,
-        dir: 1 | -1
-    ) {
-        let lastDeltaX = dir > 0
-            ? isDownList                // right-side
-                ? Number.MAX_VALUE      // down
-                : 0                     // up
-            : isDownList                // left-side
-                ? Number.MAX_VALUE      // down
-                : 0;                    // up
-
-        for (let i = 0, l = list.length; i < l; i++) {
-            if (list[i].labelAlignTo !== 'none') {
-                continue;
-            }
+    interface SemiInfo {
+        list: LabelLayout[]
+        rB: number
+        maxY: number
+    };
+
+    function recalculateXOnSemiToAlignOnEllipseCurve(semi: SemiInfo) {
+        const rB = semi.rB;
+        const rB2 = rB * rB;
+        for (let i = 0; i < semi.list.length; i++) {
+            const item = semi.list[i];
+            const dy = Math.abs(item.y - cy);
+            // horizontal r is always same with original r because x is not changed.
+            const rA = r + item.len;
+            const rA2 = rA * rA;
+            // Use ellipse implicit function to calculate x
+            const dx = Math.sqrt((1 - Math.abs(dy * dy / rB2)) * rA2);
+            item.x = cx + (dx + item.len2) * dir;
+        }
+    }
 
-            const deltaY = Math.abs(list[i].y - cy);
-            const length = list[i].len;
-            const length2 = list[i].len2;
-            let deltaX = (deltaY < r + length)
-                ? Math.sqrt(
-                        (r + length + length2) * (r + length + length2)
-                        - deltaY * deltaY
-                    )
-                : Math.abs(list[i].x - cx);
-            if (isDownList && deltaX >= lastDeltaX) {
-                // right-down, left-down
-                deltaX = lastDeltaX - 10;
+    // Adjust X based on the shifted y. Make tight labels aligned on an ellipse curve.
+    function recalculateX(items: LabelLayout[]) {
+        // Extremes of
+        const topSemi = { list: [], maxY: 0} as SemiInfo;
+        const bottomSemi = { list: [], maxY: 0 } as SemiInfo;
+
+        for (let i = 0; i < items.length; i++) {
+            if (items[i].labelAlignTo !== 'none') {
+                continue;
             }
-            if (!isDownList && deltaX <= lastDeltaX) {
-                // right-up, left-up
-                deltaX = lastDeltaX + 10;
+            const item = items[i];
+            const semi = item.y > cy ? bottomSemi : topSemi;
+            const dy = Math.abs(item.y - cy);
+            if (dy > semi.maxY) {
+                const dx = item.x - cx - item.len2 * dir;
+                // horizontal r is always same with original r because x is not changed.
+                const rA = r + item.len;
+                // Canculate rB based on the topest / bottemest label.
+                const rB = dx < rA
+                    ? Math.sqrt(dy * dy / (1 - dx * dx / rA / rA))
+                    : rA;
+                semi.rB = rB;
+                semi.maxY = dy;
             }
-
-            list[i].x = cx + deltaX * dir;
-            lastDeltaX = deltaX;
+            semi.list.push(item);
         }
+
+        recalculateXOnSemiToAlignOnEllipseCurve(topSemi);
+        recalculateXOnSemiToAlignOnEllipseCurve(bottomSemi);
     }
 
     let lastY = 0;
     let delta;
     const len = list.length;
-    const upList = [];
-    const downList = [];
     for (let i = 0; i < len; i++) {
         if (list[i].position === 'outer' && list[i].labelAlignTo === 'labelLine') {
             const dx = list[i].x - farthestX;
@@ -161,16 +174,10 @@ function adjustSingleSide(
     if (viewHeight - lastY < 0) {
         shiftUp(len - 1, lastY - viewHeight);
     }
-    for (let i = 0; i < len; i++) {
-        if (list[i].y >= cy) {
-            downList.push(list[i]);
-        }
-        else {
-            upList.push(list[i]);
-        }
+
+    if (adjusted) {
+        recalculateX(list);
     }
-    changeX(upList, false, cx, cy, r, dir);
-    changeX(downList, true, cx, cy, r, dir);
 }
 
 function avoidOverlap(
@@ -291,6 +298,10 @@ export default function (
     const viewTop = viewRect.y;
     const viewHeight = viewRect.height;
 
+    function setNotShow(el: {ignore: boolean}) {
+        el.ignore = true;
+    }
+
     data.each(function (idx) {
         const sector = data.getItemGraphicEl(idx) as Sector;
         const sectorShape = sector.shape;
@@ -313,6 +324,8 @@ export default function (
         labelLineLen2 = parsePercent(labelLineLen2, viewWidth);
 
         if (Math.abs(sectorShape.endAngle - sectorShape.startAngle) < minShowLabelRadian) {
+            each(label.states, setNotShow);
+            label.ignore = true;
             return;
         }
 
@@ -400,7 +413,6 @@ export default function (
                 len2: labelLineLen2,
                 linePoints: linePoints,
                 textAlign: textAlign,
-                verticalAlign: 'middle',
                 rotation: labelRotate,
                 labelDistance: labelDistance,
                 labelAlignTo: labelAlignTo,
@@ -409,6 +421,15 @@ export default function (
                 textRect: textRect
             });
         }
+        else {
+            label.x = textX;
+            label.y = textY;
+            label.rotation = labelRotate;
+            label.setStyle({
+                align: textAlign,
+                verticalAlign: 'middle'
+            });
+        }
         sector.setTextConfig({
             inside: isLabelInside
         });
@@ -418,10 +439,6 @@ export default function (
         avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop);
     }
 
-    function setNotShow(el: {ignore: boolean}) {
-        el.ignore = true;
-    }
-
     for (let i = 0; i < labelLayoutList.length; i++) {
         const layout = labelLayoutList[i];
         const label = layout.label;
@@ -430,9 +447,10 @@ export default function (
         if (label) {
             label.x = layout.x;
             label.y = layout.y;
+            label.rotation = layout.rotation;
             label.setStyle({
                 align: layout.textAlign,
-                verticalAlign: layout.verticalAlign
+                verticalAlign: 'middle'
             });
             if (notShowLabel) {
                 each(label.states, setNotShow);


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