天天看點

Rust語言從入門到精通系列 - 支援WASM的高性能繪圖庫Plotters

作者:TinyZzh
Rust語言從入門到精通系列 - 支援WASM的高性能繪圖庫Plotters

Plotters是一個基于Rust語言的繪圖庫,由Eonil和其他貢獻者開發。它的目标是提供一個簡單易用的API,可以輕松地建立各種類型的圖表,并支援多種輸出格式。Plotters使用Rust語言的強類型系統和高性能,可以生成高品質的圖表。

Plotters提供了多種類型的圖表,包括線圖、柱狀圖、散點圖等等。它還支援多種輸出格式,包括PNG、SVG、PDF等等。使用Plotters可以輕松地建立高品質的圖表,同時還可以自定義圖表的樣式和布局。

用法實戰

安裝Plotters

在使用Plotters之前,需要安裝Plotters。可以通過以下指令安裝Plotters:

cargo install plotters           

繪制折線圖

下面是一個簡單的例子,示範如何使用Plotters繪制折線圖:

use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("plot.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption("折線圖", ("sans-serif", 30))
        .set_label_area_size(LabelAreaPosition::Left, 40)
        .set_label_area_size(LabelAreaPosition::Bottom, 40)
        .build_cartesian_2d(0..10, 0..10)?;

    chart.configure_mesh().draw()?;

    chart.draw_series(LineSeries::new(
        vec![(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9)]
            .iter()
            .map(|x| (*x, *x)),
        &BLUE,
    ))?;

    Ok(())
}           

這個例子中,我們首先建立了一個BitMapBackend對象,用于輸出PNG格式的圖檔。然後,我們建立了一個ChartBuilder對象,用于建構圖表。在ChartBuilder對象上,我們設定了标題、坐标軸标簽和網格線。最後,我們使用LineSeries對象繪制了一條折線。LineSeries對象需要一個元組清單作為輸入,每個元組包含一個x坐标和一個y坐标。在這個例子中,我們繪制了一條從(0,0)到(9,9)的折線。

繪制柱狀圖

下面是一個簡單的例子,示範如何使用Plotters繪制柱狀圖:

use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("plot.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption("柱狀圖", ("sans-serif", 30))
        .set_label_area_size(LabelAreaPosition::Left, 40)
        .set_label_area_size(LabelAreaPosition::Bottom, 40)
        .build_cartesian_2d(0..10, 0..10)?;

    chart.configure_mesh().draw()?;

    chart.draw_series(
        Histogram::vertical(&chart)
            .margin(5)
            .style(GREEN.filled())
            .data(vec![2, 3, 4, 5, 6, 7, 8, 9]),
    )?;

    Ok(())
}           

這個例子中,我們首先建立了一個BitMapBackend對象,用于輸出PNG格式的圖檔。然後,我們建立了一個ChartBuilder對象,用于建構圖表。在ChartBuilder對象上,我們設定了标題、坐标軸标簽和網格線。最後,我們使用Histogram對象繪制了一組柱狀。Histogram對象需要一個整數向量作為輸入,每個整數表示一個柱狀的高度。在這個例子中,我們繪制了一組高度為2到9的柱狀。

繪制散點圖

下面是一個簡單的例子,示範如何使用Plotters繪制散點圖:

use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("plot.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption("散點圖", ("sans-serif", 30))
        .set_label_area_size(LabelAreaPosition::Left, 40)
        .set_label_area_size(LabelAreaPosition::Bottom, 40)
        .build_cartesian_2d(0..10, 0..10)?;

    chart.configure_mesh().draw()?;

    chart.draw_series(
        PointSeries::of_element(
            [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9)],
            5,
            &BLUE,
            &|c, s, st| {
                return EmptyElement::at(c) + Circle::new((0, 0), s, st.filled());
            },
        ),
    )?;

    Ok(())
}           

這個例子中,我們首先建立了一個BitMapBackend對象,用于輸出PNG格式的圖檔。然後,我們建立了一個ChartBuilder對象,用于建構圖表。在ChartBuilder對象上,我們設定了标題、坐标軸标簽和網格線。最後,我們使用PointSeries對象繪制了一組散點。PointSeries對象需要一個元組清單作為輸入,每個元組包含一個x坐标和一個y坐标。在這個例子中,我們繪制了一組從(0,0)到(9,9)的散點。

繪制餅圖

下面是一個簡單的例子,示範如何使用Plotters繪制餅圖:

use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("plot.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let data = vec![("A", 10), ("B", 20), ("C", 30), ("D", 40)];

    let sum = data.iter().map(|(_, v)| v).sum::<i32>() as f64;

    let mut chart = ChartBuilder::on(&root)
        .caption("餅圖", ("sans-serif", 30))
        .build_ranged(0.0..1.0, 0.0..1.0)?;

    chart.draw_series(
        data.iter()
            .map(|(label, value)| {
                let value = *value as f64 / sum;
                Sector::new(
                    (0.5, 0.5),
                    value,
                    (0.0, 0.0),
                    (HSLColor(0.0, 1.0, 0.5), BLACK),
                )
                .label(label)
                .legend(move |(x, y)| {
                    Rectangle::new([(x - 5, y - 5), (x + 5, y + 5)], HSLColor(0.0, 1.0, 0.5).filled())
                })
            })
            .collect::<Vec<_>>(),
    )?;

    Ok(())
}           

這個例子中,我們首先建立了一個BitMapBackend對象,用于輸出PNG格式的圖檔。然後,我們建立了一個ChartBuilder對象,用于建構圖表。在ChartBuilder對象上,我們設定了标題。最後,我們使用Sector對象繪制了一組扇形。Sector對象需要一個位置、角度、半徑、顔色和标簽等參數。在這個例子中,我們繪制了一組占比為10%、20%、30%和40%的扇形。

繪制等高線圖

下面是一個簡單的例子,示範如何使用Plotters繪制等高線圖:

use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("plot.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption("等高線圖", ("sans-serif", 30))
        .set_label_area_size(LabelAreaPosition::Left, 40)
        .set_label_area_size(LabelAreaPosition::Bottom, 40)
        .build_cartesian_2d(0..10, 0..10)?;

    chart.configure_mesh().draw()?;

    let data = (0..=100).map(|i| {
        let x = i as f64 / 10.0;
        let y = i as f64 / 10.0;
        (x, y, (x * x + y * y).sin())
    });

    chart.draw_series(
        ContourSeries::new(
            data,
            (0..=10).map(|i| i as f64),
            (0..=10).map(|i| i as f64),
            &Palette::viridis(),
        )
        .levels(20),
    )?;

    Ok(())
}           

這個例子中,我們首先建立了一個BitMapBackend對象,用于輸出PNG格式的圖檔。然後,我們建立了一個ChartBuilder對象,用于建構圖表。在ChartBuilder對象上,我們設定了标題、坐标軸标簽和網格線。最後,我們使用ContourSeries對象繪制了一組等高線。ContourSeries對象需要一個元組清單作為輸入,每個元組包含一個x坐标、一個y坐标和一個高度。在這個例子中,我們繪制了一個以(0,0)為中心的高斯分布等高線。

繪制熱力圖

下面是一個簡單的例子,示範如何使用Plotters繪制熱力圖:

use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("plot.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption("熱力圖", ("sans-serif", 30))
        .set_label_area_size(LabelAreaPosition::Left, 40)
        .set_label_area_size(LabelAreaPosition::Bottom, 40)
        .build_cartesian_2d(0..10, 0..10)?;

    chart.configure_mesh().draw()?;

    let data = (0..=100).map(|i| {
        let x = i as f64 / 10.0;
        let y = i as f64 / 10.0;
        (x, y, (x * x + y * y).sin())
    });

    chart.draw_series(
        Heatmap::new(data, &Palette::viridis())
            .x_label("X")
            .y_label("Y"),
    )?;

    Ok(())
}           

這個例子中,我們首先建立了一個BitMapBackend對象,用于輸出PNG格式的圖檔。然後,我們建立了一個ChartBuilder對象,用于建構圖表。在ChartBuilder對象上,我們設定了标題、坐标軸标簽和網格線。最後,我們使用Heatmap對象繪制了一個熱力圖。Heatmap對象需要一個元組清單作為輸入,每個元組包含一個x坐标、一個y坐标和一個高度。在這個例子中,我們繪制了一個以(0,0)為中心的高斯分布熱力圖。

繪制箱線圖

下面是一個簡單的例子,示範如何使用Plotters繪制箱線圖:

use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("plot.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption("箱線圖", ("sans-serif", 30))
        .set_label_area_size(LabelAreaPosition::Left, 40)
        .set_label_area_size(LabelAreaPosition::Bottom, 40)
        .build_cartesian_2d(0..10, 0..10)?;

    chart.configure_mesh().draw()?;

    let data = vec![
        ("A", vec![2, 3, 4, 5, 6, 7, 8, 9]),
        ("B", vec![3, 4, 5, 6, 7, 8, 9, 10]),
        ("C", vec![4, 5, 6, 7, 8, 9, 10, 11]),
        ("D", vec![5, 6, 7, 8, 9, 10, 11, 12]),
    ];

    chart.draw_series(
        data.iter()
            .enumerate()
            .map(|(i, (label, values))| {
                BoxPlot::new_horizontal(
                    values.iter().map(|&v| (v - 2) as f64),
                    i as f64,
                    &BLACK,
                )
                .label(label)
            })
            .collect::<Vec<_>>(),
    )?;

    Ok(())
}           

這個例子中,我們首先建立了一個BitMapBackend對象,用于輸出PNG格式的圖檔。然後,我們建立了一個ChartBuilder對象,用于建構圖表。在ChartBuilder對象上,我們設定了标題、坐标軸标簽和網格線。最後,我們使用BoxPlot對象繪制了一組箱線。BoxPlot對象需要一個浮點數向量作為輸入,每個浮點數表示一個箱線的高度。在這個例子中,我們繪制了一組包含四個箱線的箱線圖。

繪制極坐标圖

下面是一個簡單的例子,示範如何使用Plotters繪制極坐标圖:

use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("plot.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption("極坐标圖", ("sans-serif", 30))
        .build_cartesian_2d(-1.2..1.2, -1.2..1.2)?;

    chart.configure_mesh().draw()?;

    chart.draw_series(LineSeries::new(
        (-100..=100)
            .map(|i| {
                let t = i as f64 / 100.0 * std::f64::consts::PI * 2.0;
                (t.cos(), t.sin())
            })
            .take(101),
        &RED,
    ))?;

    Ok(())
}           

這個例子中,我們首先建立了一個BitMapBackend對象,用于輸出PNG格式的圖檔。然後,我們建立了一個ChartBuilder對象,用于建構圖表。在ChartBuilder對象上,我們設定了标題。最後,我們使用LineSeries對象繪制了一條極坐标線。LineSeries對象需要一個元組清單作為輸入,每個元組包含一個角度和一個半徑。在這個例子中,我們繪制了一條從0度到360度的極坐标線。

自定義樣式

use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("plot.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption("Custom Style Chart", ("sans-serif", 20))
        .x_label_area_size(30)
        .y_label_area_size(30)
        .build_cartesian_2d(0f32..10f32, 0f32..10f32)?;

    chart.configure_mesh().draw()?;

    chart
        .draw_series(LineSeries::new(
            (0..10).map(|x| (x as f32, (x as f32).powf(2.0))),
            &RED.stroke_width(2),
        ))?
        .label("y=x^2");

    chart
        .draw_series(LineSeries::new(
            (0..10).map(|x| (x as f32, (x as f32).powf(2.0) + 2.0)),
            &BLUE.stroke_width(2),
        ))?
        .label("y=x^2+2");

    chart
        .configure_series_labels()
        .background_style(&WHITE.mix(0.8))
        .border_style(&BLACK)
        .draw()?;

    Ok(())
}           

這個示例建立了一個自定義樣式的圖表。我們使用stroke_width方法設定了線段的寬度,使用mix方法設定了标簽的背景色。我們還使用border_style方法設定了标簽的邊框顔色。

使用CSV檔案繪制線圖

下面是一個使用CSV檔案繪制線圖的示例代碼:

use plotters::prelude::*;
use std::fs::File;
use std::io::{BufRead, BufReader};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("line_csv.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption("Line Chart with CSV", ("sans-serif", 40))
        .set_label_area_size(LabelAreaPosition::Left, 50)
        .set_label_area_size(LabelAreaPosition::Bottom, 50)
        .build_cartesian_2d(0..10, 0..10)?;

    chart.configure_mesh().draw()?;

    let file = File::open("data.csv").unwrap();
    let reader = BufReader::new(file);

    let data: Vec<(i32, i32)> = reader
        .lines()
        .map(|line| {
            let line = line.unwrap();
            let parts: Vec<&str> = line.split(',').collect();
            let x = parts[0].parse::<i32>().unwrap();
            let y = parts[1].parse::<i32>().unwrap();
            (x, y)
        })
        .collect();

    chart.draw_series(LineSeries::new(data, &RED))?;

    Ok(())
}           

在上述代碼中,我們首先建立了一個BitMapBackend對象,用于指定輸出格式和大小。然後,我們建立了一個ChartBuilder對象,用于指定圖表的标題和坐标軸等屬性。接着,我們讀取了一個名為data.csv的CSV檔案,并将其轉換為一個包含(i32, i32)元組的向量。最後,我們通過chart.draw_series方法将線圖繪制到圖表中。

繪制帶有标簽的散點圖

下面是一個繪制帶有标簽的散點圖的示例代碼:

use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("scatter.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption("Scatter Chart with Labels", ("sans-serif", 40))
        .set_label_area_size(LabelAreaPosition::Left, 50)
        .set_label_area_size(LabelAreaPosition::Bottom, 50)
        .build_cartesian_2d(0..10, 0..10)?;

    chart.configure_mesh().draw()?;

    let data = [
        ("A", (1, 2)),
        ("B", (2, 3)),
        ("C", (3, 4)),
        ("D", (4, 5)),
        ("E", (5, 6)),
    ];

    chart.draw_series(
        data.iter()
            .map(|(label, (x, y))| {
                Circle::new((*x, *y), 5, BLUE.filled())
                    .label(*label)
                    .legend(*label)
            })
            .collect::<Vec<_>>(),
    )?;

    chart
        .configure_series_labels()
        .background_style(&WHITE.mix(0.8))
        .border_style(&BLACK)
        .draw()?;

    Ok(())
}           

在上述代碼中,我們首先建立了一個BitMapBackend對象,用于指定輸出格式和大小。然後,我們建立了一個ChartBuilder對象,用于指定圖表的标題和坐标軸等屬性。接着,我們建立了一個包含标簽的散點資料,并使用Circle對象繪制散點圖。最後,我們通過chart.configure_series_labels方法繪制标簽。

繪制堆疊柱狀圖

下面是一個繪制堆疊柱狀圖的示例代碼:

use plotters::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = BitMapBackend::new("stacked_bar.png", (640, 480)).into_drawing_area();
    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .caption("Stacked Bar Chart", ("sans-serif", 40))
        .set_label_area_size(LabelAreaPosition::Left, 50)
        .set_label_area_size(LabelAreaPosition::Bottom, 50)
        .build_cartesian_2d(0..10, 0..10)?;

    chart.configure_mesh().draw()?;

    let data = vec![
        ("A", vec![2, 3, 1, 4, 2]),
        ("B", vec![1, 4, 3, 2, 1]),
        ("C", vec![3, 2, 4, 1, 3]),
    ];

    chart.draw_series(
        data.iter()
            .enumerate()
            .map(|(i, (label, values))| {
                let color = Palette99::pick(i);
                let values = values.iter().map(|v| *v as i64).collect::<Vec<_>>();
                let sum = values.iter().sum::<i64>();
                let offset = if i == 0 { 0 } else { data[i - 1].1.iter().sum::<i32>() };
                let data = values.iter().enumerate().map(|(j, v)| (j as i32 + offset, *v)).collect::<Vec<_>>();
                BarChart::new(data, 0.5)
                    .style(color.filled())
                    .label(label)
                    .legend(format!("{} ({}%)", label, v as f64 / sum as f64 * 100.0))
            })
            .collect::<Vec<_>>(),
    )?;

    chart
        .configure_series_labels()
        .background_style(&WHITE.mix(0.8))
        .border_style(&BLACK)
        .draw()?;

    Ok(())
}           

在上述代碼中,我們首先建立了一個BitMapBackend對象,用于指定輸出格式和大小。然後,我們建立了一個ChartBuilder對象,用于指定圖表的标題和坐标軸等屬性。接着,我們建立了一個包含堆疊柱狀資料,并使用BarChart對象繪制堆疊柱狀圖。最後,我們通過chart.configure_series_labels方法繪制标簽。

總結

Plotters是一個強大的繪圖庫,可以用于建立各種類型的圖表。它提供了易于使用的API,可以輕松地建立高品質的圖表,并支援多種輸出格式。在使用Plotters時,我們可以遵循最佳實踐,以提高代碼的可讀性和可維護性。

#頭條創作挑戰賽#