public override void DrawHighlighted(SKCanvas c, IList <Highlight.Highlight> indices) { var lineData = ((Interfaces.DataProvider.ILineChartProvider)Chart).Data; foreach (var high in indices) { ILineDataSet set = lineData[high.DataSetIndex]; if (set == null || !set.IsHighlightEnabled) { continue; } Entry e = ((IDataSet)set).EntryForXValue(high.X, high.Y); if (!IsInBoundsX(e, set)) { continue; } var pix = Chart.GetTransformer(set.AxisDependency).PointValueToPixel(e.X, e.Y * Animator .PhaseY); high.SetDraw((float)pix.X, (float)pix.Y); // draw the lines DrawHighlightLines(c, (float)pix.X, (float)pix.Y, set); } }
protected void DrawDataSet(SKCanvas c, ILineDataSet dataSet) { if (dataSet.EntryCount < 1) { return; } RenderPaint.StrokeWidth = dataSet.LineWidth; RenderPaint.PathEffect = dataSet.DashPathEffect; switch (dataSet.Mode) { default: case LineDataSet.LineMode.Linear: case LineDataSet.LineMode.Stepped: DrawLinear(c, dataSet); break; case LineDataSet.LineMode.CubicBezier: DrawCubicBezier(dataSet); break; case LineDataSet.LineMode.HorizontalBezier: DrawHorizontalBezier(dataSet); break; } RenderPaint.PathEffect = null; }
private void UpdatePlot(LineChart mChart, int broadcast_idx, float[] dataVector) { LineData data = mChart.LineData; if (data != null) { ILineDataSet setx = (ILineDataSet)data.DataSets[0]; ILineDataSet sety = (ILineDataSet)data.DataSets[1]; ILineDataSet setz = (ILineDataSet)data.DataSets[2]; // add new plot entry data.AddEntry(new Entry(broadcast_idx, dataVector[0]), 0); data.AddEntry(new Entry(broadcast_idx, dataVector[1]), 1); data.AddEntry(new Entry(broadcast_idx, dataVector[2]), 2); mChart.NotifyDataSetChanged(); // limit the number of visible entries mChart.SetVisibleXRangeMaximum(max_entries); // remove plot entries outside of current view if (setx.EntryCount == max_entries_saved) { setx.RemoveEntry(0); sety.RemoveEntry(0); setz.RemoveEntry(0); mChart.AxisLeft.SpaceTop = 10f; } // move to the latest entry mChart.MoveViewToX(broadcast_idx); } }
/// <summary> /// Fills the cache with bitmaps for the given dataset. /// </summary> protected void Fill(DataSetImageCache cache, ILineDataSet set, bool drawCircleHole, bool drawTransparentCircleHole) { int colorCount = set.CircleColorCount; float circleRadius = set.CircleRadius; float circleHoleRadius = set.CircleHoleRadius; var circleBitmaps = cache.CircleBitmaps; var circlePath = cache.CirclePathBuffer; for (int i = 0; i < colorCount; i++) { var circleBitmap = new SKBitmap((int)(circleRadius * 2.1), (int)(circleRadius * 2.1)); var canvas = new SKCanvas(circleBitmap); circleBitmaps[i] = circleBitmap; RenderPaint.Color = set.GetCircleColor(i); if (drawTransparentCircleHole) { // Begin path for circle with hole circlePath.Reset(); circlePath.AddCircle( circleRadius, circleRadius, circleRadius, SKPathDirection.Clockwise); // Cut hole in path circlePath.AddCircle( circleRadius, circleRadius, circleHoleRadius, SKPathDirection.CounterClockwise); // Fill in-between canvas.DrawPath(circlePath, RenderPaint); } else { canvas.DrawCircle( circleRadius, circleRadius, circleRadius, RenderPaint); if (drawCircleHole) { canvas.DrawCircle( circleRadius, circleRadius, circleHoleRadius, CirclePaintInner); } } } }
protected void DrawHorizontalBezier(ILineDataSet dataSet) { float phaseY = Animator.PhaseY; var trans = Chart.GetTransformer(dataSet.AxisDependency); XBounds.Set(Chart, dataSet, Animator); CubicPath.Reset(); if (XBounds.Range >= 1) { var prev = ((IDataSet <Entry>)dataSet)[XBounds.Min]; var cur = prev; // let the spline start CubicPath.MoveTo(cur.X, cur.Y * phaseY); for (int j = XBounds.Min + 1; j <= XBounds.Range + XBounds.Min; j++) { prev = cur; cur = ((IDataSet <Entry>)dataSet)[j]; float cpx = (prev.X) + (cur.X - prev.X) / 2.0f; CubicPath.CubicTo( cpx, prev.Y * phaseY, cpx, cur.Y * phaseY, cur.X, cur.Y * phaseY); } } // if filled is enabled, close the path if (dataSet.DrawFilled) { CubicFillPath.Reset(); CubicFillPath.AddPath(CubicPath); // create a new path, this is bad for performance DrawCubicFill(BitmapCanvas, dataSet, CubicFillPath, trans, XBounds); } RenderPaint.Color = dataSet.Color; RenderPaint.Style = SKPaintStyle.Stroke; trans.PathValueToPixel(CubicPath); BitmapCanvas.DrawPath(CubicPath, RenderPaint); RenderPaint.PathEffect = null; }
private void AddOrientEntry(float valueX, float valueY, float valueZ) { LineData data = mOrientationChart.LineData; if (data != null) { ILineDataSet set = (ILineDataSet)data.DataSets[0]; data.AddEntry(new Entry(set.EntryCount, valueX), 0); data.AddEntry(new Entry(set.EntryCount, valueY), 1); data.AddEntry(new Entry(set.EntryCount, valueZ), 2); mOrientationChart.NotifyDataSetChanged(); // limit the number of visible entries mOrientationChart.SetVisibleXRangeMaximum(100); // move to the latest entry mOrientationChart.MoveViewToX(data.EntryCount); } }
/// <summary> /// Sets up the cache, returns true if a change of cache was required. /// </summary> public bool Init(ILineDataSet set) { int size = set.CircleColorCount; var changeRequired = false; if (CircleBitmaps == null) { CircleBitmaps = new SKBitmap[size]; changeRequired = true; } else if (CircleBitmaps.Length != size) { CircleBitmaps = new SKBitmap[size]; changeRequired = true; } return(changeRequired); }
void GenerateFilledPath(ILineDataSet lineDataSet, int startIndex, int endIndex, SKPath outputPath) { float fillMin = lineDataSet.FillFormatter.GetFillLinePosition(lineDataSet, Chart); float phaseY = Animator.PhaseY; bool isDrawSteppedEnabled = lineDataSet.Mode == LineDataSet.LineMode.Stepped; var filled = outputPath; filled.Reset(); IDataSet <Entry> dataSet = lineDataSet; var entry = dataSet[startIndex]; filled.MoveTo(entry.X, fillMin); filled.LineTo(entry.X, entry.Y * phaseY); // create a new path Entry currentEntry = null; Entry previousEntry = entry; for (int x = startIndex + 1; x <= endIndex; x++) { currentEntry = dataSet[x]; if (isDrawSteppedEnabled) { filled.LineTo(currentEntry.X, previousEntry.Y * phaseY); } filled.LineTo(currentEntry.X, currentEntry.Y * phaseY); previousEntry = currentEntry; } // close up if (currentEntry != null) { filled.LineTo(currentEntry.X, fillMin); } filled.Close(); }
protected void DrawLinearFill(SKCanvas c, ILineDataSet dataSet, Transformer trans, Bounds bounds) { SKPath filled = GenerateFilledPathBuffer; int startingIndex = bounds.Min; int endingIndex = bounds.Range + bounds.Min; int indexInterval = 128; int iterations = 0; int currentStartIndex; int currentEndIndex; // Doing this iteratively in order to avoid OutOfMemory errors that can happen on large bounds sets. do { currentStartIndex = startingIndex + (iterations * indexInterval); currentEndIndex = currentStartIndex + indexInterval; currentEndIndex = currentEndIndex > endingIndex ? endingIndex : currentEndIndex; if (currentStartIndex <= currentEndIndex) { GenerateFilledPath(dataSet, currentStartIndex, currentEndIndex, filled); trans.PathValueToPixel(filled); var fill = dataSet.Fill; if (fill != null) { fill.Draw(c, filled, RenderPaint, ViewPortHandler.ContentRect); } else { DrawFilledPath(c, filled, dataSet.FillColor, dataSet.FillAlpha); } } iterations++; } while (currentStartIndex <= currentEndIndex); }
protected void DrawCubicFill(SKCanvas c, ILineDataSet dataSet, SKPath spline, Transformer trans, Bounds bounds) { float fillMin = dataSet.FillFormatter .GetFillLinePosition(dataSet, Chart); spline.LineTo(((IDataSet)dataSet)[bounds.Min + bounds.Range].X, fillMin); spline.LineTo(((IDataSet)dataSet)[bounds.Min].X, fillMin); spline.Close(); trans.PathValueToPixel(spline); var fill = dataSet.Fill; if (fill != null) { fill.Draw(c, spline, RenderPaint, ViewPortHandler.ContentRect); } else { DrawFilledPath(c, spline, dataSet.FillColor, dataSet.FillAlpha); } }
public float GetFillLinePosition(ILineDataSet dataSet, ILineChartDataProvider dataProvider) { var chartMaxY = dataProvider.YChartMax; var chartMinY = dataProvider.YChartMin; LineData data = ((ILineChartProvider)dataProvider).Data; float fillMin; if (dataSet.YMax > 0 && dataSet.YMin < 0) { fillMin = 0f; } else { float max, min; if (data.YMax > 0) { max = 0f; } else { max = chartMaxY; } if (data.YMin < 0) { min = 0f; } else { min = chartMinY; } fillMin = dataSet.YMin >= 0 ? min : max; } return(fillMin); }
protected void DrawCircles(SKCanvas c) { RenderPaint.Style = SKPaintStyle.Fill; float phaseY = Animator.PhaseY; var dataSets = ((Interfaces.DataProvider.ILineChartProvider)Chart).Data.DataSets; for (int i = 0; i < dataSets.Count; i++) { ILineDataSet dataSet = dataSets[i]; if (!dataSet.IsVisible || !dataSet.IsDrawCirclesEnabled || dataSet.EntryCount == 0) { continue; } CirclePaintInner.Color = dataSet.CircleHoleColor; Transformer trans = Chart.GetTransformer(dataSet.AxisDependency); XBounds.Set(Chart, dataSet, Animator); float circleRadius = dataSet.CircleRadius; float circleHoleRadius = dataSet.CircleHoleRadius; var drawCircleHole = dataSet.IsDrawCircleHoleEnabled && circleHoleRadius <circleRadius && circleHoleRadius> 0.0f; var drawTransparentCircleHole = drawCircleHole && dataSet.CircleHoleColor == SKColors.Empty; if (imageCaches.TryGetValue(dataSet, out DataSetImageCache imageCache) == false) { imageCache = new DataSetImageCache(); imageCaches.Add(dataSet, imageCache); } // only fill the cache with new bitmaps if a change is required if (imageCache.Init(dataSet)) { Fill(imageCache, dataSet, drawCircleHole, drawTransparentCircleHole); } int boundsRangeCount = XBounds.Range + XBounds.Min; for (int j = XBounds.Min; j <= boundsRangeCount; j++) { Entry e = ((IDataSet)dataSet)[j]; if (e == null) { break; } var pt = trans.PointValueToPixel(e.X, e.Y * phaseY); if (!ViewPortHandler.IsInBoundsRight(pt.X)) { break; } if (!ViewPortHandler.IsInBoundsLeft(pt.X) || !ViewPortHandler.IsInBoundsY(pt.Y)) { continue; } var circleBitmap = imageCache.GetBitmap(j); if (circleBitmap != null) { c.DrawBitmap(circleBitmap, pt.X - circleRadius, pt.Y - circleRadius, null); } } } }
public override void DrawValues(SKCanvas c) { if (IsDrawingValuesAllowed(Chart)) { var dataSets = ((Interfaces.DataProvider.ILineChartProvider)Chart).Data.DataSets; for (int i = 0; i < dataSets.Count; i++) { ILineDataSet dataSet = dataSets[i]; if (!ShouldDrawValues(dataSet) || dataSet.EntryCount < 1) { continue; } // apply the text-styling defined by the DataSet ApplyValueTextStyle(dataSet); Transformer trans = Chart.GetTransformer(dataSet.AxisDependency); // make sure the values do not interfear with the circles int valOffset = (int)(dataSet.CircleRadius * 1.75f); if (!dataSet.IsDrawCirclesEnabled) { valOffset /= 2; } XBounds.Set(Chart, dataSet, Animator); var positions = trans.GenerateTransformedValuesLine(dataSet, Animator.PhaseX, Animator .PhaseY, XBounds.Min, XBounds.Max); var iconsOffset = dataSet.IconsOffset.DpToPixel(); for (int j = 0; j < positions.Length; j++) { var pos = positions[j]; if (!ViewPortHandler.IsInBoundsRight(pos.X)) { break; } if (!ViewPortHandler.IsInBoundsLeft(pos.X) || !ViewPortHandler.IsInBoundsY(pos.Y)) { continue; } Entry entry = ((IDataSet)dataSet)[j + XBounds.Min]; if (dataSet.IsDrawValuesEnabled) { DrawValue(c, dataSet.ValueFormatter, entry.Y, entry, i, pos.X, pos.Y - valOffset, dataSet.ValueTextColorAt(j)); } if (entry.Icon != null && dataSet.IsDrawIconsEnabled) { entry.Icon.Draw( c, (int)(pos.X + iconsOffset.X), (int)(pos.Y + iconsOffset.Y)); } } } } }
protected void DrawCubicBezier(ILineDataSet lineDataSet) { float phaseY = Animator.PhaseY; Transformer trans = Chart.GetTransformer(lineDataSet.AxisDependency); XBounds.Set(Chart, lineDataSet, Animator); float intensity = lineDataSet.CubicIntensity; CubicPath.Reset(); IDataSet <Entry> dataSet = lineDataSet; if (XBounds.Range >= 1) { // Take an extra point from the left, and an extra from the right. // That's because we need 4 points for a cubic bezier (cubic=4), otherwise we get lines moving and doing weird stuff on the edges of the chart. // So in the starting `prev` and `cur`, go -2, -1 // And in the `lastIndex`, add +1 int firstIndex = XBounds.Min + 1; Entry prevPrev; Entry prev = dataSet[Math.Max(firstIndex - 2, 0)]; Entry cur = dataSet[Math.Max(firstIndex - 1, 0)]; Entry next = cur; int nextIndex = -1; if (cur == null) { return; } // let the spline start CubicPath.MoveTo(cur.X, cur.Y * phaseY); for (int j = XBounds.Min + 1; j <= XBounds.Range + XBounds.Min; j++) { prevPrev = prev; prev = cur; cur = nextIndex == j ? next : dataSet[j]; nextIndex = j + 1 < lineDataSet.EntryCount ? j + 1 : j; next = dataSet[nextIndex]; float prevDx = (cur.X - prevPrev.X) * intensity; float prevDy = (cur.Y - prevPrev.Y) * intensity; float curDx = (next.X - prev.X) * intensity; float curDy = (next.Y - prev.Y) * intensity; CubicPath.CubicTo(prev.X + prevDx, (prev.Y + prevDy) * phaseY, cur.X - curDx, (cur.Y - curDy) * phaseY, cur.X, cur.Y * phaseY); } } // if filled is enabled, close the path if (lineDataSet.DrawFilled) { CubicFillPath.Reset(); CubicFillPath.AddPath(CubicPath); DrawCubicFill(BitmapCanvas, lineDataSet, CubicFillPath, trans, XBounds); } RenderPaint.Color = lineDataSet.Color; RenderPaint.Style = SKPaintStyle.Stroke; trans.PathValueToPixel(CubicPath); BitmapCanvas.DrawPath(CubicPath, RenderPaint); RenderPaint.PathEffect = null; }
protected void DrawLinear(SKCanvas c, ILineDataSet lineDataSet) { int entryCount = lineDataSet.EntryCount; bool isDrawSteppedEnabled = lineDataSet.Mode == LineDataSet.LineMode.Stepped; int pointsPerEntryPair = isDrawSteppedEnabled ? 4 : 2; Transformer trans = Chart.GetTransformer(lineDataSet.AxisDependency); float phaseY = Animator.PhaseY; RenderPaint.Style = SKPaintStyle.Stroke; SKCanvas canvas; // if the data-set is dashed, draw on bitmap-canvas if (lineDataSet.IsDashedLineEnabled) { canvas = BitmapCanvas; } else { canvas = c; } XBounds.Set(Chart, lineDataSet, Animator); // if drawing filled is enabled if (lineDataSet.DrawFilled && entryCount > 0) { DrawLinearFill(c, lineDataSet, trans, XBounds); } // more than 1 color if (lineDataSet.Colors.Count > 1) { int numberOfFloats = pointsPerEntryPair; if (_lineBuffer.Length <= numberOfFloats) { _lineBuffer = new SKPoint[numberOfFloats]; } int max = XBounds.Min + XBounds.Range; IDataSet <Entry> dataSet = lineDataSet; for (int j = XBounds.Min; j < max; j++) { Entry e = dataSet[j]; if (e == null) { continue; } _lineBuffer[0] = new SKPoint(e.X, e.Y * phaseY); if (j < XBounds.Max) { e = dataSet[j + 1]; if (e == null) { break; } if (isDrawSteppedEnabled) { _lineBuffer[1] = new SKPoint(e.X, _lineBuffer[0].Y); _lineBuffer[2] = _lineBuffer[1]; _lineBuffer[3] = new SKPoint(e.X, e.Y * phaseY); } else { _lineBuffer[1] = new SKPoint(e.X, e.Y * phaseY); } } else { _lineBuffer[1] = _lineBuffer[0]; } var pts = trans.PointValuesToPixel(_lineBuffer); // Determine the start and end coordinates of the line, and make sure they differ. var firstCoordinate = pts[0]; var lastCoordinate = pts[numberOfFloats - 1]; if (firstCoordinate == lastCoordinate) { continue; } if (!ViewPortHandler.IsInBoundsRight(firstCoordinate.X)) { break; } // make sure the lines don't do shitty things outside // bounds if (!ViewPortHandler.IsInBoundsLeft(lastCoordinate.X) || !ViewPortHandler.IsInBoundsTop(Math.Max(firstCoordinate.Y, lastCoordinate.Y)) || !ViewPortHandler.IsInBoundsBottom(Math.Min(firstCoordinate.Y, lastCoordinate.Y))) { continue; } // get the color that is set for this line-segment RenderPaint.Color = lineDataSet.ColorAt(j); for (int i = 0; i < numberOfFloats; i += 2) { canvas.DrawLine(pts[i], pts[i + 1], RenderPaint); } } } else { // only one color per dataset if (_lineBuffer.Length < Math.Max(entryCount * pointsPerEntryPair, pointsPerEntryPair)) { _lineBuffer = new SKPoint[Math.Max(entryCount * pointsPerEntryPair, pointsPerEntryPair) * 2]; } Entry e1, e2; IDataSet <Entry> dataSet = lineDataSet; e1 = dataSet[XBounds.Min]; if (e1 != null) { int j = 0; for (int x = XBounds.Min; x <= XBounds.Range + XBounds.Min; x++) { e1 = dataSet[x == 0 ? 0 : (x - 1)]; e2 = dataSet[x]; if (e1 == null || e2 == null) { continue; } _lineBuffer[j++] = new SKPoint(e1.X, e1.Y * phaseY); if (isDrawSteppedEnabled) { _lineBuffer[j++] = new SKPoint(e2.X, e1.Y * phaseY); _lineBuffer[j++] = new SKPoint(e2.X, e1.Y * phaseY); } _lineBuffer[j++] = new SKPoint(e2.X, e2.Y * phaseY); } if (j > 0) { var pts = trans.PointValuesToPixel(_lineBuffer); int size = Math.Max((XBounds.Range + 1) * pointsPerEntryPair, pointsPerEntryPair); RenderPaint.Color = lineDataSet.Color; for (int i = 0; i < size; i += 2) { canvas.DrawLine(pts[i], pts[i + 1], RenderPaint); } } } } RenderPaint.PathEffect = null; }