Plotters Tutorial with Jupyter

This is a interactive tutorial for Plotters drawing library. If you are looking at the static HTML version and want to try the interactive version. Please follow the steps:

For Ubuntu/Debian users

# Install Jupyter notebook 
sudo apt install libzmq3-dev jupyter-notebook
cargo install evcxr_jupyter
evcxr_jupyter --install
# Get the notebook
git clone https://github.com/38/plotters-doc-data
cd plotteres-doc-data
jupyter notebook

For OSX users

# Install Jupyter notebook 
brew install zeromq pkg-config
cargo install evcxr_jupyter
evcxr_jupyter --install
# Get the notebook
git clone https://github.com/38/plotters-doc-data
cd plotteres-doc-data
jupyter notebook

You can also download the latest notebook from https://raw.githubusercontent.com/38/plotters-doc-data/master/evcxr-jupyter-integration.ipynb, thus you don't have to clone the entire data repo.

Get Started

In order to use Plotters in jupyter-evcxr, you need both Jupyter and evcxr installed. Check https://github.com/google/evcxr for the instructions.

To use Plotters with jupyter-evcxr, you need to import it using the following code:

In [2]:
:dep plotters = { version = "^0.3.0", default_features = false, features = ["evcxr", "all_series"] }

Because evcxr uses only SVG images and all types of series, so we don't need other types of backend. So we should put

default_features = false, features = ["evcxr", "all_series"]

Make the compilation faster. Since evcxr shares all the artifacts among cells, after the first time we have plotters compiled, it should be faster after.

Plotters evcxr integration overview

To use plotters, the most convenient way is importing everything defined in the prelude module. It will import evcxr_figure function for evcxr integration.

Note: Currently evcxr doesn't work with nightly rust, so please make sure you are using a stable rust

In [4]:
:dep plotters = { version = "^0.3.0", default_features = false, features = ["evcxr", "all_series"] }
extern crate plotters;
// Import all the plotters prelude functions
use plotters::prelude::*;
// To create a figure that can be displayed in Jupyter notebook, use evcxr_figure function.
// The first param is the resolution of the figure.
// The second param is the closure that performes the drawing.
evcxr_figure((300, 100), |root| {
    // Do the drawings
    root.fill(&BLUE)?;
    // Tell plotters that everything is ok
    Ok(())
})
Out[4]:

Hello World

In [5]:
:dep plotters = { version = "^0.3.0", default_features = false, features = ["evcxr", "all_series"] }
extern crate plotters;
use plotters::prelude::*;

evcxr_figure((320,50), |root| {
    root.fill(&GREEN)?;
    root.draw(&Text::new("Hello World from Plotters!", (15, 15), ("Arial", 20).into_font()))?;
    Ok(())
})
Out[5]:
Hello World from Plotters!

Sub- Drawing Areas

One of the very important features is, Plotters allows drawing multiple charts in a single figure. And this is done by having sub-drawing-areas. The root drawing area is able to be splitted into smaller drawing areas, and you can always do more fine-grained splits as well.

In [6]:
:dep plotters = { version = "^0.3.0", default_features = false, features = ["evcxr", "all_series"] }
extern crate plotters;
use plotters::prelude::*;
use plotters::coord::Shift;
pub fn sierpinski_carpet(
    depth: u32, 
    drawing_area: &DrawingArea<SVGBackend, Shift>) 
-> Result<(), Box<dyn std::error::Error>> {
    if depth > 0 {
        let sub_areas = drawing_area.split_evenly((3,3));
        for (idx, sub_area) in (0..).zip(sub_areas.iter()) {
            if idx == 4 {
                sub_area.fill(&WHITE)?;
            } else {
                sierpinski_carpet(depth - 1, sub_area)?;
            }
        }
    }
    Ok(())
}
evcxr_figure((4800,4800), |root| {
    root.fill(&BLACK)?;
    sierpinski_carpet(5, &root)
}).style("width: 200px")  /* You can add CSS style to the result */
Out[6]:

Chart Context

Plotters is designed for drawing charts, plots, etc. This example demonstrate how to use Plotters chart specific APIs to draw a chart, including, labels, axis, meshes, etc. To draw a chart on the drawin area, you need to create a chart context and do some configuration.

In [7]:
:dep plotters = { version = "^0.3.0", default_features = false, features = ["evcxr", "all_series"] }
extern crate plotters;
use plotters::prelude::*;
evcxr_figure((640, 240), |root| {
    // The following code will create a chart context
    let mut chart = ChartBuilder::on(&root)
        .caption("Hello Plotters Chart Context!", ("Arial", 20).into_font())
        .build_cartesian_2d(0f32..1f32, 0f32..1f32)?;
    // Then we can draw a series on it!
    chart.draw_series((1..10).map(|x|{
        let x = x as f32/10.0;
        Circle::new((x,x), 5, &RED)
    }))?;
    Ok(())
}).style("width:60%")
Out[7]:
Hello Plotters Chart Context!

Adding Common Chart Components

We can also make Plotters draws common components for us, such as, meshes, axis, legend. In this section, we demonstrate how to do that.

The following code shows how we add mesh to the chart.

In [8]:
:dep plotters = { version = "^0.3.0", default_features = false, features = ["evcxr", "all_series"] }
extern crate plotters;
use plotters::prelude::*;
evcxr_figure((640, 480), |root| {
    // The following code will create a chart context
    let mut chart = ChartBuilder::on(&root)
        .caption("Chart Context with Mesh", ("Arial", 20).into_font())
        .build_cartesian_2d(0f32..1f32, 0f32..1f32)?;
    chart.configure_mesh().draw()?;
    Ok(())
}).style("width: 60%")
Out[8]:
Chart Context with Mesh

Then we can add axis to the chart.

In [9]:
:dep plotters = { version = "^0.3.0", default_features = false, features = ["evcxr", "all_series"] }
extern crate plotters;
use plotters::prelude::*;
evcxr_figure((640, 480), |root| {
    // The following code will create a chart context
    let mut chart = ChartBuilder::on(&root)
        .caption("Chart Context with Mesh and Axis", ("Arial", 20).into_font())
        .x_label_area_size(40)
        .y_label_area_size(40)
        .build_cartesian_2d(0f32..1f32, 0f32..1f32)?;
    
    chart.configure_mesh()
        .draw()?;
    
    Ok(())
}).style("width: 60%")
Out[9]:
Chart Context with Mesh and Axis 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0

In addition to that, we can put label text to the axis.

In [10]:
:dep plotters = { version = "^0.3.0", default_features = false, features = ["evcxr", "all_series"] }
extern crate plotters;
use plotters::prelude::*;
evcxr_figure((640, 480), |root| {
    // The following code will create a chart context
    let mut chart = ChartBuilder::on(&root)
        .caption("Chart with Axis Label", ("Arial", 20).into_font())
        .x_label_area_size(40)
        .y_label_area_size(40)
        .build_cartesian_2d(0f32..1f32, 0f32..1f32)?;
    
    chart.configure_mesh()
        .x_desc("Here's the label for X")
        .y_desc("Here's the label for Y")
        .draw()?;
    
    Ok(())
}).style("width: 60%")
Out[10]:
Chart with Axis Label Here's the label for Y Here's the label for X 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0

Then let's disable mesh lines for the X axis

In [12]:
:dep plotters = { version = "^0.3.0", default_features = false, features = ["evcxr", "all_series"] }
extern crate plotters;
use plotters::prelude::*;
evcxr_figure((640, 480), |root| {
    // The following code will create a chart context
    let mut chart = ChartBuilder::on(&root)
        .caption("Chart Context with Mesh and Axis", ("Arial", 20).into_font())
        .x_label_area_size(40)
        .y_label_area_size(40)
        .build_cartesian_2d(0f32..1f32, 0f32..1f32)?;
    
    chart.configure_mesh()
        .y_labels(10)
        .light_line_style(&TRANSPARENT)
        .disable_x_mesh()
        .draw()?;
    
    Ok(())
}).style("width: 60%")
Out[12]:
Chart Context with Mesh and Axis 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0

To create multiple charts in a single figure, you can just split the drawing area and create multiple chart context.

In [13]:
:dep plotters = { version = "^0.3.0", default_features = false, features = ["evcxr", "all_series"] }
extern crate plotters;
use plotters::prelude::*;
evcxr_figure((640, 480), |root| {
    let sub_areas = root.split_evenly((2,2));
    
    for (idx, area) in (1..).zip(sub_areas.iter()) {
        // The following code will create a chart context
        let mut chart = ChartBuilder::on(&area)
            .caption(format!("Subchart #{}", idx), ("Arial", 15).into_font())
            .x_label_area_size(40)
            .y_label_area_size(40)
            .build_cartesian_2d(0f32..1f32, 0f32..1f32)?;

        chart.configure_mesh()
            .y_labels(10)
            .light_line_style(&TRANSPARENT)
            .disable_x_mesh()
            .draw()?;
    }

    Ok(())
}).style("width: 60%")
Out[13]:
Subchart #1 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 Subchart #2 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 Subchart #3 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 Subchart #4 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0

Series

Unlike most of the plotting libraries, Plotters doesn't actually define any types of chart. All the chart is abstracted to a concept of series. By doing so, you can put a histgoram series and a line plot series into the same chart context. The series is actually defined as an iterator of elements, just this.

This gives Plotters a huge flexibility on drawing charts. You can implement you own types of series and uses the coordinate translation and chart elements.

There are few types of predefined series, just for convenience:

  • Line Series
  • Histogram
  • Point Series

Scatter Plot

First of all, let's generate some random numbers.

In [14]:
:dep rand = { version = "0.6.5" }
extern crate rand;

use rand::distributions::Normal;
use rand::distributions::Distribution;
use rand::thread_rng;
let sd = 0.13;
let random_points:Vec<(f64,f64)> = {
    let mut norm_dist = Normal::new(0.5, sd);
    let (mut x_rand, mut y_rand) = (thread_rng(), thread_rng());
    let x_iter = norm_dist.sample_iter(&mut x_rand);
    let y_iter = norm_dist.sample_iter(&mut y_rand);
    x_iter.zip(y_iter).take(1000).collect()
};
random_points.len()
Out[14]:
1000

It's trivial to draw a scatter plot with Plotters. The only need is, provide a iterator of the elements as series. The following example shows how to make a 2D normal distribution figure. The red rectangle is the two sigma area and the red cross is the mean.

In [15]:
:dep plotters = { version = "^0.3.0", default_features = false, features = ["evcxr", "all_series"] }

extern crate plotters;
use plotters::prelude::*;

evcxr_figure((640, 480), |root| {
    // The following code will create a chart context
    let mut chart = ChartBuilder::on(&root)
        .caption("Normal Distribution", ("Arial", 20).into_font())
        .x_label_area_size(40)
        .y_label_area_size(40)
        .build_ranged(0f64..1f64, 0f64..1f64)?;
    
    chart.configure_mesh()
        .disable_x_mesh()
        .disable_y_mesh()
        .draw()?;
    
    chart.draw_series(random_points.iter().map(|(x,y)| Circle::new((*x,*y), 3, GREEN.filled())));
    
    // You can alawys freely draw on the drawing backend
    let area = chart.plotting_area();
    let two_sigma = sd * 2.0;
    area.draw(&Rectangle::new(
        [(0.5 - two_sigma, 0.5 - two_sigma), (0.5 + two_sigma, 0.5 + two_sigma)], 
        RED.mix(0.3).filled())
    )?;
    area.draw(&Cross::new((0.5, 0.5), 5, &RED))?;
    
    Ok(())
}).style("width:60%")
Out[15]:
Normal Distribution 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0

Histogram

We can also have histograms. For histograms, we can use the predefined histogram series struct to build the histogram easily. The following code demonstrate how to create both histogram for X and Y value of random_points.

In [18]:
:dep plotters = { version = "^0.3.0", default_features = false, features = ["evcxr", "all_series", "all_elements"] }

extern crate plotters;
use plotters::prelude::*;

evcxr_figure((640, 480), |root| {
    let areas = root.split_evenly((2,1));
    let mut charts = vec![];
    
    // The following code will create a chart context
   for (area, name) in areas.iter().zip(["X", "Y"].into_iter()) {
        let mut chart = ChartBuilder::on(&area)
            .caption(format!("Histogram for {}", name), ("Arial", 20).into_font())
            .x_label_area_size(40)
            .y_label_area_size(40)
            .build_cartesian_2d(0u32..100u32, 0f64..0.5f64)?;
        chart.configure_mesh()
            .disable_x_mesh()
            .disable_y_mesh()
            .y_labels(5)
            .x_label_formatter(&|x| format!("{:.1}", *x as f64 / 100.0))
            .y_label_formatter(&|y| format!("{}%", (*y * 100.0) as u32))
            .draw()?;
        charts.push(chart);
    }
    
    let hist_x = Histogram::vertical(&charts[0])
        .style(RED.filled())
        .margin(0)
        .data(random_points.iter().map(|(x,_)| ((x*100.0) as u32, 0.01)));
    
    let hist_y = Histogram::vertical(&charts[0])
        .style(GREEN.filled())
        .margin(0)
        .data(random_points.iter().map(|(_,y)| ((y*100.0) as u32, 0.01)));
    
    charts[0].draw_series(hist_x);
    charts[1].draw_series(hist_y);
    
    Ok(())
}).style("width:60%")
Out[18]:
Histogram for X 10% 20% 30% 40% 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 Histogram for Y 10% 20% 30% 40% 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0

Combination of Histogram and Scatter

In [26]:
:dep plotters = { version = "^0.3.0", default_features = false, features = ["evcxr", "all_series", "all_elements"] }

use plotters::prelude::*;

evcxr_figure((640, 480), |root| {
    let root = root.titled("Scatter with Histogram Example", ("Arial", 20).into_font())?;
    
    let areas = root.split_by_breakpoints([560], [80]);

    let mut x_hist_ctx = ChartBuilder::on(&areas[0])
        .y_label_area_size(40)
        .build_cartesian_2d(0u32..100u32, 0f64..0.5f64)?;
    let mut y_hist_ctx = ChartBuilder::on(&areas[3])
        .x_label_area_size(40)
        .build_cartesian_2d(0f64..0.5f64, 0..100u32)?;
    let mut scatter_ctx = ChartBuilder::on(&areas[2])
        .x_label_area_size(40)
        .y_label_area_size(40)
        .build_cartesian_2d(0f64..1f64, 0f64..1f64)?;
    scatter_ctx.configure_mesh()
        .disable_x_mesh()
        .disable_y_mesh()
        .draw()?;
    scatter_ctx.draw_series(random_points.iter().map(|(x,y)| Circle::new((*x,*y), 3, GREEN.filled())))?;
    let x_hist = Histogram::vertical(&x_hist_ctx)
        .style(RED.filled())
        .margin(0)
        .data(random_points.iter().map(|(x,_)| ((x*100.0) as u32, 0.01)));
    let y_hist = Histogram::horizontal(&y_hist_ctx)
        .style(GREEN.filled())
        .margin(0)
        .data(random_points.iter().map(|(_,y)| ((y*100.0) as u32, 0.01)));
    x_hist_ctx.draw_series(x_hist)?;
    y_hist_ctx.draw_series(y_hist)?;
    
    Ok(())
}).style("width:60%")
Out[26]:
Scatter with Histogram Example 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0