Charts: Gefülltes Liniendiagramm

Erstellt am 10. Okt. 2017  ·  17Kommentare  ·  Quelle: danielgindi/Charts

Ich versuche, ein ausgefülltes Liniendiagramm zu zeichnen. Sie können das Bild zur besseren Übersichtlichkeit sehen.
screen shot 2017-10-09 at 23 02 38
Ich habe das Beispiel gesehen und sie verwenden das IFillFormatter-Protokoll, um die Y-Achsenposition zu erhalten, an der die gefüllte Zeile des Datensatzes endet. Aber es war eine feste Position, überhaupt nicht dynamisch. Wie kann ich diese Position von den einzelnen Elementen des Datensatzes abhängen. Zum Beispiel, wenn Index == 0, dann haben wir das Ende der gefüllten Position A, wenn Index == 1, dann haben wir einen anderen Endwert einer besetzten Position.
Oder jeder andere Vorschlag, ein Diagramm wie das obige Bild zu zeichnen.

Good Example

Hilfreichster Kommentar

Ich habe dies erreicht, indem ich den Vorschlägen von LineChartRenderer untergeordnet habe und der Klasse eine Variable hinzugefügt habe, die IFillFormatter implementiert, wodurch ich einen zweiten Datensatz als Indikator für die Fülllinie übergeben kann und Zeichnen Sie den Pfad entsprechend (nur für linear, aber ich gehe davon aus, dass Sie dasselbe für Bezier tun könnten).

class AreaFillFormatter: IFillFormatter {

    var fillLineDataSet: LineChartDataSet?

    init(fillLineDataSet: LineChartDataSet) {
        self.fillLineDataSet = fillLineDataSet
    }

    public func getFillLinePosition(dataSet: ILineChartDataSet, dataProvider: LineChartDataProvider) -> CGFloat {
        return 0.0
    }

    public func getFillLineDataSet() -> LineChartDataSet {
        return fillLineDataSet ?? LineChartDataSet()
    }

}
class CustomLineChartRenderer: LineChartRenderer {

    override open func drawLinearFill(context: CGContext, dataSet: ILineChartDataSet, trans: Transformer, bounds: XBounds) {
        guard let dataProvider = dataProvider else { return }

        let areaFillFormatter = dataSet.fillFormatter as? AreaFillFormatter

        let filled = generateFilledPath(
            dataSet: dataSet,
            fillMin: dataSet.fillFormatter?.getFillLinePosition(dataSet: dataSet, dataProvider: dataProvider) ?? 0.0,
            fillLineDataSet: areaFillFormatter?.getFillLineDataSet(),
            bounds: bounds,
            matrix: trans.valueToPixelMatrix)

        if dataSet.fill != nil
        {
            drawFilledPath(context: context, path: filled, fill: dataSet.fill!, fillAlpha: dataSet.fillAlpha)
        }
        else
        {
            drawFilledPath(context: context, path: filled, fillColor: dataSet.fillColor, fillAlpha: dataSet.fillAlpha)
        }
    }

    fileprivate func generateFilledPath(dataSet: ILineChartDataSet, fillMin: CGFloat, fillLineDataSet: ILineChartDataSet?, bounds: XBounds, matrix: CGAffineTransform) -> CGPath
    {
        let phaseY = animator?.phaseY ?? 1.0
        let isDrawSteppedEnabled = dataSet.mode == .stepped
        let matrix = matrix

        var e: ChartDataEntry!
        var fillLineE: ChartDataEntry?

        let filled = CGMutablePath()

        e = dataSet.entryForIndex(bounds.min)
        fillLineE = fillLineDataSet?.entryForIndex(bounds.min)

        if e != nil
        {
            if let fillLineE = fillLineE
            {
                filled.move(to: CGPoint(x: CGFloat(e.x), y: CGFloat(fillLineE.y * phaseY)), transform: matrix)
            }
            else
            {
                filled.move(to: CGPoint(x: CGFloat(e.x), y: fillMin), transform: matrix)
            }

            filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)), transform: matrix)
        }

        // Create the path for the data set entries
        for x in stride(from: (bounds.min + 1), through: bounds.range + bounds.min, by: 1)
        {
            guard let e = dataSet.entryForIndex(x) else { continue }

            if isDrawSteppedEnabled
            {
                guard let ePrev = dataSet.entryForIndex(x-1) else { continue }
                filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(ePrev.y * phaseY)), transform: matrix)
            }

            filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)), transform: matrix)
        }

        // Draw a path to the start of the fill line
        e = dataSet.entryForIndex(bounds.range + bounds.min)
        fillLineE = fillLineDataSet?.entryForIndex(bounds.range + bounds.min)
        if e != nil
        {
            if let fillLineE = fillLineE
            {
                filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(fillLineE.y * phaseY)), transform: matrix)
            }
            else
            {
                filled.addLine(to: CGPoint(x: CGFloat(e.x), y: fillMin), transform: matrix)
            }
        }

        // Draw the path for the fill line (backwards)
        if let fillLineDataSet = fillLineDataSet {
            for x in stride(from: (bounds.min + 1), through: bounds.range + bounds.min, by: 1).reversed()
            {
                guard let e = fillLineDataSet.entryForIndex(x) else { continue }

                if isDrawSteppedEnabled
                {
                    guard let ePrev = fillLineDataSet.entryForIndex(x-1) else { continue }
                    filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(ePrev.y * phaseY)), transform: matrix)
                }

                filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)), transform: matrix)
            }
        }

        filled.closeSubpath()

        return filled
    }
}

Alle 17 Kommentare

Schauen Sie sich drawLinearFill wo das Füllrechteck gezeichnet wird. Es berechnet das Füllrecht innerhalb von generateFilledPath und getFillLinePosition . Idealerweise sollten Sie diese Methoden überschreiben, um Ihre Gerade zwischen den Zeilen zu erhalten

Ich habe dies erreicht, indem ich den Vorschlägen von LineChartRenderer untergeordnet habe und der Klasse eine Variable hinzugefügt habe, die IFillFormatter implementiert, wodurch ich einen zweiten Datensatz als Indikator für die Fülllinie übergeben kann und Zeichnen Sie den Pfad entsprechend (nur für linear, aber ich gehe davon aus, dass Sie dasselbe für Bezier tun könnten).

class AreaFillFormatter: IFillFormatter {

    var fillLineDataSet: LineChartDataSet?

    init(fillLineDataSet: LineChartDataSet) {
        self.fillLineDataSet = fillLineDataSet
    }

    public func getFillLinePosition(dataSet: ILineChartDataSet, dataProvider: LineChartDataProvider) -> CGFloat {
        return 0.0
    }

    public func getFillLineDataSet() -> LineChartDataSet {
        return fillLineDataSet ?? LineChartDataSet()
    }

}
class CustomLineChartRenderer: LineChartRenderer {

    override open func drawLinearFill(context: CGContext, dataSet: ILineChartDataSet, trans: Transformer, bounds: XBounds) {
        guard let dataProvider = dataProvider else { return }

        let areaFillFormatter = dataSet.fillFormatter as? AreaFillFormatter

        let filled = generateFilledPath(
            dataSet: dataSet,
            fillMin: dataSet.fillFormatter?.getFillLinePosition(dataSet: dataSet, dataProvider: dataProvider) ?? 0.0,
            fillLineDataSet: areaFillFormatter?.getFillLineDataSet(),
            bounds: bounds,
            matrix: trans.valueToPixelMatrix)

        if dataSet.fill != nil
        {
            drawFilledPath(context: context, path: filled, fill: dataSet.fill!, fillAlpha: dataSet.fillAlpha)
        }
        else
        {
            drawFilledPath(context: context, path: filled, fillColor: dataSet.fillColor, fillAlpha: dataSet.fillAlpha)
        }
    }

    fileprivate func generateFilledPath(dataSet: ILineChartDataSet, fillMin: CGFloat, fillLineDataSet: ILineChartDataSet?, bounds: XBounds, matrix: CGAffineTransform) -> CGPath
    {
        let phaseY = animator?.phaseY ?? 1.0
        let isDrawSteppedEnabled = dataSet.mode == .stepped
        let matrix = matrix

        var e: ChartDataEntry!
        var fillLineE: ChartDataEntry?

        let filled = CGMutablePath()

        e = dataSet.entryForIndex(bounds.min)
        fillLineE = fillLineDataSet?.entryForIndex(bounds.min)

        if e != nil
        {
            if let fillLineE = fillLineE
            {
                filled.move(to: CGPoint(x: CGFloat(e.x), y: CGFloat(fillLineE.y * phaseY)), transform: matrix)
            }
            else
            {
                filled.move(to: CGPoint(x: CGFloat(e.x), y: fillMin), transform: matrix)
            }

            filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)), transform: matrix)
        }

        // Create the path for the data set entries
        for x in stride(from: (bounds.min + 1), through: bounds.range + bounds.min, by: 1)
        {
            guard let e = dataSet.entryForIndex(x) else { continue }

            if isDrawSteppedEnabled
            {
                guard let ePrev = dataSet.entryForIndex(x-1) else { continue }
                filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(ePrev.y * phaseY)), transform: matrix)
            }

            filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)), transform: matrix)
        }

        // Draw a path to the start of the fill line
        e = dataSet.entryForIndex(bounds.range + bounds.min)
        fillLineE = fillLineDataSet?.entryForIndex(bounds.range + bounds.min)
        if e != nil
        {
            if let fillLineE = fillLineE
            {
                filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(fillLineE.y * phaseY)), transform: matrix)
            }
            else
            {
                filled.addLine(to: CGPoint(x: CGFloat(e.x), y: fillMin), transform: matrix)
            }
        }

        // Draw the path for the fill line (backwards)
        if let fillLineDataSet = fillLineDataSet {
            for x in stride(from: (bounds.min + 1), through: bounds.range + bounds.min, by: 1).reversed()
            {
                guard let e = fillLineDataSet.entryForIndex(x) else { continue }

                if isDrawSteppedEnabled
                {
                    guard let ePrev = fillLineDataSet.entryForIndex(x-1) else { continue }
                    filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(ePrev.y * phaseY)), transform: matrix)
                }

                filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)), transform: matrix)
            }
        }

        filled.closeSubpath()

        return filled
    }
}

perfektes Beispiel um das Projekt zu meistern :)

Wie kann man diese Lösung lösen/verwenden?

@rob-k können Sie bitte ein Anwendungsbeispiel dafür geben?

Natürlich verwende ich den Code wie folgt:

let maxDataSet = LineChartDataSet()
let minDataSet = LineChartDataSet()

// ... fill the data sets

// Set the data
self.lineChart.data = LineChartData(dataSets: [maxDataSet, minDataSet])

// Set the custom line chart renderer
self.lineChart.renderer = CustomLineChartRenderer(dataProvider: self.lineChart, animator: self.lineChart.chartAnimator, viewPortHandler: self.lineChart.viewPortHandler)

maxDataSet.drawFilledEnabled = true
maxDataSet.fillFormatter = AreaFillFormatter(fillLineDataSet: minDataSet)

Ich habe dies manuell installiert und jeden Schritt wie im Beispiel befolgt, sieht immer noch so aus, als ob es nicht funktioniert @rob-k

@rob-k Ich habe versucht, mit Haltepunkten zu debuggen, wenn eine Funktion von CustomLineChartRenderer nicht aufgerufen wird

Ich habe meinen Kommentar aktualisiert, um die Einstellung von LineChartRenderer .

immer noch kein Glücksmann @rob-k

Wenn Sie die Farbe zwischen ucbDataSet und lcbDataSet wünschen, müssen Sie den zweiten Datensatz an den Formatierer des ersten übergeben. Sie übergeben das Dataset an den Formatierer selbst. Versuchen Sie Folgendes:

ucbDataSet.fillFormatter = AreaFillFormatter(fillLineDataSet: lcbDataSet)

(oder umgekehrt)

habe schon versucht, was ich zu erklären versuche
Ich habe versucht, mit Haltepunkten zu debuggen, wenn eine Funktion aus der CustomLineChartRenderer-Klasse nicht aufgerufen wurde

Ich sehe, dass Sie in Ihrem Code den Bezier-Modus verwenden:

lineChartDataSet.mode = .horizontalBezier
ucbDataSet.mode = .horizontalBezier
lcbDataSet.mode = .horizontalBezier

Der von mir bereitgestellte Code funktioniert nur für den linearen Modus. Sie müssen den Modus auf .linear ändern oder drawHorizontalBezier überschreiben und den Code entsprechend anpassen.

@rob-k hat es gerade geklappt, vielen Dank, Mann, dass du es herausgefunden hast

Wäre es möglich, dies mit CombinedChart zu implementieren?

Wäre es möglich, dies mit CombinedChart zu implementieren?

hast du die lösung gefunden? CombinedChart verwendet CombinedChartRender , nicht LinearChartRender , daher heißt der Rückruf nicht drawLinearFill , sondern verwendet den Standard-Render

die Methode drawLinearFill existiert nicht, es werden viele neue Renderings benötigt, wenn Sie in der Bibliothek suchen

Wäre es möglich, dies mit CombinedChart zu implementieren?

Lösung CombinedChart: https://github.com/PhilJay/MPAndroidChart/issues/338

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

guanyanlin picture guanyanlin  ·  3Kommentare

anhltse03448 picture anhltse03448  ·  3Kommentare

valeIT picture valeIT  ·  3Kommentare

Aungbandlab picture Aungbandlab  ·  4Kommentare

newbiebie picture newbiebie  ·  3Kommentare