///|
/// Dependency-free SVG output for ternary diagrams and audit plots.
pub(all) struct SvgCanvas {
width : Int
height : Int
elements : Array[String]
} derive(Debug, Eq)
///|
pub fn SvgCanvas::new(
width~ : Int,
height~ : Int,
) -> SvgCanvas raise @domain.ExtractionError {
if width < 1 || height < 1 {
raise @domain.ExtractionError::InvalidArgument(
message="SVG dimensions must be positive",
)
}
{ width, height, elements: [] }
}
///|
pub fn SvgCanvas::line(
self : SvgCanvas,
from : @geometry.Point,
to : @geometry.Point,
stroke~ : String,
width? : Double = 1.0,
) -> SvgCanvas raise @domain.ExtractionError {
@domain.require_positive("stroke width", width)
let elements = self.elements.copy()
elements.push(
"",
)
{ width: self.width, height: self.height, elements }
}
///|
pub fn SvgCanvas::circle(
self : SvgCanvas,
center : @geometry.Point,
radius~ : Double,
fill~ : String,
) -> SvgCanvas raise @domain.ExtractionError {
@domain.require_positive("circle radius", radius)
let elements = self.elements.copy()
elements.push(
"",
)
{ width: self.width, height: self.height, elements }
}
///|
pub fn SvgCanvas::polyline(
self : SvgCanvas,
points : ArrayView[@geometry.Point],
stroke~ : String,
fill? : String = "none",
) -> SvgCanvas raise @domain.ExtractionError {
if points.length() < 2 {
raise @domain.ExtractionError::InsufficientData(
required=2,
actual=points.length(),
)
}
let coordinates = [ for point in points => "\{point.x},\{point.y}" ].join(" ")
let elements = self.elements.copy()
elements.push(
"",
)
{ width: self.width, height: self.height, elements }
}
///|
pub fn SvgCanvas::render(self : SvgCanvas) -> String {
""
}
///|
fn escape(value : String) -> String {
value
.replace(old="&", new="&")
.replace(old="\"", new=""")
.replace(old="<", new="<")
}
///|
pub fn ternary_triangle(
width : Int,
height : Int,
) -> SvgCanvas raise @domain.ExtractionError {
let canvas = SvgCanvas::new(width~, height~)
let margin = width.to_double() * 0.08
let left = @geometry.Point::new(x=margin, y=height.to_double() - margin)
let right = @geometry.Point::new(
x=width.to_double() - margin,
y=height.to_double() - margin,
)
let top = @geometry.Point::new(x=width.to_double() / 2.0, y=margin)
canvas.polyline([left, right, top, left], stroke="#333333", fill="none")
}
///|
pub fn map_unit_triangle(
point : @geometry.Point,
width : Double,
height : Double,
) -> @geometry.Point raise @domain.ExtractionError {
@domain.require_positive("canvas width", width)
@domain.require_positive("canvas height", height)
@geometry.Point::new(x=point.x * width, y=(1.0 - point.y) * height)
}