///|
fn group_color(index : Int) -> String {
let palette = [
"#38bdf8", "#a78bfa", "#34d399", "#f472b6", "#facc15", "#2dd4bf", "#e879f9",
]
palette[index % palette.length()]
}
///|
/// Snapshot projection with MoonBit-computed component colors and an optional
/// live H1 birth representative. Requires coordinates; never invents matrix geometry.
pub fn scale_svg(
analysis : Analysis,
scale~ : Double,
interval? : Int? = None,
) -> String raise TopologyError {
let groups = connected_components(analysis, scale)
let positions = analysis.filtration.positions
if positions.is_empty() {
raise TopologyError(
"scale SVG requires display coordinates; matrix input has none",
)
}
let highlighted : Map[Int, Bool] = Map([])
if interval is Some(index) {
if index < 0 || index >= analysis.intervals.length() {
raise TopologyError("selected interval index is out of range")
}
let chosen = analysis.intervals[index]
let alive = match chosen.death {
None => true
Some(death) => scale < death
}
if chosen.dimension != 1 || chosen.birth > scale || !alive {
raise TopologyError(
"selected interval must be an H1 class alive at the snapshot scale",
)
}
ignore(cycle_edges(analysis, chosen))
for cell in chosen.representative {
highlighted[cell] = true
}
}
let colors : Map[Int, String] = Map([])
for i = 0; i < groups.length(); i = i + 1 {
for vertex in groups[i] {
colors[vertex] = group_color(i)
}
}
let mut min_x = positions[0][0]
let mut max_x = min_x
let mut min_y = positions[0][1]
let mut max_y = min_y
for point in positions {
if point[0] < min_x {
min_x = point[0]
}
if point[0] > max_x {
max_x = point[0]
}
if point[1] < min_y {
min_y = point[1]
}
if point[1] > max_y {
max_y = point[1]
}
}
let raw_span = maximum(max_x - min_x, max_y - min_y)
let span = if raw_span > 0.0 { raw_span } else { 1.0 }
let xy = positions.map(fn(point) {
(
400.0 + 400.0 * ((point[0] - min_x) / span - (max_x - min_x) / span / 2.0),
335.0 - 400.0 * ((point[1] - min_y) / span - (max_y - min_y) / span / 2.0),
)
})
let mut svg = ""
}