background
In practice, occasionally curves are smooth, but the end points are not smooth. This happens when fewer dates are selected and the relative slope of the curve on both sides of the end points is significantly different.
Online editing using Echarts can be easily reproduced.
Background knowledge
Before we dive into the details, let’s give some background. Let’s think about how to draw a curve between two points, and the easiest way to do that is to draw an arc, assuming that the two points are on the same circle, then we can figure out the equation of the circle, and then we can draw the arc on the canvas. But if we have three points, and we want to use a curve to represent the trend between the three points, it’s not appropriate to use an arc to represent the trend, in fact, it’s not appropriate to use a regular curve to represent the trend between multiple points.
Bessel curve
A Bezier curve is an irregular curve that can be drawn between two points. Bessel curves are often used to draw smooth curves in graph visualization. The process of drawing bezier curves depends on two endpoints and control points.
- For the one-dimensional Bessel equation, the number of control points is 0(dimension -1), the one-dimensional Bessel curve is a straight line, assuming that the two endpoints are P0 and P1, the equation satisfies B(t) = P0 + (P1-P0) * t, and the value of t is in the range of [0, 1].
- For two-dimensional Bezier curve, the number of control points is 1, as shown in the following figure:
- The continuous point Q0 from P0 to P1 describes a linear Bezier curve.
- The continuous point Q1 from P1 to P2 describes a linear Bezier curve.
- Describe a quadratic Bessel curve from the continuous point B (t) Q0 to Q1. That is, Q0 satisfies one-dimensional Bessel curve on P0P1; Q1 satisfies one-dimensional Bessel curve on P1P2. And B satisfies a one-dimensional Bessel curve on Q0Q1, which is tangent to the curve.
- For a three-dimensional Bezier curve, there are two control points. We can view the three-dimensional Bezier curve recursively:Firstly, Q0, Q1 and Q2 satisfy the one-dimensional Bezier equation on their respective line segments respectively. Then, Q0, Q1 and Q2 are used to carry out linear interpolation of two-dimensional Bezier curves, which can be refined into one-dimensional Bezier curves.
vector
Echarts smooth curve drawing
Conventional drawing method
Echarts uses three-dimensional Bezier curves for regular curve drawing, so each time a curve is drawn between two points, two control points need to be selected. Suppose we now have three points: P0, P1, and P2. Point P1 is in the middle of point P0 and point P2. When drawing the curve of point P0 and point P1, two control points M1 and M2 are selected. When drawing the curves of P1 and P2, two control points, N1 and N2, were selected. So if you have M2, P1, N1. As shown in the figure below: However, Echarts will judge and adjust the maximum and minimum X and Y coordinates of all points after calculating the control point M1 and M2 in actual processing to ensure that the control point does not exceed this area. The calculation method of control point M2 and control point N1 is very similar:
Without adjusting M2 and N1, these two points must be on the same straight line with P1, so the curves on both sides of P1 should also be smooth and excessive, without mutation. However, Echarts has an extra step to judge and adjust the maximum and minimum X and y coordinates of all points, which leads to the change of the coordinates of one or two points in M2 and N1, resulting in three different lines, which makes the tangent lines of two line segments on both sides of the end point at the end point abrupt change.
The new algorithm
Echarts’s new algorithm keeps both sides of each control point horizontal or vertical, depending on which direction (x/y) the data points are monotonically increasing. The advantage of this is that each point is continuous and there are derivatives, the resulting curve is differentiable, there is no mutation. The disadvantage is that it cannot handle non-monotonic cases (for example, point coordinates are: [100, 100], [200, 200], [150, 180]). The new algorithm can be started using the smoothMonotone field, the effect is as follows:
reference
- Echarts determines how to draw the curve
function drawSegment(ctx, points, start, segLen, allLen, dir, smoothMin, smoothMax, smooth, smoothMonotone, connectNulls) {
if (smoothMonotone === "none"| |! smoothMonotone) {// Conventional methods
return drawNonMono.apply(this.arguments);
} else {
return drawMono.apply(this.arguments); }}Copy the code
- Echarts general method of drawing smooth curves
// Draw smoothed line in non-monotone, in may cause undesired curve in extreme
// situations. This should be used when points are non-monotone neither in x or
// y dimension.
function drawNonMono(ctx, points, start, segLen, allLen, dir, smoothMin, smoothMax, smooth, smoothMonotone, connectNulls) {
var prevIdx = 0;
var idx = start;
for (var k = 0; k < segLen; k++) {
// segLen: points length
var p = points[idx];
if (idx >= allLen || idx < 0) {
// Points array index is out of bounds
break;
}
if (isPointNull(p)) {
if (connectNulls) {
//connectNulls skips this value when true and concatenates the first value with the next
idx += dir; // dir === 1
continue;
}
break;
}
if (idx === start) {
// Start point: move to that point; Non-starting point: Draw a line
ctx[dir > 0 ? "moveTo" : "lineTo"](p[0], p[1]);
v2Copy(cp0, p);
} else {
if (smooth > 0) {
// Whether to draw smoothly
var nextIdx = idx + dir;
var nextP = points[nextIdx];
if (connectNulls) {
// Find next point not null
while(nextP && isPointNull(points[nextIdx])) { nextIdx += dir; nextP = points[nextIdx]; }}var ratioNextSeg = 0.5;
var prevP = points[prevIdx];
var nextP = points[nextIdx];
// Last point
if(! nextP || isPointNull(nextP)) { v2Copy(cp1, p); }else {
// If next data is null in not connect case
if(isPointNull(nextP) && ! connectNulls) { nextP = p; } vec2.sub(v, nextP, prevP);// v = nextP - prevP
var lenPrevSeg;
var lenNextSeg;
if (smoothMonotone === "x" || smoothMonotone === "y") {
var dim = smoothMonotone === "x" ? 0 : 1;
lenPrevSeg = Math.abs(p[dim] - prevP[dim]);
lenNextSeg = Math.abs(p[dim] - nextP[dim]);
} else {
lenPrevSeg = vec2.dist(p, prevP);
lenNextSeg = vec2.dist(p, nextP);
}
// Use ratio of seg length
ratioNextSeg = lenNextSeg / (lenNextSeg + lenPrevSeg);
scaleAndAdd(cp1, p, v, -smooth * (1 - ratioNextSeg));
}
// constraint is a constraint
vec2Min(cp0, cp0, smoothMax); // cp0 = min(lastP, smoothMax)
vec2Max(cp0, cp0, smoothMin); // cp0 = max(cp0, smoothMin)
vec2Min(cp1, cp1, smoothMax); // cp1 = min(modifiedP, smoothMax)
vec2Max(cp1, cp1, smoothMin); // cp1 = max(cp1, smoothMin)
ctx.bezierCurveTo(
// Control point 1: cp0; Control point 2: CP1; The end: p
cp0[0],
cp0[1],
cp1[0],
cp1[1],
p[0],
p[1]);// cp0 of next segment
scaleAndAdd(cp0, p, v, smooth * ratioNextSeg);
} else {
ctx.lineTo(p[0], p[1]);
}
}
prevIdx = idx;
idx += dir;
}
return k;
}
Copy the code
- Echarts new algorithm for calculating smooth curves
// Draw smoothed line in monotone, in which only vertical or horizontal bezier
// control points will be used. This should be used when points are monotone
// either in x or y dimension.
function drawMono(ctx, points, start, segLen, allLen, dir, smoothMin, smoothMax, smooth, smoothMonotone, connectNulls) {
var prevIdx = 0;
var idx = start;
for (var k = 0; k < segLen; k++) {
var p = points[idx];
if (idx >= allLen || idx < 0) {
break;
}
if (isPointNull(p)) {
if (connectNulls) {
idx += dir;
continue;
}
break;
}
if (idx === start) {
ctx[dir > 0 ? "moveTo" : "lineTo"](p[0], p[1]);
} else {
if (smooth > 0) {
var prevP = points[prevIdx];
var dim = smoothMonotone === "y" ? 1 : 0;
// Length of control point to p, either in x or y, but not both
var ctrlLen = (p[dim] - prevP[dim]) * smooth;
v2Copy(cp0, prevP);
cp0[dim] = prevP[dim] + ctrlLen;
v2Copy(cp1, p);
cp1[dim] = p[dim] - ctrlLen;
ctx.bezierCurveTo(cp0[0], cp0[1], cp1[0], cp1[1], p[0], p[1]);
} else {
ctx.lineTo(p[0], p[1]);
}
}
prevIdx = idx;
idx += dir;
}
return k;
}
Copy the code
🏆 technology project phase iii | data visualization of those things…