plotters
plotters copied to clipboard
New example using GTK.rs and Plotters.rs (multithreaded real-time plotter)
Hi i have created a examples using GTK.rs and Plotters.rs. It's a real time plot using a thread to keep the GUI responsive at tany time. The thread starts when "START" button is clicked and is terminated when the STOP button is clicked.
The repo is there : https://github.com/Ouam74/-RUST-Real-time_plots_using_GTK.rs_and_Plotters.rs
Here is the code:
main.rs :
extern crate gtk;
extern crate glib;
extern crate gio;
extern crate cairo;
use plotters::prelude::*;
use std::env::args;
use gtk::prelude::*;
use gio::prelude::*;
use rand::Rng;
use std::{thread, time};
use std::time::Duration;
use std::sync::{Arc, Mutex};
fn build_ui(application: >k::Application){
let glade_src = include_str!("Plotter.glade");
let builder = gtk::Builder::from_string(glade_src);
let window: gtk::Window = builder.get_object("Window1").expect("Couldn't get window");
window.set_application(Some(application));
let startbutton: gtk::Button = builder.get_object("Startbutton").expect("Couldn't get StartButton");
let stopbutton: gtk::Button = builder.get_object("Stopbutton").expect("Couldn't get StopButton");
let drawingarea: gtk::DrawingArea = builder.get_object("Drawingarea").expect("Couldn't get Drawingarea");
// Create Plot ARGB32
let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, 1000, 600).expect("Can't create surface"); //build a new ImageSurface to draw on
let cr = cairo::Context::new(&surface); //build a new cairo context from that ImageSurface to draw on
// Signal / Slots
drawingarea.connect_draw(move|_, context| draw_fn(&context)); // plots drawn_fn into drawing_area
let abort = Arc::new(Mutex::new(0)); // allow `abort` to be shared across threads (Arc) and modified (this is a pointer)
let drawingarea_clone = drawingarea.clone();
let cr_clone = cr.clone();
let abort_clone = abort.clone(); // create a cloned reference before moving `abort` into the thread.
startbutton.connect_clicked(move|_| startclicked(&drawingarea_clone, &cr_clone, &abort_clone)); // connects startbutton click with function startclicked
let abort_clone = abort.clone();
stopbutton.connect_clicked(move|_| stopclicked(&abort_clone)); // connects startbutton click with function startclicked
window.show_all();
}
fn startclicked(drawingarea: >k::DrawingArea, cr: &cairo::Context, abort: &Arc<Mutex<i32>>){
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
*abort.lock().unwrap() = 0; // re-initialize "abort" to zero
// sender
thread::spawn(move || {
println!("THREAD STARTED!!!!!!");
loop { // for _i in 0..1001 {
thread::sleep(Duration::from_micros(10)); // time to get the datas
let val = 0.0; // println!("{:?}", _i);
tx.send("val").unwrap(); // send (emit) 'i' to channel, receiver will be run on the main thread
}
});
// Attach receiver to the main context and set the val from here
let drawingarea_clone = drawingarea.clone();
let abort_clone = abort.clone();
rx.attach(None, move |val| { // val is the received value that was sent by the tx
let abort = abort_clone.lock().unwrap();
if *abort == 0 {
drawingarea_clone.queue_draw(); // refresh drawingarea
glib::Continue(true)
}
else {
println!("THREAD TERMINATED!!!!!!");
glib::Continue(false)
}
});
}
fn stopclicked(abort: &Arc<Mutex<i32>>){
*abort.lock().unwrap() = 1; // this works --> let mut abort = abort.lock().unwrap(); *abort = 1; // set "abort" value to 1
}
fn createserie_iter() -> Vec<(f64, f64)> {
let mut ret = vec![];
let mut _rng = rand::thread_rng();
for j in 0..1000 {
ret.push( ( j as f64, _rng.gen::<f64>() ) );
}
ret
}
fn draw_fn(c: &cairo::Context) -> gtk::Inhibit { // plot data using plotters
let mut rng = rand::thread_rng();
let root = plotters_cairo::CairoBackend::new(&c, (600, 600)).unwrap().into_drawing_area();
root.fill(&WHITE).unwrap();
let mut chart = ChartBuilder::on(&root)
.margin(50)
.x_label_area_size(30)
.y_label_area_size(30)
.build_cartesian_2d(0.0f64..1000.0f64, 0.0f64..1.0f64)
.unwrap();
chart.configure_mesh()
.draw()
.unwrap();
let mut newserie = {
let mut current: Vec<(f64, f64)>;
current = createserie_iter();
current
};
chart.draw_series(LineSeries::new(newserie, &RED));
root.present();
Inhibit(false)
}
// fn draw_fn(cr: &cairo::Context) -> gtk::Inhibit { // plot data using cairo
// // paint canvas white
// cr.set_source_rgb(0.0, 0.0, 0.0);
// cr.paint();
//
// // draw 1000 random points
// cr.set_source_rgb(1.0, 1.0, 0.0);
// cr.set_line_width(1.0);
// for _i in 0..1000 {
// let x = (_i as f64) * 1.0;
// let y = rand::random::<f64>() * 600.0; // generate 1000 pts each time the draw_fn function is called
// cr.line_to(x, y);
// }
// cr.stroke();
// Inhibit(false)
// }
fn main() {
if gtk::init().is_err() {
println!("Failed to initialize GTK.");
return;
}
let application = gtk::Application::new(
Some("com.github.gtk-rs.examples.grid"),
Default::default(),
)
.expect("Initialization failed...");
application.connect_activate(|app|{
build_ui(app);
});
application.run(&args().collect::<Vec<_>>());
}
Plotter.glade:
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkListStore" id="liststore1">
<columns>
<!-- column-name gboolean1 -->
<column type="gboolean"/>
<!-- column-name gchar1 -->
<column type="gchar"/>
</columns>
</object>
<object class="GtkWindow" id="Window1">
<property name="visible">True</property>
<property name="events"></property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Test Olivier Pelhatre</property>
<property name="window_position">center</property>
<property name="default_width">500</property>
<property name="default_height">200</property>
<property name="opacity">0.5</property>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<child>
<object class="GtkButton" id="Startbutton">
<property name="label" translatable="yes">START</property>
<property name="width_request">100</property>
<property name="height_request">30</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="Stopbutton">
<property name="label" translatable="yes">STOP</property>
<property name="width_request">100</property>
<property name="height_request">30</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">5</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkDrawingArea" id="Drawingarea">
<property name="width_request">1000</property>
<property name="height_request">600</property>
<property name="visible">True</property>
</object>
<packing>
<property name="padding">10</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
"cargo.toml" :
[package] name = "GTK_Multithread2" version = "0.1.0" authors = [""] edition = "2018"
[dependencies] gtk = "" gio = "" gdk = "" glib = "^0" cairo = "" rand = "" plotters = "" plotters-cairo = "" cairo-rs = ""