2 min read
matplotlib描画のためのフォント探しの旅

やりたいこと: 味気ないmatplotlibの描画を、教科書で見るようないい感じの図に近づけたい。

最近はプログラミングを学ぶ時にPythonから始める流れが主流のように思います。そうすると、なんかしら可視化を行う際にまずmatplotlibを使うのが当たり前のようになります。 ただ、いわゆる”論文の図表”における作法(serifを使うだとか線の太さだとかticksだとか)に対してmatplotlibのデフォルトはあまり最適化されておらず、その目的で使うためには色々と手を加える必要があり、独自のスタイルを整えている人もいるだろうと思います。
手をつけなければいけない最重要項目はデフォルトフォントのDejaVu Sansからの脱却とそれ以外の「いいフォント」の選定で、ここでは「いいフォント」としてHersheyフォント(https://www.nv5geospatialsoftware.com/docs/Using_Hershey_Vector_Fon.html, Wikipediaなど参照)をベースに、matplotlibでどこまであがけるかを試します。

smplotlib

smplotlibは、AstroJacobLi氏が作成した、matplotlibでSupermongoのようなプロットを作るためのモジュールです。

(s(uper)m(ongo+at)plotlib?

pipでインストールできて(簡単!)、importした後にsmplotlib.set_style()で設定を適用するだけで、READMEにあるようなプロットが作成できます(簡単!)。
このモジュールの核となる部分には、HersheyフォントをOpenTypeフォントとして再現したAVHershey-OTFをさらにアップデートしたHershey_font_TTFが使われているようです。この2つの間では、U+2299(⊙)などが追加されるアップデートが行われています。

しかし、このHershey_font_TTFにおける重大な課題は、ギリシャ文字に対応していないことです。この課題についてはすでにこちらのサイトで指摘されています。そんなに使う機会が多くないとはいえ、そこまではカバーしておきたい、という気持ちから同様のフォントを探してみたらずいぶんと旅をしてしまった、というのが本題です。

hersheyFontTex

hersheyFontTexは、(おそらく)Hershey-TTFをベースに作成されたOTFフォントです。Dener-Silva氏のHershey-TTFにはUnknown_1~4.ttfというファイルが存在し、これらには色々な特殊文字が含まれていました。素人なので詳細は分かりませんが、hersheyFontTexではこれらの特殊文字も含めて、OTFフォントとしてアップデートされているようです。

設定と描画

フォントをインストールして.matplotlib/にあるフォントのキャッシュファイルを削除した後、以下の設定で利用可能になります。 ここでは、$で囲まれていようが常にHersheyTex_Bookを使うように設定します。

import matplotlib.pyplot as plt

plt.rcParams.update(
    {
        "font.family": "HersheyTex_Book",
        "xtick.direction": "in",
        "ytick.direction": "in",
        "xtick.minor.visible": True,
        "ytick.minor.visible": True,
        "mathtext.fontset": "custom",
        "mathtext.rm": "HersheyTex_Book",
        "mathtext.it": "HersheyTex_Book",
        "mathtext.bf": "HersheyTex_Book",
        "mathtext.sf": "HersheyTex_Book",
        "mathtext.tt": "HersheyTex_Book",
    }
)

Lane-Emden方程式の数値解を例に描いてみました。

smlike1

太陽質量などは以下のようにして書いています。mathtext.itで斜体になるスタイルを与えていないので(それが用意されていないので当然とも)、$H_2O$も斜体になることなく書かれています。

ax.text(
    0.7, 0.4,
    r"Solar Mass:$M_\odot$, Earth Mass:$M_\oplus$, $H_2O$",
    fontsize=12,
    transform=ax.transAxes,
    ha="center",
    va="center",
)

ax.set_xlabel(r"$\xi$")
ax.set_ylabel(
    r"theta,$\theta$, vartheta,$\vartheta$",
)
plt.title(
    "Numerical solutions of the Lane-Emden equation\n(Greeks αβγδεζηθικλμνξοπρστυφχψω are supported)"
)

matplotlibのデフォルトのカラーで引かれた曲線に、前世紀の空気感のある文字の組み合わせがいい感じです。Romanなどの別スタイルはカバーできておらずSimplexのみというところはやや寂しいところですが、これだけでも十分に満足できると思います。
とりあえずの目標は達成できました。

Everson Mono

フォント探しの旅の途中で、どこかのサイトでEverson Monoというフォントがmonospaceの例として並んでいるのを見つけました。 このフォントの利点は、(macOSユーザーとして)Homebrewで簡単にインストールできる点に加え、英語以外の文字に対するカバー範囲が非常に広いことです。上のページの”Supported Repertoire”を見ると、LatinやGreekをはじめたくさんの言語をカバーしています。Cherokee? Runic? Lisu?初めて見る文字がたくさんあります。世界は広い。

brew install --cask font-everson-mono

ちなみに、Everson Monoにはsharewareライセンスが付与されています(おそらくHomebrewでインストール・管理できることとライセンスの有無は別問題だと思われる)。 ライセンス料は25ユーロ、ニッチなプロッティングのためにこれを支払うか?と一瞬考えたりもしましたが、作成に10年以上の年月がかかっていることを考えると、妥当というか、むしろ安いまであります。 面白いフォントを知ることができてありがとうという気持ちも込めて、PayPalで送金してこの記事を書いています。

matplotlibの設定と完成品1

上記の設定から、“HersheyTex_Book”の部分だけを変更します。

import matplotlib.pyplot as plt

plt.rcParams.update(
    {
        "font.family": "Everson Mono",
        "mathtext.rm": "Everson Mono",
        "mathtext.it": "Everson Mono",
        "mathtext.bf": "Everson Mono",
        "mathtext.sf": "Everson Mono",
        "mathtext.tt": "Everson Mono",
    }
)

同様にLane-Emden方程式の数値解を描いてみます。ギリシャ文字、上・下付き文字、太陽質量なども問題なさそうです。

smlike2

matplotlibの設定と完成品2

太陽質量たちのMを斜体にしたいので、mathtext.itにスタイルを適用します。Everson Monoの斜体はitalicではなくobliqueであることに注意して、またboldも用意されているのでこちらも変更します。

plt.rcParams.update(
    {
        "mathtext.it": "Everson Mono:oblique",
        "mathtext.bf": "Everson Mono:bold",
    }
)

斜体スタイルを適用したので、化学式は普通に$H_2O$と書くのではなく、よくあるように$\mathrm{H_2O}$として斜体対策をします。

smlike2_obl1

matplotlibの設定と完成品3

さて、ここまでのプロット中のテキストは以下のように書いていました。

ax.text(
    ...,
    r"Solar Mass:$M_\odot$, Earth Mass:$M_\oplus$, $H_2O$",
    ...,
)

その出力を見ると、中央の丸の主張が強かったり、十字が丸と繋がっていなかったり、やや違和感があります。 mathtext Symbolsで定義されているものを確認すると、odotはU+2299になるようです。 Everson MonoのU+2299はというと、“CIRCLE DOT OPERATOR”という名前で、確かに中央の丸が大きいことが確認できます。 別に定義されているU+2609の”SUN”とは異なっています。 そこで、U+2609で描画するために、適当な変数に文字コードを与えてrfを指定した文字列の中に変数を入れる、という形で解決できるか試してみます。

(あれ、Hershey_font_TTFのU+2299は…?)

  • \odot\oplusの代替を定義する方法
SUN = "\u2609"
EARTH = "\u2641"

...

ax.text(
    ...,
    rf"Solar Mass:$M_{{{SUN}}}$, Earth Mass:$M_{{{EARTH}}}$," + r"$\mathrm{H_2O}$",
    ...,
)

  • M_\odotM_\oplusの代替を定義する方法
MSUN = "M_\u2609"
MEARTH = "M_\u2641"

...

ax.text(
    ...,
    rf"Solar Mass:${{{MSUN}}}$, Earth Mass:${{{MEARTH}}}$," + r"$\mathrm{H_2O}$",
    ...,
)

smlike2_obl2

いい感じです。個人的にはこれで大満足です。

Appendix

monaspace

一連の調べ物が終わりプロットの試行も終わり記事を書くぞというところで、monaspaceというフォントの存在を知りました。これもHomebrewで簡単にインストールできます。

brew install --cask font-monaspace

フォントの細かいスタイルの分類として、5つの希ガスの名前がつけられています。その中でXenonはほどほどに整ったserif(slab serifというらしい)で、Hershey_font_TTFに近い印象がありました。さらに、weightを一番細いものに設定すれば、だいぶ似た印象があるように思います。
その一方で、このフォントがリリースされてまだ3年、最近のバージョン1.3は昨年9月にリリースという若さのせいか、まだギリシャ文字への対応が届いていません。ちょっと調べてみるとニーズはあるようで、その提案が将来(ver1.4)においてReadyとなっています。もしかすると、近いうちに実装されて好敵手になるかもしれません。今のところ軍配はEverson Monoにあると思っています。

Hershey-Noailles

冒頭のHersheyフォントについて検索していると、おそらくこのフォントに辿り着くことかと思います。 hershey-noailles.luuse.io/www/ではFutura、Times、OldFrench、Helpme(…?)などの様々なスタイルに、それぞれSimplex、Duplexなどのバリエーションを確認できます。
また、typotheque.luuse.io/specimen/hershey-noaillesでは、Cursiveや太陽の記号を含んだAstorologyも確認できます。前者のページとは異なり、このページからは直接フォントをダウンロードする術がないように思いますが、gitlabを探すとそれらもダウンロードできます。
これらを同様に取り込めたらいいところなのですが、ギリシャ文字への対応がないことから現状ではHershey_font_TTFで起きうる問題が再燃してしまいます。