///|
/// Build the root group, run bounds and layout, and size the view.
fn assemble(
  spec : @spec.Spec,
  axes : Array[Axis],
  marks : Array[@scene.Mark],
  legends : Array[Legend],
  title : Title?,
  width : Double,
  height : Double,
) -> Compiled raise CompileError {
  let root = mk_item([
    ("fill", str("transparent")),
    ("stroke", str("#ddd")),
    ("x", num(0.0)),
    ("y", num(0.0)),
    ("width", num(width)),
    ("height", num(height)),
  ])
  try {
    for m in marks {
      @scene.bound_mark(m)
    }
  } catch {
    @scene.SceneError(m) => raise CompileError(m)
  }
  for a in axes {
    bound_axis(a)
  }
  let view = @scene.Bounds::new()
  ignore(view.set(0.0, 0.0, width, height))
  let xb = view.copy()
  let yb = view.copy()
  for a in axes {
    let b = axis_layout(a, width, height)
    if a.orient == "left" || a.orient == "right" {
      ignore(xb.union(b))
    } else {
      ignore(yb.union(b))
    }
  }
  for m in marks {
    ignore(view.union(m.bounds))
  }
  // legends: lay out each, then place them by orient next to the plot
  for l in legends {
    legend_layout(l)
  }
  for orient in ["left", "right", "top", "bottom"] {
    let group = legends.filter(fn(l) { l.orient == orient })
    if group.length() > 0 {
      place_legends(group, orient, xb, yb, 0.0, 0.0)
    }
  }
  for l in legends {
    ignore(view.union(legend_item(l).bounds))
  }
  ignore(view.union(xb).union(yb))
  match title {
    Some(t) => ignore(view.union(title_layout(t, width, view)))
    None => ()
  }
  for a in axes {
    root.children.push(a.mark)
  }
  for m in marks {
    root.children.push(m)
  }
  for l in legends {
    root.children.push(l.mark)
  }
  match title {
    Some(t) => root.children.push(t.mark)
    None => ()
  }
  ignore(@scene.bound_group(root.bounds.clear(), root))
  let left = max(0.0, ceil(-view.x1))
  let top = max(0.0, ceil(-view.y1))
  let right = max(0.0, ceil(view.x2 - width))
  let bottom = max(0.0, ceil(view.y2 - height))
  let frame = @scene.Mark::new(Group, name="root", role="frame", items=[root])
  ignore(frame.bounds.union(root.bounds))
  let pad = match spec.padding {
    Some(p) => p.to_number()
    None => 5.0
  }
  {
    scene: frame,
    width,
    height,
    origin: (left, top),
    padding: pad,
    view_width: width + left + right,
    view_height: height + top + bottom,
    background: match spec.background {
      Some(b) => Some(b)
      None => Some("white")
    },
  }
}