// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Immutable graph input types for layout engines.
///
/// Engines must treat their inputs as read-only. To enforce this at the type
/// level, the engine-facing graph type (`GraphInput`) and all nested types must
/// not contain `mut` fields.
///|
pub struct GraphInput {
name : String
root : ObjectInput
objects : Array[ObjectInput]
edges : Array[EdgeInput]
sequence_fragments : Array[SequenceFragmentInput]
activation_boxes : Array[(String, Box)]
sequence_notes : Array[(String, String, Box)]
sequence_fragments_layout : Array[SequenceFragmentLayout]
layers : Array[LayerInput]
scenarios : Array[ScenarioInput]
steps : Array[StepInput]
legend : LegendInput?
is_folder_only : Bool
data : Map[String, GraphDataValue]
} derive(Debug)
///|
pub fn GraphInput::new(name : String) -> GraphInput {
let root = ObjectInput::new("root")
{
name,
root,
objects: [],
edges: [],
sequence_fragments: [],
activation_boxes: [],
sequence_notes: [],
sequence_fragments_layout: [],
layers: [],
scenarios: [],
steps: [],
legend: None,
is_folder_only: false,
data: Map([]),
}
}
///|
pub fn GraphInput::from_parts(
name : String,
root : ObjectInput,
objects : Array[ObjectInput],
edges : Array[EdgeInput],
sequence_fragments : Array[SequenceFragmentInput],
activation_boxes : Array[(String, Box)],
sequence_notes : Array[(String, String, Box)],
sequence_fragments_layout : Array[SequenceFragmentLayout],
layers : Array[LayerInput],
scenarios : Array[ScenarioInput],
steps : Array[StepInput],
legend : LegendInput?,
is_folder_only? : Bool = false,
data? : Map[String, GraphDataValue] = Map([]),
) -> GraphInput {
{
name,
root,
objects,
edges,
sequence_fragments,
activation_boxes,
sequence_notes,
sequence_fragments_layout,
layers,
scenarios,
steps,
legend,
is_folder_only,
data,
}
}
///|
pub fn GraphInput::find_object(self : GraphInput, id : String) -> ObjectInput? {
if self.root.abs_id_syntax == id {
return Some(self.root)
}
for obj in self.objects {
if obj.abs_id_syntax == id {
return Some(obj)
}
}
if self.root.id == id {
return Some(self.root)
}
for obj in self.objects {
if obj.id == id {
return Some(obj)
}
}
None
}
///|
pub fn GraphInput::children_of(
self : GraphInput,
obj : ObjectInput,
) -> Array[ObjectInput] {
let children : Array[ObjectInput] = []
for child_id in obj.child_ids {
match self.find_object(child_id) {
Some(child) => children.push(child)
None => ()
}
}
children
}
///|
pub fn GraphInput::get_objects(self : GraphInput) -> Array[ObjectInput] {
self.objects
}
///|
pub fn GraphInput::get_edges(self : GraphInput) -> Array[EdgeInput] {
self.edges
}
///|
/// Whether this graph or one of its child boards contains a LaTeX label.
pub fn GraphInput::uses_latex_labels(self : GraphInput) -> Bool {
if self.root.language == Some("latex") {
return true
}
for obj in self.objects {
if obj.language == Some("latex") {
return true
}
}
for layer in self.layers {
if layer.graph.uses_latex_labels() {
return true
}
}
for scenario in self.scenarios {
if scenario.graph.uses_latex_labels() {
return true
}
}
for step in self.steps {
if step.graph.uses_latex_labels() {
return true
}
}
false
}
///|
fn StyleInput::enables_sketch(self : StyleInput) -> Bool {
match self.sketch {
Some(value) => value.as_bool().unwrap_or(false)
None => false
}
}
///|
/// Whether this graph or one of its child boards explicitly enables sketch
/// rendering on an object or edge.
pub fn GraphInput::uses_sketch_styles(self : GraphInput) -> Bool {
if self.root.style.enables_sketch() {
return true
}
for obj in self.objects {
if obj.style.enables_sketch() {
return true
}
}
for edge in self.edges {
if edge.style.enables_sketch() {
return true
}
}
for layer in self.layers {
if layer.graph.uses_sketch_styles() {
return true
}
}
for scenario in self.scenarios {
if scenario.graph.uses_sketch_styles() {
return true
}
}
for step in self.steps {
if step.graph.uses_sketch_styles() {
return true
}
}
false
}
///|
fn StyleInput::with_default_font(
self : StyleInput,
font : String,
) -> StyleInput {
if self.font is Some(_) {
self
} else {
{ ..self, font: Some(StyleValue::from_string(font)) }
}
}
///|
/// Apply a layout-time fallback font without overriding explicit object or edge
/// font styles. Theme rules use this before invoking any layout backend so text
/// measurement remains backend-independent.
pub fn GraphInput::with_default_font(
self : GraphInput,
font : String,
) -> GraphInput {
let objects = self.objects.map(fn(obj) {
{ ..obj, style: obj.style.with_default_font(font) }
})
let edges = self.edges.map(fn(edge) {
{ ..edge, style: edge.style.with_default_font(font) }
})
let layers = self.layers.map(fn(layer) {
LayerInput::new(layer.name, layer.graph.with_default_font(font))
})
let scenarios = self.scenarios.map(fn(scenario) {
ScenarioInput::new(scenario.name, scenario.graph.with_default_font(font))
})
let steps = self.steps.map(fn(step) {
StepInput::new(step.name, step.graph.with_default_font(font))
})
{
..self,
root: { ..self.root, style: self.root.style.with_default_font(font) },
objects,
edges,
layers,
scenarios,
steps,
}
}
///|
fn StyleInput::disables_text_transform(self : StyleInput) -> Bool {
match self.text_transform {
Some(value) => value.get_value().to_lower() == "none"
None => false
}
}
///|
fn ObjectInput::with_caps_lock(self : ObjectInput) -> ObjectInput {
if self.shape_type == Code ||
self.language == Some("latex") ||
self.style.disables_text_transform() {
self
} else {
{ ..self, label: self.label.to_upper() }
}
}
///|
fn EdgeInput::with_caps_lock(self : EdgeInput) -> EdgeInput {
if self.style.disables_text_transform() {
self
} else {
{ ..self, label: self.label.to_upper() }
}
}
///|
/// Apply a theme's caps-lock rule before layout so measured labels and rendered
/// labels use the same text. Explicit `text-transform: none`, code blocks, and
/// LaTeX labels retain their source text, matching D2's theme semantics.
pub fn GraphInput::with_caps_lock(self : GraphInput) -> GraphInput {
let objects = self.objects.map(fn(obj) { obj.with_caps_lock() })
let edges = self.edges.map(fn(edge) { edge.with_caps_lock() })
let layers = self.layers.map(fn(layer) {
LayerInput::new(layer.name, layer.graph.with_caps_lock())
})
let scenarios = self.scenarios.map(fn(scenario) {
ScenarioInput::new(scenario.name, scenario.graph.with_caps_lock())
})
let steps = self.steps.map(fn(step) {
StepInput::new(step.name, step.graph.with_caps_lock())
})
{
..self,
root: self.root.with_caps_lock(),
objects,
edges,
layers,
scenarios,
steps,
}
}
///|
fn copy_graph_data_value(value : GraphDataValue) -> GraphDataValue {
match value {
Text(text) => Text(text)
StringArray(values) => {
let copied : Array[String] = []
for item in values {
copied.push(item)
}
StringArray(copied)
}
}
}
///|
pub fn split_syntax_path(id : String) -> Array[String] {
if id.length() == 0 {
return []
}
let segments : Array[String] = []
let current = StringBuilder::new()
let mut quote : Char? = None
let mut escaped = false
for ch in id {
if escaped {
current.write_char(ch)
escaped = false
continue
}
match quote {
Some(q) => {
current.write_char(ch)
if ch == '\\' {
escaped = true
} else if ch == q {
quote = None
}
}
None => {
if ch == '.' {
segments.push(current.to_string())
current.reset()
continue
}
current.write_char(ch)
if ch == '"' || ch == '\'' {
quote = Some(ch)
}
}
}
}
segments.push(current.to_string())
segments
}
///|
pub fn syntax_path_depth(id : String) -> Int {
split_syntax_path(id).length()
}
///|
pub fn syntax_path_last(id : String) -> String {
let segments = split_syntax_path(id)
if segments.is_empty() {
id
} else {
segments[segments.length() - 1]
}
}
///|
pub fn display_key_segment(segment : String) -> String {
if segment.length() < 2 {
return segment
}
let quote = segment[0]
if (quote != '"' && quote != '\'') || segment[segment.length() - 1] != quote {
return segment
}
let out = StringBuilder::new()
let mut escaped = false
for i = 1; i < segment.length() - 1; i = i + 1 {
match segment[i].to_char() {
Some(ch) =>
if escaped {
if quote == '"' {
match ch {
'a' => out.write_char('\u{7}')
'b' => out.write_char('\u{8}')
'f' => out.write_char('\u{c}')
'n' => out.write_char('\n')
'r' => out.write_char('\r')
't' => out.write_char('\t')
'v' => out.write_char('\u{b}')
_ => out.write_char(ch)
}
} else {
out.write_char(ch)
}
escaped = false
} else if ch == '\\' {
escaped = true
} else {
out.write_char(ch)
}
None => ()
}
}
if escaped {
out.write_char('\\')
}
out.to_string()
}
///|
pub fn syntax_path_last_display(id : String) -> String {
display_key_segment(syntax_path_last(id))
}
///|
pub struct LayerInput {
name : String
graph : GraphInput
} derive(Debug)
///|
pub fn LayerInput::new(name : String, graph : GraphInput) -> LayerInput {
{ name, graph }
}
///|
pub struct ScenarioInput {
name : String
graph : GraphInput
} derive(Debug)
///|
pub fn ScenarioInput::new(name : String, graph : GraphInput) -> ScenarioInput {
{ name, graph }
}
///|
pub struct StepInput {
name : String
graph : GraphInput
} derive(Debug)
///|
pub fn StepInput::new(name : String, graph : GraphInput) -> StepInput {
{ name, graph }
}
///|
pub struct SequenceFragmentInput {
fragment_type : String
condition : String?
start_index : Int
end_index : Int
operands : Array[FragmentOperandInput]
} derive(Debug)
///|
pub fn SequenceFragmentInput::new(
fragment_type : String,
condition : String?,
start_index : Int,
end_index : Int,
operands : Array[FragmentOperandInput],
) -> SequenceFragmentInput {
{ fragment_type, condition, start_index, end_index, operands }
}
///|
pub struct FragmentOperandInput {
condition : String?
} derive(Debug)
///|
pub fn FragmentOperandInput::new(condition : String?) -> FragmentOperandInput {
{ condition, }
}
///|
pub fn ObjectReference::contained_by(
self : ObjectReference,
abs_id_syntax : String,
) -> Bool {
match self.scope_abs_id_syntax {
Some(scope) =>
scope == abs_id_syntax || scope.has_prefix(abs_id_syntax + ".")
None => false
}
}
///|
pub fn EdgeReference::contained_by(
self : EdgeReference,
abs_id_syntax : String,
) -> Bool {
match self.scope_abs_id_syntax {
Some(scope) =>
scope == abs_id_syntax || scope.has_prefix(abs_id_syntax + ".")
None => false
}
}
///|
/// Immutable representation of a graph object for layout engines.
///
/// Fields:
///
/// * `id` : Compatibility absolute identifier using semantic path values.
/// * `id_val` : Semantic value of the local object identifier.
/// * `id_syntax` : Canonical source syntax for the local object identifier.
/// * `abs_id_syntax` : Canonical source syntax for the absolute object path.
/// * `references` : Source references that compiled into this object.
/// * `label` : Display text for the object.
/// * `shape_type` : Visual shape of the object.
/// * `style` : Visual styling properties.
/// * `box` : Optional precomputed bounding box.
/// * `label_box` : Optional precomputed label bounding box.
/// * `child_ids` : Array of child object absolute syntax identifiers.
/// * `z_index` : Layer ordering for visual stacking.
/// * `icon` : Optional icon identifier.
/// * `tooltip` : Optional tooltip text.
/// * `link` : Optional hyperlink URL.
/// * `classes` : Array of CSS class names for styling.
/// * `language` : Optional language tag preserved for code/text handling.
/// * `sql_constraints` : Optional SQL constraints preserved for class/sql_table children.
/// * `grid_rows` : Optional number of grid rows for layout.
/// * `grid_columns` : Optional number of grid columns for layout.
/// * `grid_gap` : Optional spacing between grid items.
/// * `horizontal_gap` : Optional horizontal spacing between children.
/// * `vertical_gap` : Optional vertical spacing between children.
/// * `grid_column_span` : Optional number of columns this object spans.
/// * `grid_row_span` : Optional number of rows this object spans.
/// * `near` : Optional identifier of object to position near.
/// * `top` : Optional fixed top coordinate in pixels.
/// * `left` : Optional fixed left coordinate in pixels.
/// * `direction` : Optional layout direction override (`up|down|left|right`).
///
pub struct ObjectInput {
id : String
id_val : String
id_syntax : String
abs_id_syntax : String
references : Array[ObjectReference]
label : String
shape_type : ShapeType
style : StyleInput
box : Box?
label_box : Box?
child_ids : Array[String]
z_index : Int
icon : String?
icon_position : String?
tooltip : String?
tooltip_position : String?
link : String?
label_position : String?
classes : Array[String]
language : String?
sql_constraints : Array[String]
grid_rows : Int?
grid_columns : Int?
grid_row_directed : Bool?
grid_gap : Double?
horizontal_gap : Double?
vertical_gap : Double?
grid_column_span : Int?
grid_row_span : Int?
near : String?
top : Int?
left : Int?
direction : String?
} derive(Debug)
///|
pub fn ObjectInput::new(
id : String,
id_val? : String = id,
id_syntax? : String = id,
abs_id_syntax? : String = id,
) -> ObjectInput {
{
id,
id_val,
id_syntax,
abs_id_syntax,
references: [],
label: id_val,
shape_type: Rectangle,
style: StyleInput::new(),
box: None,
label_box: None,
child_ids: [],
z_index: 0,
icon: None,
icon_position: None,
tooltip: None,
tooltip_position: None,
link: None,
label_position: None,
classes: [],
language: None,
sql_constraints: [],
grid_rows: None,
grid_columns: None,
grid_row_directed: None,
grid_gap: None,
horizontal_gap: None,
vertical_gap: None,
grid_column_span: None,
grid_row_span: None,
near: None,
top: None,
left: None,
direction: None,
}
}
///|
pub fn ObjectInput::from_parts(
id : String,
label : String,
shape_type : ShapeType,
style : StyleInput,
box : Box?,
label_box : Box?,
child_ids : Array[String],
z_index : Int,
icon : String?,
tooltip : String?,
link : String?,
classes : Array[String],
grid_rows : Int?,
grid_columns : Int?,
grid_gap : Double?,
horizontal_gap : Double?,
vertical_gap : Double?,
grid_column_span : Int?,
grid_row_span : Int?,
near : String?,
top : Int?,
left : Int?,
direction? : String? = None,
language? : String? = None,
sql_constraints? : Array[String] = [],
id_val? : String = id,
id_syntax? : String = id,
abs_id_syntax? : String = id,
references? : Array[ObjectReference] = [],
icon_position? : String? = None,
tooltip_position? : String? = None,
label_position? : String? = None,
grid_row_directed? : Bool? = None,
) -> ObjectInput {
{
id,
id_val,
id_syntax,
abs_id_syntax,
references,
label,
shape_type,
style,
box,
label_box,
child_ids,
z_index,
icon,
tooltip,
link,
icon_position,
tooltip_position,
label_position,
classes,
language,
sql_constraints,
grid_rows,
grid_columns,
grid_row_directed,
grid_gap,
horizontal_gap,
vertical_gap,
grid_column_span,
grid_row_span,
near,
top,
left,
direction,
}
}
///|
pub fn ObjectInput::with_direction(
self : ObjectInput,
direction : String?,
) -> ObjectInput {
{ ..self, direction, }
}
///|
pub fn ObjectInput::lookup_id(self : ObjectInput) -> String {
self.abs_id_syntax
}
///|
pub fn ObjectInput::get_box(self : ObjectInput) -> Box? {
self.box
}
///|
pub fn ObjectInput::get_label_box(self : ObjectInput) -> Box? {
self.label_box
}
///|
pub struct EdgeInput {
index : Int
src_id : String
dst_id : String
src_id_syntax : String
dst_id_syntax : String
references : Array[EdgeReference]
classes : Array[String]
src_arrow : Bool
dst_arrow : Bool
src_arrowhead : ArrowheadType
dst_arrowhead : ArrowheadType
src_arrowhead_label : String?
dst_arrowhead_label : String?
src_arrowhead_label_color : String?
dst_arrowhead_label_color : String?
src_anchor : String?
dst_anchor : String?
label : String
icon : String?
icon_position : String?
icon_border_radius : Double?
link : String?
style : StyleInput
route : Array[Point]
bend_points : Array[Point]?
is_curve : Bool
z_index : Int
reference_count : Int
label_box : Box?
src_column_index : Int?
dst_column_index : Int?
} derive(Debug)
///|
pub fn EdgeInput::new(
index : Int,
src_id : String,
dst_id : String,
src_arrow : Bool,
dst_arrow : Bool,
src_id_syntax? : String = src_id,
dst_id_syntax? : String = dst_id,
) -> EdgeInput {
{
index,
src_id,
dst_id,
src_id_syntax,
dst_id_syntax,
references: [],
classes: [],
src_arrow,
dst_arrow,
src_arrowhead: if src_arrow {
Triangle
} else {
None
},
dst_arrowhead: if dst_arrow {
Triangle
} else {
None
},
src_arrowhead_label: None,
dst_arrowhead_label: None,
src_arrowhead_label_color: None,
dst_arrowhead_label_color: None,
src_anchor: None,
dst_anchor: None,
label: "",
icon: None,
icon_position: None,
icon_border_radius: None,
link: None,
style: StyleInput::new(),
route: [],
bend_points: None,
is_curve: false,
z_index: 0,
reference_count: 1,
label_box: None,
src_column_index: None,
dst_column_index: None,
}
}
///|
pub fn EdgeInput::from_parts(
index : Int,
src_id : String,
dst_id : String,
src_arrow : Bool,
dst_arrow : Bool,
src_arrowhead : ArrowheadType,
dst_arrowhead : ArrowheadType,
src_arrowhead_label : String?,
dst_arrowhead_label : String?,
src_arrowhead_label_color : String?,
dst_arrowhead_label_color : String?,
src_anchor : String?,
dst_anchor : String?,
label : String,
style : StyleInput,
route : Array[Point],
bend_points : Array[Point]?,
is_curve : Bool,
z_index : Int,
reference_count : Int,
label_box : Box?,
src_column_index : Int?,
dst_column_index : Int?,
src_id_syntax? : String = src_id,
dst_id_syntax? : String = dst_id,
references? : Array[EdgeReference] = [],
icon? : String? = None,
icon_position? : String? = None,
icon_border_radius? : Double? = None,
link? : String? = None,
classes? : Array[String] = [],
) -> EdgeInput {
{
index,
src_id,
dst_id,
src_id_syntax,
dst_id_syntax,
references,
classes,
src_arrow,
dst_arrow,
src_arrowhead,
dst_arrowhead,
src_arrowhead_label,
dst_arrowhead_label,
src_arrowhead_label_color,
dst_arrowhead_label_color,
src_anchor,
dst_anchor,
label,
icon,
icon_position,
icon_border_radius,
link,
style,
route,
bend_points,
is_curve,
z_index,
reference_count,
label_box,
src_column_index,
dst_column_index,
}
}
///|
pub fn EdgeInput::get_src_id(self : EdgeInput) -> String {
self.src_id
}
///|
pub fn EdgeInput::lookup_src_id(self : EdgeInput) -> String {
self.src_id_syntax
}
///|
pub fn EdgeInput::get_dst_id(self : EdgeInput) -> String {
self.dst_id
}
///|
pub fn EdgeInput::lookup_dst_id(self : EdgeInput) -> String {
self.dst_id_syntax
}
///|
pub fn EdgeInput::get_label_box(self : EdgeInput) -> Box? {
self.label_box
}
///|
/// A style value with its source range.
///
/// The value is preserved as a string. Consumers may parse it as needed.
pub struct StyleValue {
value : String
range : @lexer.Range
} derive(Eq, Debug)
///|
pub fn StyleValue::new(value : String, range : @lexer.Range) -> StyleValue {
{ value, range }
}
///|
/// Create a style value without a concrete source location.
pub fn StyleValue::synthetic(value : String) -> StyleValue {
let p = @lexer.Position::zero()
{ value, range: @lexer.Range::new(p, p) }
}
///|
pub fn StyleValue::as_bool(self : StyleValue) -> Bool? {
match self.value.to_lower() {
"true" => Some(true)
"false" => Some(false)
_ => None
}
}
///|
pub fn StyleValue::as_int(self : StyleValue) -> Int? {
Some(@string.parse_int(self.value)) catch {
_ => None
}
}
///|
pub fn StyleValue::as_double(self : StyleValue) -> Double? {
Some(@string.parse_double(self.value)) catch {
_ => None
}
}
///|
pub fn StyleValue::get_value(self : StyleValue) -> String {
self.value
}
///|
pub fn StyleValue::get_range(self : StyleValue) -> @lexer.Range {
self.range
}
///|
pub fn StyleValue::from_string(value : String) -> StyleValue {
StyleValue::synthetic(value)
}
///|
pub fn StyleValue::from_bool(value : Bool) -> StyleValue {
let s = if value { "true" } else { "false" }
StyleValue::synthetic(s)
}
///|
pub fn StyleValue::from_int(value : Int) -> StyleValue {
StyleValue::synthetic("\{value}")
}
///|
pub fn StyleValue::from_double(value : Double) -> StyleValue {
StyleValue::synthetic("\{value}")
}
///|
pub fn StyleValue::from_string_opt(value : String?) -> StyleValue? {
match value {
Some(v) => Some(StyleValue::from_string(v))
None => None
}
}
///|
pub fn StyleValue::from_bool_opt(value : Bool?) -> StyleValue? {
match value {
Some(v) => Some(StyleValue::from_bool(v))
None => None
}
}
///|
pub fn StyleValue::from_int_opt(value : Int?) -> StyleValue? {
match value {
Some(v) => Some(StyleValue::from_int(v))
None => None
}
}
///|
pub fn StyleValue::from_double_opt(value : Double?) -> StyleValue? {
match value {
Some(v) => Some(StyleValue::from_double(v))
None => None
}
}
///|
pub struct StyleInput {
opacity : StyleValue?
stroke : StyleValue?
fill : StyleValue?
stroke_width : StyleValue?
stroke_dash : StyleValue?
border_radius : StyleValue?
shadow : StyleValue?
three_d : StyleValue?
multiple : StyleValue?
font : StyleValue?
font_size : StyleValue?
font_color : StyleValue?
bold : StyleValue?
italic : StyleValue?
underline : StyleValue?
animated : StyleValue?
filled : StyleValue?
double_border : StyleValue?
text_transform : StyleValue?
font_family : StyleValue?
fill_pattern : StyleValue?
sketch : StyleValue?
curved : StyleValue?
} derive(Eq, Debug)
///|
pub fn StyleInput::new() -> StyleInput {
{
opacity: None,
stroke: None,
fill: None,
stroke_width: None,
stroke_dash: None,
border_radius: None,
shadow: None,
three_d: None,
multiple: None,
font: None,
font_size: None,
font_color: None,
bold: None,
italic: None,
underline: None,
animated: None,
filled: None,
double_border: None,
text_transform: None,
font_family: None,
fill_pattern: None,
sketch: None,
curved: None,
}
}
///|
pub fn StyleInput::from_parts(
opacity : StyleValue?,
stroke : StyleValue?,
fill : StyleValue?,
stroke_width : StyleValue?,
stroke_dash : StyleValue?,
border_radius : StyleValue?,
shadow : StyleValue?,
three_d : StyleValue?,
multiple : StyleValue?,
font : StyleValue?,
font_size : StyleValue?,
font_color : StyleValue?,
bold : StyleValue?,
italic : StyleValue?,
underline : StyleValue?,
animated : StyleValue?,
filled : StyleValue?,
double_border : StyleValue?,
text_transform : StyleValue?,
font_family : StyleValue?,
fill_pattern : StyleValue?,
sketch : StyleValue?,
curved : StyleValue?,
) -> StyleInput {
{
opacity,
stroke,
fill,
stroke_width,
stroke_dash,
border_radius,
shadow,
three_d,
multiple,
font,
font_size,
font_color,
bold,
italic,
underline,
animated,
filled,
double_border,
text_transform,
font_family,
fill_pattern,
sketch,
curved,
}
}
///|
pub enum LegendEntryInput {
ShapeEntry(
id~ : String,
shape~ : ShapeType,
label~ : String,
style~ : StyleInput?
)
ConnectionEntry(
src_id~ : String,
dst_id~ : String,
index~ : Int,
src_arrow_enabled~ : Bool,
dst_arrow_enabled~ : Bool,
src_arrow~ : ArrowheadType,
dst_arrow~ : ArrowheadType,
label~ : String,
style~ : StyleInput?
)
CustomEntry(icon~ : String, label~ : String)
Separator
} derive(Debug)
///|
pub fn LegendEntryInput::shape(
shape : ShapeType,
label : String,
style : StyleInput?,
id? : String = label,
) -> LegendEntryInput {
ShapeEntry(id~, shape~, label~, style~)
}
///|
pub fn LegendEntryInput::connection(
src_arrow : ArrowheadType,
dst_arrow : ArrowheadType,
label : String,
style : StyleInput?,
src_id? : String = "src",
dst_id? : String = "dst",
index? : Int = 0,
src_arrow_enabled? : Bool = src_arrow != None,
dst_arrow_enabled? : Bool = dst_arrow != None,
) -> LegendEntryInput {
ConnectionEntry(
src_id~,
dst_id~,
index~,
src_arrow_enabled~,
dst_arrow_enabled~,
src_arrow~,
dst_arrow~,
label~,
style~,
)
}
///|
pub fn LegendEntryInput::custom(
icon : String,
label : String,
) -> LegendEntryInput {
CustomEntry(icon~, label~)
}
///|
pub fn LegendEntryInput::separator() -> LegendEntryInput {
Separator
}
///|
pub struct LegendInput {
title : String?
position : LegendPosition
entries : Array[LegendEntryInput]
fill : String?
stroke : String?
padding : Double
entry_gap : Double
icon_size : Double
} derive(Debug)
///|
pub fn LegendInput::from_parts(
title : String?,
position : LegendPosition,
entries : Array[LegendEntryInput],
fill : String?,
stroke : String?,
padding : Double,
entry_gap : Double,
icon_size : Double,
) -> LegendInput {
{ title, position, entries, fill, stroke, padding, entry_gap, icon_size }
}
///|
pub fn LegendInput::is_empty(self : LegendInput) -> Bool {
self.entries.is_empty()
}
///|
pub fn LegendInput::entry_count(self : LegendInput) -> Int {
let mut count = 0
for entry in self.entries {
match entry {
Separator => ()
_ => count += 1
}
}
count
}
///|
pub fn GraphInput::from_model(graph : GraphModel) -> GraphInput {
let objects : Array[ObjectInput] = []
for obj in graph.objects {
objects.push(ObjectInput::from_model(obj))
}
let edges : Array[EdgeInput] = []
for e in graph.edges {
edges.push(EdgeInput::from_model(e))
}
let fragments : Array[SequenceFragmentInput] = []
for f in graph.sequence_fragments {
fragments.push(SequenceFragmentInput::from_model(f))
}
let layers : Array[LayerInput] = []
for layer in graph.layers {
layers.push(
LayerInput::new(layer.name, GraphInput::from_model(layer.graph)),
)
}
let scenarios : Array[ScenarioInput] = []
for scenario in graph.scenarios {
scenarios.push(
ScenarioInput::new(scenario.name, GraphInput::from_model(scenario.graph)),
)
}
let steps : Array[StepInput] = []
for step in graph.steps {
steps.push(StepInput::new(step.name, GraphInput::from_model(step.graph)))
}
let data : Map[String, GraphDataValue] = Map([])
for key, value in graph.data {
data[key] = copy_graph_data_value(value)
}
{
name: graph.name,
root: ObjectInput::from_model(graph.root),
objects,
edges,
sequence_fragments: fragments,
activation_boxes: [],
sequence_notes: [],
sequence_fragments_layout: [],
layers,
scenarios,
steps,
legend: match graph.legend {
Some(l) => Some(LegendInput::from_model(l))
None => None
},
is_folder_only: graph.is_folder_only,
data,
}
}
///|
pub fn ObjectInput::from_model(obj : ObjectModel) -> ObjectInput {
let child_ids : Array[String] = []
for child in obj.children {
child_ids.push(child.abs_id_syntax)
}
let classes : Array[String] = []
for c in obj.classes {
classes.push(c)
}
let sql_constraints : Array[String] = []
for constraint in obj.sql_constraints {
sql_constraints.push(constraint)
}
let references : Array[ObjectReference] = []
for reference in obj.references {
references.push(reference)
}
ObjectInput::from_parts(
obj.id,
obj.label,
obj.shape_type,
StyleInput::from_model(obj.style),
obj.box,
obj.label_box,
child_ids,
obj.z_index,
obj.icon,
obj.tooltip,
obj.link,
classes,
obj.grid_rows,
obj.grid_columns,
obj.grid_gap,
obj.horizontal_gap,
obj.vertical_gap,
obj.grid_column_span,
obj.grid_row_span,
obj.near,
obj.top,
obj.left,
direction=obj.direction,
language=obj.language,
sql_constraints~,
id_val=obj.id_val,
id_syntax=obj.id_syntax,
abs_id_syntax=obj.abs_id_syntax,
references~,
icon_position=obj.icon_position,
tooltip_position=obj.tooltip_position,
label_position=obj.label_position,
)
}
///|
pub fn EdgeInput::from_model(edge : EdgeModel) -> EdgeInput {
let bend_points = match edge.bend_points {
Some(ps) => {
let copied : Array[Point] = []
for p in ps {
copied.push(p)
}
Some(copied)
}
None => None
}
let references : Array[EdgeReference] = []
for reference in edge.references {
references.push(reference)
}
EdgeInput::from_parts(
edge.index,
edge.src_id,
edge.dst_id,
edge.src_arrow,
edge.dst_arrow,
edge.src_arrowhead,
edge.dst_arrowhead,
edge.src_arrowhead_label,
edge.dst_arrowhead_label,
edge.src_arrowhead_label_color,
edge.dst_arrowhead_label_color,
edge.src_anchor,
edge.dst_anchor,
edge.label,
StyleInput::from_model(edge.style),
[],
bend_points,
edge.is_curve,
edge.z_index,
edge.reference_count,
edge.label_box,
edge.src_column_index,
edge.dst_column_index,
src_id_syntax=edge.src_id_syntax,
dst_id_syntax=edge.dst_id_syntax,
references~,
icon=edge.icon,
icon_position=edge.icon_position,
icon_border_radius=edge.icon_border_radius,
link=edge.link,
classes=edge.classes,
)
}
///|
pub fn StyleInput::from_model(style : StyleModel) -> StyleInput {
StyleInput::from_parts(
style.opacity,
style.stroke,
style.fill,
style.stroke_width,
style.stroke_dash,
style.border_radius,
style.shadow,
style.three_d,
style.multiple,
style.font,
style.font_size,
style.font_color,
style.bold,
style.italic,
style.underline,
style.animated,
style.filled,
style.double_border,
style.text_transform,
style.font_family,
style.fill_pattern,
style.sketch,
style.curved,
)
}
///|
pub fn SequenceFragmentInput::from_model(
fragment : SequenceFragmentModel,
) -> SequenceFragmentInput {
let operands : Array[FragmentOperandInput] = []
for op in fragment.operands {
operands.push({ condition: op.condition })
}
{
fragment_type: fragment.fragment_type,
condition: fragment.condition,
start_index: fragment.start_index,
end_index: fragment.end_index,
operands,
}
}
///|
pub fn LegendInput::from_model(legend : LegendModel) -> LegendInput {
let entries : Array[LegendEntryInput] = []
for entry in legend.entries {
entries.push(entry)
}
LegendInput::from_parts(
legend.title,
legend.position,
entries,
legend.fill,
legend.stroke,
legend.padding,
legend.entry_gap,
legend.icon_size,
)
}