Shinobi Charts: example of how to subclass SChartCrosshairTooltip or SChartCrosshairMultiValueTooltip - ios

I'm using the Shinobi Controls charting package on iOS and I cannot fathom how to implement a crosshair tooltip showing multiple y values. In my case I have a candlestick chart showing standard financial OHLC data values using the SChartCandlestickSeries class. The documentation seems to imply that I need to subclass an SChartSeries in order to implement some SChartData protocol methods, but I can't believe it's that involved.
I'm still struggling through the documentation here, but if anyone has some example code it would be worth its weight in gold right now!
Note: I've tried simply assigning an instance of SChartCrosshairMultiValueTooltip to the chart's crosshair.tooltip property but that doesn't seem to do very much - I just see a normal tooltip displaying a single x and y value.

It sounds like you're very much along the right lines. You need a multi-valued series (supplied by the appropriate datasource method):
- (SChartSeries *)sChart:(ShinobiChart *)chart seriesAtIndex:(int)index
{
SChartCandleStickSeries *series = [SChartCandlestickSeries new];
series.crosshairEnabled = YES;
return series;
}
And then the chart needs to have a tooltip set to an instance of the type you mentioned (SChartCrosshairMultiValueTooltip):
ShinobiChart *chart = [[ShinobiChart alloc] initWithFrame:self.view.bounds
withPrimaryXAxisType:SChartAxisTypeNumber
withPrimaryYAxisType:SChartAxisTypeNumber];
chart.datasource = self;
[self.view addSubview:chart];
chart.delegate = self;
chart.crosshair.tooltip = [SChartCrosshairMultiValueTooltip new];
For completeness, the following is the data point method of the datasource:
- (id<SChartData>)sChart:(ShinobiChart *)chart
dataPointAtIndex:(int)dataIndex
forSeriesAtIndex:(int)seriesIndex
{
SChartMultiYDataPoint *d = [SChartMultiYDataPoint new];
d.xValue = #(dataIndex);
[d.yValues setValue:_data[dataIndex] forKey:SChartCandlestickKeyOpen];
[d.yValues setValue:#([_data[dataIndex] doubleValue] * 1.3) forKey:SChartCandlestickKeyHigh];
[d.yValues setValue:#([_data[dataIndex] doubleValue] * 0.8) forKey:SChartCandlestickKeyLow];
[d.yValues setValue:#([_data[dataIndex] doubleValue] * 1.1) forKey:SChartCandlestickKeyClose];
return d;
}
(Note that the values here are just samples)

Related

How to select a Column series Bar in code

I have a bar graph chart working and I can select bars by tapping them.
In -sChart:seriesAtIndex: of my ShinobiChart datasource I have implemented:
SChartColumnSeries *series = [[SChartColumnSeries alloc] init];
series.detectTapsOutsideBar = YES;
series.selectionMode = SChartSelectionPoint;
Which is working well. What I want to do now is to be able to select a specific bar based on the index of the data behind it. How do you do this? I have looked on the chart, the series but cannot find any method to select a column.
Also for extra points :) I need to ensure at least one column is always selected.
UPDATE:
I tried adding the following code:
for (int index = 0; index < self.chartView.series[0].dataSeries.dataPoints.count; index++)
{
SChartDataPoint *point = (SChartDataPoint *)self.chartView.series[0].dataSeries.dataPoints[index];
if (lapIndex == index)
{
point.selected = YES;
}
else
{
point.selected = NO;
}
}
Seemed to have no effect at all. I also tried re drawing the chart.
In the end I removed that code and called -reloadData and -redrawChart on the chart and then set selected in the datasource. This is working.
DISCLAIMER I am a developer at ShinobiControls.
We have recently changed our data point selection API which shall be coming up in our next release to make this a bit clearer.
Currently, you have to loop through your series' data points via the "dataSeries.dataPoints" array. Then cast the point you pulled off the array from type id to SChartDataPoint and set the selected property on that point.
Or if you want to select a data point when your chart initially draws, you can just set the selected property of the SChartDataPoint object you return in the SChartDatasource method "dataPointAtIndex:".
To make sure only one point is selected at a time you can set the "togglePointSelection" BOOL property to NO. Setting this property to YES means you can select more than one point at a time.

change color of selected slice in Pie chart using iOS Charts

In the iOS Charts library, unlike the BarChartDataSet class, the PieChartDataSet does not contain any property highlightAlpha that can be used to set a different alpha to the selected slice on the pie chart.
Although such a property can be introduced and using CGContextSetAlpha() we can modify the transparency of the highlighted slice, I want to do it without making any change in the library code. How can it be done?
I checked the code, currently, it does not support this.
public override func drawHighlighted(context context: CGContext, indices: [ChartHighlight])
{
...
CGContextSetFillColorWithColor(context, set.colorAt(xIndex).CGColor)
...
}
This just read data set color and use it to highlight. I think you are welcome to file a PR for such feature. Or I will do it when I have time.
For you, changing the source code seems the only option right now. That's why I think it's good for you to contribute.
For now, I have solved the problem using the delegate method:
- (void)chartValueSelected:(ChartViewBase * __nonnull)chartView
entry:(ChartDataEntry * __nonnull)entry
dataSetIndex:(NSInteger)dataSetIndex
highlight:(ChartHighlight * __nonnull)highlight
{
PieChartView *pPieChartView = (PieChartView *)chartView;
PieChartDataSet *pDataSet = (PieChartDataSet *)[pPieChartView.data.dataSets objectAtIndex:dataSetIndex];
NSMutableArray *pColors = [[NSMutableArray alloc] initWithArray:pDataSet.colors
copyItems:YES];
for (int nIndex = 0; nIndex < pColors.count; nIndex++) {
UIColor *pColor = [pColors objectAtIndex:nIndex];
if (nIndex == entry.xIndex) {
pColor = [pColor colorWithAlphaComponent:1];
}
else {
pColor = [pColor colorWithAlphaComponent:0.3];
}
[pColors replaceObjectAtIndex:nIndex
withObject:pColor];
}
pDataSet.colors = pColors;
}
In my case, I load the slices with alpha component less than 1. On highlighting the slice, the alpha value is changed to 1.
The same effect can be achieved if the highlightAlpha property is introduced in PieChartDataSet class. In the drawHighlighted method, CGContextSetAlpha(context, highlightAlpha) needs to be called. The BarChartDataSet has highlight colors as well, which are absent in PieChartDataSet.

Coreplot iOS - Custom space between graph bars

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;
}

Shinobi Controls - use the hoop data point

I've got a test Shinobi Control chart working but wnat to put a hoop, "O", at each data point.
How do you do this?
You need to set the point color on the series style object appropriately. The series which have the ability to show point (SChartScatterSeries, SChartLineSeries, SChartBandSeries) all have a pointStyle property within their style object, which is an instance of SChartPointStyle. This has the following relevant properties:
innerColor
color
innerRadius
outerRadius
showPoints
Depending on the effect you want (and the series type you are using) you need to set these appropriately. For example, to show 'hoops' on an SChartScatterSeries, you could use the following for the data source method:
- (SChartSeries *)sChart:(ShinobiChart *)chart seriesAtIndex:(int)index
{
_series = [SChartColumnSeries new];
SChartScatterSeries *series = [SChartScatterSeries new];
series.style.pointStyle.innerColor = [UIColor whiteColor];
return series;
}
Note, if you're using SChartBandSeries or SChartLineSeries, these have showPoints set to NO by default, so you would need to set this to YES yourself.

iOS: Tapku Calendar: Need to select multiple dates

I've been checking the Tapku Calendar code for a bit and searched and read all the relevant questions and responses here however none seem to really offer the correct solution to the problem: How to select multiple dates, either programmatically or by tapping. Just a simple blue tile over two adjacent dates would make me happy :-) The post below seems to have a similar question however the answer does not work. The place in the code is not hit unless the month changes - not exactly what I am looking for. What would be great is a higher-level implementation of selectDate: that would select multiple dates. But just the right place to tweak in the library would be a great place to start is anyone is more familiar with the code. Much appreciated.
iOS: Tapku calendar library - allow selecting multiple dates for current month
So after a bit of stepping through code, I have this rudimentary method using a hammer. I adopted most of the code from TKCalendarMonthView.m->selectDay:day method. The method I created basically creates a new TKCalendarMonthTiles object and fills in the details and then adds subviews onto the main TKCalendarMonthTiles object (self). I tag the subviews so I can first get rid of them if they exist at the beginning of the method as I only want to select one additional day (you could leave the subviews attached if you want them to remain in the UI). I don't track the dates or store them or anything however this meets my needs.
The idea is to simply create a view with the correct tile image you want to use and one that contains the text label of the actual "date" like "14" then add those views as subviews to self. The borrowed code does all the calculations for "where" that date tile resides in the grid, so the view is drawn at the correct location. Code:
- (void)markDay:(int)day {
// First, remove any old subviews
[[self viewWithTag:42] removeFromSuperview];
[[self viewWithTag:43] removeFromSuperview];
int pre = firstOfPrev < 0 ? 0 : lastOfPrev - firstOfPrev + 1;
int tot = day + pre;
int row = tot / 7;
int column = (tot % 7)-1;
TKCalendarMonthTiles *deliveryTile = [[TKCalendarMonthTiles alloc] init];
deliveryTile.selectedImageView.image = [UIImage imageWithContentsOfFile:TKBUNDLE(#"TapkuLibrary.bundle/Images/calendar/MyDateTile.png")];
deliveryTile.currentDay.text = [NSString stringWithFormat:#"%d",day];
if(column < 0){
column = 6;
row--;
}
CGRect r = deliveryTile.selectedImageView.frame;
r.origin.x = (column*46);
r.origin.y = (row*44)-1;
deliveryTile.selectedImageView.frame = r;
deliveryTile.currentDay.frame = r;
[[deliveryTile selectedImageView] setTag:42];
[[deliveryTile currentDay] setTag:43];
[self addSubview:deliveryTile.selectedImageView];
[self addSubview:deliveryTile.currentDay];
} // markDay:
I call this method at the end of TKCalendarMonthView.m->selectDay:day as well as at the end of TKCalendarMonthView.m->-reactToTouch:down. Limited testing so far so good. Off to figure out why the timezone setting keeps thinking its tomorrow (I am in Pacific time zone).
Cheers, Michael

Resources