tiny.zen.diagram.bounds
Defined in diagram.
API (7)
Actions
Public operations.
Types and contracts
Public types and contracts.
Source
Source: lib/zen/src/diagram/bounds.zig
zig
const std = @import("std");const category = @import("category.zig");const domain = @import("domain.zig");const model = @import("model.zig");const root = @import("root.zig");const stack = @import("stack.zig");const spec = root.spec;pub const Bounds = model.Bounds;pub const StackTotal = struct { x: []const u8, positive: f64 = 0, negative: f64 = 0,};const StackSpan = struct { start: f64, end: f64,};pub const ScratchPlan = struct { categories: usize, stack_totals: usize, pub fn inspect(document: *const spec.Document) ScratchPlan { var categories: usize = 0; var stack_totals: usize = 0; const stacked = stack.has(document); for (document.marks.items, 0..) |mark, index| { if (categoryValue(mark)) |label_value| { if (!hasCategory(document.marks.items[0..index], label_value)) categories += 1; } if (stacked) switch (mark) { .bar => |bar| if (!hasBar(document.marks.items[0..index], bar.x)) { stack_totals += 1; }, else => {}, }; } return .{ .categories = categories, .stack_totals = stack_totals, }; }};pub fn compute(allocator: std.mem.Allocator, document: *const spec.Document) !Bounds { const plan = ScratchPlan.inspect(document); var bounds = Bounds{}; errdefer bounds.deinit(allocator); try bounds.categories.ensureTotalCapacityPrecise(allocator, plan.categories); var stack_totals: std.ArrayList(StackTotal) = .empty; defer stack_totals.deinit(allocator); try stack_totals.ensureTotalCapacityPrecise(allocator, plan.stack_totals); return computePrepared(&bounds, &stack_totals, document);}pub fn computeIn( categories: [][]const u8, stack_totals: []StackTotal, document: *const spec.Document,) !Bounds { const plan = ScratchPlan.inspect(document); std.debug.assert(categories.len >= plan.categories); std.debug.assert(stack_totals.len >= plan.stack_totals); var bounds = Bounds{ .categories = .{ .items = categories[0..0], .capacity = categories.len, }, .category_slots_owned = false, }; var totals = std.ArrayList(StackTotal){ .items = stack_totals[0..0], .capacity = stack_totals.len, }; return computePrepared(&bounds, &totals, document);}fn computePrepared( bounds: *Bounds, stack_totals: *std.ArrayList(StackTotal), document: *const spec.Document,) !Bounds { bounds.x_kind = document.scales.x.kind; bounds.y_kind = document.scales.y.kind; bounds.x_base = document.scales.x.base; bounds.y_base = document.scales.y.base; const x_min = document.scales.x.min orelse document.frame.x_min; const x_max = document.scales.x.max orelse document.frame.x_max; const y_min = document.scales.y.min orelse document.frame.y_min; const y_max = document.scales.y.max orelse document.frame.y_max; bounds.y_min = y_min orelse if (bounds.y_kind == .log) 1 else 0; bounds.y_max = y_max orelse if (bounds.y_kind == .log) 10 else 1; bounds.x_min = x_min orelse if (bounds.x_kind == .log) 1 else 0; bounds.x_max = x_max orelse if (bounds.x_kind == .log) 10 else 1; var saw_y = y_min != null or y_max != null; var saw_x = x_min != null or x_max != null; const stack_bars = stack.has(document); if (stack_bars and bounds.y_kind == .log) return error.InvalidScale; for (document.marks.items) |mark| switch (mark) { .bar => |bar| { bounds.categorical = true; category.appendAssumeCapacity(bounds, bar.x); if (stack_bars) { const span = advanceStack(stack_totals, bar.x, bar.y); includeY(bounds, &saw_y, span.start); includeY(bounds, &saw_y, span.end); } else { includeY(bounds, &saw_y, bar.y); if (bounds.y_kind == .linear) includeY(bounds, &saw_y, 0); } }, .point => |point| { includeXValue(bounds, &saw_x, point.x); includeY(bounds, &saw_y, point.y); }, .rule => |rule| { includeX(bounds, &saw_x, rule.x1); includeX(bounds, &saw_x, rule.x2); includeY(bounds, &saw_y, rule.y1); includeY(bounds, &saw_y, rule.y2); }, .text => |text| { includeXValue(bounds, &saw_x, text.x); includeY(bounds, &saw_y, text.y); }, .box => |box| { includeX(bounds, &saw_x, box.x - box.width / 2); includeX(bounds, &saw_x, box.x + box.width / 2); includeY(bounds, &saw_y, box.y - box.height / 2); includeY(bounds, &saw_y, box.y + box.height / 2); }, .edge => |edge| { includeX(bounds, &saw_x, edge.x1); includeX(bounds, &saw_x, edge.x2); includeY(bounds, &saw_y, edge.y1); includeY(bounds, &saw_y, edge.y2); }, }; if (!saw_y) { bounds.y_min = if (bounds.y_kind == .log) 1 else 0; bounds.y_max = if (bounds.y_kind == .log) 10 else 1; } if (!saw_x) { bounds.x_min = if (bounds.x_kind == .log) 1 else 0; bounds.x_max = if (bounds.x_kind == .log) 10 else 1; } domain.expand(&bounds.y_min, &bounds.y_max, bounds.y_kind, bounds.y_base); domain.expand(&bounds.x_min, &bounds.x_max, bounds.x_kind, bounds.x_base); try domain.validate(bounds.*); return bounds.*;}fn advanceStack( totals: *std.ArrayList(StackTotal), x: []const u8, y: f64,) StackSpan { for (totals.items) |*total| { if (!std.mem.eql(u8, total.x, x)) continue; return advanceTotal(total, y); } totals.appendAssumeCapacity(.{ .x = x }); return advanceTotal(&totals.items[totals.items.len - 1], y);}fn advanceTotal(total: *StackTotal, y: f64) StackSpan { if (y >= 0) { const start = total.positive; total.positive += y; return .{ .start = start, .end = total.positive }; } const start = total.negative; total.negative += y; return .{ .start = start, .end = total.negative };}fn includeXValue(bounds: *Bounds, saw_x: *bool, value: spec.XValue) void { switch (value) { .number => |number| includeX(bounds, saw_x, number), .text => |text| { bounds.categorical = true; category.appendAssumeCapacity(bounds, text); }, }}fn includeX(bounds: *Bounds, saw_x: *bool, value: f64) void { if (!saw_x.*) { bounds.x_min = value; bounds.x_max = value; saw_x.* = true; return; } bounds.x_min = @min(bounds.x_min, value); bounds.x_max = @max(bounds.x_max, value);}fn includeY(bounds: *Bounds, saw_y: *bool, value: f64) void { if (!saw_y.*) { bounds.y_min = value; bounds.y_max = value; saw_y.* = true; return; } bounds.y_min = @min(bounds.y_min, value); bounds.y_max = @max(bounds.y_max, value);}fn categoryValue(mark: spec.Mark) ?[]const u8 { return switch (mark) { .bar => |bar| bar.x, .point => |point| switch (point.x) { .text => |text| text, .number => null, }, .text => |text_mark| switch (text_mark.x) { .text => |text| text, .number => null, }, else => null, };}fn hasCategory(marks: []const spec.Mark, label_value: []const u8) bool { for (marks) |mark| { const candidate = categoryValue(mark) orelse continue; if (std.mem.eql(u8, candidate, label_value)) return true; } return false;}fn hasBar(marks: []const spec.Mark, x: []const u8) bool { for (marks) |mark| switch (mark) { .bar => |bar| if (std.mem.eql(u8, bar.x, x)) return true, else => {}, }; return false;}pub fn barBaseline(bounds: Bounds) f64 { return if (bounds.y_kind == .log) bounds.y_min else 0;}test "bounds scratch plan counts unique categories and stacked totals" { const marks = [_]spec.Mark{ .{ .bar = .{ .x = @constCast("a"), .y = 1 } }, .{ .bar = .{ .x = @constCast("a"), .y = 2 } }, .{ .point = .{ .x = .{ .text = @constCast("b") }, .y = 3 } }, }; var transforms = [_]spec.Transform{ .{ .stack = .{} }, }; var document = spec.Document{ .allocator = std.testing.allocator, .marks = .{ .items = @constCast(&marks), .capacity = marks.len }, .transforms = .{ .items = &transforms, .capacity = transforms.len }, }; const plan = ScratchPlan.inspect(&document); try std.testing.expectEqual(@as(usize, 2), plan.categories); try std.testing.expectEqual(@as(usize, 1), plan.stack_totals);}Source: lib/zen/src/diagram/root.zig:2
zig
pub const bounds = @import("bounds.zig");Audit
| Definitions | 7 |
|---|---|
| Public names | 7 |
| Members | 5 |
| Version | 26.7.0 |
| Revision | daab053ee433 |