// The `sort` property of discrete position channels: the order of the band /
// point scale domain.
///|
fn first_appearance(
rows : Array[Map[String, @value.Value]],
field : String,
) -> Array[@value.Value] {
let seen : Map[String, Bool] = Map([])
let out : Array[@value.Value] = []
for r in rows {
let v = @value.get_field(r, field)
let k = @scale.value_key(v)
if !seen.contains(k) {
seen[k] = true
out.push(v)
}
}
out
}
///|
fn cmp_double(a : Double, b : Double) -> Int32 {
if a < b {
-1
} else if a > b {
1
} else {
0
}
}
///|
/// Order `keys` by an aggregate of `field` over the rows of each key.
fn order_by_aggregate(
pre : Array[Map[String, @value.Value]],
key_field : String,
op : String,
field : String?,
descending : Bool,
keys : Array[@value.Value],
) -> Array[@value.Value] raise CompileError {
let agg = @transform.aggregate(pre, [key_field], [
{ op, field: if op == "count" { None } else { field }, out: "__sort", },
]) catch {
e => raise transform_err(e)
}
let value_of : Map[String, @value.Value] = Map([])
for r in agg {
value_of[@scale.value_key(@value.get_field(r, key_field))] = @value.get_field(
r, "__sort",
)
}
@transform.stable_sort(keys, fn(a, b) {
let c = @value.compare_values(
match value_of.get(@scale.value_key(a)) {
Some(v) => v
None => Null
},
match value_of.get(@scale.value_key(b)) {
Some(v) => v
None => Null
},
)
if descending {
-c
} else {
c
}
})
}
///|
/// The domain order requested by `sort`, or None for the default (ascending).
fn sort_domain(
p : Pos,
sort : @value.Value,
pre : Array[Map[String, @value.Value]],
data : Array[Map[String, @value.Value]],
px : Pos?,
py : Pos?,
) -> Array[@value.Value]? raise CompileError {
let path = "encoding.sort"
match sort {
Null => Some(first_appearance(data, p.field))
Str("ascending") => None
Str("descending") => {
let asc = sorted_unique(data, p.field)
asc.rev_in_place()
Some(asc)
}
Arr(order) => {
let index : Map[String, Double] = Map([])
for i in 0.. v
None => order.length().to_double()
}
let ib = match index.get(@scale.value_key(b)) {
Some(v) => v
None => order.length().to_double()
}
cmp_double(ia, ib)
}),
)
}
Str(s) => {
let desc = s.has_prefix("-")
let target = if desc { s[1:].to_owned() } else { s }
let other = match target {
"x" => px
"y" => py
_ => raise nope(path + " " + s)
}
match other {
Some({ def: { aggregate: Some(op), field, .. }, .. }) =>
Some(
order_by_aggregate(
pre,
p.field,
op,
field,
desc,
first_appearance(data, p.field),
),
)
_ => raise nope(path + " " + s)
}
}
Obj(m) => {
let op = match m.get("op") {
Some(Str(o)) => o
_ => raise nope(path + ".op")
}
let field = match m.get("field") {
Some(Str(f)) => Some(f)
_ => None
}
let desc = match m.get("order") {
Some(Str("descending")) => true
_ => false
}
Some(
order_by_aggregate(
pre,
p.field,
op,
field,
desc,
first_appearance(data, p.field),
),
)
}
_ => raise nope(path)
}
}