///|
pub(all) enum SparklineType {
Line
Column
WinLoss
} derive(Eq, Debug)
///|
pub struct SparklineColor {
rgb : String?
theme : Int?
tint : Double?
} derive(Debug)
///|
fn rgb(value : StringView) -> SparklineColor raise XlsxError {
let raw = value.to_owned().to_upper()
let hex = match raw.strip_prefix("#") {
Some(rest) => rest.to_owned()
None => raw
}
let normalized = match hex.length() {
6 => "FF" + hex
8 => hex
_ =>
raise InvalidSparkline(msg="sparkline color must be RRGGBB or AARRGGBB")
}
for ch in normalized {
let ok = (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')
if !ok {
raise InvalidSparkline(msg="sparkline color must be hex")
}
}
{ rgb: Some(normalized), theme: None, tint: None }
}
///|
fn theme(theme : Int, tint? : Double = 0.0) -> SparklineColor {
let tint_opt = if tint == 0.0 { None } else { Some(tint) }
{ rgb: None, theme: Some(theme), tint: tint_opt }
}
///|
pub struct Sparkline {
range_ref : String
location : String
}
///|
pub struct SparklineGroupOptions {
mut display_empty_cells_as : String
mut date_axis : Bool
mut line_weight : Double?
mut manual_max : Int?
mut manual_min : Int?
mut markers : Bool
mut high : Bool
mut low : Bool
mut first : Bool
mut last : Bool
mut negative : Bool
mut display_x_axis : Bool
mut display_hidden : Bool
mut right_to_left : Bool
mut color_series : SparklineColor?
mut color_negative : SparklineColor?
mut color_axis : SparklineColor?
mut color_markers : SparklineColor?
mut color_first : SparklineColor?
mut color_last : SparklineColor?
mut color_high : SparklineColor?
mut color_low : SparklineColor?
} derive(Debug)
///|
fn SparklineGroupOptions::new() -> SparklineGroupOptions {
{
display_empty_cells_as: "",
date_axis: false,
line_weight: None,
manual_max: None,
manual_min: None,
markers: false,
high: false,
low: false,
first: false,
last: false,
negative: false,
display_x_axis: false,
display_hidden: false,
right_to_left: false,
color_series: None,
color_negative: None,
color_axis: None,
color_markers: None,
color_first: None,
color_last: None,
color_high: None,
color_low: None,
}
}
///|
pub struct SparklineGroup {
sparkline_type : SparklineType
sparklines : Array[Sparkline]
options : SparklineGroupOptions
}
///|
pub struct SparklineOptions {
locations : Array[String]
ranges : Array[String]
location : Array[String]
range : Array[String]
mut sparkline_type : SparklineType
mut style : Int
mut weight : Double?
mut date_axis : Bool
mut markers : Bool
mut high : Bool
mut low : Bool
first : Bool
last : Bool
negative : Bool
mut axis : Bool
mut hidden : Bool
mut reverse : Bool
mut series_color : String?
mut negative_color : String?
mut markers_color : String?
mut first_color : String?
mut last_color : String?
mut high_color : String?
mut low_color : String?
mut max : Int
mut cust_max : Int
mut min : Int
mut cust_min : Int
mut manual_max : Int?
mut manual_min : Int?
mut empty_cells : String?
} derive(Debug)
///|
pub fn SparklineOptions::new(
locations : ArrayView[String],
ranges : ArrayView[String],
) -> SparklineOptions raise XlsxError {
if locations.length() == 0 || ranges.length() == 0 {
raise InvalidSparkline(msg="sparkline inputs empty")
}
if locations.length() != ranges.length() {
raise InvalidSparkline(msg="sparkline inputs mismatch")
}
let locs : Array[String] = []
let rs : Array[String] = []
for v in locations {
locs.push(v)
}
for v in ranges {
rs.push(v)
}
{
locations: locs,
ranges: rs,
location: locs,
range: rs,
sparkline_type: Line,
style: 0,
weight: None,
date_axis: false,
markers: false,
high: false,
low: false,
first: false,
last: false,
negative: false,
axis: false,
hidden: false,
reverse: false,
series_color: None,
negative_color: None,
markers_color: None,
first_color: None,
last_color: None,
high_color: None,
low_color: None,
max: 0,
cust_max: 0,
min: 0,
cust_min: 0,
manual_max: None,
manual_min: None,
empty_cells: None,
}
}
///|
pub fn SparklineOptions::set_type(
self : SparklineOptions,
sparkline_type : SparklineType,
) -> Unit {
self.sparkline_type = sparkline_type
}
///|
pub fn SparklineOptions::set_style(
self : SparklineOptions,
style : Int,
) -> Unit {
self.style = style
}
///|
pub fn SparklineOptions::set_weight(
self : SparklineOptions,
weight : Double,
) -> Unit raise XlsxError {
if weight <= 0.0 {
raise InvalidSparkline(msg="sparkline weight must be > 0")
}
self.weight = Some(weight)
}
///|
pub fn SparklineOptions::set_date_axis(
self : SparklineOptions,
date_axis : Bool,
) -> Unit {
self.date_axis = date_axis
}
///|
pub fn SparklineOptions::set_markers(
self : SparklineOptions,
markers : Bool,
) -> Unit {
self.markers = markers
}
///|
pub fn SparklineOptions::set_high(self : SparklineOptions, high : Bool) -> Unit {
self.high = high
}
///|
pub fn SparklineOptions::set_low(self : SparklineOptions, low : Bool) -> Unit {
self.low = low
}
///|
pub fn SparklineOptions::set_axis(self : SparklineOptions, axis : Bool) -> Unit {
self.axis = axis
}
///|
pub fn SparklineOptions::set_hidden(
self : SparklineOptions,
hidden : Bool,
) -> Unit {
self.hidden = hidden
}
///|
pub fn SparklineOptions::set_reverse(
self : SparklineOptions,
reverse : Bool,
) -> Unit {
self.reverse = reverse
}
///|
pub fn SparklineOptions::set_series_color(
self : SparklineOptions,
value : String,
) -> Unit {
self.series_color = Some(value)
}
///|
pub fn SparklineOptions::set_negative_color(
self : SparklineOptions,
value : String,
) -> Unit {
self.negative_color = Some(value)
}
///|
pub fn SparklineOptions::set_markers_color(
self : SparklineOptions,
value : String,
) -> Unit {
self.markers_color = Some(value)
}
///|
pub fn SparklineOptions::set_first_color(
self : SparklineOptions,
value : String,
) -> Unit {
self.first_color = Some(value)
}
///|
pub fn SparklineOptions::set_last_color(
self : SparklineOptions,
value : String,
) -> Unit {
self.last_color = Some(value)
}
///|
pub fn SparklineOptions::set_high_color(
self : SparklineOptions,
value : String,
) -> Unit {
self.high_color = Some(value)
}
///|
pub fn SparklineOptions::set_low_color(
self : SparklineOptions,
value : String,
) -> Unit {
self.low_color = Some(value)
}
///|
pub fn SparklineOptions::set_manual_max(
self : SparklineOptions,
max : Int,
) -> Unit {
self.max = max
self.cust_max = max
self.manual_max = Some(max)
}
///|
pub fn SparklineOptions::clear_manual_max(self : SparklineOptions) -> Unit {
self.max = 0
self.cust_max = 0
self.manual_max = None
}
///|
pub fn SparklineOptions::set_manual_min(
self : SparklineOptions,
min : Int,
) -> Unit {
self.min = min
self.cust_min = min
self.manual_min = Some(min)
}
///|
pub fn SparklineOptions::clear_manual_min(self : SparklineOptions) -> Unit {
self.min = 0
self.cust_min = 0
self.manual_min = None
}
///|
pub fn SparklineOptions::set_empty_cells(
self : SparklineOptions,
mode : StringView,
) -> Unit raise XlsxError {
match mode {
"gap" | "zero" | "span" => self.empty_cells = Some(mode.to_owned())
_ => raise InvalidSparkline(msg="sparkline empty cells invalid")
}
}
///|
let sparkline_ext_uri = "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"
///|
let sparkline_ns_x14 = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"
///|
let sparkline_ns_xm = "http://schemas.microsoft.com/office/excel/2006/main"
///|
fn sparkline_type_to_string(kind : SparklineType) -> String {
match kind {
Column => "column"
WinLoss => "stacked"
Line => "line"
}
}
///|
fn sparkline_type_from_string(value : StringView) -> SparklineType {
match value {
"column" => Column
"winLoss" => WinLoss
"stacked" => WinLoss
_ => Line
}
}
///|
priv struct SparklinePreset {
color_series : SparklineColor
color_negative : SparklineColor
color_markers : SparklineColor
color_first : SparklineColor
color_last : SparklineColor
color_high : SparklineColor
color_low : SparklineColor
}
///|
let sparkline_presets : Array[SparklinePreset] = [
{
color_series: theme(4, tint=-0.499984740745262),
color_negative: theme(5),
color_markers: theme(4, tint=-0.499984740745262),
color_first: theme(4, tint=0.3999755851924192),
color_last: theme(4, tint=0.3999755851924192),
color_high: theme(4),
color_low: theme(4),
}, // 0
{
color_series: theme(4, tint=-0.499984740745262),
color_negative: theme(5),
color_markers: theme(4, tint=-0.499984740745262),
color_first: theme(4, tint=0.3999755851924192),
color_last: theme(4, tint=0.3999755851924192),
color_high: theme(4),
color_low: theme(4),
}, // 1
{
color_series: theme(5, tint=-0.499984740745262),
color_negative: theme(6),
color_markers: theme(5, tint=-0.499984740745262),
color_first: theme(5, tint=0.3999755851924192),
color_last: theme(5, tint=0.3999755851924192),
color_high: theme(5),
color_low: theme(5),
}, // 2
{
color_series: theme(6, tint=-0.499984740745262),
color_negative: theme(7),
color_markers: theme(6, tint=-0.499984740745262),
color_first: theme(6, tint=0.3999755851924192),
color_last: theme(6, tint=0.3999755851924192),
color_high: theme(6),
color_low: theme(6),
}, // 3
{
color_series: theme(7, tint=-0.499984740745262),
color_negative: theme(8),
color_markers: theme(7, tint=-0.499984740745262),
color_first: theme(7, tint=0.3999755851924192),
color_last: theme(7, tint=0.3999755851924192),
color_high: theme(7),
color_low: theme(7),
}, // 4
{
color_series: theme(8, tint=-0.499984740745262),
color_negative: theme(9),
color_markers: theme(8, tint=-0.499984740745262),
color_first: theme(8, tint=0.3999755851924192),
color_last: theme(8, tint=0.3999755851924192),
color_high: theme(8),
color_low: theme(8),
}, // 5
{
color_series: theme(9, tint=-0.499984740745262),
color_negative: theme(4),
color_markers: theme(9, tint=-0.499984740745262),
color_first: theme(9, tint=0.3999755851924192),
color_last: theme(9, tint=0.3999755851924192),
color_high: theme(9),
color_low: theme(9),
}, // 6
{
color_series: theme(4, tint=-0.249977111117893),
color_negative: theme(5),
color_markers: theme(5, tint=-0.249977111117893),
color_first: theme(5, tint=-0.249977111117893),
color_last: theme(5, tint=-0.249977111117893),
color_high: theme(5),
color_low: theme(5),
}, // 7
{
color_series: theme(5, tint=-0.249977111117893),
color_negative: theme(6),
color_markers: theme(6, tint=-0.249977111117893),
color_first: theme(6, tint=-0.249977111117893),
color_last: theme(6, tint=-0.249977111117893),
color_high: theme(6, tint=-0.249977111117893),
color_low: theme(6, tint=-0.249977111117893),
}, // 8
{
color_series: theme(6, tint=-0.249977111117893),
color_negative: theme(7),
color_markers: theme(7, tint=-0.249977111117893),
color_first: theme(7, tint=-0.249977111117893),
color_last: theme(7, tint=-0.249977111117893),
color_high: theme(7, tint=-0.249977111117893),
color_low: theme(7, tint=-0.249977111117893),
}, // 9
{
color_series: theme(7, tint=-0.249977111117893),
color_negative: theme(8),
color_markers: theme(8, tint=-0.249977111117893),
color_first: theme(8, tint=-0.249977111117893),
color_last: theme(8, tint=-0.249977111117893),
color_high: theme(8, tint=-0.249977111117893),
color_low: theme(8, tint=-0.249977111117893),
}, // 10
{
color_series: theme(8, tint=-0.249977111117893),
color_negative: theme(9),
color_markers: theme(9, tint=-0.249977111117893),
color_first: theme(9, tint=-0.249977111117893),
color_last: theme(9, tint=-0.249977111117893),
color_high: theme(9, tint=-0.249977111117893),
color_low: theme(9, tint=-0.249977111117893),
}, // 11
{
color_series: theme(9, tint=-0.249977111117893),
color_negative: theme(4),
color_markers: theme(4, tint=-0.249977111117893),
color_first: theme(4, tint=-0.249977111117893),
color_last: theme(4, tint=-0.249977111117893),
color_high: theme(4, tint=-0.249977111117893),
color_low: theme(4, tint=-0.249977111117893),
}, // 12
{
color_series: theme(4),
color_negative: theme(5),
color_markers: theme(4, tint=-0.249977111117893),
color_first: theme(4, tint=-0.249977111117893),
color_last: theme(4, tint=-0.249977111117893),
color_high: theme(4, tint=-0.249977111117893),
color_low: theme(4, tint=-0.249977111117893),
}, // 13
{
color_series: theme(5),
color_negative: theme(6),
color_markers: theme(5, tint=-0.249977111117893),
color_first: theme(5, tint=-0.249977111117893),
color_last: theme(5, tint=-0.249977111117893),
color_high: theme(5, tint=-0.249977111117893),
color_low: theme(5, tint=-0.249977111117893),
}, // 14
{
color_series: theme(6),
color_negative: theme(7),
color_markers: theme(6, tint=-0.249977111117893),
color_first: theme(6, tint=-0.249977111117893),
color_last: theme(6, tint=-0.249977111117893),
color_high: theme(6, tint=-0.249977111117893),
color_low: theme(6, tint=-0.249977111117893),
}, // 15
{
color_series: theme(7),
color_negative: theme(8),
color_markers: theme(7, tint=-0.249977111117893),
color_first: theme(7, tint=-0.249977111117893),
color_last: theme(7, tint=-0.249977111117893),
color_high: theme(7, tint=-0.249977111117893),
color_low: theme(7, tint=-0.249977111117893),
}, // 16
{
color_series: theme(8),
color_negative: theme(9),
color_markers: theme(8, tint=-0.249977111117893),
color_first: theme(8, tint=-0.249977111117893),
color_last: theme(8, tint=-0.249977111117893),
color_high: theme(8, tint=-0.249977111117893),
color_low: theme(8, tint=-0.249977111117893),
}, // 17
{
color_series: theme(9),
color_negative: theme(4),
color_markers: theme(9, tint=-0.249977111117893),
color_first: theme(9, tint=-0.249977111117893),
color_last: theme(9, tint=-0.249977111117893),
color_high: theme(9, tint=-0.249977111117893),
color_low: theme(9, tint=-0.249977111117893),
}, // 18
{
color_series: theme(4, tint=0.3999755851924192),
color_negative: theme(0, tint=-0.499984740745262),
color_markers: theme(4, tint=0.7999816888943144),
color_first: theme(4, tint=-0.249977111117893),
color_last: theme(4, tint=-0.249977111117893),
color_high: theme(4, tint=-0.499984740745262),
color_low: theme(4, tint=-0.499984740745262),
}, // 19
{
color_series: theme(5, tint=0.3999755851924192),
color_negative: theme(0, tint=-0.499984740745262),
color_markers: theme(5, tint=0.7999816888943144),
color_first: theme(5, tint=-0.249977111117893),
color_last: theme(5, tint=-0.249977111117893),
color_high: theme(5, tint=-0.499984740745262),
color_low: theme(5, tint=-0.499984740745262),
}, // 20
{
color_series: theme(6, tint=0.3999755851924192),
color_negative: theme(0, tint=-0.499984740745262),
color_markers: theme(6, tint=0.7999816888943144),
color_first: theme(6, tint=-0.249977111117893),
color_last: theme(6, tint=-0.249977111117893),
color_high: theme(6, tint=-0.499984740745262),
color_low: theme(6, tint=-0.499984740745262),
}, // 21
{
color_series: theme(7, tint=0.3999755851924192),
color_negative: theme(0, tint=-0.499984740745262),
color_markers: theme(7, tint=0.7999816888943144),
color_first: theme(7, tint=-0.249977111117893),
color_last: theme(7, tint=-0.249977111117893),
color_high: theme(7, tint=-0.499984740745262),
color_low: theme(7, tint=-0.499984740745262),
}, // 22
{
color_series: theme(8, tint=0.3999755851924192),
color_negative: theme(0, tint=-0.499984740745262),
color_markers: theme(8, tint=0.7999816888943144),
color_first: theme(8, tint=-0.249977111117893),
color_last: theme(8, tint=-0.249977111117893),
color_high: theme(8, tint=-0.499984740745262),
color_low: theme(8, tint=-0.499984740745262),
}, // 23
{
color_series: theme(9, tint=0.3999755851924192),
color_negative: theme(0, tint=-0.499984740745262),
color_markers: theme(9, tint=0.7999816888943144),
color_first: theme(9, tint=-0.249977111117893),
color_last: theme(9, tint=-0.249977111117893),
color_high: theme(9, tint=-0.499984740745262),
color_low: theme(9, tint=-0.499984740745262),
}, // 24
{
color_series: theme(1, tint=0.499984740745262),
color_negative: theme(1, tint=0.249977111117893),
color_markers: theme(1, tint=0.249977111117893),
color_first: theme(1, tint=0.249977111117893),
color_last: theme(1, tint=0.249977111117893),
color_high: theme(1, tint=0.249977111117893),
color_low: theme(1, tint=0.249977111117893),
}, // 25
{
color_series: theme(1, tint=0.3499862666707358),
color_negative: theme(0, tint=0.249977111117893),
color_markers: theme(0, tint=0.249977111117893),
color_first: theme(0, tint=0.249977111117893),
color_last: theme(0, tint=0.249977111117893),
color_high: theme(0, tint=0.249977111117893),
color_low: theme(0, tint=0.249977111117893),
}, // 26
{
color_series: { rgb: Some("FF323232"), theme: None, tint: None },
color_negative: { rgb: Some("FFD00000"), theme: None, tint: None },
color_markers: { rgb: Some("FFD00000"), theme: None, tint: None },
color_first: { rgb: Some("FFD00000"), theme: None, tint: None },
color_last: { rgb: Some("FFD00000"), theme: None, tint: None },
color_high: { rgb: Some("FFD00000"), theme: None, tint: None },
color_low: { rgb: Some("FFD00000"), theme: None, tint: None },
}, // 27
{
color_series: { rgb: Some("FF000000"), theme: None, tint: None },
color_negative: { rgb: Some("FF0070C0"), theme: None, tint: None },
color_markers: { rgb: Some("FF0070C0"), theme: None, tint: None },
color_first: { rgb: Some("FF0070C0"), theme: None, tint: None },
color_last: { rgb: Some("FF0070C0"), theme: None, tint: None },
color_high: { rgb: Some("FF0070C0"), theme: None, tint: None },
color_low: { rgb: Some("FF0070C0"), theme: None, tint: None },
}, // 28
{
color_series: { rgb: Some("FF376092"), theme: None, tint: None },
color_negative: { rgb: Some("FFD00000"), theme: None, tint: None },
color_markers: { rgb: Some("FFD00000"), theme: None, tint: None },
color_first: { rgb: Some("FFD00000"), theme: None, tint: None },
color_last: { rgb: Some("FFD00000"), theme: None, tint: None },
color_high: { rgb: Some("FFD00000"), theme: None, tint: None },
color_low: { rgb: Some("FFD00000"), theme: None, tint: None },
}, // 29
{
color_series: { rgb: Some("FF0070C0"), theme: None, tint: None },
color_negative: { rgb: Some("FF000000"), theme: None, tint: None },
color_markers: { rgb: Some("FF000000"), theme: None, tint: None },
color_first: { rgb: Some("FF000000"), theme: None, tint: None },
color_last: { rgb: Some("FF000000"), theme: None, tint: None },
color_high: { rgb: Some("FF000000"), theme: None, tint: None },
color_low: { rgb: Some("FF000000"), theme: None, tint: None },
}, // 30
{
color_series: { rgb: Some("FF5F5F5F"), theme: None, tint: None },
color_negative: { rgb: Some("FFFFB620"), theme: None, tint: None },
color_markers: { rgb: Some("FFD70077"), theme: None, tint: None },
color_first: { rgb: Some("FF5687C2"), theme: None, tint: None },
color_last: { rgb: Some("FF359CEB"), theme: None, tint: None },
color_high: { rgb: Some("FF56BE79"), theme: None, tint: None },
color_low: { rgb: Some("FFFF5055"), theme: None, tint: None },
}, // 31
{
color_series: { rgb: Some("FF5687C2"), theme: None, tint: None },
color_negative: { rgb: Some("FFFFB620"), theme: None, tint: None },
color_markers: { rgb: Some("FFD70077"), theme: None, tint: None },
color_first: { rgb: Some("FF777777"), theme: None, tint: None },
color_last: { rgb: Some("FF359CEB"), theme: None, tint: None },
color_high: { rgb: Some("FF56BE79"), theme: None, tint: None },
color_low: { rgb: Some("FFFF5055"), theme: None, tint: None },
}, // 32
{
color_series: { rgb: Some("FFC6EFCE"), theme: None, tint: None },
color_negative: { rgb: Some("FFFFC7CE"), theme: None, tint: None },
color_markers: { rgb: Some("FF8CADD6"), theme: None, tint: None },
color_first: { rgb: Some("FFFFDC47"), theme: None, tint: None },
color_last: { rgb: Some("FFFFEB9C"), theme: None, tint: None },
color_high: { rgb: Some("FF60D276"), theme: None, tint: None },
color_low: { rgb: Some("FFFF5367"), theme: None, tint: None },
}, // 33
{
color_series: { rgb: Some("FF00B050"), theme: None, tint: None },
color_negative: { rgb: Some("FFFF0000"), theme: None, tint: None },
color_markers: { rgb: Some("FF0070C0"), theme: None, tint: None },
color_first: { rgb: Some("FFFFC000"), theme: None, tint: None },
color_last: { rgb: Some("FFFFC000"), theme: None, tint: None },
color_high: { rgb: Some("FF00B050"), theme: None, tint: None },
color_low: { rgb: Some("FFFF0000"), theme: None, tint: None },
}, // 34
{
color_series: theme(3),
color_negative: theme(9),
color_markers: theme(8),
color_first: theme(4),
color_last: theme(5),
color_high: theme(6),
color_low: theme(7),
}, // 35
]
///|
fn sparkline_preset(style : Int) -> SparklinePreset raise XlsxError {
if style < 0 || style > 35 {
raise InvalidSparkline(msg="sparkline style out of range")
}
sparkline_presets[style]
}
///|
fn write_sparkline_color_xml(
sb : LimitedXmlBuilder,
tag_name : StringView,
color : SparklineColor?,
) -> Unit raise XlsxError {
match color {
None => ()
Some(value) => {
sb.write_view(" {
sb.write_view(" rgb=\"")
sb.write_xml_attr(rgb)
sb.write_view("\"")
}
None => ()
}
match value.theme {
Some(t) => sb.write_view(" theme=\"\{t}\"")
None => ()
}
match value.tint {
Some(t) => sb.write_view(" tint=\"\{t}\"")
None => ()
}
sb.write_view("/>\n")
}
}
}
///|
fn write_sparkline_formula_ref_xml(
sb : LimitedXmlBuilder,
sheet_name : StringView,
range_ref : StringView,
) -> Unit raise XlsxError {
if range_ref.contains("!") {
sb.write_xml_text(range_ref)
} else {
sb.write_char('\'')
for character in sheet_name {
match character {
'\'' => sb.write_view("''")
'&' => sb.write_view("&")
'<' => sb.write_view("<")
'>' => sb.write_view(">")
_ => sb.write_char(character)
}
}
sb.write_view("'!")
sb.write_xml_text(range_ref)
}
}
///|
fn write_sparkline_ext_xml(
sheet_name : StringView,
groups : ArrayView[SparklineGroup],
budget? : WritePartBudget,
) -> String raise XlsxError {
let sb = LimitedXmlBuilder::new(budget)
sb.write_view(" \n")
sb.write_view(
write_sparkline_ext_block_xml(
sheet_name,
groups,
budget?=sb.remaining_budget(),
),
)
sb.write_view(" \n")
sb.to_string()
}
///|
fn write_sparkline_ext_block_xml(
sheet_name : StringView,
groups : ArrayView[SparklineGroup],
budget? : WritePartBudget,
) -> String raise XlsxError {
let sb = LimitedXmlBuilder::new(budget)
sb.write_view(" \n")
sb.write_view(" \n")
for group in groups {
sb.write_view(" sb.write_view(" lineWeight=\"\{value}\"")
None => ()
}
match group.options.manual_max {
Some(value) => sb.write_view(" manualMax=\"\{value}\"")
None => ()
}
match group.options.manual_min {
Some(value) => sb.write_view(" manualMin=\"\{value}\"")
None => ()
}
if group.options.markers {
sb.write_view(" markers=\"\{bool_attr(true)}\"")
}
if group.options.high {
sb.write_view(" high=\"\{bool_attr(true)}\"")
}
if group.options.low {
sb.write_view(" low=\"\{bool_attr(true)}\"")
}
if group.options.first {
sb.write_view(" first=\"\{bool_attr(true)}\"")
}
if group.options.last {
sb.write_view(" last=\"\{bool_attr(true)}\"")
}
if group.options.negative {
sb.write_view(" negative=\"\{bool_attr(true)}\"")
}
if group.options.display_x_axis {
sb.write_view(" displayXAxis=\"\{bool_attr(true)}\"")
}
if group.options.display_hidden {
sb.write_view(" displayHidden=\"\{bool_attr(true)}\"")
}
if group.options.right_to_left {
sb.write_view(" rightToLeft=\"\{bool_attr(true)}\"")
}
sb.write_view(">\n")
write_sparkline_color_xml(sb, "colorSeries", group.options.color_series)
write_sparkline_color_xml(sb, "colorNegative", group.options.color_negative)
write_sparkline_color_xml(sb, "colorAxis", group.options.color_axis)
write_sparkline_color_xml(sb, "colorMarkers", group.options.color_markers)
write_sparkline_color_xml(sb, "colorFirst", group.options.color_first)
write_sparkline_color_xml(sb, "colorLast", group.options.color_last)
write_sparkline_color_xml(sb, "colorHigh", group.options.color_high)
write_sparkline_color_xml(sb, "colorLow", group.options.color_low)
sb.write_view(" \n")
for sparkline in group.sparklines {
sb.write_view(" \n")
sb.write_view(" ")
write_sparkline_formula_ref_xml(sb, sheet_name, sparkline.range_ref)
sb.write_view("\n")
sb.write_view(" ")
sb.write_xml_text(sparkline.location)
sb.write_view("\n")
sb.write_view(" \n")
}
sb.write_view(" \n")
sb.write_view(" \n")
}
sb.write_view(" \n")
sb.write_view(" \n")
sb.to_string()
}
///|
fn parse_sparkline_color(tag : StringView) -> SparklineColor? raise XlsxError {
let rgb = attr_value(tag, "rgb")
let theme = attr_value(tag, "theme")
let tint = attr_value(tag, "tint")
let parsed_rgb = match rgb {
Some(v) => Some(v.to_string())
None => None
}
let parsed_theme = match theme {
Some(v) => Some(@string.parse_int(v.to_string(), base=10) catch { _ => 0 })
None => None
}
let parsed_tint = match tint {
Some(v) => Some(@string.parse_double(v.to_string()) catch { _ => 0.0 })
None => None
}
if parsed_rgb is None && parsed_theme is None {
None
} else {
Some({ rgb: parsed_rgb, theme: parsed_theme, tint: parsed_tint })
}
}
///|
fn find_sparkline_color_in_body(
body : StringView,
tag_name : StringView,
) -> SparklineColor? raise XlsxError {
let text = body.to_owned()
let open = " pos
None => return None
}
let rest = text[start:]
let end = match rest.find(">") {
Some(pos) => pos
None => return None
}
let tag = rest[:end]
parse_sparkline_color(tag)
}
///|
fn parse_sparkline_groups(
xml : StringView,
) -> Array[SparklineGroup] raise XlsxError {
fn normalize_sparkline_range_ref(
range_ref : StringView,
) -> String raise XlsxError {
let text = range_ref.to_owned()
match text.find("!") {
Some(pos) => {
let range_part = text[pos + 1:]
normalize_range_ref(range_part)
}
None => normalize_range_ref(text)
}
}
let groups : Array[SparklineGroup] = []
let mut first = true
for chunk in xml.split("") {
Some(pos) => pos
None => raise InvalidXml(msg="sparklineGroup tag not closed")
}
let tag = text[:tag_end]
let group_end = match text.find("") {
Some(pos) => pos
None => raise InvalidXml(msg="sparklineGroup end missing")
}
let body = text[tag_end + 1:group_end]
let kind = match attr_value(tag, "type") {
Some(value) => sparkline_type_from_string(value)
None => Line
}
let options = SparklineGroupOptions::new()
match attr_value(tag, "displayEmptyCellsAs") {
Some(value) => options.display_empty_cells_as = value.to_string()
None => ()
}
match attr_value(tag, "dateAxis") {
Some(value) => options.date_axis = parse_bool_attr(value)
None => ()
}
match attr_value(tag, "lineWeight") {
Some(value) =>
options.line_weight = Some(
@string.parse_double(value.to_string()) catch {
_ => raise InvalidXml(msg="sparkline lineWeight invalid")
},
)
None => ()
}
match attr_value(tag, "manualMax") {
Some(value) =>
options.manual_max = Some(
@string.parse_int(value.to_string(), base=10) catch {
_ => raise InvalidXml(msg="sparkline manualMax invalid")
},
)
None => ()
}
match attr_value(tag, "manualMin") {
Some(value) =>
options.manual_min = Some(
@string.parse_int(value.to_string(), base=10) catch {
_ => raise InvalidXml(msg="sparkline manualMin invalid")
},
)
None => ()
}
match attr_value(tag, "markers") {
Some(value) => options.markers = parse_bool_attr(value)
None => ()
}
match attr_value(tag, "high") {
Some(value) => options.high = parse_bool_attr(value)
None => ()
}
match attr_value(tag, "low") {
Some(value) => options.low = parse_bool_attr(value)
None => ()
}
match attr_value(tag, "first") {
Some(value) => options.first = parse_bool_attr(value)
None => ()
}
match attr_value(tag, "last") {
Some(value) => options.last = parse_bool_attr(value)
None => ()
}
match attr_value(tag, "negative") {
Some(value) => options.negative = parse_bool_attr(value)
None => ()
}
match attr_value(tag, "displayXAxis") {
Some(value) => options.display_x_axis = parse_bool_attr(value)
None => ()
}
match attr_value(tag, "displayHidden") {
Some(value) => options.display_hidden = parse_bool_attr(value)
None => ()
}
match attr_value(tag, "rightToLeft") {
Some(value) => options.right_to_left = parse_bool_attr(value)
None => ()
}
options.color_series = find_sparkline_color_in_body(body, "colorSeries") catch {
_ => None
}
options.color_negative = find_sparkline_color_in_body(body, "colorNegative") catch {
_ => None
}
options.color_axis = find_sparkline_color_in_body(body, "colorAxis") catch {
_ => None
}
options.color_markers = find_sparkline_color_in_body(body, "colorMarkers") catch {
_ => None
}
options.color_first = find_sparkline_color_in_body(body, "colorFirst") catch {
_ => None
}
options.color_last = find_sparkline_color_in_body(body, "colorLast") catch {
_ => None
}
options.color_high = find_sparkline_color_in_body(body, "colorHigh") catch {
_ => None
}
options.color_low = find_sparkline_color_in_body(body, "colorLow") catch {
_ => None
}
let sparklines : Array[Sparkline] = []
let mut sparkline_first = true
for spark_chunk in body.split("") {
if sparkline_first {
sparkline_first = false
continue
}
let spark_text = spark_chunk.to_owned()
let spark_end = match spark_text.find("") {
Some(pos) => pos
None => raise InvalidXml(msg="sparkline end missing")
}
let spark_body = spark_text[:spark_end]
let range = match spark_body.find("") {
Some(pos) => {
let rest = spark_body[pos + 6:]
let end = match rest.find("") {
Some(value) => value
None => raise InvalidXml(msg="sparkline range end missing")
}
unescape_xml_text(rest[:end])
}
None => raise InvalidXml(msg="sparkline range missing")
}
let location = match spark_body.find("") {
Some(pos) => {
let rest = spark_body[pos + 10:]
let end = match rest.find("") {
Some(value) => value
None => raise InvalidXml(msg="sparkline location end missing")
}
unescape_xml_text(rest[:end])
}
None => raise InvalidXml(msg="sparkline location missing")
}
let (row, col) = cell_ref_to_rc(location)
let canonical_loc = cell_ref_from(row, col)
let normalized_range = normalize_sparkline_range_ref(range)
sparklines.push({ range_ref: normalized_range, location: canonical_loc })
}
if sparklines.length() > 0 {
groups.push({ sparkline_type: kind, sparklines, options })
}
}
groups
}
///|
fn sparkline_group_options_from_sparkline_options(
opts : SparklineOptions,
) -> SparklineGroupOptions raise XlsxError {
let preset = sparkline_preset(opts.style)
let group_opts = SparklineGroupOptions::new()
group_opts.display_empty_cells_as = match opts.empty_cells {
Some(value) => value
None => "gap"
}
group_opts.date_axis = opts.date_axis
group_opts.line_weight = opts.weight
group_opts.manual_max = opts.manual_max
group_opts.manual_min = opts.manual_min
group_opts.high = opts.high
group_opts.low = opts.low
group_opts.first = opts.first
group_opts.last = opts.last
group_opts.negative = opts.negative
group_opts.display_x_axis = opts.axis
group_opts.markers = opts.markers
group_opts.display_hidden = opts.hidden
group_opts.right_to_left = opts.reverse
group_opts.color_axis = Some({
rgb: Some("FF000000"),
theme: None,
tint: None,
})
group_opts.color_series = Some(preset.color_series)
group_opts.color_negative = Some(preset.color_negative)
group_opts.color_markers = Some(preset.color_markers)
group_opts.color_first = Some(preset.color_first)
group_opts.color_last = Some(preset.color_last)
group_opts.color_high = Some(preset.color_high)
group_opts.color_low = Some(preset.color_low)
match opts.series_color {
Some(value) => group_opts.color_series = Some(rgb(value))
None => ()
}
match opts.negative_color {
Some(value) => group_opts.color_negative = Some(rgb(value))
None => ()
}
match opts.markers_color {
Some(value) => group_opts.color_markers = Some(rgb(value))
None => ()
}
match opts.first_color {
Some(value) => group_opts.color_first = Some(rgb(value))
None => ()
}
match opts.last_color {
Some(value) => group_opts.color_last = Some(rgb(value))
None => ()
}
match opts.high_color {
Some(value) => group_opts.color_high = Some(rgb(value))
None => ()
}
match opts.low_color {
Some(value) => group_opts.color_low = Some(rgb(value))
None => ()
}
group_opts
}
///|
fn build_sparklines(
locations : ArrayView[String],
ranges : ArrayView[String],
) -> Array[Sparkline] raise XlsxError {
fn normalize_sparkline_range_for_write(
range_ref : StringView,
) -> String raise XlsxError {
let text = range_ref.to_owned()
match text.find("!") {
Some(pos) => {
let sheet_prefix = text[:pos + 1]
let range_part = text[pos + 1:]
let normalized = normalize_range_ref(range_part)
sheet_prefix.to_owned() + normalized
}
None => normalize_range_ref(text)
}
}
let sparklines : Array[Sparkline] = []
for i, location in locations {
let (row, col) = cell_ref_to_rc(location)
let canonical_loc = cell_ref_from(row, col)
let normalized_range = normalize_sparkline_range_for_write(ranges[i])
sparklines.push({ range_ref: normalized_range, location: canonical_loc })
}
sparklines
}
///|
fn build_sparkline_group_from_options(
opts : SparklineOptions,
) -> SparklineGroup raise XlsxError {
let sparklines = build_sparklines(opts.locations, opts.ranges)
let options = sparkline_group_options_from_sparkline_options(opts)
{ sparkline_type: opts.sparkline_type, sparklines, options }
}