back references for visnodes
This commit is contained in:
parent
f02d8e419e
commit
ad35f289c7
19 changed files with 354 additions and 220 deletions
15
bank.svg
Normal file
15
bank.svg
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 1387 135"><style>svg {
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
.node {
|
||||
border: 1px solid black;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
font-size: 10px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
</style><rect id="calc" x="45" y="45" width="61" height="15" stroke="white" fill="none" /><text x="75" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="75" dy="10">Calculation</tspan> /><rect id="acc_interest_calc" x="121" y="45" width="145" height="15" stroke="white" fill="none" /><text x="193" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="193" dy="10">Account interest Calculation</tspan> /><rect id="interest_rates" x="281" y="45" width="76" height="15" stroke="white" fill="none" /><text x="319" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="319" dy="10">Interest Rates</tspan> /><rect id="config" x="372" y="45" width="71" height="15" stroke="white" fill="none" /><text x="407" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="407" dy="10">Configuration</tspan> /><rect id="nob_execution" x="473" y="60" width="71" height="15" stroke="white" fill="none" /><text x="508" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="508" dy="10">NoB Execution</tspan> /><rect id="coll_reinst_inst" x="559" y="60" width="204" height="15" stroke="white" fill="none" /><text x="661" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="661" dy="10">Collection of Reinstatement instructions</tspan> /><rect id="nob" x="458" y="45" width="320" height="45" stroke="white" fill="none" /><text x="618" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="618" dy="10">NoB</tspan> /><rect id="reporting" x="793" y="45" width="52" height="15" stroke="white" fill="none" /><text x="819" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="819" dy="10">Reporting</tspan> /><rect id="functions" x="30" y="30" width="830" height="75" stroke="white" fill="none" /><text x="445" y="30" fill="white" text-anchor="middle" stroke-width="1" class="node" /><rect id="bank_motor" x="905" y="60" width="57" height="15" stroke="white" fill="none" /><text x="933" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="933" dy="10">Bank Motor</tspan> /><rect id="bank_scripts" x="977" y="60" width="66" height="15" stroke="white" fill="none" /><text x="1010" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="1010" dy="10">Bank Scripts</tspan> /><rect id="bank_client" x="1058" y="60" width="61" height="15" stroke="white" fill="none" /><text x="1088" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="1088" dy="10">Bank Client</tspan> /><rect id="bank_db" x="1134" y="60" width="42" height="15" stroke="white" fill="none" /><text x="1155" y="60" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="1155" dy="10">Bank DB</tspan> /><rect id="bank" x="890" y="45" width="301" height="45" stroke="white" fill="none" /><text x="1040" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="1040" dy="10">Bank</tspan> /><rect id="interest_engine" x="1206" y="45" width="76" height="15" stroke="white" fill="none" /><text x="1244" y="45" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="1244" dy="10">InterestEngine</tspan> /><rect id="systems" x="875" y="30" width="422" height="75" stroke="white" fill="none" /><text x="1086" y="30" fill="white" text-anchor="middle" stroke-width="1" class="node" /><rect id="lanes" x="15" y="15" width="1297" height="105" stroke="white" fill="none" /><text x="663" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node" /><rect id="bank" x="1327" y="15" width="0" height="0" stroke="white" fill="none" /><text x="1327" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node" /><rect id="bank_scripts" x="1342" y="15" width="0" height="0" stroke="white" fill="none" /><text x="1342" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node" /><rect id="bank_motor" x="1357" y="15" width="0" height="0" stroke="white" fill="none" /><text x="1357" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node" /><rect id="interest_engine" x="1372" y="15" width="0" height="0" stroke="white" fill="none" /><text x="1372" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node" /></svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
|
|
@ -28,6 +28,9 @@ structure {
|
|||
}
|
||||
|
||||
styles {
|
||||
structure(group){
|
||||
orientation: vertical
|
||||
}
|
||||
lanes(group) {
|
||||
type: textnode
|
||||
orientation: horizontal
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<svg width="100" height="100" viewBox="0 0 15 0" xmlns="http://www.w3.org/2000/svg"><style>svg {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 230 75"><style>svg {
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
|
|
@ -12,4 +12,4 @@
|
|||
font-size: 10px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
</style></svg>
|
||||
</style><rect id="node1" x="15" y="15" width="37" height="30" stroke="white" fill="none" /><text x="33" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="33" dy="10">Node 1</tspan><tspan x="33" dy="10">longer</tspan> /><rect id="node2" x="67" y="15" width="37" height="15" stroke="white" fill="none" /><text x="85" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="85" dy="10">Node 2</tspan> /><rect id="node3" x="119" y="15" width="96" height="45" stroke="white" fill="none" /><text x="167" y="15" fill="white" text-anchor="middle" stroke-width="1" class="node"<tspan x="167" dy="10">Node 3</tspan><tspan x="167" dy="10">is longer</tspan><tspan x="167" dy="10">than you are to me</tspan> /></svg>
|
||||
|
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 1.1 KiB |
BIN
src/.DS_Store
vendored
Normal file
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
122
src/lib.rs
122
src/lib.rs
|
|
@ -1,61 +1,125 @@
|
|||
pub mod parse;
|
||||
pub mod render;
|
||||
|
||||
use crate::Element::{Edge, Node};
|
||||
use parse::tokens::TokenType;
|
||||
use crate::parse::tokens::TokenType;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Vis {
|
||||
pub structure: Vec<Element>,
|
||||
pub structure: VisNode,
|
||||
pub styles: Vec<StyleNode>,
|
||||
}
|
||||
|
||||
impl Vis {
|
||||
pub fn get_node(&self, id: &str) -> Option<&VisNode> {
|
||||
if self.structure.id == id {
|
||||
return Some(&self.structure);
|
||||
} else {
|
||||
for child in &self.structure.children {
|
||||
if child.id == id {
|
||||
return Some(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_style(&self, node: &VisNode) -> Option<&StyleNode> {
|
||||
let style = self.styles.iter().find(|s| s.id_ref == node.id);
|
||||
if style.is_none() && node.id != "structure" {
|
||||
for child in &node.children {
|
||||
if let Some(style) = self.get_style(child) {
|
||||
return Some(style);
|
||||
}
|
||||
}
|
||||
None
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum NodeType {
|
||||
Node,
|
||||
Edge,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Element {
|
||||
Node(String, Option<String>, Vec<Element>),
|
||||
Edge(String, String, TokenType, Option<String>),
|
||||
pub struct VisNode {
|
||||
pub node_type: NodeType,
|
||||
pub id: String,
|
||||
targetid: Option<String>,
|
||||
pub label: Option<String>,
|
||||
pub children: Vec<VisNode>,
|
||||
pub parent: Option<String>,
|
||||
edgetype: Option<TokenType>,
|
||||
}
|
||||
|
||||
impl Element {
|
||||
pub fn new_node(id: &str, label: Option<&str>, children: Vec<Element>) -> Element {
|
||||
Element::Node(id.into(), label.map(|s| s.into()), children)
|
||||
impl VisNode {
|
||||
pub fn new_node(
|
||||
id: impl Into<String>,
|
||||
label: Option<impl Into<String>>,
|
||||
children: Vec<VisNode>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
targetid: None,
|
||||
label: label.map(|s| s.into()),
|
||||
children,
|
||||
parent: None,
|
||||
node_type: NodeType::Node,
|
||||
edgetype: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_edge(
|
||||
id: String,
|
||||
source: String,
|
||||
token: TokenType,
|
||||
label: Option<String>,
|
||||
) -> Element {
|
||||
Edge(id, source, token, label)
|
||||
sourceid: impl Into<String>,
|
||||
targetid: impl Into<String>,
|
||||
edgetype: TokenType,
|
||||
label: Option<impl Into<String>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: sourceid.into(),
|
||||
targetid: Some(targetid.into()),
|
||||
edgetype: Some(edgetype),
|
||||
label: label.map(|l| l.into()),
|
||||
children: vec![],
|
||||
parent: None,
|
||||
node_type: NodeType::Edge,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Element {
|
||||
impl Display for VisNode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Node(id, label, children) => {
|
||||
match &self.node_type {
|
||||
NodeType::Node => {
|
||||
let mut string = String::new();
|
||||
string.push_str(&format!(
|
||||
"Node {{{}: {}",
|
||||
id,
|
||||
label.as_ref().unwrap_or(&"".to_string())
|
||||
self.id,
|
||||
self.label.as_ref().unwrap_or(&"".to_string())
|
||||
));
|
||||
for child in children {
|
||||
for child in &self.children {
|
||||
string.push_str(&format!(" {}", child));
|
||||
}
|
||||
string.push('}');
|
||||
write!(f, "{}", string)
|
||||
}
|
||||
Edge(id, source, token, label) => {
|
||||
let mut string = "Edge {{".to_string();
|
||||
string.push_str(&format!("{} {} {:?}", id, source, token));
|
||||
if let Some(label) = label {
|
||||
string.push_str(&format!(" {}", label));
|
||||
NodeType::Edge => {
|
||||
write!(f, "Edge {{")?;
|
||||
write!(
|
||||
f,
|
||||
"{} {} {:?}",
|
||||
self.id,
|
||||
self.targetid.as_ref().unwrap(),
|
||||
self.edgetype
|
||||
)?;
|
||||
if let Some(label) = &self.label {
|
||||
write!(f, " {}", label)?;
|
||||
}
|
||||
string.push('}');
|
||||
write!(f, "{}", string)
|
||||
write!(f, "}}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
Token,
|
||||
TokenType::{self, *},
|
||||
},
|
||||
ContainerType, Element, StyleNode, Vis,
|
||||
ContainerType, StyleNode, Vis, VisNode,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -13,10 +13,21 @@ pub fn parse_vis(contents: &str) -> anyhow::Result<Vis> {
|
|||
// println!("{:?}", tokens);
|
||||
let mut parser = Parser::new(tokens);
|
||||
|
||||
Ok(Vis {
|
||||
structure: parser.structure()?,
|
||||
let structure = parser.structure()?;
|
||||
|
||||
let mut vis = Vis {
|
||||
structure: VisNode::new_node("structure", None::<String>, structure),
|
||||
styles: parser.styles()?,
|
||||
})
|
||||
};
|
||||
|
||||
// add bottom up references (sorry no actual refs, but clones)
|
||||
vis.structure.children.iter_mut().for_each(|node| {
|
||||
let c = node.clone();
|
||||
node.children.iter_mut().for_each(|child| {
|
||||
child.parent.replace(c.id.to_owned());
|
||||
});
|
||||
});
|
||||
Ok(vis)
|
||||
}
|
||||
|
||||
struct Parser {
|
||||
|
|
@ -29,7 +40,7 @@ impl Parser {
|
|||
Self { tokens, current: 0 }
|
||||
}
|
||||
|
||||
fn structure(&mut self) -> anyhow::Result<Vec<Element>> {
|
||||
fn structure(&mut self) -> anyhow::Result<Vec<VisNode>> {
|
||||
if self.match_token(Structure) {
|
||||
self.elements()
|
||||
} else {
|
||||
|
|
@ -37,7 +48,7 @@ impl Parser {
|
|||
}
|
||||
}
|
||||
|
||||
fn elements(&mut self) -> anyhow::Result<Vec<Element>> {
|
||||
fn elements(&mut self) -> anyhow::Result<Vec<VisNode>> {
|
||||
// println!("nodes {:?}", self.peek());
|
||||
self.consume(LeftBrace, "Expected '{'")?;
|
||||
let mut nodes = vec![];
|
||||
|
|
@ -47,7 +58,7 @@ impl Parser {
|
|||
Ok(nodes)
|
||||
}
|
||||
|
||||
fn element(&mut self) -> anyhow::Result<Element> {
|
||||
fn element(&mut self) -> anyhow::Result<VisNode> {
|
||||
// println!("node {:?}", self.peek());
|
||||
let id = self.id()?;
|
||||
// println!("id {}", id);
|
||||
|
|
@ -68,14 +79,14 @@ impl Parser {
|
|||
vec![]
|
||||
};
|
||||
|
||||
Ok(Element::Node(id, title, children))
|
||||
Ok(VisNode::new_node(id, title, children))
|
||||
}
|
||||
}
|
||||
|
||||
fn edge(&mut self, from_id: String, arrow: Token) -> anyhow::Result<Element> {
|
||||
fn edge(&mut self, from_id: String, arrow: Token) -> anyhow::Result<VisNode> {
|
||||
let to_id = self.id()?;
|
||||
let title = self.title()?;
|
||||
Ok(Element::Edge(from_id, to_id, arrow.tokentype, title))
|
||||
Ok(VisNode::new_edge(from_id, to_id, arrow.tokentype, title))
|
||||
}
|
||||
|
||||
fn title(&mut self) -> anyhow::Result<Option<String>> {
|
||||
|
|
@ -119,8 +130,13 @@ impl Parser {
|
|||
}
|
||||
|
||||
fn style(&mut self) -> anyhow::Result<StyleNode> {
|
||||
if self.check(&Identifier) {
|
||||
let idref = self.peek().lexeme.to_owned();
|
||||
if self.check(&Identifier) || self.check(&Structure) {
|
||||
let idref = if self.check(&Structure) {
|
||||
// only structure element can also be referenced
|
||||
"structure".to_string()
|
||||
} else {
|
||||
self.peek().lexeme.to_owned()
|
||||
};
|
||||
self.advance();
|
||||
let containertype = self.containertype()?;
|
||||
self.consume(RightParen, "Expected ')'")?;
|
||||
|
|
@ -227,7 +243,7 @@ impl Parser {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Element;
|
||||
use crate::NodeType::Node;
|
||||
|
||||
#[test]
|
||||
fn test_parse() {
|
||||
|
|
@ -244,28 +260,32 @@ mod tests {
|
|||
if let Some(vis) = vis {
|
||||
// Validate the parsed structure by asserting its elements
|
||||
assert_eq!(
|
||||
vis.structure.len(),
|
||||
vis.structure.children.len(),
|
||||
1,
|
||||
"The structure should contain one top-level element"
|
||||
);
|
||||
|
||||
if let Element::Node(id, title, children) = &vis.structure[0] {
|
||||
assert_eq!(id, "top", "The ID of the first node should be 'top'");
|
||||
if vis.structure.children[0].node_type == Node {
|
||||
let node = &vis.structure.children[0];
|
||||
assert_eq!(node.id, "top", "The ID of the first node should be 'top'");
|
||||
assert_eq!(
|
||||
title.as_ref().unwrap(),
|
||||
node.label.as_ref().unwrap(),
|
||||
&"top-node".to_owned(),
|
||||
"The title of the first node should be 'top-node'"
|
||||
);
|
||||
assert_eq!(children.len(), 1);
|
||||
let child = &children[0];
|
||||
if let Element::Node(id, title, children) = child {
|
||||
assert_eq!(id, "child", "The ID of the second node should be 'child'");
|
||||
assert_eq!(node.children.len(), 1);
|
||||
let child = &node.children[0];
|
||||
if child.node_type == Node {
|
||||
assert_eq!(
|
||||
title.as_ref().unwrap(),
|
||||
child.id, "child",
|
||||
"The ID of the second node should be 'child'"
|
||||
);
|
||||
assert_eq!(
|
||||
child.label.as_ref().unwrap(),
|
||||
&"child-node".to_owned(),
|
||||
"The title of the second node should be 'child-node'"
|
||||
);
|
||||
assert_eq!(children.len(), 0);
|
||||
assert_eq!(child.children.len(), 0);
|
||||
}
|
||||
} else {
|
||||
panic!("The top-level element should be a Node");
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ use crate::render::svglib::rect::rect;
|
|||
use crate::render::svglib::svg::{svg, Svg};
|
||||
use crate::render::svglib::text::text;
|
||||
use crate::render::Renderer;
|
||||
use crate::Element::Node;
|
||||
use crate::{Element, StyleNode, Vis};
|
||||
use crate::{StyleNode, Vis, VisNode};
|
||||
|
||||
pub struct SvgRender;
|
||||
|
||||
|
|
@ -15,7 +14,7 @@ impl Renderer for SvgRender {
|
|||
svg.height(100);
|
||||
let style = include_str!("svg_node.css");
|
||||
svg.style(style);
|
||||
let (w, h) = render_elements(&mut svg, &vis.structure, &vis.styles, 0, 0);
|
||||
let (w, h) = render_elements(&mut svg, &vis.structure.children, &vis.styles, 0, 0);
|
||||
svg.viewbox(format!("0 0 {} {}", w, h));
|
||||
Ok(svg.to_string().into_bytes())
|
||||
}
|
||||
|
|
@ -23,7 +22,7 @@ impl Renderer for SvgRender {
|
|||
|
||||
fn render_elements(
|
||||
svg: &mut Svg,
|
||||
elements: &Vec<Element>,
|
||||
elements: &[VisNode],
|
||||
_styles: &Vec<StyleNode>,
|
||||
start_x: u32,
|
||||
start_y: u32,
|
||||
|
|
@ -36,43 +35,41 @@ fn render_elements(
|
|||
let y = start_y;
|
||||
|
||||
for element in elements {
|
||||
if let Node(id, label, children) = element {
|
||||
let (width, height) = if !children.is_empty() {
|
||||
render_elements(svg, children, _styles, x + padding, y + padding)
|
||||
} else if let Node(_, Some(label), _) = element {
|
||||
let label_width = label.lines().map(|l| l.len()).max().unwrap_or(0) as u32;
|
||||
let label_height = label.lines().count() as u32;
|
||||
((label_width as f32 * 4.9 + 8.0) as u32, label_height * 15)
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
let (width, height) = if !element.children.is_empty() {
|
||||
render_elements(svg, &element.children, _styles, x + padding, y + padding)
|
||||
} else if let Some(label) = element.label.as_ref() {
|
||||
let label_width = label.lines().map(|l| l.len()).max().unwrap_or(0) as u32;
|
||||
let label_height = label.lines().count() as u32;
|
||||
((label_width as f32 * 4.9 + 8.0) as u32, label_height * 15)
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
|
||||
total_width += width + padding;
|
||||
total_height = u32::max(total_height, height + padding);
|
||||
total_width += width + padding;
|
||||
total_height = u32::max(total_height, height + padding);
|
||||
|
||||
svg.add(
|
||||
rect()
|
||||
.id(id)
|
||||
.x(x + padding)
|
||||
.y(y + padding)
|
||||
.width(width)
|
||||
.height(height)
|
||||
.stroke("white")
|
||||
.fill("none"),
|
||||
);
|
||||
svg.add(
|
||||
rect()
|
||||
.id(element.id.clone())
|
||||
.x(x + padding)
|
||||
.y(y + padding)
|
||||
.width(width)
|
||||
.height(height)
|
||||
.stroke("white")
|
||||
.fill("none"),
|
||||
);
|
||||
|
||||
svg.add(
|
||||
text()
|
||||
.x(x + padding + width / 2)
|
||||
.y(start_y + padding)
|
||||
.fill("white")
|
||||
.text(label.as_ref().map(|l| l.as_str()).unwrap_or(""))
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("stroke-width", "1")
|
||||
.class("node"),
|
||||
);
|
||||
x += width + padding;
|
||||
}
|
||||
svg.add(
|
||||
text()
|
||||
.x(x + padding + width / 2)
|
||||
.y(start_y + padding)
|
||||
.fill("white")
|
||||
.text(element.label.as_ref().unwrap_or(&"".to_string()))
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("stroke-width", "1")
|
||||
.class("node"),
|
||||
);
|
||||
x += width + padding;
|
||||
}
|
||||
|
||||
(total_width + padding, total_height + padding)
|
||||
|
|
@ -81,7 +78,7 @@ fn render_elements(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{ContainerType, StyleNode, Vis};
|
||||
use crate::{ContainerType, StyleNode, Vis, VisNode};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
|
|
@ -98,32 +95,32 @@ mod tests {
|
|||
};
|
||||
|
||||
// Create mock Elements
|
||||
let element1 = Node(
|
||||
"node1".to_string(),
|
||||
Some("Node 1\nlonger".to_string()),
|
||||
let element1 = VisNode::new_node(
|
||||
"node1",
|
||||
Some("Node 1\nlonger"),
|
||||
vec![], // No child elements
|
||||
);
|
||||
let element2 = Node(
|
||||
"node2".to_string(),
|
||||
Some("Node 2".to_string()),
|
||||
let element2 = VisNode::new_node(
|
||||
"node2",
|
||||
Some("Node 2"),
|
||||
vec![], // No child elements
|
||||
);
|
||||
|
||||
let element3 = Node(
|
||||
"node3".to_string(),
|
||||
Some("Node 3\nis longer\nthan you are to me".to_string()),
|
||||
let element3 = VisNode::new_node(
|
||||
"node3",
|
||||
Some("Node 3\nis longer\nthan you are to me"),
|
||||
vec![], // No child elements
|
||||
);
|
||||
let root = Node(
|
||||
"root".to_string(),
|
||||
Some("root".to_string()),
|
||||
let root = VisNode::new_node(
|
||||
"structure",
|
||||
Some("root"),
|
||||
vec![element1, element2, element3],
|
||||
);
|
||||
|
||||
// Create Vis structure
|
||||
let vis = Vis {
|
||||
styles: vec![style_node],
|
||||
structure: vec![root],
|
||||
structure: root,
|
||||
};
|
||||
|
||||
let svg_output = SvgRender {}.render(vis).unwrap();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Shape, Value};
|
||||
use crate::render::svglib::{att, att_to_string, Att, SvgElement, ElementType, Shape, Value};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub fn circle() -> Circle {
|
||||
Circle::default()
|
||||
|
|
@ -43,17 +44,20 @@ impl Shape for Circle {
|
|||
}
|
||||
}
|
||||
|
||||
impl Element for Circle {
|
||||
fn get_type(&self) -> ElementType {
|
||||
ElementType::Circle
|
||||
}
|
||||
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
impl Display for Circle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#"<circle{} />"#,
|
||||
self.0.iter().map(att_to_string).collect::<String>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SvgElement for Circle {
|
||||
fn get_type(&self) -> ElementType {
|
||||
ElementType::Circle
|
||||
}
|
||||
|
||||
fn atts(&self) -> &[Att] {
|
||||
&self.0
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Shape, Value};
|
||||
use crate::render::svglib::{att, att_to_string, Att, ElementType, Shape, SvgElement, Value};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub fn ellipse() -> Ellipse {
|
||||
Ellipse(vec![])
|
||||
|
|
@ -46,7 +47,17 @@ impl Shape for Ellipse {
|
|||
}
|
||||
}
|
||||
|
||||
impl Element for Ellipse {
|
||||
impl Display for Ellipse {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#"<ellipse{} />"#,
|
||||
self.0.iter().map(att_to_string).collect::<String>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SvgElement for Ellipse {
|
||||
fn get_type(&self) -> ElementType {
|
||||
ElementType::Ellipse
|
||||
}
|
||||
|
|
@ -54,13 +65,6 @@ impl Element for Ellipse {
|
|||
fn atts(&self) -> &[Att] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
r#"<ellipse{} />"#,
|
||||
self.0.iter().map(att_to_string).collect::<String>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::render::svglib::div::Div;
|
||||
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
|
||||
use crate::render::svglib::{att, att_to_string, Att, ElementType, SvgElement, Value};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub fn foreign_object() -> ForeignObject {
|
||||
ForeignObject::default()
|
||||
|
|
@ -47,7 +48,21 @@ impl ForeignObject {
|
|||
}
|
||||
}
|
||||
|
||||
impl Element for ForeignObject {
|
||||
impl Display for ForeignObject {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#"<foreignObject{}>{}</foreignObject>"#,
|
||||
self.atts.iter().map(att_to_string).collect::<String>(),
|
||||
self.child
|
||||
.as_ref()
|
||||
.map(|c| c.to_string())
|
||||
.unwrap_or("".to_string())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SvgElement for ForeignObject {
|
||||
fn get_type(&self) -> ElementType {
|
||||
ElementType::ForeignObject
|
||||
}
|
||||
|
|
@ -55,15 +70,4 @@ impl Element for ForeignObject {
|
|||
fn atts(&self) -> &[Att] {
|
||||
&self.atts
|
||||
}
|
||||
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
r#"<foreignObject{}>{}</foreignObject>"#,
|
||||
self.atts.iter().map(att_to_string).collect::<String>(),
|
||||
self.child
|
||||
.as_ref()
|
||||
.map(|c| c.to_string())
|
||||
.unwrap_or("".to_string()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
|
||||
use crate::render::svglib::{att, att_to_string, Att, ElementType, SvgElement, Value};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub fn group() -> Group {
|
||||
Group {
|
||||
children: Vec::new(),
|
||||
atts: vec![],
|
||||
}
|
||||
Group::default()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Group {
|
||||
children: Vec<Box<dyn Element>>,
|
||||
children: Vec<Box<dyn SvgElement>>,
|
||||
atts: Vec<Att>,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
pub fn add(&mut self, child: impl Element + 'static) {
|
||||
pub fn add(&mut self, child: impl SvgElement + 'static) {
|
||||
self.children.push(Box::new(child));
|
||||
}
|
||||
|
||||
|
|
@ -26,7 +25,21 @@ impl Group {
|
|||
}
|
||||
}
|
||||
|
||||
impl Element for Group {
|
||||
impl Display for Group {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"<g{}>",
|
||||
self.atts.iter().map(att_to_string).collect::<String>()
|
||||
)?;
|
||||
for e in &self.children {
|
||||
write!(f, "{}", e.to_string().as_str())?;
|
||||
}
|
||||
write!(f, "</g>")
|
||||
}
|
||||
}
|
||||
|
||||
impl SvgElement for Group {
|
||||
fn get_type(&self) -> ElementType {
|
||||
ElementType::Group(Vec::new())
|
||||
}
|
||||
|
|
@ -34,19 +47,6 @@ impl Element for Group {
|
|||
fn atts(&self) -> &[Att] {
|
||||
&self.atts
|
||||
}
|
||||
|
||||
fn to_string(&self) -> String {
|
||||
let mut svg = format!(
|
||||
"<g{}>",
|
||||
self.atts.iter().map(att_to_string).collect::<String>()
|
||||
);
|
||||
|
||||
for e in &self.children {
|
||||
svg.push_str(e.to_string().as_str());
|
||||
}
|
||||
svg.push_str("</g>");
|
||||
svg
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
|
||||
use crate::render::svglib::{att, att_to_string, Att, ElementType, SvgElement, Value};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub fn image() -> Image {
|
||||
Image::default()
|
||||
|
|
@ -39,17 +40,20 @@ impl Image {
|
|||
}
|
||||
}
|
||||
|
||||
impl Element for Image {
|
||||
fn get_type(&self) -> ElementType {
|
||||
ElementType::Image
|
||||
}
|
||||
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
impl Display for Image {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#"<image{} />"#,
|
||||
self.atts.iter().map(att_to_string).collect::<String>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SvgElement for Image {
|
||||
fn get_type(&self) -> ElementType {
|
||||
ElementType::Image
|
||||
}
|
||||
|
||||
fn atts(&self) -> &[Att] {
|
||||
&self.atts
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Shape, Value};
|
||||
use crate::render::svglib::{att, att_to_string, Att, SvgElement, ElementType, Shape, Value};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub fn line() -> Line {
|
||||
Line(vec![])
|
||||
|
|
@ -34,7 +35,17 @@ impl Line {
|
|||
}
|
||||
}
|
||||
|
||||
impl Element for Line {
|
||||
impl Display for Line {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#" <line{} />"#,
|
||||
self.0.iter().map(att_to_string).collect::<String>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SvgElement for Line {
|
||||
fn get_type(&self) -> ElementType {
|
||||
ElementType::Line
|
||||
}
|
||||
|
|
@ -42,13 +53,6 @@ impl Element for Line {
|
|||
fn atts(&self) -> &[Att] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
r#" <line{} />"#,
|
||||
self.0.iter().map(att_to_string).collect::<String>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Shape for Line {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
|
||||
use crate::render::svglib::{att, att_to_string, Att, SvgElement, ElementType, Value};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub fn link(href: &str) -> Link {
|
||||
Link(vec![att("href", href)])
|
||||
|
|
@ -12,17 +13,20 @@ impl Link {
|
|||
}
|
||||
}
|
||||
|
||||
impl Element for Link {
|
||||
fn get_type(&self) -> ElementType {
|
||||
ElementType::Link
|
||||
}
|
||||
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
impl Display for Link {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#"<link{} />"#,
|
||||
self.0.iter().map(att_to_string).collect::<String>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SvgElement for Link {
|
||||
fn get_type(&self) -> ElementType {
|
||||
ElementType::Link
|
||||
}
|
||||
|
||||
fn atts(&self) -> &[Att] {
|
||||
&self.0
|
||||
|
|
|
|||
|
|
@ -76,9 +76,8 @@ pub enum ElementType {
|
|||
Rect,
|
||||
}
|
||||
|
||||
pub trait Element {
|
||||
pub trait SvgElement: Display {
|
||||
fn get_type(&self) -> ElementType;
|
||||
fn to_string(&self) -> String;
|
||||
fn atts(&self) -> &[Att];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::render::svglib::{att, Att, Element, ElementType, Shape, Value};
|
||||
use crate::render::svglib::{att, att_to_string, Att, ElementType, Shape, SvgElement, Value};
|
||||
use std::fmt;
|
||||
|
||||
pub fn path(d: &str) -> Path {
|
||||
Path::new(d)
|
||||
|
|
@ -9,15 +10,22 @@ pub struct Path {
|
|||
atts: Vec<Att>,
|
||||
}
|
||||
|
||||
impl Element for Path {
|
||||
impl fmt::Display for Path {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#"<path d="{}"{} />"#,
|
||||
self.d,
|
||||
self.atts.iter().map(att_to_string).collect::<String>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SvgElement for Path {
|
||||
fn get_type(&self) -> ElementType {
|
||||
ElementType::Path
|
||||
}
|
||||
|
||||
fn to_string(&self) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn atts(&self) -> &[Att] {
|
||||
&self.atts
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
|
||||
use crate::render::svglib::{att, att_to_string, Att, ElementType, SvgElement, Value};
|
||||
|
||||
pub fn rect() -> Rect {
|
||||
Rect::default()
|
||||
|
|
@ -60,7 +60,17 @@ impl Rect {
|
|||
}
|
||||
}
|
||||
|
||||
impl Element for Rect {
|
||||
impl std::fmt::Display for Rect {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#"<rect{} />"#,
|
||||
self.atts.iter().map(att_to_string).collect::<String>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SvgElement for Rect {
|
||||
fn get_type(&self) -> ElementType {
|
||||
ElementType::Rect
|
||||
}
|
||||
|
|
@ -68,13 +78,6 @@ impl Element for Rect {
|
|||
fn atts(&self) -> &[Att] {
|
||||
&self.atts
|
||||
}
|
||||
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
r#"<rect{} />"#,
|
||||
self.atts.iter().map(att_to_string).collect::<String>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, Element, Value};
|
||||
use crate::render::svglib::{att, att_to_string, Att, SvgElement, Value};
|
||||
use std::fmt;
|
||||
|
||||
pub fn svg() -> Svg {
|
||||
|
|
@ -8,7 +8,7 @@ pub fn svg() -> Svg {
|
|||
#[derive(Default)]
|
||||
pub struct Svg {
|
||||
style: Option<String>,
|
||||
elements: Vec<Box<dyn Element>>,
|
||||
elements: Vec<Box<dyn SvgElement>>,
|
||||
atts: Vec<Att>,
|
||||
}
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ impl Svg {
|
|||
self.style = Some(style.to_string());
|
||||
}
|
||||
|
||||
pub fn add(&mut self, child: impl Element + 'static) {
|
||||
pub fn add(&mut self, child: impl SvgElement + 'static) {
|
||||
self.elements.push(Box::new(child));
|
||||
}
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ impl Svg {
|
|||
|
||||
pub fn preserveaspectratio<V: Into<Value>>(&mut self, preserveaspectratio: V) {
|
||||
self.atts
|
||||
.push(att("preserveaspectratio", preserveaspectratio));
|
||||
.push(att("preserveAspectRatio", preserveaspectratio));
|
||||
}
|
||||
|
||||
pub fn transform<V: Into<Value>>(&mut self, transform: V) {
|
||||
|
|
@ -47,7 +47,7 @@ impl fmt::Display for Svg {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#"<svg xmlns="http://www.w3.org/2000/svg" {}>{}"#,
|
||||
r#"<svg xmlns="http://www.w3.org/2000/svg"{}>{}"#,
|
||||
self.atts.iter().map(att_to_string).collect::<String>(),
|
||||
self.style
|
||||
.as_ref()
|
||||
|
|
@ -83,7 +83,7 @@ mod tests {
|
|||
svg.preserveaspectratio("none");
|
||||
svg.add(rect().x(0).y(0).width(10).height(10));
|
||||
assert_eq!(
|
||||
r#"<svg preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg"><rect x="0" y="0" width="10" height="10" /></svg>"#,
|
||||
r#"<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none"><rect x="0" y="0" width="10" height="10" /></svg>"#,
|
||||
svg.to_string()
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::render::svglib::{att, att_to_string, Att, Element, ElementType, Value};
|
||||
use std::fmt::Write;
|
||||
use crate::render::svglib::{att, att_to_string, Att, ElementType, SvgElement, Value};
|
||||
use std::fmt::{Display, Write};
|
||||
|
||||
pub fn text() -> Text {
|
||||
Text::default()
|
||||
|
|
@ -74,16 +74,11 @@ impl Text {
|
|||
}
|
||||
}
|
||||
|
||||
impl Element for Text {
|
||||
fn get_type(&self) -> ElementType {
|
||||
ElementType::Rect
|
||||
}
|
||||
|
||||
fn to_string(&self) -> String {
|
||||
// let x: Vec<&Att> = self.atts.iter().filter(|a| a.name == "x").collect();
|
||||
// let x: String = x[0].value.to_string();
|
||||
format!(
|
||||
r#"<text{}>{}</text>"#,
|
||||
impl Display for Text {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#"<text{}{} />"#,
|
||||
self.atts.iter().map(att_to_string).collect::<String>(),
|
||||
self.text.lines().fold(String::new(), |mut output, l| {
|
||||
let x: Vec<&Att> = self.atts.iter().filter(|a| a.name == "x").collect();
|
||||
|
|
@ -93,6 +88,12 @@ impl Element for Text {
|
|||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SvgElement for Text {
|
||||
fn get_type(&self) -> ElementType {
|
||||
ElementType::Rect
|
||||
}
|
||||
|
||||
fn atts(&self) -> &[Att] {
|
||||
&self.atts
|
||||
|
|
@ -105,10 +106,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_rect() {
|
||||
let rect = text().x(0).y(0).width(10).height(10);
|
||||
let text = text().x(0).y(0).width(10).height(10);
|
||||
assert_eq!(
|
||||
r#"<rect x="0" y="0" width="10" height="10" />"#,
|
||||
rect.to_string()
|
||||
r#"<text x="0" y="0" width="10" height="10" />"#,
|
||||
text.to_string()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue