结构


  • 动画 : Animation/
  • 图表 : Charts/
  • 组件 : Components/
  • 数据 : Data/
  • 过滤 : Filters/
  • 高亮 : Highlight/
  • 渲染 : Renderers/
  • 工具 : Utils/

Filters 过滤器


过滤器 Filters/

基类: └── ChartDataBaseFilter.swift 逼近器: ├── ChartDataApproximatorFilter.swift

名词解释


approximator

n. 接近者,近似者, 逼近器

Douglas-Peucker-Algorithm

道格拉斯-普克算法

道格拉斯-普克算法(Douglas–Peucker algorithm,亦称为拉默-道格拉斯-普克算法、迭代适应点算法、分裂与合并算法)是将曲线近似表示为一系列点,并减少点的数量的一种算法。该算法的原始类型分别由乌尔斯·拉默(Urs Ramer)于1972年以及大卫·道格拉斯(David Douglas)和托马斯·普克(Thomas Peucker)于1973年提出[1],并在之后的数十年中由其他学者予以完善[2]。

https://zh.wikipedia.org/wiki/道格拉斯-普克算法

https://en.wikipedia.org/wiki/Ramer–Douglas–Peucker_algorithm

tolerance

公差(gong chai)

几何参数的公差有尺寸公差、形状公差、位置公差等。

①尺寸公差。指允许尺寸的变动量,等于最大极限尺寸与最小极限尺寸代数差的绝对值。
②形状公差。指单一实际要素的形状所允许的变动全量,包括直线度、平面度、圆度、圆柱度、线轮廓度和面轮廓度6个项目。
③位置公差。指关联实际要素的位置对基准所允许的变动全量,它限制零件的两个或两个以上的点、线、面之间的相互位置关系,包括平行度、垂直度、倾斜度、同轴度、对称度、位置度、圆跳动和全跳动8个项目。公差表示了零件的制造精度要求,反映了其加工难易程度。

ChartDataApproximatorFilter


枚举

@objc
public enum ApproximatorType: Int
{
	case None			// 无
	case RamerDouglasPeucker	// 拉默-道格拉斯-普克
}

属性

/// the type of filtering algorithm to use
/// 逼近算法类型
public var type = ApproximatorType.None

/// the tolerance to be filtered with
/// When using the Douglas-Peucker-Algorithm, the tolerance is an angle in degrees, that will trigger the filtering
/// 公差的作用
/// 当使用 道格拉斯-普克 算法时,tolerance 作为一个角度,触发逼近
public var tolerance = Double(0.0)

/// 绽放比例
public var scaleRatio = Double(1.0)
/// delta 比例
public var deltaRatio = Double(1.0)

构造方法

public override init()
{
	super.init()
}

/// Initializes the approximator with the given type and tolerance. 
/// If toleranec <= 0, no filtering will be done.
/// 根据tolerance初始化逼近器
/// 如果 tolerance <= 0, 逼近完成
public init(type: ApproximatorType, tolerance: Double)
{
	super.init()

	setup(type, tolerance: tolerance)
}

成员方法

/// Sets type and tolerance.
/// If tolerance <= 0, no filtering will be done.
/// 如果 公差 <= 0, 不再逼近
public func setup(type: ApproximatorType, tolerance: Double)
{
	self.type = type
	self.tolerance = tolerance
}

/// Sets the ratios for x- and y-axis, as well as the ratio of the scale levels
/// 设置xy轴的比例,也就是缩放比例
public func setRatios(deltaRatio: Double, scaleRatio: Double)
{
	self.deltaRatio = deltaRatio
	self.scaleRatio = scaleRatio
}

/// Filters according to type. Uses the pre set set tolerance
/// 逼近器取决于 type, 根据之前的set设置tolerance
///
/// :param: points the points to filter
/// 参数: 将要被逼近的点
public override func filter(points: [ChartDataEntry]) -> [ChartDataEntry]
{
	return filter(points, tolerance: tolerance)
}

/// Filters according to type.
///
/// :param: points the points to filter
/// :param: tolerance the angle in degrees that will trigger the filtering
public func filter(points: [ChartDataEntry], tolerance: Double) -> [ChartDataEntry]
{
	if (tolerance <= 0)
	{
		return points
	}

	switch (type)
	{
		case .RamerDouglasPeucker:
			return reduceWithDouglasPeuker(points, epsilon: tolerance)
		case .None:
			return points
	}
}

/// uses the douglas peuker algorithm to reduce the given arraylist of entries
/// 使用douglas peuker algorithm 归纳(降低)给定的 实体
private func reduceWithDouglasPeuker(entries: [ChartDataEntry], epsilon: Double) -> [ChartDataEntry]
{
	// if a shape has 2 or less points it cannot be reduced
	// 如果少于2个点,将不能归纳
	if (epsilon <= 0 || entries.count < 3)
	{
		return entries
	}

	var keep = [Bool](count: entries.count, repeatedValue: false)

	// first and last always stay
	// 保留头和尾
	keep[0] = true
	keep[entries.count - 1] = true

	// first and last entry are entry point to recursion
	// 头和尾作为递归入口
	algorithmDouglasPeucker(entries, epsilon: epsilon, start: 0, end: entries.count - 1, keep: &keep)

	// create a new array with series, only take the kept ones
	var reducedEntries = [ChartDataEntry]()
	for (var i = 0; i < entries.count; i++)
	{
		if (keep[i])
		{
			let curEntry = entries[i]
			reducedEntries.append(ChartDataEntry(value: curEntry.value, xIndex: curEntry.xIndex))
		}
	}

	return reducedEntries
}

/// apply the Douglas-Peucker-Reduction to an ArrayList of Entry with a given epsilon (tolerance)
///
/// :param: entries
/// :param: epsilon as y-value
/// :param: start
/// :param: end
private func algorithmDouglasPeucker(entries: [ChartDataEntry], epsilon: Double, start: Int, end: Int, inout keep: [Bool])
{
	if (end <= start + 1)
	{
		// recursion finished
		return
	}

	// find the greatest distance between start and endpoint
	var maxDistIndex = Int(0)
	var distMax = Double(0.0)

	var firstEntry = entries[start]
	var lastEntry = entries[end]

	for (var i = start + 1; i < end; i++)
	{
		var dist = calcAngleBetweenLines(firstEntry, end1: lastEntry, start2: firstEntry, end2: entries[i])

		// keep the point with the greatest distance
		if (dist > distMax)
		{
			distMax = dist
			maxDistIndex = i
		}
	}

	if (distMax > epsilon)
	{
		// keep max dist point
		keep[maxDistIndex] = true

		// recursive call
		algorithmDouglasPeucker(entries, epsilon: epsilon, start: start, end: maxDistIndex, keep: &keep)
		algorithmDouglasPeucker(entries, epsilon: epsilon, start: maxDistIndex, end: end, keep: &keep)
	} // else don't keep the point...
}

/// calculate the distance between a line between two entries and an entry (point)
///
/// :param: startEntry line startpoint
/// :param: endEntry line endpoint
/// :param: entryPoint the point to which the distance is measured from the line
private func calcPointToLineDistance(startEntry: ChartDataEntry, endEntry: ChartDataEntry, entryPoint: ChartDataEntry) -> Double
{
	var xDiffEndStart = Double(endEntry.xIndex) - Double(startEntry.xIndex)
	var xDiffEntryStart = Double(entryPoint.xIndex) - Double(startEntry.xIndex)

	var normalLength = sqrt((xDiffEndStart)
		* (xDiffEndStart)
		+ (endEntry.value - startEntry.value)
		* (endEntry.value - startEntry.value))

	return Double(fabs((xDiffEntryStart)
		* (endEntry.value - startEntry.value)
		- (entryPoint.value - startEntry.value)
		* (xDiffEndStart))) / Double(normalLength)
}

/// Calculates the angle between two given lines. The provided entries mark the starting and end points of the lines.
private func calcAngleBetweenLines(start1: ChartDataEntry, end1: ChartDataEntry, start2: ChartDataEntry, end2: ChartDataEntry) -> Double
{
	var angle1 = calcAngleWithRatios(start1, p2: end1)
	var angle2 = calcAngleWithRatios(start2, p2: end2)

	return fabs(angle1 - angle2)
}

/// calculates the angle between two entries (points) in the chart taking ratios into consideration
private func calcAngleWithRatios(p1: ChartDataEntry, p2: ChartDataEntry) -> Double
{
	var dx = Double(p2.xIndex) * Double(deltaRatio) - Double(p1.xIndex) * Double(deltaRatio)
	var dy = p2.value * scaleRatio - p1.value * scaleRatio
	return atan2(Double(dy), dx) * ChartUtils.Math.RAD2DEG
}

// calculates the angle between two entries (points) in the chart
private func calcAngle(p1: ChartDataEntry, p2: ChartDataEntry) -> Double
{
	var dx = p2.xIndex - p1.xIndex
	var dy = p2.value - p1.value
	return atan2(Double(dy), Double(dx)) * ChartUtils.Math.RAD2DEG
}