Repository navigation
Expand file tree
/
Copy pathsimplify.go
More file actions
98 lines (94 loc) · 2.76 KB
/
Copy pathsimplify.go
File metadata and controls
98 lines (94 loc) · 2.76 KB
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
// Copyright (c) 2026 the go-plot/plot authors. All rights reserved.
// Use of this source code is governed by a BSD-3-Clause license that can be
// found in the LICENSE file at the root of this repository.
package plot
import "github.com/go-plot/plot/render"
// simplifyThreshold is matplotlib's path.simplify_threshold, in pixels.
const simplifyThreshold = 1.0 / 9.0
// simplify is matplotlib's PathSimplifier, applied to a polyline in pixels:
// runs of segments that stay within simplifyThreshold of one direction are
// merged into the longest excursion forward and the longest backward, which
// leaves the drawn line indistinguishable and turns a million points across
// a few hundred pixels into a few thousand. Like matplotlib it is applied to
// lines of 128 points or more.
func simplify(pts []render.Pt) []render.Pt {
if len(pts) < 128 {
return pts
}
thr := simplifyThreshold * simplifyThreshold
out := make([]render.Pt, 0, 256)
out = append(out, pts[0])
last := pts[0]
var (
origdx, origdy, origNorm2 float64
fwdMax, bwdMax float64
lastFwdMax, lastBwdMax bool
start, next, nextBackward render.Pt
)
push := func(p render.Pt) {
if bwdMax > 0 {
if lastFwdMax {
out = append(out, nextBackward, next)
} else {
out = append(out, next, nextBackward)
}
} else {
out = append(out, next)
}
if !lastFwdMax && !lastBwdMax {
out = append(out, last)
}
origdx, origdy = p.X-last.X, p.Y-last.Y
origNorm2 = origdx*origdx + origdy*origdy
fwdMax, lastFwdMax = origNorm2, true
start = out[len(out)-1]
last, next = p, p
bwdMax, lastBwdMax = 0, false
}
for _, p := range pts[1:] {
if origNorm2 == 0 {
// No direction yet (the start, or a repeated point): this
// segment sets it.
origdx, origdy = p.X-last.X, p.Y-last.Y
origNorm2 = origdx*origdx + origdy*origdy
fwdMax, bwdMax = origNorm2, 0
lastFwdMax, lastBwdMax = true, false
start = last
next, last = p, p
continue
}
totdx, totdy := p.X-start.X, p.Y-start.Y
totdot := origdx*totdx + origdy*totdy
paradx, parady := totdot*origdx/origNorm2, totdot*origdy/origNorm2
perpdx, perpdy := totdx-paradx, totdy-parady
if perpdx*perpdx+perpdy*perpdy < thr {
paraNorm2 := paradx*paradx + parady*parady
lastFwdMax, lastBwdMax = false, false
if totdot > 0 {
if paraNorm2 > fwdMax {
lastFwdMax, fwdMax, next = true, paraNorm2, p
}
} else if paraNorm2 > bwdMax {
lastBwdMax, bwdMax, nextBackward = true, paraNorm2, p
}
last = p
continue
}
push(p)
}
if origNorm2 != 0 {
if bwdMax > 0 {
if lastFwdMax {
out = append(out, nextBackward, next)
} else {
out = append(out, next, nextBackward)
}
} else {
out = append(out, next)
}
if !lastFwdMax && !lastBwdMax {
out = append(out, last)
}
}
return out
}