Charts: Complete xAxis label wrapping support for radar/pie/horz-bar charts

Created on 17 Jun 2015  Â·  9Comments  Â·  Source: danielgindi/Charts

What's the plan to support radar/pie chart xAxis label wrapping?

Kind of important, because the radar chart radius is related to max xAxis label width.

enhancement

Most helpful comment

I got horizontal bar label wrapped to two lines working with following implementation.

//use below functions for inserting "/n" at appropriate place in long text, to be assigned to xValues string
// it will insert line break character by calculating middle position in the given string and then finding nearest space to left or right of mid index, will break string based on nearest space found index
static func getWrapedText(str:String?) ->String {

    if str == nil { return "" }

    let lb = "\n"
    let space = " "
    let words = str!.componentsSeparatedByString(space)
    let numberOfSpaces = words.count - 1

    if numberOfSpaces == 0{
        return str!
    }
    if numberOfSpaces == 1
    {
        return words.joinWithSeparator(lb)
    }
    else if numberOfSpaces == 2
    {
        return words[0] + space + words[1] + lb + words[2]
    }
    else
    {
        let parts = breakWordByNearestMidSpace(str!)
        return parts.0 + lb + parts.1
    }
}

static func breakWordByNearestMidSpace(txt:String) ->(String, String)
{
    let count = txt.characters.count

    let halfIndex = count/2

    var firstPart = txt.substringToIndex(txt.startIndex.advancedBy(halfIndex))
    var secondPart = txt.substringFromIndex(txt.startIndex.advancedBy(halfIndex))

    let leftSpaceIndexFromMid = Int(firstPart.characters.reverse().indexOf(" ")!)
    let rightSpaceIndexFromMid = (secondPart as NSString).rangeOfString(" ").location   //int conversion wasn't working, hence nsstring used

// if left space is nearer than space on right from mid index
if leftSpaceIndexFromMid < rightSpaceIndexFromMid{
let breakIndex = halfIndex - leftSpaceIndexFromMid
firstPart = txt.substringToIndex(txt.startIndex.advancedBy(breakIndex-1)) //remove space by subtracting 1
secondPart = txt.substringFromIndex(txt.startIndex.advancedBy(breakIndex))
}
else
{
let breakIndex = halfIndex + rightSpaceIndexFromMid
firstPart = txt.substringToIndex(txt.startIndex.advancedBy(breakIndex)) //remove space by adding 1
secondPart = txt.substringFromIndex(txt.startIndex.advancedBy(breakIndex+1))
}

    return (firstPart, secondPart)
} 

//change in ChartXAxisRendererHorizontalBarChart
if (viewPortHandler.isInBoundsY(position.y))
{
var attributes = [NSFontAttributeName: labelFont, NSForegroundColorAttributeName: labelTextColor]

            if align == .Right {
                let style = NSMutableParagraphStyle()
                style.alignment = NSTextAlignment.Right
                attributes[NSParagraphStyleAttributeName] = style
            }

            drawLabel(context: context, label: label!, xIndex: i, x: pos, y: position.y - _xAxis.labelHeight / 2.0, align: align, attributes: attributes)
        }

//change in ChartUtils.swift - drawAtPoint wasn't working with right alignment
internal class func drawText(context context: CGContext?, text: String, var point: CGPoint, align: NSTextAlignment, attributes: [String : AnyObject]?)
{
let width = text.sizeWithAttributes(attributes).width
let height = text.sizeWithAttributes(attributes).height

    if (align == .Center)
    {
        point.x -= width / 2.0
    }
    else if (align == .Right)
    {
        point.x -= text.sizeWithAttributes(attributes).width
    }

    UIGraphicsPushContext(context)

// (text as NSString).drawAtPoint(point, withAttributes: attributes)
(text as NSString).drawInRect(CGRect(x: point.x, y: point.y, width: width, height: height), withAttributes: attributes)
UIGraphicsPopContext()
}

This should give:

simulator screen shot 31-oct-2015 7 26 16 pm

All 9 comments

Could you maybe add a screenshot for that feature? I don't quite get this requirement.

I can't since radar/pie chart does not support wrapping...
there are some properties in ChartXAxis:

    /// if set to true, word wrapping the labels will be enabled.
    /// word wrapping is done using (value width * labelWidth)
    /// NOTE: currently supports all charts except pie/radar/horizontal-bar
    public var wordWrapEnabled = false

    /// :returns true if word wrapping the labels is enabled
    public var isWordWrapEnabled: Bool { return wordWrapEnabled }

So I think the idea is the labels on xAxis can be wrapped. For example, the radar chart, the radius of the radar chart will rely on the label width. if the xAxis label width is too large, the chart will shrink. So I want the label to be multiple-lined.

Well it says in the docs that it doesn't currently support radar/pie/horizontal-bar charts, as it is takes some delicate work implementing this correctly and efficiently in every feature.
But the plan is that everything will support word-wrapping soon.

+1

I got horizontal bar label wrapped to two lines working with following implementation.

//use below functions for inserting "/n" at appropriate place in long text, to be assigned to xValues string
// it will insert line break character by calculating middle position in the given string and then finding nearest space to left or right of mid index, will break string based on nearest space found index
static func getWrapedText(str:String?) ->String {

    if str == nil { return "" }

    let lb = "\n"
    let space = " "
    let words = str!.componentsSeparatedByString(space)
    let numberOfSpaces = words.count - 1

    if numberOfSpaces == 0{
        return str!
    }
    if numberOfSpaces == 1
    {
        return words.joinWithSeparator(lb)
    }
    else if numberOfSpaces == 2
    {
        return words[0] + space + words[1] + lb + words[2]
    }
    else
    {
        let parts = breakWordByNearestMidSpace(str!)
        return parts.0 + lb + parts.1
    }
}

static func breakWordByNearestMidSpace(txt:String) ->(String, String)
{
    let count = txt.characters.count

    let halfIndex = count/2

    var firstPart = txt.substringToIndex(txt.startIndex.advancedBy(halfIndex))
    var secondPart = txt.substringFromIndex(txt.startIndex.advancedBy(halfIndex))

    let leftSpaceIndexFromMid = Int(firstPart.characters.reverse().indexOf(" ")!)
    let rightSpaceIndexFromMid = (secondPart as NSString).rangeOfString(" ").location   //int conversion wasn't working, hence nsstring used

// if left space is nearer than space on right from mid index
if leftSpaceIndexFromMid < rightSpaceIndexFromMid{
let breakIndex = halfIndex - leftSpaceIndexFromMid
firstPart = txt.substringToIndex(txt.startIndex.advancedBy(breakIndex-1)) //remove space by subtracting 1
secondPart = txt.substringFromIndex(txt.startIndex.advancedBy(breakIndex))
}
else
{
let breakIndex = halfIndex + rightSpaceIndexFromMid
firstPart = txt.substringToIndex(txt.startIndex.advancedBy(breakIndex)) //remove space by adding 1
secondPart = txt.substringFromIndex(txt.startIndex.advancedBy(breakIndex+1))
}

    return (firstPart, secondPart)
} 

//change in ChartXAxisRendererHorizontalBarChart
if (viewPortHandler.isInBoundsY(position.y))
{
var attributes = [NSFontAttributeName: labelFont, NSForegroundColorAttributeName: labelTextColor]

            if align == .Right {
                let style = NSMutableParagraphStyle()
                style.alignment = NSTextAlignment.Right
                attributes[NSParagraphStyleAttributeName] = style
            }

            drawLabel(context: context, label: label!, xIndex: i, x: pos, y: position.y - _xAxis.labelHeight / 2.0, align: align, attributes: attributes)
        }

//change in ChartUtils.swift - drawAtPoint wasn't working with right alignment
internal class func drawText(context context: CGContext?, text: String, var point: CGPoint, align: NSTextAlignment, attributes: [String : AnyObject]?)
{
let width = text.sizeWithAttributes(attributes).width
let height = text.sizeWithAttributes(attributes).height

    if (align == .Center)
    {
        point.x -= width / 2.0
    }
    else if (align == .Right)
    {
        point.x -= text.sizeWithAttributes(attributes).width
    }

    UIGraphicsPushContext(context)

// (text as NSString).drawAtPoint(point, withAttributes: attributes)
(text as NSString).drawInRect(CGRect(x: point.x, y: point.y, width: width, height: height), withAttributes: attributes)
UIGraphicsPopContext()
}

This should give:

simulator screen shot 31-oct-2015 7 26 16 pm

Actually after the latest changes to support rotations, we simply need to remove the dtawLabel override from the horz renderer- and account for the multiline text in labelHeight calculation (currently reverting to lineHeight, ignoring the calculated height).
This is true, I think, to the other renderers as well. As they all call drawLabel, which now uses the utility drawing function from ChartUtils.

For the android counterpart it will be a little more complex because of the use of StaticLayout and so, but I think that we will take care of it soon. (Phil and I are currently working hard on dynamic data and stuff).

what is dynamic data?

I got horizontal bar label wrapped to two lines working with following implementation.

//use below functions for inserting "/n" at appropriate place in long text, to be assigned to xValues string
// it will insert line break character by calculating middle position in the given string and then finding nearest space to left or right of mid index, will break string based on nearest space found index
static func getWrapedText(str:String?) ->String {

    if str == nil { return "" }

    let lb = "\n"
    let space = " "
    let words = str!.componentsSeparatedByString(space)
    let numberOfSpaces = words.count - 1

    if numberOfSpaces == 0{
        return str!
    }
    if numberOfSpaces == 1
    {
        return words.joinWithSeparator(lb)
    }
    else if numberOfSpaces == 2
    {
        return words[0] + space + words[1] + lb + words[2]
    }
    else
    {
        let parts = breakWordByNearestMidSpace(str!)
        return parts.0 + lb + parts.1
    }
}

static func breakWordByNearestMidSpace(txt:String) ->(String, String)
{
    let count = txt.characters.count

    let halfIndex = count/2

    var firstPart = txt.substringToIndex(txt.startIndex.advancedBy(halfIndex))
    var secondPart = txt.substringFromIndex(txt.startIndex.advancedBy(halfIndex))

    let leftSpaceIndexFromMid = Int(firstPart.characters.reverse().indexOf(" ")!)
    let rightSpaceIndexFromMid = (secondPart as NSString).rangeOfString(" ").location   //int conversion wasn't working, hence nsstring used

// if left space is nearer than space on right from mid index
if leftSpaceIndexFromMid < rightSpaceIndexFromMid{
let breakIndex = halfIndex - leftSpaceIndexFromMid
firstPart = txt.substringToIndex(txt.startIndex.advancedBy(breakIndex-1)) //remove space by subtracting 1
secondPart = txt.substringFromIndex(txt.startIndex.advancedBy(breakIndex))
}
else
{
let breakIndex = halfIndex + rightSpaceIndexFromMid
firstPart = txt.substringToIndex(txt.startIndex.advancedBy(breakIndex)) //remove space by adding 1
secondPart = txt.substringFromIndex(txt.startIndex.advancedBy(breakIndex+1))
}

    return (firstPart, secondPart)
} 

//change in ChartXAxisRendererHorizontalBarChart
if (viewPortHandler.isInBoundsY(position.y))
{
var attributes = [NSFontAttributeName: labelFont, NSForegroundColorAttributeName: labelTextColor]

            if align == .Right {
                let style = NSMutableParagraphStyle()
                style.alignment = NSTextAlignment.Right
                attributes[NSParagraphStyleAttributeName] = style
            }

            drawLabel(context: context, label: label!, xIndex: i, x: pos, y: position.y - _xAxis.labelHeight / 2.0, align: align, attributes: attributes)
        }

//change in ChartUtils.swift - drawAtPoint wasn't working with right alignment
internal class func drawText(context context: CGContext?, text: String, var point: CGPoint, align: NSTextAlignment, attributes: [String : AnyObject]?)
{
let width = text.sizeWithAttributes(attributes).width
let height = text.sizeWithAttributes(attributes).height

    if (align == .Center)
    {
        point.x -= width / 2.0
    }
    else if (align == .Right)
    {
        point.x -= text.sizeWithAttributes(attributes).width
    }

    UIGraphicsPushContext(context)

// (text as NSString).drawAtPoint(point, withAttributes: attributes)
(text as NSString).drawInRect(CGRect(x: point.x, y: point.y, width: width, height: height), withAttributes: attributes)
UIGraphicsPopContext()
}

This should give:

simulator screen shot 31-oct-2015 7 26 16 pm

Please convert to swift5

Hello! I'm wondering if there has been any update to this open issue, or if we should implement workarounds for horizontal bar charts like the one listed above =]

Was this page helpful?
0 / 5 - 0 ratings

Related issues

valeIT picture valeIT  Â·  3Comments

guoyutaog picture guoyutaog  Â·  3Comments

PrashantKT picture PrashantKT  Â·  3Comments

guanyanlin picture guanyanlin  Â·  3Comments

Shunshine07 picture Shunshine07  Â·  3Comments