Rust の Relm/GTK4 で GUI アプリハローワールドメモ

クロスプラットフォーム GUI フレームワークな Relm4 と GTK4 を使って GUI アプリをつくるハローワールドのメモ。GTK4 だと macOS で動作させてもあまり GTK ぽさがないかも。良い。

https://relm4.org

Relm4 makes developing beautiful cross-platform applications idiomatic, simple and fast and enables you to become productive in just a few hours.

GTK4 の devel パッケージ準備

Linux (Debian):

$ sudo apt install libgtk-4-dev build-essential

macOS: – pkg-config 使うので入れること

$ brew install gtk4 pkg-config

Windows: – たぶんこれを(未検証)

Windows – GUI development with Rust and GTK 4

When preparing your Windows machine, you have to decide between either using the MSVC toolchain or the GNU toolchain. If in doubt, go for MSVC since that is the default on Windows. You will want to go for the GNU toolchain if you depend on libraries that can only be compiled with the GNU toolchain.

とりあえず定番の ToDo リストハローワールド

Cargo.toml

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[package]
name = "relm-test"
version = "0.1.0"
edition = "2024"
[dependencies]
relm4 = "0.9.1"
relm4-components = "0.9.1"
[package] name = "relm-test" version = "0.1.0" edition = "2024" [dependencies] relm4 = "0.9.1" relm4-components = "0.9.1"
[package]
name = "relm-test"
version = "0.1.0"
edition = "2024"

[dependencies]
relm4 = "0.9.1"
relm4-components = "0.9.1"

main.rs

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
use gtk::glib::clone;
use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, WidgetExt, EntryExt, EditableExt};
use relm4::{gtk, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent};
struct AppModel {
tasks: Vec<String>,
}
#[derive(Debug)]
enum AppMsg {
AddTask(String),
RemoveTask(usize),
}
struct AppWidgets {
task_list: gtk::ListBox,
}
impl SimpleComponent for AppModel {
type Input = AppMsg;
type Output = ();
type Init = ();
type Root = gtk::Window;
type Widgets = AppWidgets;
fn init_root() -> Self::Root {
gtk::Window::builder()
.title("ToDo List")
.default_width(400)
.default_height(300)
.resizable(false)
.build()
}
fn init(
_init: Self::Init,
window: Self::Root,
sender: ComponentSender<Self>,
) -> relm4::ComponentParts<Self> {
let model = AppModel { tasks: Vec::new() };
let vbox = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(5)
.build();
let task_entry = gtk::Entry::builder()
.placeholder_text("Enter a new task")
.build();
let add_button = gtk::Button::with_label("Add Task");
let task_list = gtk::ListBox::new();
let scrolled_window = gtk::ScrolledWindow::builder()
.vexpand(true)
.child(&task_list)
.build();
window.set_child(Some(&vbox));
vbox.set_margin_all(5);
vbox.append(&task_entry);
vbox.append(&add_button);
vbox.append(&scrolled_window);
let add_task = {
let sender = sender.clone();
let task_entry = task_entry.clone();
move || {
if let Some(text) = task_entry.text().as_str().to_owned().into() {
if !text.is_empty() {
sender.input(AppMsg::AddTask(text));
task_entry.set_text("");
}
}
}
};
task_entry.connect_activate({
let add_task = add_task.clone();
move |_| {
add_task();
}
});
add_button.connect_clicked({
let add_task = add_task.clone();
move |_| {
add_task();
}
});
let widgets = AppWidgets { task_list };
ComponentParts { model, widgets }
}
fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
match message {
AppMsg::AddTask(task) => {
self.tasks.push(task);
}
AppMsg::RemoveTask(index) => {
if index < self.tasks.len() {
self.tasks.remove(index);
}
}
}
}
fn update_view(&self, widgets: &mut Self::Widgets, sender: ComponentSender<Self>) {
while let Some(child) = widgets.task_list.first_child() {
widgets.task_list.remove(&child);
}
for (index, task) in self.tasks.iter().enumerate() {
let row = gtk::Box::new(gtk::Orientation::Horizontal, 5);
let label = gtk::Label::new(Some(task));
label.set_halign(gtk::Align::Start);
label.set_wrap(true);
label.set_wrap_mode(gtk::pango::WrapMode::Char);
label.set_xalign(0.0);
label.set_size_request(300, -1);
let remove_button = gtk::Button::with_label("Remove");
remove_button.set_halign(gtk::Align::End);
remove_button.connect_clicked(clone!(
#[strong]
sender,
move |_| {
sender.input(AppMsg::RemoveTask(index));
}
));
row.append(&label);
row.append(&remove_button);
widgets.task_list.append(&row);
}
widgets.task_list.show();
}
}
fn main() {
let app = RelmApp::new("relm4.test.todo_list");
app.run::<AppModel>(());
}
use gtk::glib::clone; use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, WidgetExt, EntryExt, EditableExt}; use relm4::{gtk, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent}; struct AppModel { tasks: Vec<String>, } #[derive(Debug)] enum AppMsg { AddTask(String), RemoveTask(usize), } struct AppWidgets { task_list: gtk::ListBox, } impl SimpleComponent for AppModel { type Input = AppMsg; type Output = (); type Init = (); type Root = gtk::Window; type Widgets = AppWidgets; fn init_root() -> Self::Root { gtk::Window::builder() .title("ToDo List") .default_width(400) .default_height(300) .resizable(false) .build() } fn init( _init: Self::Init, window: Self::Root, sender: ComponentSender<Self>, ) -> relm4::ComponentParts<Self> { let model = AppModel { tasks: Vec::new() }; let vbox = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .spacing(5) .build(); let task_entry = gtk::Entry::builder() .placeholder_text("Enter a new task") .build(); let add_button = gtk::Button::with_label("Add Task"); let task_list = gtk::ListBox::new(); let scrolled_window = gtk::ScrolledWindow::builder() .vexpand(true) .child(&task_list) .build(); window.set_child(Some(&vbox)); vbox.set_margin_all(5); vbox.append(&task_entry); vbox.append(&add_button); vbox.append(&scrolled_window); let add_task = { let sender = sender.clone(); let task_entry = task_entry.clone(); move || { if let Some(text) = task_entry.text().as_str().to_owned().into() { if !text.is_empty() { sender.input(AppMsg::AddTask(text)); task_entry.set_text(""); } } } }; task_entry.connect_activate({ let add_task = add_task.clone(); move |_| { add_task(); } }); add_button.connect_clicked({ let add_task = add_task.clone(); move |_| { add_task(); } }); let widgets = AppWidgets { task_list }; ComponentParts { model, widgets } } fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) { match message { AppMsg::AddTask(task) => { self.tasks.push(task); } AppMsg::RemoveTask(index) => { if index < self.tasks.len() { self.tasks.remove(index); } } } } fn update_view(&self, widgets: &mut Self::Widgets, sender: ComponentSender<Self>) { while let Some(child) = widgets.task_list.first_child() { widgets.task_list.remove(&child); } for (index, task) in self.tasks.iter().enumerate() { let row = gtk::Box::new(gtk::Orientation::Horizontal, 5); let label = gtk::Label::new(Some(task)); label.set_halign(gtk::Align::Start); label.set_wrap(true); label.set_wrap_mode(gtk::pango::WrapMode::Char); label.set_xalign(0.0); label.set_size_request(300, -1); let remove_button = gtk::Button::with_label("Remove"); remove_button.set_halign(gtk::Align::End); remove_button.connect_clicked(clone!( #[strong] sender, move |_| { sender.input(AppMsg::RemoveTask(index)); } )); row.append(&label); row.append(&remove_button); widgets.task_list.append(&row); } widgets.task_list.show(); } } fn main() { let app = RelmApp::new("relm4.test.todo_list"); app.run::<AppModel>(()); }
use gtk::glib::clone;
use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, WidgetExt, EntryExt, EditableExt};
use relm4::{gtk, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent};

struct AppModel {
    tasks: Vec<String>,
}

#[derive(Debug)]
enum AppMsg {
    AddTask(String),
    RemoveTask(usize),
}

struct AppWidgets {
    task_list: gtk::ListBox,
}

impl SimpleComponent for AppModel {
    type Input = AppMsg;
    type Output = ();
    type Init = ();
    type Root = gtk::Window;
    type Widgets = AppWidgets;

    fn init_root() -> Self::Root {
        gtk::Window::builder()
            .title("ToDo List")
            .default_width(400)
            .default_height(300)
            .resizable(false)
            .build()
    }

    fn init(
        _init: Self::Init,
        window: Self::Root,
        sender: ComponentSender<Self>,
    ) -> relm4::ComponentParts<Self> {
        let model = AppModel { tasks: Vec::new() };

        let vbox = gtk::Box::builder()
            .orientation(gtk::Orientation::Vertical)
            .spacing(5)
            .build();

        let task_entry = gtk::Entry::builder()
            .placeholder_text("Enter a new task")
            .build();

        let add_button = gtk::Button::with_label("Add Task");

        let task_list = gtk::ListBox::new();

        let scrolled_window = gtk::ScrolledWindow::builder()
            .vexpand(true)
            .child(&task_list)
            .build();

        window.set_child(Some(&vbox));
        vbox.set_margin_all(5);
        vbox.append(&task_entry);
        vbox.append(&add_button);
        vbox.append(&scrolled_window);

        let add_task = {
            let sender = sender.clone();
            let task_entry = task_entry.clone();
            move || {
                if let Some(text) = task_entry.text().as_str().to_owned().into() {
                    if !text.is_empty() {
                        sender.input(AppMsg::AddTask(text));
                        task_entry.set_text("");
                    }
                }
            }
        };

        task_entry.connect_activate({
            let add_task = add_task.clone();
            move |_| {
                add_task();
            }
        });

        add_button.connect_clicked({
            let add_task = add_task.clone();
            move |_| {
                add_task();
            }
        });

        let widgets = AppWidgets { task_list };

        ComponentParts { model, widgets }
    }

    fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
        match message {
            AppMsg::AddTask(task) => {
                self.tasks.push(task);
            }
            AppMsg::RemoveTask(index) => {
                if index < self.tasks.len() {
                    self.tasks.remove(index);
                }
            }
        }
    }

    fn update_view(&self, widgets: &mut Self::Widgets, sender: ComponentSender<Self>) {
        while let Some(child) = widgets.task_list.first_child() {
            widgets.task_list.remove(&child);
        }
        for (index, task) in self.tasks.iter().enumerate() {
            let row = gtk::Box::new(gtk::Orientation::Horizontal, 5);

            let label = gtk::Label::new(Some(task));
            label.set_halign(gtk::Align::Start);
            label.set_wrap(true);
            label.set_wrap_mode(gtk::pango::WrapMode::Char);
            label.set_xalign(0.0);
            label.set_size_request(300, -1);

            let remove_button = gtk::Button::with_label("Remove");
            remove_button.set_halign(gtk::Align::End);

            remove_button.connect_clicked(clone!(
                #[strong]
                sender,
                move |_| {
                    sender.input(AppMsg::RemoveTask(index));
                }
            ));

            row.append(&label);
            row.append(&remove_button);
            widgets.task_list.append(&row);
        }
        widgets.task_list.show();
    }
}

fn main() {
    let app = RelmApp::new("relm4.test.todo_list");
    app.run::<AppModel>(());
}

ToDo:

  • WIndows でも試すこと。もう少しです。こんにちは。
  • Ubuntu で動作させるとテキストボックスに日本語が入らない確認。

ダークモードメモ。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
fn main() {
// Initialize GTK
gtk::init().expect("Failed to initialize GTK");
// Enable dark mode
if let Some(settings) = gtk::Settings::default() {
settings.set_gtk_application_prefer_dark_theme(true);
}
let app = RelmApp::new("relm4.test.todo_list");
app.run::<AppModel>(());
}
fn main() { // Initialize GTK gtk::init().expect("Failed to initialize GTK"); // Enable dark mode if let Some(settings) = gtk::Settings::default() { settings.set_gtk_application_prefer_dark_theme(true); } let app = RelmApp::new("relm4.test.todo_list"); app.run::<AppModel>(()); }
fn main() {
    // Initialize GTK
    gtk::init().expect("Failed to initialize GTK");

    // Enable dark mode
    if let Some(settings) = gtk::Settings::default() {
        settings.set_gtk_application_prefer_dark_theme(true);
    }

    let app = RelmApp::new("relm4.test.todo_list");
    app.run::<AppModel>(());
}

以上

Rust の Servo html5ever ライブラリで HTML をパース

Rust の html5ever ライブラリで HTML をパースする。Servo で使われているものと思われる。メモ。

https://github.com/servo/html5ever

High-performance browser-grade HTML5 parser

Cargo.toml

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[package]
name = "htmlpaser-test"
version = "0.1.0"
edition = "2024"
[dependencies]
html5ever = "0.27.0"
markup5ever_rcdom = "0.3.0"
[package] name = "htmlpaser-test" version = "0.1.0" edition = "2024" [dependencies] html5ever = "0.27.0" markup5ever_rcdom = "0.3.0"
[package]
name = "htmlpaser-test"
version = "0.1.0"
edition = "2024"

[dependencies]
html5ever = "0.27.0"
markup5ever_rcdom = "0.3.0"

src/main.rs

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
use html5ever::driver::parse_document;
use html5ever::tendril::TendrilSink;
use markup5ever_rcdom::{RcDom, Handle};
use std::default::Default;
// HTML ドキュメントをパースする関数
fn parse_html(html: &str) -> RcDom {
parse_document(RcDom::default(), Default::default()).one(html)
}
// DOM を再帰的に表示する関数
fn print_dom(handle: &Handle, depth: usize) {
let indent = " ".repeat(depth);
let node = handle;
match node.data {
markup5ever_rcdom::NodeData::Document => {
println!("{}Document", indent);
}
markup5ever_rcdom::NodeData::Element { ref name, ref attrs, .. } => {
println!("{}Element: {}", indent, name.local);
for attr in attrs.borrow().iter() {
println!("{} Attribute: {}=\"{}\"", indent, attr.name.local, attr.value);
}
}
markup5ever_rcdom::NodeData::Text { ref contents } => {
println!("{}Text: {}", indent, contents.borrow());
}
_ => {}
}
for child in node.children.borrow().iter() {
print_dom(child, depth + 1);
}
}
fn main() {
let html = "<!DOCTYPE html><html><head><title>Test</title></head><body><h1 th:loop='test'>Hello, world!</h1></body></html>";
let dom = parse_html(html);
// DOM を再帰的に表示
print_dom(&dom.document, 0);
}
use html5ever::driver::parse_document; use html5ever::tendril::TendrilSink; use markup5ever_rcdom::{RcDom, Handle}; use std::default::Default; // HTML ドキュメントをパースする関数 fn parse_html(html: &str) -> RcDom { parse_document(RcDom::default(), Default::default()).one(html) } // DOM を再帰的に表示する関数 fn print_dom(handle: &Handle, depth: usize) { let indent = " ".repeat(depth); let node = handle; match node.data { markup5ever_rcdom::NodeData::Document => { println!("{}Document", indent); } markup5ever_rcdom::NodeData::Element { ref name, ref attrs, .. } => { println!("{}Element: {}", indent, name.local); for attr in attrs.borrow().iter() { println!("{} Attribute: {}=\"{}\"", indent, attr.name.local, attr.value); } } markup5ever_rcdom::NodeData::Text { ref contents } => { println!("{}Text: {}", indent, contents.borrow()); } _ => {} } for child in node.children.borrow().iter() { print_dom(child, depth + 1); } } fn main() { let html = "<!DOCTYPE html><html><head><title>Test</title></head><body><h1 th:loop='test'>Hello, world!</h1></body></html>"; let dom = parse_html(html); // DOM を再帰的に表示 print_dom(&dom.document, 0); }
use html5ever::driver::parse_document;
use html5ever::tendril::TendrilSink;
use markup5ever_rcdom::{RcDom, Handle};
use std::default::Default;

// HTML ドキュメントをパースする関数
fn parse_html(html: &str) -> RcDom {
    parse_document(RcDom::default(), Default::default()).one(html)
}

// DOM を再帰的に表示する関数
fn print_dom(handle: &Handle, depth: usize) {
    let indent = "  ".repeat(depth);
    let node = handle;

    match node.data {
        markup5ever_rcdom::NodeData::Document => {
            println!("{}Document", indent);
        }
        markup5ever_rcdom::NodeData::Element { ref name, ref attrs, .. } => {
            println!("{}Element: {}", indent, name.local);
            for attr in attrs.borrow().iter() {
                println!("{}  Attribute: {}=\"{}\"", indent, attr.name.local, attr.value);
            }
        }
        markup5ever_rcdom::NodeData::Text { ref contents } => {
            println!("{}Text: {}", indent, contents.borrow());
        }
        _ => {}
    }

    for child in node.children.borrow().iter() {
        print_dom(child, depth + 1);
    }
}

fn main() {
    let html = "<!DOCTYPE html><html><head><title>Test</title></head><body><h1 th:loop='test'>Hello, world!</h1></body></html>";
    let dom = parse_html(html);

    // DOM を再帰的に表示
    print_dom(&dom.document, 0);
}

結果:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ cargo run
Compiling htmlpaser-test v0.1.0 (/Users/hk2a/devel/rust/htmlpaser-test)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.98s
Running `target/debug/htmlpaser-test`
Document
Element: html
Element: head
Element: title
Text: Test
Element: body
Element: h1
Attribute: th:loop="test"
Text: Hello, world!
$ cargo run Compiling htmlpaser-test v0.1.0 (/Users/hk2a/devel/rust/htmlpaser-test) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.98s Running `target/debug/htmlpaser-test` Document Element: html Element: head Element: title Text: Test Element: body Element: h1 Attribute: th:loop="test" Text: Hello, world!
$ cargo run
   Compiling htmlpaser-test v0.1.0 (/Users/hk2a/devel/rust/htmlpaser-test)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.98s
     Running `target/debug/htmlpaser-test`
Document
  Element: html
    Element: head
      Element: title
        Text: Test
    Element: body
      Element: h1
        Attribute: th:loop="test"
        Text: Hello, world!

Jupyter Notebook の Rust カーネルでグラフや表形式を出力

Jupyter Notebook の Rust カーネルでグラフや表形式を出力するのに若干手間取ったのでメモです。VS Code 上で実行しています。Rust のセルで rust-analyzer が使えないのが若干不便。

導入

Jupyter の Rust カーネルを導入。

$ cargo install evcxr_jupyter
$ evcxr_jupyter --install
Writing /home/hiromasa/.local/share/jupyter/kernels/rust/kernel.json
Writing /home/hiromasa/.local/share/jupyter/kernels/rust/logo-32x32.png
Writing /home/hiromasa/.local/share/jupyter/kernels/rust/logo-64x64.png
Writing /home/hiromasa/.local/share/jupyter/kernels/rust/logo-LICENSE.md
Writing /home/hiromasa/.local/share/jupyter/kernels/rust/kernel.js
Writing /home/hiromasa/.local/share/jupyter/kernels/rust/lint.js
Writing /home/hiromasa/.local/share/jupyter/kernels/rust/lint.css
Writing /home/hiromasa/.local/share/jupyter/kernels/rust/lint-LICENSE
Writing /home/hiromasa/.local/share/jupyter/kernels/rust/version.txt
Installation complete

依存関係の導入

Jupyter Notebook の Rust セルで以下のマジックコマンドを実行して描画系の依存ライブラリを導入。

:dep plotters = { version = "0.3.1", default_features = false, features = ["evcxr", "line_series", "point_series"] }
:dep statrs = "0.15.0"
:dep prettytable = { git = "https://github.com/phsym/prettytable-rs", package = "prettytable-rs", features = ["evcxr"] }

グラフを描画する例

plotters を使ってグラフを描画する例。evcxr_figure を使って Jupyter に返却すると画像がでる。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
use statrs::distribution::{Binomial, Discrete};
let n = 100u64;
let p = 0.01;
let bin = Binomial::new(p, n).unwrap();
let data: Vec<(f32, f32)> = (0..=n).map(|x| (x as f32, bin.pmf(x) as f32)).collect();
let max_y = data.iter().map(|(_, y)| *y).fold(0f32, f32::max) + 0.01f32;
let figure = evcxr_figure((800, 400), |root| {
root.fill(&WHITE)?;
let mut chart = ChartBuilder::on(&root)
.caption("Binomial Distribution (n=100, p=0.01)", ("Arial", 20).into_font())
.margin(5)
.x_label_area_size(30)
.y_label_area_size(40)
.build_cartesian_2d(0f32..10f32, 0f32..max_y)?;
chart.configure_mesh()
.x_desc("当選回数")
.y_desc("確率")
.x_label_formatter(&|v| format!("{:.0}", v))
.draw()?;
// 折れ線グラフ + 点の形式へ変更
chart.draw_series(LineSeries::new(
data.iter().map(|(x, y)| (*x, *y)),
&RED,
))?;
chart.draw_series(data.iter().map(|(x, y)| {
Circle::new((*x, *y), 4, RED.filled())
}))?;
Ok(())
});
figure
use statrs::distribution::{Binomial, Discrete}; let n = 100u64; let p = 0.01; let bin = Binomial::new(p, n).unwrap(); let data: Vec<(f32, f32)> = (0..=n).map(|x| (x as f32, bin.pmf(x) as f32)).collect(); let max_y = data.iter().map(|(_, y)| *y).fold(0f32, f32::max) + 0.01f32; let figure = evcxr_figure((800, 400), |root| { root.fill(&WHITE)?; let mut chart = ChartBuilder::on(&root) .caption("Binomial Distribution (n=100, p=0.01)", ("Arial", 20).into_font()) .margin(5) .x_label_area_size(30) .y_label_area_size(40) .build_cartesian_2d(0f32..10f32, 0f32..max_y)?; chart.configure_mesh() .x_desc("当選回数") .y_desc("確率") .x_label_formatter(&|v| format!("{:.0}", v)) .draw()?; // 折れ線グラフ + 点の形式へ変更 chart.draw_series(LineSeries::new( data.iter().map(|(x, y)| (*x, *y)), &RED, ))?; chart.draw_series(data.iter().map(|(x, y)| { Circle::new((*x, *y), 4, RED.filled()) }))?; Ok(()) }); figure
use statrs::distribution::{Binomial, Discrete};

let n = 100u64;
let p = 0.01;
let bin = Binomial::new(p, n).unwrap();
let data: Vec<(f32, f32)> = (0..=n).map(|x| (x as f32, bin.pmf(x) as f32)).collect();
let max_y = data.iter().map(|(_, y)| *y).fold(0f32, f32::max) + 0.01f32;
let figure = evcxr_figure((800, 400), |root| {
    root.fill(&WHITE)?;
    let mut chart = ChartBuilder::on(&root)
        .caption("Binomial Distribution (n=100, p=0.01)", ("Arial", 20).into_font())
        .margin(5)
        .x_label_area_size(30)
        .y_label_area_size(40)
        .build_cartesian_2d(0f32..10f32, 0f32..max_y)?;
    chart.configure_mesh()
        .x_desc("当選回数")
        .y_desc("確率")
        .x_label_formatter(&|v| format!("{:.0}", v))
        .draw()?;
    // 折れ線グラフ + 点の形式へ変更
    chart.draw_series(LineSeries::new(
        data.iter().map(|(x, y)| (*x, *y)),
        &RED,
    ))?;
    chart.draw_series(data.iter().map(|(x, y)| {
        Circle::new((*x, *y), 4, RED.filled())
    }))?;
    Ok(())
});
figure

表敬式を出力する例

prettytable の Jupyter サポートを使って出力。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
use prettytable::{Table, Row, Cell};
use prettytable::{format, Attr, color};
use prettytable::evcxr::EvcxrDisplay;
let mut table = Table::new();
table.add_row(Row::new(vec![
Cell::new("Name").with_style(Attr::Bold),
Cell::new("Age").with_style(Attr::ForegroundColor(color::GREEN)),
]));
table.add_row(Row::new(vec![
Cell::new("Alice"),
Cell::new("30"),
]));
table.add_row(Row::new(vec![
Cell::new("Bob"),
Cell::new("25"),
]));
table
use prettytable::{Table, Row, Cell}; use prettytable::{format, Attr, color}; use prettytable::evcxr::EvcxrDisplay; let mut table = Table::new(); table.add_row(Row::new(vec![ Cell::new("Name").with_style(Attr::Bold), Cell::new("Age").with_style(Attr::ForegroundColor(color::GREEN)), ])); table.add_row(Row::new(vec![ Cell::new("Alice"), Cell::new("30"), ])); table.add_row(Row::new(vec![ Cell::new("Bob"), Cell::new("25"), ])); table
use prettytable::{Table, Row, Cell};
use prettytable::{format, Attr, color};
use prettytable::evcxr::EvcxrDisplay;

let mut table = Table::new();
table.add_row(Row::new(vec![
    Cell::new("Name").with_style(Attr::Bold),
    Cell::new("Age").with_style(Attr::ForegroundColor(color::GREEN)),
]));
table.add_row(Row::new(vec![
    Cell::new("Alice"),
    Cell::new("30"),
]));
table.add_row(Row::new(vec![
    Cell::new("Bob"),
    Cell::new("25"),
]));
table

表題と関係ないですが Python の例メモ

依存関係導入。

%pip install matplotlib
%pip install pandas
%pip install scipy

グラフと表敬式出力サンプル:

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import binom
import pandas as pd
 
# パラメータ設定
n = 100  # 試行回数
p = 0.01  # 成功確率
 
# 二項分布の確率質量関数
x = np.arange(0, n + 1)
pmf = binom.pmf(x, n, p)
 
# グラフ描画
plt.bar(x[:21], pmf[:21], color='blue', alpha=0.7)
plt.title('Binomial Distribution (n=100, p=0.01)')
plt.xlabel('Number of Successes')
plt.ylabel('Probability')
plt.grid(True)
plt.show()
 
# テーブル形式で出力 (Probability が 0% でないもののみ)
df = pd.DataFrame({
    'Number of Successes': x,
    'Probability (%)': (pmf * 100).round(2)
})
df_nonzero = df[df['Probability (%)'] > 0]
print(df_nonzero)