graphs
graphs
No description provided.
widget to display graphs not really sure what else to say heh
Hi @Aylur, it would be great if some sort of graphs would be supported. My ideal end goal is to have something like qtile's CPUGraph, but of course, with arbitrary values.
I'd like to help out, however, my knowledge of JS/TS is basically non-existent. I can try to rip out relevant parts from something else that draws, such as CircularProgress, but I'm sure my attempt would require a lot of cleanup.
Thanks a lot!
tbh I don't want to touch cairo
- because I just don't
- because I would have to port it to gtk4 eventually
Doing any graph widget like this is easy for anyone who knows cairo, JS/TS knowledge is not needed, since you can just copy the boilerplate from other subclasses and the only thing is implementing vfunc_draw
Since it would not make sense for graph widgets to have children it is just a matter of subclassing Gtk.DrawingArea
// this is how you can play with it in the config
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const Graph = (value) => Widget.DrawingArea({
setup(self) {
self.hook(value, () => self.queue_draw())
// you may have to set some size on it
self.set_size_request(100, 100);
self.connect('draw', (_, cr) => {
// draw implementation
// cr is the cairo.Context
}),
}
});
const value = Variable(0, { poll: [1000, Math.random] });
const graph = Graph(value);
If you feel like implementing this drawing function I can implement it as an ags subclass
Alright, that sounds like a good starting point. I'll try to get to it by the next week.
Welp, life happened, and here we are, a month later without anything from me. Sorry about that. If there is anyone who wants to take it up, feel free.
hello, can I work on this
In case anyone is interested, I built these two widgets for CPU and Memory (with the DrawingArea graph). Feel free to use/modify or ignore!
function Graph(value, history, color) {
return Widget.DrawingArea({
setup(self) {
let data = [];
self.hook(value, () => {
data.push(value.value);
if (data.length > history) {
data.shift();
}
self.queue_draw()
});
self.set_size_request(36, 12);
self.connect('draw', (_, cr) => {
let [width, height] = [self.get_allocated_width(), self.get_allocated_height()];
cr.scale(width, height);
cr.rectangle(0, 0, 1.0, 1.0);
cr.fill();
let samples = data.length - 1;
let max = 100.0;
cr.setSourceRGB(color[0], color[1], color[2]);
if (samples > 0) {
cr.moveTo(1.0, 1.0);
let x = 1.0, y = 1.0 - data[samples] / max;
cr.lineTo(x, y);
for (let j = samples - 1; j >= 0; j--) {
y = 1.0 - data[j] / max;
x = j / samples;
cr.lineTo(x, y);
}
cr.lineTo(x, 1.0);
cr.closePath();
cr.fill();
}
cr.$dispose();
});
}
})
}
let cpumonitor;
class CpuMonitor {
constructor(interval, history, color) {
this.cpu_load = Variable(0);
this.prev_idle_time = 0;
this.prev_total_time = 0;
this.label = Widget.Label({
label: this.cpu_load.bind().as(value => value.toFixed(0).toString() + "%"),
});
this.drawing = Graph(this.cpu_load, history, color);
this.button = Widget.Button({
child: this.drawing,
on_clicked: () => Utils.execAsync("kitty btop --utf-force"),
on_secondary_click: () => Utils.execAsync("kitty nvtop"),
on_hover: () => {
//top -d 0.2 -n 2 -b -o=-%CPU | tail -10 | tac | awk '{printf "%7.1f %s\n", $9 ,$12}'
this.button.tooltip_text = Utils.exec("bash -c \"top -d 0.2 -n 2 -b -o=-%CPU | tail -10 | tac | awk '{printf \\\"%7.1f %s\\n\\\", \$9, \$12}'\"");
}
});
this.box = Widget.Box({
class_name: "cpu-monitor",
children: [ this.label, this.button ]
});
const id = Utils.interval(interval, () => {
const stats = Utils.readFile("/proc/stat");
const times = stats.split('\n', 1)[0].split(/\s+/);
times.shift();
const [
user, nice, system, idle, iowait,
irq, softirq, steal, guest, guest_nice,
] = times.map(s => {
let v = parseInt(s);
return v;
});
let total = user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice;
const idle_time_delta = idle - this.prev_idle_time;
this.prev_idle_time = idle;
const total_time_delta = total - this.prev_total_time;
this.prev_total_time = total;
const utilization =
100.0 * (1.0 - idle_time_delta / total_time_delta);
this.cpu_load.setValue(Math.round(utilization));
}, this.box);
}
get_ui() {
return this.box;
}
}
function CpuGraph() {
if (!cpumonitor) {
cpumonitor = new CpuMonitor(2000, 8, [0.0, 0.57, 0.9]);
}
return cpumonitor.get_ui();
}
let memmonitor;
class MemMonitor {
constructor(interval, history, color) {
this.mem_load = Variable(0);
this.mem_available = 0;
this.mem_total = 0;
this.label = Widget.Label({
label: this.mem_load.bind().as(value => value.toFixed(0).toString() + "%"),
});
this.drawing = Graph(this.mem_load, history, color);
this.button = Widget.Button({
child: this.drawing,
on_clicked: () => Utils.execAsync("kitty btop --utf-force"),
on_secondary_click: () => Utils.execAsync("kitty nvtop"),
on_hover: () => {
let used = (this.mem_total - this.mem_available) / (1024 * 1024);
let result = used.toFixed(1).toString() + " GB used\n\n";
result += Utils.exec("bash -c \"ps -eo rss,comm --sort -rss --no-headers | head -n 10 | numfmt --to-unit=1024 --field 1 --padding 5\"");
//this.button.tooltip_text = Utils.exec("bash -c \"top -d 0.2 -n 2 -b -o=-RES | tail -10 | tac | awk '{printf \\\"%8s %s\\n\\\", \$6, \$12}'\"");
this.button.tooltip_text = result;
}
});
this.box = Widget.Box({
class_name: "mem-monitor",
children: [ this.label, this.button ]
});
const id = Utils.interval(interval, () => {
this.mem_available = 0;
this.mem_total = 0;
const stats = Utils.readFile("/proc/meminfo").split('\n');
for (let stat of stats) {
const memory = stat.split(/\s+/);
if (memory[0] == "MemTotal:") {
this.mem_total = parseInt(memory[1]);
} else if (memory[0] == "MemAvailable:") {
this.mem_available = parseInt(memory[1]);
}
if (this.mem_available > 0 && this.mem_total > 0)
break;
}
const utilization = (100.0 * (this.mem_total - this.mem_available)) / this.mem_total;
this.mem_load.setValue(Math.round(utilization));
}, this.box);
}
get_ui() {
return this.box;
}
}
function MemGraph() {
if (!memmonitor) {
memmonitor = new MemMonitor(5000, 8, [0.0, 0.7, 0.36]);
}
return memmonitor.get_ui();
}
@dawsers would you mind giving an example how to activate your graphs in AGS's bar ?
Sorry if it's an obvious question, started using AGS only yesterday :D
They are just regular Widgets you need to add to your Bar, and then your Bar to your App.
This is a part of my configuration where you can see the relevant parts. Remove the rest:
// layout of the bar
function Left() {
return Widget.Box({
spacing: 6,
setup(self) {
self.pack_start(Workspaces(), false, false, 0);
self.pack_start(SubMap(), false, false, 0);
self.pack_start(ClientTitle(), false, false, 0);
self.pack_end(MediaPlayerBox(), false, false, 0);
}
})
}
function Right() {
return Widget.Box({
spacing: 6,
setup(self) {
self.pack_start(PackageUpdates(), false, false, 0);
self.pack_start(IdleInhibitor(), false, false, 0);
self.pack_start(KeyboardLayout(), false, false, 0);
self.pack_end(SysTray(), false, false, 0);
self.pack_end(Notifications(), false, false, 0);
self.pack_end(MicrophoneIndicator(), false, false, 0);
self.pack_end(SpeakerIndicator(), false, false, 0);
self.pack_end(NetworkIndicator(), false, false, 0);
self.pack_end(GpuGraph(), false, false, 0);
self.pack_end(MemGraph(), false, false, 0);
self.pack_end(CpuGraph(), false, false, 0);
self.pack_end(Screenshot(), false, false, 0);
self.pack_end(Weather(1200000), false, false, 0);
}
})
}
function Bar(monitor = 0) {
if (monitor == 0) {
return Widget.Window({
name: `bar-${monitor}`, // name has to be unique
class_name: "bar",
monitor,
anchor: ["top", "left", "right"],
exclusivity: "exclusive",
layer: "top",
child: Widget.Box({
setup(self) {
self.pack_start(Left(), true, true, 0);
self.pack_end(Right(), true, true, 0);
self.set_center_widget(Clock(monitor));
},
}),
});
} else {
return Widget.Window({
name: `bar-${monitor}`, // name has to be unique
class_name: "bar",
monitor,
anchor: ["top", "left", "right"],
exclusivity: "exclusive",
layer: "top",
child: Widget.CenterBox({
start_widget: Workspaces(),
center_widget: Clock(monitor),
}),
});
}
}
App.config({
style: "./style.css",
windows: [
NotificationPopups(),
Bar(0),
Bar(1)
],
})
And then add the relevant style.css
@define-color wb-green #00b35b;
@define-color wb-blue #0092e6;
.cpu-monitor {
color: @wb-blue;
margin-top: 6px;
margin-bottom: 6px;
padding-right: 0;
/* avoid moving the rest when digit changes from 1 to anything else */
min-width: 80px;
}
.mem-monitor {
color: @wb-green;
padding-left: 0;
padding-right: 0px;
margin-top: 6px;
margin-bottom: 6px;
min-width: 80px;
}
Of course you also need to have the dependencies installed in your system: btop etc.