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