ellip-plot-graph 1.0.5

Generate plots for Ellip crate
/*
 * Ellip is licensed under The 3-Clause BSD, see LICENSE.
 * Copyright 2025 Sira Pornsiriprasert <code@psira.me>
 */

use ellip::ellipf;
use ellip_plot_graph::*;
use plotly::{
    Layout, Plot, Surface,
    common::{ColorScale, ColorScalePalette},
    layout::{Annotation, AspectRatio, Axis, Camera, CameraCenter, Eye, LayoutScene},
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let n_points_m = 50;
    let n_points_s2p = 50;
    let range_m = [-1.0, 1.0];
    let range_s2p = [0.0, 0.45];

    let mut m: Vec<f64> = (0..n_points_m)
        .map(|i| range_m[0] + i as f64 * (range_m[1] - range_m[0]) / (n_points_m - 1) as f64)
        .collect();

    let mut s2p: Vec<f64> = (0..n_points_s2p)
        .map(|i| {
            range_s2p[0] + i as f64 * (range_s2p[1] - range_s2p[0]) / (n_points_s2p - 1) as f64
        })
        .collect();

    let n_points_dense = 500;
    m.extend(
        (0..n_points_dense)
            .map(|i| 1.0 + i as f64 * (2.0 - 1.0) / (n_points_dense - 1) as f64)
            .collect::<Vec<f64>>(),
    );
    s2p.extend(
        (0..n_points_dense)
            .map(|i| 0.45 + i as f64 * (1.0 - 0.45) / (n_points_dense - 1) as f64)
            .collect::<Vec<f64>>(),
    );

    let ellipf_values: Vec<Vec<f64>> = s2p
        .iter()
        .map(|&s2pi| {
            m.iter()
                .map(|&mj| {
                    let phi = s2pi.sqrt().asin();
                    match ellipf(phi, mj) {
                        Ok(ans) => ans,
                        Err(_) => f64::NAN,
                    }
                })
                .collect()
        })
        .collect();

    let trace = Surface::new(ellipf_values)
        .x(m)
        .y(s2p)
        .name("F(φ,m)")
        .color_scale(ColorScale::Palette(ColorScalePalette::Viridis))
        .cmin(0.0)
        .cmax(3.5);

    let mut plot = Plot::new();
    plot.add_trace(trace);
    plot.set_layout(
        Layout::new()
            .title("Incomplete Elliptic Integral of the First Kind (F)")
            .width(800)
            .height(600)
            .scene(
                LayoutScene::new()
                    .x_axis(Axis::new().title("m").show_line(true))
                    .y_axis(
                        Axis::new()
                            .title("sin²φ")
                            .show_line(true)
                    )
                    .z_axis(
                        Axis::new()
                            .title("F(φ,m)")
                            .show_line(true)
                            .range(vec![0.0, 3.5]),
                    )
                    .aspect_ratio(AspectRatio::new().x(1.0).y(1.0))
                    .camera(
                        Camera::new().center(
                            CameraCenter::from((-0.041131071416140454, -0.08695975070301684, -0.19929655677619))
                        )
                        .eye(Eye::from((1.1978498070818442, -1.3810218675157693, 0.6951800446329962)))
                    )
            )
            .legend(
                plotly::layout::Legend::new()
                    .x(1.0)
                    .x_anchor(plotly::common::Anchor::Right)
            )
            .annotations(vec![Annotation::new()
            .text(format!(
                "Generated using <a href=\"https://docs.rs/ellip/latest/ellip/legendre/fn.ellipf.html\" target=\"_blank\">ellipf</a> from <a href=\"https://crates.io/crates/ellip\" target=\"_blank\">ellip</a> v{}",
                ellip_version()
            ))
            .x_ref("paper")
            .y_ref("paper")
            .y(-0.15)
            .x(1.08)
            .show_arrow(false)]),
    );

    make_html!(plot, "ellipf_3d.html");
    write_svg!(plot, "ellipf_3d.svg", 800, 600, 1.0);
    Ok(())
}