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.
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.
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:
- For an introduction of Plotters, see: Plotters on Crates.io;
- Check the main repository on GitHub;
- You can also visit the Plotters homepage;
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.
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();
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.
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
.
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.
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!
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.