A simple 2D plotting library that outputs graphs to SVG that can be styled using CSS.

Overview

You can find poloto on github and crates.io. Documentation at docs.rs

A simple 2D plotting library that outputs graphs to SVG that can be styled using CSS.

Poloto graphs can be stylized using css either directly in the SVG, or from inside of html with an embedded svg. The latter allows the user to dynamically match the svg to their website's theme. The user can take full advantage of CSS, adding highlight on hover, animation, shadows, strokes, etc. Check out the github examples to see this. The latest graph outputs of the examples can be found in the assets folder.

You can see it in action in this rust book broccoli-book

Gaussian Example

// PIPE me to a file!
fn main() {
    // See https://en.wikipedia.org/wiki/Gaussian_function
    let gaussian = |sigma: f64, mu: f64| {
        use std::f64::consts::TAU;
        let s = sigma.powi(2);
        let k = (sigma * TAU).sqrt().recip();
        move |x: f64| (-0.5 * (x - mu).powi(2) / s).exp() * k
    };

    let range = (0..200).map(|x| (x as f64 / 200.0) * 10.0 - 5.0);

    let g1 = gaussian(1.0, 0.0);
    let g2 = gaussian(0.5, 0.0);
    let g3 = gaussian(0.3, 0.0);

    let plotter = poloto::plot("gaussian", "x", "y")
        .line("σ = 1.0", range.clone().map(|x| [x, g1(x)]))
        .line("σ = 0.5", range.clone().map(|x| [x, g2(x)]))
        .line("σ = 0.3", range.clone().map(|x| [x, g3(x)]))
        .ymarker(0.0)
        .move_into();

    println!("{}", poloto::disp(|a| poloto::simple_theme(a, plotter)));
}

Output

demo

Data Example

// PIPE me to a file!
fn main() {
    //Source https://en.wikipedia.org/wiki/Wikipedia:Size_of_Wikipedia
    let data = [
        [2010, 3144000],
        [2011, 3518000],
        [2012, 3835000],
        [2013, 4133000],
        [2014, 4413000],
        [2015, 4682000],
        [2016, 5045000],
        [2017, 5321200],
        [2018, 5541900],
        [2019, 5773600],
        [2020, 5989400],
        [2021, 6219700],
        [2022, 0], //To complete our histogram, we manually specify when 2021 ends.
    ];

    let mut s = poloto::plot("Number of Wikipedia Articles", "Year", "Number of Articles");

    s.histogram("", &data);

    //Scale grpah to include up to the year 2025.
    //Also scale to include a value of 0 articles.
    s.xmarker(2025).ymarker(0);

    println!("{}", poloto::disp(|a| poloto::simple_theme(a, s)));
}

Output

demo

Collatz Example

{}{}{}{}", poloto::SVG_HEADER, poloto::STYLE_CONFIG_DARK_DEFAULT, ".poloto_line{stroke-dasharray:2;stroke-width:1;}", poloto::disp(|a| plotter.render(a)), poloto::SVG_END ) } ">
// PIPE me to a file!
fn main() {
    let collatz = |mut a: i128| {
        std::iter::from_fn(move || {
            //Base case
            if a == 1 {
                None
            } else {
                a = if a % 2 == 0 { a / 2 } else { 3 * a + 1 };
                Some(a)
            }
        })
        .fuse()
    };

    let mut plotter = poloto::plot("collatz", "x", "y");

    plotter.ymarker(0);

    for i in 1000..1006 {
        plotter.line(poloto::formatm!("c({})", i), (0..).zip(collatz(i)));
    }

    println!(
        "{}{}{}",
        poloto::SVG_HEADER,
        poloto::STYLE_CONFIG_DARK_DEFAULT,
        ".poloto_line{stroke-dasharray:2;stroke-width:1;}",
        poloto::disp(|a| plotter.render(a)),
        poloto::SVG_END
    )
}

Output

demo

Parametric Example

// PIPE me to a file!
fn main() {
    // https://mathworld.wolfram.com/HeartCurve.html
    let heart = |t: f64| {
        [
            16.0 * t.sin().powi(3),
            13.0 * t.cos() - 5.0 * (2.0 * t).cos() - 2.0 * (3.0 * t).cos() - (4.0 * t).cos(),
        ]
    };

    let range = (0..100).map(|x| x as f64 / 100.0).map(|x| x * 6.0 - 3.0);

    let plotter = poloto::plot("Heart Graph", "x", "y")
        .line_fill("heart", range.map(heart))
        .preserve_aspect()
        .move_into();

    println!(
        "{}",
        poloto::disp(|a| poloto::simple_theme_dark(a, plotter))
    );
}

Output

demo

Trig Example

use poloto::formatm;

// PIPE me to a file!
fn main() {
    let x = (0..500).map(|x| (x as f64 / 500.0) * 10.0);

    let mut plotter = poloto::plot(
        "Some Trigonometry Plots 🥳",
        formatm!("This is the {} label", 'x'),
        "This is the y label",
    );

    use poloto::Croppable;
    // Using poloto::Croppable, we can filter out plots and still have discontinuity.
    plotter.line(
        "tan(x)",
        x.clone()
            .map(|x| [x, x.tan()])
            .crop_above(10.0)
            .crop_below(-10.0)
            .crop_left(2.0),
    );

    plotter.line("sin(2x)", x.clone().map(|x| [x, (2.0 * x).sin()]));

    plotter.line(
        "2*cos(x)",
        x.clone().map(|x| [x, 2.0 * x.cos()]).crop_above(1.4),
    );

    println!("{}", poloto::disp(|a| poloto::simple_theme(a, plotter)));
}

Output

demo

String Interval Example

// PIPE me to a file!
fn main() {
    use poloto::util::MonthIndex;

    let data = [
        ("Jan", 3144000),
        ("Feb", 3518000),
        ("Mar", 3835000),
        ("Apr", 4133000),
        ("May", 4413000),
        ("Jun", 4682000),
        ("Jul", 5045000),
        ("Aug", 5321200),
        ("Sep", 5541900),
        ("Oct", 5773600),
        ("Nov", 5989400),
        ("Dec", 6219700),
        ("Jan", 3518000),
        ("Feb", 3518000),
    ];

    let mut s = poloto::plot("Number of Foos in 2021", "Months of 2021", "Foos");

    //Map the strings to indexes
    s.histogram("", (0..).map(MonthIndex).zip(data.iter().map(|x| x.1)));

    s.ymarker(0);

    //Lookup the strings with the index
    s.xinterval_fmt(|fmt, val, _| write!(fmt, "{}", data[usize::try_from(val.0).unwrap()].0));

    println!("{}", poloto::disp(|a| poloto::simple_theme_dark(a, s)));
}

Output

demo

CSS Usage Example

See the graphs in this report: broccoli_book

CSS classes

Below are the css classes that can be stylized. There are default styles settings for these css classes in the static strings STYLE_CONFIG_LIGHT_DEFAULT and STYLE_CONFIG_DARK_DEFAULT.

These are the css classes added through Plotter::render

  • poloto_text - all poloto text
  • poloto_axis_lines - axis lines and ticks
  • poloto_tick_labels - x and y labels as well as where labels
  • poloto_labels - title, x label, ylabel
  • poloto_title - title
  • poloto_xname - xlabel
  • poloto_yname - ylabel
  • poloto_legend_text - legend text
  • poloto_legend_icon - legend icon
  • poloto_scatter - scatter plots and legend icon
  • poloto_line - line plots and legend icon
  • poloto_histo - histogram and legend icon
  • poloto_linefill - line fill and legend icon
  • poloto_linefillraw - line fill raw and legend icon

These are the css classes added through poloto::SVG_HEADER which is used by simple_theme and simple_theme_dark.

  • poloto - default svg element

For plots:

  • poloto[n]fill - If the n'th plot requires fill. (e.g. linefill or histogram)
  • poloto[n]stroke - If the n'th plot requires stroke. (e.g. line or scatter)

Iterating plots twice

In order to calculate the right size view to scale all the plots, poloto has to iterate over all the plot points twice. Once to find the min and max bounds, and once to scale all the points by the scale determined by the first iteration.

If you are using an iterator where each iteration is expensive, consider running the iterator just once, collecting the results in a Vec. Then pass that Vec to the plotting functions. Beware of passing the buffer directly to the plotter! If you do this, you'll use a lot of memory since the plotter will clone the whole buffer. Instead pass a reference to the buffer. See the second example below.

Can I change the styling of the plots?

Yes! You can harness the power of CSS both in the svg, or outside in html with an embeded svg. Some things you can do:

  • Change the color scheme to fit your html theme.
  • Highlight one plot, make it dashed, or add hover effect
  • Animate things using @keyframes

The Plotter struct documents which css classes you can modify for the graph as a whole. Each plot function documents which css classes you can modify to change that specific plot.

Scatter plots are done using SVG paths made up of lines of zero length. This allows you to change the radius of the scatter dots by changing the stroke width.

Formatting Tick Intervals

Poloto will first print intervals in normal decimal at the precision required to capture the differences in the step size between the intervals. If the magnitude of a number is detected to be too big or small, it may switch to scientific notation, still at the required precision. It will only switch if the scientific notation version is actually less characters than the normal decimal format which is not always the case when you consider the precision that might be required to capture the step size.

Even with the above system, there are cases where the numbers all have a really big magnitude, but are all really close together (small step size). In this case, there isn't really a good way to format it. In these cases, poloto will fall back to making the number relative to the first number.

Comments
  • Replace `.twice_iter` with `Iterator + Clone`?

    Replace `.twice_iter` with `Iterator + Clone`?

    Hey, I am learning poloto API, and the Double iterator concept feels like it's not necessary?

    Feels like the following might be enough?

        pub fn line_fill<N, I>(&mut self, name: N, plots: I) -> &mut Self
        where
            N: Display + 'a,
            I: IntoIterator<Item = [f64; 2]>,
            I::IntoIter: Clone,
    
    opened by matklad 6
  • Background color

    Background color

    Hi! Since updating from 6.1 to 7.1, the background colour of the SVG disappeared (outside the viewBox). In Firefox, I see the background-color property was removed from the root element. It seems like it's been replaced by a very large circle. Why is this?

    opened by Icelk 4
  • Option to add empty name / remove bar of colour above

    Option to add empty name / remove bar of colour above

    To display additional information next to the name of the data, I've added an empty scatter. That however still results in a coloured bar above the text.

    Is there potential for an option of removing the bar and or a separate method for showing info there?

    Great project!

    opened by Icelk 4
  • Name of data is truncated

    Name of data is truncated

    When adding data, long names are truncated in the SVG. This is due to the dimensions, I believe.

    I'd be very grateful if the text would wrap or if it just wouldn't clip.

    Great project!

    opened by Icelk 4
  • Don't let users pass IntoIterator?

    Don't let users pass IntoIterator?

    I think this is a big trap that users can easily fall into.

    Doing data.line("") seems innocent, but if data is a big Vec, then this line will clone a std::vec::IntoIter, causing the entire Vec to be duplicated in memory. The user should instead do (&data).line(""). However that very sneaky and easy to overlook. I'm thinking the safest thing to do is just forbid IntoIterator and require only Iterator to force the user to write data.iter().line("") as its more clear that the entire Vec will not be duplicated.

    question 
    opened by tiby312 3
  • Avoid massive allocations

    Avoid massive allocations

    Right now, all the iterators are collected into a vec of vecs of floats of array size 2. Then they are also inserted into the document. So if the user is iterating through their own data structure, the same data can be in memory 3 times, which is wasteful.

    Originally the iterators passed in were clonable and they were used twice. Once to find the bounds and once to normalize them. This reduces memory, but the user has be to aware to not do expensive calculations in their iterator because it will happen twice. To avoid user error, I opted to instead use the iterator once but collect the results. This uses more memory but the api can't be misused.

    I think ideally, you'd run the iterators once and save them to a file while also finding the bounds. Then you would read the file again and normalize each float as you see them and write to svg.

    I think the svg crate keeps the whole svg in memory. Another improvement wooluld be if the svg was written to file as you built it keep in memory only what you need to complete in finished tags.

    I think this isn't that big of a deal though because ideally the user is not plotting a ton of data. This crate is meant to be used just to see broad trends

    opened by tiby312 3
  • Add scientific notation: Hard!

    Add scientific notation: Hard!

    If you have just one giant point, for example, the intervals all print the same value (in scientific notation).

    For example, do a scatter plot of just the point 30000000.0,30000000.0

    opened by tiby312 3
  • Using `plots!` fails.

    Using `plots!` fails.

    It gives this error message:

    the method `chain` exists for struct `poloto::build::SinglePlot<std::iter::FilterMap<std::iter::Map<std::ops::Range<{integer}>, [closure@src/bin/main.rs:500:30: 500:99]>, [closure@src/bin/main.rs:504:38: 514:26]>, std::string::String>`, but its trait bounds were not satisfied
    

    only if the trait PlotIteratorExt isn't imported. This should be done in the macro.

    opened by Icelk 2
  • Make data name optional

    Make data name optional

    When supplying an empty string to the functions here, no text is added to the legend. This isn't documented. I suggest making name an Option, which implicitly tells your users the name is optional.

    opened by Icelk 2
  • [question] Custom format of tick values

    [question] Custom format of tick values

    Thanks for the nice and easy to use crate! I could not really find that out, but is it (already) possible to format the tick values? E.g., I have a graph that has the time that it already ran as number of seconds as x-axis and want to provide some function to a Formatter, that formats it as Xh Ymin Zsec.

    opened by eknoes 2
  • Allow Strings on the x axis

    Allow Strings on the x axis

    Would it be possible to allow String input values on the x-axis, i. e.:

        let data = [
            ("Jan 2020", 3144),
            ("Feb 2020", 3518),
            ("Mar 2020", 3835),
        ];
    
    opened by Brueggus 2
  • Add annotation API

    Add annotation API

    Something like this:

    use poloto::build;
    // PIPE me to a file!
    fn main() {
        let data = vec![[0, 0], [1, 2], [2, 3]];
    
        let a = build::plot("label").line(data);
    
        poloto::data(a)
            .build_and_label(("hello world", "x", "y"))
            .annotate(|w|{
                let point=w.convert_point([1,2]);
                
                let circle=hypermelon::build::elem("circle").with(attrs!(("x",point.x),("y",point.y)))
    
                let text_pos=w.request_text_position();
    
                let text=hypermelon::build::elem("text").with(attrs!(("x",text_pos.x),("y",text_pos.y)))
    
                circle.append(text)
            })
            .append_to(poloto::header().light_theme())
            .render_stdout();
    }
    
    opened by tiby312 0
  • Use circle element for scatter when svg 2 spec is supported

    Use circle element for scatter when svg 2 spec is supported

    In conjunction with defs and use tags so as to not repeat the r attribute. Need to wait for svg 2 spec if want to keep allowing user to change radius with css

    opened by tiby312 0
Owner
Ken Reed
Ken Reed
A rust drawing library for high quality data plotting for both WASM and native, statically and realtimely 🦀 📈🚀

Plotters - A Rust drawing library focus on data plotting for both WASM and native applications ?? ?? ?? Plotters is drawing library designed for rende

Hao Hou 2.7k Jan 4, 2023
Render farm simulator & plotting for optimisation written in Rust.

Farm Usage Simulator Given a few basic charasteristics of a render farm and render jobs this app runs a few randomized farm usage scenarios and plots

ford 2 Jul 17, 2022
Externalize easily the plotting process from Rust to gnuplot.

preexplorer Easy plotter and saver of simple data. Handy tool for development stage or small computational projects. Save data, have a quick view and

Raimundo Saona 4 Jan 7, 2022
Lightweight graphs (sparklines) for use with Embedded Rust

Sparklines for Rust's Embedded-graphics Sparklines are small, high resolution graphics embedded in a context of words, numbers or images". Edward Tuft

Bernard Kobos 4 Aug 28, 2022
🔵🟠 Portal Explorer — web visualization of mind-blowing portals using ray-tracing.

In Portal Explorer you can view how interesting portals are constructed, and visually explore their properties by moving and rotating them. This program doesn't work well on mobile, better opened from PC.

ilya sheprut 99 Dec 7, 2022
This is a Rust implementation of a boid flocking simulation using the ggez graphics crate.

Boidflock This is a Rust implementation of a boid flocking simulation using the ggez graphics crate. The CLI for this program is built using the struc

Andrew Lee 12 Jan 4, 2023
KDash - A fast and simple dashboard for Kubernetes

KDash - A fast and simple dashboard for Kubernetes

null 915 Jan 4, 2023
A Rust library for drawing plots, powered by Gnuplot.

RustGnuplot A Gnuplot controller written in Rust. Documentation See here Examples A simple example: let mut fg = Figure::new(); fg.axes2d() .set_titl

null 353 Dec 26, 2022
A library of to show data (in browser, evcxr_jupyter) as table, chart...

showata A library of to show data (in browser, evcxr_jupyter) as table, chart.... The crate provides display for: image vector and slice (as table) nd

Procyon 20 Dec 12, 2022
A pure Rust visualization library inspired by D3.js

charts A pure Rust visualization library inspired by D3.js. See gallery and examples for code and more charts. Install You can add this as a dependenc

Iulian Gulea 186 Dec 29, 2022
A library to generate syntax diagrams for Rust macros.

Live demo (code) A browser add-on for Firefox, Chrome and Edge A library to generate syntax ("railroad") diagrams for Rust's macro_rules!(). Diagrams

null 468 Jan 6, 2023
Moonshine CSS - 🥃 High-proof atomic CSS framework

Moonshine CSS - ?? High-proof atomic CSS framework

Econify 25 Nov 25, 2022
Send Windows 10 styled notifications on Windows 7.

win7-notifications Send Windows 10 styled notifications on Windows 7. Note: This crate requires a win32 event loop to be running, otherwise the notifi

Tauri 9 Aug 29, 2022
Terminal plotting library for using in Rust CLI applications

textplots Terminal plotting library for using in Rust CLI applications. Should work well in any unicode terminal with monospaced font. It is inspired

Alexey Suslov 163 Dec 30, 2022
`Debug` in rust, but only supports valid rust syntax and outputs nicely formatted using pretty-please

dbg-pls A Debug-like trait for rust that outputs properly formatted code Showcase Take the following code: let code = r#" [ "Hello, World!

Conrad Ludgate 12 Dec 22, 2022
A utility that can download JavaScript and TypeScript module graphs and store them locally in a special zip file.

eszip A utility that can download JavaScript and TypeScript module graphs and store them locally in a special zip file. To create a new archive: > esz

Deno Land 162 Dec 24, 2022
Data plotting library for Rust

plotlib plotlib is a generic data visualisation and plotting library for Rust. It is currently in the very early stages of development. It can current

Matt Williams 417 Dec 31, 2022
A rust drawing library for high quality data plotting for both WASM and native, statically and realtimely 🦀 📈🚀

Plotters - A Rust drawing library focus on data plotting for both WASM and native applications ?? ?? ?? Plotters is drawing library designed for rende

Hao Hou 2.7k Jan 4, 2023
Plotting library for the Bevy game engine with a focus on esthetics and interactivity

Plotting library for the Bevy game engine with a focus on esthetics and interactivity. It can handle both data points (see the "minimal", "markers", a

Eli 40 Dec 25, 2022