///|
// Two-axis constraints are used only when a flowchart authors local edge hints.
fn directional_offsets(
  members : ArrayView[Int32],
  links : ArrayView[(Int32, Int32, Int32, @model.Direction)],
  sizes : ArrayView[(Double, Double)],
  direction : @model.Direction,
  gap : Double,
  budget : Budget,
) -> (Map[Int32, Point], Double, Double) {
  let xs = []
  let ys = []
  for (a, b, length, dir) in links {
    if !budget.charge(1) {
      return (Map([]), 0.0, 0.0)
    }
    match dir {
      LR => xs.push((a, b, length))
      RL => xs.push((b, a, length))
      TB => ys.push((a, b, length))
      BT => ys.push((b, a, length))
    }
  }
  let xr = weighted_ranks(members, xs, budget, break_cycles=true)
  let yr = weighted_ranks(members, ys, budget, break_cycles=true)
  let buckets : Map[(Int32, Int32), Int32] = Map([])
  let mut largest = 1
  let mut w = 0.0
  let mut h = 0.0
  for id in members {
    if !budget.charge(1) {
      return (Map([]), 0.0, 0.0)
    }
    let key = (xr.get(id).unwrap_or(0), yr.get(id).unwrap_or(0))
    let count = buckets.get(key).unwrap_or(0) + 1
    buckets[key] = count
    largest = Int32::max(largest, count)
    w = Double::max(w, sizes[id].0)
    h = Double::max(h, sizes[id].1)
  }
  let horizontal = horizontal(direction)
  let stride_x = (w + gap) *
    (if horizontal { 1.0 } else { largest.to_double() })
  let stride_y = (h + gap) *
    (if horizontal { largest.to_double() } else { 1.0 })
  let taken : Map[(Int32, Int32), Int32] = Map([])
  let positions = Map([])
  let mut width = 0.0
  let mut height = 0.0
  for id in members {
    if !budget.charge(1) {
      return (Map([]), 0.0, 0.0)
    }
    let key = (xr.get(id).unwrap_or(0), yr.get(id).unwrap_or(0))
    let ordinal = taken.get(key).unwrap_or(0)
    taken[key] = ordinal + 1
    let point : Point = {
      x: key.0.to_double() * stride_x +
      (if horizontal { 0.0 } else { ordinal.to_double() * (w + gap) }),
      y: key.1.to_double() * stride_y +
      (if horizontal { ordinal.to_double() * (h + gap) } else { 0.0 }),
    }
    positions[id] = point
    width = Double::max(width, point.x + sizes[id].0)
    height = Double::max(height, point.y + sizes[id].1)
  }
  (positions, width, height)
}