我正在开发一个有很多数字的财务应用程序。
我的一张图表例如从25,000美元增长到约20,000,000,000美元(指数级)
因此,使用没有对数比例的任何常规图表会导致我的大多数数据简单地“变平”,而事实上它正在快速增长。
这是一个例子
(忽略它尚未设置)
以上线图是使用SwiftChart
制作的现在我必须说实话,我甚至不能完全确定我是否可以在SO上提出这样的问题。但我不知道从哪里开始。
经过几个小时的搜索后,我找不到任何库,教程,示例应用程序或任何与IOS相关的内容具有对数轴(X或Y)的图形。
在我找不到任何东西之后,我意识到我必须自己实现这个功能。我为我的应用程序找到了最合适的库,我一直在试图弄清楚如何实现一种方法,允许使用对数刻度(功率为10)来缩放y刻度。
我已经对日志规模如何运作进行了大量研究,并在我的大学中大量使用它们。但我从来没有真正看过一个编程的例子。
我发现一个用JavaScript编写(我认为)。但不幸的是,我没有网络开发的经验,也无法正确翻译它:(
我真的不喜欢把别人的代码放在SO上,但我真的不知道从哪里开始。
如果不允许,请发表评论,我会立即将其删除
所有这些归功于这个人:https://github.com/gpbl/SwiftChart
以下是我认为需要实现日志规模的代码。
private func drawChart() {
drawingHeight = bounds.height - bottomInset - topInset
drawingWidth = bounds.width
let minMax = getMinMax()
min = minMax.min
max = minMax.max
highlightShapeLayer = nil
// Remove things before drawing, e.g. when changing orientation
for view in self.subviews {
view.removeFromSuperview()
}
for layer in layerStore {
layer.removeFromSuperlayer()
}
layerStore.removeAll()
// Draw content
for (index, series) in enumerate(self.series) {
// Separate each line in multiple segments over and below the x axis
var segments = Chart.segmentLine(series.data as ChartLineSegment)
for (i, segment) in enumerate(segments) {
let scaledXValues = scaleValuesOnXAxis( segment.map( { return $0.x } ) )
let scaledYValues = scaleValuesOnYAxis( segment.map( { return $0.y } ) )
if series.line {
drawLine(xValues: scaledXValues, yValues: scaledYValues, seriesIndex: index)
}
if series.area {
drawArea(xValues: scaledXValues, yValues: scaledYValues, seriesIndex: index)
}
}
}
drawAxes()
if xLabels != nil || series.count > 0 {
drawLabelsAndGridOnXAxis()
}
if yLabels != nil || series.count > 0 {
drawLabelsAndGridOnYAxis()
}
}
// MARK: - Scaling
private func getMinMax() -> (min: ChartPoint, max: ChartPoint) {
// Start with user-provided values
var min = (x: minX, y: minY)
var max = (x: maxX, y: maxY)
// Check in datasets
for series in self.series {
let xValues = series.data.map( { (point: ChartPoint) -> Float in
return point.x } )
let yValues = series.data.map( { (point: ChartPoint) -> Float in
return point.y } )
let newMinX = minElement(xValues)
let newMinY = minElement(yValues)
let newMaxX = maxElement(xValues)
let newMaxY = maxElement(yValues)
if min.x == nil || newMinX < min.x! { min.x = newMinX }
if min.y == nil || newMinY < min.y! { min.y = newMinY }
if max.x == nil || newMaxX > max.x! { max.x = newMaxX }
if max.y == nil || newMaxY > max.y! { max.y = newMaxY }
}
// Check in labels
if xLabels != nil {
let newMinX = minElement(xLabels!)
let newMaxX = maxElement(xLabels!)
if min.x == nil || newMinX < min.x { min.x = newMinX }
if max.x == nil || newMaxX > max.x { max.x = newMaxX }
}
if yLabels != nil {
let newMinY = minElement(yLabels!)
let newMaxY = maxElement(yLabels!)
if min.y == nil || newMinY < min.y { min.y = newMinY }
if max.y == nil || newMaxY > max.y { max.y = newMaxY }
}
if min.x == nil { min.x = 0 }
if min.y == nil { min.y = 0 }
if max.x == nil { max.x = 0 }
if max.y == nil { max.y = 0 }
return (min: (x: min.x!, y: min.y!), max: (x: max.x!, max.y!))
}
private func scaleValuesOnXAxis(values: Array<Float>) -> Array<Float> {
let width = Float(drawingWidth)
var factor: Float
if max.x - min.x == 0 { factor = 0 }
else { factor = width / (max.x - min.x) }
let scaled = values.map { factor * ($0 - self.min.x) }
return scaled
}
private func scaleValuesOnYAxis(values: Array<Float>) -> Array<Float> {
let height = Float(drawingHeight)
var factor: Float
if max.y - min.y == 0 { factor = 0 }
else { factor = height / (max.y - min.y) }
let scaled = values.map { Float(self.topInset) + height - factor * ($0 - self.min.y) }
return scaled
}
private func scaleValueOnYAxis(value: Float) -> Float {
let height = Float(drawingHeight)
var factor: Float
if max.y - min.y == 0 { factor = 0 }
else { factor = height / (max.y - min.y) }
let scaled = Float(self.topInset) + height - factor * (value - min.y)
return scaled
}
private func getZeroValueOnYAxis() -> Float {
if min.y > 0 {
return scaleValueOnYAxis(min.y)
}
else {
return scaleValueOnYAxis(0)
}
}
// MARK: - Drawings
private func isVerticalSegmentAboveXAxis(yValues: Array<Float>) -> Bool {
// YValues are "reverted" from top to bottom, so min is actually the maxz
let min = maxElement(yValues)
let zero = getZeroValueOnYAxis()
return min <= zero
}
private func drawLine(#xValues: Array<Float>, yValues: Array<Float>, seriesIndex: Int) -> CAShapeLayer {
let isAboveXAxis = isVerticalSegmentAboveXAxis(yValues)
let path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, CGFloat(xValues.first!), CGFloat(yValues.first!))
for i in 1..<yValues.count {
let y = yValues[i]
CGPathAddLineToPoint(path, nil, CGFloat(xValues[i]), CGFloat(y))
}
var lineLayer = CAShapeLayer()
lineLayer.frame = self.bounds
lineLayer.path = path
if isAboveXAxis {
lineLayer.strokeColor = series[seriesIndex].colors.above.CGColor
}
else {
lineLayer.strokeColor = series[seriesIndex].colors.below.CGColor
}
lineLayer.fillColor = nil
lineLayer.lineWidth = lineWidth
lineLayer.lineJoin = kCALineJoinBevel
self.layer.addSublayer(lineLayer)
layerStore.append(lineLayer)
return lineLayer
}
private func drawArea(#xValues: Array<Float>, yValues: Array<Float>, seriesIndex: Int) {
let isAboveXAxis = isVerticalSegmentAboveXAxis(yValues)
let area = CGPathCreateMutable()
let zero = CGFloat(getZeroValueOnYAxis())
CGPathMoveToPoint(area, nil, CGFloat(xValues[0]), zero)
for i in 0..<xValues.count {
CGPathAddLineToPoint(area, nil, CGFloat(xValues[i]), CGFloat(yValues[i]))
}
CGPathAddLineToPoint(area, nil, CGFloat(xValues.last!), zero)
var areaLayer = CAShapeLayer()
areaLayer.frame = self.bounds
areaLayer.path = area
areaLayer.strokeColor = nil
if isAboveXAxis {
areaLayer.fillColor = series[seriesIndex].colors.above.colorWithAlphaComponent(areaAlphaComponent).CGColor
}
else {
areaLayer.fillColor = series[seriesIndex].colors.below.colorWithAlphaComponent(areaAlphaComponent).CGColor
}
areaLayer.lineWidth = 0
self.layer.addSublayer(areaLayer)
layerStore.append(areaLayer)
}
private func drawAxes() {
let context = UIGraphicsGetCurrentContext()
CGContextSetStrokeColorWithColor(context, axesColor.CGColor)
CGContextSetLineWidth(context, 0.5)
// horizontal axis at the bottom
CGContextMoveToPoint(context, 0, drawingHeight + topInset)
CGContextAddLineToPoint(context, drawingWidth, drawingHeight + topInset)
CGContextStrokePath(context)
// horizontal axis at the top
CGContextMoveToPoint(context, 0, 0)
CGContextAddLineToPoint(context, drawingWidth, 0)
CGContextStrokePath(context)
// horizontal axis when y = 0
if min.y < 0 && max.y > 0 {
let y = CGFloat(getZeroValueOnYAxis())
CGContextMoveToPoint(context, 0, y)
CGContextAddLineToPoint(context, drawingWidth, y)
CGContextStrokePath(context)
}
// vertical axis on the left
CGContextMoveToPoint(context, 0, 0)
CGContextAddLineToPoint(context, 0, drawingHeight + topInset)
CGContextStrokePath(context)
// vertical axis on the right
CGContextMoveToPoint(context, drawingWidth, 0)
CGContextAddLineToPoint(context, drawingWidth, drawingHeight + topInset)
CGContextStrokePath(context)
}
任何关于我需要实现这一点的提示或帮助都将受到广泛赞赏。
我为这个问题寻求帮助或指导的可怕性而道歉。