Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Annotations issues and asking for supporting more types of markers #722

Closed
4np opened this issue Jan 28, 2016 · 10 comments
Closed

Annotations issues and asking for supporting more types of markers #722

4np opened this issue Jan 28, 2016 · 10 comments
Labels

Comments

@4np
Copy link
Contributor

4np commented Jan 28, 2016

Hi Daniel,

I have been investigating exactly the same thing as Simon in (closed) ticket #175 and I ended up at the same conclusion (it does not seem to be possible). Observe the following Line Chart (rendered using HighCharts) I am looking to replicate using iOS Charts.

screen shot 2016-01-27 at 5 33 09 pm

You have a ChartMarker on the chart on the right that highlights the point in the graph on touch. In addition there are Annotations in this chart represented by the speech bubbles. Those also support touch events.

Note that this shown graph has two types of markers: a blue dot and a speech bubble. Right now, in iOS Charts you can only define one type of ChartMarker for a Line Chart, e.g.:

        let lineChartView = LineChartView(frame: myFrame)
        ...

        // configure marker
        lineChartView.marker = BlueCircleChartMarker()

Where the BlueCircleChartMarker implementation is something like this:

import Foundation
import UIKit
import Charts

public class BlueCircleChartMarker: ChartMarker {
    public override var size: CGSize {
        return CGSize(width: 6, height: 6)
    }

    public override func draw(context context: CGContext, point: CGPoint) {
        CGContextSaveGState(context)

        CGContextSetLineWidth(context, 1.0)
        CGContextSetFillColorWithColor(context, UIColor.blueColor().CGColor)
        CGContextAddArc(context, point.x, point.y, 3.0, 0.0, CGFloat(M_PI * 2.0), 0)
        CGContextFillPath(context)

        CGContextRestoreGState(context)
    }
}

I have tried using highlightValues to try to add in annotations like the screen shot above even though that will render with the same BlueCircleChartMarker (the blue circle) as the on-touch highlight marker, but at least it was worth a try:

var highlights = [ChartHighlight]()
// some logic that determine where those three annotations need to show {
highlights.append(ChartHighlight(xIndex: xIndex, dataSetIndex: datasets.count))
// }
lineChartView.highlightValues(highlights)

However this approach does not seem to lead to the desired result. The logic in ChartData.swift seems to expect the _indicesToHighlight to have ChartHighlight instances for every xIndex, or none.

Am I overlooking something? Is there any way to get Annotations working like this?

Thank you! :)

ps. It would be good, for example, to be able to annotate a particular score in a dataset with a specific marker. I am sure there are use cases where annotating a chart with different marker types are desired. Something like this:

let marker = SpeechBubbleChartMarker()
ChartDataEntry(value: someValue, xIndex: someXIndex, annotationMarker: marker)
@liuxuan30
Copy link
Member

I know you have two types of markers, which does not support yet, and it's nice to put markers into data sets, not chart itself. But what's the other issues? I am not clear what's your problems about ChartHighlight.

When you touch the screen, it will try get the closest data set (first calculate the closest xIndex), and use this data set combining xIndex to know which data value to be highlighted.

@liuxuan30 liuxuan30 added the idea label Jan 29, 2016
@liuxuan30 liuxuan30 changed the title Annotations Annotations issues and asking for supporting more types of markers Jan 29, 2016
@4np
Copy link
Contributor Author

4np commented Jan 29, 2016

Hi Xuan,

Thanks for getting back :) I tried the highlightValue route to see if I was able add annotations to the line chart that way, which I did not succeed in. When trying to get (for example) three markers to show up in a chart consisting of multiple datasets either non or only one marker got rendered.

Some of the datasets in the back-end have gaps in them (e.g. dates without measurement). In order to draw them on the chart I split those datasets into multiple datasets so the line chart I am drawing may consist of one or more datasets:

screen shot 2016-01-29 at 8 56 53 pm

This chart consists of three datasets: the dotted grey line, the blue line on the line and a shorter blue line on the right (there's a small gap between the two). The grey line in this example could just be a limit line, but there are cases I have multiple limits in one graph so a single limit line is not sufficient. Instead I am using a dataset.

I was trying to draw markers for some xIndexes on different datasets, like this:

// third dataset, 21st point on the x-axis
ChartHighlight(xIndex: 20, dataSetIndex: 2)

Debugging why some markers were not being drawn appeared to be caused by some xIndex comparison failing. It is not completely clear why though... I got the impression the highlighting expects you to deliver as many ChartHighlights as there are datapoints?

@liuxuan30
Copy link
Member

how do you want to draw the three markers? I'm sorry but it's a lot of words and I'm lost what's the true problem... We need the info where some XIndex comparison failing.

Why you try highlightValue to draw markers? Have you tried overriding drawMarkers?

So I once needed to add as many markers as it needs, I did some customizations. It works fine, and straightforward. I just pass all the marker array via delegate, and then loop the array, get everything it needs to calculate marker position (getMarkerPosition is ios-charts func already), and do the drawing.

    internal override func drawMarkers(context context: CGContext)
    {
        if (!drawMarkers)
        {
            return
        }

        let myMarkers = (delegate as? MyChartViewDelegate)?.markerInfoArray?(self)

        if let markers = myMarkers
        {
            for (var i = 0; i < markers.count; i++)
            {
                let marker = markers[i]
                let xIndex = marker.xIndex
                let dataSetIndex = marker.dataSetIndex

                if (xIndex <= Int(_deltaX) && xIndex <= Int(_deltaX * _animator.phaseX))
                {
                    let d = _data.getDataSetByIndex(dataSetIndex)
                    let e = d.entryForXIndex(xIndex)
                    if (e == nil)
                    {
                        continue
                    }

                    let highlight = ChartHighlight(xIndex: xIndex, dataSetIndex: dataSetIndex)

                    let pos = getMarkerPosition(entry: e!, highlight: highlight)

                    // check bounds
                    if (!_viewPortHandler.isInBounds(x: pos.x, y: pos.y))
                    {
                        continue
                    }

                    // callbacks to update the content
                    marker.refreshContent(entry: e!, highlight: highlight)

                    let markerSize = marker.size
                    if (pos.y - markerSize.height <= 0.0)
                    {
                        let y = markerSize.height - pos.y
                        marker.draw(context: context, point: CGPoint(x: pos.x, y: pos.y + y))
                    }
                    else
                    {
                        marker.draw(context: context, point: pos)
                    }
                }
            }
        }
    }

@liuxuan30
Copy link
Member

And for your question, drawHighlighted is the line chart renderer func to get called when you call highlightValue triggering a needsDisplay; so in drawHighlighted, it will loop [ChartHighlight], get the data set and entry, check positions, and draw highlight, which I think is not a good place to draw your markers. You have to look at how drawHighlighted implemented if you didn't do it before.

@4np
Copy link
Contributor Author

4np commented Feb 3, 2016

Thanks for your reply @liuxuan30 :)

Unfortunately drawMarkers is an internal method of iOS Charts and can only be changed by patching the iOS Charts source directly. It would be acceptable if such functionality could be added by using Swift extensions, but ideally I would like to refrain from patching the source of libraries.

As it does not seem to be possible to add annotation support without patching the source, could we change this issue into a feature request for supporting annotations and multiple marker types?

@liuxuan30
Copy link
Member

you can file one. or just file a PR to make drawMarkers public :) daniel has made a lot of methods to be public recently.
BTW, if you are supporting iOS7, you can directly use source code and do whatever you like.

@danielgindi
Copy link
Collaborator

In Charts 3.0, markers are now something way more configurable, and protocol-based.

@infinitybrad
Copy link

In Charts 3.0, markers are now something way more configurable, and protocol-based.

how to draw two kinds of markers on one line?
I am worried all day long for this problem.
please Show me the example code ~

@aymenbraham
Copy link

Hello , i want to draw circle only on point selected . like the screenshot
Any one know how to do it ? plz i need help
Screenshot 2020-04-15 at 16 27 20

@4np
Copy link
Contributor Author

4np commented Apr 15, 2020

You probably should not ask here as this is a closed issue, but you could create your own chart marker:

import UIKit
import Charts

class MyChartMarker: UIView, IMarker {
    /// Get the marker offer before rendering.
    func offsetForDrawing(atPoint: CGPoint) -> CGPoint {
        // We don't use this, but it needs to be in here to conform to IMarker.
        return atPoint
    }

    /// Refresh the marker's properties before rendering.
    func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
        ...
    }
    
    /// Render the marker.
    func draw(context: CGContext, point: CGPoint) {
        context.saveGState()
        
        defer {
            context.restoreGState()
        }
        
        ...
    }
}

and in your chart view:

marker = MyChartMarker()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants