Skip to content

Commit 9c03d93

Browse files
committed
fix: cubic bezier mode fix
1 parent f4efbb4 commit 9c03d93

File tree

1 file changed

+94
-51
lines changed

1 file changed

+94
-51
lines changed

src/charting/renderer/LineChartRenderer.ts

+94-51
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,65 @@ import { Utils } from '../utils/Utils';
1414
import { ViewPortHandler } from '../utils/ViewPortHandler';
1515
import { LineRadarRenderer } from './LineRadarRenderer';
1616

17+
interface XYPoint {
18+
x: number;
19+
y: number;
20+
}
21+
function distanceBetweenPoints(pt1: XYPoint, pt2: XYPoint) {
22+
return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
23+
}
24+
function almostEquals(x, y, epsilon) {
25+
return Math.abs(x - y) < epsilon;
26+
}
27+
function _isPointInArea(point: XYPoint, area, margin = 0.5) {
28+
// margin - default is to match rounded decimals
29+
30+
return point && point.x > area.left - margin && point.x < area.right + margin && point.y > area.top - margin && point.y < area.bottom + margin;
31+
}
32+
const sign = Math.sign;
33+
export function splineCurve(firstPoint: XYPoint, middlePoint: XYPoint, afterPoint: XYPoint, tension: number) {
34+
// Props to Rob Spencer at scaled innovation for his post on splining between points
35+
// http://scaledinnovation.com/analytics/splines/aboutSplines.html
36+
37+
// This function must also respect "skipped" points
38+
39+
const previous = firstPoint;
40+
const current = middlePoint;
41+
const next = afterPoint;
42+
const d01 = distanceBetweenPoints(current, previous);
43+
const d12 = distanceBetweenPoints(next, current);
44+
45+
let s01 = d01 / (d01 + d12);
46+
let s12 = d12 / (d01 + d12);
47+
48+
// If all points are the same, s01 & s02 will be inf
49+
s01 = isNaN(s01) ? 0 : s01;
50+
s12 = isNaN(s12) ? 0 : s12;
51+
52+
const fa = tension * s01; // scaling factor for triangle Ta
53+
const fb = tension * s12;
54+
55+
return {
56+
previous: {
57+
x: current.x - fa * (next.x - previous.x),
58+
y: current.y - fa * (next.y - previous.y)
59+
},
60+
next: {
61+
x: current.x + fb * (next.x - previous.x),
62+
y: current.y + fb * (next.y - previous.y)
63+
}
64+
};
65+
}
66+
function getXYValue(dataSet, index) {
67+
const xKey = dataSet.xProperty;
68+
const yKey = dataSet.yProperty;
69+
const entry = dataSet.getEntryForIndex(index);
70+
if (entry[yKey] === undefined || entry[yKey] === null) {
71+
return null;
72+
}
73+
return { x: getEntryXValue(entry, xKey, index), y: entry[yKey] };
74+
}
75+
1776
// fix drawing "too" thin paths on iOS
1877

1978
export class DataSetImageCache {
@@ -279,6 +338,7 @@ export class LineChartRenderer extends LineRadarRenderer {
279338
return [];
280339
}
281340
}
341+
282342
@profile
283343
generateCubicPath(dataSet: ILineDataSet, outputPath: Path) {
284344
if (this.mXBounds.range >= 1) {
@@ -291,10 +351,6 @@ export class LineChartRenderer extends LineRadarRenderer {
291351
const xKey = dataSet.xProperty;
292352
const yKey = dataSet.yProperty;
293353
const intensity = dataSet.getCubicIntensity();
294-
let prevDx = 0;
295-
let prevDy = 0;
296-
let curDx = 0;
297-
let curDy = 0;
298354

299355
// Take an extra polet from the left, and an extra from the right.
300356
// That's because we need 4 points for a cubic bezier (cubic=4), otherwise we get lines moving and doing weird stuff on the edges of the chart.
@@ -305,57 +361,45 @@ export class LineChartRenderer extends LineRadarRenderer {
305361
// let firstIndex = this.mXBounds.min + 1;
306362
const lastIndex = this.mXBounds.min + this.mXBounds.range;
307363

308-
let i = Math.max(firstIndex - 2, 0);
309-
let prevPrev = dataSet.getEntryForIndex(i);
310-
let prevPrevXVal = getEntryXValue(prevPrev, xKey, i);
311-
i = Math.max(firstIndex - 1, 0);
312-
let prev = dataSet.getEntryForIndex(i);
313-
let prevXVal = getEntryXValue(prev, xKey, i);
314-
i = Math.max(firstIndex, 0);
315-
let cur = dataSet.getEntryForIndex(i);
316-
let curXVal = getEntryXValue(cur, xKey, i);
317-
let next = cur;
318-
let nextXVal = curXVal;
319-
let nextIndex = -1;
320-
321-
if (cur == null) return [];
322-
323364
const float32arr = this.mLineBuffer;
324365
let index = 0;
325-
// outputPath.reset();
326-
// outputPath.moveTo(curXVal, cur[yKey] * phaseY);
327-
float32arr[index++] = curXVal;
328-
float32arr[index++] = cur[yKey] * phaseY;
329-
// let the spline start
330-
for (let j = firstIndex + 1; j <= lastIndex; j++) {
331-
const newEntry = dataSet.getEntryForIndex(j);
332-
if (newEntry[yKey] === undefined || newEntry[yKey] === null) {
366+
let nextIndex = -1;
367+
let next: XYPoint;
368+
let controlPoints;
369+
let point: XYPoint;
370+
let prev: XYPoint;
371+
let prevControlPoints;
372+
for (let j = firstIndex; j <= lastIndex; j++) {
373+
point = getXYValue(dataSet, j);
374+
if (!point) {
375+
if (j === 0) {
376+
return [];
377+
}
333378
continue;
334379
}
335-
prevPrev = prev;
336-
prevPrevXVal = prevXVal;
337-
prev = cur;
338-
prevXVal = curXVal;
339-
cur = nextIndex === j ? next : newEntry;
340-
curXVal = nextIndex === j ? nextXVal : getEntryXValue(newEntry, xKey, j);
341-
nextIndex = Math.min(j + 1, dataSet.getEntryCount() - 1);
342-
next = dataSet.getEntryForIndex(nextIndex);
343-
nextXVal = getEntryXValue(next, xKey, nextIndex);
344-
if (next[yKey] === undefined || next[yKey] === null) {
380+
if (!prev) {
381+
prev = point;
382+
}
383+
nextIndex = j + 1 < dataSet.getEntryCount() ? j + 1 : j;
384+
next = getXYValue(dataSet, nextIndex);
385+
if (!next) {
345386
continue;
346387
}
347-
prevDx = (curXVal - prevPrevXVal) * intensity;
348-
prevDy = (cur[yKey] - prevPrev[yKey]) * intensity;
349-
curDx = (nextXVal - prevXVal) * intensity;
350-
curDy = (next[yKey] - prev[yKey]) * intensity;
351-
352-
float32arr[index++] = prevXVal + prevDx;
353-
float32arr[index++] = (prev[yKey] + prevDy) * phaseY;
354-
float32arr[index++] = curXVal - curDx;
355-
float32arr[index++] = (cur[yKey] - curDy) * phaseY;
356-
float32arr[index++] = curXVal;
357-
float32arr[index++] = cur[yKey] * phaseY;
358-
// outputPath.cubicTo(prevXVal + prevDx, (prev[yKey] + prevDy) * phaseY, curXVal - curDx, (cur[yKey] - curDy) * phaseY, curXVal, cur[yKey] * phaseY);
388+
controlPoints = splineCurve(prev, point, next, intensity);
389+
if (j === 0) {
390+
float32arr[index++] = point.x;
391+
float32arr[index++] = point.y * phaseY;
392+
} else {
393+
float32arr[index++] = prevControlPoints.next.x;
394+
float32arr[index++] = prevControlPoints.next.y * phaseY;
395+
float32arr[index++] = controlPoints.previous.x;
396+
float32arr[index++] = controlPoints.previous.y * phaseY;
397+
float32arr[index++] = point.x;
398+
float32arr[index++] = point.y * phaseY;
399+
}
400+
401+
prevControlPoints = controlPoints;
402+
prev = point;
359403
}
360404
const points = Utils.pointsFromBuffer(float32arr);
361405
outputPath.setCubicLines(points, 0, index);
@@ -536,6 +580,7 @@ export class LineChartRenderer extends LineRadarRenderer {
536580
const renderPaint = this.renderPaint;
537581
let paintColorsShader;
538582
if (nbColors > 1) {
583+
// TODO: we transforms points in there. Could be dangerous if used after
539584
paintColorsShader = this.getMultiColorsShader(colors, points, trans, dataSet, renderPaint);
540585
}
541586

@@ -562,8 +607,6 @@ export class LineChartRenderer extends LineRadarRenderer {
562607
oldShader = renderPaint.getShader();
563608
renderPaint.setShader(paintColorsShader);
564609
}
565-
// trans.pointValuesToPixel(points);
566-
// this.drawLines(c, points, 0,index, renderPaint);
567610
trans.pathValueToPixel(linePath);
568611
this.drawPath(c, linePath, renderPaint);
569612
if (paintColorsShader) {

0 commit comments

Comments
 (0)