// Bounding boxes of marks and items (vega-scenegraph `bound/*` and the
// `bound` function of every mark type).
///|
let deg_to_rad : Double = 3.141592653589793 / 180.0
///|
/// vega `boundStroke`: grow by the stroke width when the item is stroked.
pub fn bound_stroke(bounds : Bounds, item : Item) -> Bounds {
let stroked = item.truthy("stroke") &&
!(item.get("opacity") is Num(0.0)) &&
!(item.get("strokeOpacity") is Num(0.0))
if stroked {
bounds.expand(item.num_or("strokeWidth", 1.0))
} else {
bounds
}
}
///|
fn nan_to_zero(x : Double) -> Double {
if x.is_nan() || x == 0.0 {
0.0
} else {
x
}
}
///|
fn bound_rect(bounds : Bounds, item : Item) -> Bounds {
let x = item.num0("x")
let y = item.num0("y")
let x2 = nan_to_zero(x + item.get("width").to_number())
let y2 = nan_to_zero(y + item.get("height").to_number())
bound_stroke(bounds.set(x, y, x2, y2), item)
}
///|
fn bound_rule(bounds : Bounds, item : Item) -> Bounds {
let x1 = item.num0("x")
let y1 = item.num0("y")
let x2 = match item.opt_num("x2") {
Some(v) => v
None => x1
}
let y2 = match item.opt_num("y2") {
Some(v) => v
None => y1
}
bound_stroke(bounds.set(x1, y1, x2, y2), item)
}
///|
fn bound_text(bounds : Bounds, item : Item) -> Bounds {
let mut h = font_size(item)
let x = item.num0("x")
let y = item.num0("y")
let mut dx = item.num0("dx")
let dy = item.num0("dy") + baseline_offset(item) - @util.js_round(0.8 * h)
let w = match text_lines(item) {
Lines(lines) => {
h = h + line_height(item) * (lines.length() - 1).to_double()
let mut m = 0.0
for l in lines {
m = @util.js_max(m, text_width(item, l))
}
m
}
Single(v) => text_width(item, v)
}
match item.str("align") {
Some("center") => dx = dx - w / 2.0
Some("right") => dx = dx - w
_ => ()
}
dx = dx + x
let top = dy + y
ignore(bounds.set(dx, top, dx + w, top + h))
let angle = item.num0("angle")
if angle != 0.0 {
ignore(bounds.rotate(angle * deg_to_rad, x, y))
}
bounds
}
///|
fn bound_symbol(bounds : Bounds, item : Item) -> Bounds raise SceneError {
let ctx = Bound(BoundCtx::new(bounds, deg=item.num0("angle")))
draw_item_symbol(ctx, item)
bound_stroke(bounds, item).translate(item.num0("x"), item.num0("y"))
}
///|
pub fn bound_group(bounds : Bounds, group : Item) -> Bounds {
let clip = group.truthy("clip")
if !clip {
for m in group.children {
ignore(bounds.union(m.bounds))
}
}
if (clip || group.truthy("width") || group.truthy("height")) &&
!group.truthy("noBound") {
ignore(bounds.add(0.0, 0.0).add(group.num0("width"), group.num0("height")))
}
ignore(bound_stroke(bounds, group))
bounds.translate(group.num0("x"), group.num0("y"))
}
///|
/// Compute bounds bottom-up for a mark and everything below it.
pub fn bound_mark(mark : Mark) -> Unit raise SceneError {
match mark.marktype {
Line | Area => {
ignore(mark.bounds.clear())
if mark.items.length() > 0 {
let ctx = Bound(BoundCtx::new(mark.bounds))
if mark.marktype is Line {
draw_line(ctx, mark.items)
} else {
draw_area(ctx, mark.items)
}
ignore(bound_stroke(mark.bounds, mark.items[0]))
}
}
_ => {
ignore(mark.bounds.clear())
for item in mark.items {
ignore(item.bounds.clear())
match mark.marktype {
Group => {
for child in item.children {
bound_mark(child)
}
ignore(bound_group(item.bounds, item))
}
Rect => ignore(bound_rect(item.bounds, item))
Rule => ignore(bound_rule(item.bounds, item))
Text => ignore(bound_text(item.bounds, item))
Symbol => ignore(bound_symbol(item.bounds, item))
_ => ()
}
ignore(mark.bounds.union(item.bounds))
}
}
}
}