// Path building. Shapes draw through the `PathCtx` interface, which has two
// implementations: `PathBuf` (SVG path text, as d3-path 3.1) and `BoundCtx`
// (bounding boxes, as vega-scenegraph's `boundContext`).
///|
let tau : Double = 6.283185307179586
///|
let half_pi : Double = 1.5707963267948966
///|
let epsilon : Double = 1.0e-6
///|
let tau_epsilon : Double = 6.283185307179586 - 1.0e-6
// ---- SVG path text ----
///|
/// d3-path's `Path`: appends SVG path commands; with `digits` coordinates are
/// rounded to that many decimals (d3-shape's default is 3, vega's own
/// rectangle shape uses full precision).
pub struct PathBuf {
out : StringBuilder
digits : Int32?
mut x0 : Double
mut y0 : Double
mut x1 : Double
mut y1 : Double
mut started : Bool
}
///|
pub fn PathBuf::new(digits? : Int32) -> PathBuf {
{
out: StringBuilder(),
digits,
x0: 0.0,
y0: 0.0,
x1: 0.0,
y1: 0.0,
started: false,
}
}
///|
fn PathBuf::num(self : PathBuf, x : Double) -> String {
match self.digits {
None => @util.js_number_to_string(x)
Some(d) => {
let mut k = 1.0
for _ in 0.. String {
self.out.to_string()
}
///|
pub fn PathBuf::move_to(self : PathBuf, x : Double, y : Double) -> Unit {
self.x0 = x
self.x1 = x
self.y0 = y
self.y1 = y
self.started = true
self.out.write_string("M" + self.num(x) + "," + self.num(y))
}
///|
pub fn PathBuf::close_path(self : PathBuf) -> Unit {
if self.started {
self.x1 = self.x0
self.y1 = self.y0
self.out.write_string("Z")
}
}
///|
pub fn PathBuf::line_to(self : PathBuf, x : Double, y : Double) -> Unit {
self.x1 = x
self.y1 = y
self.out.write_string("L" + self.num(x) + "," + self.num(y))
}
///|
pub fn PathBuf::quad_to(
self : PathBuf,
x1 : Double,
y1 : Double,
x : Double,
y : Double,
) -> Unit {
self.x1 = x
self.y1 = y
self.out.write_string(
"Q" +
self.num(x1) +
"," +
self.num(y1) +
"," +
self.num(x) +
"," +
self.num(y),
)
}
///|
pub fn PathBuf::bezier_to(
self : PathBuf,
x1 : Double,
y1 : Double,
x2 : Double,
y2 : Double,
x : Double,
y : Double,
) -> Unit {
self.x1 = x
self.y1 = y
self.out.write_string(
"C" +
self.num(x1) +
"," +
self.num(y1) +
"," +
self.num(x2) +
"," +
self.num(y2) +
"," +
self.num(x) +
"," +
self.num(y),
)
}
///|
pub fn PathBuf::arc(
self : PathBuf,
x : Double,
y : Double,
r : Double,
a0 : Double,
a1 : Double,
ccw : Bool,
) -> Unit {
let dx = r * @util.js_cos(a0)
let dy = r * @util.js_sin(a0)
let px = x + dx
let py = y + dy
let cw = if ccw { 0 } else { 1 }
let mut da = if ccw { a0 - a1 } else { a1 - a0 }
if !self.started {
self.x1 = px
self.y1 = py
self.x0 = px
self.y0 = py
self.started = true
self.out.write_string("M" + self.num(px) + "," + self.num(py))
} else if (self.x1 - px).abs() > epsilon || (self.y1 - py).abs() > epsilon {
self.x1 = px
self.y1 = py
self.out.write_string("L" + self.num(px) + "," + self.num(py))
}
if r == 0.0 {
return
}
if da < 0.0 {
da = da.mod(tau) + tau
}
let rs = self.num(r)
if da > tau_epsilon {
self.x1 = px
self.y1 = py
self.out.write_string(
"A" +
rs +
"," +
rs +
",0,1," +
cw.to_string() +
"," +
self.num(x - dx) +
"," +
self.num(y - dy) +
"A" +
rs +
"," +
rs +
",0,1," +
cw.to_string() +
"," +
self.num(px) +
"," +
self.num(py),
)
} else if da > epsilon {
let ex = x + r * @util.js_cos(a1)
let ey = y + r * @util.js_sin(a1)
self.x1 = ex
self.y1 = ey
self.out.write_string(
"A" +
rs +
"," +
rs +
",0," +
(if da >= 3.141592653589793 { "1" } else { "0" }) +
"," +
cw.to_string() +
"," +
self.num(ex) +
"," +
self.num(ey),
)
}
}
///|
pub fn PathBuf::rect(
self : PathBuf,
x : Double,
y : Double,
w : Double,
h : Double,
) -> Unit {
self.x0 = x
self.x1 = x
self.y0 = y
self.y1 = y
self.started = true
self.out.write_string(
"M" +
self.num(x) +
"," +
self.num(y) +
"h" +
self.num(w) +
"v" +
self.num(h) +
"h" +
self.num(-w) +
"Z",
)
}
// ---- bounding boxes ----
///|
/// vega-scenegraph's `boundContext`: accumulates the extent of a path,
/// optionally rotated by `deg` degrees about the origin.
pub struct BoundCtx {
bounds : Bounds
rot : Double
ma : Double
mb : Double
mc : Double
md : Double
mut lx : Double
mut ly : Double
}
///|
pub fn BoundCtx::new(bounds : Bounds, deg~ : Double = 0.0) -> BoundCtx {
if deg != 0.0 && !deg.is_nan() {
let rot = deg * deg_to_rad_const
let c = @util.js_cos(rot)
let s = @util.js_sin(rot)
{ bounds, rot, ma: c, mb: s, mc: -s, md: c, lx: 0.0, ly: 0.0, }
} else {
{ bounds, rot: 0.0, ma: 1.0, mb: 0.0, mc: 0.0, md: 1.0, lx: 0.0, ly: 0.0, }
}
}
///|
let deg_to_rad_const : Double = 3.141592653589793 / 180.0
///|
fn BoundCtx::px(self : BoundCtx, x : Double, y : Double) -> Double {
self.ma * x + self.mc * y
}
///|
fn BoundCtx::py(self : BoundCtx, x : Double, y : Double) -> Double {
self.mb * x + self.md * y
}
///|
fn BoundCtx::add_l(self : BoundCtx, x : Double, y : Double) -> Unit {
self.lx = x
self.ly = y
ignore(self.bounds.add(x, y))
}
///|
fn BoundCtx::add_pl(self : BoundCtx, x : Double, y : Double) -> Unit {
self.add_l(self.px(x, y), self.py(x, y))
}
///|
fn BoundCtx::add_p(self : BoundCtx, x : Double, y : Double) -> Unit {
ignore(self.bounds.add(self.px(x, y), self.py(x, y)))
}
///|
pub fn BoundCtx::move_to(self : BoundCtx, x : Double, y : Double) -> Unit {
self.add_pl(x, y)
}
///|
pub fn BoundCtx::line_to(self : BoundCtx, x : Double, y : Double) -> Unit {
self.add_pl(x, y)
}
///|
pub fn BoundCtx::close_path(_self : BoundCtx) -> Unit {
()
}
///|
pub fn BoundCtx::rect(
self : BoundCtx,
x : Double,
y : Double,
w : Double,
h : Double,
) -> Unit {
if self.rot != 0.0 {
self.add_p(x + w, y)
self.add_p(x + w, y + h)
self.add_p(x, y + h)
self.add_pl(x, y)
} else {
ignore(self.bounds.add(x + w, y + h))
self.add_l(x, y)
}
}
///|
fn quad_extrema(
x0 : Double,
x1 : Double,
x2 : Double,
cb : (Double) -> Unit,
) -> Unit {
let t = (x0 - x1) / (x0 + x2 - 2.0 * x1)
if 0.0 < t && t < 1.0 {
cb(x0 + (x1 - x0) * t)
}
}
///|
fn cubic_at(
t : Double,
x0 : Double,
x1 : Double,
x2 : Double,
x3 : Double,
) -> Double {
let s = 1.0 - t
let s2 = s * s
let t2 = t * t
s2 * s * x0 + 3.0 * s2 * t * x1 + 3.0 * s * t2 * x2 + t2 * t * x3
}
///|
fn cubic_extrema(
x0 : Double,
x1 : Double,
x2 : Double,
x3 : Double,
cb : (Double) -> Unit,
) -> Unit {
let a = x3 - x0 + 3.0 * x1 - 3.0 * x2
let b = x0 + x2 - 2.0 * x1
let c = x0 - x1
let mut t0 = 0.0
let mut t1 = 0.0
if a.abs() > 1.0e-14 {
let r = b * b + c * a
if r >= 0.0 {
let sr = r.sqrt()
t0 = (-b + sr) / a
t1 = (-b - sr) / a
}
} else {
t0 = 0.5 * c / b
}
if 0.0 < t0 && t0 < 1.0 {
cb(cubic_at(t0, x0, x1, x2, x3))
}
if 0.0 < t1 && t1 < 1.0 {
cb(cubic_at(t1, x0, x1, x2, x3))
}
}
///|
pub fn BoundCtx::quad_to(
self : BoundCtx,
x1 : Double,
y1 : Double,
x2 : Double,
y2 : Double,
) -> Unit {
let px1 = self.px(x1, y1)
let py1 = self.py(x1, y1)
let px2 = self.px(x2, y2)
let py2 = self.py(x2, y2)
quad_extrema(self.lx, px1, px2, fn(x) {
ignore(self.bounds.add(x, self.bounds.y1))
})
quad_extrema(self.ly, py1, py2, fn(y) {
ignore(self.bounds.add(self.bounds.x1, y))
})
self.add_l(px2, py2)
}
///|
pub fn BoundCtx::bezier_to(
self : BoundCtx,
x1 : Double,
y1 : Double,
x2 : Double,
y2 : Double,
x3 : Double,
y3 : Double,
) -> Unit {
let px1 = self.px(x1, y1)
let py1 = self.py(x1, y1)
let px2 = self.px(x2, y2)
let py2 = self.py(x2, y2)
let px3 = self.px(x3, y3)
let py3 = self.py(x3, y3)
cubic_extrema(self.lx, px1, px2, px3, fn(x) {
ignore(self.bounds.add(x, self.bounds.y1))
})
cubic_extrema(self.ly, py1, py2, py3, fn(y) {
ignore(self.bounds.add(self.bounds.x1, y))
})
self.add_l(px3, py3)
}
///|
pub fn BoundCtx::arc(
self : BoundCtx,
cx : Double,
cy : Double,
r : Double,
sa0 : Double,
ea0 : Double,
ccw0 : Bool,
) -> Unit {
let mut sa = sa0 + self.rot
let mut ea = ea0 + self.rot
let mut ccw = ccw0
self.lx = r * @util.js_cos(ea) + cx
self.ly = r * @util.js_sin(ea) + cy
if (ea - sa).abs() > tau - 1.0e-8 {
ignore(self.bounds.add(cx - r, cy - r))
ignore(self.bounds.add(cx + r, cy + r))
} else {
let update = fn(a : Double) {
ignore(
self.bounds.add(r * @util.js_cos(a) + cx, r * @util.js_sin(a) + cy),
)
}
update(sa)
update(ea)
if ea != sa {
sa = sa.mod(tau)
if sa < 0.0 {
sa = sa + tau
}
ea = ea.mod(tau)
if ea < 0.0 {
ea = ea + tau
}
if ea < sa {
ccw = !ccw
let s = sa
sa = ea
ea = s
}
if ccw {
ea = ea - tau
let mut s = sa - sa.mod(half_pi)
let mut i = 0
while i < 4 && s > ea {
update(s)
i = i + 1
s = s - half_pi
}
} else {
let mut s = sa - sa.mod(half_pi) + half_pi
let mut i = 0
while i < 4 && s < ea {
update(s)
i = i + 1
s = s + half_pi
}
}
}
}
}
// ---- one drawing target ----
///|
/// Where a shape draws: SVG path text or a bounding box.
pub enum Ctx {
Buf(PathBuf)
Bound(BoundCtx)
}
///|
pub fn Ctx::move_to(self : Ctx, x : Double, y : Double) -> Unit {
match self {
Buf(p) => p.move_to(x, y)
Bound(b) => b.move_to(x, y)
}
}
///|
pub fn Ctx::line_to(self : Ctx, x : Double, y : Double) -> Unit {
match self {
Buf(p) => p.line_to(x, y)
Bound(b) => b.line_to(x, y)
}
}
///|
pub fn Ctx::quad_to(
self : Ctx,
x1 : Double,
y1 : Double,
x : Double,
y : Double,
) -> Unit {
match self {
Buf(p) => p.quad_to(x1, y1, x, y)
Bound(b) => b.quad_to(x1, y1, x, y)
}
}
///|
pub fn Ctx::bezier_to(
self : Ctx,
x1 : Double,
y1 : Double,
x2 : Double,
y2 : Double,
x : Double,
y : Double,
) -> Unit {
match self {
Buf(p) => p.bezier_to(x1, y1, x2, y2, x, y)
Bound(b) => b.bezier_to(x1, y1, x2, y2, x, y)
}
}
///|
pub fn Ctx::arc(
self : Ctx,
x : Double,
y : Double,
r : Double,
a0 : Double,
a1 : Double,
ccw : Bool,
) -> Unit {
match self {
Buf(p) => p.arc(x, y, r, a0, a1, ccw)
Bound(b) => b.arc(x, y, r, a0, a1, ccw)
}
}
///|
pub fn Ctx::rect(
self : Ctx,
x : Double,
y : Double,
w : Double,
h : Double,
) -> Unit {
match self {
Buf(p) => p.rect(x, y, w, h)
Bound(b) => b.rect(x, y, w, h)
}
}
///|
pub fn Ctx::close_path(self : Ctx) -> Unit {
match self {
Buf(p) => p.close_path()
Bound(b) => b.close_path()
}
}