13 releases

0.3.12 Jan 8, 2023
0.3.11 Feb 20, 2021
0.3.10 Nov 17, 2020
0.0.0 Nov 10, 2020

#55 in Visualization

22 downloads per month

MIT license

95KB
2K SLoC

C++ 1K SLoC // 0.2% comments Rust 708 SLoC // 0.2% comments

plotters-conrod

Test and Build Build and Release Buy Me A Coffee

This is an implementation of a Conrod backend for Plotters. This is more efficient than using the default Bitmap backend when plotting in Conrod, as it has been observed that Conrod was quite inefficient at re-rendering images at high FPS (eg. for real-time plotting).

This backend has been optimized as for speed, and as to render plots that look very similar to the default Bitmap backend, if not indistinguishable. Note that some specific plotting features supported in the Bitmap backend may not be implemented there, though.

đŸ‡Ģ🇷 Crafted in Nantes, France.

Who uses it?

MakAir
  • 👋 You use plotters-conrod and you want to be listed there? Contact me.
  • ℹī¸ The MakAir open-source medical ventilator uses plotters-conrod on its Rust-based MakAir Control UI.

What is Plotters?

Plotters is an extensible Rust drawing library that can be used to plot data on nice-looking graphs, rendering them through a plotting backend (eg. to a Bitmap image raw buffer, to your GUI backend, to an SVG file, etc.).

For more details on Plotters, please check the following links:

How to install?

Include plotters-conrod in your Cargo.toml dependencies:

[dependencies]
plotters-conrod = "0.3"

The plotters-conrod version used should match your plotters version. If there is no such plotters-conrod version yet, using an older plotters-conrod version than your plotters should usually work.

How to use?

First, import ConrodBackend and ConrodBackendReusableGraph:

use plotters_conrod::{ConrodBackend, ConrodBackendReusableGraph};

Then, build the re-usable graph instance (outside of your drawing loop):

let mut conrod_graph = ConrodBackendReusableGraph::build();

⚠ī¸ This should be put outside of your loop and called once; failing to do so will result in heavy CPU usage due to the graph being rebuilt for every frame!

Finally, for each frame you draw (ie. your main loop), call:

// Where:
//  - 'ui' is the UiCell that was derived from Ui for this frame;
//  - '(plot_width, plot_height)' is the size of your plot in pixels (make sure it matches its parent canvas size);
//  - 'ids.parent' is the widget::Id of the canvas that contains your plot (of the same size than the plot itself);
//  - 'fonts.regular' is the font::Id of the font to use to draw text (ie. a Conrod font identifier);
//  - 'conrod_graph' is a mutable reference to the graph instance you built outside of the drawing loop (pass it as a mutable reference);
let drawing = ConrodBackend::new(
    ui,
    (plot_width, plot_height),
    ids.parent,
    fonts.regular,
    &mut conrod_graph,
).into_drawing_area();

//-
// Build your chart as usual here, using the regular Plotters syntax
//-

If you are looking for a full example of an implementation, please check cpu-monitor.rs.

How to run the examples?

Example #1: cpu-monitor

This example samples your CPU load every second, and renders it in a real-time chart:

cargo run --release --example cpu-monitor

The first plot uses plotters-conrod, while the second plot uses the default Bitmap backend as a reference. This can be used to compare the output and performance of both plotting backends. The Bitmap reference plot can be disabled by setting REFERENCE_BITMAP_ENABLED to false.

How lightweight is it compared to other backends?

The plotters-conrod backend was designed to perform all expensive computational work on the GPU, rather than on the CPU. This is a much more efficient, especially for large plot draw areas (in pixels).

While the default Bitmap backend rasterizer would only use the CPU (quite heavily at high FPS on real-time plots), this Conrod backend sends most of its work over to the GPU, as it uses OpenGL primitives to draw shapes.

Measurements have been made by running this example comparing the Bitmap backend with the Conrod backend, on a 2019 MacBook Pro laptop, running a 2,3GHz 8-Core Intel Core i9 CPU with a Radeon Pro 560X 4GB GPU. The example was compiled in --release mode, with all CPU optimizations enabled.

Those are the measurements results for a 800x480 pixels plot at 30 FPS:

  • ➡ī¸ Bitmap backend: CPU ~31% stable, GPU ~10% mean (the CPU draws, then the GPU renders the bitmap on its framebuffer);
  • ➡ī¸ Conrod backend: CPU ~4% stable, GPU ~15% mean (the CPU passes data, the GPU draws);

Memory usage is about the same for both backends.

Are there any limitations?

Limitation #1: No pixel-by-pixel rendering

As Conrod is known to be quite inefficient at rendering image widgets at any high-enough FPS (the likely cause is that it bypasses the GPU and does heavy CPU processing work), it was chosen to ignore the rendering of pixel primitives.

The default Plotters rasterizer has been disabled in that case, as to avoid rendering performance to be degraded without the library user noticing. This guarantees that the GPU is used for rendering, while the CPU does minimal work.

It means that, some complex plot types may not render well. Though, rest assured that common plot types have been tested to render exactly as expected, eg. LineSeries or Histogram.

There are plans to implement those pixel-based rendering methods in the future. If you already have an implementation, feel free to PR this library!

Limitation #2: Limited text rendering

Only a single font family (ie. serif, sans-serif, etc.) and a single font style (ie. regular, bold, etc.) are supported for text rendering. The reason is that Conrod makes it quite tedious to load fonts and pass them over, so we better off limit the backend API to a single font for simplicity's sake. As well, font transforms are not supported due to the underlying Conrod renderer, which does not seem to support text rotations.

Dependencies

~6MB
~111K SLoC