-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathlttb.go
102 lines (76 loc) · 2.44 KB
/
lttb.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// Package lttb implements the Largest-Triangle-Three-Buckets algorithm for downsampling points
/*
The downsampled data maintains the visual characteristics of the original line
using considerably fewer data points.
This is a translation of the javascript code at
https://github.com/sveinn-steinarsson/flot-downsample/
*/
package lttb
import (
"math"
"golang.org/x/exp/constraints"
)
// Point is a point on a line
type Point[T constraints.Float] struct {
X T
Y T
}
// LTTB down-samples the data to contain only threshold number of points that
// have the same visual shape as the original data
func LTTB[T constraints.Float](data []Point[T], threshold int) []Point[T] {
if threshold < 3 {
threshold = 3
}
if threshold >= len(data) {
return data // Nothing to do
}
sampled := make([]Point[T], 0, threshold)
// Bucket size. Leave room for start and end data points
every := float64(len(data)-2) / float64(threshold-2)
sampled = append(sampled, data[0]) // Always add the first point
bucketStart := 1
bucketCenter := int(math.Floor(every)) + 1
var a int
for i := 0; i < threshold-2; i++ {
bucketEnd := int(math.Floor(float64(i+2)*every)) + 1
// Calculate point average for next bucket (containing c)
avgRangeStart := bucketCenter
avgRangeEnd := bucketEnd
if avgRangeEnd >= len(data) {
avgRangeEnd = len(data)
}
avgRangeLength := T(avgRangeEnd - avgRangeStart)
var avgX, avgY T
for ; avgRangeStart < avgRangeEnd; avgRangeStart++ {
avgX += data[avgRangeStart].X
avgY += data[avgRangeStart].Y
}
avgX /= avgRangeLength
avgY /= avgRangeLength
// Get the range for this bucket
rangeOffs := bucketStart
rangeTo := bucketCenter
// Point a
pointAX := data[a].X
pointAY := data[a].Y
maxArea := T(-1.0)
var nextA int
for ; rangeOffs < rangeTo; rangeOffs++ {
// Calculate triangle area over three buckets
area := (pointAX-avgX)*(data[rangeOffs].Y-pointAY) - (pointAX-data[rangeOffs].X)*(avgY-pointAY)
// We only care about the relative area here.
// Calling math.Abs() is slower than squaring
area *= area
if area > maxArea {
maxArea = area
nextA = rangeOffs // Next a is this b
}
}
sampled = append(sampled, data[nextA]) // Pick this point from the bucket
a = nextA // This a is the next a (chosen b)
bucketStart = bucketCenter
bucketCenter = bucketEnd
}
sampled = append(sampled, data[len(data)-1]) // Always add last
return sampled
}