pissang commented on code in PR #17461:
URL: https://github.com/apache/echarts/pull/17461#discussion_r996377448


##########
src/chart/funnel/funnelLayout.ts:
##########
@@ -324,68 +377,242 @@ export default function funnelLayout(ecModel: 
GlobalModel, api: ExtensionAPI) {
             ];
         };
 
+        const getLinePointsBySize = function (offset: number, itemSize: 
number) {
+            // do not caculate line width in this func
+            if (orient === 'horizontal') {
+                const itemHeight = itemSize;
+                let y0;
+                switch (funnelAlign) {
+                    case 'top':
+                        y0 = y;
+                        break;
+                    case 'center':
+                        y0 = y + (viewHeight - itemHeight) / 2;
+                        break;
+                    case 'bottom':
+                        y0 = y + (viewHeight - itemHeight);
+                        break;
+                }
+
+                return [
+                    [offset, y0],
+                    [offset, y0 + itemHeight]
+                ];
+            }
+            const itemWidth = itemSize;
+            let x0;
+            switch (funnelAlign) {
+                case 'left':
+                    x0 = x;
+                    break;
+                case 'center':
+                    x0 = x + (viewWidth - itemWidth) / 2;
+                    break;
+                case 'right':
+                    x0 = x + viewWidth - itemWidth;
+                    break;
+            }
+            return [
+                [x0, offset],
+                [x0 + itemWidth, offset]
+            ];
+        };
+
+        // adjust related param
         if (sort === 'ascending') {
             // From bottom to top
             itemSize = -itemSize;
-            gap = -gap;
+            const symbol = !dynamicHeight && dynamicArea ? 1 : -1;
+            gap = gap * symbol;
             if (orient === 'horizontal') {
-                x += viewWidth;
+                x += symbol === 1 ? 0 : viewWidth;
             }
             else {
-                y += viewHeight;
+                y += symbol === 1 ? 0 : viewHeight;
             }
             indices = indices.reverse();
         }
-
-        for (let i = 0; i < indices.length; i++) {
-            const idx = indices[i];
-            const nextIdx = indices[i + 1];
-            const itemModel = data.getItemModel<FunnelDataItemOption>(idx);
-
-            if (orient === 'horizontal') {
-                let width = itemModel.get(['itemStyle', 'width']);
-                if (width == null) {
-                    width = itemSize;
+        else {
+            if (dynamicArea && !dynamicHeight) {
+                gap = -gap;
+                if (orient === 'horizontal') {
+                    x += viewWidth;
                 }
                 else {
-                    width = parsePercent(width, viewWidth);
-                    if (sort === 'ascending') {
-                        width = -width;
-                    }
+                    y += viewHeight;
                 }
+            }
+        }
 
-                const start = getLinePoints(idx, x);
-                const end = getLinePoints(nextIdx, x + width);
+        // dynamicArea about
+        const areaExtent = [0, viewHeight * viewWidth / 2];
+        // auxiliary variable
+        let cumulativeArea = 0;
+        let cumulativeHeight = 0;
+        // piece top and bottom
+        let pieceAreaBottom = 0;
+        let pieceAreaTop = 0;
 
-                x += width + gap;
+        const valueSum = valueArr.reduce((pre, cur) => pre + cur);
 
-                data.setItemLayout(idx, {
-                    points: start.concat(end.slice().reverse())
-                });
+        const getPieceHeight = function (pieceHeight: number | string, idx?: 
number): number {
+            // get funnel piece height pass to getLinePoints func based on 
data value
+            const val = data.get(valueDim, idx) as number || 0;
+
+            if (dynamicHeight) {
+                // in dy height, user can't set itemHeight or itemWidth
+                pieceHeight = linearMap(val, [0, valueSum], [0, size], true);
+
+                pieceHeight = sort === 'ascending' ? -pieceHeight : 
pieceHeight;
+                return pieceHeight;
+            }
+            else if (dynamicArea) {
+                // in dy size, user can't set itemHeight or itemWidth too
+                const pieceArea = linearMap(val, [0, valueSum], areaExtent, 
true);
+
+                cumulativeArea += pieceArea;
+                pieceAreaTop = pieceAreaBottom;
+
+                // calculate bottom line length and top line length
+                pieceAreaBottom = Math.sqrt(2 * cumulativeArea * size / 
viewSize);
+                pieceHeight = pieceAreaBottom * viewSize / size - 
cumulativeHeight;
+
+                cumulativeHeight += pieceHeight;
+                pieceHeight = sort === 'ascending' ? pieceHeight : 
-pieceHeight;
+                return pieceHeight;
+            }
+
+            // default mapping
+            if (pieceHeight == null) {
+                pieceHeight = itemSize;
             }
             else {
-                let height = itemModel.get(['itemStyle', 'height']);
-                if (height == null) {
-                    height = itemSize;
+                pieceHeight = parsePercent(pieceHeight, orient === 
'horizontal' ? viewWidth : viewHeight);
+                pieceHeight = sort === 'ascending' ? -pieceHeight : 
pieceHeight;
+            }
+            return pieceHeight;
+        };
+
+        // dy height funnel size about
+        const thickDegree = parsePercent(seriesModel.get('thickDegree'), 100);
+        thickDegree >= 100 && console.warn('thickDegree shouldn\'t be greater 
than or equal to 100');

Review Comment:
   Every `warn` should be in the `__DEV__` condition so it will be emitted in 
the production.



##########
src/chart/funnel/FunnelView.ts:
##########
@@ -167,6 +167,141 @@ class FunnelPiece extends graphic.Polygon {
             stroke: visualColor
         });
     }
+
+    ratePiece: RatePiece;
+}
+
+class RatePiece extends graphic.Polygon {

Review Comment:
   Seems there are too many code same to the `FunnelPiece`. Could we reuse them?



##########
src/chart/funnel/funnelLayout.ts:
##########
@@ -324,68 +377,242 @@ export default function funnelLayout(ecModel: 
GlobalModel, api: ExtensionAPI) {
             ];
         };
 
+        const getLinePointsBySize = function (offset: number, itemSize: 
number) {
+            // do not caculate line width in this func
+            if (orient === 'horizontal') {
+                const itemHeight = itemSize;
+                let y0;
+                switch (funnelAlign) {
+                    case 'top':
+                        y0 = y;
+                        break;
+                    case 'center':
+                        y0 = y + (viewHeight - itemHeight) / 2;
+                        break;
+                    case 'bottom':
+                        y0 = y + (viewHeight - itemHeight);
+                        break;
+                }
+
+                return [
+                    [offset, y0],
+                    [offset, y0 + itemHeight]
+                ];
+            }
+            const itemWidth = itemSize;
+            let x0;
+            switch (funnelAlign) {
+                case 'left':
+                    x0 = x;
+                    break;
+                case 'center':
+                    x0 = x + (viewWidth - itemWidth) / 2;
+                    break;
+                case 'right':
+                    x0 = x + viewWidth - itemWidth;
+                    break;
+            }
+            return [
+                [x0, offset],
+                [x0 + itemWidth, offset]
+            ];
+        };
+
+        // adjust related param
         if (sort === 'ascending') {
             // From bottom to top
             itemSize = -itemSize;
-            gap = -gap;
+            const symbol = !dynamicHeight && dynamicArea ? 1 : -1;
+            gap = gap * symbol;
             if (orient === 'horizontal') {
-                x += viewWidth;
+                x += symbol === 1 ? 0 : viewWidth;
             }
             else {
-                y += viewHeight;
+                y += symbol === 1 ? 0 : viewHeight;
             }
             indices = indices.reverse();
         }
-
-        for (let i = 0; i < indices.length; i++) {
-            const idx = indices[i];
-            const nextIdx = indices[i + 1];
-            const itemModel = data.getItemModel<FunnelDataItemOption>(idx);
-
-            if (orient === 'horizontal') {
-                let width = itemModel.get(['itemStyle', 'width']);
-                if (width == null) {
-                    width = itemSize;
+        else {
+            if (dynamicArea && !dynamicHeight) {
+                gap = -gap;
+                if (orient === 'horizontal') {
+                    x += viewWidth;
                 }
                 else {
-                    width = parsePercent(width, viewWidth);
-                    if (sort === 'ascending') {
-                        width = -width;
-                    }
+                    y += viewHeight;
                 }
+            }
+        }
 
-                const start = getLinePoints(idx, x);
-                const end = getLinePoints(nextIdx, x + width);
+        // dynamicArea about
+        const areaExtent = [0, viewHeight * viewWidth / 2];
+        // auxiliary variable
+        let cumulativeArea = 0;
+        let cumulativeHeight = 0;
+        // piece top and bottom
+        let pieceAreaBottom = 0;
+        let pieceAreaTop = 0;
 
-                x += width + gap;
+        const valueSum = valueArr.reduce((pre, cur) => pre + cur);
 
-                data.setItemLayout(idx, {
-                    points: start.concat(end.slice().reverse())
-                });
+        const getPieceHeight = function (pieceHeight: number | string, idx?: 
number): number {
+            // get funnel piece height pass to getLinePoints func based on 
data value
+            const val = data.get(valueDim, idx) as number || 0;
+
+            if (dynamicHeight) {
+                // in dy height, user can't set itemHeight or itemWidth
+                pieceHeight = linearMap(val, [0, valueSum], [0, size], true);
+
+                pieceHeight = sort === 'ascending' ? -pieceHeight : 
pieceHeight;
+                return pieceHeight;
+            }
+            else if (dynamicArea) {
+                // in dy size, user can't set itemHeight or itemWidth too
+                const pieceArea = linearMap(val, [0, valueSum], areaExtent, 
true);
+
+                cumulativeArea += pieceArea;
+                pieceAreaTop = pieceAreaBottom;
+
+                // calculate bottom line length and top line length
+                pieceAreaBottom = Math.sqrt(2 * cumulativeArea * size / 
viewSize);
+                pieceHeight = pieceAreaBottom * viewSize / size - 
cumulativeHeight;
+
+                cumulativeHeight += pieceHeight;
+                pieceHeight = sort === 'ascending' ? pieceHeight : 
-pieceHeight;
+                return pieceHeight;
+            }
+
+            // default mapping
+            if (pieceHeight == null) {
+                pieceHeight = itemSize;
             }
             else {
-                let height = itemModel.get(['itemStyle', 'height']);
-                if (height == null) {
-                    height = itemSize;
+                pieceHeight = parsePercent(pieceHeight, orient === 
'horizontal' ? viewWidth : viewHeight);
+                pieceHeight = sort === 'ascending' ? -pieceHeight : 
pieceHeight;
+            }
+            return pieceHeight;
+        };
+
+        // dy height funnel size about
+        const thickDegree = parsePercent(seriesModel.get('thickDegree'), 100);
+        thickDegree >= 100 && console.warn('thickDegree shouldn\'t be greater 
than or equal to 100');
+        const maxSize = thickDegree < 100 ? sizeExtent[1] * 100 / (100 - 
thickDegree) : sizeExtent[1];
+        let resSize = maxSize;
+
+        // rate funnel about
+        const showRate = seriesModel.get('showRate');
+        let firstVal: number;
+
+        // exit shape control
+        const exitWidth = parsePercent(seriesModel.get('exitWidth'), 100);
+
+        const setLayoutPoints =
+            // The subsequent funnel shape modification will be done in this 
func.
+            // We don’t need to concern direction when we use this function to 
set points.
+            function (
+                index: number,
+                idx: number,
+                nextIdx: number,
+                pieceHeight: number,
+                pos: number
+            ): void {
+                if (dynamicHeight) {
+                    const start = getLinePointsBySize(pos, resSize / maxSize * 
viewSize);
+                    index === indices.length - 1 && exitWidth === 100
+                        || (
+                            resSize += sort === 'ascending' ? pieceHeight : 
-pieceHeight
+                        );
+                    const end = getLinePointsBySize(pos + pieceHeight, resSize 
/ maxSize * viewSize);
+
+                    data.setItemLayout(idx, {
+                        points: start.concat(end.slice().reverse())
+                    });
+                    return;
                 }
-                else {
-                    height = parsePercent(height, viewHeight);
-                    if (sort === 'ascending') {
-                        height = -height;
-                    }
+                else if (dynamicArea) {
+                    const start = getLinePointsBySize(pos, pieceAreaTop);
+                    const end = getLinePointsBySize(pos + pieceHeight, 
pieceAreaBottom);
+
+                    data.setItemLayout(idx, {
+                        points: start.concat(end.slice().reverse())
+                    });
+                    return;
                 }
+                else if (showRate) {
+                    // data piece
+                    const dataStart = getLinePoints(idx, pos);
+                    let dataEnd;
+                    const val = data.get(valueDim, idx) as number || 0;
+                    if (exitWidth !== undefined && index === indices.length - 
1) {
+                        const itemSize = linearMap(val, [min, max], 
sizeExtent, true);
+                        const exitSize = itemSize * (exitWidth > 100 ? 100 : 
exitWidth) / 100;
+                        dataEnd = getLinePointsBySize(pos + pieceHeight / 2, 
exitSize);
+                    }
+                    else {
+                        dataEnd = getLinePoints(idx, pos + pieceHeight / 2);
+                    }
 
-                const start = getLinePoints(idx, y);
-                const end = getLinePoints(nextIdx, y + height);
+                    // rate piece
+                    const rateStart = getLinePoints(idx, pos + pieceHeight / 
2);
+                    const rateEnd = getLinePoints(nextIdx, pos + pieceHeight);
 
-                y += height + gap;
+                    // rate label text about
+                    const nextVal = data.get(valueDim, nextIdx) as number || 0;
+                    let rate: number | string = nextVal / val;
+                    rate = 'Rate ' + (rate * 100).toFixed(0) + '%';
+                    if (index === 0) {
+                        firstVal = val;
+                    }
+                    else if (index === indices.length - 1) {
+                        const lastVal = val;
+                        rate = lastVal / firstVal;
+                        rate = 'Overall rate ' + (rate * 100).toFixed(0) + '%';

Review Comment:
   Still the default rate string should not be fixed here. It's not `i18n` 
friendly and hard to be formatted



##########
src/chart/funnel/FunnelView.ts:
##########
@@ -167,6 +167,141 @@ class FunnelPiece extends graphic.Polygon {
             stroke: visualColor
         });
     }
+
+    ratePiece: RatePiece;
+}
+
+class RatePiece extends graphic.Polygon {
+
+    constructor(data: SeriesData, idx: number) {
+        super();
+
+        const polygon = this;
+        const labelLine = new graphic.Polyline();
+        const text = new graphic.Text();
+        polygon.setTextContent(text);
+        this.setTextGuideLine(labelLine);
+
+        this.updateData(data, idx, true);
+    }
+
+    updateData(data: SeriesData, idx: number, firstCreate?: boolean) {
+        const polygon = this;
+        const layout = data.getItemLayout(idx);
+        const seriesModel = data.hostModel;
+        let opacity: number;
+
+        if (layout.isLastPiece) {
+            opacity = 1;
+        }
+        else {
+            opacity = 0.5;
+        }
+
+        if (!firstCreate) {
+            saveOldStyle(polygon);
+        }
+
+        if (layout.isLastPiece) {
+            polygon.useStyle(Object.assign(data.getItemVisual(idx, 'style'), { 
fill: 'rgba(0,0,0,0)' }));

Review Comment:
   If you want hide the last piece. Set it to `ignore: true` is the best way. 
Or set `invisible: true` if you still want it to be interactable



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscr...@echarts.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


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

Reply via email to