Ich versuche, ein ausgefülltes Liniendiagramm zu zeichnen. Sie können das Bild zur besseren Übersichtlichkeit sehen.
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.
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
Hilfreichster Kommentar
Ich habe dies erreicht, indem ich den Vorschlägen von
LineChartRenderer
untergeordnet habe und der Klasse eine Variable hinzugefügt habe, dieIFillFormatter
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).