// Chart builder API and main rendering orchestrator.
///|
/// Chart builder wraps a ChartOption and provides chainable methods.
pub struct Chart {
option : ChartOption
}
///|
/// Create a new chart. Chart type is determined per-series via Series::bar/line/scatter.
pub fn Chart::new() -> Chart {
let xAxis = AxisOption::defaultX()
let yAxis = AxisOption::linearY()
let series : Array[Series] = []
let option = ChartOption::new(xAxis, yAxis, series)
{ option, }
}
///|
/// Deprecated: use Chart::new() instead. Chart type is per-series.
pub fn Chart::bar() -> Chart {
Chart::new()
}
///|
/// Deprecated: use Chart::new() instead. Chart type is per-series.
pub fn Chart::line() -> Chart {
Chart::new()
}
///|
/// Deprecated: use Chart::new() instead. Chart type is per-series.
pub fn Chart::scatter() -> Chart {
Chart::new()
}
///|
/// Set chart title.
pub fn Chart::title(self : Chart, t : String) -> Chart {
{ option: { ..self.option, title: Some(t) } }
}
///|
/// Set chart width and height.
pub fn Chart::size(self : Chart, w : Double, h : Double) -> Chart {
{ option: { ..self.option, width: w, height: h } }
}
///|
/// Set width only.
pub fn Chart::width(self : Chart, w : Double) -> Chart {
{ option: { ..self.option, width: w } }
}
///|
/// Set height only.
pub fn Chart::height(self : Chart, h : Double) -> Chart {
{ option: { ..self.option, height: h } }
}
///|
/// Set X axis label.
pub fn Chart::xLabel(self : Chart, label : String) -> Chart {
let xAxis = self.option.xAxis.withLabel(label)
{ option: { ..self.option, xAxis, } }
}
///|
/// Set Y axis label.
pub fn Chart::yLabel(self : Chart, label : String) -> Chart {
let yAxis = self.option.yAxis.withLabel(label)
{ option: { ..self.option, yAxis, } }
}
///|
/// Set X axis tick positions and labels.
/// `positions` — data coordinate for each tick.
/// `labels` — display label for each tick (must match positions length).
/// When positions is empty, ticks are auto-inferred from data.
pub fn Chart::xTicks(
self : Chart,
positions : Array[Double],
labels : Array[String],
) -> Chart {
let xAxis = {
..self.option.xAxis,
tickPositions: positions,
tickLabels: labels,
}
{ option: { ..self.option, xAxis, } }
}
///|
/// Set Y axis range.
pub fn Chart::yRange(self : Chart, min : Double, max : Double) -> Chart {
let yAxis = { ..self.option.yAxis, kind: Linear(min, max) }
{ option: { ..self.option, yAxis, } }
}
///|
/// Set tick font size for both axes.
pub fn Chart::tickFontSize(self : Chart, size : Double) -> Chart {
let xAxis = { ..self.option.xAxis, tickFontSize: size }
let yAxis = { ..self.option.yAxis, tickFontSize: size }
{ option: { ..self.option, xAxis, yAxis } }
}
///|
/// Set axis label (title) font size for both axes.
pub fn Chart::labelFontSize(self : Chart, size : Double) -> Chart {
let xAxis = { ..self.option.xAxis, labelFontSize: size }
let yAxis = { ..self.option.yAxis, labelFontSize: size }
{ option: { ..self.option, xAxis, yAxis } }
}
///|
/// Enable Y-axis grid lines with default color.
pub fn Chart::yGrid(self : Chart, show : Bool) -> Chart {
let yAxis = { ..self.option.yAxis, gridLine: show }
{ option: { ..self.option, yAxis, } }
}
///|
/// Enable X-axis grid lines with default color.
pub fn Chart::xGrid(self : Chart, show : Bool) -> Chart {
let xAxis = { ..self.option.xAxis, gridLine: show }
{ option: { ..self.option, xAxis, } }
}
///|
/// Set bar width ratio (0.0~1.0). Fraction of band width occupied by bars. Default: 0.7.
pub fn Chart::barWidthRatio(self : Chart, ratio : Double) -> Chart {
{ option: { ..self.option, barWidthRatio: ratio } }
}
///|
/// Set bar gap ratio (0.0~1.0). Gap between bars in a group, as fraction of bar width. Default: 0.0.
pub fn Chart::barGap(self : Chart, gap : Double) -> Chart {
{ option: { ..self.option, barGap: gap } }
}
///|
/// Set legend font size.
pub fn Chart::legendFontSize(self : Chart, size : Double) -> Chart {
{ option: { ..self.option, legendFontSize: size } }
}
///|
/// Set legend visibility and position.
pub fn Chart::legend(self : Chart, show : Bool, position : Position) -> Chart {
{ option: { ..self.option, legend: show, legendPosition: position } }
}
///|
/// Add a series to the chart.
pub fn Chart::series(self : Chart, s : Series) -> Chart {
let newSeries = self.option.series.copy()
newSeries.push(s)
let legend = newSeries.length() > 1
{ option: { ..self.option, series: newSeries, legend } }
}
///|
/// Add multiple series at once.
pub fn Chart::addSeries(self : Chart, ss : Array[Series]) -> Chart {
let mut chart = self
for i = 0; i < ss.length(); i = i + 1 {
chart = chart.series(ss[i])
}
chart
}
///|
/// Set chart margins.
pub fn Chart::margin(
self : Chart,
top : Double,
right : Double,
bottom : Double,
left : Double,
) -> Chart {
{ option: { ..self.option, margin: { top, right, bottom, left } } }
}
///|
/// Set chart background color.
pub fn Chart::background(self : Chart, color : Color) -> Chart {
{ option: { ..self.option, background: color } }
}
///|
/// Set font family for all text elements.
pub fn Chart::fontFamily(self : Chart, family : String) -> Chart {
{ option: { ..self.option, fontFamily: family } }
}
///|
/// Export the internal ChartOption (escape hatch for advanced users).
pub fn Chart::toOption(self : Chart) -> ChartOption {
self.option
}
///|
/// Render the chart to an SVG string.
/// Returns Result with the SVG string on success, error message on failure.
pub fn Chart::toSvg(self : Chart) -> Result[String, String] {
render(self.option)
}
///|
/// Render the chart and save to a file (native target only).
/// Returns Ok(()) on success, Err(message) on failure.
pub fn Chart::save(self : Chart, filename : String) -> Result[Unit, String] {
match self.toSvg() {
Ok(svg) => saveSvg(filename, svg)
Err(e) => Err(e)
}
}
///|
/// Render a ChartOption directly to SVG (static entry point).
pub fn render(opt : ChartOption) -> Result[String, String] {
// Compute layout
let layout = match computeLayout(opt) {
Ok(l) => l
Err(msg) => return Err(msg)
}
let bg = opt.background.toSvg()
let mut svg = svgOpen(width=opt.width, height=opt.height, bg~) + "\n"
// Render title
svg = svg + renderTitle(opt) + "\n"
// Render axes
svg = svg + renderYAxis(layout, opt) + "\n"
svg = svg + renderXAxis(layout, opt) + "\n"
// Render each series
for i = 0; i < opt.series.length(); i = i + 1 {
let s = opt.series[i]
match s.chartType {
Bar => svg = svg + renderBars(layout, opt, i) + "\n"
Line => svg = svg + renderLine(layout, opt, i) + "\n"
Scatter => svg = svg + renderScatter(layout, opt, i) + "\n"
BoxPlot => svg = svg + renderBoxplot(layout, opt, i) + "\n"
}
// Render error bars on top
svg = svg + renderErrorBars(layout, opt, i) + "\n"
}
// Render legend
svg = svg + renderLegend(layout, opt) + "\n"
svg = svg + svgClose()
Ok(svg)
}