gitee.com/quant1x/num@v0.3.2/slope.go (about)

     1  package num
     2  
     3  import "math"
     4  
     5  const (
     6  	// 1度=多少弧度
     7  	radiansPreDegrees = math.Pi / 180.00
     8  	// 1弧度=多少度
     9  	degreesPreRadian = 180.00 / math.Pi
    10  )
    11  
    12  // Slope 计算斜率
    13  func Slope(x1 int, y1 float64, x2 int, y2 float64) float64 {
    14  	return (y2 - y1) / float64(x2-x1)
    15  }
    16  
    17  // TriangleBevel 三角形斜边
    18  func TriangleBevel(slope float64, x1 int, y1 float64, xn int) float64 {
    19  	return slope*float64(xn-x1) + y1
    20  }
    21  
    22  // degreesToRadian 角度转弧度
    23  //
    24  //	弧度=角度÷180×π
    25  func degreesToRadian(degrees float64) float64 {
    26  	// degrees * math.Pi / 180
    27  	return degrees * radiansPreDegrees
    28  }
    29  
    30  // radianToDegrees 弧度转角度
    31  //
    32  //	角度=弧度×180÷π
    33  func radianToDegrees(radian float64) float64 {
    34  	// (radian * 180) / math.Pi
    35  	return radian * degreesPreRadian
    36  }
    37  
    38  // DegreesToSlope 角度转斜率
    39  func DegreesToSlope(x float64) float64 {
    40  	radians := degreesToRadian(x)
    41  	return math.Tan(radians)
    42  }
    43  
    44  // SlopeToDegrees 斜率转角度
    45  func SlopeToDegrees(m float64) float64 {
    46  	// math.Atan(m) * 180 / math.Pi
    47  	radians := math.Atan(m)
    48  	degrees := radianToDegrees(radians)
    49  	return degrees
    50  }
    51  
    52  // Point 点
    53  type Point struct {
    54  	X float64 // x 轴
    55  	Y float64 // y 轴
    56  }
    57  
    58  func NewPoint[T1 Number, T2 Number](x T1, y T2) Point {
    59  	return Point{X: float64(x), Y: float64(y)}
    60  }
    61  
    62  func (this Point) Add(p Point) Point {
    63  	return Point{X: this.X + p.X, Y: this.Y + p.Y}
    64  }
    65  
    66  func (this Point) ToDataPoint() DataPoint {
    67  	return DataPoint{X: int(this.X), Y: this.Y}
    68  }
    69  
    70  // DataPoint 数据点, X轴为时间类切片的索引, Y轴为具体数值
    71  type DataPoint struct {
    72  	X int     // Y切片的索引
    73  	Y float64 // 值
    74  }
    75  
    76  func (this DataPoint) ToPoint() Point {
    77  	return Point{X: float64(this.X), Y: this.Y}
    78  }
    79  
    80  // Line LineEquation 线性方程式
    81  //
    82  //	① Ax + By + C = 0
    83  //	② 斜率k = -(A/B)
    84  //	③ x截距 = -(C/A), y截距 = -(C/B)
    85  type Line struct {
    86  	slope     float64 // 斜率, k
    87  	intercept float64 // 截距, b
    88  	offset    float64 // 偏移量, 左边点的x周期起始点
    89  }
    90  
    91  // CalculateLineEquation 已知两个点, 计算线性方程式
    92  func CalculateLineEquation(point1, point2 Point) Line {
    93  	m := (point2.Y - point1.Y) / (point2.X - point1.X)
    94  	c := point1.Y - m*point1.X
    95  
    96  	line := Line{slope: m, intercept: c, offset: point1.X}
    97  	return line
    98  }
    99  
   100  // Radian 弧度
   101  func (this Line) Radian() float64 {
   102  	return math.Atan(this.slope)
   103  }
   104  
   105  // Degrees 计算角度
   106  func (this Line) Degrees() float64 {
   107  	return this.Radian() * degreesPreRadian
   108  }
   109  
   110  // Angle 计算两条直线之间的角度
   111  func (this Line) Angle(other Line) float64 {
   112  	return this.v3Angle(other)
   113  }
   114  
   115  func (this Line) v1Angle(other Line) float64 {
   116  	theta := math.Atan(other.slope-this.slope) * degreesPreRadian
   117  	return theta
   118  }
   119  
   120  func (this Line) v2Angle(other Line) float64 {
   121  	m := other.slope * this.slope
   122  	if m == -1 {
   123  		if other.slope < 0 {
   124  			return -90
   125  		} else {
   126  			return 90
   127  		}
   128  	}
   129  	theta := math.Atan((other.slope-this.slope)/(1+m)) * degreesPreRadian
   130  	return theta
   131  }
   132  
   133  func (this Line) v3Angle(other Line) float64 {
   134  	angle1 := other.Degrees()
   135  	angle2 := this.Degrees()
   136  	theta := angle1 - angle2
   137  	return theta
   138  }
   139  
   140  // Equation 返回方程式 A, B, C
   141  func (this Line) Equation() (A, B, C float64) {
   142  	A = this.slope
   143  	B = -1.0
   144  	C = this.intercept
   145  	return
   146  }
   147  
   148  // Distance 点到线的距离
   149  //
   150  //	                         ___________
   151  //	公式 |(Ax0 + By0 + C)| / √(A^2 + B^2)
   152  func (this Line) Distance(p Point) float64 {
   153  	A, B, C := this.Equation()
   154  	numerator := A*p.X + B*p.Y + C
   155  	//numerator = math.Abs(numerator)
   156  	denominator := math.Sqrt(A*A + B*B)
   157  	distance := numerator / denominator
   158  	return distance
   159  }
   160  
   161  // HorizontalDistance 水平方向距离
   162  //
   163  //	p点到线的水平方向上的距离
   164  func (this Line) HorizontalDistance(p Point) float64 {
   165  	// 计算水平方向相交点, Y轴相等
   166  	x := this.X(p.Y)
   167  	return x - p.X
   168  }
   169  
   170  // VerticalDistance 垂直方向距离
   171  //
   172  //	p点到线的垂直方向上的距离
   173  func (this Line) VerticalDistance(p Point) float64 {
   174  	y := this.Y(p.X)
   175  	return y - p.Y
   176  }
   177  
   178  // ShortestDistance 计算 与另外一条直线的最短距离
   179  func (this Line) ShortestDistance(other Line) float64 {
   180  	distance := (other.intercept - this.intercept) / math.Sqrt(1+this.slope*this.slope)
   181  	return distance
   182  }
   183  
   184  // ParallelLine 通过 一个点的y轴坐标计算一个新的平行线
   185  func (this Line) ParallelLine(p Point) Line {
   186  	newIntercept := p.Y - this.slope*p.X
   187  	return Line{slope: this.slope, intercept: newIntercept, offset: p.X}
   188  }
   189  
   190  // X 通过 y 轴坐标计算 x轴坐标
   191  func (this Line) X(y float64) float64 {
   192  	x := (y - this.intercept) / this.slope
   193  	return x
   194  }
   195  
   196  // Y 通过 x 轴坐标计算 y轴坐标
   197  func (this Line) Y(x float64) float64 {
   198  	y := this.slope*x + this.intercept
   199  	return y
   200  }
   201  
   202  // Incr 增量方式得出line延伸的x轴和y轴
   203  func (this Line) Incr(n int) (x, y float64) {
   204  	xAxis := this.offset + float64(n)
   205  	yAxis := this.Y(xAxis)
   206  	return xAxis, yAxis
   207  }
   208  
   209  // SymmetricParallelLine 计算 点对称(等距离)的平行线
   210  func (this Line) SymmetricParallelLine(p Point) Line {
   211  	return this.v2SymmetricParallelLine(p)
   212  }
   213  
   214  // SymmetricParallelLine 计算 点对称(等距离)的平行线
   215  func (this Line) v1SymmetricParallelLine(p Point) Line {
   216  	// 1. 确定点p到line的距离
   217  	distance := this.Distance(p)
   218  	// 2. 规划直角三角形
   219  	// 2.1 斜边 Hypotenuse
   220  	hypotenuse := distance
   221  	// 2.2 计算 斜边于底边的角度
   222  	lineDegrees := this.Degrees()
   223  	alpha := 180 - lineDegrees
   224  	radian := degreesToRadian(alpha)
   225  	// 2.3 底边 x, Opposite side
   226  	opposite := hypotenuse * math.Sin(radian)
   227  	// 2.4 邻边 y, Adjacent side
   228  	adjacent := hypotenuse * math.Cos(radian)
   229  	// 3. 计算新的平行线
   230  	newPoint := p.Add(Point{X: opposite, Y: adjacent})
   231  	newLine := this.ParallelLine(newPoint)
   232  	//// 验证2x距离
   233  	//d := this.Distance(newPoint)
   234  	//_ = d
   235  	return newLine
   236  }
   237  
   238  // SymmetricParallelLine 计算 点对称(等距离)的平行线
   239  func (this Line) v2SymmetricParallelLine(p Point) Line {
   240  	// 1. 确定点p到line的垂直距离, 即以p的x轴确定line的y坐标
   241  	distance := this.VerticalDistance(p)
   242  	// 2. 确定平行线的点
   243  	newPoint := p.Add(Point{X: 0, Y: -distance})
   244  	// 3. 计算新的平行线
   245  	newLine := this.ParallelLine(newPoint)
   246  	//// 验证2x距离
   247  	//d := this.Distance(newPoint)
   248  	//_ = d
   249  	return newLine
   250  }
   251  
   252  // 趋势变化
   253  const (
   254  	TendencyUnchanged       = 0  // 趋势不变
   255  	TendencyBreakThrough    = 1  // break through 突破
   256  	TendencyFallDrastically = -1 // fall drastically 跌破
   257  )
   258  
   259  // Extend 延伸线
   260  func (this Line) Extend(data []float64, digits int) (X, Y []float64, tendency int) {
   261  	offset := int(this.offset)
   262  	count := len(data)
   263  	length := count - offset
   264  	x := make([]float64, length)
   265  	y := make([]float64, length)
   266  	for i := 0; i < length; i++ {
   267  		x[i] = float64(i + offset)
   268  		y[i] = this.Y(x[i])
   269  		y[i] = Decimal(y[i], digits)
   270  	}
   271  	X = x
   272  	Y = y
   273  	tendency = TendencyUnchanged
   274  	if length >= 2 {
   275  		d1 := data[count-1]
   276  		d2 := data[count-2]
   277  		y1 := y[length-1]
   278  		y2 := y[length-2]
   279  		if d2 < y2 && d1 > y1 {
   280  			tendency = TendencyBreakThrough
   281  		} else if d2 > y2 && d1 < y1 {
   282  			tendency = TendencyFallDrastically
   283  		}
   284  	}
   285  	return
   286  }
   287  
   288  // Analyze 分析线性趋势
   289  func (this Line) Analyze(data []float64, digits int) (X, Y []float64, tendency []LinerTrend) {
   290  	offset := int(this.offset)
   291  	count := len(data)
   292  	length := count - offset
   293  	x := make([]float64, length)
   294  	y := make([]float64, length)
   295  	for i := 0; i < length; i++ {
   296  		x[i] = float64(i + offset)
   297  		y[i] = this.Y(x[i])
   298  		y[i] = Decimal(y[i], digits)
   299  	}
   300  	X = x
   301  	Y = y
   302  	tendency = Cross(data[offset:], y)
   303  	return
   304  }