upgrade graph drawing
This commit is contained in:
parent
45f659cdd7
commit
24895afce6
@ -36,6 +36,7 @@ pub fn build(b: *std.Build) !void {
|
|||||||
|
|
||||||
const resource_file = b.addWriteFiles();
|
const resource_file = b.addWriteFiles();
|
||||||
|
|
||||||
|
// https://www.ryanliptak.com/blog/zig-is-a-windows-resource-compiler/
|
||||||
// TODO: Generate icon file at build time
|
// TODO: Generate icon file at build time
|
||||||
exe.addWin32ResourceFile(.{
|
exe.addWin32ResourceFile(.{
|
||||||
.file = resource_file.add("daq-view.rc", "IDI_ICON ICON \"./src/assets/icon.ico\""),
|
.file = resource_file.add("daq-view.rc", "IDI_ICON ICON \"./src/assets/icon.ico\""),
|
||||||
|
276
src/main.zig
276
src/main.zig
@ -14,6 +14,8 @@ const Allocator = std.mem.Allocator;
|
|||||||
const FontFace = @import("font-face.zig");
|
const FontFace = @import("font-face.zig");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Vec2 = rl.Vector2;
|
const Vec2 = rl.Vector2;
|
||||||
|
const Rect = rl.Rectangle;
|
||||||
|
const clamp = std.math.clamp;
|
||||||
|
|
||||||
const icon_png = @embedFile("./assets/icon.png");
|
const icon_png = @embedFile("./assets/icon.png");
|
||||||
|
|
||||||
@ -58,7 +60,7 @@ fn raylibTraceLogCallback(logType: c_int, text: [*c]const u8, args: raylib_h.va_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remap(from_min: f32, from_max: f32, to_min: f32, to_max: f32, value: f32) f32 {
|
fn remap(comptime T: type, from_min: T, from_max: T, to_min: T, to_max: T, value: T) T {
|
||||||
const t = (value - from_min) / (from_max - from_min);
|
const t = (value - from_min) / (from_max - from_min);
|
||||||
return std.math.lerp(to_min, to_max, t);
|
return std.math.lerp(to_min, to_max, t);
|
||||||
}
|
}
|
||||||
@ -107,6 +109,169 @@ pub fn nanoToSeconds(ns: i128) f32 {
|
|||||||
return @as(f32, @floatFromInt(ns)) / std.time.ns_per_s;
|
return @as(f32, @floatFromInt(ns)) / std.time.ns_per_s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ViewRectangle = struct {
|
||||||
|
from: f32, // inclusive
|
||||||
|
to: f32, // exclusive
|
||||||
|
min_value: f64,
|
||||||
|
max_value: f64,
|
||||||
|
left_aligned: bool = true,
|
||||||
|
color: rl.Color = rl.Color.red,
|
||||||
|
dot_size: f32 = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
fn mapSampleX(draw_rect: rl.Rectangle, view_rect: ViewRectangle, index: f64) f64 {
|
||||||
|
return remap(
|
||||||
|
f64,
|
||||||
|
view_rect.from, view_rect.to,
|
||||||
|
draw_rect.x, draw_rect.x + draw_rect.width,
|
||||||
|
index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mapSampleY(draw_rect: rl.Rectangle, view_rect: ViewRectangle, sample: f64) f64 {
|
||||||
|
return remap(
|
||||||
|
f64,
|
||||||
|
view_rect.min_value, view_rect.max_value,
|
||||||
|
@floatCast(draw_rect.y + draw_rect.height), @floatCast(draw_rect.y),
|
||||||
|
sample
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mapSamplePointToGraph(draw_rect: rl.Rectangle, view_rect: ViewRectangle, index: f64, sample: f64) Vec2 {
|
||||||
|
return .{
|
||||||
|
.x = @floatCast(mapSampleX(draw_rect, view_rect, index)),
|
||||||
|
.y = @floatCast(mapSampleY(draw_rect, view_rect, sample))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clampIndex(value: f32, size: usize) f32 {
|
||||||
|
const size_f32: f32 = @floatFromInt(size);
|
||||||
|
return clamp(value, 0, size_f32);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clampIndexUsize(value: f32, size: usize) usize {
|
||||||
|
const size_f32: f32 = @floatFromInt(size);
|
||||||
|
return @intFromFloat(clamp(value, 0, size_f32));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drawGraph(draw_rect: rl.Rectangle, view_rect: ViewRectangle, samples: []const f64) void {
|
||||||
|
assert(view_rect.left_aligned); // TODO:
|
||||||
|
assert(view_rect.to > view_rect.from);
|
||||||
|
|
||||||
|
rl.drawRectangleLinesEx(draw_rect, 1, rl.Color.black);
|
||||||
|
|
||||||
|
if (view_rect.from > @as(f32, @floatFromInt(samples.len))) return;
|
||||||
|
if (view_rect.to < 0) return;
|
||||||
|
|
||||||
|
const sample_count = view_rect.to - view_rect.from;
|
||||||
|
const samples_per_column = sample_count / draw_rect.width;
|
||||||
|
|
||||||
|
|
||||||
|
const samples_threshold = 2;
|
||||||
|
if (samples_per_column >= samples_threshold) {
|
||||||
|
var i = clampIndex(view_rect.from, samples.len);
|
||||||
|
while (i < clampIndex(view_rect.to, samples.len)) : (i += samples_per_column) {
|
||||||
|
const color = view_rect.color;
|
||||||
|
|
||||||
|
const from_index = clampIndexUsize(i, samples.len);
|
||||||
|
const to_index = clampIndexUsize(i+samples_per_column, samples.len);
|
||||||
|
const column_samples = samples[from_index..to_index];
|
||||||
|
if (column_samples.len == 0) continue;
|
||||||
|
|
||||||
|
var column_min = column_samples[0];
|
||||||
|
var column_max = column_samples[0];
|
||||||
|
|
||||||
|
for (column_samples) |sample| {
|
||||||
|
column_min = @min(column_min, sample);
|
||||||
|
column_max = @max(column_max, sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = mapSampleX(draw_rect, view_rect, @floatFromInt(from_index));
|
||||||
|
const y_min = mapSampleY(draw_rect, view_rect, column_min);
|
||||||
|
const y_max = mapSampleY(draw_rect, view_rect, column_max);
|
||||||
|
|
||||||
|
if (column_samples.len == 1 or @abs(y_max - y_min) < 1) {
|
||||||
|
|
||||||
|
rl.drawLineV(
|
||||||
|
mapSamplePointToGraph(draw_rect, view_rect, i, samples[from_index]),
|
||||||
|
mapSamplePointToGraph(draw_rect, view_rect, i-1, samples[clampIndexUsize(i-1, samples.len-1)]),
|
||||||
|
rl.Color.blue
|
||||||
|
);
|
||||||
|
|
||||||
|
rl.drawLineV(
|
||||||
|
mapSamplePointToGraph(draw_rect, view_rect, i, samples[from_index]),
|
||||||
|
mapSamplePointToGraph(draw_rect, view_rect, i+1, samples[clampIndexUsize(i+1, samples.len-1)]),
|
||||||
|
rl.Color.blue
|
||||||
|
);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
rl.drawLineV(
|
||||||
|
.{ .x = @floatCast(x), .y = @floatCast(y_min) },
|
||||||
|
.{ .x = @floatCast(x), .y = @floatCast(y_max) },
|
||||||
|
color
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rl.beginScissorMode(
|
||||||
|
@intFromFloat(@round(draw_rect.x)),
|
||||||
|
@intFromFloat(@round(draw_rect.y)),
|
||||||
|
@intFromFloat(@round(draw_rect.width)),
|
||||||
|
@intFromFloat(@round(draw_rect.height))
|
||||||
|
);
|
||||||
|
defer rl.endScissorMode();
|
||||||
|
|
||||||
|
{
|
||||||
|
const from_index = clampIndexUsize(@floor(view_rect.from), samples.len);
|
||||||
|
const to_index = clampIndexUsize(@ceil(view_rect.to) + 1, samples.len);
|
||||||
|
|
||||||
|
for (from_index..(to_index-1)) |i| {
|
||||||
|
const from_point = mapSamplePointToGraph(draw_rect, view_rect, @floatFromInt(i), samples[i]);
|
||||||
|
const to_point = mapSamplePointToGraph(draw_rect, view_rect, @floatFromInt(i + 1), samples[i + 1]);
|
||||||
|
rl.drawLineV(from_point, to_point, view_rect.color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const from_index = clampIndexUsize(@ceil(view_rect.from), samples.len);
|
||||||
|
const to_index = clampIndexUsize(@ceil(view_rect.to), samples.len);
|
||||||
|
|
||||||
|
const min_circle_size = 0.5;
|
||||||
|
const max_circle_size = view_rect.dot_size;
|
||||||
|
var circle_size = remap(f32, samples_threshold, 0.2, min_circle_size, max_circle_size, samples_per_column);
|
||||||
|
circle_size = @min(circle_size, max_circle_size);
|
||||||
|
|
||||||
|
for (from_index..to_index) |i| {
|
||||||
|
const center = mapSamplePointToGraph(draw_rect, view_rect, @floatFromInt(i), samples[i]);
|
||||||
|
rl.drawCircleV(center, circle_size, view_rect.color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn readSamplesFromFile(allocator: Allocator, file: std.fs.File) ![]f64 {
|
||||||
|
try file.seekTo(0);
|
||||||
|
const byte_count = try file.getEndPos();
|
||||||
|
assert(byte_count % 8 == 0);
|
||||||
|
|
||||||
|
var samples = try allocator.alloc(f64, @divExact(byte_count, 8));
|
||||||
|
errdefer allocator.free(samples);
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
var buffer: [4096]u8 = undefined;
|
||||||
|
while (true) {
|
||||||
|
const count = try file.readAll(&buffer);
|
||||||
|
if (count == 0) break;
|
||||||
|
|
||||||
|
for (0..@divExact(count, 8)) |j| {
|
||||||
|
samples[i] = std.mem.bytesToValue(f64, &buffer[(j*8)..][0..8]);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
const start_time = std.time.nanoTimestamp();
|
const start_time = std.time.nanoTimestamp();
|
||||||
|
|
||||||
@ -160,7 +325,15 @@ pub fn main() !void {
|
|||||||
});
|
});
|
||||||
defer app.deinit();
|
defer app.deinit();
|
||||||
|
|
||||||
for (devices) |device| {
|
const example_samples1_file = try std.fs.cwd().openFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_I.bin", .{});
|
||||||
|
defer example_samples1_file.close();
|
||||||
|
const example_samples1 = try readSamplesFromFile(allocator, example_samples1_file);
|
||||||
|
defer allocator.free(example_samples1);
|
||||||
|
|
||||||
|
//for (devices) |device| {
|
||||||
|
{
|
||||||
|
const device = "Dev1";
|
||||||
|
|
||||||
if (try ni_daq.checkDeviceAIMeasurementType(device, .Voltage)) {
|
if (try ni_daq.checkDeviceAIMeasurementType(device, .Voltage)) {
|
||||||
const voltage_ranges = try ni_daq.listDeviceAIVoltageRanges(device);
|
const voltage_ranges = try ni_daq.listDeviceAIVoltageRanges(device);
|
||||||
assert(voltage_ranges.len > 0);
|
assert(voltage_ranges.len > 0);
|
||||||
@ -177,6 +350,7 @@ pub fn main() !void {
|
|||||||
.min_value = min_sample,
|
.min_value = min_sample,
|
||||||
.max_value = max_sample,
|
.max_value = max_sample,
|
||||||
});
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,9 +401,13 @@ pub fn main() !void {
|
|||||||
.font = rl.getFontDefault()
|
.font = rl.getFontDefault()
|
||||||
};
|
};
|
||||||
|
|
||||||
var zoom: f64 = 1.0;
|
var view_from: f32 = 0;
|
||||||
|
//var view_width: f32 = @floatCast(sample_rate * 20);
|
||||||
|
var view_width: f32 = 1400;//@floatFromInt(example_samples1.len);
|
||||||
|
|
||||||
while (!rl.windowShouldClose()) {
|
while (!rl.windowShouldClose()) {
|
||||||
|
const dt = rl.getFrameTime();
|
||||||
|
|
||||||
rl.beginDrawing();
|
rl.beginDrawing();
|
||||||
defer rl.endDrawing();
|
defer rl.endDrawing();
|
||||||
|
|
||||||
@ -247,54 +425,55 @@ pub fn main() !void {
|
|||||||
channel_samples.mutex.lock();
|
channel_samples.mutex.lock();
|
||||||
for (0.., channel_samples.samples) |channel_index, samples| {
|
for (0.., channel_samples.samples) |channel_index, samples| {
|
||||||
const channel = app.channels.items[channel_index];
|
const channel = app.channels.items[channel_index];
|
||||||
const min_sample: f32 = @floatCast(channel.min_sample);
|
|
||||||
const max_sample: f32 = @floatCast(channel.max_sample);
|
|
||||||
|
|
||||||
const max_visible_samples: u32 = @intFromFloat(sample_rate * 20);
|
drawGraph(
|
||||||
var shown_samples = samples.items;
|
rl.Rectangle{
|
||||||
if (shown_samples.len > max_visible_samples) {
|
.x = 20,
|
||||||
shown_samples = samples.items[(samples.items.len-max_visible_samples-1)..(samples.items.len-1)];
|
.y = 20,
|
||||||
}
|
.width = window_width - 40,
|
||||||
|
.height = window_height - 40
|
||||||
if (shown_samples.len >= 2) {
|
},
|
||||||
const color = channel.color; // rl.Color.alpha(channel.color, 1.5 / @as(f32, @floatFromInt(channels.items.len)));
|
.{
|
||||||
|
.from = view_from,
|
||||||
const samples_per_pixel = max_visible_samples / window_width;
|
.to = view_from + view_width,
|
||||||
|
.min_value = channel.min_sample,
|
||||||
var i: f32 = 0;
|
.max_value = channel.max_sample
|
||||||
while (i < @as(f32, @floatFromInt(shown_samples.len)) - samples_per_pixel) : (i += samples_per_pixel) {
|
},
|
||||||
const next_i = i + samples_per_pixel;
|
samples.items
|
||||||
|
);
|
||||||
var min_slice_sample = shown_samples[@intFromFloat(i)];
|
|
||||||
var max_slice_sample = shown_samples[@intFromFloat(i)];
|
|
||||||
|
|
||||||
for (@intFromFloat(i)..@intFromFloat(next_i)) |sub_i| {
|
|
||||||
min_slice_sample = @min(min_slice_sample, shown_samples[sub_i]);
|
|
||||||
max_slice_sample = @max(max_slice_sample, shown_samples[sub_i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const offset_i: f32 = @floatFromInt(max_visible_samples - shown_samples.len);
|
|
||||||
|
|
||||||
const start_pos = Vec2.init(
|
|
||||||
(offset_i + i) / max_visible_samples * window_width,
|
|
||||||
remap(min_sample, max_sample, 0, window_height, @as(f32, @floatCast(min_slice_sample)) * @as(f32, @floatCast(zoom)))
|
|
||||||
);
|
|
||||||
|
|
||||||
const end_pos = Vec2.init(
|
|
||||||
(offset_i + i) / max_visible_samples * window_width,
|
|
||||||
remap(min_sample, max_sample, 0, window_height, @as(f32, @floatCast(max_slice_sample)) * @as(f32, @floatCast(zoom)))
|
|
||||||
);
|
|
||||||
rl.drawLineV(start_pos, end_pos, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
channel_samples.mutex.unlock();
|
channel_samples.mutex.unlock();
|
||||||
|
|
||||||
if (rl.isKeyPressedRepeat(rl.KeyboardKey.key_e) or rl.isKeyPressed(rl.KeyboardKey.key_e)) {
|
// drawGraph(
|
||||||
zoom *= 1.1;
|
// rl.Rectangle{
|
||||||
|
// .x = 100,
|
||||||
|
// .y = 20,
|
||||||
|
// .width = window_width - 200,
|
||||||
|
// .height = window_height - 40
|
||||||
|
// },
|
||||||
|
// .{
|
||||||
|
// .from = view_from,
|
||||||
|
// .to = view_from + view_width,
|
||||||
|
// .min_value = -1,
|
||||||
|
// .max_value = 1
|
||||||
|
// },
|
||||||
|
// example_samples1
|
||||||
|
// );
|
||||||
|
|
||||||
|
const move_speed = view_width * 0.25;
|
||||||
|
if (rl.isKeyDown(.key_d)) {
|
||||||
|
view_from += move_speed * dt;
|
||||||
}
|
}
|
||||||
if (rl.isKeyPressedRepeat(rl.KeyboardKey.key_q) or rl.isKeyPressed(rl.KeyboardKey.key_q)) {
|
if (rl.isKeyDown(.key_a)) {
|
||||||
zoom *= 0.9;
|
view_from -= move_speed * dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const zoom_speed = 0.5;
|
||||||
|
if (rl.isKeyDown(.key_w)) {
|
||||||
|
view_width *= (1 - zoom_speed * dt);
|
||||||
|
}
|
||||||
|
if (rl.isKeyDown(.key_s)) {
|
||||||
|
view_width *= (1 + zoom_speed * dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rl.isKeyPressed(rl.KeyboardKey.key_f3)) {
|
if (rl.isKeyPressed(rl.KeyboardKey.key_f3)) {
|
||||||
@ -310,7 +489,10 @@ pub fn main() !void {
|
|||||||
try font_face.drawTextAlloc(allocator, "Time: {d:.03}", .{now_since_start}, Vec2.init(10, y), rl.Color.black);
|
try font_face.drawTextAlloc(allocator, "Time: {d:.03}", .{now_since_start}, Vec2.init(10, y), rl.Color.black);
|
||||||
y += 10;
|
y += 10;
|
||||||
|
|
||||||
try font_face.drawTextAlloc(allocator, "Zoom: {d:.03}", .{zoom}, Vec2.init(10, y), rl.Color.black);
|
try font_face.drawTextAlloc(allocator, "View from: {d:.03}", .{view_from}, Vec2.init(10, y), rl.Color.black);
|
||||||
|
y += 10;
|
||||||
|
|
||||||
|
try font_face.drawTextAlloc(allocator, "View width: {d:.03}", .{view_width}, Vec2.init(10, y), rl.Color.black);
|
||||||
y += 10;
|
y += 10;
|
||||||
|
|
||||||
try font_face.drawTextAlloc(allocator, "Dropped samples: {d:.03}", .{app.task_pool.droppedSamples()}, Vec2.init(10, y), rl.Color.black);
|
try font_face.drawTextAlloc(allocator, "Dropped samples: {d:.03}", .{app.task_pool.droppedSamples()}, Vec2.init(10, y), rl.Color.black);
|
||||||
|
Loading…
Reference in New Issue
Block a user