Currently I am plotting the graph with two data plots(two lines on the graph), when I click on one line "indexOfVisiblePointClosestToPlotAreaPoint" method giving me right index and other one throwing me wrong one, not even closest visible one its skipping/jumping multiple points in between. Ex: if I click on index number 20 it jumps to 15 or 25 vice versa. Here is the calculation to find the index
-(NSUInteger)indexOfVisiblePointClosestToPlotAreaPoint:(CGPoint)viewPoint
{
NSUInteger dataCount = self.cachedDataCount;
CGPoint *viewPoints = calloc(dataCount, sizeof(CGPoint));
BOOL *drawPointFlags = calloc(dataCount, sizeof(BOOL));
[self calculatePointsToDraw:drawPointFlags forPlotSpace:(CPTXYPlotSpace *)self.plotSpace includeVisiblePointsOnly:YES numberOfPoints:dataCount];
[self calculateViewPoints:viewPoints withDrawPointFlags:drawPointFlags numberOfPoints:dataCount];
NSInteger result = [self extremeDrawnPointIndexForFlags:drawPointFlags numberOfPoints:dataCount extremeNumIsLowerBound:YES];
if ( result != NSNotFound ) {
CGFloat minimumDistanceSquared = CPTNAN;
for ( NSUInteger i = (NSUInteger)result; i < dataCount; ++i ) {
if ( drawPointFlags[i] ) {
CGFloat distanceSquared = squareOfDistanceBetweenPoints(viewPoint, viewPoints[i]);
if ( isnan(minimumDistanceSquared) || (distanceSquared < minimumDistanceSquared)) {
minimumDistanceSquared = distanceSquared;
result = (NSInteger)i;
}
}
}
}
free(viewPoints);
free(drawPointFlags);
return (NSUInteger)result;
}
Are you checking the plot parameter passed in the delegate method? The touch will register on the frontmost plot if there are any points within the plotSymbolMarginForHitDetection. It won't even check the other plot unless nothing hits on the front one. With two lines close together like that, you'll need to use a small hit margin to make sure touches register on the right plot.
Related
I'm using iOS charts framework to plot this chart, I want to detect tap or touch only on the line's path or on the small circle's on the lines.
My question is,
Is there any default code block to do this?
I tried comparing the entry.value with the array plotted(as in the following code), but it doesn't workout.
-(void)chartValueSelected:(ChartViewBase *)chartView entry:(ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(ChartHighlight *)highlight{
if ([arrayOfPlottedValues containsObject:[NSNumber numberWithInt:(int)entry.value]]) {
//Tapped on line path
}
else{
//Tapped on empty area
}
}
Any insights will be appreciated.
eg : Line chart
I found a way by considering #Wingzero's suggestion, but the major difference was that, I just used the touch point to find out if its on the "marker" or if its outside it. I'm not sure if its the right way, but the solution is,
-(void)chartValueSelected:(ChartViewBase *)chartView entry:(ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(ChartHighlight *)highlight{
//-----------------------------------------------------getting recognizer value
UIGestureRecognizer *recognisedGesture = [chartView.gestureRecognizers objectAtIndex:0];
CGPoint poinOfTouch =[recognisedGesture locationInView:chartView];
CGPoint poinOfMarker =[chartView getMarkerPositionWithEntry:entry highlight:highlight];
if (check if the chartview is BarChartView and if true) {
//-----------------------------------------------------If you want to detect touch/tap only on barchartview's bars
if (poinOfTouch.y > poinOfMarker.y) {
NSLog(#"within the bar area!");
}
else{
NSLog(#"Outside the bar area!");
}
}
else
{
//-----------------------------------------------------If you want to detect touch/tap only on linechartView's markers
//-----------------------------------------------------creating two arrays of x and y points(possible nearby points of touch location)
NSMutableArray *containingXValue = [[NSMutableArray alloc]init];
NSMutableArray *containingYValue = [[NSMutableArray alloc]init];
for (int i =0 ; i<5; i++) {
int roundedX = (poinOfMarker.x + 0.5);
int sumXValuesPositive = roundedX+i;
[containingXValue addObject:[NSNumber numberWithInt:sumXValuesPositive]];
int sumXValuesNegative = roundedX-i;
[containingXValue addObject:[NSNumber numberWithInt:sumXValuesNegative]];
int roundedY = (poinOfMarker.y + 0.5);
int sumYValuesPositive = roundedY+i;
[containingYValue addObject:[NSNumber numberWithInt:sumYValuesPositive]];
int sumYValuesNegative = roundedY-i;
[containingYValue addObject:[NSNumber numberWithInt:sumYValuesNegative]];
}
//-----------------------------------------------------------------------------------------------------------------------------------------
int roundXPointTOuched = (poinOf.x + 0.5);
int roundYPointTOuched = (poinOf.y + 0.5);
//-----------------------------------------------------check if touchpoint exists in the arrays of possible points
if ([containingXValue containsObject:[NSNumber numberWithInt:roundXPointTOuched]] && [containingYValue containsObject:[NSNumber numberWithInt:roundYPointTOuched]])
{
// continue, the click is on marker!!!!
}
else
{
// stop, the click is not on marker!!!!
}
//-----------------------------------------------------------------------------------------------------------------------------------------
}
}
}
Edit : The initial solution was applicable only for the line chart, Now if this same situation arises for bar chart, you could handle it with the above code itself.
Man, I'd been running around it for a while now, feeling really great for getting a positive lead. There is no direction for this issue yet, hope this will be useful for someone like me cheers!
P.S. I'm marking this as answer just to make sure, it reaches the needful :). Thanks
It has a default highlight logic, that is, calculate the closest dataSet and xIndex, so we know which data to highlight.
You can customize this logic to restrain the allowed smallest distance. e.g. define the max allowed distance is 10, if the touch point is away from the closest dot > 10, you return false and not highlgiht.
Highlighter is a class, like BarChartHighlighter, ChartHighlighter, etc.
Update towards your comment:
when you tapped, the delegate method get called, so you know which data is highlighted. Your codes seems fine, however the condition code is blackbox to me. But the delegate will be called for sure, so you only have to worry about your logic.
I have a UITextView which contains text and images. I also have a CGPoint array containing coordinates of images added by a user in the UITextFied. Whenever texts in the UITextView changes (addition or deletion), I get the position of the cursor which is a CGPoint object.
Now I want to loop through my CGPoint array to find how many images fall after the cursor position whether on the same line or lines below. How do I go about it?
Any help would be very much appreciated.
Sure:
var cursor = message.caretRectForPosition(message.selectedTextRange?.end).origin;
for i in 0...emoji1.count-1 {
if ((cursor.x > emoji_pt[i].x) && (cursor.y <= emoji_pt[i].y)) {
continue;
}
else {
//the emoji is after the cursor.
// Return the index and process all images after that index
}
}
To handle the Y position
Bool onPreviousLine = (emoji_pt[i].y < cursor.y)
To handle the X position (lineHeight being a constant depending on your case and the size of images, you could probably get away with some low constant value (e.g. 1.0)).
Bool onTheSameLine = abs(Double(emoji_pt[i].y - cursor.y)) < Double(lineHeight / 2)
Bool beforeX = (emoji_pt[i].x < cursor.x)
Together
if onPreviousLine || (onTheSameLine && beforeX) {
continue
}
In my actionscript 3 i have a textfield, which i fill with the messages (get them from the webservice). Not all the messages are visible, so every (for Example) 5s i'm moving them vertically to the top.
To do that, i'm using the Tween effect, but the thing is, that it moves only the visible text and not the rest text together (the invisible one).
When scrolling - i can see the rest text.
Image to show the situation (Sorry for my skills):
Here are some code: Function to fill messages object. Plus count how many lines every message will take to display ( so i could easilly use the tween effect (From - to))
private function getNewMsg(e:ResultEvent):void
{
size = e.result.toString().split(" ").length - 1;
for (var i=0; i<size; i++)
{
smsList.push(new smsItem(e.result[i].Id, e.result[i].text));
}
summ = 0;
for (var d=0; d<size; d++)
{
textfield.appendText(smsList[d].Text.toUpperCase()+'\n');
if (d >= 1)
{
smsList[d].Lines = textfield.numLines - summ;
summ += smsList[d].Lines;
}
else
{
smsList[d].Lines = textfield.numLines-1;
summ += smsList[d].Lines + 1;
}
}
twEnd = smsList[1].Lines * (-30.5);
}
And on timer i'm starting the Tween:
function timerHandler(e:TimerEvent):void
{
myTweenX = new Tween(textfield, "y", None.easeIn, twStart, twEnd, 4, true)
myTweenX.addEventListener(TweenEvent.MOTION_FINISH, tweenCompleted);
}
function tweenCompleted(e:TweenEvent):void
{
twInd++;
twStart = twEnd;
twEnd += smsList[twInd].Lines * (-30.5);
}
Found the problem. Because of my fixed textfield height, text wasn't. After made the textfields height auto size, evrything got fixed.
I want to know if it is possible to have custom spacing between bars after some fixed interval using Coreplot iOS library.
Like in the image below, after each 7 bars an unusual barspace is shown.
And if it is possible can you please guide how can this be achieved ?
CPTBarPlot has the code to manage this.
-(BOOL)barAtRecordIndex:(NSUInteger)idx basePoint:(CGPoint *)basePoint tipPoint:(CGPoint *)tipPoint
Basically gets the bar and sets its basePoint and tipPoint.
At the end, it is using barOffsetLength to offset each bar based on its index.
CGFloat barOffsetLength = [self lengthInView:self.barOffset] * self.barOffsetScale;
For vertical bars, in your case, its offsetting the x coord of base and tip point. These are usually the same. Here you have the choice of adding your own offset.
Simply, here's what you need to do there in the same function:
CGFloat barOffsetLength = [self lengthInView:self.barOffset] * self.barOffsetScale;
if ([self.dataSource hasGapBeforeIndex:idx]) {
offsetGap += [self.dataSource gapValue];
}
// Offset
if ( horizontalBars ) {
basePoint->y += barOffsetLength;
tipPoint->y += barOffsetLength;
}
else {
//HERO
basePoint->x += barOffsetLength + offsetGap;
tipPoint->x += barOffsetLength + offsetGap;
}
Here, you introduce a new variable in CPTBarPlot called offsetGap which gets increments everytime you introduce a gap. (be careful, this needs to be reset to zero when you change the dataset).
Also, in CPTPlotDataSource introduce
- (BOOL) hasGapBeforeIndex:(NSUInteger)index;
- (CGFloat) gapValue;
and implement it in your View Controller. Now you can introduce the gap anywhere.
PS: This obviously is a hack and upsets the axis labels and other things that might also need adjustment, but gives an overview anyway.
I played around with the sample app to achieve this.
You need to modify the positioning in your Core Plot data source method for the x axis
- (NSNumber *) numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)idx
and take into account where you want the spacing to occur. If you still don't get it, please post some code and I'll show you on that.
Logic example :
I want to represent the data for a month, lets say one that has 30 days, but at each 5 days, I want a pause at each 5 days. So instead of returning 30 in
- (NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot
, you return 34, and at indexes 6, 11, 16, 21 and 26 you return 0 for the method above.
You can extend this if you want not that much space for the 'pauses' and return double the amount of days (60), minus 4 (because for the pauses you return only for one record the value 0) and return for each 2 records the corresponding value in your data source. This can be again extended to your needed multiplier. I hope you got what I mean.
Thanks to #zakishaheen answer I managed to achieve this, but I broke label position and scroll content size 😄. This implementation is hacky thats why I decided not to continue with fixing it, its more just an example.
I created custom CustomOffsetBarPlot class and apply some Objective-C runtime magic.
- (BOOL)superImplementation:(SEL)selector idx:(NSUInteger)idx basePoint:(nonnull CGPoint *)basePoint tipPoint:(nonnull CGPoint *)tipPoint {
Class granny = [self superclass];
BOOL(* grannyImp)(id,SEL,NSUInteger,CGPoint*, CGPoint*) = (BOOL (*)(id,SEL,NSUInteger,CGPoint*, CGPoint*))class_getMethodImplementation(granny, selector);
return grannyImp(self, selector, idx, basePoint, tipPoint);
}
-(BOOL)barAtRecordIndex:(NSUInteger)idx basePoint:(nonnull CGPoint *)basePoint tipPoint:(nonnull CGPoint *)tipPoint {
SEL selector = _cmd;
CGPoint originBasePointStart = *basePoint;
CGPoint originTipPointStart = *tipPoint;
[self superImplementation:selector idx:0 basePoint:&originBasePointStart tipPoint:&originTipPointStart];
BOOL result = [self superImplementation:selector idx:idx basePoint:basePoint tipPoint:tipPoint];
Class granny = [self class];
SEL lengthView = NSSelectorFromString(#"lengthInView:");
CGFloat(* grannyImp)(id,SEL,NSDecimal) = (CGFloat (*)(id,SEL,NSDecimal))class_getMethodImplementation(granny, lengthView);
CGFloat barOffsetLengthOrigin = grannyImp(self, selector, self.barOffset.decimalValue);
NSInteger barOffsetLength = originBasePointStart.x + idx * 18 + idx * 5; // idx * 5 - your offset
basePoint->x = barOffsetLength;
tipPoint->x = barOffsetLength;
return result;
}
I am drawing the line using following code, it works just amazing,
http://www.merowing.info/2012/04/drawing-smooth-lines-with-cocos2d-ios-inspired-by-paper/
Now I want to.....
1> Detect if the line Intersect with itself.
2) Detect if CCSprite is inside this closed line or not.
While searching I came across many logics for LineIntersection but none of them are accurate. I am giving one of them which detects an intersection, but it also detects it when there is no intersection of line.
First Method
- (BOOL) lineIntersectOccured:(CGPoint)t1 pointEnd:(CGPoint)t2
{
BOOL result = NO;
int pointsCount = [arrlinePoints count];
CGPoint cp1;
CGPoint cp2;
for(int i = 0, j = 1; j < pointsCount; i++,j++)
{
[[arrlinePoints objectAtIndex:i] getValue:&cp1];
[[arrlinePoints objectAtIndex:j] getValue:&cp2];
// lines connected do not need to be included.
if((cp2.x == t1.x && cp2.y == t1.y) || (cp1.x == t2.x && cp1.y == t2.y))
{
continue;
}
CGPoint diffLA = CGPointMake(cp2.x - cp1.x,cp2.y - cp1.y);
CGPoint diffLB = CGPointMake(t2.x - t1.x, t2.y - t1.y);
float compA = diffLA.x*cp1.y - diffLA.y * cp1.x;
float compB = diffLB.x*t1.y - diffLB.y*t1.x;
BOOL compA1 = (diffLA.x*t1.y - diffLA.y*t1.x) < compA;
BOOL compA2 = (diffLA.x*t2.y - diffLA.y*t2.x) < compA;
BOOL compB1 = (diffLB.x*cp1.y - diffLB.y*cp1.x) < compB;
BOOL compB2 = (diffLB.x*cp2.y - diffLB.y*cp2.x) < compB;
if(((!compA1 && compA2) || (compA1 && !compA2)) && ((!compB1 && compB2) || (compB1 && !compB2)))
{
result = YES;
}
}
return result;
}
And this is how I call this method,
I have stored my points in the arrLinePoints from pangesture recognizer method
if ([self lineIntersectOccured:[[arrlinePoints objectAtIndex:0] CGPointValue] pointEnd:[[arrlinePoints objectAtIndex:[arrlinePoints count] - 1] CGPointValue]] )
{
NSLog(#"Line Intersected");
}
This gives me true even with the Following Situation
I also have tried the same Functionality with different Approach by adding view into CCDirector's view
UIBezierPath intersect
But this is giving performance issues , my fps reduced to almost 3 to 6. And also that intersection issue remains the same.
The Perfect Situation for Intersection is
Please help as soon as possible!
Thanks for all Support.
As you construct the path yourself, it should not be necessary to test pixels. Instead use the points used to create the path.
It shouldn't be too hard to find a good line segment intersection algorithm. It seems the top answer of this question has a good method:
Determining if two line segments intersect?
Once you've found a hit, use that exact hit point and the history of points to construct a polygon.
From there you should be able to perform a "point in polygon" test.
A few tips for performance:
In search for the intersection, only check the newest line segment for collision with the others (all lines that did not intersect previously will not intersect with each other this time around)
You might skip segments when you can conclude that both points are on one extreme of the line segment, for example you could skip foreign if:
current.a.x < current.b.x && (foreign.a.x < current.a.x && foreign.b.x < current.a.x)
I hope this helps you along.
Not implemented though, I guess you can detect pixel value of the line drawn with the pixel value of your drawn line inside the touchesMoved method.
Or you can refer here for detailed approach. The same work has been done here.