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/09/18 14:12:26 UTC

[incubator-echarts] branch line-optimize updated: fix(line): optimizing polyline smooth algorithm.

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

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


The following commit(s) were added to refs/heads/line-optimize by this push:
     new e106d64  fix(line): optimizing polyline smooth algorithm.
e106d64 is described below

commit e106d64244f771a204a1fca55b0e4567a42c4ed2
Author: pissang <bm...@gmail.com>
AuthorDate: Fri Sep 18 22:11:20 2020 +0800

    fix(line): optimizing polyline smooth algorithm.
    
    Limiting the control points inside the range of two connect points. Instead of the global bounding box in the previous implementation
---
 .gitignore                 |   7 ++-
 src/chart/line/LineView.ts |  16 +++---
 src/chart/line/poly.ts     | 134 +++++++++++++++++++++++----------------------
 test/area-smooth.html      |  98 +++++++++++++++++++++++++++++++++
 4 files changed, 181 insertions(+), 74 deletions(-)

diff --git a/.gitignore b/.gitignore
index aa2f9b2..1c992a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -174,7 +174,7 @@ map/raw
 theme/thumb
 pre-publish-tmp
 todo
-**/*.log
+*.log
 *.sublime-workspace
 *.sublime-project
 
@@ -191,4 +191,7 @@ todo
 /index.blank.js
 /extension-esm
 /extension
-*.tgz
\ No newline at end of file
+*.tgz
+
+# Result of node.js perf
+*.asm
\ No newline at end of file
diff --git a/src/chart/line/LineView.ts b/src/chart/line/LineView.ts
index e9ebec9..e0df31d 100644
--- a/src/chart/line/LineView.ts
+++ b/src/chart/line/LineView.ts
@@ -594,10 +594,12 @@ class LineView extends ChartView {
         enableHoverEmphasis(polyline, focus, blurScope);
 
         const smooth = getSmooth(seriesModel.get('smooth'));
+        const smoothMonotone = seriesModel.get('smoothMonotone');
+        const connectNulls = seriesModel.get('connectNulls');
         polyline.setShape({
-            smooth: smooth,
-            smoothMonotone: seriesModel.get('smoothMonotone'),
-            connectNulls: seriesModel.get('connectNulls')
+            smooth,
+            smoothMonotone,
+            connectNulls
         });
 
         if (polygon) {
@@ -618,10 +620,10 @@ class LineView extends ChartView {
             }
 
             polygon.setShape({
-                smooth: smooth,
-                stackedOnSmooth: stackedOnSmooth,
-                smoothMonotone: seriesModel.get('smoothMonotone'),
-                connectNulls: seriesModel.get('connectNulls')
+                smooth,
+                stackedOnSmooth,
+                smoothMonotone,
+                connectNulls
             });
 
             setStatesStylesFromModel(polygon, seriesModel, 'areaStyle');
diff --git a/src/chart/line/poly.ts b/src/chart/line/poly.ts
index 6071dbb..24a14f6 100644
--- a/src/chart/line/poly.ts
+++ b/src/chart/line/poly.ts
@@ -39,14 +39,12 @@ function drawSegment(
     segLen: number,
     allLen: number,
     dir: number,
-    smoothMin: number[],
-    smoothMax: number[],
     smooth: number,
     smoothMonotone: 'x' | 'y' | 'none',
     connectNulls: boolean
 ) {
-    let px: number;
-    let py: number;
+    let prevX: number;
+    let prevY: number;
     let cpx0: number;
     let cpy0: number;
     let cpx1: number;
@@ -75,8 +73,8 @@ function drawSegment(
             cpy0 = y;
         }
         else {
-            const dx = x - px;
-            const dy = y - py;
+            const dx = x - prevX;
+            const dy = y - prevY;
 
             // Ignore tiny segment.
             if ((dx * dx + dy * dy) < 1) {
@@ -102,103 +100,105 @@ function drawSegment(
                 let ratioNextSeg = 0.5;
                 let vx: number = 0;
                 let vy: number = 0;
+                let nextCpx0;
+                let nextCpy0;
                 // Is last point
                 if (tmpK >= segLen || isPointNull(nextX, nextY)) {
                     cpx1 = x;
                     cpy1 = y;
                 }
                 else {
-                    vx = nextX - px;
-                    vy = nextY - py;
+                    vx = nextX - prevX;
+                    vy = nextY - prevY;
 
-                    const dx0 = x - px;
+                    const dx0 = x - prevX;
                     const dx1 = nextX - x;
-                    const dy0 = y - py;
+                    const dy0 = y - prevY;
                     const dy1 = nextY - y;
                     let lenPrevSeg;
                     let lenNextSeg;
                     if (smoothMonotone === 'x') {
                         lenPrevSeg = Math.abs(dx0);
                         lenNextSeg = Math.abs(dx1);
+                        cpx1 = x - lenPrevSeg * smooth;
+                        cpy1 = y;
+                        nextCpx0 = x + lenPrevSeg * smooth;
+                        nextCpy0 = y;
                     }
                     else if (smoothMonotone === 'y') {
                         lenPrevSeg = Math.abs(dy0);
                         lenNextSeg = Math.abs(dy1);
+                        cpx1 = x;
+                        cpy1 = y - lenPrevSeg * smooth;
+                        nextCpx0 = x;
+                        nextCpy0 = y + lenPrevSeg * smooth;
                     }
                     else {
                         lenPrevSeg = Math.sqrt(dx0 * dx0 + dy0 * dy0);
                         lenNextSeg = Math.sqrt(dx1 * dx1 + dy1 * dy1);
-                    }
-
-                    // Use ratio of seg length
-                    ratioNextSeg = lenNextSeg / (lenNextSeg + lenPrevSeg);
 
-                    cpx1 = x - vx * smooth * (1 - ratioNextSeg);
-                    cpy1 = y - vy * smooth * (1 - ratioNextSeg);
+                        // Use ratio of seg length
+                        ratioNextSeg = lenNextSeg / (lenNextSeg + lenPrevSeg);
+
+                        cpx1 = x - vx * smooth * (1 - ratioNextSeg);
+                        cpy1 = y - vy * smooth * (1 - ratioNextSeg);
+
+                        // cp0 of next segment
+                        nextCpx0 = x + vx * smooth * ratioNextSeg;
+                        nextCpy0 = y + vy * smooth * ratioNextSeg;
+
+                        // Smooth constraint between point and next point.
+                        // Avoid exceeding extreme after smoothing.
+                        nextCpx0 = mathMin(nextCpx0, mathMax(nextX, x));
+                        nextCpy0 = mathMin(nextCpy0, mathMax(nextY, y));
+                        nextCpx0 = mathMax(nextCpx0, mathMin(nextX, x));
+                        nextCpy0 = mathMax(nextCpy0, mathMin(nextY, y));
+                        // Reclaculate cp1 based on the adjusted cp0 of next seg.
+                        vx = nextCpx0 - x;
+                        vy = nextCpy0 - y;
+
+                        cpx1 = x - vx * lenPrevSeg / lenNextSeg;
+                        cpy1 = y - vy * lenPrevSeg / lenNextSeg;
+
+                        // Smooth constraint between point and prev point.
+                        // Avoid exceeding extreme after smoothing.
+                        cpx1 = mathMin(cpx1, mathMax(prevX, x));
+                        cpy1 = mathMin(cpy1, mathMax(prevY, y));
+                        cpx1 = mathMax(cpx1, mathMin(prevX, x));
+                        cpy1 = mathMax(cpy1, mathMin(prevY, y));
+
+                        // Adjust next cp0 again.
+                        vx = x - cpx1;
+                        vy = y - cpy1;
+                        nextCpx0 = x + vx * lenNextSeg / lenPrevSeg;
+                        nextCpy0 = y + vy * lenNextSeg / lenPrevSeg;
+                    }
                 }
-                // Smooth constraint
-                cpx0 = mathMin(cpx0, smoothMax[0]);
-                cpy0 = mathMin(cpy0, smoothMax[1]);
-                cpx0 = mathMax(cpx0, smoothMin[0]);
-                cpy0 = mathMax(cpy0, smoothMin[1]);
-
-                cpx1 = mathMin(cpx1, smoothMax[0]);
-                cpy1 = mathMin(cpy1, smoothMax[1]);
-                cpx1 = mathMax(cpx1, smoothMin[0]);
-                cpy1 = mathMax(cpy1, smoothMin[1]);
 
                 ctx.bezierCurveTo(cpx0, cpy0, cpx1, cpy1, x, y);
 
-                // cp0 of next segment
-                cpx0 = x + vx * smooth * ratioNextSeg;
-                cpy0 = y + vy * smooth * ratioNextSeg;
+                cpx0 = nextCpx0;
+                cpy0 = nextCpy0;
             }
             else {
                 ctx.lineTo(x, y);
             }
         }
 
-        px = x;
-        py = y;
+        prevX = x;
+        prevY = y;
         idx += dir;
     }
 
     return k;
 }
 
-function getBoundingBox(points: ArrayLike<number>, smoothConstraint?: boolean) {
-    const ptMin = [Infinity, Infinity];
-    const ptMax = [-Infinity, -Infinity];
-    if (smoothConstraint) {
-        for (let i = 0; i < points.length;) {
-            const x = points[i++];
-            const y = points[i++];
-            if (x < ptMin[0]) {
-                ptMin[0] = x;
-            }
-            if (y < ptMin[1]) {
-                ptMin[1] = y;
-            }
-            if (x > ptMax[0]) {
-                ptMax[0] = x;
-            }
-            if (y > ptMax[1]) {
-                ptMax[1] = y;
-            }
-        }
-    }
-    return {
-        min: smoothConstraint ? ptMin : ptMax,
-        max: smoothConstraint ? ptMax : ptMin
-    };
-}
-
 class ECPolylineShape {
     points: ArrayLike<number>;
     smooth = 0;
     smoothConstraint = true;
     smoothMonotone: 'x' | 'y' | 'none';
-    connectNulls = false;
+    connectNulls: boolean;
 }
 
 interface ECPolylineProps extends PathProps {
@@ -232,7 +232,7 @@ export class ECPolyline extends Path<ECPolylineProps> {
         let i = 0;
         let len = points.length / 2;
 
-        const result = getBoundingBox(points, shape.smoothConstraint);
+        // const result = getBoundingBox(points, shape.smoothConstraint);
 
         if (shape.connectNulls) {
             // Must remove first and last null values avoid draw error in polygon
@@ -250,7 +250,9 @@ export class ECPolyline extends Path<ECPolylineProps> {
         while (i < len) {
             i += drawSegment(
                 ctx, points, i, len, len,
-                1, result.min, result.max, shape.smooth,
+                1,
+                // result.min, result.max,
+                shape.smooth,
                 shape.smoothMonotone, shape.connectNulls
             ) + 1;
         }
@@ -286,8 +288,6 @@ export class ECPolygon extends Path {
         let i = 0;
         let len = points.length / 2;
         const smoothMonotone = shape.smoothMonotone;
-        const bbox = getBoundingBox(points, shape.smoothConstraint);
-        const stackedOnBBox = getBoundingBox(stackedOnPoints, shape.smoothConstraint);
 
         if (shape.connectNulls) {
             // Must remove first and last null values avoid draw error in polygon
@@ -305,12 +305,16 @@ export class ECPolygon extends Path {
         while (i < len) {
             const k = drawSegment(
                 ctx, points, i, len, len,
-                1, bbox.min, bbox.max, shape.smooth,
+                1,
+                // bbox.min, bbox.max,
+                shape.smooth,
                 smoothMonotone, shape.connectNulls
             );
             drawSegment(
                 ctx, stackedOnPoints, i + k - 1, k, len,
-                -1, stackedOnBBox.min, stackedOnBBox.max, shape.stackedOnSmooth,
+                -1,
+                // stackedOnBBox.min, stackedOnBBox.max,
+                shape.stackedOnSmooth,
                 smoothMonotone, shape.connectNulls
             );
             i += k + 1;
diff --git a/test/area-smooth.html b/test/area-smooth.html
new file mode 100644
index 0000000..c369d13
--- /dev/null
+++ b/test/area-smooth.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<!--
+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">
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <script src="lib/esl.js"></script>
+        <script src="lib/config.js"></script>
+        <script src="lib/jquery.min.js"></script>
+        <script src="lib/facePrint.js"></script>
+        <script src="lib/testHelper.js"></script>
+        <!-- <script src="ut/lib/canteen.js"></script> -->
+        <link rel="stylesheet" href="lib/reset.css" />
+    </head>
+    <body>
+        <style>
+        </style>
+
+
+
+        <div id="main0"></div>
+
+
+
+
+
+
+
+
+
+        <script>
+        require(['echarts'/*, 'map/js/china' */], function (echarts) {
+            var option;
+            // $.getJSON('./data/nutrients.json', function (data) {});
+
+            option = {
+                xAxis: {
+                    type: 'time'
+                },
+                yAxis: {
+                    type: 'value',
+                    boundaryGap: [0, '100%']
+                },
+                series: [
+                    {
+                        name: '模拟数据',
+                        type: 'line',
+                        smooth: true,
+                        // areaStyle: {},
+                        data: [
+                            [Date.UTC(1970, 9, 29), 0],
+                            [Date.UTC(1970, 10, 9), 0],
+                            [Date.UTC(1970, 11, 1), 6],
+                            [Date.UTC(1971, 0, 1), 6],
+                            [Date.UTC(1971, 0, 10), 6],
+                            [Date.UTC(1971, 1, 19), 0],
+                            [Date.UTC(1971, 2, 25), 0],
+                            [Date.UTC(1971, 3, 19), 3],
+                            [Date.UTC(1971, 3, 30), 3],
+                            [Date.UTC(1971, 4, 14), 3],
+                            [Date.UTC(1971, 4, 24), 0],
+                            [Date.UTC(1971, 5, 10), 0]
+                        ]
+                    }
+                ]
+            };
+            var chart = testHelper.create(echarts, 'main0', {
+                title: [
+                    'Case from https://github.com/apache/incubator-echarts/issues/4556'
+                ],
+                option: option
+            });
+        });
+        </script>
+
+
+    </body>
+</html>
+


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