Prontto
Prontto

Reputation: 1681

Drawing linechart in Swift with 1000+ datapoints

So I have Custom view an I'm trying to draw line chart there. First I draw xGrid, yGrid and middleline. Here are my functions to do that:

var width: CGFloat = 400
var height: CGFloat = 200
var xLines: CGFloat = 20
var yLines: CGFloat = 10
let color = NSColor.blackColor()

func drawMiddleLine() {
    let middleHeight = height / 2
    var context = NSGraphicsContext.currentContext()?.CGContext
    CGContextSetStrokeColorWithColor(context, color.CGColor)
    CGContextSetLineWidth(context, 2)
    CGContextMoveToPoint(context, 0, middleHeight)
    CGContextAddLineToPoint(context, width, middleHeight)
    CGContextStrokePath(context)
}

func drawYGrid() {
    let space = height / yLines
    var context = NSGraphicsContext.currentContext()?.CGContext
    CGContextSetStrokeColorWithColor(context, color.CGColor)
    for index in 0...Int(yLines) {
        CGContextMoveToPoint(context, width, (CGFloat(index) * space))
        CGContextAddLineToPoint(context, 0, (CGFloat(index) * space))
    }
    CGContextStrokePath(context)
}

func drawXGrid() {
    let space = width / xLines
    var context = NSGraphicsContext.currentContext()?.CGContext
    CGContextSetStrokeColorWithColor(context, color.CGColor)
    for index in 0...Int(xLines) {
        CGContextMoveToPoint(context, (CGFloat(index) * space), self.bounds.height)
        CGContextAddLineToPoint(context, CGFloat(index) * space, 0)
    }
    CGContextStrokePath(context)
}

Now I have basic grid which width is two times height. Now I want to scale my y-axis, so I'm going to scale my data (In this example, I only take max positive number):

func getMaxValue(data: Array<CGFloat>) ->CGFloat {
    let max = maxElement(data)
    return max
}

And now I scale my y-Axis:

func scaleYAxis(data: Array<CGFloat>) ->Array<CGFloat> {
    let maxValue = getMaxValue(data)
    var factor = height / maxValue / 4
    var scaledY = data.map({datum -> CGFloat in
        var newValue = datum * factor
        return newValue
        })
    return scaledY
}

But when I'm drawing my line with too many datapoints, my drawing will get messed up, because there is too many datapoints. It's ok when there is about 50 datapoints.

For example, I want to get something like this in results: JavaScript linechart with many many datapoints

Any ideas how can I manage to get that in Swift?

And my drawing method is something like that:

func drawLine(data: Array<CGFloat>) {
    var path = CGPathCreateMutable()
    var middleHeight = height / 2
    CGPathMoveToPoint(path, nil, 0, middleHeight)
    var scaledY = sclaleYAxis(data)

    for index in 0..<data.count {
        var xSpot = data[index]
        var ySpot = middleHeight + scaledY[index]

        CGPathAddLineToPoint(path, nil, xSpot, ySpot)
    }
    var layer = CAShapeLayer()
    layer.frame = self.bounds
    layer.path = path
    layer.strokeColor = NSColor.greenColor().CGColor
    layer.fillColor = nil
    layer.lineWidth = 3
    self.layer?.addSublayer(layer)

    // I Have lineLayerStore and I delete all lines when it is needed
    lineLayerStore.append(layer)
}

Upvotes: 1

Views: 2203

Answers (1)

erkanyildiz
erkanyildiz

Reputation: 13234

I cannot understand what the exact problem is without seeing a screenshot but visibility of the datapoints depends on the number of pixels on the screen.

You cannot display more datapoints than the number of pixels, without letting some datapoints share the same pixel column or downsampling the data.

You have 3 options:

  1. Make X axis scrollable and set scrollview's contentSize width to the number data points.

  2. Keep X axis width fixed, downsample the data to reduce the number of datapoints to be displayed, then draw the chart regarding the downsampled data.

  3. Do nothing, try to draw all the datapoints within the fixed width, if your calculations are correct some datapoints will overlap and share the same pixel column in the chart.

Explanation Edit for 3rd option:

For displaying 1000 datapoints on a 200 pixels wide X axis, there will be 5 datapoints per pixel. So;

Datapoints 0, 1, 2, 3 and 4 will be drawn on 1st pixel column.

Datapoints 5, 6, 7, 8 and 9 will be drawn on 2nd pixel column.

Datapoints 10, 11, 12, 13 and 14 will be drawn on 3rd pixel column.

And so on.

For your example, there are 400 pixels and 1000 datapoints, it means 2.5 datapoint per pixel. So;

Datapoints 0, 1, 2 will be drawn on 1st pixel column.

Datapoints 3, 4 will be drawn on 2nd pixel column.

Datapoints 5, 6, 7 will be drawn on 3rd pixel column.

Datapoints 8, 9 will be drawn on 4th pixel column.

And going on like that.

Upvotes: 1

Related Questions