I'm using Shinobi iOS control for drawing line charts in my app. I'm currently rendering a serious of 0s value. The resulting graph is like that:
Now, the problem is that the quantity I'm representing is a currency so it cannot be negative. How can I limit the range to start from 0 in this specific case?
This is how I create the graph:
// initiatialise line chart
ShinobiChart *chartView = [[ShinobiChart alloc]initWithFrame:frame];
chartView.clipsToBounds = NO;
//Choose the light theme for this chart
SChartLightTheme *theme = [SChartLightTheme new];
//perform any theme stylign here before applying to the chart
chartView.theme = theme;
chartView.title = #"my Title";
chartView.autoresizingMask = UIViewAutoresizingNone;
// Use a number axis for the x axis.
SChartDateTimeAxis *xAxis = [[SChartDateTimeAxis alloc] init];
xAxis.title = #"date";
xAxis.tickLabelClippingModeHigh = SChartTickLabelClippingModeTicksAndLabelsPersist;
//keep tick marks at the right end
//Make some space at the axis limits to prevent clipping of the datapoints
xAxis.rangePaddingHigh = [SChartDateFrequency dateFrequencyWithDay:1];
xAxis.rangePaddingLow = [SChartDateFrequency dateFrequencyWithDay:1];
//allow zooming and panning
xAxis.enableGesturePanning = YES;
xAxis.enableGestureZooming = YES;
xAxis.enableMomentumPanning = YES;
xAxis.enableMomentumZooming = YES;
chartView.xAxis = xAxis;
// Use a number axis for the y axis.
SChartNumberAxis *yAxis = [[SChartNumberAxis alloc] init];
yAxis.title = #"Value";
yAxis.enableGesturePanning = YES;
yAxis.enableGestureZooming = YES;
yAxis.enableMomentumPanning = YES;
yAxis.enableMomentumZooming = YES;
yAxis.title = #"Value";
yAxis.style.titleStyle.position = SChartTitlePositionBottomOrLeft;
chartView.yAxis = yAxis;
chartView.userInteractionEnabled = YES;
chartView.gesturePanType = SChartGesturePanTypeNone;
yAxis.anchorPoint = #0;
Set this and it should solve your issue.
Related
I implemented UIdynamics for objects (views) bouncing around the screen. On the simulator this works flawlessly, but but when testing on an actual iphone, the boundaries don't work. I will post code if necessary but this seems to me like im missing something conceptually. Any tips or ideas?
additional details: the boundaries surround the screen and i have tested on both sizes of simulator and it works fine.
"bad" is the name of the view-- (also "screenWidth" and "screenHeight" have been defined as instance variables)
///left wall
badCollision = [[UICollisionBehavior alloc] initWithItems:#[bad]];
badCollision.collisionDelegate = self;
CGPoint pt1 = CGPointMake(0, 0);
CGPoint pt2 = CGPointMake(0, screenHeight);
[badCollision addBoundaryWithIdentifier:#"leftWall" fromPoint:pt1 toPoint:pt2];
[animator addBehavior:badCollision];
//right wall
badCollision = [[UICollisionBehavior alloc] initWithItems:#[bad]];
badCollision.collisionDelegate = self;
pt1 = CGPointMake(screenWidth, 0);
pt2 = CGPointMake(screenWidth, screenHeight);
[badCollision addBoundaryWithIdentifier:#"rightWall" fromPoint:pt1 toPoint:pt2];
[animator addBehavior:badCollision];
//top wall
badCollision = [[UICollisionBehavior alloc] initWithItems:#[bad]];
badCollision.collisionDelegate = self;
pt1 = CGPointMake(0, 0);
pt2 = CGPointMake(screenWidth, 0);
[badCollision addBoundaryWithIdentifier:#"topWall" fromPoint:pt1 toPoint:pt2];
[animator addBehavior:badCollision];
//bottom wall
badCollision = [[UICollisionBehavior alloc] initWithItems:#[bad]];
badCollision.collisionDelegate = self;
pt1 = CGPointMake(0, screenHeight);
pt2 = CGPointMake(screenWidth, screenHeight);
[badCollision addBoundaryWithIdentifier:#"bottomWall" fromPoint:pt1 toPoint:pt2];
[animator addBehavior:badCollision];
And here is what happens when "bad" hits one of the walls.
NSLog(#"Wall Hit");
UIPushBehavior *badForce = [[UIPushBehavior alloc] initWithItems:#[item] mode:UIPushBehaviorModeInstantaneous];
UIView *itemView = (UIView*)item;
itemView.backgroundColor = [UIColor redColor];
[UIView animateWithDuration:0.3 animations:^{
itemView.backgroundColor = [UIColor blueColor];
}];
int xneg = (int)drand48();
if (xneg < .51)
xneg = 1;
else
xneg = -1;
int yneg = (int)drand48();
if (yneg < .51)
yneg = 1;
else
yneg = -1;
double xSpeed = xneg*(drand48()+1)/20+.02;
double ySpeed = yneg*(drand48()+1)/20+.02;
badForce.pushDirection = CGVectorMake(xSpeed,ySpeed);
badForce.active = YES;
Not even the print statement will show in the log
You seem to be behaving like you wanted to update an existing behavior (your badForce.active = YES), but you're creating a new push behavior every time. Do one or the other. If you're going to create a new push behavior every time, you have to add it to your animator every time. If you're going to create it once, then add it to your animator once, and then just update and activate it each time.
You probably want to create one push behavior, add it once, and update it again and again:
- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p
{
NSString *wallIdentifier = (id)identifier;
NSLog(#"Wall Hit: %#", wallIdentifier);
if (!self.push) {
self.push = [[UIPushBehavior alloc] initWithItems:#[item] mode:UIPushBehaviorModeInstantaneous];
self.push.active = NO;
[self.animator addBehavior:self.push];
}
UIView *itemView = (UIView*)item;
itemView.backgroundColor = [UIColor redColor];
[UIView animateWithDuration:0.3 animations:^{
itemView.backgroundColor = [UIColor blueColor];
}];
double weight = 0.5;
double xSpeed = weight * (drand48() * 2.0 - 1.0); // rand number between -weight and weight
double ySpeed = weight * (drand48() * 2.0 - 1.0); // rand number between -weight and weight
if ([wallIdentifier isEqualToString:#"bottomWall"])
ySpeed = -fabs(ySpeed);
else if ([wallIdentifier isEqualToString:#"topWall"])
ySpeed = fabs(ySpeed);
else if ([wallIdentifier isEqualToString:#"leftWall"])
xSpeed = fabs(xSpeed);
else if ([wallIdentifier isEqualToString:#"rightWall"])
xSpeed = -fabs(xSpeed);
self.push.pushDirection = CGVectorMake(xSpeed,ySpeed);
self.push.active = YES;
}
Note, I hope you forgive me for tweaking your pushDirection algorithm, but I inferred from your creating of the four walls (rather than the much easier process of defining the boundaries of the reference view to be the collision boundary) that you wanted the push vector to vary based upon which wall you happened to collide with. The above pushes down if you hit the top wall (and vice versa) and pushes right if you hit the left wall (and vice versa). But use whatever push vector algorithm you want.
The main observation is that you either want to add one push behavior and update it again and again, or you want to just add new instantaneous push behaviors each time. But the above works on both simulator and device (and I'm honestly not clear why it worked on one and not the other for you; I don't see how your original code could have worked on either).
I'm using CorePlot to draw PieChart. I would like to display labels for slices on slices themselves. Is there a way to get coordinates of each slice and then setting the frame of CPTLayer that holds the text label to adjust to coordinates of the slice?
What I am doing so far:
-(CPRLayer*) datLabelForPlot(CPTPlot*)plot recordIndex:(NSUInteger)index {
static CPTMutableTextStyle *textStyle = nil;
NSString *string = #"Test";
if ( !textStyle) {
textStyle= [[CPTMutableTextStyle alloc] init];
textStyle.color = [CPTColor whiteColor];
}
CPTLayer *layer = [[[CPTLayer alloc] initWithFrame:CGRectMake(50,50, 100, 20)]autorelease];
CPTTextLayer *newLayer = nil;
newLayer = [[[CPTTextLayer alloc] initWithText:string style:textStyle] autorelease];
[layer addSublayer:newLayer];
return layer;
}
but regardless of the layer frame, label is always displayed at the same position (outside the chart). How to set the appropriate layer frame to display the text on the slice itself?
Here is the image of the points I would like to know:
Have you tried setting the CPPieChart labelOffset property to a negative value? May be it doesn't provide the level of precision that you need, but it's an easy solution.
Positive Offset:
Negative Offset:
The centerAnchor is expressed as a fraction of the size of the plot area, so you can use the following code to compute its pixel position (point "O" on your picture):
CPTPieChart *pieChart; // the pie chart
CGRect plotAreaBounds = pieChart.plotArea.bounds;
CGPoint anchor = pieChart.centerAnchor;
CGPoint centerPoint = CGPointMake(plotAreaBounds.origin.x + plotAreaBounds.size.width * anchor.x,
plotAreaBounds.origin.y + plotAreaBounds.size.height * anchor.y);
You can look at the Core Plot source code to see how the pie chart computes the positions of the labels. This code accounts for slices that are "exploded" and centers the label between what you called points "A" and "B", offset by the labelOffset. It hides the label if the datasource returned a missing value (NAN) for the slice. The index corresponds to the datasource index of the pie slice. The relevant bits are:
double currentWidth = [self cachedDoubleForField:CPTPieChartFieldSliceWidthNormalized recordIndex:index];
if ( isnan(currentWidth) ) {
contentLayer.hidden = YES;
}
else {
id<CPTPieChartDataSource> theDataSource = id<CPTPieChartDataSource>)self.dataSource;
BOOL dataSourceProvidesRadialOffsets = [theDataSource respondsToSelector:#selector(radialOffsetForPieChart:recordIndex:)];
CGFloat radialOffset = 0.0;
if ( dataSourceProvidesRadialOffsets ) {
radialOffset = [theDataSource radialOffsetForPieChart:self recordIndex:index];
}
CGFloat labelRadius = self.pieRadius + self.labelOffset + radialOffset;
double startingWidth = 0.0;
if ( index > 0 ) {
startingWidth = [self cachedDoubleForField:CPTPieChartFieldSliceWidthSum recordIndex:index - 1];
}
CGFloat labelAngle = [self radiansForPieSliceValue:startingWidth + currentWidth / (CGFloat)2.0];
label.displacement = CGPointMake( labelRadius * cos(labelAngle), labelRadius * sin(labelAngle) );
contentLayer.hidden = NO;
}
Here is my code:
CPTXYPlotSpace *barGraphPlotSpace = [[CPTXYPlotSpace alloc] init];
barGraphPlotSpace.yRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromFloat(0.0) length:CPDecimalFromFloat(100.0)];
CPTXYAxis *rightY = [[CPTXYAxis alloc] init];
rightY.coordinate = CPTCoordinateY;
rightY.orthogonalCoordinateDecimal = CPTDecimalFromFloat(oneDay*7);
rightY.plotSpace = barGraphPlotSpace;
[graph addPlotSpace:barGraphPlotSpace];
This doesn't add another axis to my graph though.
What I'm trying to do is get a second y axis which will go from 0-100 (percent). To do this I'm creating a new plot space and a new y axis adding the new plot space to the y axis and adding the plot space to the graph.
What am I doing wrong?
Thank you.
You need to add the new axis to the graph:
NSMutableArray *newAxes = [graph.axisSet.axes mutableCopy];
[newAxes addObject:rightY];
graph.axisSet.axes = newAxes;
[newAxes release];
I am new to coreplot. I would like to insert a horizontal scroll bar on my application in order to move my graph. I don't know how do to this.
I find some code on internet like this:
-(IBAction)moveLineLocation:(id)sender {
CPPlotRange *rangeX = plotSpace.xRange;
CPPlotRange *rangeY = plotSpace.yRange;
rangeX.location = CPDecimalAdd(rangeX.location, CPDecimalFromFloat(-0.5));
plotSpace.xRange = rangeX;
plotSpace.yRange = rangeY;
[graph.axisSet relabelAxes];
[graph reloadData];
}
But it's not working. Do you have any hint,idea please
Regards
You can make the Graph scroll without scrollbars:
plotSpace.allowsUserInteraction = YES;
plotSpace.globalYRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(-5f) length:CPDecimalFromFloat(5)];
plotSpace.globalXRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(-5f) length:CPDecimalFromFloat(5)];
Those ranges are the limits of the scrolling. Make the Y range equal to your graph range to remove scrolling on the Y axis.
this is the code in the continuation of this question....Allow horizontal scrolling only in the core-plot barchart?
-(BOOL)pointingDeviceDraggedAtPoint:(CGPoint)interactionPoint
{
if ( !self.allowsUserInteraction || !self.graph.plotArea ) {
return NO;
}
CGPoint pointInPlotArea = [self.graph.plotArea convertPoint:interactionPoint toLayer:self.graph.plotArea];
if ( isDragging ) {
pointInPlotArea.y = lastDragPoint.y;//-- Madhup Changed it for allwoing scrolling only in horizontal direction --//
//-- Madhup Changed it for allwoing scrolling only in horizontal direction --//
//CGPoint displacement = CGPointMake(pointInPlotArea.x-lastDragPoint.x, pointInPlotArea.y-lastDragPoint.y);
CGPoint displacement = CGPointMake(pointInPlotArea.x-lastDragPoint.x, 0);
//--******************************--//
CGPoint pointToUse = pointInPlotArea;
// Allow delegate to override
if ( [self.delegate respondsToSelector:#selector(plotSpace:willDisplaceBy:)] ) {
displacement = [self.delegate plotSpace:self willDisplaceBy:displacement];
pointToUse = CGPointMake(lastDragPoint.x+displacement.x, lastDragPoint.y+displacement.y);
}
NSDecimal lastPoint[2], newPoint[2];
[self plotPoint:lastPoint forPlotAreaViewPoint:lastDragPoint];
[self plotPoint:newPoint forPlotAreaViewPoint:pointToUse];
CPPlotRange *newRangeX = [[self.xRange copy] autorelease];
CPPlotRange *newRangeY = [[self.yRange copy] autorelease];
NSDecimal shiftX = CPDecimalSubtract(lastPoint[0], newPoint[0]);
NSDecimal shiftY = CPDecimalSubtract(lastPoint[1], newPoint[1]);
newRangeX.location = CPDecimalAdd(newRangeX.location, shiftX);
newRangeY.location = CPDecimalAdd(newRangeY.location, shiftY);
// Delegate override
if ( [self.delegate respondsToSelector:#selector(plotSpace:willChangePlotRangeTo:forCoordinate:)] ) {
newRangeX = [self.delegate plotSpace:self willChangePlotRangeTo:newRangeX forCoordinate:CPCoordinateX];
newRangeY = [self.delegate plotSpace:self willChangePlotRangeTo:newRangeY forCoordinate:CPCoordinateY];
}
self.xRange = newRangeX;
self.yRange = newRangeY;
//-- Madhup Changed it for keeping y axis fixed --//
NSLog(#"%#",self.graph.axisSet.axes);
NSMutableArray *axisArr= [[NSMutableArray alloc] initWithArray:self.graph.axisSet.axes];
CPXYAxis *yAxis = [axisArr objectAtIndex:1];
CGPoint point = yAxis.position;
point.y -= lastDragPoint.x;
yAxis.position = point;
[axisArr replaceObjectAtIndex:1 withObject:yAxis];
self.graph.axisSet.axes = axisArr;
[axisArr release];
NSLog(#"%#",self.graph.axisSet.axes);
//--******************************--//
lastDragPoint = pointInPlotArea;
return YES;
}
return NO;
}
Now from the code you people can see that i am able to stop the scrolling of map only in horizontal direction, but still I am not able to keep the y-axis fixed. I have written some code for that in this method too but it does not seem to be working.
if you want to fix axis when scrolling add this lines
CPTXYAxisSet *axisSet = (CPTXYAxisSet *)_graph.axisSet;
axisSet.xAxis.axisConstraints = [CPTConstraints constraintWithLowerOffset:0.0];
axisSet.yAxis.axisConstraints = [CPTConstraints constraintWithLowerOffset:0.0];
The original question now has two viable solutions that work for the latest, as of this date, code in the repository.
Allow horizontal scrolling only in the core-plot barchart?
To keep the graph only in quadrant one:
Allow horizontal scrolling only in the core-plot barchart?
To only allow scrolling in the horizontal:
Allow horizontal scrolling only in the core-plot barchart?
I've used both solutions with a scatter plot and it works great.