😊

rust狂信者のための書き散らしグラフ生成のススメ

2023/12/05に公開1

概要

この記事では、グラフオブジェクトを意識していい感じにかける書き散らしグラフのノウハウを通して、rust狂信者の書き散らしプログラムの可能性を布教します。

研究をしていると、このデータパッとグラフ化して傾向みたいなーみたいなシチュエーションが往々にしてあります。
自分はそういうときPython3 + matplotlibを使用していましたが、ドキュメントが多く、書き散らすにはいいというメリットこそあるものの、型がないので予期せぬことが起きたり、オプション指定が面倒だったり、書き散らしたまま負の遺産化しやすかったりという課題を感じていました。
2年ほど前まではgnuplotを使用していて、これも素晴らしいソリューションですが、書き味がモダンでないので、悔しくもmatplotに甘んじていたわけです。
Excelを使うのは本当に敗北なので、どうしたもんか考えていましたが、rustのplottersクレートが良さそうで、そこそこいいやり方に辿り着いたので紹介します。

plottersをスクリプト実行できるようにしたサンプルコードはGitHubにあげてあるので参考にどうぞ
https://github.com/waarrk/rust_playground

rustのスクリプト実行実装の現在

rustは本来(というより最初に得る知識では)、cargoというビルドシステムを使うために、cargoを初期化してコードをsrcフォルダの中に格納し、tomlファイルを編集して依存関係を定義します。

しかし、この方法だと複数の実行バイナリがある場合にコマンドにめんどくささが生じます。
(ファイル構造を見直すか、exampleフォルダ機能を使うか、実行時のコマンドを見直すかといったアプローチが考えられます)

グラフのような書き散らしの場合、コードスニペットがひとつの方が楽だと個人的には考えていて(そんなに大規模に扱わないので)準備された書きやすい環境の頼りがなくても比較的書き散らし的(人間のステートレス?)に書きたいよねというモチベーションがあり、この部分は自分の中で非常に大きなデメリットでした。

そこで、基本的な戦略としてrustをスクリプト実装することを考えます。

現在のところ、方法は3つほどあると思われます。(割とこの3種がごっちゃになって議論されている)
今回は最も後に標準化される可能性が高いだろうという目算でPre-RFCのcargo-scriptを採用しました。

rust-script

https://rust-script.org/

日本語でrustのスクリプト実装を調べるとヒットするものです。

#!/usr/bin/env rust-script

のようなシェバンを読み込んで動作します。

cargo-script

https://docs.rs/crate/cargo-script/0.2.1

#!/usr/bin/env run-cargo-script

こちらもシェバンで動作します。割と長くメンテナされているようで、かなり情報量が多い印象です。ただしまだv0.2.1です

Pre-RFC: cargo-script

https://internals.rust-lang.org/t/pre-rfc-cargo-script-for-everyone/18639/1

rust RFCの3424で議論されているほうのcargo-scriptです。

#!/usr/bin/env cargo

これもシェバンで動作します。
もうNightly Rustにマージされているので、rustup updateで最新版にすれば一応使えます。

rustup update
rustup install nightly

で準備できますので、あとはスクリプトを置いてあるフォルダで

rustup override set nightly

でrustupツールチェーンを切り替えて完了です。
一応rustcが切り替わっているか確認しておきましょう

$ rustup -V                  
rustup 1.26.0 (2023-04-05)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.76.0-nightly (9fad68599 2023-12-03)`

まだNightlyなので、プログラムのシェバンは次のように変更する必要があります

#!/usr/bin/env cargo +nightly -Z script
hello_world.rs
#!/usr/bin/env cargo +nightly -Z script

fn main() {
    println!("Hello, world!");
}
$ chmod +x hello_world.rs
$ ./hello_world.rs      
warning: `package.edition` is unspecified, defaulting to `2021`
   Compiling hello_world v0.0.0 (/Users/waarrk/Documents/software_prayground/rust_playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.46s
     Running `/Users/username/.cargo/target/b7/75e93cec796f3c/debug/hello_world`
Hello, world!

env

  • MacBook Pro Apple M1 16GB ( MacOS 13.1 )
  • cargo 1.74.0 (ecb9851af 2023-10-18)
  • rustc 1.76.0-nightly (9fad68599 2023-12-03)
  • rustup 1.26.0 (2023-04-05)
  • nightly-aarch64-apple-darwin
  • plotters = "0.3.3"

plotters

https://docs.rs/plotters/latest/plotters/

グラフ描画には、plottersを選定しました。おそらくrustで最もメンテされているグラフ描画系クレートで、点群や棒グラフはもちろん、ビットマップ画像など様々対応しており、何よりコードが好きなので選びました。

CSVからのデータ読み込みについては別記事を書いたことがあるのでよければ。

https://zenn.dev/waarrk/articles/c14adce3faee9e

公式の二次関数描画サンプルを一部修正して試します。

plotters_sample.rs
#!/usr/bin/env cargo +nightly -Z script

//! ```cargo
//! [dependencies]
//! plotters = "0.3.3"
//! ```

use plotters::prelude::*;
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // ディレクトリが存在しない場合は作成
    fs::create_dir_all("plotters-output")?;

    // 描画エリアの初期化
    let root = BitMapBackend::new("plotters-output/sample.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    // 図枠/グリッド設定
    let mut chart = ChartBuilder::on(&root)
        .caption("y=x^2", ("sans-serif", 50).into_font())
        .margin(5)
        .x_label_area_size(30)
        .y_label_area_size(30)
        .build_cartesian_2d(-1f32..1f32, -0.1f32..1f32)?;
    // 図枠/グリッド描画
    chart.configure_mesh().draw()?;

    // プロット描画
    chart
        .draw_series(LineSeries::new(
            (-50..=50).map(|x| x as f32 / 50.0).map(|x| (x, x * x)),
            &RED,
        ))?
        .label("y = x^2")
        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &RED));

    // ラベルの設定と描画
    chart
        .configure_series_labels()
        .background_style(&WHITE.mix(0.8))
        .border_style(&BLACK)
        .draw()?;

    // 描画結果保存
    root.present()?;

    Ok(())

次のように実行します。

$ chmod +x plotters_sample.rs
$ ./plotters_sample.rs

plotters-outputディレクトリが追加され、sample.pngが書き出されました!

外部ライブラリも同時に使う別のサンプルも試してみます。

plotters_sample_normal_dist.rs
//! ```cargo
//! [dependencies]
//! plotters = "0.3.3"
//! rand = "0.8.3"
//! rand_distr = "0.4.0"
//! rand_xorshift = "0.3.0"
//! ```

gifアニメーション描画サンプルも動作しました。

まとめ

これで、matplotに頼らなくてもモダンで綺麗なグラフがすらっと書けるのではないでしょうか。

https://docs.rs/plotters/latest/plotters/
を参照しつつより複雑な描画も可能かと思いますので、そちらの説明は公式ドキュメントに渡します。(plottersはまだ開発中なので盛り上げるためにメンテナになるのもいいかもです)

グラフに限らず、rust書き散らしの参考になれば嬉しいです。

Discussion

goggle555goggle555

とても参考になりました!Plottersがcargo-scriptでここまで素直に動くとは...
これでRustユーザーがもっと増えてくれると嬉しいですね〜