Reputation: 59
With a LineChartDataSet()
with drawValuesEnabled
set to true, is it possible to draw the value under the entry instead of its default position of drawing the value above the label?
I haven't immediately been able to find anything in the API that helps.
Thanks!
Upvotes: 2
Views: 1926
Reputation: 632
I've implemented something to solve the problem with positioning labels. My goal was to exclude overlapping labels and graph lines. The main idea is to add newlines and spaces to the value string to position it correctly.
Source code is in Objective-C unfortunately, but it can be easily adopted for Swift
LineChartValueFormatter.h file:
#import <Foundation/Foundation.h>
@interface LineChartValueFormatter : NSObject
- (instancetype)initWithChartData:(NSArray<NSNumber *> *)chartData;
- (NSString *)formattedValueStringAtIndex:(NSUInteger)index;
@end
LineChartValueFormatter.m file:
#import "LineChartValueFormatter.h"
typedef enum : NSUInteger {
ChartValueDirectionTop,
ChartValueDirectionTopLeft,
ChartValueDirectionTopRight,
ChartValueDirectionBottom,
ChartValueDirectionBottomLeft,
ChartValueDirectionBottomRight,
} ChartValueDirection;
BOOL chartValueDirectionIsBottom(ChartValueDirection direction) {
return (direction == ChartValueDirectionBottom || direction == ChartValueDirectionBottomLeft || direction == ChartValueDirectionBottomRight);
}
BOOL chartValueDirectionIsTop(ChartValueDirection direction) {
return (direction == ChartValueDirectionTop|| direction == ChartValueDirectionTopLeft || direction == ChartValueDirectionTopRight);
}
@interface LineChartValueFormatter ()
@property (nonatomic, strong) NSArray<NSNumber *> * chartData;
@property (nonatomic, strong) NSMutableArray<NSNumber *> * directions;
@end
@implementation LineChartValueFormatter
- (instancetype)initWithChartData:(NSArray<NSNumber *> *)chartData {
self = [super init];
if (self) {
self.chartData = chartData;
self.directions = [NSMutableArray arrayWithCapacity:chartData.count];
[self.directions addObject:@([self firstValueDirection])];
for (NSInteger i = 1; i < self.chartData.count - 1; ++i) {
[self.directions addObject:@([self commonValueDirectionAtIndex:i])];
}
[self.directions addObject:@([self lastValueDirection])];
}
return self;
}
- (NSString *)formattedValueStringAtIndex:(NSUInteger)index {
if (!self.directions || !self.directions.count) {
return valueString;
}
ChartValueDirection direction = [self.directions[index] integerValue];
NSString * newlineString = @"\n";
NSString * valueString = [self.chartData[index] stringValue];
NSUInteger numberOfSpaces = valueString.length;
// There is U+2007 space used (“Tabular width”, the width of digits), not usual space
NSString * spaceString = [@"" stringByPaddingToLength:numberOfSpaces
withString:@" "
startingAtIndex:0];
switch (direction) {
case ChartValueDirectionTopLeft:
return [NSString stringWithFormat:@"%@%@", valueString, spaceString];
case ChartValueDirectionTopRight:
return [NSString stringWithFormat:@"%@%@", spaceString, valueString];
case ChartValueDirectionBottom:
return [NSString stringWithFormat:@"%@%@", newlineString, valueString];
case ChartValueDirectionBottomLeft:
return [NSString stringWithFormat:@"%@%@%@", newlineString, valueString, spaceString];
case ChartValueDirectionBottomRight:
return [NSString stringWithFormat:@"%@%@%@", newlineString, spaceString, valueString];
default:
return valueString;
}
}
- (ChartValueDirection)firstValueDirection {
double rate = [self.chartData[0] doubleValue];
double nextRate = [self.chartData[1] doubleValue];
if (nextRate > rate) {
return ChartValueDirectionBottomRight;
}
return ChartValueDirectionTopRight;
}
- (ChartValueDirection)lastValueDirection {
NSUInteger count = self.chartData.count;
double rate = [self.chartData[count - 1] doubleValue];
double previousRate = [self.chartData[count - 2] doubleValue];
if (previousRate > rate) {
return ChartValueDirectionBottomLeft;
}
return ChartValueDirectionTopLeft;
}
- (ChartValueDirection)commonValueDirectionAtIndex:(NSUInteger)index {
double rate = [self.chartData[index] doubleValue];
double previousRate = [self.chartData[index - 1] doubleValue];
double nextRate = [self.chartData[index + 1] doubleValue];
if (previousRate > rate && rate > nextRate) {
return ChartValueDirectionBottomLeft;
}
if (previousRate >= rate && rate <= nextRate) {
return ChartValueDirectionBottom;
}
if (previousRate < rate && rate < nextRate) {
return ChartValueDirectionTopLeft;
}
if (previousRate <= rate && rate >= nextRate) {
return ChartValueDirectionTop;
}
return ChartValueDirectionTop;
}
@end
Usage example:
// Here is your code for configuring charts ...
// chartData is your array of y values of type NSArray<NSNumber *> * defined and filled somewhere above
// lineChartDataSet is your dataset for graph drawing defined and initialized somewhere above
LineChartValueFormatter * formatter = [[LineChartValueFormatter alloc] initWithChartData:chartData];
lineChartDataSet.valueFormatter = [ChartDefaultValueFormatter withBlock:^NSString * _Nonnull(double value, ChartDataEntry * _Nonnull entry, NSInteger dataSetIndex, ChartViewPortHandler * _Nullable viewPortHandler) {
return [formatter formattedValueStringAtIndex:entry.x];
}];
This is not really great solution but it allows to fix problem without changing Charts
source code, that was critical for me. Hope it helps someone else
Upvotes: 1
Reputation: 20234
Currently, there's no option to easily change the position of the LineChartDataSet
's label but such a feature has been suggested:
[Feature Request] Make it easier to change the position of the labels drawn above the circles in a LineChartView #2581.
Sadly... this feature request has been open for a long time now so not sure if anyone is even interested in implementing it.
However... if it's absolutely necessary then you can make changes in LineChartRenderer.drawValues(context:).
Here, it calls ChartUtils.drawText(context:text:point:align:attributes:)
, passing a CGPoint
whose y
value determines that position.
You can add some points to the y
position by changing the following line:
pt.y - CGFloat(valOffset) - valueFont.lineHeight
to:
pt.y - CGFloat(valOffset) - valueFont.lineHeight + 100
I hope this helps in some way but be aware that all changes will be local to your project and if you/someone else updates the component then these changes will be lost.
Upvotes: 0