Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use colour for text colouring without slowing down the process?

I have found that time of the string colouring depends on how many different NSColors are used. In code below if I use only one colour for the three cases then the text colouring process is 3 times faster than in the case when three different colours are used for these three cases, each colour for each case. Why ? Is there a way not to slow down the colouring for three different colours ?

for i in 0..<arrayOfNSRangesForA.count
{
    textFromStorage.addAttribute(NSForegroundColorAttributeName, value: NSColor.green, range: arrayOfNSRangesForA[i])
}

for i in 0..<arrayOfNSRangesForT.count
{
   textFromStorage.addAttribute(NSForegroundColorAttributeName, value: NSColor.green, range: arrayOfNSRangesForT[i])
}

for i in 0..<arrayOfNSRangesForC.count
{
    textFromStorage.addAttribute(NSForegroundColorAttributeName, value: NSColor.green, range: arrayOfNSRangesForC[i])
}

Update I have found one more BAD thing. When I changed colouring from NSForegroundColorAttributeNameto NSBackgroundColorAttributeName the running time has increased significantly - 10 times. For 20 000 characters, it was for one colour, for NSForegroundColorAttributeName- 1 sec, for NSBackgroundColorAttributeName - 10 sec; if three colours - 3 and 30 sec accordingly. For me it is very bad feature of Swift !!! It is not possible to do normal work with DNA (ATGC sequence) colouring, since the length of DNA is thousands of A,T,G,C characters!

Update In comments I have a suggestion to colour only visible part of text. I have tried this approach and it is much worse even for shorter text in comparison with what I did in standard way. So, I had NSRange of text for visible part of text, and did colouring on fly while scrolling by using notification when scrolling is on. It is a bad way.

like image 878
VYT Avatar asked Jan 31 '17 16:01

VYT


1 Answers

The biggest obstacle is laying out all these attributed characters in the text view. Colorize the DNA sequence takes minimal amount of time. Instead of writing your own layout manager or text storage class, you can adopt a divide-and-conquer approach by colorizing the text view in chunks at a time:

@IBOutlet var textView: NSTextView!
var dnaSequence: String!
var attributedDNASequence: NSAttributedString!

@IBAction func colorize(_ sender: Any) {
    self.dnaSequence = "ACGT" // your plaintext DNA sequence
    self.attributedDNASequence = self.makeAttributedDNASequence()

    // Rendering long string with the same attributes throughout is extremely fast
    self.textView.textStorage?.setAttributedString(NSAttributedString(string: dnaSequence))

    let step = 10_000   // colorize 10k characters at a time
    let delay = 0.2     // delay between each render
    for (i, location) in stride(from: 0, to: self.dnaSequence.characters.count, by: step).enumerated() {
        let length = min(step, self.dnaSequence.characters.count - location)
        let range = NSMakeRange(location, length)

        // Since we are modifying the textStorage of a GUI object (NSTextView)
        // we should do it on the main thread
        DispatchQueue.main.asyncAfter(deadline: .now() + (delay * Double(i))) {
            let subtext = self.attributedDNASequence.attributedSubstring(from: range)

            print("Replacing text in range \(location) to \(location + length)")
            self.textView.textStorage?.replaceCharacters(in: range, with: subtext)
        }
    }
}


// MARK: -
var colorA = NSColor.red
var colorC = NSColor.green
var colorG = NSColor.blue
var colorT = NSColor.black

func makeAttributedDNASequence() -> NSAttributedString {
    let attributedText = NSMutableAttributedString(string: dnaSequence)
    var index = dnaSequence.startIndex
    var color: NSColor!

    for i in 0..<dnaSequence.characters.count {
        switch dnaSequence[index] {
        case "A":
            color = colorA
        case "C":
            color = colorC
        case "G":
            color = colorG
        case "T":
            color = colorT
        default:
            color = NSColor.black
        }

        attributedText.addAttribute(NSForegroundColorAttributeName, value: color, range: NSMakeRange(i,1))
        index = dnaSequence.index(after: index)
    }

    return attributedText
}

The trick is to make the application as responsive as possible so the user is unaware that things are still being done in the background. With a small delay (<= 0.3 second) I couldn't scroll my mouse fast enough to reach the end of text view before everything has been colorized (100k characters).

On a 100k-character test, it took 0.7 seconds to until the colorized string first appeared inside the text view instead of the 7 seconds if I did everything at once.

like image 109
Code Different Avatar answered Oct 15 '22 17:10

Code Different