use std::ffi::CString;
use std::ptr;
use graphviz_sys as sys;
use crate::error::GraphvizError;
use crate::graph::Graph;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Engine {
Dot,
Neato,
Fdp,
Sfdp,
Twopi,
Circo,
Osage,
Patchwork,
}
impl Engine {
pub(crate) fn as_cstr(&self) -> Result<CString, GraphvizError> {
let name = match self {
Engine::Dot => "dot",
Engine::Neato => "neato",
Engine::Fdp => "fdp",
Engine::Sfdp => "sfdp",
Engine::Twopi => "twopi",
Engine::Circo => "circo",
Engine::Osage => "osage",
Engine::Patchwork => "patchwork",
};
CString::new(name).map_err(|_| GraphvizError::InvalidEngine)
}
pub fn all() -> impl Iterator<Item = Engine> {
[
Engine::Dot,
Engine::Neato,
Engine::Fdp,
Engine::Sfdp,
Engine::Twopi,
Engine::Circo,
Engine::Osage,
Engine::Patchwork,
].iter().copied()
}
}
pub struct Context {
pub(crate) inner: *mut sys::GVC_t,
}
impl Context {
pub fn new() -> Result<Self, GraphvizError> {
let inner = unsafe { sys::gvContext() };
if inner.is_null() {
return Err(GraphvizError::ContextCreationFailed);
}
Ok(Context { inner })
}
pub fn new_with_plugins(builtins: bool, demand_loading: bool) -> Result<Self, GraphvizError> {
let builtins_ptr = if builtins {
&raw const sys::lt_preloaded_symbols as *const _
} else {
ptr::null()
};
let inner = unsafe {
sys::gvContextPlugins(builtins_ptr, if demand_loading { 1 } else { 0 })
};
if inner.is_null() {
return Err(GraphvizError::ContextCreationFailed);
}
Ok(Context { inner })
}
}
impl Drop for Context {
fn drop(&mut self) {
if !self.inner.is_null() {
unsafe { sys::gvFreeContext(self.inner) };
}
}
}
pub fn apply_layout(
context: &Context,
graph: &mut Graph,
engine: Engine
) -> Result<(), GraphvizError> {
let engine_cstr = engine.as_cstr()?;
let result = unsafe {
sys::gvLayout(context.inner, graph.inner, engine_cstr.as_ptr())
};
if result == 0 {
Ok(())
} else {
Err(GraphvizError::LayoutFailed)
}
}
pub fn free_layout(
context: &Context,
graph: &mut Graph,
) -> Result<(), GraphvizError> {
let result = unsafe {
sys::gvFreeLayout(context.inner, graph.inner)
};
if result == 0 {
Ok(())
} else {
Err(GraphvizError::FreeLayoutFailed)
}
}
pub struct LayoutSettings {
pub size: Option<(f64, f64)>,
pub ratio: Option<f64>,
pub rankdir: Option<String>,
pub overlap: Option<String>,
pub nodesep: Option<f64>,
pub ranksep: Option<f64>,
pub splines: Option<String>,
pub margin: Option<(f64, f64)>,
pub label: Option<String>,
pub fontname: Option<String>,
pub fontsize: Option<f64>,
pub minlen: Option<i32>,
pub orientation: Option<f64>,
pub concentrate: Option<bool>,
}
impl Default for LayoutSettings {
fn default() -> Self {
LayoutSettings {
size: None,
ratio: None,
rankdir: None,
overlap: None,
nodesep: None,
ranksep: None,
splines: None,
margin: None,
label: None,
fontname: None,
fontsize: None,
minlen: None,
orientation: None,
concentrate: None,
}
}
}
impl LayoutSettings {
pub fn new() -> Self {
Default::default()
}
pub fn apply(&self, graph: &Graph) -> Result<(), GraphvizError> {
if let Some((width, height)) = self.size {
graph.set_attribute("size", &format!("{},{}!", width, height))?;
}
if let Some(ratio) = self.ratio {
graph.set_attribute("ratio", &ratio.to_string())?;
}
if let Some(ref rankdir) = self.rankdir {
graph.set_attribute("rankdir", rankdir)?;
}
if let Some(ref overlap) = self.overlap {
graph.set_attribute("overlap", overlap)?;
}
if let Some(nodesep) = self.nodesep {
graph.set_attribute("nodesep", &nodesep.to_string())?;
}
if let Some(ranksep) = self.ranksep {
graph.set_attribute("ranksep", &ranksep.to_string())?;
}
if let Some(ref splines) = self.splines {
graph.set_attribute("splines", splines)?;
}
if let Some((x, y)) = self.margin {
graph.set_attribute("margin", &format!("{},{}", x, y))?;
}
if let Some(ref label) = self.label {
graph.set_attribute("label", label)?;
}
if let Some(ref fontname) = self.fontname {
graph.set_attribute("fontname", fontname)?;
}
if let Some(fontsize) = self.fontsize {
graph.set_attribute("fontsize", &fontsize.to_string())?;
}
if let Some(minlen) = self.minlen {
graph.set_attribute("minlen", &minlen.to_string())?;
}
if let Some(orientation) = self.orientation {
graph.set_attribute("orientation", &orientation.to_string())?;
}
if let Some(concentrate) = self.concentrate {
graph.set_attribute("concentrate", if concentrate { "true" } else { "false" })?;
}
Ok(())
}
pub fn with_size(mut self, width: f64, height: f64) -> Self {
self.size = Some((width, height));
self
}
pub fn with_ratio(mut self, ratio: f64) -> Self {
self.ratio = Some(ratio);
self
}
pub fn with_rankdir(mut self, rankdir: &str) -> Self {
self.rankdir = Some(rankdir.to_owned());
self
}
pub fn with_overlap(mut self, overlap: &str) -> Self {
self.overlap = Some(overlap.to_owned());
self
}
pub fn with_nodesep(mut self, nodesep: f64) -> Self {
self.nodesep = Some(nodesep);
self
}
pub fn with_ranksep(mut self, ranksep: f64) -> Self {
self.ranksep = Some(ranksep);
self
}
pub fn with_splines(mut self, splines: &str) -> Self {
self.splines = Some(splines.to_owned());
self
}
pub fn with_margin(mut self, x: f64, y: f64) -> Self {
self.margin = Some((x, y));
self
}
pub fn with_label(mut self, label: &str) -> Self {
self.label = Some(label.to_owned());
self
}
pub fn with_fontname(mut self, fontname: &str) -> Self {
self.fontname = Some(fontname.to_owned());
self
}
pub fn with_fontsize(mut self, fontsize: f64) -> Self {
self.fontsize = Some(fontsize);
self
}
pub fn with_minlen(mut self, minlen: i32) -> Self {
self.minlen = Some(minlen);
self
}
pub fn with_orientation(mut self, orientation: f64) -> Self {
self.orientation = Some(orientation);
self
}
pub fn with_concentrate(mut self, concentrate: bool) -> Self {
self.concentrate = Some(concentrate);
self
}
}
pub fn hierarchical_layout() -> LayoutSettings {
LayoutSettings::new()
.with_rankdir("TB")
.with_splines("spline")
.with_nodesep(0.5)
.with_ranksep(0.5)
}
pub fn left_to_right_layout() -> LayoutSettings {
LayoutSettings::new()
.with_rankdir("LR")
.with_splines("spline")
.with_nodesep(0.5)
.with_ranksep(0.5)
}
pub fn radial_layout() -> LayoutSettings {
LayoutSettings::new()
.with_overlap("false")
.with_splines("spline")
}
pub fn force_directed_layout() -> LayoutSettings {
LayoutSettings::new()
.with_overlap("prism")
.with_splines("spline")
}
pub fn circular_layout() -> LayoutSettings {
LayoutSettings::new()
.with_overlap("false")
.with_splines("spline")
}