added vertical layout
This commit is contained in:
parent
660f892d6c
commit
5e5ce9541c
10 changed files with 164 additions and 84 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -8,6 +8,12 @@ version = "1.0.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.20.2"
|
version = "1.20.2"
|
||||||
|
|
@ -25,6 +31,7 @@ name = "vis"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
unicode-segmentation = "1.1"
|
unicode-segmentation = "1.1"
|
||||||
once_cell = "1.20.2"
|
once_cell = "1.20.2"
|
||||||
|
log = "0.4"
|
||||||
15
bank.svg
15
bank.svg
|
|
@ -1,15 +0,0 @@
|
||||||
<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>
|
|
||||||
|
Before Width: | Height: | Size: 4.6 KiB |
|
|
@ -28,14 +28,11 @@ structure {
|
||||||
}
|
}
|
||||||
|
|
||||||
styles {
|
styles {
|
||||||
structure(group){
|
|
||||||
orientation: vertical
|
|
||||||
}
|
|
||||||
lanes(group) {
|
lanes(group) {
|
||||||
type: textnode
|
type: textnode
|
||||||
orientation: horizontal
|
orientation: vertical
|
||||||
shape: rectangle
|
shape: rectangle
|
||||||
}
|
}
|
||||||
functions(group) {}
|
functions(group) {orientation: horizontal}
|
||||||
systems(group) {}
|
systems(group) {orientation: horizontal}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 230 75"><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="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: 1.1 KiB |
93
src/lib.rs
93
src/lib.rs
|
|
@ -2,37 +2,77 @@ pub mod parse;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
|
|
||||||
use crate::parse::tokens::TokenType;
|
use crate::parse::tokens::TokenType;
|
||||||
|
use log::debug;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
pub const BODY: &str = "structure";
|
||||||
|
pub const DEFAULT_ORIENTATION: &str = "horizontal";
|
||||||
|
pub const ORIENTATION: &str = "orientation";
|
||||||
|
pub const HORIZONTAL: &str = "horizontal";
|
||||||
|
pub const VERTICAL: &str = "vertical";
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Vis {
|
pub struct Vis {
|
||||||
pub structure: VisNode,
|
pub structure: Vec<VisNode>,
|
||||||
pub styles: Vec<StyleNode>,
|
pub styles: Vec<StyleNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vis {
|
impl Vis {
|
||||||
pub fn get_node(&self, id: &str) -> Option<&VisNode> {
|
pub fn new(structure: VisNode, styles: Vec<StyleNode>) -> Self {
|
||||||
if self.structure.id == id {
|
let mut vis = Self {
|
||||||
return Some(&self.structure);
|
structure: vec![structure],
|
||||||
|
styles,
|
||||||
|
};
|
||||||
|
|
||||||
|
// set defaults
|
||||||
|
let bodystyle = vis.get_style_node_mut(BODY);
|
||||||
|
if let Some(bodystyle) = bodystyle {
|
||||||
|
bodystyle.set_attribute_if_absent(ORIENTATION, DEFAULT_ORIENTATION);
|
||||||
} else {
|
} else {
|
||||||
for child in &self.structure.children {
|
let mut bodystyle = StyleNode::new(BODY, ContainerType::Group);
|
||||||
if child.id == id {
|
bodystyle.set_attribute(ORIENTATION, DEFAULT_ORIENTATION);
|
||||||
return Some(child);
|
vis.styles.push(bodystyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vis
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_node(&self, id: impl Into<String>) -> Option<&VisNode> {
|
||||||
|
Self::get_node_recurse(id, &self.structure)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_node_recurse(id: impl Into<String>, elements: &Vec<VisNode>) -> Option<&VisNode> {
|
||||||
|
let id = id.into();
|
||||||
|
for element in elements {
|
||||||
|
if let Some(e) = Self::get_node_recurse(id.clone(), &element.children) {
|
||||||
|
return Some(e);
|
||||||
|
} else if element.id == id {
|
||||||
|
return Some(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_styles(&self, id: &str) -> HashMap<String, String> {
|
pub fn get_style_node_mut(&mut self, id_ref: &str) -> Option<&mut StyleNode> {
|
||||||
|
self.styles.iter_mut().find(|s| s.id_ref == id_ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_style_value(&self, idref: impl Into<String>, key: &str) -> Option<String> {
|
||||||
|
self.get_styles(idref).get(key).map(String::to_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_styles(&self, id: impl Into<String>) -> HashMap<String, String> {
|
||||||
// println!("get_styles {:?}", id);
|
// println!("get_styles {:?}", id);
|
||||||
let mut styles = HashMap::new();
|
let mut styles = HashMap::new();
|
||||||
self.get_styles2(id, &mut styles);
|
self.get_styles_recurse(id, &mut styles);
|
||||||
|
debug!("found {:?}", styles);
|
||||||
styles
|
styles
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_styles2(&self, id: &str, styles: &mut HashMap<String, String>) {
|
fn get_styles_recurse(&self, id: impl Into<String>, styles: &mut HashMap<String, String>) {
|
||||||
|
let id = id.into();
|
||||||
|
debug!("get style for {}", id);
|
||||||
let node = self.get_node(id);
|
let node = self.get_node(id);
|
||||||
if let Some(node) = node {
|
if let Some(node) = node {
|
||||||
// println!("node {:?}", node);
|
// println!("node {:?}", node);
|
||||||
|
|
@ -45,7 +85,7 @@ impl Vis {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if let Some(parent) = &node.parent {
|
if let Some(parent) = &node.parent {
|
||||||
self.get_styles2(parent, styles);
|
self.get_styles_recurse(parent, styles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -144,6 +184,35 @@ pub struct StyleNode {
|
||||||
pub attributes: HashMap<String, String>,
|
pub attributes: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl StyleNode {
|
||||||
|
fn new(id_ref: impl Into<String>, containertype: ContainerType) -> Self {
|
||||||
|
Self {
|
||||||
|
id_ref: id_ref.into(),
|
||||||
|
containertype,
|
||||||
|
attributes: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_attribute(&self, key: &str) -> bool {
|
||||||
|
self.attributes.contains_key(key)
|
||||||
|
}
|
||||||
|
pub fn get_attribute(&self, key: &str) -> Option<&String> {
|
||||||
|
self.attributes.get(key)
|
||||||
|
}
|
||||||
|
pub fn get_attribute_string(&self, key: &str) -> Option<String> {
|
||||||
|
self.attributes.get(key).map(|s| s.to_owned())
|
||||||
|
}
|
||||||
|
pub fn set_attribute(&mut self, key: impl Into<String>, value: impl Into<String>) {
|
||||||
|
self.attributes.insert(key.into(), value.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_attribute_if_absent(&mut self, key: impl Into<String>, value: impl Into<String>) {
|
||||||
|
let key = key.into();
|
||||||
|
if !self.has_attribute(&key) {
|
||||||
|
self.set_attribute(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ContainerType {
|
pub enum ContainerType {
|
||||||
NonGroup, // needs thinking about
|
NonGroup, // needs thinking about
|
||||||
|
|
|
||||||
|
|
@ -6,23 +6,24 @@ use crate::{
|
||||||
ContainerType, StyleNode, Vis, VisNode,
|
ContainerType, StyleNode, Vis, VisNode,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
use log::debug;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub fn parse_vis(contents: &str) -> anyhow::Result<Vis> {
|
pub fn parse_vis(contents: &str) -> anyhow::Result<Vis> {
|
||||||
let tokens = crate::parse::scanner::scan(contents)?;
|
let tokens = crate::parse::scanner::scan(contents)?;
|
||||||
// println!("{:?}", tokens);
|
debug!("{:?}", tokens);
|
||||||
let mut parser = Parser::new(tokens);
|
let mut parser = Parser::new(tokens);
|
||||||
|
|
||||||
let structure = parser.structure()?;
|
let structure = parser.structure()?;
|
||||||
let styles = parser.styles()?;
|
let styles = parser.styles()?;
|
||||||
// println!("parsed styles{:?}", styles);
|
debug!("parsed styles{:?}", styles);
|
||||||
let mut vis = Vis {
|
let mut vis = Vis::new(
|
||||||
structure: VisNode::new_node("structure", None::<String>, structure),
|
VisNode::new_node("structure", None::<String>, structure),
|
||||||
styles,
|
styles,
|
||||||
};
|
);
|
||||||
|
|
||||||
// add bottom up references
|
// add bottom up references
|
||||||
vis.structure.children.iter_mut().for_each(|node| {
|
vis.structure.iter_mut().for_each(|node| {
|
||||||
let c = node.clone();
|
let c = node.clone();
|
||||||
node.children.iter_mut().for_each(|child| {
|
node.children.iter_mut().for_each(|child| {
|
||||||
child.parent.replace(c.id.to_owned());
|
child.parent.replace(c.id.to_owned());
|
||||||
|
|
@ -51,7 +52,7 @@ impl Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn elements(&mut self) -> anyhow::Result<Vec<VisNode>> {
|
fn elements(&mut self) -> anyhow::Result<Vec<VisNode>> {
|
||||||
// println!("nodes {:?}", self.peek());
|
debug!("nodes {:?}", self.peek());
|
||||||
self.consume(LeftBrace, "Expected '{'")?;
|
self.consume(LeftBrace, "Expected '{'")?;
|
||||||
let mut nodes = vec![];
|
let mut nodes = vec![];
|
||||||
while !self.match_token(RightBrace) {
|
while !self.match_token(RightBrace) {
|
||||||
|
|
@ -115,7 +116,7 @@ impl Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn styles(&mut self) -> anyhow::Result<Vec<StyleNode>> {
|
fn styles(&mut self) -> anyhow::Result<Vec<StyleNode>> {
|
||||||
// println!("styles {:?}", self.peek());
|
debug!("styles {:?}", self.peek());
|
||||||
if self.match_token(Styles) {
|
if self.match_token(Styles) {
|
||||||
self.consume(LeftBrace, "Expected '{'")?;
|
self.consume(LeftBrace, "Expected '{'")?;
|
||||||
let mut styles = vec![];
|
let mut styles = vec![];
|
||||||
|
|
@ -130,7 +131,7 @@ impl Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&mut self) -> anyhow::Result<StyleNode> {
|
fn style(&mut self) -> anyhow::Result<StyleNode> {
|
||||||
// println!("style {:?}", self.peek());
|
debug!("style {:?}", self.peek());
|
||||||
if self.check(&Identifier) || self.check(&Structure) {
|
if self.check(&Identifier) || self.check(&Structure) {
|
||||||
let idref = if self.check(&Structure) {
|
let idref = if self.check(&Structure) {
|
||||||
// only structure element can also be referenced
|
// only structure element can also be referenced
|
||||||
|
|
@ -138,17 +139,15 @@ impl Parser {
|
||||||
} else {
|
} else {
|
||||||
self.peek().lexeme.to_owned()
|
self.peek().lexeme.to_owned()
|
||||||
};
|
};
|
||||||
// println!("idref {:?}", idref);
|
debug!("idref {:?}", idref);
|
||||||
self.advance();
|
self.advance();
|
||||||
let containertype = self.containertype()?;
|
let containertype = self.containertype()?;
|
||||||
// println!("containertype {:?}", containertype);
|
|
||||||
self.consume(RightParen, "Expected ')'")?;
|
|
||||||
if self.peek().tokentype == Colon {
|
if self.peek().tokentype == Colon {
|
||||||
// optional
|
// optional
|
||||||
self.advance();
|
self.advance();
|
||||||
}
|
}
|
||||||
self.consume(LeftBrace, "Expected '{'")?;
|
self.consume(LeftBrace, "Expected '{'")?;
|
||||||
// println!("attributes {:?}", self.peek());
|
debug!("attributes {:?}", self.peek());
|
||||||
let attributes = self.style_elements()?;
|
let attributes = self.style_elements()?;
|
||||||
self.consume(RightBrace, "Expected '}'")?;
|
self.consume(RightBrace, "Expected '}'")?;
|
||||||
|
|
||||||
|
|
@ -163,14 +162,14 @@ impl Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style_elements(&mut self) -> anyhow::Result<HashMap<String, String>> {
|
fn style_elements(&mut self) -> anyhow::Result<HashMap<String, String>> {
|
||||||
// println!("read attributes {:?}", self.peek());
|
debug!("read attributes {:?}", self.peek());
|
||||||
let mut elements = HashMap::new();
|
let mut elements = HashMap::new();
|
||||||
let mut key = self.peek().clone();
|
let mut key = self.peek().clone();
|
||||||
while key.tokentype != RightBrace {
|
while key.tokentype != RightBrace {
|
||||||
self.advance();
|
self.advance();
|
||||||
self.consume(Colon, "Expected ':'")?;
|
self.consume(Colon, "Expected ':'")?;
|
||||||
let value = self.advance().clone();
|
let value = self.advance().clone();
|
||||||
elements.insert(key.lexeme.to_owned(), strip_surrounding(value.lexeme));
|
elements.insert(key.lexeme.to_owned(), value.lexeme);
|
||||||
key = self.peek().clone();
|
key = self.peek().clone();
|
||||||
}
|
}
|
||||||
Ok(elements)
|
Ok(elements)
|
||||||
|
|
@ -180,8 +179,10 @@ impl Parser {
|
||||||
Ok(if self.check(&LeftParen) {
|
Ok(if self.check(&LeftParen) {
|
||||||
self.advance();
|
self.advance();
|
||||||
if self.match_token(Group) {
|
if self.match_token(Group) {
|
||||||
|
self.consume(RightParen, "Expected ')'")?;
|
||||||
ContainerType::Group
|
ContainerType::Group
|
||||||
} else {
|
} else {
|
||||||
|
self.consume(RightParen, "Expected ')'")?;
|
||||||
ContainerType::NonGroup
|
ContainerType::NonGroup
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -268,8 +269,12 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
styles {
|
styles {
|
||||||
|
structure {
|
||||||
|
adam: eve
|
||||||
|
orientation: horizontal;
|
||||||
|
}
|
||||||
top(group) {
|
top(group) {
|
||||||
orientation: "vertical";
|
orientation: vertical;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|
@ -279,13 +284,13 @@ mod tests {
|
||||||
|
|
||||||
if let Some(vis) = vis {
|
if let Some(vis) = vis {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vis.structure.children.len(),
|
vis.structure[0].children.len(),
|
||||||
1,
|
1,
|
||||||
"The structure should contain one top-level element"
|
"The structure should contain one top-level element"
|
||||||
);
|
);
|
||||||
|
|
||||||
if vis.structure.children[0].node_type == Node {
|
if vis.structure[0].children[0].node_type == Node {
|
||||||
let top = &vis.structure.children[0];
|
let top = &vis.structure[0].children[0];
|
||||||
assert_eq!(top.id, "top", "The ID of the first node should be 'top'");
|
assert_eq!(top.id, "top", "The ID of the first node should be 'top'");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
top.label.as_ref().unwrap(),
|
top.label.as_ref().unwrap(),
|
||||||
|
|
@ -309,12 +314,12 @@ mod tests {
|
||||||
} else {
|
} else {
|
||||||
panic!("The top-level element should be a Node");
|
panic!("The top-level element should be a Node");
|
||||||
}
|
}
|
||||||
assert_eq!(vis.styles.len(), 1);
|
assert_eq!(vis.styles.len(), 2);
|
||||||
let styles = vis.get_styles("top");
|
let styles = vis.get_styles("top");
|
||||||
assert_eq!(styles.len(), 1);
|
assert_eq!(styles.len(), 2);
|
||||||
assert_eq!(styles["orientation"], "vertical");
|
assert_eq!(styles["orientation"], "vertical"); // overrides parent style
|
||||||
} else {
|
} else {
|
||||||
panic!("Parsed structure was unexpectedly None");
|
panic!("Parsed structure was None");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ use crate::render::svglib::rect::rect;
|
||||||
use crate::render::svglib::svg::{svg, Svg};
|
use crate::render::svglib::svg::{svg, Svg};
|
||||||
use crate::render::svglib::text::text;
|
use crate::render::svglib::text::text;
|
||||||
use crate::render::Renderer;
|
use crate::render::Renderer;
|
||||||
use crate::{StyleNode, Vis, VisNode};
|
use crate::{StyleNode, Vis, VisNode, BODY, HORIZONTAL, ORIENTATION};
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
pub struct SvgRender;
|
pub struct SvgRender;
|
||||||
|
|
||||||
|
|
@ -14,14 +15,17 @@ impl Renderer for SvgRender {
|
||||||
svg.height(100);
|
svg.height(100);
|
||||||
let style = include_str!("svg_node.css");
|
let style = include_str!("svg_node.css");
|
||||||
svg.style(style);
|
svg.style(style);
|
||||||
let (w, h) = render_elements(&mut svg, &vis.structure.children, &vis.styles, 0, 0);
|
let (width, height) =
|
||||||
svg.viewbox(format!("0 0 {} {}", w, h));
|
render_elements(&vis, &mut svg, BODY, &vis.structure, &vis.styles, 0, 0);
|
||||||
|
svg.viewbox(format!("0 0 {} {}", width, height));
|
||||||
Ok(svg.to_string().into_bytes())
|
Ok(svg.to_string().into_bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_elements(
|
fn render_elements(
|
||||||
|
vis: &Vis,
|
||||||
svg: &mut Svg,
|
svg: &mut Svg,
|
||||||
|
parent: &str,
|
||||||
elements: &[VisNode],
|
elements: &[VisNode],
|
||||||
_styles: &Vec<StyleNode>,
|
_styles: &Vec<StyleNode>,
|
||||||
start_x: u32,
|
start_x: u32,
|
||||||
|
|
@ -32,11 +36,19 @@ fn render_elements(
|
||||||
let mut total_width = 0;
|
let mut total_width = 0;
|
||||||
let mut total_height = 0;
|
let mut total_height = 0;
|
||||||
let mut x = start_x;
|
let mut x = start_x;
|
||||||
let y = start_y;
|
let mut y = start_y;
|
||||||
|
|
||||||
for element in elements {
|
for element in elements {
|
||||||
let (width, height) = if !element.children.is_empty() {
|
let (width, height) = if !element.children.is_empty() {
|
||||||
render_elements(svg, &element.children, _styles, x + padding, y + padding)
|
render_elements(
|
||||||
|
vis,
|
||||||
|
svg,
|
||||||
|
element.id.as_str(),
|
||||||
|
&element.children,
|
||||||
|
_styles,
|
||||||
|
x + padding,
|
||||||
|
y + padding,
|
||||||
|
)
|
||||||
} else if let Some(label) = element.label.as_ref() {
|
} 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_width = label.lines().map(|l| l.len()).max().unwrap_or(0) as u32;
|
||||||
let label_height = label.lines().count() as u32;
|
let label_height = label.lines().count() as u32;
|
||||||
|
|
@ -45,8 +57,18 @@ fn render_elements(
|
||||||
(0, 0)
|
(0, 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
total_width += width + padding;
|
debug!(
|
||||||
total_height = u32::max(total_height, height + padding);
|
"{}:{:?}",
|
||||||
|
&element.id,
|
||||||
|
vis.get_style_value(&element.id, ORIENTATION)
|
||||||
|
);
|
||||||
|
if is_horizontal_orientation(vis, parent) {
|
||||||
|
total_width += width + padding;
|
||||||
|
total_height = u32::max(total_height, height + padding);
|
||||||
|
} else {
|
||||||
|
total_width = u32::max(total_width, width + padding);
|
||||||
|
total_height += height + padding;
|
||||||
|
}
|
||||||
|
|
||||||
svg.add(
|
svg.add(
|
||||||
rect()
|
rect()
|
||||||
|
|
@ -69,12 +91,22 @@ fn render_elements(
|
||||||
.attr("stroke-width", "1")
|
.attr("stroke-width", "1")
|
||||||
.class("node"),
|
.class("node"),
|
||||||
);
|
);
|
||||||
x += width + padding;
|
if is_horizontal_orientation(vis, parent) {
|
||||||
|
x += width + padding;
|
||||||
|
} else {
|
||||||
|
y += height + padding;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(total_width + padding, total_height + padding)
|
(total_width + padding, total_height + padding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_horizontal_orientation(vis: &Vis, id: impl Into<String>) -> bool {
|
||||||
|
let hor = vis.get_style_value(id, ORIENTATION);
|
||||||
|
let hor = hor.as_deref();
|
||||||
|
hor == Some(HORIZONTAL) || hor == None
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -120,7 +152,7 @@ mod tests {
|
||||||
// Create Vis structure
|
// Create Vis structure
|
||||||
let vis = Vis {
|
let vis = Vis {
|
||||||
styles: vec![style_node],
|
styles: vec![style_node],
|
||||||
structure: root,
|
structure: vec![root],
|
||||||
};
|
};
|
||||||
|
|
||||||
let svg_output = SvgRender {}.render(vis).unwrap();
|
let svg_output = SvgRender {}.render(vis).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,6 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ellipse() {
|
fn test_ellipse() {
|
||||||
let ellipse = ellipse().cx(0).cy(0).rx(10).ry(15);
|
let ellipse = ellipse().cx(0).cy(0).rx(10).ry(15);
|
||||||
println!("{:?}", ellipse);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
r#"<ellipse cx="0" cy="0" rx="10" ry="15" />"#,
|
r#"<ellipse cx="0" cy="0" rx="10" ry="15" />"#,
|
||||||
ellipse.to_string()
|
ellipse.to_string()
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ impl Display for Text {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
r#"<text{}{} />"#,
|
r#"<text{}>{}</text>"#,
|
||||||
self.atts.iter().map(att_to_string).collect::<String>(),
|
self.atts.iter().map(att_to_string).collect::<String>(),
|
||||||
self.text.lines().fold(String::new(), |mut output, l| {
|
self.text.lines().fold(String::new(), |mut output, l| {
|
||||||
let x: Vec<&Att> = self.atts.iter().filter(|a| a.name == "x").collect();
|
let x: Vec<&Att> = self.atts.iter().filter(|a| a.name == "x").collect();
|
||||||
|
|
@ -108,7 +108,7 @@ mod tests {
|
||||||
fn test_rect() {
|
fn test_rect() {
|
||||||
let text = text().x(0).y(0).width(10).height(10);
|
let text = text().x(0).y(0).width(10).height(10);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
r#"<text x="0" y="0" width="10" height="10" />"#,
|
r#"<text x="0" y="0" width="10" height="10"></text>"#,
|
||||||
text.to_string()
|
text.to_string()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue