lib/zen/src/diagram/category.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 const model = @import("model.zig");
4 const root = @import("root.zig");
5
6 const spec = root.spec;
7 const Bounds = model.Bounds;
8
9 pub const label_buffer_bytes: usize = 512;
10
11 pub fn appendAssumeCapacity(bounds: *Bounds, label_value: []const u8) void {
12 for (bounds.categories.items) |item| {
13 if (std.mem.eql(u8, item, label_value)) return;
14 }
15 bounds.categories.appendAssumeCapacity(label_value);
16 }
17
18 pub fn label(buffer: *[label_buffer_bytes]u8, value: spec.XValue) []const u8 {
19 return switch (value) {
20 .number => |number| std.fmt.bufPrint(buffer, "{d:.3}", .{number}) catch unreachable,
21 .text => |text| text,
22 };
23 }
24
25 pub fn coordinate(label_value: []const u8, bounds: Bounds, left: f64, right: f64) f64 {
26 const count = @max(bounds.categories.items.len, 1);
27 const slot = (right - left) / @as(f64, @floatFromInt(count));
28 for (bounds.categories.items, 0..) |item, index| {
29 if (std.mem.eql(u8, item, label_value)) return left + slot * (@as(f64, @floatFromInt(index)) + 0.5);
30 }
31 return left + slot * 0.5;
32 }
33
34 test "category labels fit every finite f64 without allocation" {
35 var buffer: [label_buffer_bytes]u8 = undefined;
36 try std.testing.expect(label(&buffer, .{ .number = std.math.floatMax(f64) }).len < buffer.len);
37 try std.testing.expect(label(&buffer, .{ .number = -std.math.floatMax(f64) }).len < buffer.len);
38 try std.testing.expectEqualStrings("borrowed", label(&buffer, .{ .text = @constCast("borrowed") }));
39 }