Skip to content

Commit

Permalink
Merge pull request #935 from danielgindi/horizontal-cubic-line
Browse files Browse the repository at this point in the history
Horizontal cubic line
  • Loading branch information
danielgindi committed Apr 9, 2016
2 parents 0a43a1c + 7b5462e commit 6b146fb
Show file tree
Hide file tree
Showing 8 changed files with 354 additions and 134 deletions.
14 changes: 13 additions & 1 deletion Charts/Classes/Data/Implementations/Standard/ChartDataSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,19 @@ public class ChartDataSet: ChartBaseDataSet
/// the last end value used for calcMinMax
internal var _lastEnd: Int = 0

public var yVals: [ChartDataEntry] { return _yVals }
/// the array of y-values that this DataSet represents.
public var yVals: [ChartDataEntry]
{
get
{
return _yVals
}
set
{
_yVals = newValue
notifyDataSetChanged()
}
}

/// Use this method to tell the data set that the underlying data has changed
public override func notifyDataSetChanged()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public class LineChartDataSet: LineRadarChartDataSet, ILineChartDataSet

// MARK: - Styling functions and accessors

/// The drawing mode for this line dataset
///
/// **default**: Linear
public var mode: LineChartMode = LineChartMode.Linear

private var _cubicIntensity = CGFloat(0.2)

/// Intensity for cubic lines (min = 0.05, max = 1)
Expand All @@ -64,16 +69,36 @@ public class LineChartDataSet: LineRadarChartDataSet, ILineChartDataSet
}
}

/// If true, cubic lines are drawn instead of linear
public var drawCubicEnabled = false
@available(*, deprecated=1.0, message="Use `mode` instead.")
public var drawCubicEnabled: Bool
{
get
{
return mode == .CubicBezier
}
set
{
mode = newValue ? LineChartMode.CubicBezier : LineChartMode.Linear
}
}

/// - returns: true if drawing cubic lines is enabled, false if not.
@available(*, deprecated=1.0, message="Use `mode` instead.")
public var isDrawCubicEnabled: Bool { return drawCubicEnabled }

/// If true, stepped lines are drawn instead of linear
public var drawSteppedEnabled = false

/// - returns: true if drawing stepped lines is enabled, false if not.
@available(*, deprecated=1.0, message="Use `mode` instead.")
public var drawSteppedEnabled: Bool
{
get
{
return mode == .Stepped
}
set
{
mode = newValue ? LineChartMode.Stepped : LineChartMode.Linear
}
}

@available(*, deprecated=1.0, message="Use `mode` instead.")
public var isDrawSteppedEnabled: Bool { return drawSteppedEnabled }

/// The radius of the drawn circles.
Expand Down Expand Up @@ -167,9 +192,10 @@ public class LineChartDataSet: LineRadarChartDataSet, ILineChartDataSet
copy.cubicIntensity = cubicIntensity
copy.lineDashPhase = lineDashPhase
copy.lineDashLengths = lineDashLengths
copy.lineCapType = lineCapType
copy.drawCirclesEnabled = drawCirclesEnabled
copy.drawCubicEnabled = drawCubicEnabled
copy.drawSteppedEnabled = drawSteppedEnabled
copy.drawCircleHoleEnabled = drawCircleHoleEnabled
copy.mode = mode
return copy
}
}
22 changes: 18 additions & 4 deletions Charts/Classes/Data/Interfaces/ILineChartDataSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,42 @@
import Foundation
import CoreGraphics

@objc
public enum LineChartMode: Int
{
case Linear
case Stepped
case CubicBezier
case HorizontalBezier
}

@objc
public protocol ILineChartDataSet: ILineRadarChartDataSet
{
// MARK: - Data functions and accessors

// MARK: - Styling functions and accessors

/// The drawing mode for this line dataset
///
/// **default**: Linear
var mode: LineChartMode { get set }

/// Intensity for cubic lines (min = 0.05, max = 1)
///
/// **default**: 0.2
var cubicIntensity: CGFloat { get set }

/// If true, cubic lines are drawn instead of linear
@available(*, deprecated=1.0, message="Use `mode` instead.")
var drawCubicEnabled: Bool { get set }

/// - returns: true if drawing cubic lines is enabled, false if not.
@available(*, deprecated=1.0, message="Use `mode` instead.")
var isDrawCubicEnabled: Bool { get }

/// If true, stepped lines are drawn instead of linear
@available(*, deprecated=1.0, message="Use `mode` instead.")
var drawSteppedEnabled: Bool { get set }

/// - returns: true if drawing stepped lines is enabled, false if not.
@available(*, deprecated=1.0, message="Use `mode` instead.")
var isDrawSteppedEnabled: Bool { get }

/// The radius of the drawn circles.
Expand Down
118 changes: 90 additions & 28 deletions Charts/Classes/Renderers/LineChartRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,23 @@ public class LineChartRenderer: LineRadarChartRenderer
}

// if drawing cubic lines is enabled
if (dataSet.isDrawCubicEnabled)
switch dataSet.mode
{
drawCubic(context: context, dataSet: dataSet)
}
else
{ // draw normal (straight) lines
case .Linear: fallthrough
case .Stepped:
drawLinear(context: context, dataSet: dataSet)

case .CubicBezier:
drawCubicBezier(context: context, dataSet: dataSet)

case .HorizontalBezier:
drawHorizontalBezier(context: context, dataSet: dataSet)
}

CGContextRestoreGState(context)
}

public func drawCubic(context context: CGContext, dataSet: ILineChartDataSet)
public func drawCubicBezier(context context: CGContext, dataSet: ILineChartDataSet)
{
guard let
trans = dataProvider?.getTransformer(dataSet.axisDependency),
Expand Down Expand Up @@ -134,12 +138,12 @@ public class LineChartRenderer: LineRadarChartRenderer
// let the spline start
CGPathMoveToPoint(cubicPath, &valueToPixelMatrix, CGFloat(cur.xIndex), CGFloat(cur.value) * phaseY)

for j in minx + 1 ..< min(size, entryCount - 1)
for j in minx + 1 ..< min(size, entryCount)
{
prevPrev = prev
prev = cur
cur = next
next = dataSet.entryForIndex(j + 1)
next = entryCount > j + 1 ? dataSet.entryForIndex(j + 1) : cur

if next == nil { break }

Expand All @@ -148,29 +152,87 @@ public class LineChartRenderer: LineRadarChartRenderer
curDx = CGFloat(next.xIndex - prev.xIndex) * intensity
curDy = CGFloat(next.value - prev.value) * intensity

CGPathAddCurveToPoint(cubicPath, &valueToPixelMatrix, CGFloat(prev.xIndex) + prevDx, (CGFloat(prev.value) + prevDy) * phaseY,
CGFloat(cur.xIndex) - curDx,
(CGFloat(cur.value) - curDy) * phaseY, CGFloat(cur.xIndex), CGFloat(cur.value) * phaseY)
CGPathAddCurveToPoint(cubicPath, &valueToPixelMatrix,
CGFloat(prev.xIndex) + prevDx,
(CGFloat(prev.value) + prevDy) * phaseY,
CGFloat(cur.xIndex) - curDx,
(CGFloat(cur.value) - curDy) * phaseY,
CGFloat(cur.xIndex),
CGFloat(cur.value) * phaseY)
}
}

CGContextSaveGState(context)

if (dataSet.isDrawFilledEnabled)
{
// Copy this path because we make changes to it
let fillPath = CGPathCreateMutableCopy(cubicPath)

if (size > entryCount - 1)
drawCubicFill(context: context, dataSet: dataSet, spline: fillPath!, matrix: valueToPixelMatrix, from: minx, to: size)
}

CGContextBeginPath(context)
CGContextAddPath(context, cubicPath)
CGContextSetStrokeColorWithColor(context, drawingColor.CGColor)
CGContextStrokePath(context)

CGContextRestoreGState(context)
}

public func drawHorizontalBezier(context context: CGContext, dataSet: ILineChartDataSet)
{
guard let
trans = dataProvider?.getTransformer(dataSet.axisDependency),
animator = animator
else { return }

let entryCount = dataSet.entryCount

guard let
entryFrom = dataSet.entryForXIndex(self.minX < 0 ? self.minX : 0, rounding: .Down),
entryTo = dataSet.entryForXIndex(self.maxX, rounding: .Up)
else { return }

let diff = (entryFrom == entryTo) ? 1 : 0
let minx = max(dataSet.entryIndex(entry: entryFrom) - diff - 1, 0)
let maxx = min(max(minx + 2, dataSet.entryIndex(entry: entryTo) + 1), entryCount)

let phaseX = animator.phaseX
let phaseY = animator.phaseY

// get the color that is specified for this position from the DataSet
let drawingColor = dataSet.colors.first!

// the path for the cubic-spline
let cubicPath = CGPathCreateMutable()

var valueToPixelMatrix = trans.valueToPixelMatrix

let size = Int(ceil(CGFloat(maxx - minx) * phaseX + CGFloat(minx)))

if (size - minx >= 2)
{
var prev: ChartDataEntry! = dataSet.entryForIndex(minx)
var cur: ChartDataEntry! = prev

if cur == nil { return }

// let the spline start
CGPathMoveToPoint(cubicPath, &valueToPixelMatrix, CGFloat(cur.xIndex), CGFloat(cur.value) * phaseY)

for j in minx + 1 ..< min(size, entryCount)
{
prevPrev = dataSet.entryForIndex(entryCount - (entryCount >= 3 ? 3 : 2))
prev = dataSet.entryForIndex(entryCount - 2)
cur = dataSet.entryForIndex(entryCount - 1)
next = cur

if prevPrev == nil || prev == nil || cur == nil { return }
prev = cur
cur = dataSet.entryForIndex(j)

prevDx = CGFloat(cur.xIndex - prevPrev.xIndex) * intensity
prevDy = CGFloat(cur.value - prevPrev.value) * intensity
curDx = CGFloat(next.xIndex - prev.xIndex) * intensity
curDy = CGFloat(next.value - prev.value) * intensity
let cpx = CGFloat(prev.xIndex) + CGFloat(cur.xIndex - prev.xIndex) / 2.0

// the last cubic
CGPathAddCurveToPoint(cubicPath, &valueToPixelMatrix, CGFloat(prev.xIndex) + prevDx, (CGFloat(prev.value) + prevDy) * phaseY,
CGFloat(cur.xIndex) - curDx,
(CGFloat(cur.value) - curDy) * phaseY, CGFloat(cur.xIndex), CGFloat(cur.value) * phaseY)
CGPathAddCurveToPoint(cubicPath,
&valueToPixelMatrix,
cpx, CGFloat(prev.value) * phaseY,
cpx, CGFloat(cur.value) * phaseY,
CGFloat(cur.xIndex), CGFloat(cur.value) * phaseY)
}
}

Expand Down Expand Up @@ -240,7 +302,7 @@ public class LineChartRenderer: LineRadarChartRenderer
let valueToPixelMatrix = trans.valueToPixelMatrix

let entryCount = dataSet.entryCount
let isDrawSteppedEnabled = dataSet.isDrawSteppedEnabled
let isDrawSteppedEnabled = dataSet.mode == .Stepped
let pointsPerEntryPair = isDrawSteppedEnabled ? 4 : 2

let phaseX = animator.phaseX
Expand Down Expand Up @@ -425,7 +487,7 @@ public class LineChartRenderer: LineRadarChartRenderer
{
let phaseX = animator?.phaseX ?? 1.0
let phaseY = animator?.phaseY ?? 1.0
let isDrawSteppedEnabled = dataSet.isDrawSteppedEnabled
let isDrawSteppedEnabled = dataSet.mode == .Stepped
var matrix = matrix

var e: ChartDataEntry!
Expand Down
71 changes: 52 additions & 19 deletions ChartsDemo/Classes/Demos/CubicLineChartViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ - (void)viewDidLoad
@{@"key": @"toggleFilled", @"label": @"Toggle Filled"},
@{@"key": @"toggleCircles", @"label": @"Toggle Circles"},
@{@"key": @"toggleCubic", @"label": @"Toggle Cubic"},
@{@"key": @"toggleHorizontalCubic", @"label": @"Toggle Horizontal Cubic"},
@{@"key": @"toggleStepped", @"label": @"Toggle Stepped"},
@{@"key": @"toggleHighlight", @"label": @"Toggle Highlight"},
@{@"key": @"animateX", @"label": @"Animate X"},
@{@"key": @"animateY", @"label": @"Animate Y"},
Expand Down Expand Up @@ -129,25 +131,35 @@ - (void)setDataCount:(int)count range:(double)range
[yVals1 addObject:[[ChartDataEntry alloc] initWithValue:val xIndex:i]];
}

LineChartDataSet *set1 = [[LineChartDataSet alloc] initWithYVals:yVals1 label:@"DataSet 1"];
set1.drawCubicEnabled = YES;
set1.cubicIntensity = 0.2;
set1.drawCirclesEnabled = NO;
set1.lineWidth = 1.8;
set1.circleRadius = 4.0;
[set1 setCircleColor:UIColor.whiteColor];
set1.highlightColor = [UIColor colorWithRed:244/255.f green:117/255.f blue:117/255.f alpha:1.f];
[set1 setColor:UIColor.whiteColor];
set1.fillColor = UIColor.whiteColor;
set1.fillAlpha = 1.f;
set1.drawHorizontalHighlightIndicatorEnabled = NO;
set1.fillFormatter = [[CubicLineSampleFillFormatter alloc] init];

LineChartData *data = [[LineChartData alloc] initWithXVals:xVals dataSet:set1];
[data setValueFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:9.f]];
[data setDrawValues:NO];

_chartView.data = data;
LineChartDataSet *set1 = nil;
if (_chartView.data.dataSetCount > 0)
{
set1 = (LineChartDataSet *)_chartView.data.dataSets[0];
set1.yVals = yVals1;
[_chartView notifyDataSetChanged];
}
else
{
set1 = [[LineChartDataSet alloc] initWithYVals:yVals1 label:@"DataSet 1"];
set1.drawCubicEnabled = YES;
set1.cubicIntensity = 0.2;
set1.drawCirclesEnabled = NO;
set1.lineWidth = 1.8;
set1.circleRadius = 4.0;
[set1 setCircleColor:UIColor.whiteColor];
set1.highlightColor = [UIColor colorWithRed:244/255.f green:117/255.f blue:117/255.f alpha:1.f];
[set1 setColor:UIColor.whiteColor];
set1.fillColor = UIColor.whiteColor;
set1.fillAlpha = 1.f;
set1.drawHorizontalHighlightIndicatorEnabled = NO;
set1.fillFormatter = [[CubicLineSampleFillFormatter alloc] init];

LineChartData *data = [[LineChartData alloc] initWithXVals:xVals dataSet:set1];
[data setValueFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:9.f]];
[data setDrawValues:NO];

_chartView.data = data;
}
}

- (void)optionTapped:(NSString *)key
Expand Down Expand Up @@ -185,6 +197,27 @@ - (void)optionTapped:(NSString *)key
return;
}

if ([key isEqualToString:@"toggleStepped"])
{
for (id<ILineChartDataSet> set in _chartView.data.dataSets)
{
set.drawSteppedEnabled = !set.isDrawSteppedEnabled;
}

[_chartView setNeedsDisplay];
}

if ([key isEqualToString:@"toggleHorizontalCubic"])
{
for (id<ILineChartDataSet> set in _chartView.data.dataSets)
{
set.mode = set.mode == LineChartModeCubicBezier ? LineChartModeHorizontalBezier : LineChartModeCubicBezier;
}

[_chartView setNeedsDisplay];
return;
}

[super handleOption:key forChartView:_chartView];
}

Expand Down
Loading

0 comments on commit 6b146fb

Please sign in to comment.