Nicholas
Nicholas

Reputation: 1935

How to add a single point to a lineChart in iOS-charts

I have a lineChart and I want to add a single point (scattered point). Is that possible? Is there any workaround? I tried to figure it out by to me seems impossible since even using scatter data only I have to use the same xVals array, and in my case the line has some xVals points and the single scattered point only one value on X and one on Y.

Thanks

EDIT: This is what I want to achieve: enter image description here

To me the only solution using iOS-charts is to superimpose two view charts...

Upvotes: 0

Views: 2767

Answers (1)

Wingzero
Wingzero

Reputation: 9754

Are you asking for truncating the line chart? I am not clear what's your question. If you just want scatter chart + line chart, use CombinedChartView.

I had a feature request for truncating line chart, so we can have 'single point' in the line chart, so if you don't want to connect the dots. Check if it is https://github.com/danielgindi/ios-charts/issues/280. If true, you could help comment so the author will know it.

On your side, what you can try is to subclass the line chart renderer and protocol to allow you add single dots. For me, I want to truncate the line if the current dot cannot be connected to the next one. such as y values: 1-2-3 5(single dot) 7-8-9.

You have to provide what you want so I can help you better.

For your ask in comment, I can only provide a few code get you started, but you have to fully understand what am I trying to do.

Knowledge you must have: CoreGraphics + CGAffineTransform (iOS), viewPortHandler, transformer, axisRenderer (ios-charts).

In my problem, I will subclass ScatterChartView, so I can draw xAxis like yAxis. The idea is to disable xAxis, and let rightAxis to be HorizontalBarChartYAxisRenderer acting as xAxis.

First, subclass ScatterChartView and override initialize():

public override func initialize() {
    super.initialize();
    doubleTapToZoomEnabled = false;
    xAxis.enabled = false;

    // to determine how much space left for the dots to be drawn

    _leftYAxisRenderer = ChartYAxisRenderer(viewPortHandler: _viewPortHandler, yAxis: _leftAxis, transformer: _leftAxisTransformer);

    // right axis will be the bottom axis above xAxis
    _rightAxisTransformer = ChartTransformerHorizontalBarChart(viewPortHandler: _viewPortHandler);
    _rightYAxisRenderer = MyChartYAxisRendererHorizontalBarChart(viewPortHandler: _viewPortHandler, yAxis: _rightAxis, transformer: _rightAxisTransformer);

    renderer = MyScatterChartRenderer(delegate: self, animator: _animator, viewPortHandler: _viewPortHandler);
}

override prepareValuePxMatrix

internal override func prepareValuePxMatrix()
{
    // replace the chartXMin and deltaX with rightAxis values to keep the CTM correct
    _rightAxisTransformer.prepareMatrixValuePx(chartXMin: _rightAxis.axisMinimum, deltaX: CGFloat(_rightAxis.axisRange), deltaY: CGFloat(_leftAxis.axisRange), chartYMin: _leftAxis.axisMinimum);
    _leftAxisTransformer.prepareMatrixValuePx(chartXMin: _rightAxis.axisMinimum, deltaX: CGFloat(_rightAxis.axisRange), deltaY: CGFloat(_leftAxis.axisRange), chartYMin: _leftAxis.axisMinimum);
}

Until now, you have create the rightAxis acting as xAxis, and generate its matrix.

Now for the data part, I will create two dataSets, and assign them to AxisDependencyLeft and AxisDependencyRight via dataSet.axisDependency, so I can know how to render them.

In the chart renderer, I will read the two dataSets, one for x and one for y, and utilize the transformer to get the postion to draw.

such as:

    var xDataSet = scatterData.getRight()
    var yDataSet = scatterData.getLeft()
    var dataSet = yDataSet as! ScatterChartDataSet

    var trans = delegate!.scatterChartRenderer(self, transformerForAxis: dataSet.axisDependency)

    var phaseX = _animator.phaseX
    var phaseY = _animator.phaseY

    var xEntries = (xDataSet as! ScatterChartDataSet).yVals
    var entries = dataSet.yVals
    // if count not equal, something goes off
    if (xEntries.count != entries.count)
    {
        return
    }

    var shapeSize = dataSet.scatterShapeSize
    var shapeHalf = shapeSize / 2.0

    var point = CGPoint()

    var valueToPixelMatrix = trans.valueToPixelMatrix

    var shape = dataSet.scatterShape

    CGContextSaveGState(context)

    for (var j = 0, count = Int(min(ceil(CGFloat(entries.count) * _animator.phaseX), CGFloat(entries.count))); j < count; j++)
    {
        var e = entries[j]
        var xEntry = xEntries[j]
        point.x = CGFloat(xEntry.value)
        point.y = CGFloat(e.value) * phaseY
        point = CGPointApplyAffineTransform(point, valueToPixelMatrix)
        // now you have the point to render, such as:
        if (shape == .Circle)
        {
            CGContextSetFillColorWithColor(context, dataSet.colorAt(j).CGColor)
            var rect = CGRect()
            rect.origin.x = point.x - shapeHalf
            rect.origin.y = point.y - shapeHalf
            rect.size.width = shapeSize
            rect.size.height = shapeSize
            CGContextFillEllipseInRect(context, rect)
        }
    }

I cannot provide all the code I have. You have to understand what am I doing, so you can imagine yours. The only magic is you get a chance to define how you want to render it.

Upvotes: 1

Related Questions