Blurry text in legend labels


#1

The text labels in my chart legend are slightly blurred. Thanks to numerous Google results, I realize this is due to programatically-created UI elements being positioned at non-integer locations. I had the same problem with a number (but not all) of my axes’ tick mark labels, and was able to solve it via tickMark.tickLabel.frame = CGRectIntegral(tickMark.tickLabel.frame); in the sChart:alterTickMark:beforeAddingToAxis: callback method, but I can’t figure out where to insert a similar call for legend labels. I tried iterating over ShinobiChart.legend.labels to no effect. I have been modifying the legend label font via ShinobiChart.legend.style.font, but the problem is still present if I don’t.

Any input is appreciated.


#2

I have the same problem, ended up doing my own legend in a table. 


#3

I’ve figured out a partial solution. Apparently, the labels’ frames change when being drawn the first time; presumably, that’s when they’re placed. The trick is to round their positions after they’ve been drawn; i.e., in the sChartRenderFinished: delegate method, do something to the effect of

for (UILabel *label in chart.legend.labels)
    label.frame = CGRectIntegral(label.frame)

I’ve noticed an unfortunate hole in the functionality, though: if your app supports device rotation to some, but not all, orientations, everything’s OK as long as you rotate within permitted orientations, but if you orient to a non-permitted orientation then back to a supported one, the labels won’t be positioned correctly on the next supported orientation you rotate to. That is, if 0°, 90°, and 270° are supported by your app but 180° isn’t, you can rotate between 0°, 90°, and 270° all day with no problems; however, if you rotate to 180°, you can rotate back to 90° or 270° OK, but rotating to 0° will cause the labels to blur up again. Weird, I know. It may be that this is a problem with the simulator; I’ll try again and see.


#4

Hi Samuel,

You are completely right about the cause of the blurring. Another solution would be to loop through every sub-view of the chart recursively, you could use a method like this…

- (void) align:(UIView *)view
{
    for(UIView *sub in [view subviews])
    [self align:sub];
 
    view.frame = CGRectIntegral(view.frame);
}

… and then simply call [self align:myChart] once your chart has finished rendering as you have already hinted towards.

The issue with rotation you have described does seem quite strange. Did you find out anything more about it? I would be interested to hear more if so!

Jan


#5

Calling align directly from sChartRenderFinished: seems to recurse infinitely. Calling it manually after a day to give the chart time to position itself removes the blurring as expected, but it causes the chart layout to change drastically – most notably, the chart becomes taller and its X-axes are drawn on top of the legend: before, after. I think I’ll just ignore the problem for now.

As far as the rotation thing is concerned, I can’t reproduce it any more – it actually stopped happening very shortly after I made that post and, having made a number of modifications to the surrounding code at this point, would be non-trivial to reproduce. Sorry I can’t be of more help.


#6

Ah Samuel, I missed out two parts of the solution, I do apologise.

  1. You would need to put a guard around your [self align:myChart] call, to ensure that your chart is only aligned once. This should stop the recursion.

  2. To explain the expanding of the chart, first I will have to explain a little about how the legend works. The legend is automatically placed, following the placement rules you set on it. Since the legend is a UIView you have the option of setting its frame manually (you can even add it as a subview to something else!). However, when you do this you become responsible for the legend’s positioning and the legend essentially moves out of the “flow” of the chart. This means that the chart’s canvas spans to fit the full size of its parent’s (the chart’s) frame.

Currently, there is no way on our API to “un-manage” our legend after we have taken control of it, which is exactly what we need to do, but for Objective-C users there is a work-around. We need to create a category on SChartLegend which exposes  the BOOL variable setUserSetFrame. This is a private API property that we use internally to check whether the user has manually set the frame of their legend, so all we need to do is set this property back to NO.

This is the category we need to create on SChartLegend:

@interface SChartLegend (hidden)
 
- (void) setUserSetFrame:(BOOL)frameSet;
 
@end

Then after the align calls, do:

[chart.legend setUserSetFrame:NO];

This should put your chart back to assuming automatic layout, and your chart should now be blurless  :grin:!

Would you be able to give the above a try and let me know if it works for you?

Thanks,
Jan


#7

That fixes the legend a treat, but not always the axes labels. However, a hybrid of your solution and my previous one ends up looking pretty slick. Thanks for the help.


#8

This may have been asked already…  Is there a reason that the framework does not do this by default when creating these views?  Having code that traverses the Shinobi Chart view heirarchy and making adjustments to each view’s frame is very much a work-around.


#9

Hi Jmonroe,

I’ve added a task to our backlog to investigate this improvement. Hopefully the work-around will be sufficient until we have had a chance to look into this.  :smile:

Jan


#10

Any news on this issue, it’s a big issue for me and it’s a nasty workaround. The main problem I have is if you implment this workaround the app gets stuck in a loop constantly calling render did finish. Putting a guard on it does not work because as soon as the chart is re-render i.e. device rotation etc it will not realign the views.

This is fundamental and needs to be fixed interally when calculating the rects for all the views. Can’t be that hard for someone to go through the codebase and call CGRectIntegral after each frame calculation?


#11
>> camsoft >> This is fundamental and needs to be fixed interally when calculating the rects for all the views. Can't be that hard for someone to go through the codebase and call CGRectIntegral after each frame calculation?

Yes, you are completely right. My company is also waiting for a fix since month!  :slight_frown:

So please, ShinobiTeam, fix this bug soon.


#12

Having now implemented this workaround it really does not work as soon as anything internally causes a redraw the alignments revert to their sub pixel values. Putting a guard around [self align:myChart] works initally but as soon as you interact with interactive charts this causes a redraw and then the guard would block the [self align:myChart]  method. Removing the guard causes the app to get stuck in a loop constantly calling the render finished delegate method.

Really need a solution to this because my labels on my charts of looking really blury on iPad Mini and iPad 2 which don’t have retina displays.