Swift iOS LineChart с разными цветами на основе значений

Я использую библиотеку диаграмм для создания диаграмм в своем приложении. Мне нужно создать диаграмму, подобную следующей.

Насколько я пытался, мне удалось получить следующее.

Я искал день, чтобы найти решение, но не могу найти решение. Так что если кто-то из вас знает, как это исправить или у вас есть идеи по этому поводу, пожалуйста, поделитесь со мной. Благодарю.

2 ответа

Мне удалось добиться после долгих поисков. Я добился этого, отредактировав файлы библиотеки. Смотрите запрос на извлечение на GitHub. Пожалуйста, измените файлы LineChartDataSet.swift, ILineChartDataSet.swift, LineChartRenderer.swift как показано в Pull Request для достижения этой цели.

Для градиентной линии вы можете обновить библиотеку диаграмм до версии 4.0.0 или выше. Или, если вы не хотите обновляться, вы можете создать собственное средство визуализации линейных диаграмм. Я сделал один для рисования градиентной линии на линейной диаграмме. Не стесняйтесь использовать его или вносить изменения.

Однако вам придется внести изменения в положение градиента, чтобы получить точные результаты. А также укажите желаемые цвета в наборе данных.

      class GradientLineChartRenderer: LineChartRenderer {
var _xBounds = XBounds()

init(view: LineChartView) {
    super.init(dataProvider: view, animator: view.chartAnimator, viewPortHandler: view.viewPortHandler)
}

func shouldDrawValues(forDataSet set: IChartDataSet) -> Bool
{
    return set.isVisible && (set.isDrawValuesEnabled || set.isDrawIconsEnabled)
}

override func drawHorizontalBezier(context: CGContext, dataSet: ILineChartDataSet) {
    guard let dataProvider = dataProvider else { return }
    
    let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency)
    
    let phaseY = animator.phaseY
    
    _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator)
    
    let drawingColor = dataSet.colors.first!
    
    let cubicPath = CGMutablePath()
    
    let valueToPixelMatrix = trans.valueToPixelMatrix
    
    if _xBounds.range >= 1
    {
        var prev: ChartDataEntry! = dataSet.entryForIndex(_xBounds.min)
        var cur: ChartDataEntry! = prev
        
        if cur == nil { return }
        
        cubicPath.move(to: CGPoint(x: CGFloat(cur.x), y: CGFloat(cur.y * phaseY)), transform: valueToPixelMatrix)
        
        for j in _xBounds.dropFirst()
        {
            prev = cur
            cur = dataSet.entryForIndex(j)
            
            let cpx = CGFloat(prev.x + (cur.x - prev.x) / 2.0)
            
            cubicPath.addCurve(
                to: CGPoint(
                    x: CGFloat(cur.x),
                    y: CGFloat(cur.y * phaseY)),
                control1: CGPoint(
                    x: cpx,
                    y: CGFloat(prev.y * phaseY)),
                control2: CGPoint(
                    x: cpx,
                    y: CGFloat(cur.y * phaseY)),
                transform: valueToPixelMatrix)
        }
    }
    
    context.saveGState()
    
    if dataSet.isDrawFilledEnabled
    {
        let fillPath = cubicPath.mutableCopy()
        
        drawCubicFill(context: context, dataSet: dataSet, spline: fillPath!, matrix: valueToPixelMatrix, bounds: _xBounds)
    }
    let fillPath = cubicPath.mutableCopy()
    drawGradientLine(context: context, dataSet: dataSet, spline: fillPath!, matrix: valueToPixelMatrix)
    
    context.restoreGState()
}

func drawGradientLine(context: CGContext, dataSet: ILineChartDataSet, spline: CGPath, matrix: CGAffineTransform)
    {
        let gradientPositions = [0,0]

        let boundingBox = spline.boundingBox
            .insetBy(dx: -dataSet.lineWidth / 2, dy: -dataSet.lineWidth / 2)

        guard !boundingBox.isNull, !boundingBox.isInfinite, !boundingBox.isEmpty else {
            return
        }

        let gradientStart = CGPoint(x: 0, y: boundingBox.minY)
        let gradientEnd = CGPoint(x: 0, y: boundingBox.maxY)
        let gradientColorComponents: [CGFloat] = dataSet.colors
            .reversed()
            .reduce(into: []) { (components, color) in
                guard let (r, g, b, a) = color.nsuirgba else {
                    return
                }
                components += [r, g, b, a]
            }
        let gradientLocations: [CGFloat] = gradientPositions
            .map { (position) in
                let location = CGPoint(x: boundingBox.minX, y: CGFloat(position))
                    .applying(matrix)
                let normalizedLocation = (location.y - boundingBox.minY)
                    / (boundingBox.maxY - boundingBox.minY)
                return normalizedLocation.clamped(to: 0...1)
            }

        let baseColorSpace = CGColorSpaceCreateDeviceRGB()
        guard let gradient = CGGradient(
                colorSpace: baseColorSpace,
                colorComponents: gradientColorComponents,
                locations: gradientLocations,
                count: gradientLocations.count) else {
            return
        }

        context.saveGState()
        defer { context.restoreGState() }

        context.beginPath()
        context.addPath(spline)
        context.replacePathWithStrokedPath()
        context.clip()
        context.drawLinearGradient(gradient, start: gradientStart, end: gradientEnd, options: [])
    }
}

extension NSUIColor
{
    var nsuirgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)? {
        var red: CGFloat = 0
        var green: CGFloat = 0
        var blue: CGFloat = 0
        var alpha: CGFloat = 0

        guard getRed(&red, green: &green, blue: &blue, alpha: &alpha) else {
            return nil
        }

        return (red: red, green: green, blue: blue, alpha: alpha)
    }
}

extension Comparable
{
    func clamped(to range: ClosedRange<Self>) -> Self
    {
        if self > range.upperBound
        {
            return range.upperBound
        }
        else if self < range.lowerBound
        {
            return range.lowerBound
        }
        else
        {
            return self
        }
    }
}

Чтобы использовать средство визуализации, используйте это, где chartView - это ваш LineChartView.

      chartView.renderer = GradientLineChartRenderer(
            view: chartView
        )
Другие вопросы по тегам