-
Notifications
You must be signed in to change notification settings - Fork 2
/
line.go
109 lines (90 loc) · 2.38 KB
/
line.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
103
104
105
106
107
108
109
package plot
import "math"
// Line implements a simple line plot.
type Line struct {
Style
Label string
Data []Point
}
// NewLine creates a new line element from the given points.
func NewLine(label string, points []Point) *Line {
return &Line{
Label: label,
Data: points,
}
}
// Stats calculates element statistics.
func (line *Line) Stats() Stats {
return PointsStats(line.Data)
}
// Draw draws the element to canvas.
func (line *Line) Draw(plot *Plot, canvas Canvas) {
canvas = canvas.Clip(canvas.Bounds())
points := project(line.Data, plot.X, plot.Y, canvas.Bounds())
if !line.Style.IsZero() {
canvas.Poly(points, &line.Style)
} else {
canvas.Poly(points, &plot.Theme.Line)
}
}
// OptimizedLine implements a simple line plot.
type OptimizedLine struct {
Style
Label string
Data []Point
ThresholdPx float64
}
// NewOptimizedLine creates a new line element that tries to optimize drawing.
func NewOptimizedLine(label string, points []Point, thresholdPx float64) *OptimizedLine {
return &OptimizedLine{
Label: label,
ThresholdPx: thresholdPx,
Data: points,
}
}
// Stats calculates element statistics.
func (line *OptimizedLine) Stats() Stats {
return PointsStats(line.Data)
}
// Draw draws the element to canvas.
func (line *OptimizedLine) Draw(plot *Plot, canvas Canvas) {
canvas = canvas.Clip(canvas.Bounds())
points := project(line.Data, plot.X, plot.Y, canvas.Bounds())
const optimizeCount = 100
if len(points) < optimizeCount {
if !line.Style.IsZero() {
canvas.Poly(points, &line.Style)
} else {
canvas.Poly(points, &plot.Theme.Line)
}
return
}
// always include the first point
optimized := points[:1]
prev := points[0]
mid := points[1]
for _, next := range points[2:] {
// does the mid change significantly from the previous?
if math.Abs(prev.Y-mid.Y) < line.ThresholdPx && math.Abs(prev.X-mid.X) < line.ThresholdPx {
mid = next
continue
}
// is the mid on the line from prev to next?
p := invlerp(mid.X, prev.X, next.X)
knownY := lerp(p, prev.Y, next.Y)
if math.Abs(knownY-mid.X) < line.ThresholdPx {
mid = next
continue
}
// otherwise let's output
optimized = append(optimized, mid)
prev, mid = mid, next
}
// add the final point
optimized = append(optimized, points[len(points)-1])
if !line.Style.IsZero() {
canvas.Poly(optimized, &line.Style)
} else {
canvas.Poly(optimized, &plot.Theme.Line)
}
}