evcxr-jupyter-integration

# 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]:

## 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]:

## 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]:

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]:

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]:

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]:

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]:

## 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]:

### 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]:

### 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]:

## 3D Plots¶

Plotters also support 3D plotting since 0.3. To plotting a 3D chart, it's quite simple: when constructing the `ChartContext`, instead of `build_cartesian_2d`, use `build_cartesian_3d` instead. Then you should get a plotting environment that supports accepts 3-dimensional coordinates.

In [41]:

    :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("3D Plotting", ("Arial", 20).into_font())?;

        let mut chart = ChartBuilder::on(&root)
            .build_cartesian_3d(-10.0..10.0, -10.0..10.0, -10.0..10.0)?;
        // Draw a red circle parallel to XOZ panel
        chart.draw_series(LineSeries::new(
            (-314..314).map(|a| a as f64 / 100.0).map(|a| (8.0 * a.cos(), 0.0, 8.0 *a.sin())),
            &RED,
        ))?;
        // Draw a green circle parallel to YOZ panel
        chart.draw_series(LineSeries::new(
            (-314..314).map(|a| a as f64 / 100.0).map(|a| (0.0, 8.0 * a.cos(), 8.0 *a.sin())),
            &GREEN,
        ))?;

        Ok(())
    })

Out[41]:

### Draw Plot Components¶

Just like the 2D plots, we are able to draw the axis and axis panel easily. Unlike the 2D plots, 3D plots use the function `configure_axes` to configure the chart components.

In [42]:

    :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("3D Plotting", ("Arial", 20).into_font())?;

        let mut chart = ChartBuilder::on(&root)
            .build_cartesian_3d(-10.0..10.0, -10.0..10.0, -10.0..10.0)?;

        chart.configure_axes().draw()?;

        // Draw a red circle parallel to XOZ panel
        chart.draw_series(LineSeries::new(
            (-314..314).map(|a| a as f64 / 100.0).map(|a| (8.0 * a.cos(), 0.0, 8.0 *a.sin())),
            &RED,
        ))?;
        // Draw a green circle parallel to YOZ panel
        chart.draw_series(LineSeries::new(
            (-314..314).map(|a| a as f64 / 100.0).map(|a| (0.0, 8.0 * a.cos(), 8.0 *a.sin())),
            &GREEN,
        ))?;

        Ok(())
    })

Out[42]:

### Adjusting the 3D View¶

Unlike the 2D plot, 3D plot allows you to change the point of view. For example, rotate the plot or scale the plot, etc. This is done by the projection matrix, Plotters allows you override the default projection matrix, so that you can customize your view point of the 3D Plot. The following example shows how to look at the plot from top to bottom in the plot we have previously created.

In [47]:

    :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("3D Plotting", ("Arial", 20).into_font())?;

        let mut chart = ChartBuilder::on(&root)
            .build_cartesian_3d(-10.0..10.0, -10.0..10.0, -10.0..10.0)?;
        chart.with_projection(|mut p| {
            p.pitch = 1.5707; // 90 degreen pitch, thus we are looking the plot from top
            p.yaw = 0.0;      // Make plot's X axis parallel to screen's X axis
            p.into_matrix() // build the projection matrix
        });

        chart.configure_axes().draw()?;

        // Draw a red circle parallel to XOZ panel
        chart.draw_series(LineSeries::new(
            (-314..314).map(|a| a as f64 / 100.0).map(|a| (8.0 * a.cos(), 0.0, 8.0 *a.sin())),
            &RED,
        ))?;
        // Draw a green circle parallel to YOZ panel
        chart.draw_series(LineSeries::new(
            (-314..314).map(|a| a as f64 / 100.0).map(|a| (0.0, 8.0 * a.cos(), 8.0 *a.sin())),
            &GREEN,
        ))?;

        Ok(())
    })

Out[47]:
In [48]:

    :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("3D Plotting", ("Arial", 20).into_font())?;

        let mut chart = ChartBuilder::on(&root)
            .build_cartesian_3d(-10.0..10.0, -10.0..10.0, -10.0..10.0)?;
        chart.with_projection(|mut p| {
            p.pitch = 0.7;
            p.yaw = 0.7;
            p.scale = 0.5;
            p.into_matrix() // build the projection matrix
        });

        chart.configure_axes().draw()?;

        // Draw a red circle parallel to XOZ panel
        chart.draw_series(LineSeries::new(
            (-314..314).map(|a| a as f64 / 100.0).map(|a| (8.0 * a.cos(), 0.0, 8.0 *a.sin())),
            &RED,
        ))?;
        // Draw a green circle parallel to YOZ panel
        chart.draw_series(LineSeries::new(
            (-314..314).map(|a| a as f64 / 100.0).map(|a| (0.0, 8.0 * a.cos(), 8.0 *a.sin())),
            &GREEN,
        ))?;

        Ok(())
    })

Out[48]:

### A 2D Guassian Distribution Example¶

In [124]:

    :dep plotters = { version = "^0.3.0", default_features = false, features = ["evcxr", "all_series", "all_elements"] }
    use plotters::prelude::*;
    fn pdf(x: f64, y: f64) -> f64 {
        const SDX: f64 = 0.1;
        const SDY: f64 = 0.1;
        const A: f64 = 5.0;
        let x = x as f64 / 10.0;
        let y = y as f64 / 10.0;
        A * (- x * x / 2.0 / SDX / SDX - y * y / 2.0 / SDY / SDY).exp()
    }
    evcxr_figure((640 * 2, 480), |root| {

        let root = root.titled("2D Guassian PDF", ("Arial", 20).into_font())?;

        let (left, right) = root.split_horizontally(640);

        for (pitch, yaw, area) in vec![(0.6, 0.3, left), (1.5707, 0.0, right)] {
            let mut chart = ChartBuilder::on(&area)
                .build_cartesian_3d(-3.0..3.0, 0.0..6.0, -3.0..3.0)?;
            chart.with_projection(|mut p| {
                p.pitch = pitch;
                p.yaw = yaw;
                p.scale = 0.7;
                p.into_matrix() // build the projection matrix
            });

            chart.configure_axes().draw()?;

            let series = (-15..15).map(|x| std::iter::repeat(x).zip(-15..15)).flatten().map(|(x, z)|{
                let x = x as f64 / 5.0;
                let z = z as f64 / 5.0;
               Polygon::new(vec![
                       (x, pdf(x,z), z),
                       (x + 0.2, pdf(x + 0.2,z), z),
                       (x + 0.2, pdf(x + 0.2,z + 0.2), z + 0.2),
                       (x, pdf(x,z + 0.2), z + 0.2),
                   ],
                   &HSLColor(240.0 / 360.0 - 240.0 / 360.0 * pdf(x,z) / 5.0,1.0,0.7))
            });

            chart.draw_series(series);
        }

        Ok(())
    })

Out[124]: