// Discrete scales: band and point (vega-scale 7.4.3 `scaleBand`, BSD-3-Clause)
// and ordinal (d3-scale 4 `ordinal`, ISC), plus the sequential colour scale.
///|
/// A key identifying a domain value the way d3's `InternMap` does: values of
/// different types never collide (the number 1 and the string "1" differ).
pub fn value_key(v : @value.Value) -> String {
match v {
Null => "z:"
Bool(b) => "b:" + (if b { "1" } else { "0" })
Num(x) => "n:" + @util.js_number_to_string(x)
Str(s) => "s:" + s
other => "o:" + other.to_js_string()
}
}
///|
/// vega-scale's `bandSpace(count, paddingInner, paddingOuter)`.
pub fn band_space(
count : Double,
padding_inner : Double,
padding_outer : Double,
) -> Double {
let space = count - padding_inner + padding_outer * 2.0
if count != 0.0 {
if space > 0.0 {
space
} else {
1.0
}
} else {
0.0
}
}
///|
fn clamp01(x : Double) -> Double {
@util.js_max(0.0, @util.js_min(1.0, x))
}
///|
/// Band and point scales. A point scale is a band scale with an inner padding
/// of 1 whose `padding` only changes the outer padding.
pub struct Band {
is_point : Bool
mut keys : Array[String]
mut range : (Double, Double)
mut padding_inner : Double
mut padding_outer : Double
mut align : Double
mut round : Bool
mut step : Double
mut bandwidth : Double
mut positions : Array[Double]
}
///|
fn Band::make(is_point : Bool) -> Band {
let b : Band = {
is_point,
keys: [],
range: (0.0, 1.0),
padding_inner: if is_point {
1.0
} else {
0.0
},
padding_outer: 0.0,
align: 0.5,
round: false,
step: 0.0,
bandwidth: 0.0,
positions: [],
}
b.rescale()
b
}
///|
pub fn Band::band() -> Band {
Band::make(false)
}
///|
pub fn Band::point() -> Band {
Band::make(true)
}
///|
fn Band::rescale(self : Band) -> Unit {
let n = self.keys.length().to_double()
let (r0, r1) = self.range
let reverse = r1 < r0
let stop = if reverse { r0 } else { r1 }
let mut start = if reverse { r1 } else { r0 }
let space = band_space(n, self.padding_inner, self.padding_outer)
self.step = (stop - start) / (if space != 0.0 { space } else { 1.0 })
if self.round {
self.step = self.step.floor()
}
start = start +
(stop - start - self.step * (n - self.padding_inner)) * self.align
self.bandwidth = self.step * (1.0 - self.padding_inner)
if self.round {
start = @util.js_round(start)
self.bandwidth = @util.js_round(self.bandwidth)
}
let values : Array[Double] = []
for i in 0.. Unit {
let keys : Array[String] = []
let seen : Map[String, Bool] = Map([])
for v in values {
let k = value_key(v)
if !seen.contains(k) {
seen[k] = true
keys.push(k)
}
}
self.keys = keys
self.rescale()
}
///|
pub fn Band::set_range(self : Band, r0 : Double, r1 : Double) -> Unit {
self.range = (r0, r1)
self.rescale()
}
///|
/// `rangeRound`: set the range and round positions and sizes to integers.
pub fn Band::set_range_round(self : Band, r0 : Double, r1 : Double) -> Unit {
self.range = (r0, r1)
self.round = true
self.rescale()
}
///|
pub fn Band::set_round(self : Band, round : Bool) -> Unit {
self.round = round
self.rescale()
}
///|
pub fn Band::set_padding(self : Band, p : Double) -> Unit {
self.padding_outer = clamp01(p)
if !self.is_point {
self.padding_inner = self.padding_outer
}
self.rescale()
}
///|
pub fn Band::set_padding_inner(self : Band, p : Double) -> Unit {
self.padding_inner = clamp01(p)
self.rescale()
}
///|
pub fn Band::set_padding_outer(self : Band, p : Double) -> Unit {
self.padding_outer = clamp01(p)
self.rescale()
}
///|
pub fn Band::set_align(self : Band, a : Double) -> Unit {
self.align = clamp01(a)
self.rescale()
}
///|
/// Start position of the band for a domain value, None if not in the domain.
pub fn Band::map(self : Band, v : @value.Value) -> Double? {
let k = value_key(v)
for i in 0.. Ordinal {
{ keys: [], range: [], }
}
///|
pub fn Ordinal::set_domain(
self : Ordinal,
values : Array[@value.Value],
) -> Unit {
let keys : Array[String] = []
for v in values {
let k = value_key(v)
if !keys.contains(k) {
keys.push(k)
}
}
self.keys = keys
}
///|
pub fn Ordinal::set_range(self : Ordinal, range : Array[@value.Value]) -> Unit {
self.range = range
}
///|
/// The range value for a domain value; None if unknown (or the range is empty).
pub fn Ordinal::map(self : Ordinal, v : @value.Value) -> @value.Value? {
let k = value_key(v)
for i in 0.. String
}
///|
pub fn Sequential::new(
kind : ScaleKind,
interpolator : (Double) -> String,
) -> Sequential {
{ scale: Continuous::new(kind), interpolator, }
}
///|
pub fn Sequential::map(self : Sequential, x : Double) -> String? {
if x.is_nan() {
return None
}
let d = self.scale.domain
let t0 = self.scale.transform(d[0])
let t1 = self.scale.transform(d[d.length() - 1])
let k10 = if t0 == t1 { 0.0 } else { 1.0 / (t1 - t0) }
if k10 == 0.0 {
return Some((self.interpolator)(0.5))
}
let v = (self.scale.transform(x) - t0) * k10
Some((self.interpolator)(if self.scale.clamp { clamp01(v) } else { v }))
}