vis4.net

Hi, I'm Gregor, welcome to my blog where I mostly write about data visualization, cartography, colors, data journalism and some of my open source software projects.

Ansatz zur automatischen Auswahl von Linienfarben in Diagrammen

#chart#color#linien#perception#flash

Ein Problem bei der (automatischen) Erzeugung von Liniendiagrammen liegt in der Auswahl von unterscheidbaren Farben. In diesem Artikel wird ein Ansatz entwickelt, der dieses Problem lösen soll. Ausgangspunkt ist zunächst der RGB-Farbraum. Um verschiedene Farben zu erzeugen, können einfach zufällige Farben aus dem RGB-Raum ausgewählt werden. Die folgende Darstellung zeigt eine derart getroffene Auswahl von zehn verschiedenen Linienfarben (rechtsklicken um neue Zufallsfarben zu erzeugen):

Die Nachteile dieses Ansatzes sind offensichtlich und sind vor allem auch Nachteile des Farbmodells. Selbst eine gleichverteilte Auswahl von Farben im RGB-Raum würde mitunter zu nahezu identischen Farben führen. Daher habe ich mich im Folgenden auf den HSL-Farbraum konzentriert.

Wie alle wahrnehmungsorientierten Farbräume enthält auch der HSL-Raum die Dimension Farbwert - ein Wert zwischen 0° und 360° der den Farbton bestimmt. Die folgende Darstellung zeigt die Auswahl von 10 Farben mit gleichem Abstand im Farbraum (in diesem Fall 36°):

Wie man sieht bekommt man dadurch 10 Ausschnitte aus dem Regenbogenspektrum. Bei genauerem Hinsehen fallen aber immer noch Nachteile auf: So sind vor allem die beiden Grün- und Blautöne schwer voneinander zu unterscheiden. Um eine mögliche Ursache dafür zu nennen mache ich einen kurzen Ausflug zur Wahrnehmungspsychologie.

Auf unserer Netzhaut befinden sich zwei Typen von Rezeptorzellen, die sog. Stäbchen und Zapfen. Während die Stäbchen vor allem zum Sehen in der Dämmerung benutzt werden, dienen Zapfen zum Farbsehen. Nun gibt es unter den Zapfen-Zellen wiederum drei verschiedene Typen, wovon jeder zur Wahrnehmung bestimmter Farbfrequenzen geeignet ist.

Um die Sache kurz zu machen, es gibt jeweils einen Zapfentyp für rote, grüne und blaue Farbfrequenzen. Und jetzt kommts: die Verteilung der Zapfenzellen ist in etwa 10:1:1 (rot zu grün zu blau), d.h. wir haben also zehnmal so viele Zapfenzellen für Rottöne. Daher fällt es Menschen naturgemäß leichter, Rottöne voneinander zu unterscheiden als Blau- oder Grüntöne.

Ich habe versucht, diesen Umstand über eine Transferfunktion für die Farbtöne auszugleichen. Die folgende Abbildung zeigt oben einen Farbwertverlauf ohne und unten mit Korrektur. Wie man sehen kann, habe ich einfach den Bereich für grüne und blaue Farbtöne “zusammengedrückt”, um mehr Spielraum zwischen Rot und Gelb zu bekommen.

oben: normaler HSL-Verlauf, unten: HSL-Verlauf mit Farbtonkorrektur
oben: normaler HSL-Verlauf, unten: HSL-Verlauf mit Farbtonkorrektur

Wählt man die 10 Farben mit 36° Abstand nun aus diesem, korrigierten Spektrum, erhält man folgendes Resultat:

Die Farbtonkorrektur hat etwas zur Unterscheidbarkeit der grünen und blauen Linien beigetragen. Für den günstigen Fall, das die Linienhelligkeit keine weitere Bedeutung in der Visualisierung hat, kann man die Unterscheidbarkeit der Linien weiter steigern, indem man abwechselnd helle und dunkle Linien verwendet:

Zur Erzeugung der Abbildungen habe ich die ActionScript-Klasse PerceptualColor verwendet. Die Farbwertkorrektur wird über die folgende Klasse HueCorrection gelöst. Ein- und Ausgabe für die Funktion correctHue ist ein Farbwinkel zwischen 0 und 360°.

public class HueCorrection {
    private static var _tf:Array = [
        [10,5], [30,45], [50,70],
        [70,94], [110,100], [125,115],
        [145,148],[161,174], [182,179],
        [188,185],
        [210,225], [250,255]
    ];

    public static function correctHue(hue:Number):Number {
        var h:uint = hue / 360 * 255;
        var new_hue:Number;
        var lx:Number = 0;
        var ly:Number = 0;

        for (var i:uint=0; i<_tf.length; i++) {
            if (h == _tf[i][1]) {
                new_hue = _tf[i][0];
                break;
            } else if (h < _tf[i][1]) {
                new_hue = lx + ((_tf[i][0] - lx) / (_tf[i][1] - ly)) * (h - ly);
                break;
            }
            lx = _tf[i][0];
            ly = _tf[i][1];
        }
        return new_hue / 255 * 360;
    }
}