@@ -14,6 +14,65 @@ import { Utils } from '../utils/Utils';
14
14
import { ViewPortHandler } from '../utils/ViewPortHandler' ;
15
15
import { LineRadarRenderer } from './LineRadarRenderer' ;
16
16
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
+
17
76
// fix drawing "too" thin paths on iOS
18
77
19
78
export class DataSetImageCache {
@@ -279,6 +338,7 @@ export class LineChartRenderer extends LineRadarRenderer {
279
338
return [ ] ;
280
339
}
281
340
}
341
+
282
342
@profile
283
343
generateCubicPath ( dataSet : ILineDataSet , outputPath : Path ) {
284
344
if ( this . mXBounds . range >= 1 ) {
@@ -291,10 +351,6 @@ export class LineChartRenderer extends LineRadarRenderer {
291
351
const xKey = dataSet . xProperty ;
292
352
const yKey = dataSet . yProperty ;
293
353
const intensity = dataSet . getCubicIntensity ( ) ;
294
- let prevDx = 0 ;
295
- let prevDy = 0 ;
296
- let curDx = 0 ;
297
- let curDy = 0 ;
298
354
299
355
// Take an extra polet from the left, and an extra from the right.
300
356
// 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 {
305
361
// let firstIndex = this.mXBounds.min + 1;
306
362
const lastIndex = this . mXBounds . min + this . mXBounds . range ;
307
363
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
-
323
364
const float32arr = this . mLineBuffer ;
324
365
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
+ }
333
378
continue ;
334
379
}
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 ) {
345
386
continue ;
346
387
}
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 ;
359
403
}
360
404
const points = Utils . pointsFromBuffer ( float32arr ) ;
361
405
outputPath . setCubicLines ( points , 0 , index ) ;
@@ -536,6 +580,7 @@ export class LineChartRenderer extends LineRadarRenderer {
536
580
const renderPaint = this . renderPaint ;
537
581
let paintColorsShader ;
538
582
if ( nbColors > 1 ) {
583
+ // TODO: we transforms points in there. Could be dangerous if used after
539
584
paintColorsShader = this . getMultiColorsShader ( colors , points , trans , dataSet , renderPaint ) ;
540
585
}
541
586
@@ -562,8 +607,6 @@ export class LineChartRenderer extends LineRadarRenderer {
562
607
oldShader = renderPaint . getShader ( ) ;
563
608
renderPaint . setShader ( paintColorsShader ) ;
564
609
}
565
- // trans.pointValuesToPixel(points);
566
- // this.drawLines(c, points, 0,index, renderPaint);
567
610
trans . pathValueToPixel ( linePath ) ;
568
611
this . drawPath ( c , linePath , renderPaint ) ;
569
612
if ( paintColorsShader ) {
0 commit comments