Fira Code

Fira ist eine humanist Sans-Serif Schriftart, die für FirefoxOS entwickelt wurde, und wird zur Zeit für die Sans-Serif Typen, wie die Überschriften, in diesem Blog genutzt. Aber eigentlich geht es mir hier um Fira Mono die dicktengleiche Variante, die später mit Ligaturen (und mehr) zu Fira Code erweitert wurde. Ich sehe wie in genau diesem Moment im Geist des Lesers die Frage „Ligaturen in einer dicktengleichen Schrift?!“ auftaucht. Beziehungsweise „Ist da ein Tippfehler in dickengleich?“ oder „Was sind Ligaturen?“ falls der Leser kein Hobby-Typographie-Nerd ist.

Für letztere klären wir erstmal kurz die beiden Fragen:

Die Dickte bezeichnet die Breite der Metall-Lettern im klassischen Buchdruck; wenn sie für alle Glyphen gleich ist, stehen die Buchstaben immer in perfekt ausgerichteten Spalten untereinander, was von vielen für das Schreiben von Code bevorzugt wird. Die meisten Schreibmaschinen haben ebenfalls solche Schrifttypen verwendet.

fi Ligaturen sind Kontraktionen von mehreren Glyphen in eine Glyphe. Die typischen Ligaturen sind fi oder fl (allerdings nicht in der Schriftart, in der diese Zeilen geschrieben sind, weshalb ich hier ein Bild der fi Ligatur in Computer Modern zeige). Ein paar Ligaturen haben sich mittlerweile zu eigenen Symbolen entwickelt, wie das Kaufmannsund &, das ursprünglich eine Ligatur von et war (Latein für und). Aber dieses Konzept beißt sich anscheinend mit einer dicktengleichen Schrift, in der jeder Buchstabe die gleiche Breite haben soll. Der Clou an der Sache ist, dass Fira Code Ligaturen für übliche Ausdrücke für mathematische Symbole in Programmiersprachen wie >=, != und -> hat, die wie folgt dargestellt werden: >=, !=, ->. Nur zu, kopiert diese Symbole in einen Editor eurer Wahl, um zu sehen, wie sie sich wieder in ihre Bestandteile zerlegen

Nur eine Spielerei? Möglicherweise. Aber ich bin begeistert davon, und verwende Fira Code in allen Editoren, die Ligaturen unterstützen. Der Fairness halber sollte gesagt werden, dass Fira Code nicht als erstes Projekt diese Idee hatte. Hasklig beispielsweise hatte ihr erstes Release 2 Jahre vor der Veröffentlichung von Fira Code im Jahr 2014. Und mittlerweile sind Code-Ligaturen so ziemlich im Mainstream angekommen, seitdem JetBrains Mono im letzten Jahr von dem gleichnamigen IDE-Entwickler veröffentlicht wurde.

Zum Schluss möchte ich noch auf eine Kleinigkeit aufmerksam machen, die wohl nur die wenigsten Nutzer von Fira Code bewusst bemerken würden, die aber zweifellos demonstriert wie durchdacht diese Schrift ist. Denn Fira Code passt die Position von arithmetischen Symbolen an die benachbarten Glyphen an: ein + zwischen zwei Großbuchstaben ist höher als eines zwischen zwei Kleinbuchstaben.

A+A a+a, die Plus-Zeichen haben unterschiedliche vertikale Positionen

Ich persönlich weiß solche Details sehr wertzuschätzen. Es ist ein Beispiel dafür, dass alle Aspekte unserer modernen Gesellschaft, so wenige Gedanken wir uns auch darum machen und für wie trivial wir sie halten, zahllose Stunden Design und Entwicklung gekostet haben und ständig verbessert werden. Typographie — und um das klarzustellen, ich bin beileibe kein Experte — fasziniert mich. Schriften sind exakt, mit klar definierter Funktion, aber obwohl wir sie seit Jahrtausenden benutzen, ist ihre Entwicklung noch lange nicht abgeschlossen. Mit jedem neuen Medium gibt es neue Anforderungen. Marken haben steten Bedarf an individuellen Schrifttypen als Teil ihres Brandings. Für jede neue Anwendung gibt es andere Optimierungskriterien.

Und jedes Mal wenn in meinem Code = und > wieder zu => verschmelzen, freue ich mich erneut über die Magie.

Noch mehr Fraktale

Seit meinem ersten Eintrag über meinen Fraktal-tweetenden Bot @AFractalADay, habe ich selbigen noch um ein paar Fraktale erweitert, die ich hier kurz festhalten möchte. Der ganze Code ist auf Github.

Chaotic Maps

Eine Quadratic Map ist eine Rekursionsgleichung mit einem quadratischen Term, also beispielsweise

$$x_{i+1} = a_0 x^2 + a_1 x + a_2.$$

Das berühmteste Mitglied dieser Familie ist die Logistic-Map mit \(a_0=1, a_1=r, a_2=0\), die chaotisches Verhalten für \(3.56995 < r < 4\) zeigt. Aber leider ist sie nur eindimensional und ihr Attraktor deshalb nicht besonders hübsch.

Um visuell ansprechende Fraktale daraus zu erzeugen, brauchen wir also ein System aus zwei Rekursionsgleichungen, die wir als \(x\)- und \(y\)-Koordinaten betrachten können:

\begin{align*} x_{i+1} &= a_{0} + a_{1} x + a_{2} x^2 + a_{3} x y + a_{4} y + a_{5} y^2\\ y_{i+1} &= a_{6} + a_{7} x + a_{8} x^2 + a_{9} x y + a_{10} y + a_{11} y^2. \end{align*}

Jetzt haben wir 12 freie Parameter, die einen riesigen Parameterraum aufspannen, in dem etwa 1.6% aller Möglichkeiten chaotisches Verhalten mit einem seltsamen Attraktor zeigen.

Quadratic Map

Chaotische Differentialgleichungssysteme

Ein echter Klassiker ist das Differentialgleichungssystem, das die Chaostheorie begründet hat und nach dem der Schmetterlingseffekt benannt ist [1, 2]. Für bestimmte Paramtersätze verlaufen die Bahnkurven entlang eines seltsamen Attraktors, dessen fraktale Dimension \(\approx 2.06\) ist. Da der vollständige Attraktor somit in einer zweidimensionalen Projektion etwas langweilig aussieht, habe ich hier nur eine Trajektorie über kurze Zeit dargestellt.

Lorenz-Attraktor

Und es gibt eine ganze Menge weitere Differntialgleichungssysteme (und chaotic maps), die chaotische Attraktoren aufweisen. Deshalb zeige ich hier noch einen Rössler-Attraktor, der eine vereinfachte Version des Lorenz-Systems ist:

\begin{align*} \frac{\mathrm{d}x}{\mathrm{d}t} &= -(y+z)\\ \frac{\mathrm{d}y}{\mathrm{d}t} &= x + ay\\ \frac{\mathrm{d}z}{\mathrm{d}t} &= b + xz - cz \end{align*}

Und hier haben wir das Glück, dass auch seine Projektion sehr ansehnlich ist.

Rössler-Attraktor

Ich persönlich frage mich, nun wie der Attraktor für das Doppelpendel aussieht. Es ist anscheinend kein Fraktal, aber es sieht dennoch ganz interessant aus:

Doppelpendel

Ising model

Das Ising Modell für Ferromagnetismus wird auch als Drosophila der statistischen Physik bezeichnet: Es ist ein einfaches Modell, dass einen Phasenübergang aufweist — Eisen verliert seine magnetischen Eigenschaften oberhalb der Curie-Temperatur.

Es besteht aus magnetischen Momenten, Spins, die gerne in die gleiche Richtung zeigen wie ihre Nachbarn, aber durch hohe Temperatur gestört werden. Oder etwas formaler: Die innere Energie \(U\) wird durch den Hamiltonian \(\mathcal{H} = - \sum_{<ij>} s_i s_j\) bestimmt, wobei \(s_i = \pm 1\), je nachdem ob der Spin up oder down ist und die Summe über benachbarte Spins läuft. Das System wird immer einen Zustand anstreben, der die freie Energie \(F=U-TS\) minimiert. Das kann entweder passieren, indem \(U\) möglichst klein ist oder die Entropie \(S\) möglichst hoch. Bei großen Werten der Temperatur \(T\) bekommt der Entropie-Term ein höheres Gewicht, sodass Zustände mit hoher Entropie, also zufälligen Spinausrichtungen, bevorzugt sind, bei niedrigen Temperaturen werden Konfigurationen mit niedriger innerer Energie bevorzugt, also solche in denen alle Spins in die selbe Richtung zeigen. Die Temperatur, bei der sich beide Terme die Waage halten, nennt man kritische Temperatur. Hier bilden sich Regionen von Spins, die in die gleiche Richtung zeigen, auf allen Größenskalen. Die fraktale Dimension dieser Regionen ist 187/96, was solche kritische Konfigurationen interessant anzusehen macht. Ich empfehle auf das folgende Bild zu klicken und etwas hineinzuzoomen.

Kritisches Ising System

Twitter Profilhintergrundfarben

Für ein Projekt habe ich Tweets von >8‘000‘000 Twitter-Usern eingesammelt. Dabei fallen noch eine Reihe weiterer Daten an, wie die Profilhintergrundfarbe. Es wäre eine Schande diese Daten einfach verkommen zu lassen, also habe ich nach einer Möglichkeit gesucht diese Information ansprechend darzustellen, was sich als weniger trivial herausgestellt hat, als ich ursprünglich angenommen hatte: Im Idealfall sollten ähnliche Farben nahe beieinander liegen, allerdings ist der RGB Farbraum ein dreidimensionaler Kubus, ein Bild aber nur zweidimensional, sodass es keine „richtige“ Art und Weise gibt, ähnliche Farben nebeneinander anzuordnen.

Ich habe mich hier dafür entschieden eine 2D Hilbert-Kurve durch mein Bild zu legen und die Farben in der Reihenfolge zu zeichnen, in der eine 3D Hilbert-Kurve ihnen im RGB-Kubus begegnet. Wenn man dann noch die beiden Standardhintergrundfarben #F5F8FA und #C0DEED ignoriert, sieht das Ergebnis so aus.

Twitter-Profil-Hintergrundfarbe

Und dank der Python Pakete hilbertcurve und pypng ist der Code sogar ziemlich harmlos:

from math import ceil, sqrt, log2

from hilbertcurve.hilbertcurve import HilbertCurve
import png


"""
    turn an RGB string like `#C0DEED` into a tuple of integers,
    i.e., coordinates of the RGB cube
"""
def str2rgb(s):
    s = s.strip("#")
    return (int(s[0:2], 16), int(s[2:4], 16), int(s[4:6], 16))


"""
    `color_histogram` is a dict mapping an rgb string like `#F5F8FA`
    to the number of usages of this color
"""
def plot_background_colors(color_histogram, filename="colors.png"):
    defaults = {"F5F8FA", "C0DEED"}

    data = {str2rgb(rgb): d for rgb, d in color_histogram if rgb not in defaults}

    # calculate the size of the resulting image
    # for a 2D Hilbert curve, it mus be square with a width, which is a power of 2
    num_pixels = sum(data.values())
    min_width = ceil(sqrt(num_pixels))
    exponent = ceil(log2(min_width))
    width = 2**exponent

    # output buffer for a width x width png, with 4 color values per pixel
    buf = [[0 for _ in range(4 * width)] for _ in range(width)]

    hc2 = HilbertCurve(exponent, 2)
    # there are 256 = 2^8 values in each direction of the RGB cube
    hc3 = HilbertCurve(8, 3)

    sorted_rgbs = sorted(data.keys(), key=lambda x: hc3.distance_from_point(x))

    idx = 0
    for rgb in sorted_rgbs:
        for _ in range(data[rgb]):
            # get the coordinate of the next pixel
            x, y = hc2.point_from_distance(idx)
            # assign the RGBA values to the pixel
            buf[x][4 * y] = rgb[0]
            buf[x][4 * y + 1] = rgb[1]
            buf[x][4 * y + 2] = rgb[2]
            buf[x][4 * y + 3] = 255

            idx += 1

    png.from_array(buf, 'RGBA').save(filename)

Das Histogram, das als Input benötigt wird war in meinem Fall nur eine SQL Query entfernt:

SELECT profile_background_color, COUNT(profile_background_color) FROM users
    GROUP BY profile_background_color;