MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
Rustの基礎 - グラフのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
Rustの基礎 - グラフ
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == ここでは、RustでGUIアプリケーションを作成し、グラフを表示する方法を記載する。<br> <br><br> == Rustでのグラフ描画について == Rustには、グラフを描画するための複数のライブラリとアプローチが存在する。<br> 主なアプローチとしては、以下に示すようなものがある。<br> <br> * plotters *: Rustネイティブのプロット作成ライブラリ * eframe + egui_plot *: GUIフレームワークとプロット機能の組み合わせ *: eframe (egui) は、Rustで書かれたイミディエートモードGUIライブラリであり、WebAssemblyにも対応している。 * gtk-rs + Cairo *: GTKとCairoを使用した描画 <br> eframe (egui) はRustの標準ライブラリではないため、プロジェクトのCargo.tomlに依存関係を追加する必要がある。<br> 以下に示すように、Cargo.tomlの[dependencies]セクションに追加する。<br> <syntaxhighlight lang="toml"> [dependencies] eframe = "0.24" egui_plot = "0.24" </syntaxhighlight> <br> 依存関係を追加した後、以下に示すコマンドを実行してライブラリをダウンロードしてビルドする。<br> cargo build <br> eframe(egui)について詳しく知りたい場合は、[https://github.com/emilk/egui GitHubリポジトリ] や [https://docs.rs/egui/latest/egui/ 公式ドキュメント]を参照すること。<br> <br><br> == plottersライブラリ == ==== plottersライブラリとは ==== plottersは、Rustで書かれたデータ可視化ライブラリである。<br> <br> このライブラリの大きな特徴は、バックエンドに依存しない設計となっている点である。<br> つまり、同じコードで、画像ファイル(PNG、JPEG)、ベクターグラフィックス(SVG)、HTMLキャンバス、ビットマップバッファ等、様々な出力形式にグラフを描画できる。<br> <br> plottersは、科学技術計算や統計分析の分野で特に威力を発揮する。<br> グラフを画像ファイルやSVGとして出力する場合に適している。<br> 複雑な統計グラフ、ヒートマップ、3Dプロット、複数のサブプロットの配置等、高度な可視化要件にも対応できる柔軟性を持っている。<br> また、パフォーマンスにも優れており、大量のデータポイントを効率的に描画できる。<br> <br> その他、バッチ処理でレポートを生成、大量のデータを一度に可視化する場合は、plottersが向いている。<br> <br> Cargo.tomlに以下に示す依存関係を追加する。<br> <syntaxhighlight lang="toml"> [dependencies] plotters = "0.3" </syntaxhighlight> <br> plottersについて詳しく知りたい場合は、[https://github.com/plotters-rs/plotters GitHubリポジトリ] や [https://docs.rs/plotters/latest/plotters/ 公式ドキュメント]を参照すること。<br> また、[https://plotters-rs.github.io/book/ Plotters Developer's Guide]には、実践的なチュートリアルと豊富なサンプルコードが掲載されている。<br> <br> ==== plottersで作成するグラフ : 基本 ==== plottersでグラフを作成する基本的な流れを以下に示す。<br> <br> # まず、描画先のバックエンドを作成する。 # 次に、描画領域(DrawingArea)を定義して、その中にチャートを構築する。 # 最後に、データ系列を描画する。 <br> このフローは、他のプロットライブラリと比べると手続き的であるだが、細かい制御が可能になっている。<br> <br> 以下の例では、sin関数とcos関数のグラフをPNG画像として出力している。<br> <syntaxhighlight lang="rust"> use plotters::prelude::*; fn main() -> Result<(), Box<dyn std::error::Error>> { // PNGファイルへの描画バックエンドを作成 // 画像サイズは800x600ピクセル let root = BitMapBackend::new("output.png", (800, 600)) .into_drawing_area(); // 背景を白色で塗りつぶす root.fill(&WHITE)?; // チャートを構築する // x軸の範囲: -3.14から3.14(-πからπ) // y軸の範囲: -1.5から1.5 let mut chart = ChartBuilder::on(&root) .caption("Sin波とCos波のグラフ", ("sans-serif", 40).into_font()) .margin(10) .x_label_area_size(30) .y_label_area_size(30) .build_cartesian_2d(-3.14f64..3.14f64, -1.5f64..1.5f64)?; // メッシュ (グリッド線) を描画 chart.configure_mesh().draw()?; // Sin波のデータを生成して描画 // -π〜πまでを100分割してプロット chart .draw_series(LineSeries::new( (-314..314).map(|x| { let x = x as f64 / 100.0; (x, x.sin()) }), &RED, ))? .label("y = sin(x)") .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &RED)); // Cos波のデータを生成して描画 chart .draw_series(LineSeries::new( (-314..314).map(|x| { let x = x as f64 / 100.0; (x, x.cos()) }), &BLUE, ))? .label("y = cos(x)") .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &BLUE)); // 凡例を描画 chart .configure_series_labels() .background_style(&WHITE.mix(0.8)) .border_style(&BLACK) .draw()?; // バックエンドに描画内容を書き込む root.present()?; println!("グラフをoutput.pngに保存しました"); Ok(()) } </syntaxhighlight> <br> 注目すべき点は、ChartBuilderを使ってチャートの設定を段階的に構築している部分である。<br> captionでタイトルを設定、marginで余白を指定、ラベル領域のサイズを設定している。<br> <br> このようなビルダーパターンは、Rustで広く仕様されている設計手法である。<br> <br> また、draw_seriesメソッドで系列を描画した後、labelとlegendメソッドをチェーンすることにより、凡例に表示される情報を設定できる。<br> <br> ==== plottersで作成するグラフ : データ分析 ==== 統計分析では、外部ファイルからデータを読み込む場面が頻繁に発生する。<br> <br> CSVファイルを扱うために、CSVクレートを使用する。<br> そのため、Cargo.tomlに以下に示す設定を追加する。<br> <syntaxhighlight lang="toml"> [dependencies] plotters = "0.3" csv = "1.3" serde = { version = "1.0", features = ["derive"] } </syntaxhighlight> <br> 以下の例では、月ごとの売上データを記録したCSVファイルを読み込み、棒グラフとして可視化している。<br> 複数の系列 (今年度と前年度の比較等) を表示することにより、データの傾向を把握しやすくなる。<br> <br> <syntaxhighlight lang="text"> "month","current_year","previous_year" "1","120","100" "2","135","110" "3","150","125" "4","145","130" "5","160","140" "6","175","155" "7","180","160" "8","185","165" "9","190","170" "10","195","175" "11","200","180" "12","210","185" </syntaxhighlight> <br> <syntaxhighlight lang="rust"> use plotters::prelude::*; use serde::Deserialize; use std::error::Error; // CSVの行を表す構造体 #[derive(Debug, Deserialize)] struct SalesData { month: u32, current_year: f64, previous_year: f64, } fn main() -> Result<(), Box<dyn Error>> { // CSVファイルを読み込む let mut reader = csv::Reader::from_path("data.csv")?; let mut data: Vec<SalesData> = Vec::new(); for result in reader.deserialize() { let record: SalesData = result?; data.push(record); } // 描画の準備 let root = BitMapBackend::new("sales_chart.png", (1024, 768)) .into_drawing_area(); root.fill(&WHITE)?; // チャートの構築 let mut chart = ChartBuilder::on(&root) .caption("月別売上比較(今年度 vs 前年度)", ("sans-serif", 50)) .margin(20) .x_label_area_size(40) .y_label_area_size(50) .build_cartesian_2d(0f64..13f64, 0f64..250f64)?; // メッシュの設定と描画 chart .configure_mesh() .x_desc("月") .y_desc("売上 (万円)") .x_labels(12) .y_labels(10) .draw()?; // 今年度のデータを棒グラフとして描画 chart.draw_series( data.iter().map(|d| { let x = d.month as f64; // 棒の幅を0.3に設定し、x座標から0.15左にオフセット Rectangle::new( [(x - 0.15, 0.0), (x + 0.15, d.current_year)], BLUE.filled(), ) }), )? .label("今年度") .legend(|(x, y)| Rectangle::new([(x, y - 5), (x + 15, y + 5)], BLUE.filled())); // 前年度のデータを棒グラフとして描画 // 今年度の棒と重ならないように、少し右側にオフセット chart.draw_series( data.iter().map(|d| { let x = d.month as f64 + 0.3; Rectangle::new( [(x - 0.15, 0.0), (x + 0.15, d.previous_year)], RED.filled(), ) }), )? .label("前年度") .legend(|(x, y)| Rectangle::new([(x, y - 5), (x + 15, y + 5)], RED.filled())); // 凡例を描画 chart .configure_series_labels() .background_style(&WHITE.mix(0.8)) .border_style(&BLACK) .draw()?; root.present()?; println!("売上グラフをsales_chart.pngに保存しました"); Ok(()) } </syntaxhighlight> <br> serdeクレートのDeserializeトレイトを使用して、CSVの各行を構造体に自動的にマッピングしている。<br> これにより、型安全にデータを扱うことができ、コンパイル時にデータ構造の誤りを検出することができる。<br> <br> 棒グラフの描画では、Rectangleを使用して各月の売上を矩形として表現している。<br> 今年度と前年度のデータを並べて表示するために、x座標を少しずつずらして配置している。<br> このように細かい調整が可能なのが、plottersの柔軟性の高さを示している。<br> <br> また、configure_meshメソッドで軸のラベルや目盛りの数を詳細に制御できる。<br> x_descとy_descで軸の説明を追加し、x_labelsとy_labelsで目盛りの数を指定している。<br> <br> これらの設定により、グラフが読みやすくなる。<br> <br> ==== plottersで作成するグラフ : Webアプリケーション ==== plottersのメリットの1つは、バックエンドを切り替えるだけで、異なる出力形式に対応できる点である。<br> <br> SVGはベクター形式のため、拡大しても画質が劣化せず、Webページへの埋め込みにも適している。<br> <br> BitMapBackendをSVGBackendに変更するだけで、その他はほとんど変更せずに済むという点である。<br> <syntaxhighlight lang="rust"> use plotters::prelude::*; fn main() -> Result<(), Box<dyn std::error::Error>> { // SVGファイルへの描画バックエンドを作成 let root = SVGBackend::new("output.svg", (800, 600)) .into_drawing_area(); root.fill(&WHITE)?; let mut chart = ChartBuilder::on(&root) .caption("Sin波とCos波のグラフ(SVG版)", ("sans-serif", 40)) .margin(10) .x_label_area_size(30) .y_label_area_size(30) .build_cartesian_2d(-3.14f64..3.14f64, -1.5f64..1.5f64)?; chart.configure_mesh().draw()?; // Sin波を描画 chart .draw_series(LineSeries::new( (-314..314).map(|x| { let x = x as f64 / 100.0; (x, x.sin()) }), &RED, ))? .label("y = sin(x)") .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &RED)); // Cos波を描画 chart .draw_series(LineSeries::new( (-314..314).map(|x| { let x = x as f64 / 100.0; (x, x.cos()) }), &BLUE, ))? .label("y = cos(x)") .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &BLUE)); chart .configure_series_labels() .background_style(&WHITE.mix(0.8)) .border_style(&BLACK) .draw()?; root.present()?; println!("グラフをoutput.svgに保存しました"); Ok(()) } </syntaxhighlight> <br> 生成されたSVGファイルは、Webブラウザで直接開いて表示できる。<br> また、HTMLファイルに埋め込むことで、Webページ上でインタラクティブな要素として利用することも可能である。<br> <br> さらに、plottersはWebAssemblyにも対応しており、CanvasBackendを使用することにより、Webブラウザ上のHTMLキャンバス要素に直接描画することができる。<br> これにより、完全にRustで記述したWebアプリケーションで、動的なグラフ表示を実現できる。<br> <br><br> == eframe + egui_plotライブラリ == ==== eframe + egui_plotとは ==== eframeは、Rustで書かれたクロスプラットフォーム対応のGUIフレームワークである。<br> eframeは、eguiというイミディエートモードGUIライブラリの上に構築されており、シンプルなAPIを提供している。<br> <br> イミディエートモードGUIとは、従来のリテインドモードGUIとは異なるアプローチである。<br> リテインドモードでは、ウィジェットの状態をGUIフレームワーク側が保持するのに対し、イミディエートモードでは、アプリケーション側が状態を管理し、毎フレームUIを再構築する。<br> この設計により、状態管理がシンプルになり、UIとアプリケーションロジックの結合が緩くなるというメリットがある。<br> <br> egui_plotは、eframe (egui) 上で動作するプロット専用のライブラリである。<br> このライブラリを使用することで、インタラクティブなグラフをGUIアプリケーション内に埋め込むことができる。<br> ユーザはマウスでグラフをズームしたり、パンしたりすることができ、リアルタイムでデータが更新されるようなアプリケーションに適している。<br> <br> eframe + egui_plotの組み合わせは、インタラクティブなデスクトップアプリケーションを作成する場合に特に適している。<br> また、eframeはWebAssemblyへのコンパイルも容易であり、同じコードでデスクトップアプリケーションとWebアプリケーションの両方を作成できる点もメリットである。<br> データを可視化しながらユーザの入力に応じて動的に更新するような対話型のアプリケーションを構築する場合に威力を発揮する。<br> <br> eframe (egui) について詳細を知りたい場合は、[https://docs.rs/egui/latest/egui/ 公式ドキュメント]を参照すること。<br> また、[https://www.egui.rs/ 公式Webサイト]では、Webブラウザ上で動作するデモも提供されており、実際の動作を確認できる。<br> <br> ==== eframe + egui_plotで作成するグラフ : 基本 ==== まず、アプリケーションの状態を保持する構造体を定義する。<br> この構造体は、グラフのデータやUI要素の状態など、アプリケーション全体の情報を保持する役割を持つ。<br> <br> 次に、この構造体に対して <code>eframe::App</code> トレイトを実装する。<br> このトレイトの <code>update</code> メソッドが毎フレーム呼び出されるため、この中でUIとグラフを描画する処理を記述する。<br> <br> 最後に、<code>main</code> メソッドで <code>eframe::run_native</code> を呼び出してアプリケーションを起動する。<br> <br> イミディエートモードの特性により、UIの状態とアプリケーションの状態が明確に分離されるため、デバッグも容易になる。<br> <br> 以下の例では、0から10までの範囲でsin関数のグラフを描画している。<br> <syntaxhighlight lang="rust"> use eframe::egui; use egui_plot::{Line, Plot, PlotPoints}; fn main() -> Result<(), eframe::Error> { // ウィンドウのオプションを設定 // 初期ウィンドウサイズを800x600ピクセルに設定 let options = eframe::NativeOptions { initial_window_size: Some(egui::vec2(800.0, 600.0)), ..Default::default() }; // アプリケーションを起動 // 第1引数 : ウィンドウタイトル // 第2引数 : オプション // 第3引数 : アプリケーション構造体を生成するクロージャ eframe::run_native( "Rust グラフ表示の例", options, Box::new(|_cc| Box::<MyApp>::default()), ) } // アプリケーションの状態を保持する構造体 struct MyApp { // この例では状態を持たないが、通常はここにデータを保持する } impl Default for MyApp { fn default() -> Self { Self {} } } // eframe::Appトレイトを実装 // updateメソッドが毎フレーム呼び出される impl eframe::App for MyApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { // 中央パネルを作成してその中にUIを配置 egui::CentralPanel::default().show(ctx, |ui| { ui.heading("eframe & egui_plot グラフ表示"); // グラフデータの生成 // 0から10までの範囲を100ステップで分割し、sin関数の値を計算 let points: PlotPoints = (0..100) .map(|i| { let x = i as f64 * 0.1; let y = (x * 0.5).sin(); [x, y] }) .collect(); // データから折れ線グラフを作成 let line = Line::new(points); // プロットを表示 // view_aspectで縦横比を設定 (この例では横が縦の2倍) Plot::new("my_plot") .view_aspect(2.0) .show(ui, |plot_ui| { plot_ui.line(line); }); }); } } </syntaxhighlight> <br> updateメソッド内でグラフのデータを生成し、Plotウィジェットを使って表示している。<br> eframeでは、このupdateメソッドが通常は1秒間に60回程度呼び出されるため、動的にデータが変化するような場合でもスムーズにグラフが更新される。<br> <br> このコードを実行するには、プロジェクトディレクトリで以下に示すコマンドを実行する。<br> cargo run <br> また、マウスホイールでズームしたり、ドラッグしてパンできる点も、egui_plotの便利な機能である。<br> <br> ==== eframe + egui_plotで作成するグラフ : インタラクティブ機能 ==== 実用的なアプリケーションでは、ユーザの操作に応じてグラフを更新する機能が求められる。<br> <br> よく使用される機能を以下に示す。<br> * メニューバーを追加して、そこからアプリケーションを終了できるようにする。 * ボタンを配置して、グラフの再描画とクリアを行えるようにする。 * ステータスバーを追加して、現在の時刻を表示する。 <br> eframeでは、パネルという概念を使用すればUIのレイアウトを構築する。<br> <code>TopBottomPanel</code> を使用すれば画面の上部や下部に固定されたパネルを作成でき、<code>CentralPanel</code> を使用すれば残りの領域を埋めるパネルを作成できる。<br> この仕組みにより、柔軟なレイアウトを簡単に実現することができる。<br> <br> 乱数を生成するために、<code>rand</code> クレートを使用する。<br> また、現在時刻を取得するために、<code>chrono</code> クレートを使用する。<br> そのため、Cargo.tomlファイルに以下に示す依存関係を追加すること。<br> <syntaxhighlight lang="toml"> [dependencies] eframe = "0.24" egui_plot = "0.24" rand = "0.8" chrono = "0.4" </syntaxhighlight> <br> <syntaxhighlight lang="rust"> use eframe::egui; use egui_plot::{Line, Plot, PlotPoints}; use rand::Rng; use chrono::Local; fn main() -> Result<(), eframe::Error> { let options = eframe::NativeOptions { initial_window_size: Some(egui::vec2(800.0, 600.0)), ..Default::default() }; eframe::run_native( "グラフを表示するウィンドウ", options, Box::new(|_cc| Box::<GraphApp>::default()), ) } // アプリケーションの状態を保持する構造体 struct GraphApp { // グラフに表示するデータポイントのベクター data: Vec<f64>, // グラフを表示するかどうかを制御するフラグ show_graph: bool, } impl Default for GraphApp { fn default() -> Self { Self { // 初期化時にランダムなデータを生成 data: Self::generate_random_data(), // デフォルトではグラフを表示 show_graph: true, } } } impl GraphApp { // 25個のランダムな浮動小数点数を生成する関数 fn generate_random_data() -> Vec<f64> { let mut rng = rand::thread_rng(); (0..25).map(|_| rng.gen::<f64>()).collect() } // グラフを再描画する関数 // 新しいランダムデータを生成して、グラフを表示状態にする fn redraw_graph(&mut self) { self.data = Self::generate_random_data(); self.show_graph = true; } // グラフをクリアする関数 // 単純に表示フラグをfalseにすることで非表示にする fn clear_graph(&mut self) { self.show_graph = false; } } impl eframe::App for GraphApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { // 画面上部にメニューバーを配置 egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { egui::menu::bar(ui, |ui| { ui.menu_button("ファイル", |ui| { // 終了ボタンがクリックされたらウィンドウを閉じる if ui.button("終了 (Ctrl+Q)").clicked() { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } }); }); }); // 画面下部にステータスバーを配置 // 現在の日時を1秒ごとに更新して表示 egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| { let now = Local::now(); let time_str = now.format("日時 %Y年%m月%d日 %H時%M分%S秒").to_string(); ui.label(time_str); }); // 残りの領域に中央パネルを配置 egui::CentralPanel::default().show(ctx, |ui| { // 水平方向にウィジェットを並べる ui.horizontal(|ui| { ui.heading("Rust eframe & egui_plot グラフ表示"); // ボタンを右寄せで配置 // Layout::right_to_leftを使用することで、右から左へ配置される ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { // グラフを消すボタン if ui.button("グラフを消す").clicked() { self.clear_graph(); } // グラフを打つボタン if ui.button("グラフを打つ").clicked() { self.redraw_graph(); } }); }); // 区切り線を表示 ui.separator(); // グラフの表示 (show_graphがtrueの場合のみ) if self.show_graph { // データからプロット用のポイントを生成 // enumerate()でインデックスを取得し、それをx座標として使用 let points: PlotPoints = self.data .iter() .enumerate() .map(|(i, &y)| [i as f64, y]) .collect(); // 赤色の折れ線グラフを作成 let line = Line::new(points) .color(egui::Color32::from_rgb(255, 0, 0)); // プロットを表示 Plot::new("main_plot") .view_aspect(2.0) .show(ui, |plot_ui| { plot_ui.line(line); }); } }); // 1秒後に再描画をリクエスト // これにより、ステータスバーの時刻が1秒ごとに更新される ctx.request_repaint_after(std::time::Duration::from_secs(1)); } } </syntaxhighlight> <br> 上部にはメニューバーがあり、ファイルメニューから終了を選択することでアプリケーションを閉じることができる。<br> 中央部には、グラフが表示され、右上に配置された2つのボタンで操作できる。<br> 下部にはステータスバーがあり、現在の日時が1秒ごとに更新されて表示される。<br> <br> [グラフを打つ]ボタンを押下すると、新しいランダムデータが生成され、グラフが再描画される。<br> この操作は何度でも繰り返すことができ、毎回異なるパターンのグラフが表示される。<br> <br> [グラフを消す]ボタンを押下すると、グラフが非表示になり、空白の領域だけが残る。<br> <br> 再度、[グラフを打つ]ボタンを押下すれば、新しいグラフが表示される。<br> <br> このように、eframeを使用することで、状態管理がシンプルなまま、インタラクティブなアプリケーションを作成できる。<br> updateメソッド内でUIを記述し、ボタンのクリック等のイベントに応じて状態を更新すれば、自動的に画面が更新される。<br> この直感的なプログラミングモデルが、eframeの大きな利点である。<br> <br> また、Rustの所有権システムにより、メモリ管理が自動的に行われる。<br> Rustでは所有権とライフタイムの仕組みにより、メモリリークやダングリングポインタといった問題を、コンパイル時に防ぐことができる。<br> これは、長時間動作するGUIアプリケーションにとって非常に重要な特性である。<br> <br><br> == その他 == Rustでグラフ描画を行う場合、言語の特性として以下に示す事柄に注意する。<br> <br> Rustは所有権システムとライフタイムという独自の概念を持っており、これによりメモリ安全性とスレッド安全性が保証される。<br> GUIアプリケーションにおいても、この恩恵を受けることができ、実行時エラーやメモリリークのリスクを大幅に減らすことができる。<br> <br> また、Rustはゼロコスト抽象化を提供するため、高レベルな記述でありながら、C/C++に匹敵するパフォーマンスを実現できる。<br> これは、大量のデータをプロットする場合や、リアルタイムでグラフを更新する場合に特に有利である。<br> <br> Rustのエコシステムは活発に発展しており、新しいライブラリやツールが日々登場している。<br> 公式のクレートレジストリである[https://crates.io/ crates.io] や [https://docs.rs/ docs.rs]のドキュメントを定期的に確認することを推奨する。<br> <br><br> __FORCETOC__ [[カテゴリ:Rust]]
Rustの基礎 - グラフ
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse