-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathslope.go
More file actions
385 lines (333 loc) · 9.49 KB
/
slope.go
File metadata and controls
385 lines (333 loc) · 9.49 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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
package num
import "math"
const (
// 1度=多少弧度
radiansPreDegrees = math.Pi / 180.00
// 1弧度=多少度
degreesPreRadian = 180.00 / math.Pi
)
// Slope 计算斜率
func Slope(x1 int, y1 float64, x2 int, y2 float64) float64 {
return (y2 - y1) / float64(x2-x1)
}
// TriangleBevel 三角形斜边
func TriangleBevel(slope float64, x1 int, y1 float64, xn int) float64 {
return slope*float64(xn-x1) + y1
}
// degreesToRadian 角度转弧度
//
// 弧度=角度÷180×π
func degreesToRadian(degrees float64) float64 {
// degrees * math.Pi / 180
return degrees * radiansPreDegrees
}
// radianToDegrees 弧度转角度
//
// 角度=弧度×180÷π
func radianToDegrees(radian float64) float64 {
// (radian * 180) / math.Pi
return radian * degreesPreRadian
}
// DegreesToSlope 角度转斜率
func DegreesToSlope(x float64) float64 {
radians := degreesToRadian(x)
return math.Tan(radians)
}
// SlopeToDegrees 斜率转角度
func SlopeToDegrees(m float64) float64 {
// math.Atan(m) * 180 / math.Pi
radians := math.Atan(m)
degrees := radianToDegrees(radians)
return degrees
}
// Point 点
type Point struct {
X float64 // x 轴
Y float64 // y 轴
}
func NewPoint[T1 Number, T2 Number](x T1, y T2) Point {
return Point{X: float64(x), Y: float64(y)}
}
func (this Point) Add(p Point) Point {
return Point{X: this.X + p.X, Y: this.Y + p.Y}
}
func (this Point) ToDataPoint() DataPoint {
return DataPoint{X: int(this.X), Y: this.Y}
}
// DataPoint 数据点, X轴为时间类切片的索引, Y轴为具体数值
type DataPoint struct {
X int // Y切片的索引
Y float64 // 值
}
func (this DataPoint) ToPoint() Point {
return Point{X: float64(this.X), Y: this.Y}
}
// Line LineEquation 线性方程式
//
// ① Ax + By + C = 0
// ② 斜率k = -(A/B)
// ③ x截距 = -(C/A), y截距 = -(C/B)
type Line struct {
slope float64 // 斜率, k
intercept float64 // 截距, b
offset float64 // 偏移量, 左边点的x周期起始点
}
// CalculateLineEquation 已知两个点, 计算线性方程式
func CalculateLineEquation(point1, point2 Point) Line {
m := (point2.Y - point1.Y) / (point2.X - point1.X)
c := point1.Y - m*point1.X
line := Line{slope: m, intercept: c, offset: point1.X}
return line
}
// Radian 弧度
func (this Line) Radian() float64 {
return math.Atan(this.slope)
}
// Degrees 计算角度
func (this Line) Degrees() float64 {
return this.Radian() * degreesPreRadian
}
// Angle 计算两条直线之间的角度
func (this Line) Angle(other Line) float64 {
return this.v3Angle(other)
}
func (this Line) v1Angle(other Line) float64 {
theta := math.Atan(other.slope-this.slope) * degreesPreRadian
return theta
}
func (this Line) v2Angle(other Line) float64 {
m := other.slope * this.slope
if m == -1 {
if other.slope < 0 {
return -90
} else {
return 90
}
}
theta := math.Atan((other.slope-this.slope)/(1+m)) * degreesPreRadian
return theta
}
func (this Line) v3Angle(other Line) float64 {
angle1 := other.Degrees()
angle2 := this.Degrees()
theta := angle1 - angle2
return theta
}
// Equation 返回方程式 A, B, C
func (this Line) Equation() (A, B, C float64) {
A = this.slope
B = -1.0
C = this.intercept
return
}
// Distance 点到线的距离
//
// ___________
// 公式 |(Ax0 + By0 + C)| / √(A^2 + B^2)
func (this Line) Distance(p Point) float64 {
A, B, C := this.Equation()
numerator := A*p.X + B*p.Y + C
//numerator = math.Abs(numerator)
denominator := math.Sqrt(A*A + B*B)
distance := numerator / denominator
return distance
}
// HorizontalDistance 水平方向距离
//
// p点到线的水平方向上的距离
func (this Line) HorizontalDistance(p Point) float64 {
// 计算水平方向相交点, Y轴相等
x := this.X(p.Y)
return x - p.X
}
// VerticalDistance 垂直方向距离
//
// p点到线的垂直方向上的距离
func (this Line) VerticalDistance(p Point) float64 {
y := this.Y(p.X)
return y - p.Y
}
// ShortestDistance 计算 与另外一条直线的最短距离
func (this Line) ShortestDistance(other Line) float64 {
distance := (other.intercept - this.intercept) / math.Sqrt(1+this.slope*this.slope)
return distance
}
// ParallelLine 通过 一个点的y轴坐标计算一个新的平行线
func (this Line) ParallelLine(p Point) Line {
newIntercept := p.Y - this.slope*p.X
return Line{slope: this.slope, intercept: newIntercept, offset: p.X}
}
// X 通过 y 轴坐标计算 x轴坐标
func (this Line) X(y float64) float64 {
x := (y - this.intercept) / this.slope
return x
}
// Y 通过 x 轴坐标计算 y轴坐标
func (this Line) Y(x float64) float64 {
y := this.slope*x + this.intercept
return y
}
// Incr 增量方式得出line延伸的x轴和y轴
func (this Line) Incr(n int) (x, y float64) {
xAxis := this.offset + float64(n)
yAxis := this.Y(xAxis)
return xAxis, yAxis
}
// SymmetricParallelLine 计算 点对称(等距离)的平行线
func (this Line) SymmetricParallelLine(p Point) Line {
return this.v2SymmetricParallelLine(p)
}
// SymmetricParallelLine 计算 点对称(等距离)的平行线
func (this Line) v1SymmetricParallelLine(p Point) Line {
// 1. 确定点p到line的距离
distance := this.Distance(p)
// 2. 规划直角三角形
// 2.1 斜边 Hypotenuse
hypotenuse := distance
// 2.2 计算 斜边于底边的角度
lineDegrees := this.Degrees()
alpha := 180 - lineDegrees
radian := degreesToRadian(alpha)
// 2.3 底边 x, Opposite side
opposite := hypotenuse * math.Sin(radian)
// 2.4 邻边 y, Adjacent side
adjacent := hypotenuse * math.Cos(radian)
// 3. 计算新的平行线
newPoint := p.Add(Point{X: opposite, Y: adjacent})
newLine := this.ParallelLine(newPoint)
//// 验证2x距离
//d := this.Distance(newPoint)
//_ = d
return newLine
}
// SymmetricParallelLine 计算 点对称(等距离)的平行线
func (this Line) v2SymmetricParallelLine(p Point) Line {
// 1. 确定点p到line的垂直距离, 即以p的x轴确定line的y坐标
distance := this.VerticalDistance(p)
// 2. 确定平行线的点
newPoint := p.Add(Point{X: 0, Y: -distance})
// 3. 计算新的平行线
newLine := this.ParallelLine(newPoint)
//// 验证2x距离
//d := this.Distance(newPoint)
//_ = d
return newLine
}
// 趋势变化
const (
TendencyUnchanged = 0 // 趋势不变
TendencyBreakThrough = 1 // break through 突破
TendencyFallDrastically = -1 // fall drastically 跌破
)
// Extend 延伸线
func (this Line) Extend(data []float64, digits int) (X, Y []float64, tendency int) {
offset := int(this.offset)
count := len(data)
length := count - offset
x := make([]float64, length)
y := make([]float64, length)
for i := 0; i < length; i++ {
x[i] = float64(i + offset)
y[i] = this.Y(x[i])
y[i] = Decimal(y[i], digits)
}
X = x
Y = y
tendency = TendencyUnchanged
if length >= 2 {
d1 := data[count-1]
d2 := data[count-2]
y1 := y[length-1]
y2 := y[length-2]
if d2 < y2 && d1 > y1 {
tendency = TendencyBreakThrough
} else if d2 > y2 && d1 < y1 {
tendency = TendencyFallDrastically
}
}
return
}
// Analyze 分析线性趋势
func (this Line) Analyze(data []float64, digits int) (X, Y []float64, tendency []LinerTrend) {
offset := int(this.offset)
count := len(data)
length := count - offset
x := make([]float64, length)
y := make([]float64, length)
for i := 0; i < length; i++ {
x[i] = float64(i + offset)
y[i] = this.Y(x[i])
y[i] = Decimal(y[i], digits)
}
X = x
Y = y
tendency = Cross(data[offset:], y)
return
}
// Intersect 计算当前直线与另一条直线的交点。
//
// 如果两直线平行(包括重合),则返回 (0, 0) 和 false。
// 否则返回交点坐标和 true。
func (this Line) Intersect(other Line) (Point, bool) {
m1, b1 := this.slope, this.intercept
m2, b2 := other.slope, other.intercept
// 判断是否平行:斜率相等
if m1 == m2 {
return Point{}, false // 无唯一交点
}
// 解方程:x = (b2 - b1) / (m1 - m2)
x := (b2 - b1) / (m1 - m2)
// 代入第一条直线求 y
y := m1*x + b1
return Point{X: x, Y: y}, true
}
// FitLine 使用最小二乘法对给定的数据点 (x, y) 进行线性回归拟合,得到一条直线
//
// 返回值:
//
// Line: 拟合的直线(slope: 斜率, intercept: 截距, offset: 第一个x值)
// bool: 是否成功拟合(false 表示数据无效或无法确定唯一直线,如垂直线趋势)
//
// 数学模型:y = slope * x + intercept
//
// 条件:
// - len(x) == len(y) 且 ≥ 2
// - x 的值不能全部相同(否则斜率无定义,退化为垂直线,无法用斜截式表示)
func FitLine(x, y []float64) (Line, bool) {
n := len(x)
// 检查长度一致性
if n == 0 || len(y) != n {
return Line{}, false
}
// 至少需要两个点才能定义一条直线
if n < 2 {
// 单点情况:无法确定斜率,返回水平线(可选策略),但标记为不成功
return Line{slope: 0, intercept: y[0], offset: x[0]}, false
}
// 累加项
var sumX, sumY, sumXY, sumXX float64
for i := 0; i < n; i++ {
sumX += x[i]
sumY += y[i]
sumXY += x[i] * y[i]
sumXX += x[i] * x[i]
}
nf := float64(n)
denom := nf*sumXX - sumX*sumX // 分母:nΣx² - (Σx)²
// 判断是否所有 x 几乎相同(趋近于垂直线)
const epsilon = 1e-10
if math.Abs(denom) < epsilon {
return Line{}, false // 无法确定斜率(垂直线趋势),返回失败
}
// 计算斜率 k 和截距 b
slope := (nf*sumXY - sumX*sumY) / denom
intercept := (sumY - slope*sumX) / nf
// offset 设为第一个 x 值,便于后续 Extend / Incr 使用
offset := x[0]
line := Line{
slope: slope,
intercept: intercept,
offset: offset,
}
return line, true
}