对数y轴在swift?

时间:2015-06-02 06:44:55

标签: ios swift linechart logarithm

我正在开发一个有很多数字的财务应用程序。

我的一张图表例如从25,000美元增长到约20,000,000,000美元(指数级)

因此,使用没有对数比例的任何常规图表会导致我的大多数数据简单地“变平”,而事实上它正在快速增长。

这是一个例子

(忽略它尚未设置)

enter image description here

以上线图是使用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)

}

任何关于我需要实现这一点的提示或帮助都将受到广泛赞赏。

我为这个问题寻求帮助或指导的可怕性而道歉。

0 个答案:

没有答案